Config
Each pluggable component within the eVOLVER system is defined as a python class which itself contains a Config class that defines optional and required data affecting the behavior of the component that is used for serialization, deserialization, and UI form creation (see also Configuration and ConfigDescriptors).
The config class
The config classes are based on [pydantic](https://docs.pydantic.dev/latest/) models which leverage type annotations on class data members to define the data schema.
As an example, consider the following abbreviated definition of a controller class, which is a pluggable component in the eVOLVER system:
class MyController(Controller):
class Config(Controller.Config):
param_required: float
param_optional: int = 10 # has a default value
param_optional_with_desc: str = Field(default="default", description="A string parameter")
You can observe several things here:
The Config class is a nested class within the component class, which is a common pattern in eVOLVER components. This is a convenience that keeps the definition clearly in the context in which it is used, and enables the system to access the config as a standard-defined class attribute (i.e. MyController.Config).
The Config class inherits from the parent component’s Config class, which allows it to extend the configuration with additional parameters while still inheriting the base configuration.
The data members of the Config class are defined with type annotations. This is a requirement from pydantic that enables it to both validate the data, reliably perform serialization, and generate a json-schema that con be used to dynamically generate UI forms.
The Field function can be used to provide additional metadata about the particular field such as a description and validation rules (see https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.Field)
Note that while the eVOLVER system adds additional functionality - such as from-file loading and automatic handling of eVOLVER components defined in the config - pydantic does most of the heavy lifting here, so see its documentation for information on how to apply custom validations or otherwise extend the config models.
Serialized configuration
The config classes described above are what enable the eVOLVER system to be described within a static yaml file (e.g. evolver.yml), and be configured over the wire via the web API.
In the serialized configuration, we describe a component by its class and the
data members to set on its config. This is done via a
ConfigDescriptor. For example, the
above MyController class can be described in the serialized configuration as
follows (assuming it is defined in my_module):
classinfo: "my_module.MyController"
config:
param_required: 3.14
param_optional_with_desc: "Hello, world!"
JSON schema
The web API also provides and endpoint to retrieve the JSON schema for the configuration of a particular component by its fully qualified class name:
GET /schema/?classinfo={component_name}
might return something like:
{
"classinfo": "my_module.MyController",
"config": {
"properties": {
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"title": "Name"
},
"param_required": {
"title": "Param Required",
"type": "number"
},
"param_optional": {
"default": 10,
"title": "Param Optional",
"type": "integer"
},
"param_optional_with_desc": {
"default": "default",
"description": "A string parameter",
"title": "Param Optional With Desc",
"type": "string"
}
},
"required": [
"param_required"
],
"title": "Config",
"type": "object"
}
}
which contains enough information to generate a UI form for the component’s configuration.
The base of the configuration for the eVOLVER application is defined in the
Config of Evolver, so to get the schema for
the base configuration you can use:
GET /schema/?classinfo=evolver.device.Evolver
This is the schema of what will be deserialized from yaml when the eVOLVER application starts up. The schema of any dynamically configured component within can be inspected as above.
In the python interpreter, you can access the schema of any config component by calling pydantics model_json_schema() on the class:
from evolver.device import Evolver
schema = Evolver.Config.model_json_schema()
Component initialization and configuration
When a component is initialized via a config descriptor (e.g. when reading the
configuration file on startup or via the web update API - specifically by
calling create on config
descriptor or create() on the base interface), by default the members
of its config are unpacked and passed to the components constructor. Then the
base class for components
(BaseInterface) will automatically
assign the config members to the component instance as attributes.
This effectively means that parameters in the config are also class parameters and can be directly accessed and used within the component logic. In the example above, this means that the control code in MyController could access self.param_required, as in:
class MyController(Controller):
<<<...>>>
def control(self):
if self.param_required > 0:
# do something based on the required parameter
<<<...>>>
Warning
This has an implication on the mutability of the class parameters that share a name with the config members: due to the serializability requirements of components, such members should also be serializable and compatible with the config model. For example, if param has a type hint of float in the config model, then setting self.param = “string” will violate the type validation on serialization and may cause errors in the application.