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.
- labelixa-0.1.0/.gitignore +54 -0
- labelixa-0.1.0/PKG-INFO +62 -0
- labelixa-0.1.0/README.md +46 -0
- labelixa-0.1.0/labelixa/__init__.py +29 -0
- labelixa-0.1.0/labelixa/client.py +134 -0
- labelixa-0.1.0/pyproject.toml +26 -0
|
@@ -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/
|
labelixa-0.1.0/PKG-INFO
ADDED
|
@@ -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`.
|
labelixa-0.1.0/README.md
ADDED
|
@@ -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"]
|