Rules-as-config restart decider for Python services. Don't restart your whole stack on every git push.
Project description
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
Project details
Release history Release notifications | RSS feed
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 smart_restart-0.1.0.tar.gz.
File metadata
- Download URL: smart_restart-0.1.0.tar.gz
- Upload date:
- Size: 37.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d925c5f856b46b4fd9255185c0a82e0fd800acc5fa78dea18da0f40ed161e94
|
|
| MD5 |
4edb82b7bb40adef2e837f86695f84ce
|
|
| BLAKE2b-256 |
516d6c6590230cb58730e3a783b08f7bd1dc86ea212ff2dcded5279ab5889b48
|
Provenance
The following attestation bundles were made for smart_restart-0.1.0.tar.gz:
Publisher:
publish.yml on zx22413/smart-restart
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
smart_restart-0.1.0.tar.gz -
Subject digest:
0d925c5f856b46b4fd9255185c0a82e0fd800acc5fa78dea18da0f40ed161e94 - Sigstore transparency entry: 1440425181
- Sigstore integration time:
-
Permalink:
zx22413/smart-restart@9ca61206891f4fc0b400bea2c8819bef9649415e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/zx22413
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9ca61206891f4fc0b400bea2c8819bef9649415e -
Trigger Event:
release
-
Statement type:
File details
Details for the file smart_restart-0.1.0-py3-none-any.whl.
File metadata
- Download URL: smart_restart-0.1.0-py3-none-any.whl
- Upload date:
- Size: 11.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb0207b342f831506dc569eb86779f2b7e0c48d8160b1ceb74ad0a1348a431fa
|
|
| MD5 |
67b3352ed864ea0b9188d775ac7a8991
|
|
| BLAKE2b-256 |
fba1919a3b516cd869ecd10c4f754fda6f7e8b1a1a9d33823c7125ba64386a88
|
Provenance
The following attestation bundles were made for smart_restart-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on zx22413/smart-restart
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
smart_restart-0.1.0-py3-none-any.whl -
Subject digest:
fb0207b342f831506dc569eb86779f2b7e0c48d8160b1ceb74ad0a1348a431fa - Sigstore transparency entry: 1440425267
- Sigstore integration time:
-
Permalink:
zx22413/smart-restart@9ca61206891f4fc0b400bea2c8819bef9649415e -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/zx22413
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9ca61206891f4fc0b400bea2c8819bef9649415e -
Trigger Event:
release
-
Statement type: