Skip to main content

Bi-directional runtime reconciler and drift-aware state graph for full-stack systems

Project description

swop

Bi-directional runtime reconciler and drift-aware state graph for full-stack systems.

Version Python License

AI Cost Tracking

PyPI Version Python License AI Cost Human Time Model

  • ๐Ÿค– LLM usage: $2.1000 (14 commits)
  • ๐Ÿ‘ค Human dev: ~$558 (5.6h @ $100/h, 30min dedup)

Generated on 2026-04-23 using openrouter/qwen/qwen3-coder-next


Swop is a Python toolkit for inspecting, reconciling, and maintaining the architecture of full-stack CQRS projects. It scans Python source for commands, queries, events, and handlers; generates deterministic manifests; detects schema drift; and exports the runtime state graph to multiple formats.


Table of Contents


Installation

pip install swop

Development install:

pip install -e ".[dev]"

Requires Python 3.8+ and PyYAML.


Quick Start

1. Initialise a project

swop init

Scaffolds swop.yaml and the .swop/ state directory in the current folder.

2. Annotate your domain code

from dataclasses import dataclass
from swop import command, handler

@command("billing")
@dataclass
class IssueInvoice:
    customer_id: int
    amount: float

@handler(IssueInvoice)
class IssueInvoiceHandler:
    def handle(self, cmd: IssueInvoice) -> int:
        return cmd.customer_id

3. Scan and generate manifests

swop scan --format json
swop gen manifests

4. Watch for changes

swop watch

Re-runs the scan and regenerates manifests automatically when any .py file changes.


CQRS Decorators

swop provides lightweight, no-op decorators that register decorated classes in a module-global registry. They do not change runtime behaviour, so existing code continues to work unchanged.

Decorator Purpose Example
@command(context) Register a command @command("billing") @dataclass class IssueInvoice: ...
@query(context) Register a query @query("catalog") @dataclass class ListProducts: ...
@event(context, emits=[...]) Register a domain event @event("billing", emits=["InvoiceIssued"]) class PaymentReceived: ...
@handler(Target) Register a command/query handler @handler(IssueInvoice) class IssueInvoiceHandler: ...

All decorators expose a __swop_cqrs__ attribute on the decorated class with metadata including kind, context, source_file, and source_line.


CLI Reference

swop [--mode {STRICT,SOFT,OBSERVE,AUTO_HEAL}] <command>
Command Description
swop init Scaffold swop.yaml and .swop/ state dir
swop scan [--format {text,json,html}] Walk source roots and classify CQRS artifacts
swop gen manifests Generate per-context YAML manifests
swop gen proto [--out PATH] Generate .proto from manifests
swop gen grpc-python Compile Python gRPC bindings
swop gen grpc-ts Compile TypeScript gRPC bindings
swop gen services Generate service stubs from manifests
swop watch [--once] Watch source files and rebuild on change
swop sync Run one reconciliation pass
swop diff Compute drift and exit non-zero if drift exists
swop state Dump current runtime state as YAML
swop inspect backend|frontend Introspect actual runtime state
swop resolve Diff current scan against stored manifests
swop refactor --out <dir> Extract modules into a new directory
swop doctor [--deep] Verify the local swop environment
swop hook install|uninstall|status Manage the git pre-commit hook

Reconciliation modes

Mode Behaviour
STRICT Fail on any drift
SOFT Log drift, continue (default)
OBSERVE Read-only, never modify
AUTO_HEAL Apply detected fixes automatically

Python API

Scan a project

from swop import scan_project, load_config

cfg = load_config("swop.yaml")
report = scan_project(cfg)

for det in report.detections:
    print(f"{det.kind:8} {det.name:20} ({det.confidence:.1f} via {det.via})")

Generate manifests

from swop import generate_manifests

manifests = generate_manifests(report, cfg)
for mf in manifests.files:
    print(mf.path)

Watch programmatically

from swop import WatchEngine, load_config

cfg = load_config("swop.yaml")
engine = WatchEngine(config=cfg, interval=0.5, debounce=0.3)

# Single-shot rebuild
from swop import rebuild_once
result = rebuild_once(cfg, incremental=True)
print(result.format())

Runtime graph

from swop import SwopRuntime

rt = SwopRuntime(mode="SOFT")
rt.add_model("Pressure", ["temp", "pressure_low", "pressure_high"])
rt.add_service("api", ["/pressure", "/status"])
rt.add_ui_binding("#sensor-temp", "temp")

drift = rt.run_sync()
print(rt.state_yaml())

Configuration

swop.yaml describes the project structure:

version: 1
project: my-service
source_roots: [src]
exclude: ["tests/*", "__pycache__/*"]
bounded_contexts:
  - name: billing
    source: src/billing
  - name: catalog
    source: src/catalog
    external: false
bus:
  type: rabbitmq
  url: amqp://localhost
