File size: 5,064 Bytes
4eeba46 a544a50 bfe80c5 a544a50 4eeba46 a544a50 4eeba46 bfe80c5 4eeba46 bfe80c5 4eeba46 bfe80c5 4eeba46 8290bc9 a2223b1 bfe80c5 4eeba46 bfe80c5 4eeba46 8290bc9 ba32591 8290bc9 ba32591 bfe80c5 785d976 4eeba46 a544a50 bfe80c5 4eeba46 bfe80c5 4eeba46 bfe80c5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
"""Application configuration using pydantic-settings."""
from __future__ import annotations
import os
from pathlib import Path
from typing import Literal
from pydantic import Field, computed_field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
def is_running_in_hf_spaces() -> bool:
"""
Detect if running inside Hugging Face Spaces environment.
Returns:
True if running in HF Spaces, False otherwise
Detection methods (all env-var based for reliability):
1. HF_SPACES=1 env var (set by our Dockerfile)
2. SPACE_ID env var (set by HF Spaces runtime)
Note:
We intentionally avoid path-based detection (like checking for
/home/user or /app) because these paths exist on many Linux
systems and would cause false positives.
"""
# Check explicit env vars only - no path-based fallbacks
if os.environ.get("HF_SPACES") == "1":
return True
# SPACE_ID is set by HF Spaces runtime
return bool(os.environ.get("SPACE_ID"))
def is_deepisles_direct_available() -> bool:
"""
Check if DeepISLES can be invoked directly (without Docker).
Returns:
True if DEEPISLES_DIRECT_INVOCATION env var is set
This check is intentionally simple and side-effect free.
The env var is set by our Dockerfile when running on HF Spaces.
Actual module path setup happens in inference/direct.py when invoked.
Note:
We don't attempt import-based detection here because it would
require modifying sys.path, which is a side effect inappropriate
for a simple availability check.
"""
return os.environ.get("DEEPISLES_DIRECT_INVOCATION") == "1"
class Settings(BaseSettings):
"""
Application settings loaded from environment variables.
All settings can be overridden via environment variables with
the STROKE_DEMO_ prefix.
Example:
export STROKE_DEMO_LOG_LEVEL=DEBUG
export STROKE_DEMO_HF_DATASET_ID=my/dataset
"""
model_config = SettingsConfigDict(
env_prefix="STROKE_DEMO_",
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
# Logging
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO"
log_format: Literal["simple", "detailed", "json"] = "simple"
# HuggingFace
# Note: To control HF cache location, use HF_HOME env var (set in Dockerfile)
hf_dataset_id: str = "hugging-science/isles24-stroke"
hf_token: str | None = Field(default=None, repr=False) # Hidden from logs
# DeepISLES
deepisles_docker_image: str = "isleschallenge/deepisles"
deepisles_fast_mode: bool = True # SEALS-only (ISLES'22 winner, no FLAIR needed)
deepisles_timeout_seconds: int = 1800 # 30 minutes
deepisles_use_gpu: bool = True
# Paths
# Note: To control temp location, use TMPDIR env var (Python tempfile respects it)
# Results directory - MUST be /tmp for HF Spaces (only /tmp is writable)
results_dir: Path = Path("/tmp/stroke-results")
# API Settings
# Concurrency control - default to 1 for single-GPU safety (T4 has 16GB VRAM)
# Increase via STROKE_DEMO_MAX_CONCURRENT_JOBS if you have multiple GPUs
max_concurrent_jobs: int = 1
# CORS - frontend origins allowed to call this API
frontend_origins: list[str] = Field(default=["http://localhost:5173", "http://localhost:3000"])
# Public URL for constructing absolute file URLs in responses
# If not set, uses request.base_url (works for local dev)
backend_public_url: str | None = None
# UI
gradio_server_name: str = "0.0.0.0"
gradio_server_port: int = 7860
gradio_share: bool = False
# Show full Python tracebacks in Gradio UI (security: disable in production)
gradio_show_error: bool = False
@computed_field # type: ignore[prop-decorator]
@property
def is_hf_spaces(self) -> bool:
"""Check if running in HF Spaces environment."""
return is_running_in_hf_spaces()
@computed_field # type: ignore[prop-decorator]
@property
def use_direct_invocation(self) -> bool:
"""
Check if should use direct DeepISLES invocation (vs Docker).
Direct invocation is used when:
1. Running in HF Spaces (cannot run Docker-in-Docker)
2. DeepISLES modules are available for import
"""
return self.is_hf_spaces or is_deepisles_direct_available()
@field_validator("results_dir", mode="before")
@classmethod
def ensure_results_dir_exists(cls, v: Path | str) -> Path:
"""Create results directory if it doesn't exist."""
path = Path(v)
path.mkdir(parents=True, exist_ok=True)
return path
# Global settings instance
settings = Settings()
def get_settings() -> Settings:
"""Get the current settings instance."""
return settings
def reload_settings() -> Settings:
"""Reload settings from environment (useful for testing)."""
global settings
settings = Settings()
return settings
|