| """ |
| Launches a diagnostic FiftyOne App for a Hugging Face Docker Space. |
| |
| The application downloads Hub datasets into ephemeral local storage, imports |
| their records into MongoDB, and creates browser-local clones on demand. |
| """ |
|
|
| import asyncio |
| import faulthandler |
| import importlib.metadata |
| import json |
| import logging |
| import os |
| import platform |
| import shutil |
| import socket |
| import sys |
| import time |
|
|
| from hypercorn.asyncio import serve |
| from hypercorn.config import Config |
| from huggingface_hub import snapshot_download |
|
|
| import fiftyone as fo |
|
|
| from gateway import create_gateway |
|
|
|
|
| EXTERNAL_APP_ADDRESS = "0.0.0.0" |
| EXTERNAL_APP_PORT = int(os.environ.get("PORT", "7860")) |
| INTERNAL_APP_ADDRESS = "127.0.0.1" |
| INTERNAL_APP_PORT = 5151 |
| MANIFEST_PATH = os.path.join(os.path.dirname(__file__), "datasets.json") |
| LOG_FORMAT = "%(asctime)s | %(levelname)s | %(name)s | %(message)s" |
|
|
| logging.basicConfig( |
| level=os.environ.get("LOG_LEVEL", "INFO").upper(), |
| format=LOG_FORMAT, |
| stream=sys.stdout, |
| force=True, |
| ) |
| logger = logging.getLogger(__name__) |
|
|
|
|
| def main(): |
| """Loads the configured datasets and serves the FiftyOne App.""" |
| faulthandler.enable(file=sys.stderr, all_threads=True) |
| faulthandler.dump_traceback_later(300, repeat=True, file=sys.stderr) |
|
|
| _log_runtime_diagnostics() |
|
|
| try: |
| manifest = _load_manifest() |
| datasets = _load_datasets(manifest) |
| session = _launch_app(datasets[manifest["default_dataset"]]) |
| except Exception: |
| logger.exception("Fatal error while starting the FiftyOne App") |
| raise |
| finally: |
| faulthandler.cancel_dump_traceback_later() |
|
|
| logger.info( |
| "FiftyOne App is ready; starting the browser session gateway" |
| ) |
| try: |
| asyncio.run(_serve_gateway(datasets, manifest)) |
| finally: |
| session.close() |
|
|
|
|
| def _load_manifest(): |
| """Loads and validates the dataset manifest.""" |
| with open(MANIFEST_PATH) as file: |
| manifest = json.load(file) |
|
|
| entries = manifest.get("datasets") |
| if not isinstance(entries, list) or not entries: |
| raise ValueError("datasets.json must contain at least one dataset") |
|
|
| required_fields = { |
| "name", |
| "repo_id", |
| "local_path", |
| "expected_samples", |
| } |
| names = [] |
| for entry in entries: |
| missing = required_fields - entry.keys() |
| if missing: |
| raise ValueError( |
| f"Dataset manifest entry is missing fields: {sorted(missing)}" |
| ) |
|
|
| if not os.path.isabs(entry["local_path"]): |
| raise ValueError( |
| f"Dataset local_path must be absolute: {entry['local_path']}" |
| ) |
|
|
| if not entry["name"] or "/" in entry["name"]: |
| raise ValueError( |
| f"Dataset name must be a nonempty path segment: {entry['name']}" |
| ) |
|
|
| if ( |
| not isinstance(entry["expected_samples"], int) |
| or entry["expected_samples"] < 0 |
| ): |
| raise ValueError( |
| "Dataset expected_samples must be a nonnegative integer: " |
| f"{entry['expected_samples']}" |
| ) |
|
|
| names.append(entry["name"]) |
|
|
| if len(names) != len(set(names)): |
| raise ValueError("Dataset names in datasets.json must be unique") |
|
|
| if manifest.get("default_dataset") not in names: |
| raise ValueError( |
| "default_dataset must match a dataset name in datasets.json" |
| ) |
|
|
| logger.info( |
| "Dataset manifest loaded: default=%s datasets=%s", |
| manifest["default_dataset"], |
| names, |
| ) |
| return manifest |
|
|
|
|
| def _load_datasets(manifest): |
| """Downloads and imports fresh datasets from the Hugging Face Hub.""" |
| logger.info("Connecting to the FiftyOne database") |
| existing_datasets = fo.list_datasets() |
| logger.info( |
| "Database connection succeeded; existing datasets=%s", |
| existing_datasets, |
| ) |
|
|
| entries = manifest["datasets"] |
| session_prefixes = tuple( |
| entry["name"] + "-session-" for entry in entries |
| ) |
| stale_clones = [ |
| name |
| for name in existing_datasets |
| if name.startswith(session_prefixes) |
| ] |
| for name in stale_clones: |
| logger.info("Deleting stale browser dataset clone '%s'", name) |
| fo.delete_dataset(name) |
|
|
| for entry in entries: |
| dataset_name = entry["name"] |
| if dataset_name in existing_datasets: |
| logger.info("Deleting stale base dataset '%s'", dataset_name) |
| fo.delete_dataset(dataset_name) |
|
|
| datasets = {} |
| for entry in entries: |
| dataset_name = entry["name"] |
| dataset_dir = entry["local_path"] |
| expected_samples = entry["expected_samples"] |
| logger.info( |
| "Downloading Hub dataset: repo=%s directory=%s", |
| entry["repo_id"], |
| dataset_dir, |
| ) |
| download_started_at = time.monotonic() |
| snapshot_download( |
| repo_id=entry["repo_id"], |
| repo_type="dataset", |
| local_dir=dataset_dir, |
| ) |
| logger.info( |
| "Hub dataset downloaded in %.2f seconds: repo=%s directory=%s", |
| time.monotonic() - download_started_at, |
| entry["repo_id"], |
| dataset_dir, |
| ) |
|
|
| logger.info( |
| "Importing local dataset: name=%s directory=%s", |
| dataset_name, |
| dataset_dir, |
| ) |
| started_at = time.monotonic() |
| dataset = fo.Dataset.from_dir( |
| dataset_dir=dataset_dir, |
| dataset_type=fo.types.FiftyOneDataset, |
| name=dataset_name, |
| persistent=True, |
| ) |
| elapsed = time.monotonic() - started_at |
|
|
| logger.info( |
| "Dataset imported in %.2f seconds: name=%s samples=%d " |
| "media_type=%s", |
| elapsed, |
| dataset.name, |
| len(dataset), |
| dataset.media_type, |
| ) |
| if len(dataset) != expected_samples: |
| raise RuntimeError( |
| f"{dataset_name} import count mismatch: expected " |
| f"{expected_samples}, found {len(dataset)}" |
| ) |
|
|
| logger.info( |
| "Dataset validation succeeded: name=%s expected_episodes=%d", |
| dataset_name, |
| expected_samples, |
| ) |
| logger.info( |
| "Dataset schema: name=%s schema=%s", |
| dataset_name, |
| dataset.get_field_schema(), |
| ) |
| logger.info( |
| "Dataset disk usage: name=%s stats=%s", |
| dataset_name, |
| dataset.stats(include_media=True), |
| ) |
|
|
| first_sample = dataset.first() |
| logger.info( |
| "First sample diagnostics: dataset=%s id=%s filepath=%s " |
| "file_exists=%s", |
| dataset_name, |
| first_sample.id, |
| first_sample.filepath, |
| os.path.isfile(first_sample.filepath), |
| ) |
| datasets[dataset_name] = dataset |
|
|
| return datasets |
|
|
|
|
| def _launch_app(dataset): |
| """Launches the internal App behind the browser session gateway.""" |
| logger.info( |
| "Launching internal FiftyOne App: address=%s port=%d remote=True", |
| INTERNAL_APP_ADDRESS, |
| INTERNAL_APP_PORT, |
| ) |
| started_at = time.monotonic() |
| session = fo.launch_app( |
| dataset, |
| address=INTERNAL_APP_ADDRESS, |
| port=INTERNAL_APP_PORT, |
| remote=True, |
| ) |
| elapsed = time.monotonic() - started_at |
| logger.info( |
| "FiftyOne session launched in %.2f seconds: url=%s server=%s:%s", |
| elapsed, |
| session.url, |
| session.server_address, |
| session.server_port, |
| ) |
| return session |
|
|
|
|
| async def _serve_gateway(datasets, manifest): |
| """Serves the external session-aware gateway.""" |
| config = Config() |
| config.bind = [f"{EXTERNAL_APP_ADDRESS}:{EXTERNAL_APP_PORT}"] |
| config.accesslog = "-" |
| config.errorlog = "-" |
| config.graceful_timeout = 10 |
| config.keep_alive_timeout = 30 |
| logger.info( |
| "Serving session gateway: address=%s port=%d", |
| EXTERNAL_APP_ADDRESS, |
| EXTERNAL_APP_PORT, |
| ) |
| shared_media_roots = [ |
| entry["local_path"] for entry in manifest["datasets"] |
| ] |
| await serve( |
| create_gateway( |
| datasets, |
| manifest["default_dataset"], |
| shared_media_roots, |
| ), |
| config, |
| ) |
|
|
|
|
| def _log_runtime_diagnostics(): |
| """Logs non-secret runtime details useful for diagnosing Space failures.""" |
| disk = shutil.disk_usage("/") |
| package_names = ( |
| "fiftyone", |
| "fiftyone-db", |
| "pymongo", |
| "starlette", |
| "hypercorn", |
| "httpx", |
| ) |
| package_versions = {} |
| for package_name in package_names: |
| try: |
| package_versions[package_name] = importlib.metadata.version( |
| package_name |
| ) |
| except importlib.metadata.PackageNotFoundError: |
| package_versions[package_name] = "not-installed" |
|
|
| logger.info("Starting FiftyOne Hugging Face Space") |
| logger.info( |
| "Runtime: python=%s executable=%s platform=%s architecture=%s", |
| sys.version.replace("\n", " "), |
| sys.executable, |
| platform.platform(), |
| platform.machine(), |
| ) |
| logger.info( |
| "Process: pid=%d uid=%d gid=%d hostname=%s cwd=%s", |
| os.getpid(), |
| os.getuid(), |
| os.getgid(), |
| socket.gethostname(), |
| os.getcwd(), |
| ) |
| logger.info( |
| "Environment: HOME=%s PORT=%s LOG_LEVEL=%s", |
| os.environ.get("HOME"), |
| os.environ.get("PORT"), |
| os.environ.get("LOG_LEVEL"), |
| ) |
| logger.info("Package versions=%s", package_versions) |
| logger.info( |
| "Disk '/': total=%.2fGiB used=%.2fGiB free=%.2fGiB", |
| disk.total / 1024**3, |
| disk.used / 1024**3, |
| disk.free / 1024**3, |
| ) |
| logger.info( |
| "FiftyOne config: database_dir=%s database_uri=%s " |
| "dataset_zoo_dir=%s model_zoo_dir=%s default_app_port=%s " |
| "default_app_address=%s", |
| getattr(fo.config, "database_dir", None), |
| getattr(fo.config, "database_uri", None), |
| getattr(fo.config, "dataset_zoo_dir", None), |
| getattr(fo.config, "model_zoo_dir", None), |
| getattr(fo.config, "default_app_port", None), |
| getattr(fo.config, "default_app_address", None), |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|