Source code for hydrobricks.modules.base

from __future__ import annotations

from abc import ABC

from hydrobricks._exceptions import ConfigurationError


[docs] class Module(ABC): """Base class for the pluggable sub-model modules. A *module* is a swappable strategy a model plugs in to vary one part of its structure without changing the model itself (e.g. the glacier formulation). Each module *category* is a direct subclass of ``Module`` (e.g. ``GlacierModule``) that defines the category's interface and its own registry of named concrete formulations. Concrete modules register under a name with the ``register`` decorator, and a model resolves one with ``<Category>.get_module(name_or_instance)``. This base provides only the shared pluggability machinery (registry, registration and resolution); the category subclasses provide the actual interface. """ # Registry of {name: concrete class}. Declared per category subclass so names do # not collide across categories. ``_category`` is the human-readable label used in # error messages. _registry: dict[str, type[Module]] _category: str = "module"
[docs] @classmethod def register(cls, name: str): """Return a class decorator registering a concrete module under ``name``. Example ------- >>> @GlacierModule.register("gsm") ... class GSM(GlacierModule): ... ... """ def decorator(module_cls: type[Module]) -> type[Module]: cls._registry[name] = module_cls return module_cls return decorator
[docs] @classmethod def get_module(cls, module: str | Module) -> Module: """Resolve a module of this category from a registry name or an instance. Parameters ---------- module Either a registered module name (e.g. ``'gsm'``) or a module instance of this category (for custom user-defined formulations). Returns ------- Module The resolved module instance. Raises ------ ConfigurationError If the name is not registered in this category (or an instance of another category is given). """ if isinstance(module, cls): return module if isinstance(module, str) and module in cls._registry: return cls._registry[module]() available = ", ".join(sorted(cls._registry)) raise ConfigurationError( f"The {cls._category} '{module}' is not recognised. " f"Available {cls._category}s: {available}.", item_name=cls._category, item_value=module if isinstance(module, str) else type(module).__name__, reason="Unknown module", )