Configuration
The asgiconfig dialect (the recipe you write), the grammar it speaks, and
the read door the runtime reads it through.
AsgiConfigBuilder — the asgiconfig dialect: contrib/config + the server’s grammar.
The dialect is the contrib configuration builder (ConfigBuilder: the
configuration root, the four-layer read contract, the XML render) composed
with the grammar the server class declares (AsgiServer.grammar). A site
subclasses it in a config.py and overrides main(self, root); the runtime
reads the built tree through ConfigurationHandler and nothing else.
Recipes orchestrate in main and delegate each section to a method taking the
PARENT node, so a section stays small enough to read at a glance:
from genro_asgi.config import AsgiConfigBuilder
from myshop.app import Application as Shop
class ServerConfiguration(AsgiConfigBuilder):
def main(self, root):
cfg = root.configuration()
self.server_section(cfg)
cfg.applications(default="shop").application(code="shop", app_class=Shop)
def server_section(self, cfg):
'''The listener and the session TTL.'''
cfg.server(host="127.0.0.1", port=8000).session(ttl=3600)
BaseConfiguration ships the package’s OWN defaults in the same form — a
recipe, not a dict of fallbacks. Every handler the server builds layers it
under the site’s recipe, so a site inherits what it does not say; deviating
means overriding one hook method.
- class genro_asgi.config.builder.AsgiConfigBuilder(name=None)[source]
Bases:
ConfigBuilder,AsgiServerGrammarConfiguration dialect of genro-asgi: contrib layout +
AsgiServerGrammar.- Parameters:
name (str | None)
- default_config: bool | str | Path | None = None
Where this recipe’s defaults layer comes from —
DefaultConfigresolves it.None(the default) takes the conventional<base_dir>/config.pywhen that file exists;Falsemeans no defaults layer at all; a path names the file, and a missing one is aConfigError. The recipe governs its own inheritance — the server takes no kwarg for it.
- class genro_asgi.config.builder.BaseConfiguration(name=None)[source]
Bases:
AsgiConfigBuilderThe package’s shipped defaults, AS A RECIPE — the lowest layer of every site.
ConfigurationHandlerlayers it under the optional defaults recipe and the site’s own (DefaultConfig.parents_for()), so the defaults are executed through the grammar like any other recipe instead of being reproduced as constructor fallbacks. A site deviates by overriding ONE hook and nothing else —storage_keyfor the key material,storage_mountsfor the layout,server_sectionfor the listener:from genro_bag.resolvers import EnvResolver class ServerConfiguration(BaseConfiguration): storage_key = EnvResolver("GENRO_STORAGE_KEY") def storage_mounts(self, section): section.local(name="site", base_path="/srv/shop") section.s3(name="uploads", bucket="shop-media")
A recipe that subclasses
AsgiConfigBuilderdirectly inherits the same defaults: the layering is the handler’s, not the class hierarchy’s.- Parameters:
name (str | None)
- storage_key: str | BagResolver | None = None
At-rest key material of the storage section — a site sets it to a resolver.
- server_section(cfg)[source]
The
serversection, bare — the hook a machine or site recipe overrides.It declares no value on purpose, and there is no signature default to inherit either: the element’s four parameters are all
None, which the read stack reads as absent. The listener defaults stay where they live — in the constructor.
- storage_section(cfg)[source]
The
storagesection: genro-storage’s mount point plus the key material.
- storage_mounts(section)[source]
The default layout: one
site:mount on the deployment directory.The mount is
DEFAULT_SITE_MOUNTwritten as a recipe line — the tag IS itsprotocol— so the layout the mixin builds without a recipe and the layout this recipe declares cannot drift apart.The anchor is the cwd read WHEN THE RECIPE RUNS, which is boot: the same recipe follows whatever directory the deployment starts from. It is written absolute because genro-storage’s local backend rejects a relative
base_pathstring outright.
AsgiServerGrammar — the configuration grammar of AsgiServer.
The grammar the server class exposes as AsgiServer.grammar: one
configuration root (the contrib ConfigBuilder element, OVERRIDDEN here
with the full closed section list) whose sections describe the whole site.
Every section is a singleton ([0:1]), so labels are clean and every path is
stable and hand-writable: configuration.server,
configuration.authentication.oidc.<code>,
configuration.applications.<code>.
Authoring conventions inherited from contrib/config:
attributes are ANNOTATED, so their signature defaults reach the read stack of
ConfigurationHandler(an unannotated parameter never enterscall_args_validations);an attribute whose value may come from outside (env, file, url) is annotated
<type> | BagResolverand receives the resolver IN PLACE — there are no^pointerstrings in this dialect;the recipe orchestrates in
mainand delegates each section to a method taking the PARENT node.
Sections:
server— the runtime options (host,port,external_url,max_threads,shutdown_timeout_seconds) plus the server-domain childrensession(the session TTL) andtasks(declared byTaskGrammar, the class that peelstasks=).middleware— one{name: bool | dict}switch per middleware.authentication— the whole identity surface: the bootstrapadmin_password, theusers/tokensstore descriptors, theloginlockout policy, theoidcprovider collection and thecredentialshanded toAuthCore.storage— the mount point of genro-storage’s own grammar: the mounts of the server’sStorageManager, plus the section’sstorage_key.applications— the app collection keyed bycode; each entry MOUNTS the grammar itsapp_classcarries.databases— one descriptor per database handler.plugins— the router plugins armed on every routed app.openapi— the OpenAPI metadata (grammar only in core 1a).
The SPA pool is NOT a section of this dialect: a pool belongs to the application
that owns it, so its words live in that application’s own grammar and its recipe
is written under applications.<code>.orchestration.commander.
A recipe subclasses AsgiConfigBuilder and overrides main(self, root);
application classes are imported and passed as objects:
from myshop.app import Application as Shop
class ServerConfiguration(AsgiConfigBuilder):
def main(self, root):
cfg = root.configuration()
cfg.server(host="127.0.0.1", port=8000)
cfg.middleware(cors=True)
cfg.applications(default="shop").application(code="shop", app_class=Shop)
- class genro_asgi.config.elements.AsgiServerGrammar[source]
Bases:
TaskGrammarConfiguration grammar of
AsgiServer: the site layout, reading elsewhere.Grammar only — the runtime reads the built tree through
ConfigurationHandler, never through this class. Capability-owned companions are composed explicitly (TaskGrammar— thetaskschild ofserver, owned byTaskMixin).- configuration = <genro_builders.builder._decorators._DeclarativeMarker object>
- server = <genro_builders.builder._decorators._DeclarativeMarker object>
- websocket = <genro_builders.builder._decorators._DeclarativeMarker object>
- session = <genro_builders.builder._decorators._DeclarativeMarker object>
- middleware = <genro_builders.builder._decorators._DeclarativeMarker object>
- authentication = <genro_builders.builder._decorators._DeclarativeMarker object>
- admin_password = <genro_builders.builder._decorators._DeclarativeMarker object>
- users = <genro_builders.builder._decorators._DeclarativeMarker object>
- tokens = <genro_builders.builder._decorators._DeclarativeMarker object>
- login = <genro_builders.builder._decorators._DeclarativeMarker object>
- oidc = <genro_builders.builder._decorators._DeclarativeMarker object>
- provider = <genro_builders.builder._decorators._DeclarativeMarker object>
- credentials = <genro_builders.builder._decorators._DeclarativeMarker object>
- basic_user = <genro_builders.builder._decorators._DeclarativeMarker object>
- bearer_token = <genro_builders.builder._decorators._DeclarativeMarker object>
- jwt = <genro_builders.builder._decorators._DeclarativeMarker object>
- storage = <genro_builders.builder._decorators._DeclarativeMarker object>
- applications = <genro_builders.builder._decorators._DeclarativeMarker object>
- application = <genro_builders.builder._decorators._DeclarativeMarker object>
- databases = <genro_builders.builder._decorators._DeclarativeMarker object>
- database = <genro_builders.builder._decorators._DeclarativeMarker object>
- plugins = <genro_builders.builder._decorators._DeclarativeMarker object>
- plugin = <genro_builders.builder._decorators._DeclarativeMarker object>
- openapi = <genro_builders.builder._decorators._DeclarativeMarker object>
ConfigurationHandler — the server’s read door on its configuration.
A contrib ConfigHandler subclass: it inherits the callable four-layer read
stack (written value → signature default → call-site default= → noisy
KeyError) and adds the section→kwargs mapping helpers AsgiServer.__init__
consumes. It NEVER builds a server: the server builds ITS OWN handler
(AsgiServer(config=source)) and asks these helpers for its kwargs, so there
is one direction of dependency and no materializer.
The helpers read the tree by two rules, and the grammar decides which applies:
a node with a CLOSED signature is read attribute by attribute THROUGH the handler itself, so the element’s signature defaults and any resolver sitting in an attribute are honored (
server,provider,mount, …);a node with OPEN
**kwargshas no signature to consult, so its attributes are read in bulk throughbuilder.runtime_values— resolvers resolved, everything else verbatim (application,plugin,database).
Section → constructor kwarg:
server→host/port/external_url/max_threads/shutdown_timeout_seconds, itssessionchild →session_ttl, itstaskschild →tasks.middleware→middleware({name: bool | dict} switches).authentication→admin_password/users/tokens(the store kwargsAuthMixinpeels),auth(theAuthCoreentries folded fromcredentials) andserver_app(login+oidc, forwarded to the_serverapplication).storage→storage(genro-storage’s ownlist[dict]of mounts) andstorage_key(the section’s at-rest key material).applications→applications/default(each entry an(app_class, kwargs)pair the server instantiates).databases→ one descriptor per entry, registered by the server after the cooperative chain has run.plugins→plugins({code: bool | dict} switches).openapi→ no core-1a consumer; read and skipped.orchestration→ the SPA front’s whole orchestration subtree: its own three words, and under itcommander— the vertex’s kwargs and one kwargs set per declared group (the two installation paths folded in, the child’s own keys gathered into itsworker_kwargs).
- exception genro_asgi.config.handler.ConfigError[source]
Bases:
ExceptionA configuration recipe names something the runtime cannot honor.
- class genro_asgi.config.handler.ConfigurationHandler(source, parents=None)[source]
Bases:
ConfigHandlerRead door over an
asgiconfigtree, plus the section→kwargs mapping.- Parameters:
- server_kwargs()[source]
The
serversection as server kwargs, its children lifted.sessionbecomessession_ttl,tasksbecomes thetaskstuning dict andwebsocketthe websocket options: all three are server-domain (sessions, the task backbone and the sockets live on the server), so their values lift to the kwargs the owning mixins peel while the config keeps them underserverwhere they belong. Theoriginsof a handshake are written as one comma-separated string in a recipe and reach the server as the list it reads.
- middleware_config()[source]
The
middlewareswitches, orNonewhen the section is absent (the composition’s own defaults then apply).
- identity_kwargs()[source]
The identity STORE kwargs of
authentication(AuthMixinpeels them).admin_passwordis theadmin_passwordnode’s VALUE, which a resolver must supply — the grammar rejects a literal at the recipe line (secrets stay out of recipes). Resolving empty is a boot error (the recipe promised a secret that does not exist — an empty bootstrap password would arm a passwordless SUPERADMIN), and so is resolving to a non-string.users/tokensare{mount, prefix}descriptors.
- admin_password(node)[source]
The bootstrap password carried by
node, resolved to a non-empty string.The grammar already rejects a literal at the recipe line (
node_value: BagResolver); here we validate what the resolver actually DELIVERED at boot.
- auth_entries()[source]
The
credentialschildren folded into theAuthCoresections.basic_userentries are keyed byusernameandbearer_tokenentries byidentity— the keysAuthCorereads back as the authenticated identity — whilejwtentries stay an ORDERED list (the first verifier that verifies wins).Nonewhen nothing is configured: the server then arms no header backend.
- server_app_kwargs()[source]
The LOGIN surface of
authentication→ the_serverapp’s kwargs.loginis the lockout policy andoidcthe providers keyed bycode. These values belong to the application that peels them, so they travel as ONE server kwarg (server_app) forwarded at mount time instead of being lifted onto the server itself.
- oidc_providers()[source]
The
oidcproviders as{code: attrs}, defaults applied.scopesandidentity_claimcome from the element’s signature, so every provider carries them whether the recipe wrote them or not;tagsdefaults to the empty list here (a mutable signature default is never declared).
- storage_config()[source]
The
storagesection as(mounts, storage_key), orNonewhen it is absent (the composition builds its default manager).The subtree is written in genro-storage’s grammar, so it is flattened GENERICALLY into that library’s
list[dict]: the tag IS the protocol and every attribute rides through as-is (nameamong them — the envelope is transparent to containment, so the children carry auto labels and their key lives in the attribute the foreign grammar declares). This dialect knows no storage vocabulary to translate.A section carrying only its
storage_keyand no mount is legitimate — “the default layout, plus this key” — so it yields an EMPTY mount list rather than an error; the composition reads that as “use the defaultsite:mount”. WithBaseConfigurationlayered underneath the merged tree normally carries thesitemount anyway, so this is the shape a handler built without parents produces.
- plugins_config()[source]
The
pluginsswitches as{code: bool | dict}, orNonewhen the section is absent (the composition arms no extra plugin).A plugin maps to
Falsewhenenabledis explicitly false, to its remaining options when it carries any, else toTrue.
- applications()[source]
The declared applications as
(app_class, kwargs)pairs, plusdefault.Every attribute of the envelope except
app_classis a constructor kwarg of the application —codeandmountincluded, since the app owns their resolution. The mounted subtree is NOT passed: an application reads its own configuration back through the handler (applications.<code>.<path>), it never receives a slice of the tree.
- databases()[source]
The
databasesdescriptors as{code, db_class, db_handler_class, params}.db_handler_classisNonewhen the recipe omits it (the server substitutes its default) andparamsare the remaining connection kwargs handed todb_class(**params).
- orchestration_kwargs(code)[source]
One application’s orchestration node, or
Nonewhen it has none.- Parameters:
code (
str) – the application whose orchestration this is — the words live underapplications.<code>.orchestration.- Return type:
- Returns:
profiles_path,profile_nameandcontrol_enabled, the three the recipe actually wrote, orNonewhen the node is absent — which is a front that declares no pool at all.
- commander_kwargs(code)[source]
The pool of one application as its vertex’s own constructor kwargs.
- Parameters:
code (
str) – the application whose pool this is — a pool belongs to the front that owns it, so the words live underapplications.<code>.orchestration.commander.- Return type:
- Returns:
The vertex’s kwargs, or
Nonewhen the node is absent — an orchestration node with no commander under it, which the front refuses.
instance_diris NOT among them: the sockets are the workers’ business, so that path is folded into every group instead (group_kwargs). The group ELECTED to receive a newcomer is declared one level down, on the collection, and is folded in here because the vertex is what reads it. What the recipe leaves out is left out, and the vertex’s own default answers.
- group_kwargs(code)[source]
One application’s groups as
{name: kwargs}, oneGroupHandlereach.- Parameters:
code (
str) – the application whose pool these groups belong to.- Return type:
The two paths of the installation live on
commanderand are folded in here, because a group is what builds the workers that need them. The one key the CHILD reads travels in its ownworker_kwargs: the group’s name, which stamps every item it writes. So a recipe writes each policy once, on the rung it belongs to, and the child is handed what is his.
- closed_attrs(path, *names)[source]
Read
namesatpaththrough the read stack, skipping the absent.One four-layer read per attribute, so a resolver sitting in an attribute resolves and the element’s signature defaults apply.
Nonemeans “not configured and no default” and is left out — the consumer’s own default then applies.
Database handler
Database handler — the core’s minimal contract for a mounted database.
A database declared in the config names a db_class (the imported class
that builds the real db from the connection parameters) and, optionally, a
db_handler_class (default AsgiDbHandlerBase). At mount time the server
builds db_handler_class(db_class(**params)) and registers the handler.
The handler is what lives in the registry and what request.db returns. It
proxies every attribute to the wrapped db via __getattr__ (so the db’s own
interface — execute and the rest — stays transparent), while owning the one
method the core itself calls: closeConnection (registered as a request
cleanup). Concrete db classes and custom handlers live outside the core; the
core only defines this contract.