read_models:
  engine: postgresql
  url: postgresql://localhost/mydb
state_dir: .swop
Key Description
source_roots Directories to scan (relative to project root)
bounded_contexts Named contexts with source paths
exclude Glob patterns to skip
bus Message-bus configuration
read_models Read-model store configuration
state_dir Local state / cache directory

Manifest Generation

For each bounded context swop generates three manifest files under .swop/manifests/<context>/:

  • commands.yml โ€” all detected commands with fields
  • queries.yml โ€” all detected queries with fields
  • events.yml โ€” all detected events with fields

Example output (billing/commands.yml):

version: 1
context: billing
commands:
  IssueInvoice:
    module: billing.ops
    fields:
      - name: customer_id
        type: int
        required: true
      - name: amount
        type: float
        required: true

These manifests are the single source of truth for downstream generators (proto, gRPC, service stubs).


Watch Mode

The watcher uses stdlib-only mtime polling โ€” no extra dependencies.

# Continuous watch
swop watch

# One-shot (CI friendly)
swop watch --once --no-incremental

The watcher automatically:

  • Skips the state directory (.swop/) so regenerated manifests do not re-trigger a rebuild.
  • Debounces bursts of changes into a single rebuild pass.
  • Tracks file creation, modification, and deletion.

Drift Detection & Resolution

Swop compares the expected state (from manifests) with the actual state (introspected from running backend/frontend) and reports drift:

swop diff
swop resolve

Drift categories:

  • schema โ€” field additions, removals, type changes
  • missing โ€” expected artifacts not found in runtime
  • unexpected โ€” runtime artifacts not in manifests

Use swop sync --mode AUTO_HEAL to apply fixes automatically.


Refactoring

Extract modules from a full-stack project into a clean output directory:

swop refactor --out ./refactored

The refactor pipeline clusters related code, builds a composed module graph, and generates new file layouts while preserving behaviour.


Development

Run tests

pytest

160 tests, all passing.

Project structure

swop/
โ”œโ”€โ”€ cli.py              # CLI entry point
โ”œโ”€โ”€ commands.py         # Command implementations
โ”œโ”€โ”€ config.py           # swop.yaml loader
โ”œโ”€โ”€ core.py             # SwopRuntime orchestrator
โ”œโ”€โ”€ cqrs/               # @command, @query, @event, @handler decorators
โ”œโ”€โ”€ graph.py            # ProjectGraph, DataModel, Service
โ”œโ”€โ”€ introspect/         # Backend & frontend state introspection
โ”œโ”€โ”€ manifests/          # YAML manifest generator
โ”œโ”€โ”€ markpact/           # Manifest parsing and sync engine
โ”œโ”€โ”€ proto/              # Protobuf generation & compilation
โ”œโ”€โ”€ reconcile.py        # Drift detection & resync
โ”œโ”€โ”€ refactor/           # Code clustering & module extraction
โ”œโ”€โ”€ resolve.py          # Schema-evolution resolution
โ”œโ”€โ”€ scan/               # AST scanner for CQRS artifacts
โ”œโ”€โ”€ services/           # Service stub generator
โ”œโ”€โ”€ sync.py             # Sync engine
โ”œโ”€โ”€ tools/              # Project init, doctor, git hooks
โ”œโ”€โ”€ versioning.py       # Graph versioning
โ””โ”€โ”€ watch/              # mtime-polling file watcher

License

Licensed under Apache-2.0.

Project details


Download files

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

Source Distribution

swop-0.2.8.tar.gz (118.4 kB view details)

Uploaded Source

Built Distribution

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

swop-0.2.8-py3-none-any.whl (115.9 kB view details)

Uploaded Python 3

File details

Details for the file swop-0.2.8.tar.gz.

File metadata

  • Download URL: swop-0.2.8.tar.gz
  • Upload date:
  • Size: 118.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for swop-0.2.8.tar.gz
Algorithm Hash digest
SHA256 f8b9b0041863ada64a268a1acfc31bb1e8924663d88ae9246a5ebd8e7e7f52d3
MD5 b8c135be75718fc6d836b303321db091
BLAKE2b-256 bb48795d34a21e0dfd3345a2600152f2e7728d83d60d82f41631c2fbeb34606f

See more details on using hashes here.

File details

Details for the file swop-0.2.8-py3-none-any.whl.

File metadata

  • Download URL: swop-0.2.8-py3-none-any.whl
  • Upload date:
  • Size: 115.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for swop-0.2.8-py3-none-any.whl
Algorithm Hash digest
SHA256 3082c3957b20f0a5a76b13a5bf00437215c5b2d5d5e9d88d61f5738e2dcba850
MD5 d7f863a0fffb8eb857c43528eb55a308
BLAKE2b-256 3070bc77fc954d5b546b64591bc2b6c7dfe8ef5aafb238b7a504421f276a5e78

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