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.0a4.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.0a4-py3-none-any.whl (73.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: spx_sdk-1.0.0a4.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.0a4.tar.gz
Algorithm Hash digest
SHA256 52dd64e9989c42ee8c508b801cc09255b601326ac6ea56c42b8f00087a930e18
MD5 056430f229ab9897541aa91efeab2450
BLAKE2b-256 300988747d2c1706b03f95057a8929f182be6f17121776decd1783318a1a901e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: spx_sdk-1.0.0a4-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.0a4-py3-none-any.whl
Algorithm Hash digest
SHA256 cc0fc0c1dfafe748629976120042cde6cbd042a9038df993bcc0046b2f4cf095
MD5 7650c697de9162369c6cb6ecff776cbb
BLAKE2b-256 b19f4570a4dfeb290e4aeb40598a8a5c7acc2d5bf76714520470008e0da717cc

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