labelixa 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
labelixa/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """Labelixa Python SDK — thin client for the Labelixa REST API.
2
+
3
+ Render, validate and convert Zebra ZPL label code without a printer:
4
+
5
+ from labelixa import Client
6
+
7
+ c = Client() # anonymous: free, rate-limited
8
+ c = Client(api_key="lbx_...") # your quota, your account
9
+
10
+ png = c.render_png("^XA^FO50,50^A0N,40^FDHello^FS^XZ")
11
+ pdf = c.render_pdf(zpl, width_in=4, height_in=6)
12
+ report = c.validate(zpl) # structured lint diagnostics
13
+ epl = c.to_epl(zpl) # ZPL -> EPL2 translation
14
+
15
+ Design notes (deliberate):
16
+ - This is a THIN wrapper. Every method maps 1:1 to a documented REST
17
+ endpoint; no client-side magic, no hidden retries that would mask
18
+ quota signals. The API reference at https://labelixa.com/docs/api is
19
+ the source of truth.
20
+ - Errors carry the server's own message. Quota exhaustion raises
21
+ ``QuotaExceeded`` with ``retry_after`` seconds and, when the server
22
+ suggests one, an ``action`` hint ("upgrade" / "addon").
23
+ """
24
+ from __future__ import annotations
25
+
26
+ from .client import Client, LabelixaError, QuotaExceeded
27
+
28
+ __all__ = ["Client", "LabelixaError", "QuotaExceeded"]
29
+ __version__ = "0.1.0"
labelixa/client.py ADDED
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ import httpx
7
+
8
+ VARSAYILAN_TABAN = "https://api.labelixa.com"
9
+ ZAMAN_ASIMI = httpx.Timeout(30.0)
10
+
11
+
12
+ class LabelixaError(RuntimeError):
13
+ """API bir hata döndürdü. `status` HTTP kodu, mesaj sunucunun kendisi.
14
+
15
+ Mesajı yeniden yazmıyoruz: sunucu mesajları zaten eyleme dönük
16
+ ("Istek basina en fazla N etiket...") ve SDK'nın araya girmesi
17
+ bilgiyi ancak bozar.
18
+ """
19
+
20
+ def __init__(self, status: int, message: str) -> None:
21
+ super().__init__(f"HTTP {status}: {message}")
22
+ self.status = status
23
+ self.message = message
24
+
25
+
26
+ class QuotaExceeded(LabelixaError):
27
+ """Kota/hız sınırı. `retry_after` saniye; `action` sunucu önerisi.
28
+
29
+ Ayrı sınıf olması önemli: 429'u genel hatadan ayıramayan istemci ya
30
+ hiç yeniden denemez ya da hemen dener — ikisi de yanlış.
31
+ """
32
+
33
+ def __init__(self, status: int, message: str,
34
+ retry_after: int, action: str | None) -> None:
35
+ super().__init__(status, message)
36
+ self.retry_after = retry_after
37
+ self.action = action
38
+
39
+
40
+ def _hata(yanit: httpx.Response) -> LabelixaError:
41
+ metin = yanit.text[:500]
42
+ if yanit.status_code in (402, 429):
43
+ try:
44
+ bekle = int(yanit.headers.get("Retry-After", "60"))
45
+ except ValueError:
46
+ bekle = 60
47
+ return QuotaExceeded(yanit.status_code, metin, bekle,
48
+ yanit.headers.get("X-Quota-Action"))
49
+ return LabelixaError(yanit.status_code, metin)
50
+
51
+
52
+ class Client:
53
+ """Labelixa API istemcisi.
54
+
55
+ `transport` parametresi test içindir (httpx.ASGITransport ile ağ
56
+ olmadan gerçek uygulamaya karşı test edilir); normal kullanımda
57
+ verilmez.
58
+ """
59
+
60
+ def __init__(self, api_key: str | None = None,
61
+ base_url: str = VARSAYILAN_TABAN, *,
62
+ transport: httpx.BaseTransport | None = None) -> None:
63
+ basliklar = {"User-Agent": "labelixa-python/0.1.0"}
64
+ if api_key:
65
+ basliklar["X-API-Key"] = api_key
66
+ self._http = httpx.Client(base_url=base_url, headers=basliklar,
67
+ timeout=ZAMAN_ASIMI, transport=transport)
68
+
69
+ # ------------------------------------------------------------- render --
70
+ def render_png(self, zpl: str, *, dpmm: int = 8, width_in: float = 4,
71
+ height_in: float = 6, index: int = 0,
72
+ rotation: int = 0) -> bytes:
73
+ """Tek etiketi PNG olarak render eder."""
74
+ basliklar = {"Content-Type": "text/plain"}
75
+ if rotation:
76
+ basliklar["X-Rotation"] = str(rotation)
77
+ y = self._http.post(
78
+ f"/v1/printers/{dpmm}dpmm/labels/{width_in:g}x{height_in:g}/{index}",
79
+ content=zpl.encode(), headers=basliklar)
80
+ if y.status_code != 200:
81
+ raise _hata(y)
82
+ return y.content
83
+
84
+ def render_pdf(self, zpl: str, *, dpmm: int = 8, width_in: float = 4,
85
+ height_in: float = 6, index: int | None = None) -> bytes:
86
+ """PDF render eder; `index=None` akıştaki TÜM etiketleri içerir.
87
+
88
+ Dikkat: tüm-etiket PDF'i kota olarak etiket SAYISI kadar düşer
89
+ (tek sayfa 1 düşer) — sunucu kuralıdır, SDK yumuşatmaz.
90
+ """
91
+ son = "" if index is None else str(index)
92
+ y = self._http.post(
93
+ f"/v1/printers/{dpmm}dpmm/labels/{width_in:g}x{height_in:g}/{son}",
94
+ content=zpl.encode(),
95
+ headers={"Content-Type": "text/plain",
96
+ "Accept": "application/pdf"})
97
+ if y.status_code != 200:
98
+ raise _hata(y)
99
+ return y.content
100
+
101
+ # -------------------------------------------------------- diagnostics --
102
+ def validate(self, zpl: str, *, dpmm: int = 8, width_in: float = 4,
103
+ height_in: float = 6) -> dict[str, Any]:
104
+ """ZPL'i lint'ler; yapısal tanılama raporu döndürür."""
105
+ y = self._http.post(
106
+ "/v1/diagnostics",
107
+ params={"dpmm": dpmm, "width": width_in, "height": height_in},
108
+ content=zpl.encode(),
109
+ headers={"Content-Type": "text/plain"})
110
+ if y.status_code != 200:
111
+ raise _hata(y)
112
+ return json.loads(y.text)
113
+
114
+ # ------------------------------------------------------------ convert --
115
+ def to_epl(self, zpl: str, *, dpmm: int = 8, width_in: float = 4,
116
+ height_in: float = 6) -> str:
117
+ """ZPL → EPL2 çevirisi. Çevrilemeyen alanlar sunucu uyarısıyla düşer."""
118
+ y = self._http.post(
119
+ f"/v1/printers/{dpmm}dpmm/labels/{width_in:g}x{height_in:g}/",
120
+ content=zpl.encode(),
121
+ headers={"Content-Type": "text/plain",
122
+ "Accept": "application/epl"})
123
+ if y.status_code != 200:
124
+ raise _hata(y)
125
+ return y.text
126
+
127
+ def close(self) -> None:
128
+ self._http.close()
129
+
130
+ def __enter__(self) -> "Client":
131
+ return self
132
+
133
+ def __exit__(self, *a: object) -> None:
134
+ self.close()
@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.4
2
+ Name: labelixa
3
+ Version: 0.1.0
4
+ Summary: Render, validate and convert Zebra ZPL label code via the Labelixa API — no printer required.
5
+ Project-URL: Homepage, https://labelixa.com
6
+ Project-URL: Documentation, https://labelixa.com/docs/api
7
+ License-Expression: MIT
8
+ Keywords: barcode,epl,label,printing,thermal-printer,zebra,zpl
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Printing
13
+ Requires-Python: >=3.9
14
+ Requires-Dist: httpx>=0.24
15
+ Description-Content-Type: text/markdown
16
+
17
+ # labelixa
18
+
19
+ Render, validate and convert **Zebra ZPL** label code from Python — no
20
+ printer required. Thin client for the [Labelixa](https://labelixa.com) API.
21
+
22
+ ```bash
23
+ pip install labelixa
24
+ ```
25
+
26
+ ```python
27
+ from labelixa import Client
28
+
29
+ c = Client() # anonymous: free, rate-limited
30
+ # c = Client(api_key="lbx_...") # your quota — https://labelixa.com/panel
31
+
32
+ zpl = "^XA^FO50,50^A0N,40^FDHello^FS^BY3^FO50,120^BCN,100,Y^FD12345678^FS^XZ"
33
+
34
+ # See what the label looks like
35
+ with open("label.png", "wb") as f:
36
+ f.write(c.render_png(zpl, width_in=4, height_in=6))
37
+
38
+ # Lint before printing
39
+ report = c.validate(zpl)
40
+ for d in report["diagnostics"]:
41
+ print(d["severity"], d["message"])
42
+
43
+ # Multi-label PDF, ZPL -> EPL2 translation
44
+ pdf = c.render_pdf(zpl)
45
+ epl = c.to_epl(zpl)
46
+ ```
47
+
48
+ Quota errors are first-class:
49
+
50
+ ```python
51
+ from labelixa import QuotaExceeded
52
+
53
+ try:
54
+ c.render_png(zpl)
55
+ except QuotaExceeded as e:
56
+ print(f"retry in {e.retry_after}s (hint: {e.action})")
57
+ ```
58
+
59
+ This SDK is a deliberately thin 1:1 wrapper over the documented REST
60
+ API — the [API reference](https://labelixa.com/docs/api) is the source
61
+ of truth. AI assistants can also use Labelixa directly over MCP:
62
+ point your client at `https://api.labelixa.com/mcp`.
@@ -0,0 +1,5 @@
1
+ labelixa/__init__.py,sha256=XRO9vsarVdYU3f-dvnoRV3C50hDlBY415mSr5xPt05s,1185
2
+ labelixa/client.py,sha256=xrPtCV08OSgZA2Yg-aMx2ALXWPPF9fX_YclrkXxDV5E,5040
3
+ labelixa-0.1.0.dist-info/METADATA,sha256=Gg-xMGFpF75nsBwmONV2HCi6D9MdxMY4uhhVFXoysnk,1860
4
+ labelixa-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
+ labelixa-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any