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 customSettingsField
will be merged with the defaultSettingsField
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 viaself._settings_field[name].get_value()
2. Convert the retrieved value by callingself._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 explicitfget
andfset
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 withXconSettings.service
, (which by default will useAPP_NAME
environmental variable).By default
{environment}
will be replaced withXconSettings.environment
, (which by default will useAPP_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'scacher
=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 isEnvironmentalProvider
. 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) …