Module xcon.conf

Expand source code
from xsettings import Settings as _Settings, SettingsField
from typing import Type, Optional, Sequence
from .directory import DirectoryListing, Directory
from . import providers
from .provider import Provider
from xsettings.env_settings import EnvVarRetriever


_env_retriever = EnvVarRetriever()


class XconSettings(_Settings):
    def __init__(self):
        super().__init__()
        # TODO: Find a simpler/easier way to allocate mutable things like `dict`m `list`,
        #  empty-objs and so on; on a per-instance basis.
        self.defaults = DirectoryListing()

    service: str = SettingsField(
        name='APP_NAME', retriever=_env_retriever, default_value='global'
    )
    """ Defaults to `APP_NAME` environment variable; otherwise will fallback to using 'global'. """

    environment: str = SettingsField(
        name='APP_ENV', retriever=_env_retriever, default_value='all'
    )
    """ Defaults to `APP_ENV` environment variable; otherwise will fallback to using 'all'. """

    defaults: DirectoryListing
    """
    A blank DirectoryListing is created per-instance for this setting,
    feel free to directly modify it to add/remove whatever default config values you want
    for the overall defaults (ie: used by all config instances as the last place to check
    before giving up on finding a value for a particular config value.

    `xcon.config.Config` will always check it's self first for any defaults that are set
    on a particular field before doing a final fall-back to check this overall defaults
    setting for a value.
    """

    directories: Sequence[Directory] = (
        Directory('/{service}/{environment}'),
        Directory('/{service}/all'),
        Directory('/global/{environment}'),
        Directory('/global/all'),
    )
    """
    Default list of directories to use.

    By default `{service}` will be replaced with `XconSettings.service`,
    (which by default will use `APP_NAME` environmental variable).

    By default `{environment}` will be replaced with `XconSettings.environment`,
    (which by default will use `APP_ENV` environmental variable).
    """

    # XCON_INTERNAL_CACHE_EXPIRATION_MINUTES
    internal_cache_expiration_minutes: int = SettingsField(
        name='XCON_INTERNAL_CACHE_EXPIRATION_MINUTES',
        retriever=_env_retriever,
        default_value=False
    )
    """
    Number of minutes to cache any values looked up and ached internally inside the providers.
    After the expiration is reached, and if value is needed again will lookup info lazily on
    demand.

    Keep in mind that if the dynamo cache is enabled and working that the dynamo cache table might
    still have the value.
    If it's still cached in the dynamo table then when the internal cache expires, will first look
    at dynamo cache table for value (which is MUCH faster then querying each provider).
    """

    # todo: rename without provider or somehow indicate cacher is not used too?
    only_env_provider: bool = SettingsField(
        name='XCON_ONLY_ENV_PROVIDER',
        retriever=_env_retriever,
        default_value=False
    )
    """
    If `False` (default): The providers are looked up like normal.

    If `True`: No matter how the providers are configured, it will only check environmental
    variables for config values; ie: the only provider used is
    `xcon.providers.environmental.EnvironmentalProvider`.
    The cacher is also disabled and not used.

    This is meant as more of a developer setting, for when a developer wants to ensure
    that while they run things locally it only checks environmental variables for all
    config values (ie: it won't make any external calls to lookup configuration).
    """

    disable_default_cacher: bool = SettingsField(
        name='XCON_DISABLE_DEFAULT_CACHER', retriever=_env_retriever, default_value=False
    )
    """ Defaults to `XCON_DISABLE_DEFAULT_CACHER` environment variable
        (you can use 'True', 'T', 'False', 'F', 0, 1, 'Yes', 'Y', 'No', 'N'
         and lower-case versions of any of these).

         If environmental variable not set, Defaults to `False`.

         If `True`: By default, the cacher will be disabled
         (can re-enable per-Config object by explicitly setting it's
          `cacher` = `xcon.providers.dynamo.DynamoCacher`).
    """

    providers: Sequence[Type[Provider]] = (
        providers.EnvironmentalProvider,
        providers.SsmParamStoreProvider,
        providers.SecretsManagerProvider
    )
    """
    Default list of providers to use, in the order to try them in.

    When pytest (unit testing) is running, this is altered to only have
    `xcon.providers.environmental.EnvironmentalProvider` listed.
    """


xcon_settings = XconSettings.proxy()

