SPOC
Build a framework in 30 lines
Say what kinds of things your app has:
import spoc
framework = spoc.Framework("models", "commands", "views")
model = framework.kind("models")
command = framework.kind("commands")
view = framework.kind("views")
Tag your code with them, anywhere in the project:
from framework import model
@model
class Post:
"""Registers as models:blog.post."""
from framework import command
@command
def publish(title: str = "Hello, SPOC") -> str:
"""Registers as commands:blog.publish."""
return f"published {title!r}"
from framework import view
@view
def posts_api() -> list[str]:
"""Registers as views:blog.posts_api."""
return []
[spoc.apps]
development = ["apps.blog"]
from pathlib import Path
from framework import framework
framework.start(Path(__file__).resolve().parent)
That's it — no registry to wire up, no list to keep in sync. Ask SPOC what it found:
spoc check
spoc list
spoc stubs
OK: /path/to/blog checks out clean
commands:blog.publish
models:blog.post
views:blog.posts_api
wrote framework.pyi (3 identifiers)
SPOC turns your application's declarations into one typed, inspectable registry.
check dry-boots your project and reports what's wrong before anything runs.
list reads the shelf. stubs writes real autocomplete for every name you just
typed — so framework.objects.models.blog.post completes in your editor before
you've written a single test. Add a fourth decorated function tomorrow; all three
commands see it with no edit to any of these files.
See it work without writing a file
pip install spoc
spoc init myproject --template starter
cd myproject && python main.py --help
usage: myproject [-h] {core.add,core.items} ...
positional arguments:
{core.add,core.items}
core.add Add an item to the store.
core.items List the items in the store.
options:
-h, --help show this help message and exit
Nobody wrote that command list. It was derived from what the generated app
registered — the same trick as above, scaffolded for you. uvx spoc init myproject
works with nothing installed at all.
What you get
- One name per component, always. Class names derive their own. Ask for a name
that isn't there and the error says which segment was wrong and what would have
matched — never a silent
None. - Autocomplete, with no code changes.
spoc stubswrites a type stub beside your entry point: names complete as you type, components come back as their real types, typos become editor errors.spoc projectionemits the same registry as JSON for tools in any language. - A lifecycle you can reason about. Modules start in dependency order and stop in reverse, sync or async.
- Problems found before runtime.
spoc checkdry-boots and reports config errors, cycles, collisions, and sync/async mismatches.spoc listandspoc explainread the registry from your terminal. - Tests in the box.
spoc.testinggives isolated framework scopes and an app-tree builder, arriving as ready-made pytest fixtures. - Zero dependencies.
dependencies = [], enforced. Optional data-format codecs live behind extras (pip install "spoc[full]").
SPOC never runs your components. FastAPI still serves your HTTP, Typer still parses your argv, Celery still runs your jobs. SPOC only answers what does this app contain, and under what name — how architecture, names, and lifecycle work underneath is in the docs.
Should you use it?
Yes, if…
- one codebase feeds several surfaces — HTTP and CLI and workers — and each re-discovers the same components its own way.
- you are shipping a framework other people write apps against.
- things must start in dependency order and stop in reverse.
- a mistyped component name should be an editor error, not a
Noneat 3am.
No, if…
- it's one app, one surface, a handful of modules. Imports are cheaper. SPOC pays off above a complexity threshold, not below it.
- you are already on Django. Its app registry is a structural model.
- you want something that runs your components. SPOC only names and orders them.
Weighing it against imports, entry points, pluggy, or a DI container specifically? Why not just…? has the one-line answer for each.
Install
Python 3.12+
pip install spoc
The generated project imports spoc at runtime, so install it where you run the
project from — installation guide.
Documentation
Read the docs — tutorials, how-to guides, and the full API reference.
A good path in: your first project → names & the registry → build a framework.
Stability
SPOC is 1.0. A public name changes incompatibly only in a major release, and only
after a completed deprecation cycle — so spoc>=1.0,<2 is the pin that matters. Every
name, command, and extra carries a tier (public, provisional, internal), and both
the tiers and every change to them are checked on each CI run.
Stability & Versioning — the tiers, the deprecation lifecycle, and the criteria 1.0 was cut against.
Links
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 spoc-0.9.0.tar.gz.
File metadata
- Download URL: spoc-0.9.0.tar.gz
- Upload date:
- Size: 275.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f24ac1e513182c3c77aaef6adc096658b4903e344bf56306c8e9ad8c2ec4dbd1
|
|
| MD5 |
4582321b9b90c76f37cc7aa571da1f72
|
|
| BLAKE2b-256 |
b01519741fd56732a4e0a6700051272d98f5ec17a81eeeb9f76c3d212ff747cf
|
File details
Details for the file spoc-0.9.0-py3-none-any.whl.
File metadata
- Download URL: spoc-0.9.0-py3-none-any.whl
- Upload date:
- Size: 157.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fed26f53a78b6a634803074a75673007d58d398c99a86fa2e1c03dc531f3b1a0
|
|
| MD5 |
dd02a2a70e54e32ded0a4cfe2748305e
|
|
| BLAKE2b-256 |
978515147d96fb35cf12562382c93f49cf0203b23719e0f0d3362b7288719ce9
|