pyapp-org/pyapp

View on GitHub
src/pyapp/typed_settings.py

Summary

Maintainability
A
35 mins
Test Coverage
"""
Typed Settings
~~~~~~~~~~~~~~

Building on the standard settings features that is a core part of pyApp typed
settings provides a more convenient way to define and access settings.

Default settings are defined using a `SettingsDef` object along with the expected
type via type annotations. The settings can then be accessed at runtime using the
same definition.

For example settings are defined in the `default_settings.py` file as:

.. code-block:: python

    from pyapp.typed_settings import SettingsDef

    class MyAppSettings(SettingsDef):
        MY_CONFIG_VALUE: str = "Foo"

`MY_CONFIG_VALUE` is added to `pyapp.conf.settings` just like any other setting
and can be overridden by any other settings file.

Where typed settings really shine is using the settings in your application.
The `SettingsDef` object can be imported from the `default_settings` file and
used to access the runtime settings values using the same definition with all
the benefits of auto-completion and typing.

.. code-block:: python

    from myapp.default_settings import MyAppSettings

    print(MyAppSettings.MY_CONFIG_VALUE)

"""

from typing import Any, Dict, Mapping, Tuple


class SettingDescriptor:
    """Descriptor that can access a named setting."""

    __slots__ = ("setting",)

    def __init__(self, setting):
        self.setting = setting

    def __get__(self, instance, owner):
        from pyapp.conf import settings

        if settings.is_configured:
            return getattr(settings, self.setting)
        return None


class SettingsDefType(type):
    """Typed Settings definition type."""

    def __new__(cls, name: str, bases, dct: Dict[str, Any], *, prefix: str = ""):
        """Generate new type."""

        if prefix and not prefix.isupper():
            raise ValueError("Prefix must be upper snake case.")

        values = []
        descriptors = {}
        for key, value in dct.items():
            # Settings must be upper case (or constant style)
            if key.isupper():
                values.append((key, value))
                descriptors[key] = SettingDescriptor(f"{prefix}{key}")

        # Update original dict.
        dct.update(descriptors)
        dct["_settings"] = tuple(values)
        dct["__slots__"] = ()

        return super().__new__(cls, name, bases, dct)


class SettingsDef(metaclass=SettingsDefType):
    """Typed settings definition."""


NamedConfig = Mapping[str, Mapping[str, Any]]
NamedPluginConfig = Mapping[str, Tuple[str, Mapping[str, Any]]]