Skip to main content

A Python library for managing and loading class instances from modules and YAML configurations.

Project description

SPX SDK

SPX SDK is the Python toolkit that powers Hammerheads' SPX simulation and automation platform. It provides the runtime building blocks for composing digital twins, orchestrating their lifecycle, validating configuration, and wiring custom logic into larger simulation pipelines.

Why SPX SDK?

  • Composable component tree – Build rich hierarchies by combining SpxComponent subclasses, containers, and reusable hooks.
  • Registry-driven configuration – Register classes once and instantiate them from Python dicts, YAML, or JSON without hand-written factories.
  • Lifecycle orchestration – Consistent prepare → start → run → pause → stop → reset → destroy transitions that propagate through the component tree.
  • Actions & attributes – Model device behaviour declaratively and bind to inputs/outputs via the actions and attributes subsystems.
  • Diagnostics & validation – Guard hooks, structured tracing, and JSON Schema validation help catch configuration issues early.
  • Extensibility first – Override only what you need; compose existing mixins or build your own domains on top.

The repository also contains utilities for loading Python modules dynamically, registering simulation hooks, and integrating with CI pipelines.

Installation

Install from PyPI:

pip install spx-sdk

Using Poetry inside a project:

poetry add spx-sdk

Quickstart

from spx_sdk.components import SpxComponent, SpxContainer
from spx_sdk.registry import register_class


@register_class(name="info")
class InfoComponent(SpxComponent):
    message: str = ""

    def run(self, *args, **kwargs):
        self.logger.info("InfoComponent → %s", self.message)


# Definition could be loaded from YAML/JSON; here we inline a dict for brevity.
definition = {
    "info": {
        "description": "Simple hello-world component",
        "message": "Hello from SPX!",
    }
}

# Build the component tree and execute a lifecycle step.
root = SpxContainer(definition, name="root")
root.prepare()
root.run()

Configuration-driven

The same hierarchy can be produced from JSON, TOML, or any dict-like structure. SpxContainer walks the configuration tree, looks up registered classes (here info), and instantiates them with their definitions. Components can override _populate for custom parsing or rely on field assignment when attributes already exist.

Hierarchy & Lifecycle Propagation

SpxContainer builds a component tree and propagates lifecycle calls to every descendant. Parent components call prepare(), start(), run(), pause(), and stop() on each child in insertion order—no extra wiring required.

from spx_sdk.components import SpxComponent, SpxContainer


class Motor(SpxComponent):
    def prepare(self, *args, **kwargs):
        super().prepare(*args, **kwargs)
        self.logger.info("Motor prepared")

    def run(self, *args, **kwargs):
        super().run(*args, **kwargs)
        self.logger.info("Motor running")

    def pause(self, *args, **kwargs):
        super().pause(*args, **kwargs)
        self.logger.info("Motor paused")


class Gearbox(SpxComponent):
    def start(self, *args, **kwargs):
        super().start(*args, **kwargs)
        self.logger.info("Gearbox engaged")

    def stop(self, *args, **kwargs):
        super().stop(*args, **kwargs)
        self.logger.info("Gearbox stopped")


plant_definition = {
    "motor": {"class": "Motor"},
    "gearbox": {"class": "Gearbox"},
}

root = SpxContainer(plant_definition, name="line")
root.prepare()  # prepare() cascades to motor and gearbox
root.start()    # start() cascades, invoking Gearbox.start()
root.run()      # run() cascades, invoking Motor.run()
root.pause()    # pause() cascades, invoking Motor.pause()
root.stop()     # stop() cascades, invoking Gearbox.stop()

This pattern supports deep hierarchies: subcontainers receive the same lifecycle calls, so complex assemblies stay synchronised. Combine it with hooks or actions to react to specific transitions without modifying the core lifecycle methods.

Diagnostics & Observability

SPX SDK collects structured diagnostics for every lifecycle operation. Two helper decorators keep insights consistent:

  • spx_sdk.diagnostics.guard wraps public APIs and converts raised exceptions into rich diagnostic payloads. You decide whether errors bubble up or are translated to HTTP-compatible responses.
  • spx_sdk.diagnostics.trace captures timing, context, and metadata for nested operations. Traces can be streamed to logs or aggregated by observability backends.

Example:

from spx_sdk.diagnostics import guard, trace


class Sensor(SpxComponent):
    @guard("sensor.collect", bubble=False, http_status=500)
    def run(self):
        with trace(self, action="sensor.readout", extra={"units": "celsius"}):
            value = self._read_hardware()
            self.logger.debug("Sensor value=%s", value)
            self._publish(value)

