labelixa 0.1.0__tar.gz

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.
@@ -0,0 +1,54 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .venv/
6
+ venv/
7
+ env/
8
+
9
+ # Veritabanı ve yerel durum
10
+ *.db
11
+ *.sqlite
12
+ *.sqlite3
13
+
14
+ # Ortam değişkenleri — GİZLİ, asla commit edilmez
15
+ .env
16
+ .env.local
17
+ .env.*.local
18
+
19
+ # Test ve geliştirme çıktıları
20
+ .pytest_cache/
21
+ .coverage
22
+ htmlcov/
23
+ cikti-*.pdf
24
+ onizleme-*.png
25
+ test-faz*.png
26
+ test-logo.png
27
+
28
+ # Editör / IDE
29
+ .vscode/
30
+ .idea/
31
+ *.swp
32
+
33
+ # İşletim sistemi
34
+ .DS_Store
35
+ Thumbs.db
36
+
37
+ # Railway / dağıtım
38
+ .railway/
39
+
40
+ # Kaldırılacak eski git dizini (izin sorunu nedeniyle silinemedi)
41
+ .git-silinecek/
42
+ tests/fixtures/render/_fark/
43
+ app/depo/
44
+ # Testler ve yerel calisma depoyu KOK dizinde acar (`LABELIXA_DEPO_YOLU`
45
+ # varsayilani `./depo`). Bu uretilmis is ciktisidir, kaynak degil —
46
+ # 2026-08-02'de yanlislikla 180 dosya depoya girdi ve temizlendi.
47
+ /depo/
48
+
49
+ # Go agent derleme ciktilari. `go build` varsayilan olarak dizin adiyla
50
+ # ayni ismi (agent/agent) uretir; 8 MB'lik ikili kaynak degildir ve
51
+ # 2026-08-09'da commit'e girmesine ramak kaldi.
52
+ /agent/agent
53
+ /agent/agent.exe
54
+ /agent/dist/
@@ -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,46 @@
1
+ # labelixa
2
+
3
+ Render, validate and convert **Zebra ZPL** label code from Python — no
4
+ printer required. Thin client for the [Labelixa](https://labelixa.com) API.
5
+
6
+ ```bash
7
+ pip install labelixa
8
+ ```
9
+
10
+ ```python
11
+ from labelixa import Client
12
+
13
+ c = Client() # anonymous: free, rate-limited
14
+ # c = Client(api_key="lbx_...") # your quota — https://labelixa.com/panel
15
+
16
+ zpl = "^XA^FO50,50^A0N,40^FDHello^FS^BY3^FO50,120^BCN,100,Y^FD12345678^FS^XZ"
17
+
18
+ # See what the label looks like
19
+ with open("label.png", "wb") as f:
20
+ f.write(c.render_png(zpl, width_in=4, height_in=6))
21
+
22
+ # Lint before printing
23
+ report = c.validate(zpl)
24
+ for d in report["diagnostics"]:
25
+ print(d["severity"], d["message"])
26
+
27
+ # Multi-label PDF, ZPL -> EPL2 translation
28
+ pdf = c.render_pdf(zpl)
29
+ epl = c.to_epl(zpl)
30
+ ```
31
+
32
+ Quota errors are first-class:
33
+
34
+ ```python
35
+ from labelixa import QuotaExceeded
36
+
37
+ try:
38
+ c.render_png(zpl)
39
+ except QuotaExceeded as e:
40
+ print(f"retry in {e.retry_after}s (hint: {e.action})")
41
+ ```
42
+
43
+ This SDK is a deliberately thin 1:1 wrapper over the documented REST
44
+ API — the [API reference](https://labelixa.com/docs/api) is the source
45
+ of truth. AI assistants can also use Labelixa directly over MCP:
46
+ point your client at `https://api.labelixa.com/mcp`.
@@ -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"
@@ -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,26 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "labelixa"
7
+ version = "0.1.0"
8
+ description = "Render, validate and convert Zebra ZPL label code via the Labelixa API — no printer required."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ dependencies = ["httpx>=0.24"]
13
+ keywords = ["zpl", "zebra", "label", "barcode", "printing", "epl", "thermal-printer"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Topic :: Printing",
18
+ "Programming Language :: Python :: 3",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://labelixa.com"
23
+ Documentation = "https://labelixa.com/docs/api"
24
+
25
+ [tool.hatch.build.targets.wheel]
26
+ packages = ["labelixa"]