Classes

class XconSettings

Base Settings class. For all class properties defined there will be a corresponding _settings_field["name"] = SettingsField created value that will control how this value is read and manipulated.

The purpose of the Settings class is to allow a library or project/service to define a number of settings that are needed in order to function. You define a number of Settings propertiess to indicate what settings are available to use in the project.

You define a Settings and properties very similar to how you define a dataclass. You specify a property name, type_hint, and default_value.

>>> class MySettings(BaseSettings):
...    name: type_hint = default_value

A default SettingsField will be configured using the name, type_hint, and default_value as follows – SettingsField(name=name, type_hint=type_hint, converter=type_hint, default_value=default_value, resolver=default_resolver)

This functionality can be overridden by setting the default_value to a custom SettingsField. The custom SettingsField will be merged with the default SettingsField overriding any fields that were defined in the custom SettingsField.

It's important to note that while we are setting these attributes on the class they will not remain as attributes on the class. The _SettingsMeta will take each attribute and convert them to a SettingsField and then place them in the class's _setting_fields attribute.

Example of various ways to allocate a SettingsField on a Settings subclass:

>>> class MySettings(BaseSettings):
...     setting_1: int
Allocates
>>> SettingsField(name="setting_1", type_hint=int, resolver=SettingsResolver)
>>> class MySettings(BaseSettings):
...     setting_1: int = 3
Allocates
>>> SettingsField(name="setting_1", type_hint=int, resolver=SettingsResolver, default_value=3)
>>> class MySettings(BaseSettings):
...     setting_1 = 3
Allocates
>>> SettingsField(name="setting_1", type_hint=int, resolver=SettingsResolver, default_value=3)
>>> class MySettings(BaseSettings):
...     setting_1: int = SettingsField(name="other", required=False)
Allocates
>>> SettingsField(name="other", type_hint=int, resolver=SettingsResolver, required=False)

Accessing Class (not instance) Attributes = Lazy Property Reference

You can do lazy forward-refrences by simply asking the Settings class (not instance) for a attribute. Doing so will return a SettingsClassProperty that is a forward reference to the singleton instance class attribute.

Examples of how you might use this

>>> class MySettings(BaseSettings):
...    my_url_setting: str
>>> class MySubSettings(BaseSettings):
...    my_field: str
>>> class SomeClass:
...    some_attr = MySettings.my_url_setting
>>>
>>> MySettings.grab().my_url_setting = "my-url"
>>> MySubSettings.grab().my_field = MySettings.my_url_setting
>>>
>>> assert SomeClass.some_attr == "my-url"
>>> assert MySubSettings.grab().some_attr == "my-url"
>>> assert MySettings.grab().some_attr == "my-url"

Setting public Class Attributes after creation time not allowed

Attempting to set a public class level attribute will result in an Error being raised.

Settings Attribute (Property) Inheritance not allowed

To keep things as simple as possible we don't allow SettingsClass attribute inheritance. You can however create a parent class that defines methods / @properties that can be inherited. Trying to set a regular (non-method/non-property/public) attribute will raise an error.

Accessing Instance Properties = Get Value and Convert

When calling MySettings.grab().my_setting the Settings class will attempt to retrieve and convert the corresponding value. Getting the the source value and converting the value is controlled by the SettingsField. Here is how it works.

Start by attempting to retrieve a property from the class instance

>>> MyClass.grab().my_setting

The Settings class will do the following 1. Attempt to retrieve a value. a. Lookup value from self via object.__getattribute__(self, key) b. If an AttributeError is thrown then lookup the value from the corresponding field via self._settings_field[name].get_value() 2. Convert the retrieved value by calling self._settings_field[name].convert_value(value)

TODO

We don't support property setters/deleters at the moment. We would need to implement a __setattr__ here, where it would check for a property setter/getter on field object. (Consider a explicit fget and fset attribute on SettingsField at that point)

Set attributes to values that are passed via key-word arguments, these are the initial values for the settings instance your creating; they are set directly on the instance as if you did this:

# These two statements do the same thing:
obj = SomeSettings(some_keyword_arg="hello")

obj = SomeSettings()
obj.some_keyword_arg="hello"

Args

retrievers
can be used to populate new instance's retrievers,

see BaseSettings.settings__instance_retrievers.

