A Signal-based reactive component microkernel for Python backend services
Project description
SignalPy Microkernel
A Signal-based reactive component kernel for backend services. The kernel is three reactive primitives (Signal, Computed, Effect) + component wiring. Everything else is components.
What is this?
A small (~2,600 LOC core) reusable kernel built on a two-axis model:
Axis 1 — The Mechanism (irreplaceable core, src/signalpy/kernel/):
- Reactivity engine — Signal, Computed, Effect primitives (Vue 3 / Preact Signals model)
- Component model —
@component,@provides,@requires,@runnable,@apidecorators - Lifecycle manager — dependency-ordered activation (toposort), reverse-ordered shutdown
- Service registry — give/take: components provide and require services by contract name
- Service bus —
invoke(target, params)(request/response) +publish/subscribe(events) - Runtime — per-component scoped context with structural isolation (credentials, storage)
- Trait system — L0–L3 traits auto-computed from decorators, queryable at runtime
Axis 2 — The Vocabulary (replaceable components, providers/ + adapters/):
- Platform components — config (Signal-backed, layered YAML + runtime updates), logging (structured JSON), credentials (per-app scoped), storage (local FS), tracing (OTel bridge)
- API Gateway — composes
@apideclarations from all components into unified surfaces per transport - Transport adapters — REST (FastAPI), MCP (tool server), CLI (Click)
Quick Example
from pydantic import BaseModel
from signalpy.kernel import Kernel, component, provides, requires, runnable, lifecycle, computed, effect
class GreetParams(BaseModel):
name: str = "world"
@component("greeter", version="1.0")
@provides("IGreeter")
@requires(config="IConfig")
class GreeterApp:
@lifecycle.activate
def activate(self):
pass # self.rt is available here
@computed
def prefix(self):
"""Cached. Auto-recomputes when config changes."""
return self.rt.config.get("greeter.prefix", "Hello")
@effect
def on_config_change(self):
"""Auto-tracks deps, re-runs when they change."""
prefix = self.rt.config.get("greeter.prefix")
print(f"Greeter prefix is now: {prefix}")
@runnable("greet", params=GreetParams, description="Greet someone by name")
async def greet(self, params):
return {"message": f"{self.prefix()}, {params.name}!"}
The same runnables automatically appear as REST endpoints, MCP tools, and CLI commands — the component never knows which transport serves it.
The 13 decorators
@component @provides @requires # core
@computed @effect # reactive
@lifecycle.activate/deactivate/health # lifecycle
@runnable @api @exportable # surface
@prop @kind @skill # metadata
@subscribe # events
Project Layout
src/signalpy/
├── kernel/ ← Axis 1 (~2,600 LOC core)
│ ├── __init__.py Kernel class: boot(), shutdown(), discover()
│ ├── reactive.py Signal, Computed, Effect, batch
│ ├── component.py 13 decorators + metadata
│ ├── runtime.py ReactiveRuntime: Signal-backed injection
│ ├── registry.py ServiceRegistry: provide(), require(), query()
│ ├── bus.py Bus: invoke(), publish(), subscribe()
│ ├── lifecycle_manager.py LifecycleManager: state machine, toposort
│ ├── traits.py TraitRegistry: L0-L3 trait definitions
│ └── contracts.py Protocol interfaces: IConfig, IStorage, ILogger, etc.
│
├── providers/ ← Axis 2: platform components
│ ├── config.py ConfigProvider → IConfig + IConfigAdmin (Signal-backed)
│ ├── logging_provider.py LoggingProvider → ILogger (structured JSON)
│ ├── credentials.py CredentialProvider → ICredentials (per-app scoped)
│ ├── storage.py StorageProvider → IStorage (local filesystem)
│ ├── auth.py AuthProvider → IAuth (token-based, pluggable)
│ ├── workspace.py WorkspaceProvider → IWorkspace (paths, settings)
│ ├── tracing.py TracingProvider → ITracer (OTel + Phoenix bridge)
│ └── gateway.py APIGateway → IGateway (surface composition)
│
├── adapters/ ← Axis 2: transport adapters
│ ├── rest.py RESTTransport (FastAPI routes)
│ ├── mcp.py MCPTransport (MCP tools)
│ └── cli.py CLITransport (Click commands)
│
├── tests/ 253 tests
└── examples/ Progressive examples (01-07)
Running
# Reactive config demo
PYTHONPATH=src python -m signalpy.examples.03_reactive_config
# Full test suite (253 tests)
PYTHONPATH=src python -m pytest src/signalpy/tests/ -v
Constitution
- Everything is a Component. Storage, auth, logging, the API layer — all components.
- Components Give and Take. No globals, no singletons, no ambient state.
- The Kernel has zero business logic. All domain behavior lives in apps.
- Transport is an adapter, never a core concern.
- Distribution is transparent. In-process or cross-network — caller doesn't know.
- Apps are deployment units, components are composition units.
- Lifecycle is explicit and managed.
- Every API has a client counterpart.
- The kernel is small. If you can't read the entire kernel source in one sitting, it's too big.
Dependencies
kernel/has ZERO required dependenciesproviders/needs: pyyaml (optional, for YAML config)adapters/restneeds: fastapiadapters/clineeds: click- Tracing needs: opentelemetry-api, opentelemetry-sdk
- Examples/tests need: pydantic
Inspiration
iPOPO (OSGi patterns for Python), Vue 3 (Signal/Computed/Effect reactivity), Dapr (building blocks), Engin/Uber Fx (give/take DI).
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file signalpy_kernel-0.1.0.tar.gz.
File metadata
- Download URL: signalpy_kernel-0.1.0.tar.gz
- Upload date:
- Size: 219.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c52f9de7bc4d94e909d6d70603bd0f3893a49950d087a5a532b6402e81b2a5e
|
|
| MD5 |
df36cc61409866d712b3d9ff5260c64e
|
|
| BLAKE2b-256 |
f54c23d18771e25e5d35ec38db1065da6717159381364a80cafc6b9bf344035e
|
Provenance
The following attestation bundles were made for signalpy_kernel-0.1.0.tar.gz:
Publisher:
publish-pypi.yml on bayeslearner/signalpy-kernel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
signalpy_kernel-0.1.0.tar.gz -
Subject digest:
6c52f9de7bc4d94e909d6d70603bd0f3893a49950d087a5a532b6402e81b2a5e - Sigstore transparency entry: 1395689169
- Sigstore integration time:
-
Permalink:
bayeslearner/signalpy-kernel@42e0326eeb8a236a9b5b1fc6c0d51c1008026fef -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bayeslearner
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@42e0326eeb8a236a9b5b1fc6c0d51c1008026fef -
Trigger Event:
push
-
Statement type:
File details
Details for the file signalpy_kernel-0.1.0-py3-none-any.whl.
File metadata
- Download URL: signalpy_kernel-0.1.0-py3-none-any.whl
- Upload date:
- Size: 142.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb03a56726a18cc1ee4bfead48638794b6c49901957c051ab593f75ccb2a9cac
|
|
| MD5 |
3352cb2fb0f298a70f59629cf4ec3315
|
|
| BLAKE2b-256 |
206216a7f2070bc9b80389265893d8236de3c315910990fbe4c3b765d41ac1b3
|
Provenance
The following attestation bundles were made for signalpy_kernel-0.1.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on bayeslearner/signalpy-kernel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
signalpy_kernel-0.1.0-py3-none-any.whl -
Subject digest:
bb03a56726a18cc1ee4bfead48638794b6c49901957c051ab593f75ccb2a9cac - Sigstore transparency entry: 1395689176
- Sigstore integration time:
-
Permalink:
bayeslearner/signalpy-kernel@42e0326eeb8a236a9b5b1fc6c0d51c1008026fef -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bayeslearner
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@42e0326eeb8a236a9b5b1fc6c0d51c1008026fef -
Trigger Event:
push
-
Statement type: