Async-first, fully typed Python SDK for the Moonraker API (Klipper 3D printer firmware)
Project description
pymoonraker
Async-first, fully typed Python SDK for the Moonraker API — the networked interface for Klipper 3D printer firmware.
Features
- Async-first — built on
asynciowithaiohttp(WebSocket) andhttpx(HTTP) - Sync wrapper — use
SyncMoonrakerClientwhen you don't need async - Fully typed — Pydantic v2 models for all printer objects, server responses, and events
- JSON-RPC over WebSocket — persistent connection with automatic request/response correlation
- Auto-generated bindings — endpoint methods generated from a YAML schema, always in sync with the API
- Event system — subscribe to 25+ Moonraker notification types with typed callbacks
- Auto-reconnect — exponential backoff reconnection with automatic re-subscription
- File operations — upload/download G-code files via HTTP transport
- Authentication — API key, JWT bearer tokens, and oneshot token support
py.typed— PEP 561 compatible, works with mypy and pyright- AI-agent friendly — AGENTS.md gives AI coding agents (Cursor, Copilot, etc.) a concise usage guide so they can use the SDK correctly
Installation
pip install pymoonraker
Quick Start
Async
import asyncio
from pymoonraker import MoonrakerClient
from pymoonraker.events import EventType
async def main():
async with MoonrakerClient("192.168.1.100") as client:
# Query server info
info = await client.server_info()
print(f"Klippy state: {info.klippy_state}")
# Query printer objects with full typing
result = await client.query_objects({
"toolhead": ["position", "homed_axes"],
"heater_bed": None, # all fields
})
print(result)
# Subscribe to real-time status updates
def on_status(data, timestamp):
print(f"Status update: {data}")
client.on(EventType.STATUS_UPDATE, on_status)
await client.subscribe_objects({"toolhead": None, "print_stats": None})
# Execute G-code
await client.gcode("G28") # Home all axes
# Start a print
await client.print_start("my_model.gcode")
asyncio.run(main())
Sync
from pymoonraker import SyncMoonrakerClient
with SyncMoonrakerClient("192.168.1.100") as client:
info = client.server_info()
print(f"Klippy state: {info.klippy_state}")
client.gcode("G28")
Auto-Generated API Namespaces
Every Moonraker endpoint is available as a typed method on namespace objects exposed as client attributes:
async with MoonrakerClient("192.168.1.100") as client:
# Access namespaces via client.printer, client.files, etc.
info = await client.printer.info()
help_text = await client.printer.gcode_help()
file_list = await client.files.list(root="gcodes")
metadata = await client.files.metadata("my_model.gcode")
You can also construct namespace instances manually if needed: from pymoonraker.api import PrinterNamespace then PrinterNamespace(client).
Event Handling
from pymoonraker.events import EventType
# Register handlers for any Moonraker notification
client.on(EventType.KLIPPY_READY, lambda: print("Klipper is ready!"))
client.on(EventType.KLIPPY_SHUTDOWN, lambda: print("Klipper shutdown!"))
client.on(EventType.GCODE_RESPONSE, lambda msg: print(f"G-code: {msg}"))
client.on(EventType.FILELIST_CHANGED, lambda info: print(f"Files changed: {info}"))
# One-shot handlers
client.once(EventType.KLIPPY_READY, lambda: print("First ready event"))
# Unsubscribe
unsub = client.on(EventType.STATUS_UPDATE, my_handler)
unsub() # remove the handler
File Operations
# Upload a G-code file
with open("model.gcode", "rb") as f:
await client.upload_file("model.gcode", f.read())
# Download a file
content = await client.download_file("gcodes", "model.gcode")
Authentication
# API key (simplest)
client = MoonrakerClient("192.168.1.100", api_key="your-api-key")
# JWT authentication is handled by the AuthManager internally
Logging (Structured Context)
The SDK emits log records with structured context fields in extra, including:
hostportrequest_id(for JSON-RPC request lifecycle logs)
extra fields are attached to log records, but they are not shown unless your application formatter includes them. As a library, pymoonraker does not configure global logging handlers/formatters for you.
If you want these fields in plain-text logs, configure a filter that provides defaults:
import logging
class MoonrakerContextDefaultsFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> bool:
# Keep values provided by logger(..., extra=...) and only fill missing fields.
if not hasattr(record, "host"):
record.host = "-"
if not hasattr(record, "port"):
record.port = "-"
if not hasattr(record, "request_id"):
record.request_id = "-"
return True
handler = logging.StreamHandler()
handler.addFilter(MoonrakerContextDefaultsFilter())
handler.setFormatter(
logging.Formatter(
"%(asctime)s %(levelname)s %(name)s "
"host=%(host)s port=%(port)s request_id=%(request_id)s %(message)s"
)
)
root = logging.getLogger()
root.setLevel(logging.INFO)
root.handlers.clear()
root.addHandler(handler)
If you already use JSON logging or another structured logging pipeline, keep your existing setup and map these record attributes there.
Architecture
pymoonraker/
├── client.py # High-level async MoonrakerClient
├── sync_client.py # Synchronous wrapper
├── exceptions.py # Exception hierarchy
├── transport/
│ ├── base.py # Abstract transport interface
│ ├── websocket.py # aiohttp WebSocket transport
│ └── http.py # httpx HTTP transport
├── rpc/
│ ├── jsonrpc.py # JSON-RPC 2.0 multiplexer
│ └── types.py # RPC request/response types
├── models/
│ ├── printer.py # Klipper printer object models
│ ├── server.py # Server info models
│ ├── job.py # Print job / history models
│ ├── files.py # File management models
│ └── common.py # Shared base models
├── events/
│ ├── dispatcher.py # Event callback registry
│ └── types.py # EventType enum (25+ events)
├── auth/
│ └── auth.py # API key, JWT, oneshot auth
└── api/
├── _generated.py # Auto-generated from YAML schema
└── __init__.py # Namespace re-exports
schema/
└── moonraker_api.yaml # Single source of truth for API bindings
scripts/
└── generate_bindings.py # YAML → Python code generator
Using with AI agents
pymoonraker is designed so that AI coding assistants (Cursor, GitHub Copilot, Claude Code, etc.) can use it reliably:
- AGENTS.md — A short guide written for agents: entry points, namespaces, events, lifecycle, and common patterns. If you are building an app with an AI agent, you can:
- Add AGENTS.md to your repo (e.g. as a copied or linked reference), or
- Point the agent at the pymoonraker repo/source and say e.g. “Use the pymoonraker SDK per AGENTS.md”.
- Strict typing — All public APIs are fully typed (Pydantic models, type hints), so agents get accurate completions and fewer mistakes.
- Stable surface — Public API is
MoonrakerClient,SyncMoonrakerClient, namespaces underpymoonraker.api,EventTypeand events; agents can rely on these names.
YAML Schema & Code Generation
API bindings are generated from schema/moonraker_api.yaml. To regenerate after editing the schema:
python scripts/generate_bindings.py
The schema defines every namespace, method, parameter, and return type. The generator produces fully typed async methods with docstrings.
Development
# Clone and set up
git clone https://github.com/thewillft/pymoonraker.git
cd pymoonraker
python -m venv .venv
.venv/Scripts/activate # Windows
# source .venv/bin/activate # macOS/Linux
# Install in dev mode
pip install -r requirements-dev.txt
pip install -e ".[dev]"
# Run tests
pytest
# Run tests with coverage
pytest --cov=pymoonraker --cov-report=term-missing
# Lint and format
ruff check src/ tests/
ruff format src/ tests/
# Type check
mypy src/pymoonraker/
# Regenerate API bindings
python scripts/generate_bindings.py
Supported Moonraker API Coverage
| Namespace | Methods | Description |
|---|---|---|
printer |
12 | Klipper control, G-code, object queries, print management |
server |
6 | Server info, config, temperature/gcode stores |
files |
8 | File listing, metadata, directory management |
job_queue |
5 | Job queue management |
history |
4 | Print history and statistics |
machine |
7 | System info, reboot, service management |
update_manager |
2 | Software updates |
power |
3 | Power device control |
access |
7 | Authentication and API keys |
database |
4 | Key-value storage |
announcements |
2 | Announcement system |
webcams |
2 | Webcam configuration |
Contributing
See CONTRIBUTING.md for development guidelines.
License
MIT — see 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 pymoonraker-0.1.0.tar.gz.
File metadata
- Download URL: pymoonraker-0.1.0.tar.gz
- Upload date:
- Size: 32.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67478b4d29f10cd8eabff40c9c5b8fece93622be41c75e2c30dba6c268d86c07
|
|
| MD5 |
5701a36e05113dd4486bdd9e3cb4b7f0
|
|
| BLAKE2b-256 |
eb3252bda86062c19019d00e42d732fac1cadc32a7a689b52173d1c2a0b2a3e3
|
Provenance
The following attestation bundles were made for pymoonraker-0.1.0.tar.gz:
Publisher:
publish.yml on thewillft/pymoonraker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymoonraker-0.1.0.tar.gz -
Subject digest:
67478b4d29f10cd8eabff40c9c5b8fece93622be41c75e2c30dba6c268d86c07 - Sigstore transparency entry: 1020018754
- Sigstore integration time:
-
Permalink:
thewillft/pymoonraker@77e569d61316523952058d8357b9846fb8efc862 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/thewillft
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@77e569d61316523952058d8357b9846fb8efc862 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pymoonraker-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pymoonraker-0.1.0-py3-none-any.whl
- Upload date:
- Size: 43.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c7a0b26af370447fde4e4681dbbb659d6488b5186510cbeca0d04d32b86854e
|
|
| MD5 |
3b93887182d95aec388078a3038d1d1b
|
|
| BLAKE2b-256 |
3291fbe47961935098a43fe1a452ac0c484195a237b6e8e8a5c2764ee6078393
|
Provenance
The following attestation bundles were made for pymoonraker-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on thewillft/pymoonraker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pymoonraker-0.1.0-py3-none-any.whl -
Subject digest:
1c7a0b26af370447fde4e4681dbbb659d6488b5186510cbeca0d04d32b86854e - Sigstore transparency entry: 1020018823
- Sigstore integration time:
-
Permalink:
thewillft/pymoonraker@77e569d61316523952058d8357b9846fb8efc862 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/thewillft
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@77e569d61316523952058d8357b9846fb8efc862 -
Trigger Event:
release
-
Statement type: