Skip to content

Schema Class

The Schema class in pydoover.config is the base class for all application configuration definitions. You subclass it, declare typed elements as class attributes, and assign it to your application's config_cls.

Defining a Schema

Declare elements as class-level attributes. The order you define them in is the order they appear in the UI form.

from pydoover.config import Schema, Integer, Number, String, Boolean

class PumpConfig(Schema):
    pump_pin = Integer(
        "Digital Output Number",
        description="The digital output pin to drive the pump",
    )
    pump_on_time = Number(
        "Pump On Time",
        default=5.2,
        description="The time in seconds to run the pump",
    )
    site_name = String(
        "Site Name",
        default="Unnamed",
    )
    auto_mode = Boolean(
        "Automatic Mode",
        default=True,
    )

In this example, pump_pin has no default, making it a required field. The operator must provide a value when deploying the application. The other three elements have defaults and are optional.

The name Parameter

Every element has an internal _name that is used as the key in JSON Schema output and deployment config dictionaries. By default, this name is auto-generated from the display_name by converting it to lowercase and replacing spaces with underscores (via sanitize_display_name).

You can set an explicit name using the name parameter.

from pydoover.config import String

# Auto-generated name: "site_name"
site = String("Site Name", default="A")

# Explicit name: "my_custom_key"
site = String("Site Name", default="A", name="my_custom_key")

Names must only contain alphanumeric characters, hyphens, underscores, and spaces. The KEY_VALIDATOR regex enforces this: ^[ a-zA-Z0-9_-]*$.

Inheritance

Schema subclasses inherit elements from their parent classes through the MRO (method resolution order). This lets you create base configurations that are extended by more specific schemas.

from pydoover.config import Schema, String, Number

class BaseConfig(Schema):
    site_name = String("Site Name", default="Unnamed")
    poll_interval = Number("Poll Interval", default=30.0)


class ExtendedConfig(BaseConfig):
    # Inherits site_name and poll_interval from BaseConfig
    alert_threshold = Number("Alert Threshold", default=40.0)

ExtendedConfig has three elements: site_name and poll_interval (inherited) plus alert_threshold (its own).

Overriding Inherited Elements

If a subclass declares an element with the same _name as an inherited element, the subclass version replaces it. The position of the overridden element is preserved from the parent.

class CustomConfig(BaseConfig):
    # Override the inherited poll_interval with a different default
    poll_interval = Number("Poll Interval", default=60.0)

This preserves the element's position in the schema output while changing its configuration.

Required Elements

An element is required by default if it has no default value (i.e., the default is NotSet). You can override this behaviour with the required parameter.

from pydoover.config import String, NotSet

# Required (no default)
name = String("Name")

# Optional (has a default)
nickname = String("Nickname", default="")

# Explicitly required even though a default exists
important = String("Important", default="fallback", required=True)

# Explicitly optional (must provide a default)
optional = String("Optional", default=None, required=False)
Warning

Passing required=False without a default raises a ValueError. An optional element needs a fallback value for when the operator does not provide one.

When deployment config is loaded, missing required elements raise a ValueError. Missing optional elements are set to their default value.

Accessing Values

After configuration is loaded (during the application's setup phase), access element values via the .value property.

class MyProcessor(Application):
    config_cls = PumpConfig

    async def setup(self):
        pin = self.config.pump_pin.value       # int
        time = self.config.pump_on_time.value   # float
        name = self.config.site_name.value      # str
        auto = self.config.auto_mode.value      # bool

If you access .value before configuration has been loaded, a ValueError is raised with a message indicating which element is not set.

JSON Schema Generation

Call to_schema() on the Schema class (not an instance) to generate the JSON Schema representation.

import json

schema = PumpConfig.to_schema()
print(json.dumps(schema, indent=2))

This produces a JSON Schema draft 2020-12 document with:

  • A properties object containing each element's schema fragment
  • A required array listing elements that have no default
  • Custom extensions like x-name, x-hidden, x-position, x-required, and x-advanced for the Doover UI

The output can be used for validation, documentation, or form generation outside of the Doover platform.

Schema Name

The name class parameter (passed via __init_subclass__) sets the schema's title in the JSON Schema output. It defaults to "$default".

class MyConfig(Schema, name="Pump Controller Configuration"):
    pump_pin = Integer("Pump Pin")

The name appears as the title field in the generated JSON Schema.

Exporting to doover_config.json

The export() class method writes the schema to a doover_config.json file, which is included in the application package for deployment.

from pathlib import Path
from pydoover.config import Schema, Number

class Config(Schema):
    threshold = Number("Threshold", default=40.0)

# Export the schema to the project's config file
Config.export(Path("doover_config.json"), "my_app_name")

This writes (or updates) the file with the schema under the given app name key:

{
    "my_app_name": {
        "config_schema": {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "$id": "",
            "title": "$default",
            "type": "object",
            "properties": { ... },
            "additionalElements": true,
            "required": []
        }
    }
}

If the file already exists, export() merges the schema into it rather than overwriting.

Information Circle

In most cases, you do not call export() manually. The Doover CLI command doover config-schema export handles this for you as part of the application build process. You can also validate schemas with doover config-schema validate and generate sample configs with doover config-schema generate.

Runtime Configuration Loading

When the application starts (or a processor is invoked), the framework calls _inject_deployment_config(config) with the deployment configuration dictionary. This method:

  1. For each key in the config dict, finds the matching element by name and calls load_data(value) on it
  2. For unknown keys (not declared in the schema), creates a dynamic ConfigElement and attaches it to the instance
  3. For declared elements missing from the config dict, checks if they are required (raises ValueError) or optional (sets them to their default)

You do not call _inject_deployment_config yourself. The framework handles it during setup.

Related Pages