sequenceDiagram
participant S as Signal (self.rt.config)
participant E as @effect method
participant C as @computed property
Note over S,C: Boot: effect runs once, tracks deps
E->>S: reads config → tracked
S-->>E: subscriber registered
Note over S,C: Later: config changes
S->>E: notify → effect re-runs
S->>C: notify → computed marked dirty
C->>C: next read → recomputes
SignalPy Kernel
A reactive component kernel for backend services
The Idea
Frontend frameworks like Vue and React changed how UIs work: you declare what depends on what, and when data changes, the framework updates everything automatically. No manual callbacks. No stale state.
SignalPy brings this model to backend services.
Your components declare dependencies. The kernel wraps every injected service in a reactive Signal. When you read self.rt.config inside an @effect, the kernel tracks that read. When the config service changes — new provider hot-added, config pushed, service ranking changed — your effect re-runs automatically.
from signalpy.kernel import component, requires, computed, effect, runnable
from signalpy.kernel.contracts import IConfig
@component("greeter")
@requires(config=IConfig)
class Greeter:
@computed # cached, auto-recomputes
def greeting(self):
return self.rt.config.get("greeting", "Hello")
@effect # re-runs when deps change
def log_greeting(self):
print(f"Greeting is now: {self.greeting()}")
@runnable("greet", params=GreetParams, description="Greet someone")
async def greet(self, params):
return {"message": f"{self.greeting()}, {params.name}!"}No @bind. No @unbind. No manual event wiring. The reactive graph handles propagation.
How Reactivity Works
Three primitives, implemented in 327 lines of pure Python:
| Primitive | What it does | In the kernel |
|---|---|---|
| Signal | Holds a value. Reading tracks the caller. Writing notifies all readers. | Each self.rt.config, self.rt.db is a Signal |
| Computed | Derives a value from Signals. Caches. Recomputes only when deps change. | @computed decorator on component methods |
| Effect | Runs a function. Re-runs when any Signal it read changes. | @effect decorator on component methods |
When you write self.rt.config.get("url") inside an @effect:
self.rt.config→ callsSignal.get()→ kernel records “this Effect depends on this Signal”- Later, config changes →
Signal.set(new_config)→ kernel notifies all subscribers - Your
@effectre-runs automatically — reads the new config, does whatever it needs
This is Vue 3’s reactivity model applied to service injection. Same algorithm (dependency tracking via a context variable), same composability (Computed chains, Effects compose), different domain (backend services instead of DOM).
The 13 Decorators
@component @provides @requires # core
@computed @effect # reactive
@lifecycle.activate/deactivate/health # lifecycle
@runnable @api # surface
@prop @kind @skill # metadata
@subscribe # events
Learn
Tutorials teach one concept at a time. Each builds on the last.
| # | Tutorial | Concepts |
|---|---|---|
| 1 | First Component | @component, @lifecycle, boot, shutdown |
| 2 | Give and Take | @provides, @requires, typed contracts, self.rt |
| 3 | Dynamic Services | list[C] aggregate, @effect, hot_add/hot_remove |
| 4 | Runnables & API | @runnable, @api, bus invocation, transports |
| 5 | Auth & Policy | requires_action, requires_role, bus-level auth |
| 6 | Building a Provider | Write your own ICache or IDatabase, bridge pattern |
| 7 | Commercial Patterns | Secret rotation, A/B testing, multi-tenant, extension bundles, circuit breaker, audit trail |
Reference
| Page | What’s in it |
|---|---|
| Traits (L0–L3) | 23 traits across 4 levels — what each is, how you get it, example |
| Decorators | All 12 decorators — signature, parameters, example |
| Contracts | 8 Protocol interfaces — methods, providers, usage |
| Kernel API | Kernel, ServiceRegistry, Bus, Runtime, Signal/Computed/Effect |
Concepts
Read in this order — each builds on the last:
| Page | What it explains |
|---|---|
| Architecture | Constitution, two-axis model, what’s in the kernel |
| Reactivity by Example | Consumer / Provider / Kernel side-by-side, one full cycle from boot to re-run |
| Context, ContextVar, Active Consumer | Why a single _active_consumer ContextVar is enough for many effects |
| Reactive Engine: Line by Line | Every line of reactive.py annotated |
| Threading Model | Threads, asyncio, RLock, ContextVar isolation, vs stdlib |
| Bridging External Systems | Bridge pattern, three access levels, 7 rules |
| Deployment Scales | Monolith → containers → Dapr → agent system |
Examples
| Example | Domain | Run |
|---|---|---|
01_hello.py |
— | PYTHONPATH=src python src/signalpy/examples/01_hello.py |
02_give_and_take.py |
IoT/sensors | PYTHONPATH=src python src/signalpy/examples/02_give_and_take.py |
03_reactive_config.py |
Data pipeline | PYTHONPATH=src python src/signalpy/examples/03_reactive_config.py |
04_dynamic_plugins.py |
Alerting | PYTHONPATH=src python src/signalpy/examples/04_dynamic_plugins.py |
05_auth_protected_api.py |
CMS | PYTHONPATH=src python src/signalpy/examples/05_auth_protected_api.py |
06_multi_provider_ranking.py |
Database | PYTHONPATH=src python src/signalpy/examples/06_multi_provider_ranking.py |
07_fastapi_full.py |
Web app | PYTHONPATH=src python src/signalpy/examples/07_fastapi_full.py |
secret_rotation.py |
Credential mgmt | PYTHONPATH=src python -m signalpy.examples.secret_rotation |
ab_testing.py |
Experimentation | PYTHONPATH=src python -m signalpy.examples.ab_testing |
multi_tenant.py |
SaaS / multi-tenant | PYTHONPATH=src python -m signalpy.examples.multi_tenant |
extension_bundle.py |
Plugin system | PYTHONPATH=src python -m signalpy.examples.extension_bundle |
circuit_breaker.py |
Resilience | PYTHONPATH=src python -m signalpy.examples.circuit_breaker |
audit_trail.py |
Compliance | PYTHONPATH=src python -m signalpy.examples.audit_trail |
At a Glance
| Kernel core | ~2600 lines, 9 files, zero required dependencies |
| Decorators | 12: @component, @provides, @requires, @computed, @effect, @runnable, @api, @lifecycle.*, @prop, @kind, @skill, @subscribe |
| Reactivity | Signal, Computed, Effect — Vue 3-style, zero dependencies |
| Providers | 9: config, logging, credentials, storage, auth, tracing, workspace, gateway, configadmin |
| Adapters | 3: REST (FastAPI), MCP, CLI (Click) |
| Tests | 58 |
| Traits | 23 across 4 levels (L0 Kernel, L1 Platform, L2 App, L3 Instance) |