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: $1.9500 (13 commits)
  • ๐Ÿ‘ค Human dev: ~$458 (4.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.7.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.7-py3-none-any.whl (115.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: swop-0.2.7.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.7.tar.gz
Algorithm Hash digest
SHA256 f14c654de8e32ca2a77d93a2b92cf932ad69affcb714f6f67769a32c2a4c4753
MD5 7e0bcf41d6f59e328658f4a054a82a11
BLAKE2b-256 a879fa17c0494a463432d5fdcd5d1e3cc66117b69b081f32de54271cc2570d77

See more details on using hashes here.

File details

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

File metadata

  • Download URL: swop-0.2.7-py3-none-any.whl
  • Upload date:
  • Size: 115.8 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.7-py3-none-any.whl
Algorithm Hash digest
SHA256 f17505fc13ec2225a50cf6e5a1b6a750873819f20ea46789ecea665df92aa6a9
MD5 7522efa217397523e0b4c90a3d0a54d2
BLAKE2b-256 995874d34663c0d1f6aead0e21533560a069ef819ab1bae199df6aef3bf9cfd0

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