Skip to content

Schema Base Class

The Schema class is the foundation for defining configuration schemas in pydoover. It provides automatic element registration, JSON Schema generation, and deployment configuration injection.

Import

from pydoover import config

Class Definition

class Schema:
    """Represents the configuration schema for a Doover application."""

Creating a Schema

Subclass Schema and define configuration elements in __init__:

from pydoover import config

class PumpControlConfig(config.Schema):
    def __init__(self):
        self.pump_pin = config.Integer(
            "Digital Output Number",
            description="The digital output pin to drive the pump."
        )
        self.pump_on_time = config.Number(
            "Pump On Time",
            default=5.2,
            description="The time in seconds to run the pump."
        )
        self.engine_type = config.Enum(
            "Engine Type",
            choices=["Honda", "John Deere", "Cat"],
            default="Honda",
            description="The type of diesel engine."
        )

How Element Registration Works

When you assign a ConfigElement to an attribute, the Schema class automatically:

  1. Transforms the display name to a key (lowercase, spaces to underscores)
  2. Assigns a position based on definition order
  3. Registers the element in an internal map
# When you write:
self.pump_pin = config.Integer("Digital Output Number")

# The Schema class:
# 1. Sets element._name to "digital_output_number"
# 2. Sets element._position to the current count
# 3. Stores element in the internal map

Methods

to_dict()

Converts the schema to a JSON Schema dictionary.

def to_dict(self) -> dict

Returns: A dictionary representing the JSON Schema (draft 2020-12).

Example:

schema = PumpControlConfig()
json_schema = schema.to_dict()

# Result structure:
# {
#     "$schema": "https://json-schema.org/draft/2020-12/schema",
#     "$id": "",
#     "title": "Application Config",
#     "type": "object",
#     "properties": { ... },
#     "additionalElements": True,
#     "required": [ ... ]
# }

export()

Exports the schema to a JSON file.

def export(self, fp: pathlib.Path, app_name: str) -> None

Parameters:

ParameterTypeDescription
fppathlib.PathPath to the JSON file (typically doover_config.json)
app_namestrApplication name used as the key in the config file

Example:

from pathlib import Path
from pydoover import config

class MyConfig(config.Schema):
    def __init__(self):
        self.setting = config.String("Setting", default="value")

if __name__ == "__main__":
    schema = MyConfig()
    schema.export(Path("doover_config.json"), "my_app")

The export method:

  1. Reads existing doover_config.json if it exists
  2. Updates or creates the config_schema field under the app name
  3. Writes the updated JSON back to the file

add_element()

Manually adds an element to the schema. Usually not needed as attribute assignment handles this automatically.

def add_element(self, element: ConfigElement) -> None

Parameters:

ParameterTypeDescription
elementConfigElementThe configuration element to add

Raises: ValueError if an element with the same name already exists.

_inject_deployment_config()

Internal method that loads deployment configuration values into elements.

def _inject_deployment_config(self, config: dict[str, Any]) -> None

Parameters:

ParameterTypeDescription
configdict[str, Any]Dictionary of configuration values

Behavior:

  1. For each key in the config, finds the matching element and calls load_data()
  2. For elements not in the config:
    • Required elements raise ValueError
    • Optional elements are set to their default value
  3. Unknown keys are logged and skipped

Complete Example

from pathlib import Path
from pydoover import config

class SensorConfig(config.Schema):
    def __init__(self):
        # Required element (no default)
        self.sensor_id = config.String(
            "Sensor ID",
            description="Unique identifier for the sensor"
        )

        # Optional elements (have defaults)
        self.poll_interval = config.Number(
            "Poll Interval",
            default=1.0,
            description="Seconds between sensor readings"
        )
        self.enable_logging = config.Boolean(
            "Enable Logging",
            default=True,
            description="Whether to log sensor readings"
        )

        # Enum with choices
        self.sensor_type = config.Enum(
            "Sensor Type",
            choices=["Temperature", "Humidity", "Pressure"],
            default="Temperature"
        )

# Export the schema
if __name__ == "__main__":
    SensorConfig().export(Path("doover_config.json"), "sensor_app")

Integration with Applications

In a Doover application, the schema is typically a class attribute:

from pydoover.docker import Application
from pydoover import config

class SensorConfig(config.Schema):
    def __init__(self):
        self.poll_interval = config.Number("Poll Interval", default=1.0)

class SensorApp(Application):
    config = SensorConfig()

    def setup(self):
        # Access configuration values
        interval = self.config.poll_interval.value
        print(f"Polling every {interval} seconds")

The framework automatically calls _inject_deployment_config() with values from the deployment before your application's setup() method runs.

Key Validation

Display names (and thus keys) must match the pattern:

^[ a-zA-Z0-9_-]*$

Valid characters:

  • Letters (a-z, A-Z)
  • Numbers (0-9)
  • Spaces
  • Hyphens (-)
  • Underscores (_)

Invalid keys raise a ValueError at element creation time.

Related