"""
Base registry class
"""
import inspect
from functools import lru_cache
from typing import Any, Generator
from moderngl_window.conf import settings
from moderngl_window.exceptions import ImproperlyConfigured
from moderngl_window.loaders.base import BaseLoader
from moderngl_window.meta.base import ResourceDescription
from moderngl_window.utils.module_loading import import_string
[docs]
class BaseRegistry:
"""Base class for all resource pools"""
settings_attr = ""
"""str: The name of the attribute in :py:class:`~moderngl_window.conf.Settings`
containting a list of loader classes.
"""
def __init__(self) -> None:
"""Initialize internal attributes"""
self._resources: list[ResourceDescription] = []
@property
def count(self) -> int:
"""int: The number of resource descriptions added.
This is only relevant when using `add` and `load_pool`.
"""
return len(self._resources)
@property
def loaders(self) -> Generator[type[BaseLoader], None, None]:
"""Generator: Loader classes for this resource type"""
for loader in getattr(settings, self.settings_attr):
yield self._loader_cls(loader)
@lru_cache(maxsize=None)
def _loader_cls(self, python_path: str) -> type[BaseLoader]:
cls = import_string(python_path)
assert issubclass(cls, BaseLoader), f"{python_path} does not lead to a Loader"
return cls
[docs]
def load(self, meta: ResourceDescription) -> Any:
"""
Loads a resource using the configured finders and loaders.
Args:
meta (ResourceDescription): The resource description
"""
self._check_meta(meta)
self.resolve_loader(meta)
cls = meta.loader_cls(meta)
assert cls is not None, f"Could not load {meta}, no arributes named 'loader_cls'"
return cls.load()
[docs]
def add(self, meta: ResourceDescription) -> None:
"""
Adds a resource description without loading it.
The resource is loaded and returned when ``load_pool()`` is called.
Args:
meta (ResourceDescription): The resource description
"""
self._check_meta(meta)
self.resolve_loader(meta)
self._resources.append(meta)
[docs]
def load_pool(self) -> Generator[tuple[ResourceDescription, Any], None, None]:
"""
Loads all the data files using the configured finders.
This is only relevant when resource have been added to this
pool using ``add()``.
Returns:
Generator of (meta, resource) tuples
"""
for meta in self._resources:
resource = self.load(meta)
yield meta, resource
self._resources = []
[docs]
def resolve_loader(self, meta: ResourceDescription) -> None:
"""
Attempts to assign a loader class to a ResourceDescription.
Args:
meta (:py:class:`~moderngl_window.meta.base.ResourceDescription`):
The resource description instance
"""
# Get loader using kind if specified
if meta.kind:
for loader_cls in self.loaders:
if loader_cls.kind == meta.kind:
meta.loader_cls = loader_cls
return
raise ImproperlyConfigured(
"Resource has invalid loader kind '{}': {}\nAvailable loaders: {}".format(
meta.kind, meta, [loader.kind for loader in self.loaders]
)
)
# Get loader based on file extension
for loader_cls in self.loaders:
if loader_cls.supports_file(meta):
meta.loader_cls = loader_cls
return
raise ImproperlyConfigured("Could not find a loader for: {}".format(meta))
def _check_meta(self, meta: Any) -> None:
"""Check is the instance is a resource description
Raises:
ImproperlyConfigured if not a ResourceDescription instance
"""
if inspect.isclass(type(meta)):
if issubclass(meta.__class__, ResourceDescription):
return
raise ImproperlyConfigured(
"Resource loader got type {}, not a resource description".format(type(meta))
)