| | import glob |
| | from abc import ABC, abstractmethod |
| | from datetime import datetime, timedelta |
| | from typing import Dict, Generic, List, Optional, Tuple, TypeVar |
| |
|
| | component_type = TypeVar("component_type") |
| |
|
| |
|
| | class ComponentRegistry(ABC, Generic[component_type]): |
| | def __init__(self, file_glob: str, ttl: Optional[timedelta] = None) -> None: |
| | super().__init__() |
| | self._registry: Optional[Dict[str, component_type]] = None |
| | self._registry_update: datetime = datetime.fromtimestamp(0) |
| | self._file_glob: str = file_glob |
| | self._ttl: Optional[timedelta] = ttl |
| |
|
| | @abstractmethod |
| | def _load_component(self, path: str) -> Tuple[str, component_type]: |
| | raise NotImplementedError |
| |
|
| | def is_available(self, freshness: Optional[timedelta] = None) -> bool: |
| | if self._registry is None: |
| | return False |
| | staleness = datetime.now() - self._registry_update |
| | if self._ttl is not None and staleness > self._ttl: |
| | return False |
| | if freshness is not None and staleness > freshness: |
| | return False |
| | return True |
| |
|
| | def get_registry( |
| | self, |
| | force_reload: bool = False, |
| | freshness: Optional[timedelta] = None, |
| | show_error: bool = False, |
| | ) -> Dict[str, component_type]: |
| | if not force_reload and self.is_available(freshness): |
| | assert self._registry is not None |
| | return self._registry |
| |
|
| | registry: Dict[str, component_type] = {} |
| | for path in glob.glob(self._file_glob): |
| | try: |
| | name, component = self._load_component(path) |
| | except Exception as e: |
| | if show_error: |
| | print(f"failed to loading component from {path}, skipping: {e}") |
| | continue |
| | if component is None: |
| | if show_error: |
| | print(f"failed to loading component from {path}, skipping") |
| | continue |
| | registry[name] = component |
| |
|
| | self._registry_update = datetime.now() |
| | self._registry = registry |
| | return registry |
| |
|
| | @property |
| | def registry(self) -> Dict[str, component_type]: |
| | return self.get_registry() |
| |
|
| | def get_list(self, force_reload: bool = False, freshness: Optional[timedelta] = None) -> List[component_type]: |
| | registry = self.get_registry(force_reload, freshness) |
| | keys = sorted(registry.keys()) |
| | return [registry[k] for k in keys] |
| |
|
| | @property |
| | def list(self) -> List[component_type]: |
| | return self.get_list() |
| |
|
| | def get(self, name: str) -> Optional[component_type]: |
| | return self.registry.get(name, None) |
| |
|
| | def __getitem__(self, name: str) -> Optional[component_type]: |
| | return self.get(name) |
| |
|
| | @property |
| | def file_glob(self) -> str: |
| | return self._file_glob |
| |
|
| | @file_glob.setter |
| | def file_glob(self, file_glob: str) -> None: |
| | if self._file_glob == file_glob: |
| | return |
| | self._file_glob = file_glob |
| | self._registry = None |
| |
|