Skip to content

Latest commit

 

History

History
181 lines (135 loc) · 5.1 KB

File metadata and controls

181 lines (135 loc) · 5.1 KB

printsocket

Python client for the PrintSocket cloud print API.

A lightweight agent runs on a machine, connects outbound to PrintSocket, and exposes that machine's printers (and scales) to a REST API. This library wraps API v1: devices, printers, scales, documents, print jobs, webhooks, and API keys, plus webhook signature verification for your receiver.

Zero dependencies; the standard library does all the work. Requires Python 3.10 or newer, and ships with inline type hints. Full API documentation lives at www.printsocket.com/docs.

Install

pip install printsocket

Quickstart

An sk_test_ key comes with a virtual device and printer that runs the full job lifecycle, so this works before any hardware is enrolled:

import os
import printsocket

ps = printsocket.PrintSocket(api_key=os.environ["PRINTSOCKET_API_KEY"])

printers = ps.printers.list({"state": "online"})

job = ps.jobs.create({
    "printer_id": printers["data"][0]["id"],
    "title": "Order #12345 label",
    "content": {"format": "pdf", "url": "https://example.com/label.pdf"},
    "metadata": {"order_id": "12345"},
})

print(job["id"], job["status"])  # job_...  queued

Responses are dicts with the exact snake_case fields the API reference documents, so the docs read straight across to the code.

Configuration

ps = printsocket.PrintSocket(
    api_key="sk_live_...",                        # required
    base_url="https://api.printsocket.com/v1",    # default
    timeout=30.0,                                 # seconds per attempt
    max_retries=2,                                # connection failures, 429s, and 5xx
)

Errors

Every API error raises a typed subclass of APIError carrying status, type, code, param, and request_id (quote the request id in support requests):

import printsocket

try:
    ps.jobs.cancel(job_id)
except printsocket.ConflictError as e:
    if e.code == "job_not_cancelable":
        ...  # already printing or finished
    else:
        raise

The classes are InvalidRequestError, AuthenticationError, PermissionDeniedError, NotFoundError, ConflictError, RateLimitError, BillingError, and ServerError, one per error.type the API returns. Requests that never got a response raise APIConnectionError.

Retries and idempotency

Connection failures, 429s, and 5xx responses are retried automatically (max_retries, default 2), honoring Retry-After. Every POST carries an Idempotency-Key header, generated when you do not pass one, and the key is identical across the client's own retry attempts, so a retried create cannot produce a duplicate job. To extend the guarantee across your own retries, pass a key derived from your record:

ps.jobs.create(params, idempotency_key="order-12345-label")

Pagination

list() returns one page (data, has_more, next_cursor). Each list resource also has iterate(), which follows cursors for you:

for job in ps.jobs.iterate({"status": "failed", "limit": 100}):
    print(job["id"], (job.get("error") or {}).get("message"))

Documents

Upload once, print many times:

with open("packing-slip.pdf", "rb") as f:
    doc = ps.documents.upload(f.read(), "application/pdf", expire_after_seconds=3600)

ps.jobs.create({
    "printer_id": "prn_...",
    "content": {"format": "pdf", "document_id": doc["id"]},
})

ps.documents.create_from_url({"source_url": ...}) has the API fetch the file server-side instead.

Webhooks

Subscribe with the client, verify deliveries with printsocket.webhook. Verification needs the raw request body; a decoded and re-encoded body will not match the signature.

import printsocket
from printsocket import webhook

endpoint = ps.webhooks.create({
    "url": "https://example.com/printsocket/webhook",
    "events": ["job.*", "printer.state_changed"],
})
# endpoint["secret"] is shown only this once; store it.

# In your receiver (Flask shown; any framework works the same way):
@app.post("/printsocket/webhook")
def receive():
    try:
        event = webhook.construct_event(
            request.get_data(),
            request.headers.get("PrintSocket-Signature", ""),
            os.environ["PRINTSOCKET_WEBHOOK_SECRET"],
        )
    except printsocket.WebhookVerificationError:
        return "", 400
    # Delivery is at-least-once: dedupe on event["id"] before acting.
    return "", 200

Enrolling devices

Generate a short-lived, single-use token server-side and hand it to the agent installer, so your API keys never touch a customer machine:

token = ps.enrollment_tokens.create({"name": "Front desk PC"})
# token["token"] is the secret; it expires in about an hour.

Scales

scale = ps.scales.get("scl_...")
reading = scale.get("reading")
if reading and reading["stable"]:
    print(reading["weight_grams"], "g at", reading["captured_at"])

Development

PYTHONPATH=src python -m unittest discover -s tests

The test suite uses only the standard library on purpose; it runs anywhere Python does, with no install step.

License

MIT