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.0rc8.tar.gz (58.4 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.0rc8-py3-none-any.whl (74.5 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: spx_sdk-1.0.0rc8.tar.gz
  • Upload date:
  • Size: 58.4 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.0rc8.tar.gz
Algorithm Hash digest
SHA256 330a5c0de807ecb2e802161336f6a12c7f981eacd73515d51769f8d66ffb6fe7
MD5 d5aaa74297778674d2f7260714f22631
BLAKE2b-256 f4df293f307fb39429905fa511d55fcb1541f04e174ac3ea7c85ab64d74245ae

See more details on using hashes here.

File details

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

File metadata

  • Download URL: spx_sdk-1.0.0rc8-py3-none-any.whl
  • Upload date:
  • Size: 74.5 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.0rc8-py3-none-any.whl
Algorithm Hash digest
SHA256 1a6ccbc77a2224f3cd6fe72ff57a0ca73f57e5a636ccbbced92e41912dd1a33f
MD5 e69414431112dddca9d3a9cbf5a79077
BLAKE2b-256 ba8ee5f2a7d74721cb279b32d35439ee95367c2ace193c81e5459100db04dfcb

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