Expand source code
class XconSettings(_Settings):
    def __init__(self):
        super().__init__()
        # TODO: Find a simpler/easier way to allocate mutable things like `dict`m `list`,
        #  empty-objs and so on; on a per-instance basis.
        self.defaults = DirectoryListing()

    service: str = SettingsField(
        name='APP_NAME', retriever=_env_retriever, default_value='global'
    )
    """ Defaults to `APP_NAME` environment variable; otherwise will fallback to using 'global'. """

    environment: str = SettingsField(
        name='APP_ENV', retriever=_env_retriever, default_value='all'
    )
    """ Defaults to `APP_ENV` environment variable; otherwise will fallback to using 'all'. """

    defaults: DirectoryListing
    """
    A blank DirectoryListing is created per-instance for this setting,
    feel free to directly modify it to add/remove whatever default config values you want
    for the overall defaults (ie: used by all config instances as the last place to check
    before giving up on finding a value for a particular config value.

    `xcon.config.Config` will always check it's self first for any defaults that are set
    on a particular field before doing a final fall-back to check this overall defaults
    setting for a value.
    """

    directories: Sequence[Directory] = (
        Directory('/{service}/{environment}'),
        Directory('/{service}/all'),
        Directory('/global/{environment}'),
        Directory('/global/all'),
    )
    """
    Default list of directories to use.

    By default `{service}` will be replaced with `XconSettings.service`,
    (which by default will use `APP_NAME` environmental variable).

    By default `{environment}` will be replaced with `XconSettings.environment`,
    (which by default will use `APP_ENV` environmental variable).
    """

    # XCON_INTERNAL_CACHE_EXPIRATION_MINUTES
    internal_cache_expiration_minutes: int = SettingsField(
        name='XCON_INTERNAL_CACHE_EXPIRATION_MINUTES',
        retriever=_env_retriever,
        default_value=False
    )
    """
    Number of minutes to cache any values looked up and ached internally inside the providers.
    After the expiration is reached, and if value is needed again will lookup info lazily on
    demand.

    Keep in mind that if the dynamo cache is enabled and working that the dynamo cache table might
    still have the value.
    If it's still cached in the dynamo table then when the internal cache expires, will first look
    at dynamo cache table for value (which is MUCH faster then querying each provider).
    """

    # todo: rename without provider or somehow indicate cacher is not used too?
    only_env_provider: bool = SettingsField(
        name='XCON_ONLY_ENV_PROVIDER',
        retriever=_env_retriever,
        default_value=False
    )
    """
    If `False` (default): The providers are looked up like normal.

    If `True`: No matter how the providers are configured, it will only check environmental
    variables for config values; ie: the only provider used is
    `xcon.providers.environmental.EnvironmentalProvider`.
    The cacher is also disabled and not used.

    This is meant as more of a developer setting, for when a developer wants to ensure
    that while they run things locally it only checks environmental variables for all
    config values (ie: it won't make any external calls to lookup configuration).
    """

    disable_default_cacher: bool = SettingsField(
        name='XCON_DISABLE_DEFAULT_CACHER', retriever=_env_retriever, default_value=False
    )
    """ Defaults to `XCON_DISABLE_DEFAULT_CACHER` environment variable
        (you can use 'True', 'T', 'False', 'F', 0, 1, 'Yes', 'Y', 'No', 'N'
         and lower-case versions of any of these).

         If environmental variable not set, Defaults to `False`.

         If `True`: By default, the cacher will be disabled
         (can re-enable per-Config object by explicitly setting it's
          `cacher` = `xcon.providers.dynamo.DynamoCacher`).
    """

    providers: Sequence[Type[Provider]] = (
        providers.EnvironmentalProvider,
        providers.SsmParamStoreProvider,
        providers.SecretsManagerProvider
    )
    """
    Default list of providers to use, in the order to try them in.

    When pytest (unit testing) is running, this is altered to only have
    `xcon.providers.environmental.EnvironmentalProvider` listed.
    """

Ancestors

Static methods

def __init_subclass__(thread_sharable=Default, attributes_to_skip_while_copying: Optional[Iterable[str]] = Default, **kwargs)

Inherited from: BaseSettings.__init_subclass__

Args

thread_sharable
If False: While a dependency is lazily auto-created, we will ensure we do it per-thread, and not make it visible …
def grab() ‑> ~T