Guards guard the outer API (run), while traces describe nested steps. All diagnostics include the component path, so log aggregation tools can correlate entries across the hierarchy.

Validation Pipeline

Configuration validation is handled by a JSON Schema backend (spx_sdk.validation._jsonschema_backend). Key features:

  • Load schemas directly from package resources or custom paths.
  • Validate component definitions, actions, and attributes before runtime.
  • Receive enumerated error messages with pointers to invalid data.

You can trigger validation programmatically:

from spx_sdk.validation import JSONSchemaValidator

validator = JSONSchemaValidator(schema="schemas/component.json")
problems = validator.validate(definition)
if problems:
    for issue in problems:
        print(f"[{issue.path}] {issue.message}")
    raise ValueError("Invalid definition")

When using SpxContainer, validation hooks can run automatically: failed checks raise guarded diagnostics so CI pipelines fail fast with meaningful feedback. For CLI or automation use, wire the validator into your release tooling to block deployments when definitions drift from the contract.

Defining Schemas

Schemas live alongside your components and describe allowed keys, required attributes, and value types. A minimal JSON Schema for the InfoComponent from the quickstart could look like this:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "InfoComponent",
  "type": "object",
  "properties": {
    "description": {
      "type": ["string", "null"],
      "description": "Optional human-readable summary."
    },
    "message": {
      "type": "string",
      "minLength": 1,
      "description": "Message emitted during run()."
    }
  },
  "required": ["message"],
  "additionalProperties": false
}

Store the schema under schemas/info_component.json (or similar) and point the validator to it:

validator = JSONSchemaValidator(schema="schemas/info_component.json")
validator.raise_for_errors(definition)  # convenience helper that raises on failure

Complex hierarchies can compose schemas—refer to shared fragments with $ref or register multiple schemas per component type. Validation runs before lifecycle execution, so your system fails fast when configuration drifts from the contract.

Key Concepts

  • SpxComponent – Base class for every node. Handles child management, lifecycle, and configuration population. Each component exposes an optional description derived from its definition.
  • SpxContainer – Specialised component that reads a configuration tree and instantiates registered children automatically.
  • Registry@register_class decorator exposes classes under stable names so definitions stay declarative.
  • Actions & attributes – Pluggable behaviours that let components exchange data or invoke business logic during lifecycles.
  • Diagnostics – Guard and trace helpers wrap operations with structured error handling and instrumentation.
  • Validation – JSON Schema backend keeps definitions consistent and provides actionable error messages.

Explore the spx_sdk package to see how these pieces come together.

Examples & Documentation

  • Browse the examples/ directory for end-to-end demonstrations.
  • Review unit tests under tests/ to understand expected behaviour and extension points.
  • API docs are under active development; for now, inline docstrings and tests are the canonical reference.

Contributing

  1. Clone the repository and install dependencies:

    poetry install
    
  2. Run the test suite before submitting changes:

    poetry run pytest
    
  3. Follow the existing coding style and ensure new public APIs are covered by tests.

Bug reports and feature suggestions are welcome via GitHub issues or pull requests.

License

SPX SDK is released under the MIT License. See LICENSE for details.

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

spx_sdk-1.0.0rc5.tar.gz (57.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

spx_sdk-1.0.0rc5-py3-none-any.whl (73.9 kB view details)

Uploaded Python 3

File details

Details for the file spx_sdk-1.0.0rc5.tar.gz.

File metadata

  • Download URL: spx_sdk-1.0.0rc5.tar.gz
  • Upload date:
  • Size: 57.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spx_sdk-1.0.0rc5.tar.gz
Algorithm Hash digest
SHA256 129d6e33be6fac480dabf15174aa708bd7c1e08a579bc6d9880ee1b0a45d4f09
MD5 632095b3c8920d3b7318ab24947cc09b
BLAKE2b-256 5ea7f8363ef6ea4575bc70cbc956d8bdd8952e0db4f415e16c18b9917dbb98f3

See more details on using hashes here.

File details

Details for the file spx_sdk-1.0.0rc5-py3-none-any.whl.

File metadata

  • Download URL: spx_sdk-1.0.0rc5-py3-none-any.whl
  • Upload date:
  • Size: 73.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spx_sdk-1.0.0rc5-py3-none-any.whl
Algorithm Hash digest
SHA256 3cbd81067ef4e4a0e847056aa1734ca6cf6101536bf6f61bee9b6dd4a46797d1
MD5 68d015a9bc417d8a2a258247728a473b
BLAKE2b-256 cc94ff78a06b45ca70643e0c418f9f554d0ac7887a58387656332f780afbd927

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page