Adding API docs to all interfaces (#309)

* api view with basic html

* added base docs for raw inputs/outputs

* reading correct url from frontend

* styling

* fill in the blank request snippet

* post a request from docs

* post button and random generator

* styling

* added view the api to interface

* style changes

* corrected input output docs

* prefill with example instead of random

* added curl and javascript syntax

* removed scrollbars

* API doc fixes

* added correct docs to all pre/post processing methods

* updated to new doc style

* live demo with sample inputs

* fixing golden screenshots

* correct timeseries preprocess doc

* correct timeseries preprocess doc

* correct timeseries preprocess doc

* fixed overwrite issue

* remove static from git

* fix merge

* fix tests

* fix tests

* fix tests

Co-authored-by: Ali Abid <you@example.comgit>
This commit is contained in:
Ali Abdalla 2021-10-27 11:49:14 -07:00 committed by GitHub
parent f15a033232
commit 9bf16c2211
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 787 additions and 50 deletions

View File

@ -18,6 +18,7 @@ jobs:
. venv/bin/activate
pip install --upgrade pip
pip install -r gradio.egg-info/requires.txt
pip install shap IPython
pip install selenium==4.0.0a6.post2 coverage
- run:
command: |

2
.gitignore vendored
View File

@ -32,7 +32,7 @@ demo/flagged
test.txt
build/
gradio/launches.json
gradio/frontend/static
gradio/templates/frontend/static
workspace.code-workspace
*.db
demo/files/*.avi

View File

@ -1,2 +1,2 @@
recursive-include gradio/frontend *
recursive-include gradio/templates *
include gradio/version.txt

View File

@ -29,7 +29,7 @@ module.exports = {
webpackConfig.entry = "./src/index";
webpackConfig.output = {
publicPath: "",
path: path.resolve(__dirname, "../gradio/frontend"),
path: path.resolve(__dirname, "../gradio/templates/frontend"),
filename: "static/bundle.js",
chunkFilename: "static/js/[name].chunk.js"
};

View File

@ -39,15 +39,19 @@ export class GradioPage extends React.Component {
false
)}
</div>
<a href="/api/" target="_blank" class="footer" rel="noreferrer">
<span>view the api </span><img class="logo" src="https://i.ibb.co/6DVLqmf/noun-tools-2220412.png" alt="logo"/>
<span> |</span>
<a
href="https://gradio.app"
target="_blank"
className="footer"
rel="noreferrer"
>
<span>built with</span>
<span> built with</span>
<img className="logo" src={logo} alt="logo" />
</a>
</a>
</div>
</div>
);
@ -335,6 +339,7 @@ export class GradioInterface extends React.Component {
<button className="panel_button submit" onClick={this.submit}>
Submit
</button>
)}
</div>
</div>

View File

@ -19,6 +19,9 @@
.footer {
@apply flex-shrink-0 flex gap-1 items-center text-gray-400 dark:text-gray-50 justify-center py-2;
}
.api {
@apply flex-shrink-0 flex gap-1 items-center text-gray-400 dark:text-gray-50 justify-end py-2;
}
.logo {
@apply h-6;
}

View File

@ -1,4 +1,4 @@
Metadata-Version: 2.1
Metadata-Version: 1.0
Name: gradio
Version: 2.3.9
Summary: Python library for easily interacting with trained machine learning models
@ -6,9 +6,6 @@ Home-page: https://github.com/gradio-app/gradio-UI
Author: Abubakar Abid
Author-email: a12d@stanford.edu
License: Apache License 2.0
Description: UNKNOWN
Keywords: machine learning,visualization,reproducibility
Platform: UNKNOWN
License-File: LICENSE
UNKNOWN

View File

@ -1,17 +1,15 @@
numpy
pydub
matplotlib
pandas
pillow
ffmpy
markdown2
pycryptodome
requests
paramiko
analytics-python
Flask>=1.1.1
Flask-Cors>=3.0.8
flask-cachebuster
Flask-Login
IPython
shap
Flask>=1.1.1
analytics-python
ffmpy
flask-cachebuster
markdown2
matplotlib
numpy
pandas
paramiko
pillow
pycryptodome
pydub
requests

View File

@ -78,6 +78,13 @@ class InputComponent(Component):
'''
pass
def generate_sample(self):
"""
Returns a sample value of the input that would be accepted by the api. Used for api documentation.
"""
pass
class Textbox(InputComponent):
"""
Component creates a textbox for user to enter input. Provides a string as an argument to the wrapped function.
@ -130,6 +137,10 @@ class Textbox(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (str): text input
"""
if self.type == "str":
return x
elif self.type == "number":
@ -195,10 +206,13 @@ class Textbox(InputComponent):
result.append((self.interpretation_separator, 0))
return result
def generate_sample(self):
return "Hello World"
class Number(InputComponent):
"""
Component creates a field for user to enter numeric input. Provides a nuber as an argument to the wrapped function.
Component creates a field for user to enter numeric input. Provides a number as an argument to the wrapped function.
Input type: float
Demos: tax_calculator.py, titanic_survival.py
"""
@ -228,8 +242,10 @@ class Number(InputComponent):
def preprocess(self, x):
"""
Parameters:
x (number): numeric input
Returns:
(float): Number representing function input
(float): number representing function input
"""
return float(x)
@ -275,6 +291,9 @@ class Number(InputComponent):
interpretation.insert(int(len(interpretation) / 2), [x, None])
return interpretation
def generate_sample(self):
return 1
class Slider(InputComponent):
"""
@ -319,6 +338,15 @@ class Slider(InputComponent):
"slider": {},
}
def preprocess(self, x):
"""
Parameters:
x (number): numeric input
Returns:
(number): numeric input
"""
return x
def preprocess_example(self, x):
"""
Returns:
@ -345,6 +373,9 @@ class Slider(InputComponent):
"""
return scores
def generate_sample(self):
return self.maximum
class Checkbox(InputComponent):
"""
@ -376,6 +407,15 @@ class Checkbox(InputComponent):
"checkbox": {},
}
def preprocess(self, x):
"""
Parameters:
x (bool): boolean input
Returns:
(bool): boolean input
"""
return x
def preprocess_example(self, x):
"""
Returns:
@ -402,6 +442,8 @@ class Checkbox(InputComponent):
else:
return None, scores[0]
def generate_sample(self):
return True
class CheckboxGroup(InputComponent):
"""
@ -433,6 +475,12 @@ class CheckboxGroup(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (List[str]): list of selected choices
Returns:
(Union[List[str], List[int]]): list of selected choices as strings or indices within choice list
"""
if self.type == "value":
return x
elif self.type == "index":
@ -481,6 +529,8 @@ class CheckboxGroup(InputComponent):
def restore_flagged(self, data):
return json.loads(data)
def generate_sample(self):
return self.choices
class Radio(InputComponent):
"""
@ -512,6 +562,12 @@ class Radio(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (str): selected choice
Returns:
(Union[str, int]): selected choice as string or index within choice list
"""
if self.type == "value":
return x
elif self.type == "index":
@ -539,6 +595,9 @@ class Radio(InputComponent):
scores.insert(self.choices.index(x), None)
return scores
def generate_sample(self):
return self.choices[0]
class Dropdown(InputComponent):
"""
@ -570,6 +629,12 @@ class Dropdown(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (str): selected choice
Returns:
(Union[str, int]): selected choice as string or index within choice list
"""
if self.type == "value":
return x
elif self.type == "index":
@ -597,6 +662,10 @@ class Dropdown(InputComponent):
scores.insert(self.choices.index(x), None)
return scores
def generate_sample(self):
return self.choices[0]
class Image(InputComponent):
"""
Component creates an image upload box with editing capabilities.
@ -647,6 +716,12 @@ class Image(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (str): base64 url data
Returns:
(Union[numpy.array, PIL.Image, file-object]): image in requested format
"""
if x is None:
return x
im = processing_utils.decode_base64_to_image(x)
@ -791,6 +866,9 @@ class Image(InputComponent):
"""
return self.save_flagged_file(dir, label, data, encryption_key)
def generate_sample(self):
return test_data.BASE64_IMAGE
class Video(InputComponent):
"""
@ -823,9 +901,15 @@ class Video(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (Dict[name: str, data: str]): JSON object with filename as 'name' property and base64 data as 'data' property
Returns:
(str): file path to video
"""
if x is None:
return x
file_name, file_data, is_example = x["name"], x["data"], x["is_example"]
file_name, file_data, is_example = x["name"], x["data"], x.get("is_example", False)
if is_example:
file = processing_utils.create_tmp_copy_of_file(file_name)
else:
@ -857,6 +941,9 @@ class Video(InputComponent):
"""
return self.save_flagged_file(dir, label, data, encryption_key)
def generate_sample(self):
return test_data.BASE64_VIDEO
class Audio(InputComponent):
"""
@ -877,8 +964,7 @@ class Audio(InputComponent):
requires_permissions = source == "microphone"
self.type = type
self.optional = optional
self.test_input = {"name": "sample.wav",
"data": test_data.BASE64_AUDIO, "is_example": False}
self.test_input = test_data.BASE64_AUDIO
self.interpret_by_tokens = True
super().__init__(label, requires_permissions)
@ -898,6 +984,12 @@ class Audio(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (Dict[name: str, data: str]): JSON object with filename as 'name' property and base64 data as 'data' property
Returns:
(Union[Tuple[int, numpy.array], file-object, numpy.array]): audio in requested format
"""
if x is None:
return x
file_name, file_data, is_example = x["name"], x["data"], x.get("is_example", False)
@ -1023,12 +1115,15 @@ class Audio(InputComponent):
"""
return self.save_flagged_file(dir, label, data, encryption_key)
def generate_sample(self):
return test_data.BASE64_AUDIO
class File(InputComponent):
"""
Component accepts generic file uploads.
Input type: Union[file-object, bytes, List[Union[file-object, bytes]]]
Demos: zip_to_json.py, zip_two_files.py
Demos: zisp_to_json.py, zip_two_files.py
"""
def __init__(self, file_count="single", type="file", label=None, keep_filename=True, optional=False):
@ -1061,11 +1156,17 @@ class File(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (List[Dict[name: str, data: str]]): List of JSON objects with filename as 'name' property and base64 data as 'data' property
Returns:
(Union[file-object, bytes, List[Union[file-object, bytes]]]): File objects in requested format
"""
if x is None:
return None
def process_single_file(f):
file_name, data, is_example = f["name"], f["data"], f["is_example"]
file_name, data, is_example = f["name"], f["data"], f.get("is_example", False)
if self.type == "file":
if is_example:
return processing_utils.create_tmp_copy_of_file(file_name)
@ -1093,6 +1194,9 @@ class File(InputComponent):
"""
return self.save_flagged_file(dir, label, data["data"], encryption_key)
def generate_sample(self):
return test_data.BASE64_FILE
class Dataframe(InputComponent):
"""
@ -1151,6 +1255,12 @@ class Dataframe(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (List[List[Union[str, number, bool]]]): 2D array of str, numeric, or bool data
Returns:
(Union[pandas.DataFrame, numpy.array, List[Union[str, float]], List[List[Union[str, float]]]]): Dataframe in requested format
"""
if self.type == "pandas":
if self.headers:
return pd.DataFrame(x, columns=self.headers)
@ -1175,6 +1285,9 @@ class Dataframe(InputComponent):
def restore_flagged(self, data):
return json.loads(data)
def generate_sample(self):
return [[1, 2, 3], [4, 5, 6]]
class Timeseries(InputComponent):
"""
@ -1213,10 +1326,16 @@ class Timeseries(InputComponent):
}
def preprocess(self, x):
"""
Parameters:
x (Dict[data: List[List[Union[str, number, bool]]], headers: List[str], range: List[number]]): Dict with keys 'data': 2D array of str, numeric, or bool data, 'headers': list of strings for header names, 'range': optional two element list designating start of end of subrange.
Returns:
(pandas.DataFrame): Dataframe of timeseries data
"""
if x is None:
return x
dataframe = pd.DataFrame(data=x["data"], columns=x["headers"])
if x["range"] is not None:
if x.get("range") is not None:
dataframe = dataframe.loc[dataframe[self.x or 0] >= x["range"][0]]
dataframe = dataframe.loc[dataframe[self.x or 0] <= x["range"][1]]
return dataframe
@ -1230,6 +1349,11 @@ class Timeseries(InputComponent):
def restore_flagged(self, data):
return json.loads(data)
def generate_sample(self):
return {"data": [[1] + [2] * len(self.y)] * 4, "headers": [self.x] + self.y}
def get_input_instance(iface):
if isinstance(iface, str):

View File

@ -25,9 +25,9 @@ from gradio import encryptor
from gradio import queue
from functools import wraps
import io
import inspect
import traceback
INITIAL_PORT_VALUE = int(os.getenv(
'GRADIO_SERVER_PORT', "7860")) # The http server will try to open on port 7860. If not available, 7861, 7862, etc.
TRY_NUM_PORTS = int(os.getenv(
@ -37,8 +37,8 @@ LOCALHOST_NAME = os.getenv(
GRADIO_API_SERVER = "https://api.gradio.app/v1/tunnel-request"
GRADIO_FEATURE_ANALYTICS_URL = "https://api.gradio.app/gradio-feature-analytics/"
STATIC_TEMPLATE_LIB = pkg_resources.resource_filename("gradio", "frontend/")
STATIC_PATH_LIB = pkg_resources.resource_filename("gradio", "frontend/static")
STATIC_TEMPLATE_LIB = pkg_resources.resource_filename("gradio", "templates/")
STATIC_PATH_LIB = pkg_resources.resource_filename("gradio", "templates/frontend/static")
VERSION_FILE = pkg_resources.resource_filename("gradio", "version.txt")
with open(VERSION_FILE) as version_file:
GRADIO_STATIC_ROOT = "https://gradio.s3-us-west-2.amazonaws.com/" + \
@ -129,7 +129,7 @@ def get_first_available_port(initial, final):
@login_check
def main():
session["state"] = None
return render_template("index.html", config=app.interface.config)
return render_template("frontend/index.html", config=app.interface.config)
@app.route("/static/<path:path>", methods=["GET"])
@ -145,7 +145,7 @@ def static_resource(path):
def login():
if request.method == "GET":
config = get_config()
return render_template("index.html", config=config)
return render_template("frontend/index.html", config=config)
elif request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
@ -207,6 +207,49 @@ def predict():
return jsonify(output)
def get_types(cls_set, component):
docset = []
types = []
if component == "input":
for cls in cls_set:
doc = inspect.getdoc(cls.preprocess)
doc_lines = doc.split("\n")
docset.append(doc_lines[1].split(":")[-1])
types.append(doc_lines[1].split(")")[0].split("(")[-1])
else:
for cls in cls_set:
doc = inspect.getdoc(cls.postprocess)
doc_lines = doc.split("\n")
docset.append(doc_lines[-1].split(":")[-1])
types.append(doc_lines[-1].split(")")[0].split("(")[-1])
return docset, types
@app.route("/api/", methods=["GET"])
def api_docs():
inputs = [type(inp) for inp in app.interface.input_components]
outputs = [type(out) for out in app.interface.output_components]
input_types_doc, input_types = get_types(inputs, "input")
output_types_doc, output_types = get_types(outputs, "output")
input_names = [type(inp).__name__ for inp in app.interface.input_components]
output_names = [type(out).__name__ for out in app.interface.output_components]
sample_inputs = [inp.generate_sample() for inp in app.interface.input_components]
docs = {
"inputs": input_names,
"outputs": output_names,
"len_inputs": len(inputs),
"len_outputs": len(outputs),
"inputs_lower": [name.lower() for name in input_names],
"outputs_lower": [name.lower() for name in output_names],
"input_types": input_types,
"output_types": output_types,
"input_types_doc": input_types_doc,
"output_types_doc": output_types_doc,
"sample_inputs": sample_inputs
}
return render_template("api_docs.html", **docs)
def log_feature_analytics(feature):
if app.interface.analytics_enabled:
try:

View File

@ -68,6 +68,12 @@ class Textbox(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (str): text output
Returns:
(Union[str, number]): output value
"""
if self.type == "str" or self.type == "auto":
return str(y)
elif self.type == "number":
@ -98,6 +104,12 @@ class Label(OutputComponent):
super().__init__(label)
def postprocess(self, y):
"""
Parameters:
y (Dict[str, float]): dictionary mapping label to confidence value
Returns:
(Dict[label: str, confidences: List[Dict[label: str, confidence: number]]]): Object with key 'label' representing primary label, and key 'confidences' representing a list of label-confidence pairs
"""
if self.type == "label" or (self.type == "auto" and (isinstance(y, str) or isinstance(y, Number))):
return {"label": str(y)}
elif self.type == "confidences" or (self.type == "auto" and isinstance(y, dict)):
@ -190,6 +202,12 @@ class Image(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (Union[numpy.array, PIL.Image, str, matplotlib.pyplot, Tuple[Union[numpy.array, PIL.Image, str], List[Tuple[str, float, float, float, float]]]]): image in specified format
Returns:
(str): base64 url data
"""
if self.type == "auto":
if isinstance(y, np.ndarray):
dtype = "numpy"
@ -252,6 +270,12 @@ class Video(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (str): path to video
Returns:
(str): base64 url data
"""
returned_format = y.split(".")[-1].lower()
if self.type is not None and returned_format != self.type:
output_file_name = y[0: y.rindex(
@ -292,6 +316,12 @@ class KeyValues(OutputComponent):
super().__init__(label)
def postprocess(self, y):
"""
Parameters:
y (Union[Dict, List[Tuple[str, Union[str, int, float]]]]): dictionary or tuple list representing key value pairs
Returns:
(List[Tuple[str, Union[str, number]]]): list of key value pairs
"""
if isinstance(y, dict):
return list(y.items())
elif isinstance(y, list):
@ -343,6 +373,13 @@ class HighlightedText(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (Union[Dict, List[Tuple[str, Union[str, int, float]]]]): dictionary or tuple list representing key value pairs
Returns:
(List[Tuple[str, Union[str, number]]]): list of key value pairs
"""
return y
def save_flagged(self, dir, label, data, encryption_key):
@ -380,6 +417,12 @@ class Audio(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (Union[Tuple[int, numpy.array], str]): audio data in requested format
Returns:
(str): base64 url data
"""
if self.type in ["numpy", "file", "auto"]:
if self.type == "numpy" or (self.type == "auto" and isinstance(y, tuple)):
sample_rate, data = y
@ -416,6 +459,12 @@ class JSON(OutputComponent):
super().__init__(label)
def postprocess(self, y):
"""
Parameters:
y (Union[Dict, List, str]): JSON output
Returns:
(Union[Dict, List]): JSON output
"""
if isinstance(y, str):
return json.dumps(y)
else:
@ -448,6 +497,15 @@ class HTML(OutputComponent):
'''
super().__init__(label)
def postprocess(self, x):
"""
Parameters:
y (str): HTML output
Returns:
(str): HTML output
"""
return x
@classmethod
def get_shortcut_implementations(cls):
return {
@ -476,6 +534,12 @@ class File(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (str): file path
Returns:
(Dict[name: str, size: number, data: str]): JSON object with key 'name' for filename, 'data' for base64 url, and 'size' for filesize in bytes
"""
return {
"name": os.path.basename(y),
"size": os.path.getsize(y),
@ -532,6 +596,12 @@ class Dataframe(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (Union[pandas.DataFrame, numpy.array, List[Union[str, float]], List[List[Union[str, float]]]]): dataframe in given format
Returns:
(Dict[headers: List[str], data: List[List[Union[str, number]]]]): JSON object with key 'headers' for list of header names, 'data' for 2D array of string or numeric data
"""
if self.type == "auto":
if isinstance(y, pd.core.frame.DataFrame):
dtype = "pandas"
@ -589,6 +659,12 @@ class Carousel(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (List[List[Any]]): carousel output
Returns:
(List[List[Any]]): 2D array, where each sublist represents one set of outputs or 'slide' in the carousel
"""
if isinstance(y, list):
if len(y) != 0 and not isinstance(y[0], list):
y = [[z] for z in y]
@ -667,6 +743,12 @@ class Timeseries(OutputComponent):
}
def postprocess(self, y):
"""
Parameters:
y (pandas.DataFrame): timeseries data
Returns:
(Dict[headers: List[str], data: List[List[Union[str, number]]]]): JSON object with key 'headers' for list of header names, 'data' for 2D array of string or numeric data
"""
return {
"headers": y.columns.values.tolist(),
"data": y.values.tolist()

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{
"files": {
"main.css": "/static/css/main.ac4c682c.css",
"main.css": "/static/css/main.6169d032.css",
"main.js": "/static/bundle.js",
"index.html": "/index.html",
"static/media/arrow-left.e497f657.svg": "/static/media/arrow-left.e497f657.svg",
@ -10,7 +10,7 @@
},
"entrypoints": [
"static/bundle.css",
"static/css/main.ac4c682c.css",
"static/css/main.6169d032.css",
"static/bundle.js"
]
}

View File

@ -8,4 +8,4 @@
window.config = {{ config|tojson }};
} catch (e) {
window.config = {};
}</script><script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script><title>Gradio</title><link href="static/bundle.css" rel="stylesheet"><link href="static/css/main.ac4c682c.css" rel="stylesheet"></head><body style="height:100%"><div id="root" style="height:100%"></div><script src="static/bundle.js"></script></body></html>
}</script><script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.1/iframeResizer.contentWindow.min.js"></script><title>Gradio</title><link href="static/bundle.css" rel="stylesheet"><link href="static/css/main.6169d032.css" rel="stylesheet"></head><body style="height:100%"><div id="root" style="height:100%"></div><script src="static/bundle.js"></script></body></html>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

@ -89,14 +89,14 @@ class TestImage(unittest.TestCase):
class TestAudio(unittest.TestCase):
def test_as_component(self):
x_wav = {"name": "sample.wav", "data": gr.test_data.BASE64_AUDIO, "is_example": False}
x_wav = gr.test_data.BASE64_AUDIO
audio_input = gr.inputs.Audio()
output = audio_input.preprocess(x_wav)
self.assertEqual(output[0], 8000)
self.assertEqual(output[1].shape, (8046,))
def test_in_interface(self):
x_wav = {"name": "sample.wav", "data": gr.test_data.BASE64_AUDIO, "is_example": False}
x_wav = gr.test_data.BASE64_AUDIO
def max_amplitude_from_wav_file(wav_file):
audio_segment = AudioSegment.from_file(wav_file.name)
data = np.array(audio_segment.get_array_of_samples())
@ -110,11 +110,7 @@ class TestAudio(unittest.TestCase):
class TestFile(unittest.TestCase):
def test_in_interface(self):
x_file = {
"name": "audio.wav",
"data": gr.test_data.BASE64_AUDIO,
"is_example": False
}
x_file = gr.test_data.BASE64_AUDIO
def get_size_of_file(file_obj):
return os.path.getsize(file_obj.name)

View File

@ -108,7 +108,7 @@ class TestHighlightedText(unittest.TestCase):
class TestAudio(unittest.TestCase):
def test_as_component(self):
y_audio = gr.processing_utils.decode_base64_to_file(gr.test_data.BASE64_AUDIO)
y_audio = gr.processing_utils.decode_base64_to_file(gr.test_data.BASE64_AUDIO["data"])
audio_output = gr.outputs.Audio(type="file")
self.assertTrue(audio_output.postprocess(y_audio.name).startswith("data:audio/wav;base64,UklGRuI/AABXQVZFZm10IBAAA"))

View File

@ -18,7 +18,7 @@ fi
read -p "frontend updates? " -r
if [[ $REPLY =~ ^[Yy]$ ]]
then
aws s3 cp gradio/frontend s3://gradio/$new_version/ --recursive
aws s3 cp gradio/templates/frontend s3://gradio/$new_version/ --recursive
echo $new_version > gradio/version.txt
fi