Settings API¶
The application-facing API of the settings package.
advexp.settings.Settings
¶
Base class for typed application settings.
All operations are class-level: the settings class itself is the singleton.
Instances are supported for reads and writes, but they share the class
storage. :meth:to_object and :meth:update_from_object copy values out
to a detached instance of the class and back from it, and :meth:to_dict
and :meth:update_from_dict do the same with a plain dict.
Every method that addresses one setting by name (the single-setting passes
and the setting-metadata methods) also accepts a getter lambda that selects
the member: save_setting(lambda s: s.port). The lambda is inspected,
never called.
load_settings()
classmethod
¶
Load all declared settings from their storages and report the fallbacks.
The behavior matches
:meth:advexp.configuration.Configuration.load_configuration: both use the
one load pass of the family.
Every member is loaded, whatever happened to the previous one. A member
declared :data:~advexp.settings.REQUIRED whose storage provides nothing
usable records an error. The error names the cause (no value, a value that
cannot be converted, a constraint violation, or a failed storage), the
member, its setting, and the class. An optional member (one declared with a
value, with NO_DEFAULT, or without default=) 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.settings.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 storage
condition (a corrupt or inaccessible storage) 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.
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 storages produced or its declared default. A value from an earlier load therefore never survives when the current storages 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 storage 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.
save_settings()
classmethod
¶
Save the current values of all declared settings.
Ordinary backend errors are logged and skipped. A user-facing error (a broken storage) propagates and aborts the whole pass, so the settings declared after the failing one (including those of other backends) stay unsaved.
delete_settings()
classmethod
¶
Delete all declared settings from their storages and reset them to their defaults.
Ordinary backend errors are logged and skipped. A user-facing error
propagates and aborts the whole pass (other backends included). A member
declared :data:~advexp.settings.REQUIRED has no default to reset to:
once its storage entries are gone, the member is unset, and reading it
raises an error, as before any load.
reset_settings()
classmethod
¶
Reset all declared settings to their defaults (no storage access).
A member declared :data:~advexp.settings.REQUIRED has no default to
serve, so the pass clears its current value instead. This leaves the
member in the never-loaded state, in which reading it raises an error:
the state that a fresh declaration has before any load. The other members
are reset in the normal way, whatever the position of the required member
in the declaration order.
validate_settings()
classmethod
¶
Check every declared setting against all its constraints.
This is the strict manual check, in the same family as the load and save
passes. In one pass, it checks the field constraints (with their when=
conditions resolved against the current values), the cross-member rules (a
comparison bound that references another member), and the __validate__
hook. Every violation of every member is collected into one
:class:~advexp.settings_base.setting_validation_error.SettingValidationError,
which carries the per-member report in its problems attribute. A CLI or
a test can read this report as data. Nothing changes: values and storages
stay exactly as they were. If everything passes, nothing is raised:
load_setting(setting_name)
classmethod
¶
Load one setting from its storage and report the fallback of the member.
setting_name is the member's name or a getter lambda that selects it:
load_setting(lambda s: s.port).
The return value is the row of this member in the report that
:meth:load_settings returns:
None, if the member got its value, or got its default in the normal way (the storage held nothing for it).- A :class:
~advexp.settings.MemberLoadError(:class:~advexp.settings.SourceLoadError, :class:~advexp.settings.ConversionError, or :class:~advexp.settings.ConstraintViolationError), if a member with a default fell back to it. This is the same entry that the report of the class pass would carry.
A member declared :data:~advexp.settings.REQUIRED raises an error
instead. There is no aggregate for a single member, so the caller sees the
own error of the member (for example, a failed source, or a read of a
never-loaded member). A user-facing storage condition is raised unwrapped,
as in the class pass.
save_setting(setting_name)
classmethod
¶
Save the current value of one setting.
setting_name is the member's name or a getter lambda selecting
it: save_setting(lambda s: s.port).
delete_setting(setting_name)
classmethod
¶
Delete one setting from its storage and reset it to its default.
setting_name is the member's name or a getter lambda that selects it:
delete_setting(lambda s: s.port). A member declared
:data:~advexp.settings.REQUIRED is left unset after the delete: it has no
default to stand at, so reading it raises an error, as before any load.
reset_setting(setting_name)
classmethod
¶
Reset one setting to its default (no storage access).
setting_name is the member's name or a getter lambda that selects it:
reset_setting(lambda s: s.port). A member declared
:data:~advexp.settings.REQUIRED is cleared instead: it has no default to
stand at, so reading it raises an error, as before any load.
contains_setting(setting_name)
classmethod
¶
Return whether the setting's storage holds a value.
setting_name is the member's name or a getter lambda selecting
it: contains_setting(lambda s: s.port).
to_dict()
classmethod
¶
Return a snapshot of all declared settings, keyed by member name.
Values are deep-copied, so changing the snapshot never affects the class (and vice versa). Members that were never assigned or loaded report their effective default. Secure settings are included as they are, so treat the snapshot accordingly:
update_from_dict(data, *, ignore_unknown=False)
classmethod
¶
Set declared settings from a mapping keyed by member name.
Values are converted to the annotated types exactly as on load
("8080" for an int member becomes 8080). Then the whole update
is checked against every declared constraint over the merged state (the
new values together with the current ones): the own constraints of each
member with its when= condition, the cross-member rules, and the
__validate__ hook. If there is a violation, every finding is collected
into one :class:SettingValidationError and nothing is applied. The values
that are passed are deep-copied, so the class storage never shares mutable
state with the mapping.
Keys that are not declared settings raise KeyError. The update is a
mutator, and a mistyped name should fail loudly, instead of hiding in a
partial update. For a dictionary from elsewhere (for example, the
model_dump() of another model that has fields this class never
declared), pass ignore_unknown=True instead: the unknown keys are
skipped, with a debug record, and the declared ones are applied.
Storages are not written. Call :meth:save_settings to persist the
values:
to_object()
classmethod
¶
Return a snapshot of all declared settings as a detached instance.
The returned instance is of the settings class itself, so its fields carry
the declared names and annotations. If you rename a setting, every stale
field access on a snapshot becomes a type-checker error (and an
AttributeError at runtime). The instance holds its own deep-copied
values: reading or writing them never affects the class, and vice versa.
Members that were never assigned or loaded report their effective default.
Secure settings are included as they are, so treat the snapshot
accordingly. Apply the snapshot back with :meth:update_from_object:
update_from_object(obj, *, ignore_unknown=False)
classmethod
¶
Set declared settings from the fields of an object.
The object is usually a snapshot from :meth:to_object. Any object with a
plain attribute namespace also works (for example,
SimpleNamespace(**to_dict()) or a dataclass instance), and the field
names are the member names. Values are converted to the annotated types,
and the whole update is checked over the merged state, exactly as in
:meth:update_from_dict. The values are then deep-copied.
Field names that are not declared settings raise KeyError.
ignore_unknown=True skips them in the same way, with a debug record;
use it for an object from elsewhere whose namespace has fields that this
class never declared. A failed conversion or check applies nothing.
Storages are not written. Call :meth:save_settings to persist the
values:
get_plugin(plugin_class)
classmethod
¶
Return the plugin instance bound to this settings class (cached).
set_setting_metadata(setting_name, metadata_name, metadata_value)
classmethod
¶
Store metadata for a setting on this class, keyed by member name.
None is a value like any other; removing an entry is the job of
:meth:delete_setting_metadata. The entry is stored in the own storage of
this class, and the readers along the chain (this class and its
subclasses) resolve it live. A setting reference that the class does not
declare raises KeyError; every setting-metadata operation applies the
same check. Values are deep-copied in both directions, so the stored
metadata and the objects of the caller never share mutable state.
get_setting_metadata(setting_name, metadata_name, default=None)
classmethod
¶
Read one metadata entry of a setting; the nearest entry wins.
The ChainMap rule applies along the inheritance chain of the member (from this class down to the class that declared the member): the most derived entry that holds the key wins, whichever side declared or wrote it.
get_setting_all_metadata(setting_name)
classmethod
¶
Return every metadata entry of one setting, keyed by metadata name.
This is the snapshot form of :meth:get_setting_metadata, and the way to
enumerate everything that the setting carries along its chain. For each
key, the most derived entry wins. A runtime write adds keys on top of the
inherited ones without removing them. A member that is redeclared in a
subclass carries only the metadata of the redeclaration, because its chain
ends at the subclass. The dict and its values are deep-copied. A setting
with nothing stored anywhere in its chain returns an empty dict.
setting_name accepts a getter in place of the name string. A member
that the class does not declare raises KeyError, the same check that
every setting-metadata operation applies.
contains_setting_metadata(setting_name, metadata_name)
classmethod
¶
Return whether a metadata entry of the setting is stored under the name.
This is the unambiguous form of :meth:get_setting_metadata. get
returns its default both for a missing entry and for a stored None,
while contains tells the two cases apart. It checks the whole chain,
with the same resolution that get applies. setting_name accepts a
getter in place of the name string, like the other setting operations.
delete_setting_metadata(setting_name, metadata_name)
classmethod
¶
Remove the own entry of the class for a setting; a missing entry is not an error.
Only what this class itself declared or stored is removed. An entry of the
declaring class or of another class in the chain stays visible, in the same
way that deleting an attribute of a subclass makes the lookup fall back to
the attribute of the base class. The method returns whether the class had
an entry of its own to remove. An entry that was seeded by the metadata=
declaration of the member is removed in the same way: the declaration seeds
the storage only once, and the entry does not come back.
set_class_metadata(metadata_name, metadata_value)
classmethod
¶
Store class-level metadata on this class; None is a value like any
other.
The entry is stored in the own storage of this class, and the readers along
the inheritance chain (this class and its subclasses) resolve it live. The
write reaches every subclass at once, while the base classes are never
touched. Removing an entry is the job of :meth:delete_class_metadata.
Values are deep-copied in both directions, so the stored metadata and the
objects of the caller never share mutable state.
get_class_metadata(metadata_name, default=None)
classmethod
¶
Read a class-level metadata entry; the nearest entry wins.
The rule is the same as for class attribute lookup: the own storage of this class first, then the storages of the base classes. Declarations and runtime writes are resolved alike, and they are resolved live, so a runtime write into a base class is what a subclass reads.
get_class_all_metadata()
classmethod
¶
Return every class-level metadata entry, keyed by metadata name.
This is the snapshot form of :meth:get_class_metadata, and the way to
enumerate everything that the class carries: its own entries and those of
its chain, in one dict of deep-copied values. A class with nothing stored
anywhere in its chain returns an empty dict.
contains_class_metadata(metadata_name)
classmethod
¶
Return whether a class-level metadata entry is stored under the name.
This is the unambiguous form of :meth:get_class_metadata. get returns
its default both for a missing entry and for a stored None, while
contains tells the two cases apart. It checks the whole inheritance
chain, with the same resolution that get applies.
delete_class_metadata(metadata_name)
classmethod
¶
Remove the own class-level metadata entry of the class; a missing entry is not an error.
Only what this class itself declared or stored is removed. An entry of an ancestor stays visible, in the same way that deleting an attribute of a subclass makes the lookup fall back to the attribute of the base class. The method returns whether the class had an entry of its own to remove. An entry that was seeded by the declaration in the class body is removed in the same way: the declaration seeds the storage only once, and the entry does not come back.
delete_class_all_metadata()
classmethod
¶
Remove every own class-level metadata entry of the class.
Only the own storage of the class is cleared. The entries of the ancestors stay visible to the readers of the chain: clearing a subclass does not clear its base classes. The iteration order of a dynamic plugin and the entries that were seeded by the declaration in the class body are own entries of the class, and they are removed with the rest.
Library configuration¶
advexp.settings.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.
Attribute parameters¶
The forms that a member's declaration can take. The plain parameter and
the non-storage parameter 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.settings.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 own source binding of the class resolves. This is
the type-checker-friendly spelling of :class:Setting.
It is the rich form of the plain declaration: the member reads the one
source that its class declares through options=, and the family of the
binding builds its address. If default= is omitted (or is REQUIRED),
the member is required: the source must provide its value, or the load
aborts.
name= is the full literal address in the storage of the member: the
environment prefix, the case, and the file 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 order.
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 source
provides, and they are the same constraints that every member factory of
the family applies.
advexp.settings.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)
¶
The attribute of a member that the own source binding of the class resolves.
The attribute names the setting, not the storage. A member declared through
it has no backend of its own: it reads the one source that the class
declares through options=. Both the plain declaration (token: str)
and the rich one (:func:setting) produce this attribute. The family of
the binding builds the address of the member by its own rules (the
environment prefix and case, the JSON key pattern), so the member is
addressed according to the storage that it actually reads. A declared
name= is a full literal address in that storage.
advexp.settings.non_storage_setting(*, name=None, default=NO_DEFAULT, 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 member without a storage. This is the type-checker-friendly spelling.
This is the own declaration of the core: a member that no storage ever
serves. Its value is the declared default or the last assignment, and the
load pass resets it to the declared default. Save and delete skip it, while
update_from_dict and update_from_object set it like any other
member, and to_dict includes it. A plain function that returns Any
is the spelling that mypy accepts; the factories of the individual backends
(json_setting and the like) use the same form.
default= is required: a member that no storage ever serves must state
its value, and the factory refuses a declaration without one.
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. They apply wherever a
value reaches the member: declared defaults, assignment, load,
update_from_dict and update_from_object, and save.
A comparison parameter also accepts the attribute object of a member that
was declared earlier in the same class body. This is a member reference
(ge=min_port). It is compared with the current value of that member
wherever the whole class is checked at once (class creation, load, update,
save, :meth:~advexp.settings.Settings.validate_settings). when= takes
only such a reference: while the referenced member is falsy, none of the
constraints of this member apply. Rules that a comparison cannot state
(for example, conditional requirements or "at least one of") belong to the
__validate__ hook of the settings class.
advexp.settings.plugins.json_settings.json_setting(*, name=None, default=NO_DEFAULT, 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 setting. 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.
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 placed 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
:func:~advexp.settings.setting documents, and they are applied wherever a
value reaches the member.
advexp.settings.plugins.json_settings.JSONSetting(*, name=None, default=NO_DEFAULT, 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 stored in the JSON settings 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"stores the setting inside{"net": {"proxy": {...}}}.path="/mirrors/1"stores it inside the second element of themirrorsarray.path="/endpoints/api.internal"stores it 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.settings.plugins.toml_settings.toml_setting(*, name=None, default=NO_DEFAULT, 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 setting. 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.
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 placed 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
:func:~advexp.settings.setting documents, and they are applied wherever a
value reaches the member.
advexp.settings.plugins.toml_settings.TOMLSetting(*, name=None, default=NO_DEFAULT, 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 stored in the TOML settings file.
name= is the address of the setting 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" stores the key inside 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"stores the setting inside[net.proxy].path="/mirrors/1"stores it inside the second element of themirrorsarray.path="/endpoints/api.internal"stores it 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.settings.plugins.yaml_settings.yaml_setting(*, name=None, default=NO_DEFAULT, 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 setting. 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.
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 placed 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
:func:~advexp.settings.setting documents, and they are applied wherever a
value reaches the member.
advexp.settings.plugins.yaml_settings.YAMLSetting(*, name=None, default=NO_DEFAULT, 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 stored in the YAML settings 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"stores the setting inside{"net": {"proxy": {...}}}.path="/mirrors/1"stores it inside the second element of themirrorssequence.path="/endpoints/api.internal"stores it 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.settings.plugins.environment.variables.environment_variable_setting(*, name=None, default=NO_DEFAULT, 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 setting. 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.
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 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
:func:~advexp.settings.setting documents, and they are applied wherever a
value reaches the member.
advexp.settings.plugins.environment.dotenv.dotenv_setting(*, name=None, default=NO_DEFAULT, 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 setting. 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.
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
:func:~advexp.settings.setting documents, and they are applied wherever a
value reaches the member.
Source builders¶
The only way to bind a class to its storage. A builder is passed as the
options= argument of the class statement. Use one builder per family; a
class can use as many families as it has storages.
advexp.settings.plugins.json_settings.json_source(file=None, name_pattern=None, dynamic_file=None, parameters=None)
¶
Bind a settings class to its JSON file storage, 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 keeps everything in one JSON file)
or next to the builders of other families in one options= sequence:
file names the file of the declared members of the class. A builder
without file uses the module default of the backend. The name 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 binding and are shared by every templated path of
the class.
name_pattern sets the pattern that builds the keys. If it is omitted,
the full {module}.{class}.{member} pattern is used. The explicit
name= of a member is the full address in the document and never passes
through the pattern.
dynamic_file moves the dynamic entries of the class (the settings with
names given at runtime) into a JSON file of their own. A class without it
keeps them in the static file. The name may contain template slots in the
same way as file, filled from the same parameters.
The binding is a metadata entry of the class itself. A subclass without its
own json_source inherits the nearest one above it, and a subclass with
its own json_source switches to it for itself and its subclasses. The
base class keeps reading its own file. Two json_source builders in one
options= make the class statement fail, as for every metadata name
that is declared twice.
advexp.settings.plugins.toml_settings.toml_source(file=None, name_pattern=None, dynamic_file=None, parameters=None)
¶
Bind a settings class to its TOML file storage, 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 keeps everything in one TOML
file) or next to the builders of other families in one options=
sequence:
file names the file of the declared members of the class. A builder
without file uses the module default of the backend. The name 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. The parameters are
declared once next to the binding and are shared by every templated path of
the class.
name_pattern sets the pattern that builds the keys. If it is omitted,
the full {module}.{class}.{member} pattern is used. The explicit
name= of a member is the full address in the document and never passes
through the pattern.
dynamic_file moves the dynamic entries of the class (the settings with
names given at runtime) into a TOML file of their own. A class without it
keeps them in the static file. The name may contain template slots in the
same way as file, filled from the same parameters.
The binding is a metadata entry of the class itself. A subclass without its
own toml_source inherits the nearest one above it, and a subclass with
its own toml_source switches to it for itself and its subclasses. The
base class keeps reading its own file. Two toml_source builders in one
options= make the class statement fail, as for every metadata name
that is declared twice.
advexp.settings.plugins.yaml_settings.yaml_source(file=None, name_pattern=None, dynamic_file=None, parameters=None)
¶
Bind a settings class to its YAML file storage, 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 keeps everything in one YAML
file) or next to the builders of other families in one options=
sequence:
file names the file of the declared members of the class. The extension
is not checked: any path works. A builder without file uses the module
default of the backend. 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 binding and are shared by every templated path of
the class.
name_pattern sets the pattern that builds the keys. If it is omitted,
the full {module}.{class}.{member} pattern is used. The explicit
name= of a member is the full address in the document and never passes
through the pattern.
dynamic_file moves the dynamic entries of the class (the settings with
names given at runtime) into a YAML file of their own. A class without it
keeps them in the static file. The name may contain template slots in the
same way as file, filled from the same parameters.
The binding is a metadata entry of the class itself. A subclass without its
own yaml_source inherits the nearest one above it, and a subclass with
its own yaml_source switches to it for itself and its subclasses. The
base class keeps reading its own file. Two yaml_source builders in one
options= make the class statement fail, as for every metadata name
that is declared twice.
advexp.settings.plugins.environment.variables.env_source(name_prefix=None, name_case=None, nested_delimiter=None)
¶
Bind a settings class to the process environment, 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 next to
the builders of other families in one options= sequence:
class AppSettings(Settings, options=env.env_source(
name_prefix="APP_", name_case=env.NameCase.UPPER,
)):
mode: str
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 the process
environment and a .env file share it, because they are one variable
namespace. Declare the dictionary either on this builder or on
dotenv_source, but only once per class: two declarations make the
class statement fail, as for every metadata name that is declared
twice. A class that declares no dictionary reads member names as written,
with no prefix.
The binding is a metadata entry of the class itself. A subclass without its own environment binding inherits the nearest one above it, and a subclass with its own binding switches to it for itself and its subclasses. The base class keeps reading its own variables.
advexp.settings.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.settings.plugins.environment.dotenv.dotenv_source(file=None, name_prefix=None, name_case=None, nested_delimiter=None, parameters=None)
¶
Bind a settings class to a .env file, 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 next to
the builders of other families in one options= sequence:
class AppSettings(Settings, options=dotenv_source(
file=".env", name_prefix="APP_", name_case=NameCase.UPPER,
)):
mode: str
file names the file that the backend reads and writes. A builder
without file uses the module default of the backend. An absent file
behaves as an empty one: required members are reported, and defaults stand
in. The name may contain template slots that are resolved through
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. A class has only one seat for these rules, and the process
environment and this file share it. Declare the rules either on this
builder or on env_source, but only once per class: two declarations
make the class statement fail, as for every metadata name that is
declared twice. A class that declares no dictionary reads member names as
written, with no prefix.
The binding is a metadata entry of the class itself. A subclass without its
own dotenv_source inherits the nearest one above it, and a subclass with
its own dotenv_source switches to it for itself and its subclasses. The
base class keeps reading its own file.
advexp.settings.plugins.mapping_source.mapping_source(mapping=None)
¶
Bind a settings class to its dictionary storage.
Pass the result of the builder to the options= keyword of the class
statement:
mapping seeds the storage. The dictionary is copied at the call, so a
later change to the object of the caller never reaches the storage, and
every value that a load finds is served as a fresh copy in the same way. If
the builder is called without a dictionary, it binds an empty storage. The
application fills it at runtime through
mapping_source_for(AppSettings).set({...}), and a load that runs before
any set passes over the storage as a miss.
The storage 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. A save writes only the member keys and leaves the rest
of the dictionary as it is. A delete removes the key of its member in the
same way.
The storage is the only source binding of the class. If a class declares it
next to another source builder, the class statement fails. The own seats
of a family (json_file(...), or a factory member of a family) can be
used next to it without restrictions.
advexp.settings.plugins.mapping_source.mapping_source_for(cls)
¶
The mapping storage of cls: the link of the binding, with set and
get.
Bind the storage with mapping_source(...) in the class statement,
and reach it from the class:
The resolver reads the nearest binding. A subclass without its own
mapping_source shares the storage of its base class: one dictionary, one
set, one get. The resolver raises an error that names the class if
the class was not built by the family, or if the class has no mapping
binding.
advexp.settings.plugins.mapping_source.MappingStorageLink(mapping=None)
¶
The link of the binding: the dictionary of the mapping storage, with the two handles of the resolver on it.
The link carries the serializer of the storage and its dictionary, which is
the seat that the whole family reads. set(mapping) and get() are the
channel of the application. The loads and saves of the class are the other
channel, and both channels work on the same dictionary.
set(mapping)
¶
Replace the dictionary of the storage 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. This includes the keys
that the saves of the class wrote. The save channel and this channel are
two separate channels, and a save never brings back what a set has
removed. The dictionary is deep-copied on the way in, and get returns a
copy, so the object of the caller and the state of the storage share
nothing.
get()
¶
Return the dictionary of the storage as it stands now: a deep copy, so changes that the caller makes never reach the storage.
The dictionary holds the last set and the saves of the class together,
keyed by member name. A foreign key that the application stored keeps its
place among them.
File binders¶
advexp.settings.plugins.json_settings.json_file(path)
¶
Name the JSON file of a settings class, in the form used by this distribution.
The file seat is the file of the class for its json_setting members,
when the builder of another family holds the binding. The seat and its
validation are part of the shared vocabulary of the backend; see
:func:advexp.settings_base.json_file.json_file. The stamp makes the
class statement of a class of another distribution reject the entry.
advexp.settings.plugins.toml_settings.toml_file(path)
¶
Name the TOML file of a settings class, in the form used by this distribution.
The file seat is the file of the class for its toml_setting members,
when the builder of another family holds the binding. The seat and its
validation are part of the shared vocabulary of the backend; see
:func:advexp.settings_base.toml_file.toml_file. The stamp makes the
class statement of a class of another distribution reject the entry.
advexp.settings.plugins.yaml_settings.yaml_file(path)
¶
Name the YAML file of a settings class, in the form used by this distribution.
The file seat is the file of the class for its yaml_setting members,
when the builder of another family holds the binding. The seat and its
validation are part of the shared vocabulary of the backend; see
:func:advexp.settings_base.yaml_file.yaml_file. The stamp makes the
class statement of a class of another distribution reject the entry.
advexp.settings.plugins.environment.dotenv.dotenv_file(path)
¶
Name the .env file of a settings class, in the form used by this
distribution.
The file seat is the file of the class for its dotenv_setting members,
when the builder of another family holds the binding. The seat and its
validation are part of the shared vocabulary of the family; see
:func:advexp.settings_base.dotenv_file.dotenv_file. The stamp makes the
class statement of a class of another distribution reject the entry.
Path parameters¶
advexp.settings.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 defaults¶
advexp.settings.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.settings.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.settings.plugins.json_settings.JSONSettingsConfiguration
¶
JSON backend configuration (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Default settings file location (str or os.PathLike).
It applies to settings classes without a json_file(...) binding.
When neither is set, the first storage access raises.
A settings module can keep its file to itself — declare it 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; every other module keeps reading the attribute, which stays the process-wide channel — set it once at startup for the whole application. The declaration is an ordinary module-level statement: it takes effect during the module's import, so a tool that imports the settings modules collects it without running the application.
advexp.settings.plugins.toml_settings.TOMLSettingsConfiguration
¶
TOML backend configuration (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Default settings file location (str or os.PathLike).
It applies to settings classes without a toml_file(...) binding.
When neither is set, the first storage access raises.
A settings module can keep its file to itself — declare it 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; every other module keeps reading the attribute, which stays the process-wide channel — set it once at startup for the whole application. The declaration is an ordinary module-level statement: it takes effect during the module's import, so a tool that imports the settings modules collects it without running the application.
advexp.settings.plugins.yaml_settings.YAMLSettingsConfiguration
¶
YAML backend configuration (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Default settings file location (str or os.PathLike).
It applies to settings classes without a yaml_file(...) binding.
When neither is set, the first storage access raises.
A settings module can keep its file to itself — declare it 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; every other module keeps reading the attribute, which stays the process-wide channel — set it once at startup for the whole application. The declaration is an ordinary module-level statement: it takes effect during the module's import, so a tool that imports the settings modules collects it without running the application.
advexp.settings.plugins.environment.dotenv.DotenvConfiguration
¶
Dotenv backend configuration (class-level state).
file_path = '.env'
class-attribute
instance-attribute
¶
Default file location (str or os.PathLike).
A relative path resolves against the working directory of the running
process. It applies to settings classes without a dotenv_file(...)
binding; an absent file behaves as an empty one — the declared defaults
stand in. The default is the ecosystem's conventional file, so the
backend works with no configuration at all.
A settings module can keep its file to itself — declare it 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; every other module keeps reading the attribute — the process-wide default above.
Dynamic settings¶
The plugins that store entries whose names are defined at runtime. The
plugin object holds the section parameters and the runtime file_path;
the configuration holder holds the module-level file and the name pattern.
advexp.settings.plugins.json_dynamic_settings.JSONDynamicSettingsPlugin()
¶
Plugin object for dynamic settings in the JSON settings file.
The namespaced pin names the backend. Dynamic entries use the same
serializer and the same file as the JSONSetting members of the class,
so one document holds the static and the runtime settings of the class side
by side: the static entries under their full names, and the dynamic ones in
the dedicated section of the document (see
:attr:DocumentDynamicSettingsPlugin.dynamic_settings_section_name and
:attr:DocumentDynamicSettingsPlugin.dynamic_settings_section_path,
inherited here).
The shared file is the default, not a rule. The dynamic_file= parameter
of the json_source(...) binding of the class (class metadata; the
nearest entry along the class chain wins) or the module-wide
JSONDynamicSettingsConfiguration.file_path moves the dynamic entries to
a file of their own, separate from the static one.
The pin is the own registry slot of the dynamic side: the binding ties it to
JSONSettingsSerializer here and never touches the registration of
JSONSetting by the static plugin.
file_path
property
writable
¶
The own file for the dynamic entries of this class; None when the static
file governs.
Reading resolves along the class chain: the own binding of the class, or
otherwise the binding of the nearest ancestor. Assigning re-binds the class
itself: the value is validated and stored as an own metadata entry of the
class. The declarative json_source(dynamic_file=...) binding and an
assignment here are therefore the same channel. The binding moves the
dynamic entries of this class to the named file, separate from the static
side, and it wins over JSONDynamicSettingsConfiguration.file_path.
None means that there is no binding anywhere. In that case, the module
default applies. If the module default is also unset, the dynamic entries go
into the static file. delete_class_metadata removes the binding again.
advexp.settings.plugins.json_dynamic_settings.JSONDynamicSettingsConfiguration
¶
Dynamic JSON settings configuration (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Own file for the dynamic entries (str or os.PathLike).
When set, every dynamic collection without a binding of its own goes to
this file, apart from the file of the declared (static) settings; the
document inside is shaped by the section parameters of
:class:JSONDynamicSettingsPlugin as usual.
A settings 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 share the static file: the class's own binding, the static
module declaration, or JSONSettingsConfiguration.file_path.
name_pattern = DEFAULT_SETTINGS_NAME_PATTERN
class-attribute
instance-attribute
¶
The name pattern of the dynamic entries; the default is the full
pattern {module}.{class}.{name}.
The pattern shapes both visible names of an entry: the class object's
key in the document's dynamic section (the part in front of the
entry's own name) and, in the flat layout, the D-prefixed full
name at the document root. The plain names the collection works with
never change — the pattern spells them into the document. It may hold
the {NamespaceName} (module), {ClassName}, {FieldName} and
{Delimiter} slots, and it must hold {FieldName} and a class
part: without {FieldName} the entries would collide, and without a
class part the section layout has nothing to group (and parse back)
the entries by — both are refused at the first pass that needs a name.
The static members' keys are not governed here: they take their form
from the static side's own naming options (the json_options
family entry), and a backend without them keeps the default full
pattern.
Changing the pattern is a migration of every file already written: the class object's key in the section and the flat full names change with it, and the entries stored under the old spelling are no longer found. That is the declaring side's own choice — set the pattern once, before the first stored file, or move the entries by hand when it changes.
advexp.settings.plugins.toml_dynamic_settings.TOMLDynamicSettingsPlugin()
¶
Plugin object for dynamic settings in the TOML settings file.
The namespaced pin names the backend. Dynamic entries use the same
serializer and the same file as the TOMLSetting members of the class,
so one document holds the static and the runtime settings of the class side
by side: the static entries under their full names, and the dynamic ones in
the dedicated section of the document (see
:attr:DocumentDynamicSettingsPlugin.dynamic_settings_section_name and
:attr:DocumentDynamicSettingsPlugin.dynamic_settings_section_path,
inherited here).
The shared file is the default, not a rule. The toml_source(dynamic_file=...)
binding of the class (class metadata; the nearest entry along the class
chain wins) or the module-wide TOMLDynamicSettingsConfiguration.file_path
moves the dynamic entries to a file of their own, separate from the static
one.
The pin is the own registry slot of the dynamic side: the binding ties it to
TOMLSettingsSerializer here and never touches the registration of
TOMLSetting by the static plugin.
file_path
property
writable
¶
The own file for the dynamic entries of this class; None when the static
file governs.
Reading resolves along the class chain: the own binding of the class, or
otherwise the binding of the nearest ancestor. Assigning re-binds the class
itself: the value is validated and stored as an own metadata entry of the
class. The declarative toml_source(dynamic_file=...) binding and an
assignment here are therefore the same channel. The binding moves the
dynamic entries of this class to the named file, separate from the static
side, and it wins over TOMLDynamicSettingsConfiguration.file_path.
None means that there is no binding anywhere. In that case, the module
default applies. If the module default is also unset, the dynamic entries go
into the static file. delete_class_metadata removes the binding again.
advexp.settings.plugins.toml_dynamic_settings.TOMLDynamicSettingsConfiguration
¶
Dynamic TOML settings configuration (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Own file for the dynamic entries (str or os.PathLike).
When set, every dynamic collection without a binding of its own goes to
this file, apart from the file of the declared (static) settings; the
document inside is shaped by the section parameters of
:class:TOMLDynamicSettingsPlugin as usual.
A settings 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 share the static file: the class's own binding, the static
module declaration, or TOMLSettingsConfiguration.file_path.
name_pattern = DEFAULT_SETTINGS_NAME_PATTERN
class-attribute
instance-attribute
¶
The name pattern of the dynamic entries; the default is the full
pattern {module}.{class}.{name}.
The pattern shapes both visible names of an entry: the class object's
key in the document's dynamic section (the part in front of the
entry's own name) and, in the flat layout, the D-prefixed full
name at the document root. The plain names the collection works with
never change — the pattern spells them into the document. It may hold
the {NamespaceName} (module), {ClassName}, {FieldName} and
{Delimiter} slots, and it must hold {FieldName} and a class
part: without {FieldName} the entries would collide, and without a
class part the section layout has nothing to group (and parse back)
the entries by — both are refused at the first pass that needs a name.
The static members' keys are not governed here: they take their form
from the static side's own naming options (the toml_options
family entry), and a backend without them keeps the default full
pattern.
Changing the pattern is a migration of every file already written: the class object's key in the section and the flat full names change with it, and the entries stored under the old spelling are no longer found. That is the declaring side's own choice — set the pattern once, before the first stored file, or move the entries by hand when it changes.
advexp.settings.plugins.yaml_dynamic_settings.YAMLDynamicSettingsPlugin()
¶
Plugin object for dynamic settings in the YAML settings file.
The namespaced pin names the backend. Dynamic entries use the same
serializer and the same file as the YAMLSetting members of the class,
so one document holds the static and the runtime settings of the class side
by side: the static entries under their full names, and the dynamic ones in
the dedicated section of the document (see
:attr:DocumentDynamicSettingsPlugin.dynamic_settings_section_name and
:attr:DocumentDynamicSettingsPlugin.dynamic_settings_section_path,
inherited here).
The shared file is the default, not a rule. The yaml_source(dynamic_file=...)
binding of the class (class metadata; the nearest entry along the class
chain wins) or the module-wide YAMLDynamicSettingsConfiguration.file_path
moves the dynamic entries to a file of their own, separate from the static
one.
The pin is the own registry slot of the dynamic side: the binding ties it to
YAMLSettingsSerializer here and never touches the registration of
YAMLSetting by the static plugin.
file_path
property
writable
¶
The own file for the dynamic entries of this class; None when the static
file governs.
Reading resolves along the class chain: the own binding of the class, or
otherwise the binding of the nearest ancestor. Assigning re-binds the class
itself: the value is validated and stored as an own metadata entry of the
class. The declarative yaml_source(dynamic_file=...) binding and an
assignment here are therefore the same channel. The binding moves the
dynamic entries of this class to the named file, separate from the static
side, and it wins over YAMLDynamicSettingsConfiguration.file_path.
None means that there is no binding anywhere. In that case, the module
default applies. If the module default is also unset, the dynamic entries go
into the static file. delete_class_metadata removes the binding again.
advexp.settings.plugins.yaml_dynamic_settings.YAMLDynamicSettingsConfiguration
¶
Dynamic YAML settings configuration (class-level state).
file_path = None
class-attribute
instance-attribute
¶
Own file for the dynamic entries (str or os.PathLike).
When set, every dynamic collection without a binding of its own goes to
this file, apart from the file of the declared (static) settings; the
document inside is shaped by the section parameters of
:class:YAMLDynamicSettingsPlugin as usual.
A settings 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 share the static file: the class's own binding, the static
module declaration, or YAMLSettingsConfiguration.file_path.
name_pattern = DEFAULT_SETTINGS_NAME_PATTERN
class-attribute
instance-attribute
¶
The name pattern of the dynamic entries; the default is the full
pattern {module}.{class}.{name}.
The pattern shapes both visible names of an entry: the class object's
key in the document's dynamic section (the part in front of the
entry's own name) and, in the flat layout, the D-prefixed full
name at the document root. The plain names the collection works with
never change — the pattern spells them into the document. It may hold
the {NamespaceName} (module), {ClassName}, {FieldName} and
{Delimiter} slots, and it must hold {FieldName} and a class
part: without {FieldName} the entries would collide, and without a
class part the section layout has nothing to group (and parse back)
the entries by — both are refused at the first pass that needs a name.
The static members' keys are not governed here: they take their form
from the static side's own naming options (the yaml_options
family entry), and a backend without them keeps the default full
pattern.
Changing the pattern is a migration of every file already written: the class object's key in the section and the flat full names change with it, and the entries stored under the old spelling are no longer found. That is the declaring side's own choice — set the pattern once, before the first stored file, or move the entries by hand when it changes.
Errors and sentinels¶
advexp.settings.SettingValidationError(message, problems=None)
¶
A value violates the constraints that are declared on a setting member.
The error is raised wherever a value meets the declared constraints:
assignment to a setting, update_from_dict/update_from_object, a save
of a value that no longer satisfies them, and the manual
validate_settings(). A load keeps its own error policy: it falls back to
the default with a logged warning, like a value that does not convert.
A check over the whole settings object (an update, a save, or the manual
validation) collects every violation of every member at once. Such an error
carries the findings as problems: a dict of member name to the list of
its problem descriptions. Tools can read the data instead of parsing the
message, while the message itself lists the same findings for the reader of
the log.
Subclassing :class:ValueError keeps a plain except ValueError working.
In that case, a violation cannot be distinguished from a failed conversion,
while the precise class can still be caught on its own.
advexp.settings.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.settings.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.settings.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.settings.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.settings.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.settings.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.settings.PluginNotEnabledError
¶
A setting attribute has no registered serializer (plugin not imported).
advexp.settings.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.settings.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.settings.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.