Dynamic Modules¶
We have seen in many examples given on how to statically configure a module. In this section, we are going to look at different ways to dynamically set up a module.
Why is this important? Consider a scenario where a general-purpose module needs to behave differently in different use cases, it may be useful to use a configuration-based approach to allow customization. This is similar to the concept of a "plugin" in many systems, where a generic facility requires some configuration before it can be used by a consumer.
Module Dynamic Setup¶
To dynamically configure a module, the module should inherit from IModuleSetup
and provide a setup
method or setup_register
method that performs the necessary actions for setting up the module and then returns a DynamicModule
or ModuleSetup
instance.
import typing as t
from ellar.core.modules import DynamicModule, ModuleSetup
class IModuleSetup:
"""Modules that must have a custom setup should inherit from IModuleSetup"""
@classmethod
def setup(cls, *args: t.Any, **kwargs: t.Any) -> DynamicModule:
pass
@classmethod
def register_setup(cls, *args: t.Any, **kwargs: t.Any) -> ModuleSetup:
pass
Note that setup
method returns a DynamicModule
instance, while register_setup
method returns a ModuleSetup
instance. The DynamicModule
instance is used when the module requires some configuration before it can be used by a consumer, while the ModuleSetup
instance is used when the module does not require any additional configuration outside the ones provided in the application config.
DynamicModule¶
DynamicModule
is a dataclass type that is used to override Module
decorated attributes without having to modify the module code directly. In other words, it gives you the flexibility to reconfigure module.
For example, Let's look at the code below:
from ellar.common import Module
from ellar.core import DynamicModule
from ellar.di import ProviderConfig
@Module(providers=[ServiceA, ServiceB])
class ModuleA:
pass
# we can reconfigure ModuleA dynamically using `DynamicModule` as shown below
@Module(
modules=[
DynamicModule(
ModuleA,
providers=[
ProviderConfig(ServiceA, use_class=str),
ProviderConfig(ServiceB, use_class=dict),
]
)
]
)
class ApplicationModule:
pass
ModuleA
has been defined with some arbitrary providers (ServiceA
and ServiceB
), but during registration of ModuleA
in ApplicationModule
, we used DynamicModule
to override its Module attribute providers
with a new set of data. ModuleSetup¶
ModuleSetup is a dataclass type that used to set up a module based on its dependencies. It allows you to define the module dependencies and allow a callback factory function for a module dynamic set up.
ModuleSetup
Properties:
module
: a required property that defines the type of module to be configured. The value must be a subclass of ModuleBase or IModuleSetup.inject
: a sequence property that holds the types to be injected to the factory method. The order of the types will determine the order at which they are injected.factory
: a factory function used to configure the module and takeModule
type as first argument and other services as listed ininject
attribute.
Let's look this ModuleSetup
example code below with our focus on how we eventually configured DynamicService
type, how we used my_module_configuration_factory
to dynamically build MyModule
module.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
|
DynamicService
, whose parameter depended on some values from application config and from another service Foo
. We then set up a MyModule
and used as setup method which takes all parameter needed by DynamicService
after that, we created DynamicService
as a singleton and registered as a provider in MyModule
for it to be accessible and injectable. At this point, looking at the setup function of MyModule
, its clear MyModule
depends on Config
and Foo
service. And this is where ModuleSetup
usefulness comes in.
During registration in ApplicationModule
, we wrapped MyModule
around a ModuleSetup
and stated its dependencies in the inject
property and also provided a my_module_configuration_factory
factory that takes in module dependencies and return a DynamicModule
configuration of MyModule
.
When AppFactory
starts module bootstrapping, my_module_configuration_factory
will be called with all the required parameters and returned a DynamicModule
of MyModule
.
For more example, checkout Ellar Throttle Module or Ellar Cache Module
Lazy Loading Modules¶
Ellar supports loading module decorated classes through a string reference using LazyModuleImport
. For a better application context availability usage in module like, current_config
, current_app
and current_injector
, it's advised to go with lazy module import.
For example, we can lazy load CarModule
from our example here into ApplicationModule
project_name/root_module.py | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
In the above illustration, we provided a string reference to CarModule
into LazyModuleImport
instance. And during AppFactory
Module bootstrap, CarModule
will be resolved, validated and registered into the application
Properties¶
LazyModuleImport
attributes,
module
: String reference for Module importsetup
: Module setup function name for modules that requires specific function as in case ofDynamicModule
andModuleSetup
.setup_options
: Module setup function parameters
Lazy Loading DynamicModules¶
Having the understanding of DynamicModule
and its registration pattern, to lazy load DynamicModule follows the same pattern.
For example, lets lazy load MyModule
as a DynamicModule
. For that to happen, we need to call MyModule.setup
with some parameters and in turn returns a DynamicModule
project_name/root_module.py | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
Let's rewrite this using LazyModuleImport
.
project_name/root_module.py | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
Lazy Loading ModuleSetup¶
Just as in DynamicModule
, ModuleSetup
can be lazy loaded the same way. Let's take CacheModule for example.
project_name/root_module.py | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 |
|
CacheModule
through register_setup
function which returns a ModuleSetup
that configures the CacheModule
to read its configurations from application config.