Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 33 additions & 8 deletions pyprusalink/types.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from enum import Enum
from typing import TypedDict
from typing import NotRequired, TypedDict

"""Types of the v1 API. Source: https://github.com/prusa3d/Prusa-Link-Web/blob/master/spec/openapi.yaml"""

Expand Down Expand Up @@ -27,15 +27,40 @@ class Capabilities(TypedDict):


class VersionInfo(TypedDict):
"""Version data."""
"""Version data from /api/version.

Field availability differs between PrusaLink variants and firmware versions:

Bundled PrusaLink (Prusa-Firmware-Buddy, all printers):
Always returned: api, server, nozzle_diameter, text, hostname, capabilities
Returned on v6.5.1+ only: firmware, printer
- v6.5.1 (2025-11-11): added on Core One L
- v6.5.3 (2026-03-24): propagated to Core One/MK4/MK3.9/MK3.5 family
- XL (6.4.x track) and MINI (6.4.0): not yet backported
- Source: https://github.com/prusa3d/Prusa-Firmware-Buddy/commit/64b7a21
Comment thread
heikkih marked this conversation as resolved.

Example response from MK4 firmware 6.4.0 (no firmware/printer fields):
{"api": "2.0.0", "server": "2.1.2", "nozzle_diameter": 0.4,
"text": "PrusaLink", "hostname": "prusa-mk4",
"capabilities": {"upload-by-put": True}}

Standalone PrusaLink (RPi-based installations):
May return version and sdk per the Prusa-Link-Web OpenAPI spec; these
are never returned by bundled firmware.

Use dict.get() for any field other than `api` to handle absence safely.
"""

api: str
version: str
printer: str
text: str
firmware: str
sdk: str | None
capabilities: Capabilities | None
text: NotRequired[str]
server: NotRequired[str]
hostname: NotRequired[str]
nozzle_diameter: NotRequired[float]
firmware: NotRequired[str]
printer: NotRequired[str]
version: NotRequired[str]
sdk: NotRequired[str]
capabilities: NotRequired[Capabilities]


class PrinterInfo(TypedDict):
Expand Down
36 changes: 31 additions & 5 deletions tests/test_prusalink.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,47 @@


async def test_get_version(pl, respx_mock):
"""Modern firmware (Buddy v6.5.1+) returns firmware and printer fields."""
respx_mock.get(f"{HOST}/api/version").mock(
return_value=httpx.Response(
200,
json={
"api": "2.0.0",
"version": "0.7.0",
"printer": "1.3.1",
"text": "PrusaLink 0.7.0",
"firmware": "3.10.1-4697",
"server": "2.1.2",
"nozzle_diameter": 0.40,
"text": "PrusaLink",
"hostname": "prusa-core-one",
"firmware": "6.5.3+12780",
"printer": "7.1.0",
"capabilities": {"upload-by-put": True},
},
)
)
result = await pl.get_version()
assert result["api"] == "2.0.0"
assert result["firmware"] == "3.10.1-4697"
assert result["firmware"] == "6.5.3+12780"
assert result["printer"] == "7.1.0"


async def test_get_version_legacy_firmware(pl, respx_mock):
"""Legacy firmware (Buddy <v6.5.1, XL, MINI) omits firmware and printer fields."""
respx_mock.get(f"{HOST}/api/version").mock(
return_value=httpx.Response(
200,
json={
"api": "2.0.0",
"server": "2.1.2",
"nozzle_diameter": 0.40,
"text": "PrusaLink",
"hostname": "prusa-mk39",
"capabilities": {"upload-by-put": True},
},
)
)
result = await pl.get_version()
assert result["api"] == "2.0.0"
assert "firmware" not in result
assert "printer" not in result


async def test_get_info(pl, respx_mock):
Expand Down