pm2-rpc
Pure-Python client for PM2's daemon RPC socket.
[!CAUTION] Experimental (v0.2.0) — Early-stage personal project. API may change without notice.
Talks directly to ~/.pm2/rpc.sock using PM2's pm2-axon / pm2-axon-rpc
wire stack (AMP framing → amp-message arg packing → axon-rpc body). The
runtime makes zero pm2 CLI subprocess calls — every operation is one
round-trip on the Unix socket. Returned values are plain dicts in PM2's
own shape, no extra abstraction.
Install
pip install pm2-rpc # core (only stdlib)
pip install 'pm2-rpc[yaml]' # add YAML ecosystem-config support
Requires Python ≥ 3.11 and a running PM2 daemon on the same host.
Quick start
import os
import pm2_rpc as pm2
# List
for p in pm2.list():
env = p["pm2_env"]
print(env["pm_id"], env["name"], env["status"])
# Describe (raises pm2.NotFound)
proc = pm2.describe("worker")
print(proc["pm2_env"]["restart_time"])
# Start a fresh process — no ecosystem file required
pm2.start("scripts/worker.py", name="worker", env={"DEBUG": "1"})
# Lifecycle knobs for apps with awkward shutdown / file-watch needs:
pm2.start("dev_server.py", name="api", kill_timeout=10000) # 10s window
pm2.start("notebook.py", name="nb", kill_signal="SIGINT") # jupyter-style
pm2.start("worker.py", name="w", watch=["src", "config.yaml"]) # PM2-managed reload
# Or from an ecosystem.config.{json,yaml}
pm2.start_ecosystem("ecosystem.config.json", only="worker")
# Flip an env var without re-reading the ecosystem file (avoids the
# `pm2 restart NAME --update-env` foot-gun where passing ecosystem.config.js
# silently overrides --update-env)
pm2.restart("worker", env={**os.environ, "APP_MODE": "suite"})
# Logs (tails the file PM2 writes on disk, no streaming)
print(pm2.logs("worker", lines=50))
print(pm2.error_logs("worker", lines=20))
# Teardown
pm2.stop("worker") # keeps the entry, status='stopped'
pm2.delete("worker") # removes it entirely
API
list() -> list[dict] |
All registered processes |
exists(name) -> bool |
Cheap existence probe |
describe(target) -> dict |
One process; raises NotFound |
start(script, **opts) -> dict |
Launch (fork mode); see kwargs below |
start_ecosystem(config, *, only=, cwd=) -> list[dict] |
.json/.yaml; .js raises UnsupportedConfigError |
restart(target, *, env=None) -> dict |
env merges server-side via Object.assign |
stop(target) -> dict |
Graceful stop, keeps entry |
delete(target) -> None |
Unregister |
env(target) -> dict[str, str] |
The runtime env the process sees |
logs(target, lines=15) -> str |
Last N lines of stdout |
error_logs(target, lines=15) -> str |
Last N lines of stderr |
pm2_rpc.start() kwargs use PM2's vocabulary: name, interpreter, cwd,
args, env, autorestart, out_file, error_file, merge_logs,
kill_timeout, kill_signal, watch. pm2_rpc.restart() additionally
accepts kill_timeout to bump the graceful-shutdown window for the next stop.
Low-level access if you need it:
from pm2_rpc import rpc_call, PM2Error
rpc_call("getMonitorData", {}) # raw axon-rpc round-trip
What's not supported (v0.2.0)
- Cluster mode (
exec_mode: "cluster",instances > 1) — fork-mode only. .js/.cjs/.mjsecosystem configs (require node to evaluatemodule.exports). Convert to.json/.yaml, usepm2_rpc.start()with app fields, or shell out topm2 start ecosystem.config.js --only NAME.- NVM-pinned interpreters (
exec_interpreter: "node@18.0.0"). filter_env, source-map auto-detection.$PATHlookup of script names — pass an absolute path (useshutil.which()yourself if you really want a PATH binary).
Development
uv sync --group dev
uv run pytest tests/ # 64 tests, ~6s, requires a live PM2 daemon
uv run ruff check # lint
uv run ruff format # format (omit --check to apply)
uv run mypy # typecheck pm2_rpc/
CI runs lint + format + typecheck + the full 64-test suite + build on every PR — PM2 is installed from npm on the runner.
Fixture processes are named pm2rpc-test-* so they're easy to identify
if a test crashes and leaks one.
License
MIT — see LICENSE.
Metadata
Release files for pm2-rpc 0.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pm2_rpc-0.2.3.tar.gz | 11.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pm2_rpc-0.2.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 25.5 kB
Release files / pm2_rpc-0.2.3.tar.gz
| Download URL | pm2_rpc-0.2.3.tar.gz |
|---|---|
| Size | 11.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
880fd09a02c84fae386f27d2c081cf5c050c739f0f956bba91eb541870500165
|
|
BLAKE2b-256 checksum How to use checksums |
ca5c6c5105a0856f45c6e4b2096ec789aec567809223ec0ffb56f0b68ab2ab3a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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}
|
Release files / pm2_rpc-0.2.3-py3-none-any.whl
| Download URL | pm2_rpc-0.2.3-py3-none-any.whl |
|---|---|
| Size | 13.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
945f8d30ad92d98fe270c8090849f0b43d1f048c56e85550a416ec05ccc4904b
|
|
BLAKE2b-256 checksum How to use checksums |
699f4999e34ea2f4a23e7b2f8a02a4979797f13be5f49993e018eaefa5a63590
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","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}
|