From 3f4b5d0b26c0f9b24a25353dacf2d186b5bb8dc9 Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Mon, 10 Aug 2020 16:51:40 -0700 Subject: [PATCH 1/3] docs --- build/lib/gradio/generate_docs.py | 2 ++ build/lib/gradio/interface.py | 52 ++++++++++++++++----------- build/lib/gradio/networking.py | 58 ++++++++++++++++++++----------- gradio/generate_docs.py | 2 ++ gradio/interface.py | 14 +++----- 5 files changed, 77 insertions(+), 51 deletions(-) diff --git a/build/lib/gradio/generate_docs.py b/build/lib/gradio/generate_docs.py index eed6d5496e..6cce3115e9 100644 --- a/build/lib/gradio/generate_docs.py +++ b/build/lib/gradio/generate_docs.py @@ -9,6 +9,8 @@ def get_params(func): params_doc = [] documented_params = {"self"} for param_line in params_str.split("\n")[1:]: + if param_line.strip() == "Returns": + break space_index = param_line.index(" ") colon_index = param_line.index(":") name = param_line[:space_index] diff --git a/build/lib/gradio/interface.py b/build/lib/gradio/interface.py index 1214f11b6e..13dd8ded21 100644 --- a/build/lib/gradio/interface.py +++ b/build/lib/gradio/interface.py @@ -21,7 +21,6 @@ import weakref import analytics import os - PKG_VERSION_URL = "https://gradio.app/api/pkg-version" analytics.write_key = "uxIFddIEuuUcFLf9VgH2teTEtPlWdkNy" analytics_url = 'https://api.gradio.app/' @@ -48,7 +47,7 @@ class Interface: def __init__(self, fn, inputs, outputs, verbose=False, examples=None, live=False, show_input=True, show_output=True, capture_session=False, title=None, description=None, - thumbnail=None, server_port=None, server_name=networking.LOCALHOST_NAME, + thumbnail=None, server_port=None, server_name=networking.LOCALHOST_NAME, allow_screenshot=True, allow_flagging=True, flagging_dir="flagged"): """ @@ -69,6 +68,7 @@ class Interface: allow_flagging (bool): if False, users will not see a button to flag an input and output. flagging_dir (str): what to name the dir where flagged data is stored. """ + def get_input_instance(iface): if isinstance(iface, str): shortcut = InputComponent.get_all_shortcut_implementations()[iface] @@ -90,6 +90,7 @@ class Interface: "Output interface must be of type `str` or " "`OutputComponent`" ) + if isinstance(inputs, list): self.input_interfaces = [get_input_instance(i) for i in inputs] else: @@ -135,7 +136,7 @@ class Interface: try: import tensorflow as tf self.session = tf.get_default_graph(), \ - tf.keras.backend.get_session() + tf.keras.backend.get_session() except (ImportError, AttributeError): # If they are using TF >= 2.0 or don't have TF, # just ignore this. @@ -151,7 +152,7 @@ class Interface: "_{}".format(index)): index += 1 self.flagging_dir = self.flagging_dir + "/" + dir_name + \ - "_{}".format(index) + "_{}".format(index) try: requests.post(analytics_url + 'gradio-initiated-analytics/', @@ -188,8 +189,8 @@ class Interface: iface[1]["label"] = ret_name except ValueError: pass - - return config + + return config def process(self, raw_input): """ @@ -208,7 +209,7 @@ class Interface: durations = [] for predict_fn in self.predict: start = time.time() - if self.capture_session and not(self.session is None): + if self.capture_session and not (self.session is None): graph, sess = self.session with graph.as_default(): with sess.as_default(): @@ -238,31 +239,35 @@ class Interface: return processed_output, durations def close(self): - if self.simple_server and not(self.simple_server.fileno() == -1): # checks to see if server is running + if self.simple_server and not (self.simple_server.fileno() == -1): # checks to see if server is running print("Closing Gradio server on port {}...".format(self.server_port)) networking.close_server(self.simple_server) + def run_until_interrupted(self, thread, path_to_local_server): + try: + while 1: + pass + except (KeyboardInterrupt, OSError): + print("Keyboard interruption in main thread... closing server.") + thread.keep_running = False + networking.url_ok(path_to_local_server) + def launch(self, inline=None, inbrowser=None, share=False, debug=False): """ Parameters - inline (bool): whether to display in the interface inline on python - notebooks. - inbrowser (bool): whether to automatically launch the interface in a - new tab on the default browser. - share (bool): whether to create a publicly shareable link from - your computer for the interface. - debug (bool): if True, and the interface was launched from Google - Colab, prints the errors in the cell output. - :returns + inline (bool): whether to display in the interface inline on python notebooks. + inbrowser (bool): whether to automatically launch the interface in a new tab on the default browser. + share (bool): whether to create a publicly shareable link from your computer for the interface. + debug (bool): if True, and the interface was launched from Google Colab, prints the errors in the cell output. + Returns httpd (str): HTTPServer object path_to_local_server (str): Locally accessible link share_url (str): Publicly accessible link (if share=True) """ - output_directory = tempfile.mkdtemp() # Set up a port to serve the directory containing the static files with interface. - server_port, httpd = networking.start_simple_server(self, output_directory, self.server_name, - server_port=self.server_port) + server_port, httpd, thread = networking.start_simple_server( + self, output_directory, self.server_name, server_port=self.server_port) path_to_local_server = "http://{}:{}/".format(self.server_name, server_port) networking.build_template(output_directory) @@ -277,7 +282,7 @@ class Interface: print("IMPORTANT: You are using gradio version {}, " "however version {} " "is available, please upgrade.".format( - current_pkg_version, latest_pkg_version)) + current_pkg_version, latest_pkg_version)) print('--------') except: # TODO(abidlabs): don't catch all exceptions pass @@ -370,6 +375,11 @@ class Interface: data=data) except requests.ConnectionError: pass # do not push analytics if no network + + is_in_interactive_mode = bool(getattr(sys, 'ps1', sys.flags.interactive)) + if not is_in_interactive_mode: + self.run_until_interrupted(thread, path_to_local_server) + return httpd, path_to_local_server, share_url diff --git a/build/lib/gradio/networking.py b/build/lib/gradio/networking.py index 1c013b193a..4e518a1e94 100644 --- a/build/lib/gradio/networking.py +++ b/build/lib/gradio/networking.py @@ -9,6 +9,7 @@ from http.server import HTTPServer as BaseHTTPServer, SimpleHTTPRequestHandler import pkg_resources from distutils import dir_util from gradio import inputs, outputs +import time import json from gradio.tunneling import create_tunnel import urllib.request @@ -16,6 +17,8 @@ from shutil import copyfile import requests import sys import analytics +import csv + 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. @@ -183,18 +186,32 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n os.makedirs(interface.flagging_dir, exist_ok=True) output = {'inputs': [interface.input_interfaces[ i].rebuild( - interface.flagging_dir, msg['data']['input_data']) for i + interface.flagging_dir, msg['data']['input_data'][i]) for i in range(len(interface.input_interfaces))], 'outputs': [interface.output_interfaces[ i].rebuild( - interface.flagging_dir, msg['data']['output_data']) for i + interface.flagging_dir, msg['data']['output_data'][i]) + for i in range(len(interface.output_interfaces))]} - with open("{}/log.txt".format(interface.flagging_dir), - 'a+') as f: - f.write(json.dumps(output)) - f.write("\n") + log_fp = "{}/log.csv".format(interface.flagging_dir) + is_new = not os.path.exists(log_fp) + + with open(log_fp, "a") as csvfile: + headers = ["input_{}".format(i) for i in range(len( + output["inputs"]))] + ["output_{}".format(i) for i in + range(len(output["outputs"]))] + writer = csv.DictWriter(csvfile, delimiter=',', + lineterminator='\n', + fieldnames=headers) + if is_new: + writer.writeheader() + + writer.writerow( + dict(zip(headers, output["inputs"] + + output["outputs"])) + ) else: self.send_error(404, 'Path not found: {}'.format(self.path)) @@ -205,22 +222,21 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n self.base_path = base_path BaseHTTPServer.__init__(self, server_address, RequestHandlerClass) + class QuittableHTTPThread(threading.Thread): + def __init__(self, httpd): + super().__init__(daemon=False) + self.httpd = httpd + self.keep_running =True + + def run(self): + while self.keep_running: + self.httpd.handle_request() + httpd = HTTPServer(directory_to_serve, (server_name, port)) - - # Now loop forever - def serve_forever(): - try: - while True: - sys.stdout.flush() - httpd.serve_forever() - except (KeyboardInterrupt, OSError): - httpd.shutdown() - httpd.server_close() - - thread = threading.Thread(target=serve_forever, daemon=False) + thread = QuittableHTTPThread(httpd=httpd) thread.start() - return httpd + return httpd, thread def start_simple_server(interface, directory_to_serve=None, server_name=None, server_port=None): @@ -229,8 +245,8 @@ def start_simple_server(interface, directory_to_serve=None, server_name=None, se port = get_first_available_port( server_port, server_port + TRY_NUM_PORTS ) - httpd = serve_files_in_background(interface, port, directory_to_serve, server_name) - return port, httpd + httpd, thread = serve_files_in_background(interface, port, directory_to_serve, server_name) + return port, httpd, thread def close_server(server): diff --git a/gradio/generate_docs.py b/gradio/generate_docs.py index eed6d5496e..6cce3115e9 100644 --- a/gradio/generate_docs.py +++ b/gradio/generate_docs.py @@ -9,6 +9,8 @@ def get_params(func): params_doc = [] documented_params = {"self"} for param_line in params_str.split("\n")[1:]: + if param_line.strip() == "Returns": + break space_index = param_line.index(" ") colon_index = param_line.index(":") name = param_line[:space_index] diff --git a/gradio/interface.py b/gradio/interface.py index 62ca6ab0b0..13dd8ded21 100644 --- a/gradio/interface.py +++ b/gradio/interface.py @@ -255,15 +255,11 @@ class Interface: def launch(self, inline=None, inbrowser=None, share=False, debug=False): """ Parameters - inline (bool): whether to display in the interface inline on python - notebooks. - inbrowser (bool): whether to automatically launch the interface in a - new tab on the default browser. - share (bool): whether to create a publicly shareable link from - your computer for the interface. - debug (bool): if True, and the interface was launched from Google - Colab, prints the errors in the cell output. - :returns + inline (bool): whether to display in the interface inline on python notebooks. + inbrowser (bool): whether to automatically launch the interface in a new tab on the default browser. + share (bool): whether to create a publicly shareable link from your computer for the interface. + debug (bool): if True, and the interface was launched from Google Colab, prints the errors in the cell output. + Returns httpd (str): HTTPServer object path_to_local_server (str): Locally accessible link share_url (str): Publicly accessible link (if share=True) From 105691749537676ffbcd9be58b1df2ef6e675e1b Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Thu, 13 Aug 2020 12:59:23 -0700 Subject: [PATCH 2/3] add CORS --- build/lib/gradio/cors.py | 43 +++++++++++++++++++++++++++++++ build/lib/gradio/generate_docs.py | 2 ++ build/lib/gradio/inputs.py | 2 +- build/lib/gradio/interface.py | 2 -- build/lib/gradio/networking.py | 6 +++++ gradio/generate_docs.py | 2 ++ gradio/inputs.py | 2 +- gradio/interface.py | 2 -- gradio/networking.py | 6 +++++ 9 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 build/lib/gradio/cors.py diff --git a/build/lib/gradio/cors.py b/build/lib/gradio/cors.py new file mode 100644 index 0000000000..7b153b9ab4 --- /dev/null +++ b/build/lib/gradio/cors.py @@ -0,0 +1,43 @@ +import SimpleHTTPServer +class CORSHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def send_head(self): + """Common code for GET and HEAD commands. + This sends the response code and MIME headers. + Return value is either a file object (which has to be copied + to the outputfile by the caller unless the command was HEAD, + and must be closed by the caller under all circumstances), or + None, in which case the caller has nothing further to do. + """ + path = self.translate_path(self.path) + f = None + if os.path.isdir(path): + if not self.path.endswith('/'): + # redirect browser - doing basically what apache does + self.send_response(301) + self.send_header("Location", self.path + "/") + self.end_headers() + return None + for index in "index.html", "index.htm": + index = os.path.join(path, index) + if os.path.exists(index): + path = index + break + else: + return self.list_directory(path) + ctype = self.guess_type(path) + try: + # Always read in binary mode. Opening files in text mode may cause + # newline translations, making the actual size of the content + # transmitted *less* than the content-length! + f = open(path, 'rb') + except IOError: + self.send_error(404, "File not found") + return None + self.send_response(200) + self.send_header("Content-type", ctype) + fs = os.fstat(f.fileno()) + self.send_header("Content-Length", str(fs[6])) + self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + return f diff --git a/build/lib/gradio/generate_docs.py b/build/lib/gradio/generate_docs.py index 6cce3115e9..30a798666b 100644 --- a/build/lib/gradio/generate_docs.py +++ b/build/lib/gradio/generate_docs.py @@ -39,6 +39,8 @@ def document(cls_set): inp = {} inp["name"] = cls.__name__ doc = inspect.getdoc(cls) + if doc.startswith("DEPRECATED"): + continue inp["doc"] = "\n".join(doc.split("\n")[:-1]) inp["type"] = doc.split("\n")[-1].split("type: ")[-1] inp["params"], inp["params_doc"] = get_params(cls.__init__) diff --git a/build/lib/gradio/inputs.py b/build/lib/gradio/inputs.py index 9e29b64ea5..401c8cda3e 100644 --- a/build/lib/gradio/inputs.py +++ b/build/lib/gradio/inputs.py @@ -206,7 +206,7 @@ class Radio(InputComponent): class Dropdown(InputComponent): """ Component creates a dropdown of which only one can be selected. Provides string representing selected choice as an argument to the wrapped function. - Input type: str + Input type: Union[str, int] """ def __init__(self, choices, type="value", label=None): diff --git a/build/lib/gradio/interface.py b/build/lib/gradio/interface.py index 13dd8ded21..62e2ca0185 100644 --- a/build/lib/gradio/interface.py +++ b/build/lib/gradio/interface.py @@ -58,8 +58,6 @@ class Interface: verbose (bool): whether to print detailed information during launch. examples (List[List[Any]]): sample inputs for the function; if provided, appears below the UI components and can be used to populate the interface. Should be nested list, in which the outer list consists of samples and each inner list consists of an input corresponding to each input component. live (bool): whether the interface should automatically reload on change. - show_input (bool): if False, removes the input from the interface and underlays it in the output. - show_output (bool): if False, removes the output from the interface and overlays it in the input. capture_session (bool): if True, captures the default graph and session (needed for Tensorflow 1.x) title (str): a title for the interface; if provided, appears above the input and output components. description (str): a description for the interface; if provided, appears above the input and output components. diff --git a/build/lib/gradio/networking.py b/build/lib/gradio/networking.py index 4e518a1e94..858e148c0f 100644 --- a/build/lib/gradio/networking.py +++ b/build/lib/gradio/networking.py @@ -151,6 +151,12 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n self.send_header("Content-type", "application/json") self.end_headers() + def end_headers(self): + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST') + self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate') + return super(HTTPHandler, self).end_headers() + def translate_path(self, path): path = SimpleHTTPRequestHandler.translate_path(self, path) relpath = os.path.relpath(path, os.getcwd()) diff --git a/gradio/generate_docs.py b/gradio/generate_docs.py index 6cce3115e9..30a798666b 100644 --- a/gradio/generate_docs.py +++ b/gradio/generate_docs.py @@ -39,6 +39,8 @@ def document(cls_set): inp = {} inp["name"] = cls.__name__ doc = inspect.getdoc(cls) + if doc.startswith("DEPRECATED"): + continue inp["doc"] = "\n".join(doc.split("\n")[:-1]) inp["type"] = doc.split("\n")[-1].split("type: ")[-1] inp["params"], inp["params_doc"] = get_params(cls.__init__) diff --git a/gradio/inputs.py b/gradio/inputs.py index 9e29b64ea5..401c8cda3e 100644 --- a/gradio/inputs.py +++ b/gradio/inputs.py @@ -206,7 +206,7 @@ class Radio(InputComponent): class Dropdown(InputComponent): """ Component creates a dropdown of which only one can be selected. Provides string representing selected choice as an argument to the wrapped function. - Input type: str + Input type: Union[str, int] """ def __init__(self, choices, type="value", label=None): diff --git a/gradio/interface.py b/gradio/interface.py index 13dd8ded21..62e2ca0185 100644 --- a/gradio/interface.py +++ b/gradio/interface.py @@ -58,8 +58,6 @@ class Interface: verbose (bool): whether to print detailed information during launch. examples (List[List[Any]]): sample inputs for the function; if provided, appears below the UI components and can be used to populate the interface. Should be nested list, in which the outer list consists of samples and each inner list consists of an input corresponding to each input component. live (bool): whether the interface should automatically reload on change. - show_input (bool): if False, removes the input from the interface and underlays it in the output. - show_output (bool): if False, removes the output from the interface and overlays it in the input. capture_session (bool): if True, captures the default graph and session (needed for Tensorflow 1.x) title (str): a title for the interface; if provided, appears above the input and output components. description (str): a description for the interface; if provided, appears above the input and output components. diff --git a/gradio/networking.py b/gradio/networking.py index 4e518a1e94..858e148c0f 100644 --- a/gradio/networking.py +++ b/gradio/networking.py @@ -151,6 +151,12 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n self.send_header("Content-type", "application/json") self.end_headers() + def end_headers(self): + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST') + self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate') + return super(HTTPHandler, self).end_headers() + def translate_path(self, path): path = SimpleHTTPRequestHandler.translate_path(self, path) relpath = os.path.relpath(path, os.getcwd()) From dfeedee9c458662c204755abf7d4076ed4c878b9 Mon Sep 17 00:00:00 2001 From: Ali Abid Date: Fri, 14 Aug 2020 11:10:49 -0700 Subject: [PATCH 3/3] allow cache --- gradio/networking.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gradio/networking.py b/gradio/networking.py index 2f7e12c54e..15879b1634 100644 --- a/gradio/networking.py +++ b/gradio/networking.py @@ -135,7 +135,6 @@ def serve_files_in_background(interface, port, directory_to_serve=None, server_n def end_headers(self): self.send_header('Access-Control-Allow-Origin', '*') self.send_header('Access-Control-Allow-Methods', 'GET, POST') - self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate') return super(HTTPHandler, self).end_headers() def translate_path(self, path):