Metadata-Version: 2.5
Name: printsocket
Version: 0.1.0
Summary: Python client for the PrintSocket cloud print API
Project-URL: Homepage, https://www.printsocket.com/docs
Project-URL: Repository, https://github.com/print-socket/printsocket-python
Project-URL: Issues, https://github.com/print-socket/printsocket-python/issues
Author: Mark Sanborn
License-Expression: MIT
License-File: LICENSE
Keywords: api,cloud print,escpos,print,printer,printing,printsocket,sdk,zpl
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Printing
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown

# printsocket

Python client for the [PrintSocket](https://www.printsocket.com) 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](https://www.printsocket.com/docs).

## Install

```sh
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:

```python
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

```python
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):

```python
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:

```python
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:

```python
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:

```python
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.

```python
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:

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

## Scales

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

## Development

```sh
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
