A bash-driven scaffolder for Python 3.12 service templates.
./pyskel <template> <name> copies one of seven templates and
substitutes the template name with yours. The result is a runnable
service skeleton you can pip install -e . and start immediately.
Pick the shape that matches your workload — each template's own README has detailed "when to use" / "when not to use" guidance.
| Template | Shape | Pick when |
|---|---|---|
simple |
Single process, single loop | Polling, schedulers, single-tenant daemons |
multi_t |
One process, N threads, no shared queue | Concurrent I/O fan-out (parallel pollers, scrapers) |
multi_t_q |
Producer/consumer with bounded queue, retry, durable replay | Pipelines that pull-then-process and survive restarts |
multi_p |
One parent process, N child processes, shared mp.Event |
CPU-bound parallelism, process isolation |
multi_p_h |
FastAPI + uvicorn prefork (HTTP/JSON) | Public APIs, browser-friendly debug, REST services |
multi_p_g |
grpcio + protobuf, multi-process via SO_REUSEPORT |
Internal RPC, high-RPS service-to-service, streaming |
multi_p_t |
Apache Thrift, multi-process via SO_REUSEPORT |
Legacy Thrift integrations (HBase gateway, Hive, etc.) |
aio |
Single-process asyncio loop with TaskGroup | Async IO services (HTTP clients, DB drivers, brokers) |
mq |
Asyncio + Redis Streams consumer (group, retry, DLQ) | Brokered tasks, event consumers, durable replay queues |
For new RPC services in 2026 with no Thrift constraint, prefer
multi_p_g over multi_p_t. For new HTTP services, prefer multi_p_h.
# Interactive (lists tpl/ and prompts for template + name)
./pyskel
# Non-interactive
./pyskel simple my_service
./pyskel multi_p_h my_api
# List available templates
./pyskel --listThe generated project goes into your current working directory.
Run pyskel from wherever you want the project to land — no need to
cd into the repo first.
cd ~/projects/
/path/to/pyskel/pyskel multi_p_h my_api
cd my_api
pip install -e .
./control.sh startGenerator host (machine running pyskel):
- bash 4+ — uses parameter expansion (
${var^},${var//x/y}) and globstar that bash 3.2 doesn't support. macOS ships bash 3.2; install the modern one withbrew install bash. - POSIX essentials:
cp,mv,mkdir,cat,printf,find. - No
gsed, norename, notput. The generator is plain-bash text substitution — single platform-neutral code path.
Generated projects:
- Python 3.12+
pip install -e .for runtime deps (Dynaconf, plus framework for network templates: FastAPI/uvicorn, grpcio, thrift)multi_p_gandmulti_p_tship pre-generated stubs so they install and run without codegen tools. To regenerate after editing IDL:multi_p_g→pip install -e '.[dev]'(addsgrpcio-tools)multi_p_t→brew install thrift/apt install thrift-compiler(the Thrift compiler is a system package, not a pip package)
The skeleton is consistent across templates:
my_service/
├── pyproject.toml PEP 621, Python 3.12+
├── settings.yaml Dynaconf config + stdlib logging dictConfig
├── control.sh start/stop/restart/status (pid file in logs/)
├── README.md copy of the template's README
└── my_service/
├── __init__.py
├── main.py entry point (python -m my_service.main)
├── config.py Dynaconf loader
└── core.py service class with run() / stop()
Network templates (multi_p_h, multi_p_g, multi_p_t) add a
handler.py. RPC templates add proto/ (IDL source) and <pkg>/pb/
(generated stubs). multi_t_q adds a tasks.py for the Task dataclass
and processor.
Conventions across all templates:
control.shlives at the project root (not inscripts/).kill -0 $pidfor process-existence checks (POSIX, novmmap//procbranching).- Stdlib
loggingdictConfig insettings.yaml. Nologuru. - Bounded shutdown — every template's
stophas a deadline + escalation. - Cross-process logging in multi-process templates uses
QueueHandler+QueueListenerso all workers write through the parent's handlers (single log file, no interleaving). SO_REUSEPORTfor the two RPC templates' multi-worker port sharing.