From fca993027580e6869d749a74ce11e41e62c65122 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Sun, 24 Feb 2019 23:34:07 -0800 Subject: [PATCH] added networking to support linux --- Test Notebook.ipynb | 69 +++--- build/lib/gradio/__init__.py | 158 +------------- build/lib/gradio/interface.py | 201 ++++++++++++++++++ build/lib/gradio/networking.py | 114 ++++++++-- ...-any.whl => gradio-0.2.0-py3-none-any.whl} | Bin 725546 -> 727283 bytes gradio.egg-info/PKG-INFO | 2 +- gradio.egg-info/SOURCES.txt | 1 + gradio/networking.py | 18 +- setup.py | 2 +- 9 files changed, 362 insertions(+), 203 deletions(-) create mode 100644 build/lib/gradio/interface.py rename dist/{gradio-0.1.8-py3-none-any.whl => gradio-0.2.0-py3-none-any.whl} (98%) diff --git a/Test Notebook.ipynb b/Test Notebook.ipynb index 9fef82ee7b..0c590b94ce 100644 --- a/Test Notebook.ipynb +++ b/Test Notebook.ipynb @@ -2,9 +2,19 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model available publicly for 8 hours at: http://22c0b84e.ngrok.io/interface.html\n", + "The autoreload extension is already loaded. To reload it, use:\n", + " %reload_ext autoreload\n" + ] + } + ], "source": [ "%load_ext autoreload\n", "%autoreload 2\n", @@ -14,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -24,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 7, "metadata": { "scrolled": true }, @@ -43,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -52,31 +62,42 @@ "text": [ "NOTE: Gradio is in beta stage, please report all bugs to: a12d@stanford.edu\n", "Model available locally at: http://localhost:7862/interface.html\n", - "Model available publicly for 8 hours at: http://9fb08e2b.ngrok.io/interface.html\n" + "Model available publicly for 8 hours at: https://81712345.ngrok.io/interface.html\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "127.0.0.1 - - [24/Feb/2019 22:39:11] \"GET /interface.html HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:12] \"GET /js/all-io.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:12] \"GET /css/bootstrap.min.css HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:12] \"GET /css/draw-a-digit.css HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:12] \"GET /js/bootstrap.min.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:12] \"GET /js/bootstrap-notify.min.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:12] \"GET /js/textbox-input.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:12] \"GET /js/textbox-output.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:15] \"GET /interface.html HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:16] \"GET /js/bootstrap.min.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:16] \"GET /js/all-io.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:16] \"GET /css/bootstrap.min.css HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:16] \"GET /js/textbox-input.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:16] \"GET /css/draw-a-digit.css HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:16] \"GET /js/bootstrap-notify.min.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:17] \"GET /js/textbox-output.js HTTP/1.1\" 200 -\n", - "127.0.0.1 - - [24/Feb/2019 22:39:17] code 404, message File not found\n", - "127.0.0.1 - - [24/Feb/2019 22:39:17] \"GET /favicon.ico HTTP/1.1\" 404 -\n" + "127.0.0.1 - - [24/Feb/2019 23:05:10] \"GET /interface.html HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:10] \"GET /js/all-io.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:10] \"GET /css/bootstrap.min.css HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:10] \"GET /css/draw-a-digit.css HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:10] \"GET /js/bootstrap.min.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:10] \"GET /js/bootstrap-notify.min.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:10] \"GET /js/textbox-input.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:10] \"GET /js/textbox-output.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:28] \"GET /interface.html HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:29] \"GET /js/all-io.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:29] \"GET /css/bootstrap.min.css HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:30] \"GET /css/draw-a-digit.css HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:30] \"GET /js/bootstrap.min.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:30] \"GET /js/bootstrap-notify.min.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:30] \"GET /js/textbox-input.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:30] \"GET /js/textbox-output.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:05:31] code 404, message File not found\n", + "127.0.0.1 - - [24/Feb/2019 23:05:31] \"GET /favicon.ico HTTP/1.1\" 404 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:15] \"GET /interface.html HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:24] \"GET /interface.html HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:24] \"GET /js/all-io.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:24] \"GET /js/bootstrap.min.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:24] \"GET /css/bootstrap.min.css HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:24] \"GET /js/bootstrap-notify.min.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:25] \"GET /css/draw-a-digit.css HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:25] \"GET /js/textbox-input.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:25] \"GET /js/textbox-output.js HTTP/1.1\" 200 -\n", + "127.0.0.1 - - [24/Feb/2019 23:06:26] code 404, message File not found\n", + "127.0.0.1 - - [24/Feb/2019 23:06:26] \"GET /favicon.ico HTTP/1.1\" 404 -\n" ] } ], diff --git a/build/lib/gradio/__init__.py b/build/lib/gradio/__init__.py index adcf0b9675..ee12ab164f 100644 --- a/build/lib/gradio/__init__.py +++ b/build/lib/gradio/__init__.py @@ -1,157 +1 @@ -import asyncio -import websockets -import nest_asyncio -import webbrowser -import pkg_resources -from bs4 import BeautifulSoup -from gradio import inputs -from gradio import outputs -from gradio import networking -import os -import shutil -import tempfile - -nest_asyncio.apply() - -LOCALHOST_IP = '127.0.0.1' -INITIAL_WEBSOCKET_PORT = 9200 -TRY_NUM_PORTS = 100 - -BASE_TEMPLATE = pkg_resources.resource_filename('gradio', 'templates/all_io.html') -JS_PATH_LIB = pkg_resources.resource_filename('gradio', 'js/') -CSS_PATH_LIB = pkg_resources.resource_filename('gradio', 'css/') -JS_PATH_TEMP = 'js/' -CSS_PATH_TEMP = 'css/' -TEMPLATE_TEMP = 'interface.html' -BASE_JS_FILE = 'js/all-io.js' - - -class Interface(): - """ - """ - - def __init__(self, input, output, model, model_type, preprocessing_fn=None, postprocessing_fn=None): - """ - :param model_type: what kind of trained model, can be 'keras' or 'sklearn'. - :param model_obj: the model object, such as a sklearn classifier or keras model. - :param model_params: additional model parameters. - """ - self.input_interface = inputs.registry[input](preprocessing_fn) - self.output_interface = outputs.registry[output](postprocessing_fn) - self.model_type = model_type - self.model_obj = model - - def _build_template(self, temp_dir): - input_template_path = pkg_resources.resource_filename( - 'gradio', self.input_interface._get_template_path()) - output_template_path = pkg_resources.resource_filename( - 'gradio', self.output_interface._get_template_path()) - input_page = open(input_template_path) - output_page = open(output_template_path) - input_soup = BeautifulSoup(input_page.read(), features="html.parser") - output_soup = BeautifulSoup(output_page.read(), features="html.parser") - - all_io_page = open(BASE_TEMPLATE) - all_io_soup = BeautifulSoup(all_io_page.read(), features="html.parser") - input_tag = all_io_soup.find("div", {"id": "input"}) - output_tag = all_io_soup.find("div", {"id": "output"}) - - input_tag.replace_with(input_soup) - output_tag.replace_with(output_soup) - - f = open(os.path.join(temp_dir, TEMPLATE_TEMP), "w") - f.write(str(all_io_soup.prettify)) - - self._copy_files(JS_PATH_LIB, os.path.join(temp_dir, JS_PATH_TEMP)) - self._copy_files(CSS_PATH_LIB, os.path.join(temp_dir, CSS_PATH_TEMP)) - return - - def _copy_files(self, src_dir, dest_dir): - if not os.path.exists(dest_dir): - os.makedirs(dest_dir) - src_files = os.listdir(src_dir) - for file_name in src_files: - full_file_name = os.path.join(src_dir, file_name) - if os.path.isfile(full_file_name): - shutil.copy(full_file_name, dest_dir) - - def _set_socket_url_in_js(self, temp_dir, socket_url): - with open(os.path.join(temp_dir, BASE_JS_FILE)) as fin: - lines = fin.readlines() - lines[0] = 'var NGROK_URL = "{}"\n'.format(socket_url.replace('http', 'ws')) - - with open(os.path.join(temp_dir, BASE_JS_FILE), 'w') as fout: - for line in lines: - fout.write(line) - - def _set_socket_port_in_js(self, temp_dir, socket_port): - with open(os.path.join(temp_dir, BASE_JS_FILE)) as fin: - lines = fin.readlines() - lines[1] = 'var SOCKET_PORT = {}\n'.format(socket_port) - - with open(os.path.join(temp_dir, BASE_JS_FILE), 'w') as fout: - for line in lines: - fout.write(line) - - def predict(self, array): - if self.model_type=='sklearn': - return self.model_obj.predict(array) - elif self.model_type=='keras': - return self.model_obj.predict(array) - elif self.model_type=='func': - return self.model_obj(array) - else: - raise ValueError('model_type must be one of: "sklearn" or "keras" or "func".') - - async def communicate(self, websocket, path): - """ - Method that defines how this interface communicates with the websocket. - :param websocket: a Websocket object used to communicate with the interface frontend - :param path: ignored - """ - while True: - try: - msg = await websocket.recv() - processed_input = self.input_interface._pre_process(msg) - prediction = self.predict(processed_input) - processed_output = self.output_interface._post_process(prediction) - await websocket.send(str(processed_output)) - except websockets.exceptions.ConnectionClosed: - pass - - def launch(self, share_link=True): - """ - Standard method shared by interfaces that launches a websocket at a specified IP address. - """ - networking.kill_processes([4040, 4041]) - output_directory = tempfile.mkdtemp() - - server_port = networking.start_simple_server(output_directory) - path_to_server = 'http://localhost:{}/'.format(server_port) - self._build_template(output_directory) - - ports_in_use = networking.get_ports_in_use() - for i in range(TRY_NUM_PORTS): - if not ((INITIAL_WEBSOCKET_PORT + i) in ports_in_use): - break - else: - raise OSError("All ports from {} to {} are in use. Please close a port.".format( - INITIAL_WEBSOCKET_PORT, INITIAL_WEBSOCKET_PORT + TRY_NUM_PORTS)) - - start_server = websockets.serve(self.communicate, LOCALHOST_IP, INITIAL_WEBSOCKET_PORT + i) - self._set_socket_port_in_js(output_directory, INITIAL_WEBSOCKET_PORT + i) - - if share_link: - site_ngrok_url = networking.setup_ngrok(server_port) - socket_ngrok_url = networking.setup_ngrok(INITIAL_WEBSOCKET_PORT, api_url=networking.NGROK_TUNNELS_API_URL2) - self._set_socket_url_in_js(output_directory, socket_ngrok_url) - print("NOTE: Gradio is in beta stage, please report all bugs to: a12d@stanford.edu") - print("Model available locally at: {}".format(path_to_server + TEMPLATE_TEMP)) - print("Model available publicly for 8 hours at: {}".format(site_ngrok_url + '/' + TEMPLATE_TEMP)) - asyncio.get_event_loop().run_until_complete(start_server) - try: - asyncio.get_event_loop().run_forever() - except RuntimeError: # Runtime errors are thrown in jupyter notebooks because of async. - pass - - webbrowser.open(path_to_server + TEMPLATE_TEMP) +from gradio.interface import Interface # This makes Interface importable as gradio.Interface. diff --git a/build/lib/gradio/interface.py b/build/lib/gradio/interface.py new file mode 100644 index 0000000000..04b1cf628f --- /dev/null +++ b/build/lib/gradio/interface.py @@ -0,0 +1,201 @@ +import asyncio +import websockets +import nest_asyncio +import webbrowser +import pkg_resources +from bs4 import BeautifulSoup +from gradio import inputs +from gradio import outputs +from gradio import networking +import os +import shutil +import tempfile + +nest_asyncio.apply() + +LOCALHOST_IP = '127.0.0.1' +INITIAL_WEBSOCKET_PORT = 9200 +TRY_NUM_PORTS = 100 + +BASE_TEMPLATE = pkg_resources.resource_filename('gradio', 'templates/all_io.html') +JS_PATH_LIB = pkg_resources.resource_filename('gradio', 'js/') +CSS_PATH_LIB = pkg_resources.resource_filename('gradio', 'css/') +JS_PATH_TEMP = 'js/' +CSS_PATH_TEMP = 'css/' +TEMPLATE_TEMP = 'interface.html' +BASE_JS_FILE = 'js/all-io.js' + + +class Interface(): + """ + """ + + # Dictionary in which each key is a valid `model_type` argument to constructor, and the value being the description. + VALID_MODEL_TYPES = {'sklearn': 'sklearn model', 'keras': 'keras model', 'function': 'python function'} + + def __init__(self, input, output, model, model_type=None, preprocessing_fn=None, postprocessing_fn=None): + """ + :param model_type: what kind of trained model, can be 'keras' or 'sklearn'. + :param model_obj: the model object, such as a sklearn classifier or keras model. + :param model_params: additional model parameters. + """ + self.input_interface = inputs.registry[input](preprocessing_fn) + self.output_interface = outputs.registry[output](postprocessing_fn) + self.model_obj = model + if model_type is None: + model_type = self._infer_model_type(model) + if model_type is None: + raise ValueError("model_type could not be inferred, please specify parameter `model_type`") + else: + print("Model type not explicitly identified, inferred to be: {}".format( + self.VALID_MODEL_TYPES[model_type])) + elif not(model_type.lower() in self.VALID_MODEL_TYPES): + ValueError('model_type must be one of: {}'.format(self.VALID_MODEL_TYPES)) + self.model_type = model_type + + def _infer_model_type(self, model): + if callable(model): + return 'function' + + try: + import sklearn + if isinstance(model, sklearn.base.BaseEstimator): + return 'sklearn' + except ImportError: + pass + + try: + import tensorflow as tf + if isinstance(model, tf.keras.Model): + return 'keras' + except ImportError: + pass + + try: + import keras + if isinstance(model, keras.Model): + return 'keras' + except ImportError: + pass + + return None + + def _build_template(self, temp_dir): + input_template_path = pkg_resources.resource_filename( + 'gradio', self.input_interface._get_template_path()) + output_template_path = pkg_resources.resource_filename( + 'gradio', self.output_interface._get_template_path()) + input_page = open(input_template_path) + output_page = open(output_template_path) + input_soup = BeautifulSoup(input_page.read(), features="html.parser") + output_soup = BeautifulSoup(output_page.read(), features="html.parser") + + all_io_page = open(BASE_TEMPLATE) + all_io_soup = BeautifulSoup(all_io_page.read(), features="html.parser") + input_tag = all_io_soup.find("div", {"id": "input"}) + output_tag = all_io_soup.find("div", {"id": "output"}) + + input_tag.replace_with(input_soup) + output_tag.replace_with(output_soup) + + f = open(os.path.join(temp_dir, TEMPLATE_TEMP), "w") + f.write(str(all_io_soup.prettify)) + + self._copy_files(JS_PATH_LIB, os.path.join(temp_dir, JS_PATH_TEMP)) + self._copy_files(CSS_PATH_LIB, os.path.join(temp_dir, CSS_PATH_TEMP)) + return + + def _copy_files(self, src_dir, dest_dir): + if not os.path.exists(dest_dir): + os.makedirs(dest_dir) + src_files = os.listdir(src_dir) + for file_name in src_files: + full_file_name = os.path.join(src_dir, file_name) + if os.path.isfile(full_file_name): + shutil.copy(full_file_name, dest_dir) + + def _set_socket_url_in_js(self, temp_dir, socket_url): + with open(os.path.join(temp_dir, BASE_JS_FILE)) as fin: + lines = fin.readlines() + lines[0] = 'var NGROK_URL = "{}"\n'.format(socket_url.replace('http', 'ws')) + + with open(os.path.join(temp_dir, BASE_JS_FILE), 'w') as fout: + for line in lines: + fout.write(line) + + def _set_socket_port_in_js(self, temp_dir, socket_port): + with open(os.path.join(temp_dir, BASE_JS_FILE)) as fin: + lines = fin.readlines() + lines[1] = 'var SOCKET_PORT = {}\n'.format(socket_port) + + with open(os.path.join(temp_dir, BASE_JS_FILE), 'w') as fout: + for line in lines: + fout.write(line) + + def predict(self, array): + if self.model_type=='sklearn': + return self.model_obj.predict(array) + elif self.model_type=='keras': + return self.model_obj.predict(array) + elif self.model_type=='function': + return self.model_obj(array) + else: + ValueError('model_type must be one of: {}'.format(self.VALID_MODEL_TYPES)) + + async def communicate(self, websocket, path): + """ + Method that defines how this interface communicates with the websocket. + :param websocket: a Websocket object used to communicate with the interface frontend + :param path: ignored + """ + while True: + try: + msg = await websocket.recv() + processed_input = self.input_interface._pre_process(msg) + prediction = self.predict(processed_input) + processed_output = self.output_interface._post_process(prediction) + await websocket.send(str(processed_output)) + except websockets.exceptions.ConnectionClosed: + pass + + def launch(self, share_link=False, verbose=True): + """ + Standard method shared by interfaces that launches a websocket at a specified IP address. + """ + output_directory = tempfile.mkdtemp() + server_port = networking.start_simple_server(output_directory) + path_to_server = 'http://localhost:{}/'.format(server_port) + self._build_template(output_directory) + + ports_in_use = networking.get_ports_in_use(INITIAL_WEBSOCKET_PORT, INITIAL_WEBSOCKET_PORT + TRY_NUM_PORTS) + for i in range(TRY_NUM_PORTS): + if not ((INITIAL_WEBSOCKET_PORT + i) in ports_in_use): + break + else: + raise OSError("All ports from {} to {} are in use. Please close a port.".format( + INITIAL_WEBSOCKET_PORT, INITIAL_WEBSOCKET_PORT + TRY_NUM_PORTS)) + + start_server = websockets.serve(self.communicate, LOCALHOST_IP, INITIAL_WEBSOCKET_PORT + i) + self._set_socket_port_in_js(output_directory, INITIAL_WEBSOCKET_PORT + i) + if verbose: + print("NOTE: Gradio is in beta stage, please report all bugs to: a12d@stanford.edu") + print("Model available locally at: {}".format(path_to_server + TEMPLATE_TEMP)) + + if share_link: + networking.kill_processes([4040, 4041]) + site_ngrok_url = networking.setup_ngrok(server_port) + socket_ngrok_url = networking.setup_ngrok(INITIAL_WEBSOCKET_PORT, api_url=networking.NGROK_TUNNELS_API_URL2) + self._set_socket_url_in_js(output_directory, socket_ngrok_url) + if verbose: + print("Model available publicly for 8 hours at: {}".format(site_ngrok_url + '/' + TEMPLATE_TEMP)) + else: + if verbose: + print("To create a public link, set `share_link=True` in the argument to `launch()`") + + asyncio.get_event_loop().run_until_complete(start_server) + try: + asyncio.get_event_loop().run_forever() + except RuntimeError: # Runtime errors are thrown in jupyter notebooks because of async. + pass + + webbrowser.open(path_to_server + TEMPLATE_TEMP) diff --git a/build/lib/gradio/networking.py b/build/lib/gradio/networking.py index e41f55494d..f5015aefdd 100644 --- a/build/lib/gradio/networking.py +++ b/build/lib/gradio/networking.py @@ -4,11 +4,18 @@ import zipfile import io import sys import os +import socket from psutil import process_iter, AccessDenied from signal import SIGTERM # or SIGKILL +import threading +from http.server import HTTPServer as BaseHTTPServer, SimpleHTTPRequestHandler +import stat +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry INITIAL_PORT_VALUE = 7860 TRY_NUM_PORTS = 100 +LOCALHOST_NAME = 'localhost' LOCALHOST_PREFIX = 'localhost:' NGROK_TUNNELS_API_URL = "http://localhost:4040/api/tunnels" # TODO(this should be captured from output) NGROK_TUNNELS_API_URL2 = "http://localhost:4041/api/tunnels" # TODO(this should be captured from output) @@ -20,28 +27,94 @@ NGROK_ZIP_URLS = { } -def get_ports_in_use(): +def get_ports_in_use(start, stop): ports_in_use = [] - for proc in process_iter(): - for conns in proc.connections(kind='inet'): - ports_in_use.append(conns.laddr.port) + for port in range(start, stop): + try: + s = socket.socket() # create a socket object + s.bind((LOCALHOST_NAME, port)) # Bind to the port + s.close() + except OSError: + ports_in_use.append(port) return ports_in_use + # ports_in_use = [] + # try: + # for proc in process_iter(): + # for conns in proc.connections(kind='inet'): + # ports_in_use.append(conns.laddr.port) + # except AccessDenied: + # pass # TODO(abidlabs): somehow find a way to handle this issue? + # return ports_in_use + + +def serve_files_in_background(port, directory_to_serve=None): + # class Handler(http.server.SimpleHTTPRequestHandler): + # def __init__(self, *args, **kwargs): + # super().__init__(*args, directory=directory_to_serve, **kwargs) + # + # server = socketserver.ThreadingTCPServer(('localhost', port), Handler) + # # Ensures that Ctrl-C cleanly kills all spawned threads + # server.daemon_threads = True + # # Quicker rebinding + # server.allow_reuse_address = True + # + # # A custom signal handle to allow us to Ctrl-C out of the process + # def signal_handler(signal, frame): + # print('Exiting http server (Ctrl+C pressed)') + # try: + # if (server): + # server.server_close() + # finally: + # sys.exit(0) + # + # # Install the keyboard interrupt handler + # signal.signal(signal.SIGINT, signal_handler) + class HTTPHandler(SimpleHTTPRequestHandler): + """This handler uses server.base_path instead of always using os.getcwd()""" + + def translate_path(self, path): + path = SimpleHTTPRequestHandler.translate_path(self, path) + relpath = os.path.relpath(path, os.getcwd()) + fullpath = os.path.join(self.server.base_path, relpath) + return fullpath + + class HTTPServer(BaseHTTPServer): + """The main server, you pass in base_path which is the path you want to serve requests from""" + + def __init__(self, base_path, server_address, RequestHandlerClass=HTTPHandler): + self.base_path = base_path + BaseHTTPServer.__init__(self, server_address, RequestHandlerClass) + + httpd = HTTPServer(directory_to_serve, (LOCALHOST_NAME, port)) + + # Now loop forever + def serve_forever(): + try: + while True: + sys.stdout.flush() + httpd.serve_forever() + except KeyboardInterrupt: + pass + + thread = threading.Thread(target=serve_forever) + thread.start() def start_simple_server(directory_to_serve=None): # TODO(abidlabs): increment port number until free port is found - ports_in_use = get_ports_in_use() + ports_in_use = get_ports_in_use(start=INITIAL_PORT_VALUE, stop=INITIAL_PORT_VALUE + TRY_NUM_PORTS) for i in range(TRY_NUM_PORTS): if not((INITIAL_PORT_VALUE + i) in ports_in_use): break else: raise OSError("All ports from {} to {} are in use. Please close a port.".format( INITIAL_PORT_VALUE, INITIAL_PORT_VALUE + TRY_NUM_PORTS)) - if directory_to_serve is None: - subprocess.Popen(['python', '-m', 'http.server', str(INITIAL_PORT_VALUE + i)]) - else: - cmd = ' '.join(['python', '-m', 'http.server', '-d', directory_to_serve, str(INITIAL_PORT_VALUE + i)]) - subprocess.Popen(cmd, shell=True) # Doesn't seem to work if list is passed for some reason. + serve_files_in_background(INITIAL_PORT_VALUE + i, directory_to_serve) + # if directory_to_serve is None: + # subprocess.Popen(['python', '-m', 'http.server', str(INITIAL_PORT_VALUE + i)]) + # else: + # cmd = ' '.join(['python', '-m', 'http.server', '-d', directory_to_serve, str(INITIAL_PORT_VALUE + i)]) + # subprocess.Popen(cmd, shell=True) # Doesn't seem to work if list is passed for some reason. return INITIAL_PORT_VALUE + i @@ -49,19 +122,30 @@ def download_ngrok(): try: zip_file_url = NGROK_ZIP_URLS[sys.platform] except KeyError: - print("Sorry, we don't currently support your operating system, please leave us a note on GitHub, and we'll look into it!") + print("Sorry, we don't currently support your operating system, please leave us a note on GitHub, and " + "we'll look into it!") return - r = requests.get(zip_file_url) z = zipfile.ZipFile(io.BytesIO(r.content)) z.extractall() + if sys.platform == 'darwin' or sys.platform == 'linux': + st = os.stat('ngrok') + os.chmod('ngrok', st.st_mode | stat.S_IEXEC) def setup_ngrok(local_port, api_url=NGROK_TUNNELS_API_URL): if not(os.path.isfile('ngrok.exe')): download_ngrok() - subprocess.Popen(['ngrok', 'http', str(local_port)]) - r = requests.get(api_url) + if sys.platform == 'win32': + subprocess.Popen(['ngrok', 'http', str(local_port)]) + else: + subprocess.Popen(['./ngrok', 'http', str(local_port)]) + session = requests.Session() + retry = Retry(connect=3, backoff_factor=0.5) + adapter = HTTPAdapter(max_retries=retry) + session.mount('http://', adapter) + session.mount('https://', adapter) + r = session.get(api_url) for tunnel in r.json()['tunnels']: if LOCALHOST_PREFIX + str(local_port) in tunnel['config']['addr']: return tunnel['public_url'] @@ -75,6 +159,6 @@ def kill_processes(process_ids): if conns.laddr.port in process_ids: proc.send_signal(SIGTERM) # or SIGKILL except AccessDenied: - print("Unable to kill processes, please kill manually.") + pass diff --git a/dist/gradio-0.1.8-py3-none-any.whl b/dist/gradio-0.2.0-py3-none-any.whl similarity index 98% rename from dist/gradio-0.1.8-py3-none-any.whl rename to dist/gradio-0.2.0-py3-none-any.whl index 0ca5c6b439b5ec4845b9c7b02b894613191e2e2c..f30762c7664358f072e748a13199d19ab672be20 100644 GIT binary patch delta 9011 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 JAc0=B{{r`>f=>Vd delta 7311 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 V0lNnm1&M+jh(Mrn3>ez=e*n}85w-vT diff --git a/gradio.egg-info/PKG-INFO b/gradio.egg-info/PKG-INFO index dfa53c2fb6..87be5a7f0e 100644 --- a/gradio.egg-info/PKG-INFO +++ b/gradio.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: gradio -Version: 0.1.8 +Version: 0.2.0 Summary: Python library for easily interacting with trained machine learning models Home-page: https://github.com/abidlabs/gradio Author: Abubakar Abid diff --git a/gradio.egg-info/SOURCES.txt b/gradio.egg-info/SOURCES.txt index 472e90528b..a9a72989e6 100644 --- a/gradio.egg-info/SOURCES.txt +++ b/gradio.egg-info/SOURCES.txt @@ -3,6 +3,7 @@ README.md setup.py gradio/__init__.py gradio/inputs.py +gradio/interface.py gradio/networking.py gradio/outputs.py gradio/preprocessing_utils.py diff --git a/gradio/networking.py b/gradio/networking.py index 2e9201c15f..f5015aefdd 100644 --- a/gradio/networking.py +++ b/gradio/networking.py @@ -10,6 +10,8 @@ from signal import SIGTERM # or SIGKILL import threading from http.server import HTTPServer as BaseHTTPServer, SimpleHTTPRequestHandler import stat +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry INITIAL_PORT_VALUE = 7860 TRY_NUM_PORTS = 100 @@ -119,25 +121,31 @@ def start_simple_server(directory_to_serve=None): def download_ngrok(): try: zip_file_url = NGROK_ZIP_URLS[sys.platform] - print(zip_file_url) except KeyError: - print("Sorry, we don't currently support your operating system, please leave us a note on GitHub, and we'll look into it!") + print("Sorry, we don't currently support your operating system, please leave us a note on GitHub, and " + "we'll look into it!") return r = requests.get(zip_file_url) z = zipfile.ZipFile(io.BytesIO(r.content)) z.extractall() - if (sys.platform=='darwin' or sys.platform=='linux'): + if sys.platform == 'darwin' or sys.platform == 'linux': st = os.stat('ngrok') os.chmod('ngrok', st.st_mode | stat.S_IEXEC) + def setup_ngrok(local_port, api_url=NGROK_TUNNELS_API_URL): if not(os.path.isfile('ngrok.exe')): download_ngrok() - if sys.platform=='win32': + if sys.platform == 'win32': subprocess.Popen(['ngrok', 'http', str(local_port)]) else: subprocess.Popen(['./ngrok', 'http', str(local_port)]) - r = requests.get(api_url) + session = requests.Session() + retry = Retry(connect=3, backoff_factor=0.5) + adapter = HTTPAdapter(max_retries=retry) + session.mount('http://', adapter) + session.mount('https://', adapter) + r = session.get(api_url) for tunnel in r.json()['tunnels']: if LOCALHOST_PREFIX + str(local_port) in tunnel['config']['addr']: return tunnel['public_url'] diff --git a/setup.py b/setup.py index acfc36861d..1136c46cab 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ except ImportError: setup( name='gradio', - version='0.1.8', + version='0.2.0', include_package_data=True, description='Python library for easily interacting with trained machine learning models', author='Abubakar Abid',