Inherited from: BaseSettings.grab

Gets a potentially shared dependency from the current udpend.context.XContext

def proxy() ‑> ~R

Inherited from: BaseSettings.proxy

Returns a proxy-object, that when and attribute is asked for, it will proxy it to the current object of cls

def proxy_attribute(attribute_name: str) ‑> Any

Inherited from: BaseSettings.proxy_attribute

Returns a proxy-object, that when and attribute is asked for, it will proxy it to the current attribute value on the current object of cls

Instance variables

var defaults

A blank DirectoryListing is created per-instance for this setting, feel free to directly modify it to add/remove whatever default config values you want for the overall defaults (ie: used by all config instances as the last place to check before giving up on finding a value for a particular config value.

Config will always check it's self first for any defaults that are set on a particular field before doing a final fall-back to check this overall defaults setting for a value.

Expand source code
@SettingsClassProperty
def lazy_retriever(calling_cls):
    return getattr(self.grab(), key)
var directories

Default list of directories to use.

By default {service} will be replaced with XconSettings.service, (which by default will use APP_NAME environmental variable).

By default {environment} will be replaced with XconSettings.environment, (which by default will use APP_ENV environmental variable).

Expand source code
@SettingsClassProperty
def lazy_retriever(calling_cls):
    return getattr(self.grab(), key)
var disable_default_cacher

Defaults to XCON_DISABLE_DEFAULT_CACHER environment variable (you can use 'True', 'T', 'False', 'F', 0, 1, 'Yes', 'Y', 'No', 'N' and lower-case versions of any of these).

If environmental variable not set, Defaults to False.

If True: By default, the cacher will be disabled (can re-enable per-Config object by explicitly setting it's cacher = DynamoCacher).

Expand source code
@SettingsClassProperty
def lazy_retriever(calling_cls):
    return getattr(self.grab(), key)
var environment

Defaults to APP_ENV environment variable; otherwise will fallback to using 'all'.

Expand source code
@SettingsClassProperty
def lazy_retriever(calling_cls):
    return getattr(self.grab(), key)
var internal_cache_expiration_minutes

Number of minutes to cache any values looked up and ached internally inside the providers. After the expiration is reached, and if value is needed again will lookup info lazily on demand.

Keep in mind that if the dynamo cache is enabled and working that the dynamo cache table might still have the value. If it's still cached in the dynamo table then when the internal cache expires, will first look at dynamo cache table for value (which is MUCH faster then querying each provider).

Expand source code
@SettingsClassProperty
def lazy_retriever(calling_cls):
    return getattr(self.grab(), key)
var obj

Inherited from: BaseSettings.obj

class property/attribute that will return the current dependency for the subclass it's asked on by calling Dependency.grab, passing no extra …

var only_env_provider

If False (default): The providers are looked up like normal.

If True: No matter how the providers are configured, it will only check environmental variables for config values; ie: the only provider used is EnvironmentalProvider. The cacher is also disabled and not used.

This is meant as more of a developer setting, for when a developer wants to ensure that while they run things locally it only checks environmental variables for all config values (ie: it won't make any external calls to lookup configuration).

Expand source code
@SettingsClassProperty
def lazy_retriever(calling_cls):
    return getattr(self.grab(), key)
var providers

Default list of providers to use, in the order to try them in.

When pytest (unit testing) is running, this is altered to only have EnvironmentalProvider listed.

Expand source code
@SettingsClassProperty
def lazy_retriever(calling_cls):
    return getattr(self.grab(), key)
var service

Defaults to APP_NAME environment variable; otherwise will fallback to using 'global'.

Expand source code
@SettingsClassProperty
def lazy_retriever(calling_cls):
    return getattr(self.grab(), key)
var settings__instance_retrievers : List[SettingsRetrieverProtocol]

Inherited from: BaseSettings.settings__instance_retrievers

You can add one or more retrievers to this instance of settings (won't modify default_retrievers for the entire class, only modifies this specific …

Methods

def __call__(self, func)

Inherited from: BaseSettings.__call__

This makes Resource subclasses have an ability to be used as function decorators by default unless this method is overriden to provide some other …

def __copy__(self)

Inherited from: BaseSettings.__copy__

Basic shallow copy protection (I am wondering if I should just remove this default copy code) …