smart-restart
Rules-as-config restart decider for Python services. Don't restart your whole stack on every git push.
The problem
You ship a one-line bug fix to one service in your monorepo. Your deploy script restarts everything — bot, API, worker, scheduler — and you eat 30 seconds of cold starts and dropped connections, every time.
Most CI/CD scripts pick between two extremes:
- Restart everything always. Safe, but wasteful and slow.
- Hand-maintained "if file in dir then restart X." Fast, but rots silently when imports cross boundaries: someone refactors
utils/db.pyand forgets thatworker/jobs.pyimports it, the worker keeps the old code, you get a Heisenbug at 3 a.m.
smart-restart is the middle ground: a small decider that takes the list of changed files and a YAML rule set, then prints which actions are needed. Optionally, it walks the AST-derived import graph in reverse so editing a leaf utility correctly triggers restarts for every service that imports it.
It does not run the actions. Your deploy script does. smart-restart just decides.
Quick start
pip install smart-restart # coming with v0.1.0
Write a restart-rules.yaml:
actions:
pip_install:
order: 10 # lower = runs earlier
restart_api:
order: 20
rules:
- pattern: "requirements.txt"
actions: [pip_install, restart_api]
- pattern: "src/**"
actions: [restart_api]
- pattern: "**/*.md"
actions: [] # explicitly do nothing
fallback: [restart_api] # for files that match no rule
Pipe a diff into it:
$ git diff --name-only HEAD~1 HEAD | smart-restart
pip_install
restart_api
That's the output your deploy script consumes. Wire it into bash, Ansible, GitHub Actions, anything that takes stdout.
Import-chain expansion (the interesting bit)
The base decider only knows about files you literally changed. That misses the case where a leaf module changes and dependents don't.
Pass --root <project> and smart-restart will parse every .py file under <project>, build a static import graph, and expand the changed file list to include every transitive importer:
src/
utils/db.py # you changed this
api/handlers.py # imports utils.db
worker/jobs.py # imports utils.db
$ echo "src/utils/db.py" | smart-restart --root src --show-expanded
INFO: import-chain expansion added 2 file(s):
+ smart_restart/api/handlers.py
+ smart_restart/worker/jobs.py
restart_api
restart_worker
If restart-rules.yaml only matches src/api/** to restart_api and src/worker/** to restart_worker, the expansion ensures both fire even though only db.py was in the diff.
--root and src-layout
If your project uses src-layout (code under src/your_pkg/), pass --root src, not --root .. The graph indexes files by their dotted module name; the dotted name has to match what your imports actually say (from your_pkg.foo resolves to <root>/your_pkg/foo.py).
| Layout | Use |
|---|---|
src/your_pkg/... (src-layout) |
--root src |
your_pkg/... (flat / package at root) |
--root . |
Multiple roots (e.g. services/api/, services/worker/) |
run once per root, merge actions yourself |
Why a separate decider
- One source of truth. Change
restart-rules.yaml, every script that consumes the decider stays in sync. - Composable. Pipe it into bash, Ansible, GitHub Actions, anything that takes stdout. No framework lock-in.
- Diffable. Restart rules live in version control, not buried in a shell script.
- Testable. Pure function, no side effects, hermetic.
Configuration reference
actions: # required: action name -> {order: int}
<name>:
order: <int> # lower = runs earlier; default 100
rules: # required: list, evaluated top-down, first match wins
- pattern: <glob> # ** matches across directories, * within one segment
actions: [<name>...] # actions to fire (use [] to explicitly skip)
fallback: # what to do for files that match no rule
none # default: contribute no actions
| all # fire every declared action
| [<name>...] # fire just these
First-match-wins semantics: each file is matched against rules top-to-bottom, only the first hit contributes. This lets you put a narrow **/*.md → [] rule above a broad src/** → [restart_api] rule.
Status
🚧 Alpha — extracted from a closed-source dogfood project (~2 years of daily use).
Done in v0.1:
- Generic rule engine with first-match-wins semantics
- YAML-declared actions (no hardcoded service names)
- Import-chain reverse closure (AST-based)
- Glob patterns with
**recursion - Pure decider — no execution, no side effects
- 37 unit tests, fixture-driven
Deferred to v0.2+:
- LLM fallback for unmatched files (the upstream project has it; needs to be re-cast as a plugin to keep v0.1 dependency-free)
- Multi-root graph merge in one invocation
- Auto-detection of src-layout from
pyproject.toml - Star-import edge tracking at the symbol level
License
Metadata
Release files for smart-restart 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| smart_restart-0.1.0.tar.gz | 37.6 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| smart_restart-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 49.0 kB
Release files / smart_restart-0.1.0.tar.gz
| Download URL | smart_restart-0.1.0.tar.gz |
|---|---|
| Size | 37.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0d925c5f856b46b4fd9255185c0a82e0fd800acc5fa78dea18da0f40ed161e94
|
|
BLAKE2b-256 checksum How to use checksums |
516d6c6590230cb58730e3a783b08f7bd1dc86ea212ff2dcded5279ab5889b48
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 5, 2026.
Transparency logRelease files / smart_restart-0.1.0-py3-none-any.whl
| Download URL | smart_restart-0.1.0-py3-none-any.whl |
|---|---|
| Size | 11.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fb0207b342f831506dc569eb86779f2b7e0c48d8160b1ceb74ad0a1348a431fa
|
|
BLAKE2b-256 checksum How to use checksums |
fba1919a3b516cd869ecd10c4f754fda6f7e8b1a1a9d33823c7125ba64386a88
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 5, 2026.
Transparency logSecurity for smart-restart 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for smart_restart-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 5, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.