Unified handler logic exposition with simple configuration management and integrated logging to Kafka.
Environment variable XPOSER_CONFIG defines from where to load the config file. This can be overridden using --config command line argument
Environment variables must follow the same structure and naming as found in the configuration_xx_yy.yaml examples
Pydantic settings utilize prefixes (xp is the global configurations prefix)
Configuration precedence: pydantic settings -> overridden by environment -> overridden by command line So in the default config model we have foo = Field('bar') So if you have in your config.yaml a variable ('xp_' prefix is mandatory!) xp_foo = 'baz' it will be overridden with the environment variable XP_FOO = 'bak' and also will be overridden from the command line variable (it's case insensitive) --xp_foo= 'bang' or --XP_FOO='bang'
There is basically one process for attaining and loading configurations, however based on prefixes and configuration objects (pydantic) its possible to map variables from the configuration object for multiple levels: global level: prefix 'xp_' mapping location: core/configuration_model.py application level: prefix is your choice
By default, all configuration parameter is loaded to global configuration object. If something does not have a
counterpart in the main configuration object, it will loiter there with the xp_ prefix unprocessed
So if you provide an xp_mysettings_custom_param = 'foobar'
in the configuration (or Environment or CLI arg), the global
configuration object, that is accessible from the Context.config
will hold that parameter and value.
Whenever you have your own pydantic settings object named MYSETTINGS, that you want to be defined in your config.yaml, environment or command line, you can do it like:
-
lets say your application has a sample config model, create a shallow copy of the keys,
app_config_defaults = SampleAppHTTPConfigModel.model_construct(_validate=False)
-
Merge the ctx.config values but not all, only those which match to SampleAppHTTPConfigModel fields (prefixing with ' app_fast_logic')
app_config_merged = Configurator.mergePrefixedAttributes(app_config_defaults, ctx.config, 'xp_mysettings_')
-
Validate your model to make sure all fits
app_config_merged.model_validate(app_config_merged)
so your app_config_merged will contain custom_param = 'foobar'
Xposer creates a main loop and through creating XPTask it creates a new thread and runs the required entry point fn of the XPControllers start services
An XPController is a small component that responsible to initialize the services and your core application logic/pkgs The XPController must be implemented by the developer XPController has two main implementable method:
@abstractmethod async def startXPController(self) -> None:
@abstractmethod async def tearDownXPController(self) -> None:
both must be implemented keeping mind that the XPController is running within its own thread and is responsible for the graceful shutdown of its internal business logic
async/await exceptions must be caught inline using try/catch
loop.create_task(...) exceptions caught loop exception handlers these tasks are not gathered and possibly run forever
threaded functions are always awaited and exceptions are routed to the main thread using threadsafe Queue.queue passed downstream using explicit Context propagation