Spaces:
Sleeping
Sleeping
| """ Functions related to analytics and telemetry. """ | |
| from __future__ import annotations | |
| import asyncio | |
| import json | |
| import os | |
| import pkgutil | |
| import threading | |
| import urllib.parse | |
| import warnings | |
| from distutils.version import StrictVersion | |
| from typing import Any | |
| import requests | |
| import gradio | |
| from gradio import wasm_utils | |
| from gradio.context import Context | |
| from gradio.utils import GRADIO_VERSION | |
| # For testability, we import the pyfetch function into this module scope and define a fallback coroutine object to be patched in tests. | |
| try: | |
| from pyodide.http import pyfetch as pyodide_pyfetch # type: ignore | |
| except ImportError: | |
| async def pyodide_pyfetch(*args, **kwargs): | |
| raise NotImplementedError( | |
| "pyodide.http.pyfetch is not available in this environment." | |
| ) | |
| ANALYTICS_URL = "https://api.gradio.app/" | |
| PKG_VERSION_URL = "https://api.gradio.app/pkg-version" | |
| def analytics_enabled() -> bool: | |
| """ | |
| Returns: True if analytics are enabled, False otherwise. | |
| """ | |
| return os.getenv("GRADIO_ANALYTICS_ENABLED", "True") == "True" | |
| def _do_analytics_request(url: str, data: dict[str, Any]) -> None: | |
| if wasm_utils.IS_WASM: | |
| asyncio.ensure_future( | |
| _do_wasm_analytics_request( | |
| url=url, | |
| data=data, | |
| ) | |
| ) | |
| else: | |
| threading.Thread( | |
| target=_do_normal_analytics_request, | |
| kwargs={ | |
| "url": url, | |
| "data": data, | |
| }, | |
| ).start() | |
| def _do_normal_analytics_request(url: str, data: dict[str, Any]) -> None: | |
| data["ip_address"] = get_local_ip_address() | |
| try: | |
| requests.post(url, data=data, timeout=5) | |
| except (requests.ConnectionError, requests.exceptions.ReadTimeout): | |
| pass # do not push analytics if no network | |
| async def _do_wasm_analytics_request(url: str, data: dict[str, Any]) -> None: | |
| data["ip_address"] = await get_local_ip_address_wasm() | |
| # We use urllib.parse.urlencode to encode the data as a form. | |
| # Ref: https://docs.python.org/3/library/urllib.request.html#urllib-examples | |
| body = urllib.parse.urlencode(data).encode("ascii") | |
| headers = { | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| } | |
| try: | |
| await asyncio.wait_for( | |
| pyodide_pyfetch(url, method="POST", headers=headers, body=body), | |
| timeout=5, | |
| ) | |
| except asyncio.TimeoutError: | |
| pass # do not push analytics if no network | |
| def version_check(): | |
| try: | |
| version_data = pkgutil.get_data(__name__, "version.txt") | |
| if not version_data: | |
| raise FileNotFoundError | |
| current_pkg_version = version_data.decode("ascii").strip() | |
| latest_pkg_version = requests.get(url=PKG_VERSION_URL, timeout=3).json()[ | |
| "version" | |
| ] | |
| if StrictVersion(latest_pkg_version) > StrictVersion(current_pkg_version): | |
| print( | |
| f"IMPORTANT: You are using gradio version {current_pkg_version}, " | |
| f"however version {latest_pkg_version} is available, please upgrade." | |
| ) | |
| print("--------") | |
| except json.decoder.JSONDecodeError: | |
| warnings.warn("unable to parse version details from package URL.") | |
| except KeyError: | |
| warnings.warn("package URL does not contain version info.") | |
| except Exception: | |
| pass | |
| def get_local_ip_address() -> str: | |
| """ | |
| Gets the public IP address or returns the string "No internet connection" if unable | |
| to obtain it or the string "Analytics disabled" if a user has disabled analytics. | |
| Does not make a new request if the IP address has already been obtained in the | |
| same Python session. | |
| """ | |
| if not analytics_enabled(): | |
| return "Analytics disabled" | |
| if Context.ip_address is None: | |
| try: | |
| ip_address = requests.get( | |
| "https://checkip.amazonaws.com/", timeout=3 | |
| ).text.strip() | |
| except (requests.ConnectionError, requests.exceptions.ReadTimeout): | |
| ip_address = "No internet connection" | |
| Context.ip_address = ip_address | |
| else: | |
| ip_address = Context.ip_address | |
| return ip_address | |
| async def get_local_ip_address_wasm() -> str: | |
| """The Wasm-compatible version of get_local_ip_address().""" | |
| if not analytics_enabled(): | |
| return "Analytics disabled" | |
| if Context.ip_address is None: | |
| try: | |
| response = await asyncio.wait_for( | |
| pyodide_pyfetch( | |
| # The API used by the normal version (`get_local_ip_address()`), `https://checkip.amazonaws.com/``, blocks CORS requests, so here we use a different API. | |
| "https://api.ipify.org" | |
| ), | |
| timeout=5, | |
| ) | |
| response_text: str = await response.string() # type: ignore | |
| ip_address = response_text.strip() | |
| except (asyncio.TimeoutError, OSError): | |
| ip_address = "No internet connection" | |
| Context.ip_address = ip_address | |
| else: | |
| ip_address = Context.ip_address | |
| return ip_address | |
| def initiated_analytics(data: dict[str, Any]) -> None: | |
| if not analytics_enabled(): | |
| return | |
| _do_analytics_request( | |
| url=f"{ANALYTICS_URL}gradio-initiated-analytics/", | |
| data=data, | |
| ) | |
| def launched_analytics(blocks: gradio.Blocks, data: dict[str, Any]) -> None: | |
| if not analytics_enabled(): | |
| return | |
| blocks_telemetry, inputs_telemetry, outputs_telemetry, targets_telemetry = ( | |
| [], | |
| [], | |
| [], | |
| [], | |
| ) | |
| from gradio.blocks import BlockContext | |
| for x in list(blocks.blocks.values()): | |
| blocks_telemetry.append(x.get_block_name()) if isinstance( | |
| x, BlockContext | |
| ) else blocks_telemetry.append(str(x)) | |
| for x in blocks.dependencies: | |
| targets_telemetry = targets_telemetry + [ | |
| # Sometimes the target can be the Blocks object itself, so we need to check if its in blocks.blocks | |
| str(blocks.blocks[y]) | |
| for y in x["targets"] | |
| if y in blocks.blocks | |
| ] | |
| inputs_telemetry = inputs_telemetry + [ | |
| str(blocks.blocks[y]) for y in x["inputs"] if y in blocks.blocks | |
| ] | |
| outputs_telemetry = outputs_telemetry + [ | |
| str(blocks.blocks[y]) for y in x["outputs"] if y in blocks.blocks | |
| ] | |
| additional_data = { | |
| "version": GRADIO_VERSION, | |
| "is_kaggle": blocks.is_kaggle, | |
| "is_sagemaker": blocks.is_sagemaker, | |
| "using_auth": blocks.auth is not None, | |
| "dev_mode": blocks.dev_mode, | |
| "show_api": blocks.show_api, | |
| "show_error": blocks.show_error, | |
| "title": blocks.title, | |
| "inputs": blocks.input_components | |
| if blocks.mode == "interface" | |
| else inputs_telemetry, | |
| "outputs": blocks.output_components | |
| if blocks.mode == "interface" | |
| else outputs_telemetry, | |
| "targets": targets_telemetry, | |
| "blocks": blocks_telemetry, | |
| "events": [str(x["trigger"]) for x in blocks.dependencies], | |
| "is_wasm": wasm_utils.IS_WASM, | |
| } | |
| data.update(additional_data) | |
| _do_analytics_request(url=f"{ANALYTICS_URL}gradio-launched-telemetry/", data=data) | |
| def integration_analytics(data: dict[str, Any]) -> None: | |
| if not analytics_enabled(): | |
| return | |
| _do_analytics_request( | |
| url=f"{ANALYTICS_URL}gradio-integration-analytics/", | |
| data=data, | |
| ) | |
| def error_analytics(message: str) -> None: | |
| """ | |
| Send error analytics if there is network | |
| Parameters: | |
| message: Details about error | |
| """ | |
| if not analytics_enabled(): | |
| return | |
| data = {"error": message} | |
| _do_analytics_request( | |
| url=f"{ANALYTICS_URL}gradio-error-analytics/", | |
| data=data, | |
| ) | |