Download app/forecasting/base_client.py from JW55/SurfSense: direct link, hf CLI and curl.
- Browser
- Download file 5.21 kB
-
https://huggingface.co/spaces/JW55/SurfSense/resolve/main/app/forecasting/base_client.py
- Command line
-
hf download hf://spaces/JW55/SurfSense/app/forecasting/base_client.py
-
curl -L -o base_client.py https://huggingface.co/spaces/JW55/SurfSense/resolve/main/app/forecasting/base_client.py
5.21 kB
| """ | |
| Base interface for forecast API clients. | |
| All forecast providers (Stormglass, NOAA, etc.) implement this interface | |
| to ensure consistent behavior across the system. | |
| """ | |
| from abc import ABC, abstractmethod | |
| from typing import Optional | |
| from datetime import datetime | |
| from .models import ForecastResponse, Coordinates | |
| class ForecastAPIClient(ABC): | |
| """ | |
| Abstract base class for forecast API clients. | |
| All forecast data providers must implement this interface to be | |
| compatible with the ForecastIntegrationAgent. | |
| """ | |
| def source_name(self) -> str: | |
| """Return the name of this data source.""" | |
| pass | |
| def is_configured(self) -> bool: | |
| """Return True if this client is properly configured (e.g., has API key).""" | |
| pass | |
| def requires_api_key(self) -> bool: | |
| """Return True if this data source requires an API key.""" | |
| pass | |
| async def get_forecast( | |
| self, | |
| lat: float, | |
| lon: float, | |
| days: int = 5 | |
| ) -> Optional[ForecastResponse]: | |
| """ | |
| Fetch forecast data for a location. | |
| Args: | |
| lat: Latitude of the location | |
| lon: Longitude of the location | |
| days: Number of days to forecast (default: 5) | |
| Returns: | |
| ForecastResponse if successful, None if failed | |
| """ | |
| pass | |
| async def get_tide( | |
| self, | |
| lat: float, | |
| lon: float, | |
| date: Optional[datetime] = None | |
| ) -> Optional[dict]: | |
| """ | |
| Fetch tide data for a location. | |
| Args: | |
| lat: Latitude of the location | |
| lon: Longitude of the location | |
| date: Date for tide data (default: today) | |
| Returns: | |
| Tide data dictionary if successful, None if failed | |
| """ | |
| pass | |
| def supports_location(self, lat: float, lon: float) -> bool: | |
| """ | |
| Check if this data source supports the given location. | |
| Some providers (like NOAA) only cover certain regions. | |
| Default implementation returns True (global coverage). | |
| Args: | |
| lat: Latitude | |
| lon: Longitude | |
| Returns: | |
| True if location is supported | |
| """ | |
| return True | |
| async def health_check(self) -> bool: | |
| """ | |
| Check if the API is accessible and working. | |
| Returns: | |
| True if API is healthy | |
| """ | |
| return self.is_configured | |
| class ForecastClientRegistry: | |
| """ | |
| Registry for managing multiple forecast API clients. | |
| Provides source selection and fallback logic. | |
| """ | |
| def __init__(self): | |
| self._clients: dict[str, ForecastAPIClient] = {} | |
| self._priority_order: list[str] = [] | |
| def register(self, client: ForecastAPIClient, priority: int = 100) -> None: | |
| """ | |
| Register a forecast client. | |
| Args: | |
| client: The forecast client to register | |
| priority: Lower number = higher priority (default: 100) | |
| """ | |
| name = client.source_name | |
| self._clients[name] = client | |
| # Insert maintaining priority order | |
| inserted = False | |
| for i, existing_name in enumerate(self._priority_order): | |
| existing_client = self._clients[existing_name] | |
| # This is simplified - in practice you'd store priority separately | |
| if not inserted: | |
| self._priority_order.insert(i, name) | |
| inserted = True | |
| break | |
| if not inserted: | |
| self._priority_order.append(name) | |
| def get_client(self, name: str) -> Optional[ForecastAPIClient]: | |
| """Get a specific client by name.""" | |
| return self._clients.get(name) | |
| def get_available_clients(self) -> list[ForecastAPIClient]: | |
| """Get all configured and available clients.""" | |
| return [ | |
| client for client in self._clients.values() | |
| if client.is_configured | |
| ] | |
| def get_best_client_for_location( | |
| self, | |
| lat: float, | |
| lon: float | |
| ) -> Optional[ForecastAPIClient]: | |
| """ | |
| Get the best available client for a specific location. | |
| Considers: | |
| 1. Whether client supports the location | |
| 2. Whether client is configured | |
| 3. Priority order | |
| Args: | |
| lat: Latitude | |
| lon: Longitude | |
| Returns: | |
| Best available client, or None if no suitable client | |
| """ | |
| for name in self._priority_order: | |
| client = self._clients[name] | |
| if client.is_configured and client.supports_location(lat, lon): | |
| return client | |
| return None | |
| def registered_sources(self) -> list[str]: | |
| """List all registered source names.""" | |
| return list(self._clients.keys()) | |
| # Global registry instance | |
| forecast_registry = ForecastClientRegistry() | |