Configuration API¶
The application API of the counterpart package, which is read-only and focused on loading.
advexp.configuration.Configuration
¶
Base class for read-only application configuration.
All operations are class-level: the configuration class itself is the
singleton. Instances are supported for reads, but they share the class
storage. :meth:to_object copies the values out to a detached instance of
the class, and :meth:to_dict copies them to a read-only mapping.
Sources are only ever read. There is no write-back pass; this is the main
difference between this package and :mod:advexp.settings. The values
themselves are set only by a load: assigning to a declared member, on the
class or on an instance, raises TypeError. A :meth:to_object
snapshot is completely frozen: none of its attributes can be written or
deleted.
A member declared without default= is required: it must get its value
from a source. If a load finds nothing usable for a required member, it
raises one abort of the package's load pass,
:class:~advexp.configuration.AggregateLoadError, with an entry for each
failed member (a distinct class for each cause, all subclasses of
:class:~advexp.settings_base.member_load_error.MemberLoadError). It
raises one error however many members failed, so one run reports them all.
Reading a required member that was never loaded raises the same error.
An optional member never aborts a load: it stands at its default, and its
load error goes into the report that :meth:load_configuration returns.
If a run has both an abort and fallbacks, the fallbacks are kept next to
the abort in its fallbacks attribute.
:data:~advexp.settings_base.required.REQUIRED is the explicit spelling
of a required member, and the default parameter of the bundled sources
defaults to it. :data:~advexp.settings_base.no_default.NO_DEFAULT keeps
its family-wide meaning: the member is optional, and None stands in
for it.
load_configuration(overrides=None)
classmethod
¶
Load all declared members from their sources and report the fallbacks.
Every member is loaded, whatever happened to the previous one. A required
member (one declared REQUIRED) whose sources provide nothing usable
records an error. The error names the cause (no value, a value that cannot
be converted, a constraint violation, or a failed source), the member, its
setting, and the class. An optional member records the same error, but it
stands at its default instead.
A plain miss is not an error. For an optional member, a miss is the normal
way in which the default gets used, so it is not reported at the usual log
levels; a debug record names the address that came up empty. When the pass
aborts, the optional misses are attached to the abort in the aggregate's
misses, so the reader of the failure can see which optional members
stood at their defaults.
At the end, one abort reports every required failure at once. It is always
:class:~advexp.configuration.AggregateLoadError, however many members
failed. Its errors tuple holds each member's own error, and its
fallbacks tuple holds the optional fallbacks of the same run. If there
is no required failure, there is no abort: the method returns the
fallbacks as its report (an empty tuple when every member loaded cleanly),
so a caller can learn what fell back without parsing logs. A user-facing
source condition (a corrupt or inaccessible source) does not wait for the
end: if the failing member has a default that would mask it, the condition
propagates and aborts the whole pass immediately.
Each member is resolved through the class's source chain, and the first link that provides a value wins. A member declared through a family's factory reads only the links of that family. A broken link ends the walk down the chain, so the layers below it never hide the problem.
overrides is a layer above the whole chain, not a declared link. Its
keys are member names, and its values go through the same conversion and
constraint checks as the values of a link. It can reach any member, even
one that a path= hides. Keys that name no member (for example, a
model_dump() of another model may carry keys that this class never
declared) and keys that name constant members (which no override reaches)
are dropped, with one debug record for each. A foreign dictionary therefore
never makes the load fail.
The pass builds the class state from scratch and applies it in one step. A completed load fully determines the state: every member takes either the value that its sources produced or its declared default. A value from an earlier load therefore never survives when the current sources hold nothing for that member, so one class can be reused across loads, and each pass stands on its own. A pass that ends in the aggregate abort, or in a propagating source condition, leaves the class exactly as the call found it: no values are partly applied, and no defaults are served by the failed run. So if a reload fails, the process keeps the configuration that it is already running with. Nothing is written back: sources are read-only for this class, and the load pass is the only writer of the values themselves.
Library configuration¶
advexp.configuration.LibraryConfiguration
¶
Library-wide configuration (all state is class-level).
The registries of the family live here (attributes to serializers, plugin classes, and class-metadata validators), next to the parameter that an application touches: the log level of the whole family. The form of storage names is not part of it: a name pattern is the own vocabulary of the naming backend, and every backend family carries its own.
register_settings_plugin(plugin_class)
classmethod
¶
Register a plugin class and call its setup() hook, if there is one.
A plugin may define a setup() class method. It runs once, right at
registration, and it is the place where a plugin usually registers its
attribute-to-serializer pairs through :meth:register_settings_attribute.
setup() is optional: a plugin may register its pieces inline at import
time instead.
:class:~advexp.settings_base.settings_base_plugin.SettingsBasePlugin ships
a setup(). It registers the declared
attribute_class/serializer_class pair, so a plugin that derives from
it usually declares the pair instead of writing its own setup(). Bundled
plugins call this method when their module is imported, which is what makes
importing a plugin module enable the plugin.
Registering a plugin that is already registered is a no-op: the setup()
hook does not run a second time, so repeated imports or an explicit
re-registration leave the state of the plugin untouched.
registered_plugin_classes()
classmethod
¶
Return the registered plugin classes, in registration order.
register_settings_attribute(attribute_class, serializer_factory)
classmethod
¶
Map an attribute class to the serializer class that serves its settings.
Registering the same pair again is a no-op: several plugins may register one
shared attribute-to-serializer pair. Registering the same attribute class
with a different serializer class raises :class:ValueError: two backends
that claim one attribute are a conflict, not an override.
A dynamic-attribute pin is registered in the same way, as a slot of its own: the serializer that is bound to it serves only the dynamic entries, and it never touches the registration of the attribute class that the pin namespaces.
register_class_metadata_validator(metadata_name, validator)
classmethod
¶
Map a class metadata key to the validator of its values.
A plugin family that owns a metadata key (its options vocabulary) registers the validator when its module is imported. This is the same convention that the plugin registry follows, where the import enables the plugin.
The machinery then checks every stored value. A declaration in a class body
makes the class statement fail, and a runtime call of
:meth:~advexp.settings_base.context.SettingsContext.set_class_metadata
raises an error before the value is stored. Keys that have no validator
belong to the open channel alone: whatever the application stores there is
never questioned.
Registering the same pair again is a no-op: a family module that is imported
twice leaves the state untouched. Registering the same key with a
different validator raises :class:ValueError: two families that claim one
key are a conflict, not an override.
set_log_level(level)
classmethod
¶
Set the log level on the root of the family, which governs every channel.
The records of the machinery are carried by the channel of the distribution
that drives them (advexp.settings_base, or the own channel of a
distribution). All of these channels are under the advexp root, so one
level that is set here covers them, whichever distribution is in use. A
channel that has its own explicitly set level keeps it, because the
logging module resolves the nearest level that is set.
logger()
classmethod
¶
Return the logging channel of the family, ready to be configured.
Every channel of the family (the own channel of a distribution, and the one
of the foundation) lives under the root of the family. One object therefore
receives every record that the machinery writes: you can set a level on it
or add a handler with a formatter to it, all with standard logging. The
name stays the own spelling of the library: the application configures the
channel that it was handed, and never one that it looked up by name:
handler = logging.StreamHandler()
handler.setFormatter(
logging.Formatter("%(levelname)s:%(name)s: %(message)s")
)
logger = LibraryConfiguration.logger()
logger.addHandler(handler)
logger.propagate = False # the application's root stays quiet
The propagation parameter matters when the application also configures the
root logger. logging.basicConfig() puts a handler there, so a channel
that is configured here would duplicate its records. Turning the propagation
off prevents this. An application that configures nothing but
basicConfig needs none of this: the records flow to its root in the usual
way.
The source chain¶
advexp.configuration.Sources(*bindings)
¶
The source chain of a layered class, in its resolution order.
It is built from the results of the source builders
(Sources(env_source(...), json_source(...))), and the first link that is
named is the first one that is read. Iteration returns the links in that
order. The chain itself is plain data that the load pass walks.
An empty chain cannot be created: a layered class with nothing to read from
is a mistake in the declaration, and the class statement is the place to
report it.
Attribute parameters¶
The forms that a member's declaration can take. The chain and constant
parameters come from the package root; the file families provide theirs
in their plugin modules — the same modules that the examples import as
js, ts, ys, env and dotenv.
advexp.configuration.chain_setting(*, name=None, default=REQUIRED, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Declare a member that the whole source chain resolves. This is the
type-checker-friendly spelling of :class:ChainSetting.
It is the rich form of the plain declaration: the member reads the class's
chain from top to bottom, and every link builds its address by the rules
of its own family. If default= is omitted (or is REQUIRED), the
member is required: the chain must provide its value, or the load aborts.
name= is the full literal address in every named link: the prefix, the
case, and the key pattern never change it. aliases= are full literal
addresses in the same way. A load tries the primary address first, then
the aliases in their declared order, in every named link of the chain. The
mapping link is the exception: its dictionary knows only the member name.
The keyword parameters after aliases are the declared value
constraints: gt/ge/lt/le/eq/ne, min_length/max_length, regex,
validator, and when. They are applied to whatever the chain
provides, and they are the same constraints that every member factory of
the family applies.
advexp.configuration.ChainSetting(*, name=None, default=REQUIRED, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
The attribute of a member that the whole source chain resolves.
The attribute names the setting, not the storage. A member declared
through it has no source of its own: it reads the class's chain from top
to bottom, and the first link that provides a value wins. Both the plain
declaration (token: str) and the rich one (:func:chain_setting)
produce this attribute. Each named link builds the member's address by
the rules of its own family (the environment prefix and case, the JSON
key pattern), so the same member is addressed differently in different
links. A declared aliases= entry is a full literal address in every
named link.
advexp.configuration.constant_setting(*, default=REQUIRED, metadata=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Declare a constant member. This is the type-checker-friendly spelling of
:class:ConstantSetting.
The member lives outside the source chain: its value is the declared
default=, every load serves exactly that value, and the load overrides
(load_configuration(overrides={...})) skip the member and write a debug
record. The value goes through the same declared-default machinery as any
other member: it is converted to the member's annotation at the class
statement, so a value in storage form ("0:30:00" for a timedelta
member) works as it does for any other member. default= is required: a
constant without a value could never be loaded, so the factory refuses it.
The keyword parameters after metadata are the declared value
constraints. gt/ge/lt/le/eq/ne compare the value, min_length and
max_length bound len(value), regex is an unanchored pattern
that a str value must match, and validator is a callable that
receives the value and returns whether it passes. when= names another
member: while that member's value is falsy, the whole set of constraints is
switched off. The constraints check the declared value in the same way as
for any other member: a constant is immune to the sources, but not to its
own declaration.
advexp.configuration.ConstantSetting(*, default=REQUIRED, metadata=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
The attribute of a member outside the source chain.
The member's value is its declared default, and nothing else: no link of the chain is asked for it, the load overrides skip it, and a reload serves the same declared value again. Examples are a schema version pinned in code, or a feature flag that the sources must not control. This is a member that the application states about itself, rather than reads from its environment. The declaration requires a value: a constant member without one could never be loaded, so the factory refuses it outright.
SOURCELESS = True
class-attribute
instance-attribute
¶
The load machinery's mark: no source is ever asked for this member, and the load overrides skip it.
advexp.configuration.plugins.json_settings.json_setting(*, name=None, default=REQUIRED, path=None, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Declare a JSON-file member. This is the type-checker-friendly spelling of
JSONSetting.
It builds exactly the attribute that the class builds, with the same
keyword parameters. Both spellings exist because of typing. Assigning an
attribute instance to an annotated member
(port: int = JSONSetting(...)) is valid Python (the settings metaclass
replaces the attribute with a value descriptor), but type checkers cannot
see the replacement and report a type mismatch. They accept a plain
function that returns Any. The Any return also leaves default=
unchecked, deliberately: a default may be given in its storage form
(default="4" for an int member), and it is converted at class
creation, where a default that cannot be converted makes the class
statement fail.
default=REQUIRED (which is also the value used when default= is
omitted) makes the member required: a configuration class has no
placeholder value for it. The load aborts with
:class:~advexp.settings_base.missing_value_error.MissingValueError when
the document holds nothing usable for the member, and reading a member that
was never loaded raises the same error. :data:NO_DEFAULT keeps the
meaning that it has in the whole family: the member is optional, and
None stands in for it.
aliases= declares additional full storage keys. A load looks up the
primary key first, then the aliases in their order, and the first key
found wins. Every alias is read under the same path=.
The keyword parameters after aliases are the declared value constraints
(gt/ge/lt/le/eq/ne, min_length/max_length, regex,
validator, when). They are the same constraints that the base
attribute documents, and they are applied wherever a value reaches the
member.
advexp.configuration.plugins.json_settings.JSONSetting(*, name=None, default=REQUIRED, path=None, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Setting attribute: the value is read from the JSON configuration file.
name= is the storage key and may contain any characters. Without it,
the {module}.{class}.{member} pattern is used. The optional path=
locates the key in the document tree as a JSON Pointer (RFC 6901): a
leading / and slash-separated segments, where ~1 escapes / and
~0 escapes ~. For example:
path="/net/proxy"reads the setting from{"net": {"proxy": {...}}}.path="/mirrors/1"reads it from the second element of themirrorsarray.path="/endpoints/api.internal"reads it from under a key that contains a dot.
A segment that consists only of digits indexes an array if the node it
steps into is an array, and names an object key otherwise. path="" and
path=None both mean the document root.
advexp.configuration.plugins.toml_settings.toml_setting(*, name=None, default=REQUIRED, path=None, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Declare a TOML-file member. This is the type-checker-friendly spelling of
TOMLSetting.
It builds exactly the attribute that the class builds, with the same
keyword parameters. Both spellings exist because of typing. Assigning an
attribute instance to an annotated member
(port: int = TOMLSetting(...)) is valid Python (the settings metaclass
replaces the attribute with a value descriptor), but type checkers cannot
see the replacement and report a type mismatch. They accept a plain
function that returns Any. The Any return also leaves default=
unchecked, deliberately: a default may be given in its storage form
(default="4" for an int member), and it is converted at class
creation, where a default that cannot be converted makes the class
statement fail.
default=REQUIRED (which is also the value used when default= is
omitted) makes the member required: a configuration class has no
placeholder value for it. The load aborts with
:class:~advexp.settings_base.missing_value_error.MissingValueError when
the document holds nothing usable for the member, and reading a member that
was never loaded raises the same error. :data:NO_DEFAULT keeps the
meaning that it has in the whole family: the member is optional, and
None stands in for it.
aliases= declares additional full storage addresses. A load looks up
the primary address first, then the aliases in their order, and the first
address found wins. Every alias is read under the same path= and is
nested by its own dots.
The keyword parameters after aliases are the declared value constraints
(gt/ge/lt/le/eq/ne, min_length/max_length, regex,
validator, when). They are the same constraints that the base
attribute documents, and they are applied wherever a value reaches the
member.
advexp.configuration.plugins.toml_settings.TOMLSetting(*, name=None, default=REQUIRED, path=None, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Setting attribute: the value is read from the TOML configuration file.
name= is the address of the member in the document. Every dot in the
name (the table separator of TOML) separates a table level, and the last
part is the key. For example, name="net.host" reads from the [net]
table, and a name without a dot is a plain key. Without a name, the
{module}.{class}.{member} pattern is used, and its dots nest in the
same way. A literal key that contains a dot is reached through path=,
because the segments of a pointer are never split.
The optional path= locates the key in the document tree as a JSON
Pointer (RFC 6901): a leading / and slash-separated segments, where
~1 escapes / and ~0 escapes ~. For example:
path="/net/proxy"reads the setting from[net.proxy].path="/mirrors/1"reads it from the second element of themirrorsarray.path="/endpoints/api.internal"reads it from under a key that contains a dot.
A segment that consists only of digits indexes an array if the node it
steps into is an array, and names a table key otherwise. path="" and
path=None both mean the document root.
advexp.configuration.plugins.yaml_settings.yaml_setting(*, name=None, default=REQUIRED, path=None, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Declare a YAML-file member. This is the type-checker-friendly spelling of
YAMLSetting.
It builds exactly the attribute that the class builds, with the same
keyword parameters. Both spellings exist because of typing. Assigning an
attribute instance to an annotated member
(port: int = YAMLSetting(...)) is valid Python (the settings metaclass
replaces the attribute with a value descriptor), but type checkers cannot
see the replacement and report a type mismatch. They accept a plain
function that returns Any. The Any return also leaves default=
unchecked, deliberately: a default may be given in its storage form
(default="4" for an int member), and it is converted at class
creation, where a default that cannot be converted makes the class
statement fail.
default=REQUIRED (which is also the value used when default= is
omitted) makes the member required: a configuration class has no
placeholder value for it. The load aborts with
:class:~advexp.settings_base.missing_value_error.MissingValueError when
the document holds nothing usable for the member, and reading a member that
was never loaded raises the same error. :data:NO_DEFAULT keeps the
meaning that it has in the whole family: the member is optional, and
None stands in for it.
aliases= declares additional full storage keys. A load looks up the
primary key first, then the aliases in their order, and the first key
found wins. Every alias is read under the same path=.
The keyword parameters after aliases are the declared value constraints
(gt/ge/lt/le/eq/ne, min_length/max_length, regex,
validator, when). They are the same constraints that the base
attribute documents, and they are applied wherever a value reaches the
member.
advexp.configuration.plugins.yaml_settings.YAMLSetting(*, name=None, default=REQUIRED, path=None, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Setting attribute: the value is read from the YAML configuration file.
name= is the storage key and may contain any characters. A dot in it
stays inside the key: the name is flat and does not create a nesting level.
Without name=, the {module}.{class}.{member} pattern is used. The
optional path= locates the key in the document tree as a JSON Pointer
(RFC 6901): a leading / and slash-separated segments, where ~1
escapes / and ~0 escapes ~. For example:
path="/net/proxy"reads the setting from{net: {proxy: {...}}}.path="/mirrors/1"reads it from the second element of themirrorssequence.path="/endpoints/api.internal"reads it from under a key that contains a dot.
A segment that consists only of digits indexes a sequence if the node it
steps into is a sequence, and names a mapping key otherwise. path=""
and path=None both mean the document root.
advexp.configuration.plugins.environment.variables.environment_variable_setting(*, name=None, default=REQUIRED, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Declare an environment-variable member. This is the type-checker-friendly
spelling of EnvironmentVariableSetting.
It is the same attribute with the same keyword parameters. Only the
spelling differs, and the reason for it is typing. Assigning an attribute
instance to an annotated member is valid Python (the settings metaclass
replaces it with a value descriptor), but type checkers cannot see the
replacement and report a type mismatch. They accept a plain function that
returns Any, and the Any also prevents a default in storage form
(default="8080" for an int member) from being flagged.
default=REQUIRED (which is also the value used when default= is
omitted) makes the member required: a configuration class has no
placeholder value for it. The load aborts with
:class:~advexp.settings_base.missing_value_error.MissingValueError
when the variable holds nothing usable, and reading a member that was never
loaded raises the same error. :data:NO_DEFAULT keeps the meaning that it
has in the whole family: the member is optional, and None stands in for
it.
aliases= declares additional full variable names. A load looks up the
primary name first, then the aliases in their order, and the first name
found wins. This is how a rename is migrated: the old variable name keeps
working next to the new one.
The keyword parameters after aliases are the declared value constraints
(gt/ge/lt/le/eq/ne, min_length/max_length, regex,
validator, when). They are the same constraints that the base
attribute documents, and they are applied wherever a value reaches the
member.
advexp.configuration.plugins.environment.dotenv.dotenv_setting(*, name=None, default=REQUIRED, metadata=None, aliases=None, gt=None, ge=None, lt=None, le=None, eq=None, ne=None, min_length=None, max_length=None, regex=None, validator=None, when=None)
¶
Declare a dotenv-file member. This is the type-checker-friendly spelling
of DotenvSetting.
It is the same attribute with the same keyword parameters. Only the
spelling differs, and the reason for it is typing. Assigning an attribute
instance to an annotated member is valid Python (the settings metaclass
replaces it with a value descriptor), but type checkers cannot see the
replacement and report a type mismatch. They accept a plain function that
returns Any, and the Any also prevents a default in storage form
(default="8080" for an int member) from being flagged.
default=REQUIRED (which is also the value used when default= is
omitted) makes the member required: a configuration class has no
placeholder value for it. The load aborts with
:class:~advexp.settings_base.missing_value_error.MissingValueError
when the variable holds nothing usable, and reading a member that was never
loaded raises the same error. :data:NO_DEFAULT keeps the meaning that it
has in the whole family: the member is optional, and None stands in for
it.
aliases= declares additional full variable names. A load looks up the
primary name first, then the aliases in their order, and the first name
found wins.
The keyword parameters after aliases are the declared value constraints
(gt/ge/lt/le/eq/ne, min_length/max_length, regex,
validator, when). They are the same constraints that the base
attribute documents, and they are applied wherever a value reaches the
member.
Source builders¶
The links that a source chain is built from. They are passed as the
options= argument of the class statement, with one builder per family,
either inside Sources(...) or on its own.
advexp.configuration.plugins.json_settings.json_source(file, path=None, name_pattern=None, dynamic_file=None, dynamic_of=None, parameters=None)
¶
Declare a JSON file link, in the form used by this distribution.
Pass the result of the builder to the options= keyword of the class
statement, either alone (for a class that reads nothing else) or inside
Sources(...) as one link of the chain:
class AppConfig(Configuration, options=Sources(
env_source(name_prefix="APP_"),
json_source(file="app.json"),
)):
file names the file that the link reads. It may contain template slots
that are resolved through environment variables:
json_source(file="settings-{APP_ENV}.json", parameters=app_env) reads
the file of the environment that APP_ENV names. The parameters are
declared once next to the chain and are shared by every templated link.
path anchors every key of the link inside a subtree of the document, as
a JSON Pointer (RFC 6901). The path= of a member locates its key within
the subtree, and the path of the link is added in front of it:
json_source(file="app.json", path="/env/prod") reads members with
path="/server" from ["env"]["prod"]["server"]. A member that must
live outside the subtree needs a link of its own: the same file under
another path=.
name_pattern sets the pattern that builds the keys. If it is omitted,
the full {module}.{class}.{member} pattern is used. The link belongs to
the class that declares it: the own chain of a subclass replaces it for
that subclass only, and the base class keeps reading its own file.
The dynamic side reads the group of a writer. dynamic_of names the
group key of the writing class, which is the name under which its dynamic
entries are stored in the document ("app.LocalSettings" for a writer
class with the default name). Without it, the class reads no dynamic
entries. By default, the entries are in the file of the link (one document
for both sides). dynamic_file gives them a file of their own, which is
resolved through the same parameters if it contains template slots. The
class reaches the collection through the JSON dynamic plugin of
advexp.configuration.plugins.json_dynamic_settings.
advexp.configuration.plugins.toml_settings.toml_source(file, path=None, name_pattern=None, dynamic_file=None, dynamic_of=None, parameters=None)
¶
Declare a TOML file link, in the form used by this distribution.
Pass the result of the builder to the options= keyword of the class
statement, either alone (for a class that reads nothing else) or inside
Sources(...) as one link of the chain:
class AppConfig(Configuration, options=Sources(
env_source(name_prefix="APP_"),
toml_source(file="app.toml"),
)):
file names the file that the link reads. It may contain template slots
that are resolved through environment variables:
toml_source(file="settings-{APP_ENV}.toml", parameters=app_env) reads
the file of the environment that APP_ENV names.
path anchors every key of the link inside a subtree of the document, as
a JSON Pointer (RFC 6901). The path= of a member locates its key within
the subtree, and the path of the link is added in front of it.
name_pattern sets the pattern that builds the keys. If it is omitted,
the full {module}.{class}.{member} pattern is used, and its dots are the
table levels through which the address is nested. The link belongs to the
class that declares it: the own chain of a subclass replaces it for that
subclass only, and the base class keeps reading its own file.
The dynamic side reads the group of a writer. dynamic_of names the
group key of the writing class, which is the name under which its dynamic
entries are stored in the document ("app.LocalSettings" for a writer
class with the default name). Without it, the class reads no dynamic
entries. By default, the entries are in the file of the link (one document
for both sides). dynamic_file gives them a file of their own, which is
resolved through the same parameters if it contains template slots. The
class reaches the collection through the TOML dynamic plugin of
advexp.configuration.plugins.toml_dynamic_settings.
advexp.configuration.plugins.yaml_settings.yaml_source(file, path=None, name_pattern=None, dynamic_file=None, dynamic_of=None, parameters=None)
¶
Declare a YAML file link, in the form used by this distribution.
Pass the result of the builder to the options= keyword of the class
statement, either alone (for a class that reads nothing else) or inside
Sources(...) as one link of the chain:
class AppConfig(Configuration, options=Sources(
env_source(name_prefix="APP_"),
yaml_source(file="app.yaml"),
)):
file names the file that the link reads. The extension is not checked:
any path works. The name may contain template slots that are resolved
through environment variables:
yaml_source(file="settings-{APP_ENV}.yaml", parameters=app_env) reads
the file of the environment that APP_ENV names. The parameters are
declared once next to the chain and are shared by every templated link.
path anchors every key of the link inside a subtree of the document, as
a JSON Pointer (RFC 6901). The path= of a member locates its key within
the subtree, and the path of the link is added in front of it:
yaml_source(file="app.yaml", path="/env/prod") reads members with
path="/server" from ["env"]["prod"]["server"]. A member that must
live outside the subtree needs a link of its own: the same file under
another path=.
name_pattern sets the pattern that builds the keys. If it is omitted,
the full {module}.{class}.{member} pattern is used. The link belongs to
the class that declares it: the own chain of a subclass replaces it for
that subclass only, and the base class keeps reading its own file.
The dynamic side reads the group of a writer. dynamic_of names the
group key of the writing class, which is the name under which its dynamic
entries are stored in the document ("app.LocalSettings" for a writer
class with the default name). Without it, the class reads no dynamic
entries. By default, the entries are in the file of the link (one document
for both sides). dynamic_file gives them a file of their own, which is
resolved through the same parameters if it contains template slots. The
class reaches the collection through the YAML dynamic plugin of
advexp.configuration.plugins.yaml_dynamic_settings.
advexp.configuration.plugins.environment.variables.env_source(name_prefix=None, name_case=None, nested_delimiter=None)
¶
Declare a process-environment link, in the form used by this distribution.
Pass the result of the builder to the options= keyword of the class
statement, either alone (for a class that reads nothing else) or inside
Sources(...) as one link of the chain:
class AppConfig(Configuration, options=Sources(
env_source(name_prefix="APP_", name_case=NameCase.UPPER),
json_source(file="app.json"),
)):
The naming parameters name_prefix, name_case and
nested_delimiter declare the naming dictionary of the family for this
class. A class has only one seat for these rules, and every environment
link of the chain shares it (the process environment and a .env file
form one namespace). Declaring the parameters on two environment links in
one options= makes the class statement fail, because two records of
one dictionary could drift apart through a typo. A link without naming
parameters reads the dictionary that the nearest environment link above it
declared. If there is none, member names are used as written, with no
prefix. The dictionary belongs to the class that declares it: a subclass's
own link with naming parameters adds a dictionary of its own, and the
members of the base class keep theirs.
advexp.configuration.plugins.environment.variables.NameCase
¶
The letter-case regime of the member-name part of an environment name.
This is the spelling of the name_case option in code:
name_case=NameCase.UPPER.
UPPERandLOWERchange the case of the member name in every built name.ANYleaves the built name as declared, and makes its lookup case-insensitive instead.
It is a str enum, because its members also serve as the values of the
stored data form. The class metadata keeps the plain
"upper"/"lower"/"any" strings, and the raw metadata channel (a
hand-written entry, or a runtime set_class_metadata call) accepts both
spellings. A member compares equal to its string.
advexp.configuration.plugins.environment.dotenv.dotenv_source(file, name_prefix=None, name_case=None, nested_delimiter=None, parameters=None)
¶
Declare a .env file link, in the form used by this distribution.
Pass the result of the builder to the options= keyword of the
class statement, either alone (for a class that reads nothing else) or
inside Sources(...) as one link of the chain:
class AppConfig(Configuration, options=Sources(
env_source(name_prefix="APP_", name_case=NameCase.UPPER),
dotenv_source(file="app.env"),
)):
file names the file that the link reads. A missing file behaves as an
empty one: required members are reported, and defaults stand in. The name
may contain template slots that are filled in from environment variables:
dotenv_source(file=".env-{APP_ENV}", parameters=app_env) reads the file
of the environment that APP_ENV names. The naming parameters
name_prefix, name_case, and nested_delimiter declare the naming
dictionary of the family for this class, which has only one seat for these
rules; see
:func:advexp.configuration.plugins.environment.variables.env_source for
the rules of the seat. The link belongs to the class that declares it: a
subclass's own chain replaces it for that subclass only, and the base class
keeps reading its own file.
advexp.configuration.plugins.mapping_source.mapping_source(mapping=None)
¶
Declare a mapping link: a dictionary that is read as a source.
Pass the result of the builder to the options= keyword of the class
statement. You can pass it alone (for a class whose members all come from
the dictionary) or inside Sources(...) as the bottom layer of the
chain, which is the last place a load looks before the declared defaults:
BASE_DEFAULTS = {"token": "dev-token", "port": 8080}
class AppConfig(Configuration, options=Sources(
env_source(name_prefix="APP_"),
json_source(file="app.json"),
mapping_source(BASE_DEFAULTS),
)):
token: str
The dictionary that is passed in is copied at the call, so a later change
to the caller's object never reaches the chain. A value that a load finds
is served as a fresh copy in the same way. The dictionary uses member names
only: an explicit name= or aliases= of a member changes nothing
here, and a key that names no member is dropped, with a debug record. This
form is not meant for a dictionary that the application changes between
loads; the load overrides serve that purpose.
If the builder is called without a dictionary, it declares the live form
instead. The chain gets an empty mapping layer, and the application fills
its dictionary at runtime through
mapping_source_for(AppConfig).set({...}). A load that runs before any
set passes over the layer as a miss. In both forms the source works in
the same way: it uses member names only, serves fresh copies of the values,
and is never written back.
advexp.configuration.plugins.mapping_source.mapping_source_for(cls)
¶
The live mapping source of cls: the chain link that set() fills.
Declare the link with mapping_source() (the builder called without a
dictionary), and then reach it from the class:
The resolver reads the nearest chain. A subclass without a chain of its
own shares the mapping source of its base class: one dictionary, one
set. The resolver raises an error that names the class in these cases:
the class was not built by the family, the class has no chain, the chain
has no live link (the form that takes a dictionary has no handle for
filling it), or the chain has more than one live link.
advexp.configuration.plugins.mapping_source.MappingSourceLink()
¶
The link of the live mapping source: a dictionary that is filled at runtime.
The link carries the serializer of the mapping source and its dictionary
as the own parameter of the link. This is the same place where the
dictionary of the static form sits, and it is served in the same way: by
member names only, with every value returned as a fresh copy. The
dictionary starts empty, and set(mapping) is the only way to write to
it; nothing else changes it.
set(mapping)
¶
Replace the dictionary with mapping. The link stores a copy, not a view.
The replacement is complete: keys that the new dictionary does not contain are removed, whatever the previous dictionary held. The dictionary is deep-copied on the way in, so afterwards the caller's object and the state of the link share nothing. In the same way, a load serves every value that it finds as a fresh copy. A load reads the dictionary once, at its start, so a replacement made while a load is running takes effect from the next load.
Path parameters¶
advexp.configuration.env_parameter(variable, choices, default=None, slot=None)
¶
Declare one path parameter that is read from an environment variable.
Pass the result to the parameters= of a builder of a source that carries
a file, either alone or in a tuple:
app_env = env_parameter("APP_ENV", choices=("dev", "test", "prod"))
class AppConfig(Configuration, options=Sources(
json_source(file="app.json"),
json_source(file="settings-{APP_ENV}.json", parameters=app_env),
)):
variable names the environment variable, and the template spells the
slot by that name: settings-{APP_ENV}.json.
slot= renames the slot if the template spells another name. The value
still comes from variable, and the template spells the renamed slot, so
slot="env" reads APP_ENV into settings-{env}.json. This is
useful for a template that dictates its own slot names (a file shared with
another system, or a path that reads better with a short slot). The name
must be a valid identifier: otherwise, the braces would stay as literal text
instead of becoming a slot.
choices is the obligatory whitelist of which the value of the variable
must be one. It is checked before any file is touched.
default is the value to which an unset variable resolves. It must be one
of the choices. Without it, an unset variable breaks the link at load time.
Module file declarations¶
The reading defaults of the dynamic side can have a file of their own, just
as the backends of the settings package can. A module declares its own file
with for_this_module, and the process-wide attribute provides the file
for all other modules.
advexp.configuration.for_this_module(value)
¶
Declare the value of a parameter for the module in which this call is written.
Write the call in the assignment itself, at the top level of the module:
The function names the calling module at the call, and the metaclass of the holder consumes the marker at the moment when the assignment executes, during the import of the module. If you wrap the call in a helper of another module, it would name that other module instead: the declaration belongs to the assignment that it addresses.
The call is typed as Any on purpose: the marker can be assigned to every
parameter that a holder declares, whatever the own type of the parameter is,
and the runtime validates the value at the line of the assignment.
advexp.configuration.declared_module_defaults()
¶
Return a snapshot of every module declaration: (holder, parameter,
module) keys over the declared values.
This is the reader for tools that collect the declarations without running the application. Importing the settings modules fills the registry, because a declaration at the module level executes at import, and one call hands everything to the collector. The snapshot is a deep copy: changing it changes nothing in the registry.
advexp.configuration.plugins.json_dynamic_settings.JSONDynamicSettingsConfiguration
¶
Dynamic JSON configuration reading (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Own file for the dynamic entries this package reads (str or
os.PathLike).
When set, every dynamic collection without a binding of its own reads this file, apart from the file of the declared members; the document inside is shaped by the section parameters of the reading plugin, placed exactly as the writing side laid it out.
A module can declare the dynamic entries' own file for itself alone —
at the module's top level, with for_this_module from the package
root:
The declaration holds for the classes of the module it is written in
and wins over this attribute; modules without one keep reading the
attribute. When both are unset (the default), the dynamic entries are
read from the class's source file — the link's own file=: the
one-document default the writing side writes by default too.
advexp.configuration.plugins.toml_dynamic_settings.TOMLDynamicSettingsConfiguration
¶
Dynamic TOML configuration reading (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Own file for the dynamic entries this package reads (str or
os.PathLike).
When set, every dynamic collection without a binding of its own reads this file, apart from the file of the declared members; the document inside is shaped by the section parameters of the reading plugin, placed exactly as the writing side laid it out.
A module can declare the dynamic entries' own file for itself alone —
at the module's top level, with for_this_module from the package
root:
The declaration holds for the classes of the module it is written in
and wins over this attribute; modules without one keep reading the
attribute. When both are unset (the default), the dynamic entries are
read from the class's source file — the link's own file=: the
one-document default the writing side writes by default too.
advexp.configuration.plugins.yaml_dynamic_settings.YAMLDynamicSettingsConfiguration
¶
Dynamic YAML configuration reading (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Own file for the dynamic entries this package reads (str or
os.PathLike).
When set, every dynamic collection without a binding of its own reads this file, apart from the file of the declared members; the document inside is shaped by the section parameters of the reading plugin, placed exactly as the writing side laid it out.
A module can declare the dynamic entries' own file for itself alone —
at the module's top level, with for_this_module from the package
root:
The declaration holds for the classes of the module it is written in
and wins over this attribute; modules without one keep reading the
attribute. When both are unset (the default), the dynamic entries are
read from the class's source file — the link's own file=: the
one-document default the writing side writes by default too.
Dynamic readers¶
The read-only dynamic plugins, which read a collection previously written by Advexp.Settings, are located in their plugin modules. They are described, together with the rest of the dynamic settings, on the Dynamic settings page.
advexp.configuration.plugins.json_dynamic_settings.JSONDynamicSettingsPlugin()
¶
Plugin object for reading dynamic settings from the JSON file.
The namespaced pin names the backend. The collection reads the dynamic
entries that a writing class of the settings distribution stored in the
JSON document. The group key of the writer, declared next to the source
binding of the class (json_source(dynamic_of=...)), names the group;
the file levels and the inherited section parameters place it exactly as
the writer laid it out. The pin is the own registry slot of the dynamic
side: the binding ties it to the JSONSettingsSerializer of this package
and never touches the registration of JSONSetting by the static source
plugin.
advexp.configuration.plugins.toml_dynamic_settings.TOMLDynamicSettingsPlugin()
¶
Plugin object for reading dynamic settings from the TOML file.
The namespaced pin names the backend. The collection reads the dynamic
entries that a writing class of the settings distribution stored in the
TOML document. The group key of the writer, declared next to the source
binding of the class (toml_source(dynamic_of=...)), names the group;
the file levels and the inherited section parameters place it exactly as
the writer laid it out. The pin is the own registry slot of the dynamic
side: the binding ties it to the TOMLSettingsSerializer of this package
and never touches the registration of TOMLSetting by the static source
plugin.
advexp.configuration.plugins.yaml_dynamic_settings.YAMLDynamicSettingsPlugin()
¶
Plugin object for reading dynamic settings from the YAML file.
The namespaced pin names the backend. The collection reads the dynamic
entries that a writing class of the settings distribution stored in the
YAML document. The group key of the writer, declared next to the source
binding of the class (yaml_source(dynamic_of=...)), names the group;
the file levels and the inherited section parameters place it exactly as
the writer laid it out. The pin is the own registry slot of the dynamic
side: the binding ties it to the YAMLSettingsSerializer of this package
and never touches the registration of YAMLSetting by the static source
plugin.
Errors¶
advexp.configuration.AggregateLoadError(errors, fallbacks=(), misses=(), channel=None)
¶
The error that a failed load raises.
The pass walks every member before aborting, so one run reports every failure at once. The error has three groups of entries:
errorsholds the individual exceptions that abort the pass, in declaration order: a required member that got no usable value, or a cross-member violation that no reset to the declared defaults could settle.fallbacksholds the errors of the optional members whose sources provided a value that the members could not take (an unconvertible or invalid value). These members stand at their defaults.missesholds the optional members whose sources provided no value at all. These members also stand at their defaults.
These are the structural marks that tell the three groups apart. The message
carries each entry as a line. It opens with the channel of the distribution
that ran the pass (channel, the name under which its records are
logged), spelled as <channel> error: the load failed for ..., so an
abort that the application prints names its package even away from the log.
Every load abort is this class, however many members failed: the type of the exception never depends on the number of failures. A class in which all members are optional aborts only on a violation that the resets cannot settle. Otherwise, its load report is the tuple that the loading method returns, and the misses stay out of it (a miss is the normal way in which a default gets used). The misses are attached only to an abort, where the reader of the failure wants to see the whole class at once.
The class is a subclass of
:class:~advexp.settings_base.member_load_error.MemberLoadError, so a
family-wide except MemberLoadError (or except ValueError) handler
keeps catching load aborts.
advexp.configuration.MemberLoadError(message, member_name=None)
¶
A member could not get a valid value from its source.
This is the parent of the load-time reasons: the source provided no value, a value that cannot be converted, a constraint violation, or a failed source. It is also the parent of the aggregate abort of the load pass.
Where the reason is unknown, the error is raised directly. For example,
reading a member that was never loaded serves its default, and a member that
is declared :data:~advexp.settings_base.required.REQUIRED has none to
serve.
The error names a problem, not a policy: the declaration of a member decides
the channel. The load error of a required member is an entry of the load
abort. The load error of an optional member is an entry of the load report
that the loading method returns, and the member itself stands at its default.
Subclassing :class:ValueError keeps a plain except ValueError working:
the value errors of the family are all subclasses of ValueError.
member_name carries the member that the error is about, when the one who
raises the error knows it. The required-read error of the member descriptor
always knows it, so a handler can report the finding on that member without
parsing the message. Errors that are raised without it leave it None.
advexp.configuration.MissingValueError(message, member_name=None)
¶
The load error of a member for which the source provided no value.
For a required member, this error goes into the errors of the load
abort. An optional member treats a miss as the normal way of serving its
default. Its miss error is attached only to an abort, in the misses of
the aggregate, and it stays out of the report of a successful run.
The error is an entry of the load abort
(:class:~advexp.settings_base.aggregate_load_error.AggregateLoadError).
The message names the member, its storage name, and the class, so the reader
knows exactly what to provide. The storage name is the own spelling of the
source when it offers one (for example, the name of an environment
variable), and otherwise it is the storage name that the pattern built.
advexp.configuration.ConversionError(message, member_name=None)
¶
The value of a member did not convert to the annotated type.
The source had a value for the member, but the value cannot take the
annotated shape of the member. The error names the member, its setting, and
the class, and it shows the value and the type to which the value would not
convert (the value is masked when the member holds a secret). For a
required member, it is an entry of the load abort
(:class:~advexp.settings_base.aggregate_load_error.AggregateLoadError).
For an optional member, it is an entry of the load report that the loading
method returns, and the member stands at its default.
advexp.configuration.ConstraintViolationError(message, member_name=None)
¶
The value of a member violates the constraints that are declared on it.
The value was converted, but the own declared bounds of the member, or a
rule across members, reject it. The error names the member, its setting,
and the class, and it lists every violated constraint. For a required
member, it is an entry of the load abort
(:class:~advexp.settings_base.aggregate_load_error.AggregateLoadError).
For an optional member, it is an entry of the load report that the loading
method returns, and the member stands at its default.
advexp.configuration.SourceLoadError(message, member_name=None)
¶
The source of a member raised an error while loading its value.
The message names the member, its setting, and the class, and it carries the own error of the source (which also stays chained as the cause).
The handling depends on the kind of error and on the member:
- A source that reports its problems through the user-facing channel (a corrupt or an unreadable file) is handled in the same way on a required member, with its exception carried in the message. On an optional member, it keeps propagating as its own exception, because a served default must not hide it.
- A plain error of the source (whatever the backend raised without wrapping it) is an entry of the load report that the loading method returns for an optional member, next to the log record that the pass keeps.
advexp.configuration.PluginNotEnabledError
¶
A setting attribute has no registered serializer (plugin not imported).
Sentinels¶
advexp.configuration.REQUIRED = _Required()
module-attribute
¶
The default that makes a member required: its value must come from a source.
Declared as the default (default=REQUIRED). Every boundary that would
serve the default — a load that found nothing usable in the source, a read
of a never-loaded member — raises
:class:~advexp.settings_base.member_load_error.MemberLoadError
instead: the load-time reasons are entries of the load pass's aggregate
abort. The family-wide spelling of an optional member is
:data:~advexp.settings_base.no_default.NO_DEFAULT: None stands in,
and its load problems land in the report the loading method returns.
Secret wrappers¶
advexp.configuration.SecretStr(value)
¶
A string whose repr and str return the mask, not the value.
This is the annotated type of a member that carries a secret. Loading wraps
the string of the source into it. A casual print, a log record, or an error
message therefore shows '**********'. The only way to get the value is
:meth:get_secret_value, which is an explicit step that is easy to grep for.
The constraint checks look at the inner string, while their messages keep the mask. The storage form is the plain string: the save path unwraps the value before the serializers see it. The masking is carried by the wrapper: a plain string that is assigned to the member keeps the semantics of a plain string.
get_secret_value()
¶
Return the wrapped value. This call is the explicit step to take, and it is easy to grep for.
advexp.configuration.SecretBytes(value)
¶
Bytes whose repr and str return the mask, not the value.
This is the annotated type of a member that carries a secret. Loading wraps
the payload of the source into it (a string is encoded as UTF-8, as the bytes
conversion does). A casual print, a log record, or an error message
therefore shows '**********'. The only way to get the value is
:meth:get_secret_value, which is an explicit step that is easy to grep for.
The constraint checks look at the inner payload, while their messages keep the mask. The storage form is the plain bytes: the save path unwraps the value before the serializers see it. The masking is carried by the wrapper: plain bytes that are assigned to the member keep the semantics of plain bytes.
get_secret_value()
¶
Return the wrapped value. This call is the explicit step to take, and it is easy to grep for.