evren-sdk 0.3.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,78 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ htmlcov/
9
+ .coverage
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .pytest_cache/
13
+
14
+ # Virtual environments
15
+ .venv/
16
+ venv/
17
+ env/
18
+
19
+ # Node.js
20
+ node_modules/
21
+ *.tsbuildinfo
22
+ .vite/
23
+ coverage/
24
+
25
+ # Environment
26
+ .env
27
+ .env.*
28
+ !.env.example
29
+
30
+ # IDE
31
+ .idea/
32
+ .vscode/
33
+ *.swp
34
+ *.swo
35
+
36
+ # Cursor / AI
37
+ .cursor/
38
+ AGENTS.md
39
+ agent-transcripts/
40
+ mcps/
41
+ prompts/
42
+
43
+ # OS
44
+ .DS_Store
45
+ Thumbs.db
46
+ Desktop.ini
47
+
48
+ # Terminals (cursor IDE)
49
+ terminals/
50
+
51
+ # SSL / TLS secrets
52
+ ssl/
53
+ infra_scripts/**/certs/*.key
54
+ infra_scripts/**/certs/*.pem
55
+ infra_scripts/elasticsearch/certs/
56
+
57
+ # Build artifacts
58
+ src/frontend/dist/
59
+
60
+ # TypeScript compiled .js files (source is .tsx)
61
+ src/frontend/src/**/*.js
62
+
63
+ # Env file (secrets)
64
+ .env
65
+
66
+ # Test data / archives
67
+ *.zip
68
+ drones/
69
+ _*.py
70
+ !__init__.py
71
+ !src/backend/**/_*.py
72
+
73
+ # Internal planning
74
+ tasks/
75
+ altyapi.txt
76
+ *.bundle
77
+ start.bat
78
+ .cursorignore
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: evren-sdk
3
+ Version: 0.3.0
4
+ Summary: EVREN MLOps Platform — Python inference SDK for object detection, classification, and segmentation models.
5
+ Project-URL: Homepage, https://evren.ssyz.org.tr
6
+ Project-URL: Documentation, https://docs.ssyz.org.tr/sdk
7
+ Project-URL: Repository, https://gitlab.crudfab.com/ssb/ssyz/evren/platform
8
+ Project-URL: Changelog, https://gitlab.crudfab.com/ssb/ssyz/evren/platform/-/blob/main/sdk/CHANGELOG.md
9
+ Project-URL: Issues, https://gitlab.crudfab.com/ssb/ssyz/evren/platform/-/issues
10
+ Author-email: Serkan Peker <serkan.peker@crudfab.com>
11
+ Maintainer-email: Serkan Peker <serkan.peker@crudfab.com>
12
+ License-Expression: Apache-2.0
13
+ Keywords: computer-vision,deep-learning,evren,inference,mlops,object-detection,sdk,yolo
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: License :: OSI Approved :: Apache Software License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Topic :: Scientific/Engineering :: Image Recognition
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: >=3.10
28
+ Requires-Dist: httpx>=0.27
29
+ Provides-Extra: dev
30
+ Requires-Dist: mypy; extra == 'dev'
31
+ Requires-Dist: pytest; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio; extra == 'dev'
33
+ Requires-Dist: ruff; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # evren-sdk
37
+
38
+ EVREN platformu üzerinde eğitilmiş bilgisayarlı görü modellerine
39
+ Python'dan çıkarım (inference) yapmanızı sağlayan resmi SDK.
40
+
41
+ Desteklenen görevler: **nesne tespiti**, **sınıflandırma**, **segmentasyon**,
42
+ **döndürülmüş kutu (OBB)**, **anahtar nokta (keypoint)**.
43
+
44
+ ```bash
45
+ pip install evren-sdk
46
+ ```
47
+
48
+ ## Hızlı Başlangıç
49
+
50
+ ```python
51
+ from evren_sdk import EvrenClient
52
+
53
+ client = EvrenClient(api_key="evren_xxxxx")
54
+
55
+ result = client.predict("kullanici/model-adi", "foto.jpg", confidence=0.3)
56
+
57
+ for det in result.predictions:
58
+ print(f"{det.class_name}: %{det.confidence:.0%} bbox={det.bbox}")
59
+
60
+ print(f"Çıkarım süresi: {result.inference_ms:.0f} ms")
61
+ ```
62
+
63
+ ## Kimlik Doğrulama
64
+
65
+ Platformda **Ayarlar → API Anahtarları** sayfasından anahtar oluşturun.
66
+ Anahtar `evren_` ön eki ile başlar ve `X-API-Key` başlığı üzerinden
67
+ otomatik gönderilir.
68
+
69
+ ```python
70
+ # API anahtarı ile (önerilen)
71
+ client = EvrenClient(api_key="evren_xxxxx")
72
+
73
+ # JWT token ile de çalışır (Authorization: Bearer)
74
+ client = EvrenClient(api_key="eyJhbGci...")
75
+ ```
76
+
77
+ ## Tekil Çıkarım
78
+
79
+ ```python
80
+ result = client.predict(
81
+ model="kullanici/model-adi", # slug veya UUID
82
+ image="resim.jpg", # dosya yolu, Path veya bytes
83
+ confidence=0.25, # minimum güven eşiği
84
+ iou=0.45, # NMS IoU eşiği
85
+ image_size=640, # giriş boyutu
86
+ classes=["araba", "insan"], # isteğe bağlı sınıf filtresi
87
+ )
88
+ ```
89
+
90
+ `model` parametresi üç format kabul eder:
91
+
92
+ | Format | Açıklama |
93
+ |---|---|
94
+ | `"owner/slug"` | Son versiyonu otomatik çözer |
95
+ | `"owner/slug:v2.0"` | Belirli versiyon etiketi |
96
+ | `"019cec..."` (UUID) | Doğrudan versiyon kimliği |
97
+
98
+ ## Toplu Çıkarım (Batch)
99
+
100
+ Birden fazla görseli tek istekte GPU batch çıkarımı ile işleyin.
101
+ Tekil çağrılardan önemli ölçüde hızlıdır.
102
+
103
+ ```python
104
+ batch = client.predict_batch(
105
+ model="kullanici/model-adi",
106
+ images=["img1.jpg", "img2.jpg", "img3.jpg"],
107
+ confidence=0.3,
108
+ )
109
+
110
+ for r in batch:
111
+ print(f"{r.count} tespit, {r.inference_ms:.0f} ms")
112
+
113
+ print(f"Toplam: {batch.total_ms:.0f} ms, {batch.count} görsel")
114
+ ```
115
+
116
+ ## Model Bilgileri
117
+
118
+ ```python
119
+ # Sınıf listesi ve renkleri
120
+ info = client.model_classes("kullanici/model-adi")
121
+ print(info.architecture) # "yolo26x"
122
+ for cls in info.classes:
123
+ print(f" {cls.name}: {cls.color}")
124
+
125
+ # Modelleri listele
126
+ for m in client.list_models():
127
+ print(f"{m.full_slug} — {m.architecture}")
128
+ ```
129
+
130
+ ## Ön Yükleme (Warmup)
131
+
132
+ İlk çıkarım gecikmesini ortadan kaldırmak için modelleri
133
+ önceden GPU'ya yükleyin.
134
+
135
+ ```python
136
+ client.warmup(["kullanici/model-adi", "diger/model"])
137
+ ```
138
+
139
+ ## Asenkron Kullanım
140
+
141
+ Aynı API, `async/await` ile:
142
+
143
+ ```python
144
+ import asyncio
145
+ from evren_sdk import AsyncEvrenClient
146
+
147
+ async def main():
148
+ async with AsyncEvrenClient(api_key="evren_xxxxx") as client:
149
+ result = await client.predict("kullanici/model-adi", "foto.jpg")
150
+ batch = await client.predict_batch(
151
+ "kullanici/model-adi",
152
+ ["a.jpg", "b.jpg"],
153
+ )
154
+ print(result.predictions, batch.count)
155
+
156
+ asyncio.run(main())
157
+ ```
158
+
159
+ ## Hata Yönetimi
160
+
161
+ ```python
162
+ from evren_sdk import (
163
+ EvrenClient, NotFoundError,
164
+ RateLimitError, InferenceError,
165
+ )
166
+ import time
167
+
168
+ client = EvrenClient(api_key="evren_xxxxx")
169
+
170
+ try:
171
+ result = client.predict("kullanici/model", "test.jpg")
172
+ except NotFoundError:
173
+ print("Model bulunamadı")
174
+ except RateLimitError as e:
175
+ time.sleep(e.retry_after)
176
+ except InferenceError:
177
+ print("GPU sunucusu geçici olarak kullanılamıyor")
178
+ ```
179
+
180
+ | Exception | HTTP | Açıklama |
181
+ |---|---|---|
182
+ | `AuthenticationError` | 401, 403 | Geçersiz veya süresi dolmuş anahtar |
183
+ | `NotFoundError` | 404 | Model veya versiyon bulunamadı |
184
+ | `ValidationError` | 422 | Hatalı parametre |
185
+ | `RateLimitError` | 429 | İstek limiti aşıldı |
186
+ | `InferenceError` | 502, 503 | GPU sunucusu hatası |
187
+
188
+ ## Veri Modelleri
189
+
190
+ | Sınıf | Temel Alanlar |
191
+ |---|---|
192
+ | `PredictResult` | `predictions`, `inference_ms`, `image_width`, `image_height`, `count` |
193
+ | `Prediction` | `class_name`, `confidence`, `bbox`, `color`, `mask`, `keypoints`, `obb` |
194
+ | `BatchResult` | `results`, `total_ms`, `count` — iterable |
195
+ | `ModelClasses` | `classes`, `architecture`, `model_name`, `imgsz` — `in` operatörü destekler |
196
+ | `ModelInfo` | `id`, `name`, `slug`, `architecture`, `full_slug` |
197
+ | `ModelVersion` | `id`, `version_tag`, `framework`, `metrics` |
198
+
199
+ ## Gereksinimler
200
+
201
+ - Python ≥ 3.10
202
+ - httpx ≥ 0.27
203
+
204
+ ## Lisans
205
+
206
+ Apache License 2.0
@@ -0,0 +1,171 @@
1
+ # evren-sdk
2
+
3
+ EVREN platformu üzerinde eğitilmiş bilgisayarlı görü modellerine
4
+ Python'dan çıkarım (inference) yapmanızı sağlayan resmi SDK.
5
+
6
+ Desteklenen görevler: **nesne tespiti**, **sınıflandırma**, **segmentasyon**,
7
+ **döndürülmüş kutu (OBB)**, **anahtar nokta (keypoint)**.
8
+
9
+ ```bash
10
+ pip install evren-sdk
11
+ ```
12
+
13
+ ## Hızlı Başlangıç
14
+
15
+ ```python
16
+ from evren_sdk import EvrenClient
17
+
18
+ client = EvrenClient(api_key="evren_xxxxx")
19
+
20
+ result = client.predict("kullanici/model-adi", "foto.jpg", confidence=0.3)
21
+
22
+ for det in result.predictions:
23
+ print(f"{det.class_name}: %{det.confidence:.0%} bbox={det.bbox}")
24
+
25
+ print(f"Çıkarım süresi: {result.inference_ms:.0f} ms")
26
+ ```
27
+
28
+ ## Kimlik Doğrulama
29
+
30
+ Platformda **Ayarlar → API Anahtarları** sayfasından anahtar oluşturun.
31
+ Anahtar `evren_` ön eki ile başlar ve `X-API-Key` başlığı üzerinden
32
+ otomatik gönderilir.
33
+
34
+ ```python
35
+ # API anahtarı ile (önerilen)
36
+ client = EvrenClient(api_key="evren_xxxxx")
37
+
38
+ # JWT token ile de çalışır (Authorization: Bearer)
39
+ client = EvrenClient(api_key="eyJhbGci...")
40
+ ```
41
+
42
+ ## Tekil Çıkarım
43
+
44
+ ```python
45
+ result = client.predict(
46
+ model="kullanici/model-adi", # slug veya UUID
47
+ image="resim.jpg", # dosya yolu, Path veya bytes
48
+ confidence=0.25, # minimum güven eşiği
49
+ iou=0.45, # NMS IoU eşiği
50
+ image_size=640, # giriş boyutu
51
+ classes=["araba", "insan"], # isteğe bağlı sınıf filtresi
52
+ )
53
+ ```
54
+
55
+ `model` parametresi üç format kabul eder:
56
+
57
+ | Format | Açıklama |
58
+ |---|---|
59
+ | `"owner/slug"` | Son versiyonu otomatik çözer |
60
+ | `"owner/slug:v2.0"` | Belirli versiyon etiketi |
61
+ | `"019cec..."` (UUID) | Doğrudan versiyon kimliği |
62
+
63
+ ## Toplu Çıkarım (Batch)
64
+
65
+ Birden fazla görseli tek istekte GPU batch çıkarımı ile işleyin.
66
+ Tekil çağrılardan önemli ölçüde hızlıdır.
67
+
68
+ ```python
69
+ batch = client.predict_batch(
70
+ model="kullanici/model-adi",
71
+ images=["img1.jpg", "img2.jpg", "img3.jpg"],
72
+ confidence=0.3,
73
+ )
74
+
75
+ for r in batch:
76
+ print(f"{r.count} tespit, {r.inference_ms:.0f} ms")
77
+
78
+ print(f"Toplam: {batch.total_ms:.0f} ms, {batch.count} görsel")
79
+ ```
80
+
81
+ ## Model Bilgileri
82
+
83
+ ```python
84
+ # Sınıf listesi ve renkleri
85
+ info = client.model_classes("kullanici/model-adi")
86
+ print(info.architecture) # "yolo26x"
87
+ for cls in info.classes:
88
+ print(f" {cls.name}: {cls.color}")
89
+
90
+ # Modelleri listele
91
+ for m in client.list_models():
92
+ print(f"{m.full_slug} — {m.architecture}")
93
+ ```
94
+
95
+ ## Ön Yükleme (Warmup)
96
+
97
+ İlk çıkarım gecikmesini ortadan kaldırmak için modelleri
98
+ önceden GPU'ya yükleyin.
99
+
100
+ ```python
101
+ client.warmup(["kullanici/model-adi", "diger/model"])
102
+ ```
103
+
104
+ ## Asenkron Kullanım
105
+
106
+ Aynı API, `async/await` ile:
107
+
108
+ ```python
109
+ import asyncio
110
+ from evren_sdk import AsyncEvrenClient
111
+
112
+ async def main():
113
+ async with AsyncEvrenClient(api_key="evren_xxxxx") as client:
114
+ result = await client.predict("kullanici/model-adi", "foto.jpg")
115
+ batch = await client.predict_batch(
116
+ "kullanici/model-adi",
117
+ ["a.jpg", "b.jpg"],
118
+ )
119
+ print(result.predictions, batch.count)
120
+
121
+ asyncio.run(main())
122
+ ```
123
+
124
+ ## Hata Yönetimi
125
+
126
+ ```python
127
+ from evren_sdk import (
128
+ EvrenClient, NotFoundError,
129
+ RateLimitError, InferenceError,
130
+ )
131
+ import time
132
+
133
+ client = EvrenClient(api_key="evren_xxxxx")
134
+
135
+ try:
136
+ result = client.predict("kullanici/model", "test.jpg")
137
+ except NotFoundError:
138
+ print("Model bulunamadı")
139
+ except RateLimitError as e:
140
+ time.sleep(e.retry_after)
141
+ except InferenceError:
142
+ print("GPU sunucusu geçici olarak kullanılamıyor")
143
+ ```
144
+
145
+ | Exception | HTTP | Açıklama |
146
+ |---|---|---|
147
+ | `AuthenticationError` | 401, 403 | Geçersiz veya süresi dolmuş anahtar |
148
+ | `NotFoundError` | 404 | Model veya versiyon bulunamadı |
149
+ | `ValidationError` | 422 | Hatalı parametre |
150
+ | `RateLimitError` | 429 | İstek limiti aşıldı |
151
+ | `InferenceError` | 502, 503 | GPU sunucusu hatası |
152
+
153
+ ## Veri Modelleri
154
+
155
+ | Sınıf | Temel Alanlar |
156
+ |---|---|
157
+ | `PredictResult` | `predictions`, `inference_ms`, `image_width`, `image_height`, `count` |
158
+ | `Prediction` | `class_name`, `confidence`, `bbox`, `color`, `mask`, `keypoints`, `obb` |
159
+ | `BatchResult` | `results`, `total_ms`, `count` — iterable |
160
+ | `ModelClasses` | `classes`, `architecture`, `model_name`, `imgsz` — `in` operatörü destekler |
161
+ | `ModelInfo` | `id`, `name`, `slug`, `architecture`, `full_slug` |
162
+ | `ModelVersion` | `id`, `version_tag`, `framework`, `metrics` |
163
+
164
+ ## Gereksinimler
165
+
166
+ - Python ≥ 3.10
167
+ - httpx ≥ 0.27
168
+
169
+ ## Lisans
170
+
171
+ Apache License 2.0
@@ -0,0 +1,40 @@
1
+ """EVREN Python SDK — nesne tespiti ve goruntu cikarim istemcisi."""
2
+
3
+ from .client import AsyncEvrenClient, EvrenClient
4
+ from .exceptions import (
5
+ AuthenticationError,
6
+ EvrenError,
7
+ InferenceError,
8
+ NotFoundError,
9
+ RateLimitError,
10
+ ValidationError,
11
+ )
12
+ from .models import (
13
+ BatchResult,
14
+ ClassInfo,
15
+ ModelClasses,
16
+ ModelInfo,
17
+ ModelVersion,
18
+ Prediction,
19
+ PredictResult,
20
+ )
21
+
22
+ __version__ = "0.3.0"
23
+
24
+ __all__ = [
25
+ "EvrenClient",
26
+ "AsyncEvrenClient",
27
+ "EvrenError",
28
+ "AuthenticationError",
29
+ "NotFoundError",
30
+ "RateLimitError",
31
+ "InferenceError",
32
+ "ValidationError",
33
+ "Prediction",
34
+ "PredictResult",
35
+ "BatchResult",
36
+ "ClassInfo",
37
+ "ModelClasses",
38
+ "ModelInfo",
39
+ "ModelVersion",
40
+ ]
@@ -0,0 +1,412 @@
1
+ from __future__ import annotations
2
+
3
+ import io
4
+ import uuid
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from .exceptions import (
11
+ AuthenticationError,
12
+ InferenceError,
13
+ NotFoundError,
14
+ RateLimitError,
15
+ ValidationError,
16
+ )
17
+ from .models import (
18
+ BatchResult,
19
+ ClassInfo,
20
+ ModelClasses,
21
+ ModelInfo,
22
+ ModelVersion,
23
+ Prediction,
24
+ PredictResult,
25
+ )
26
+
27
+ _API_BASE = "https://api.ssyz.org.tr/api/v1"
28
+ _BATCH_TIMEOUT = 120.0
29
+
30
+
31
+ def _auth_headers(key: str) -> dict[str, str]:
32
+ if key.startswith("evren_"):
33
+ return {"X-API-Key": key}
34
+ return {"Authorization": f"Bearer {key}"}
35
+
36
+
37
+ def _raise_for(resp: httpx.Response) -> None:
38
+ code = resp.status_code
39
+ try:
40
+ msg = resp.json().get("detail", resp.text[:300])
41
+ except Exception:
42
+ msg = resp.text[:300]
43
+
44
+ if code in (401, 403):
45
+ raise AuthenticationError(msg, code)
46
+ if code == 404:
47
+ raise NotFoundError(msg, code)
48
+ if code == 422:
49
+ raise ValidationError(msg, code)
50
+ if code == 429:
51
+ wait = int(resp.headers.get("Retry-After", "5"))
52
+ raise RateLimitError(msg, wait)
53
+ if code >= 500:
54
+ raise InferenceError(msg, code)
55
+ resp.raise_for_status()
56
+
57
+
58
+ def _read_img(source: str | Path | bytes) -> tuple[bytes, str]:
59
+ if isinstance(source, bytes):
60
+ return source, "image.jpg"
61
+ p = Path(source)
62
+ return p.read_bytes(), p.name
63
+
64
+
65
+ def _is_uuid(val: str) -> bool:
66
+ try:
67
+ uuid.UUID(val)
68
+ return True
69
+ except (ValueError, AttributeError):
70
+ return False
71
+
72
+
73
+ def _unwrap(data: Any) -> Any:
74
+ if isinstance(data, dict) and "data" in data:
75
+ return data["data"]
76
+ return data
77
+
78
+
79
+ def _build_form(version_id: str, confidence: float,
80
+ iou: float, imgsz: int,
81
+ classes: list[str] | None) -> dict[str, str]:
82
+ d: dict[str, str] = {
83
+ "model_version_id": version_id,
84
+ "confidence_threshold": str(confidence),
85
+ "iou_threshold": str(iou),
86
+ "image_size": str(imgsz),
87
+ }
88
+ if classes:
89
+ d["classes"] = ",".join(classes)
90
+ return d
91
+
92
+
93
+ def _to_prediction(raw: dict) -> Prediction:
94
+ return Prediction(
95
+ class_name=raw.get("class_name", ""),
96
+ confidence=raw.get("confidence", 0.0),
97
+ bbox=raw.get("bbox", []),
98
+ color=raw.get("color"),
99
+ keypoints=raw.get("keypoints"),
100
+ mask=raw.get("mask"),
101
+ obb=raw.get("obb"),
102
+ task=raw.get("task"),
103
+ probs=raw.get("probs"),
104
+ )
105
+
106
+
107
+ def _to_result(body: dict) -> PredictResult:
108
+ return PredictResult(
109
+ predictions=[_to_prediction(p) for p in body.get("predictions", [])],
110
+ inference_ms=body.get("inference_ms", 0),
111
+ image_width=body.get("image_width"),
112
+ image_height=body.get("image_height"),
113
+ model_version_id=body.get("model_version_id"),
114
+ )
115
+
116
+
117
+ def _to_model_info(m: dict) -> ModelInfo:
118
+ return ModelInfo(
119
+ id=m["id"], name=m["name"], slug=m.get("slug", ""),
120
+ architecture=m.get("architecture"),
121
+ owner_username=m.get("owner_username"),
122
+ )
123
+
124
+
125
+ def _to_model_ver(v: dict) -> ModelVersion:
126
+ return ModelVersion(
127
+ id=v["id"], version_tag=v.get("version_tag", ""),
128
+ weights_url=v.get("weights_url"),
129
+ framework=v.get("framework", "pytorch"),
130
+ metrics=v.get("metrics", {}),
131
+ )
132
+
133
+
134
+ def _resolve_versions(body: Any) -> list[dict]:
135
+ data = _unwrap(body)
136
+ if isinstance(data, list):
137
+ return data
138
+ return data.get("items", data.get("data", []))
139
+
140
+
141
+ def _pick_version(versions: list[dict], tag: str | None, slug: str) -> str:
142
+ if not versions:
143
+ raise NotFoundError(f"'{slug}' icin versiyon yok", 404)
144
+
145
+ if tag is None:
146
+ return versions[0]["id"]
147
+
148
+ for v in versions:
149
+ if v.get("version_tag") == tag:
150
+ return v["id"]
151
+ raise NotFoundError(f"'{slug}' icin '{tag}' versiyonu yok", 404)
152
+
153
+
154
+ # ---------------------------------------------------------------
155
+ # sync
156
+ # ---------------------------------------------------------------
157
+
158
+
159
+ class EvrenClient:
160
+ """EVREN inference API senkron istemcisi.
161
+
162
+ >>> with EvrenClient(api_key="evren_xxx") as c:
163
+ ... r = c.predict("owner/slug", "foto.jpg")
164
+ ... print(r.predictions)
165
+ """
166
+
167
+ def __init__(self, api_key: str, *,
168
+ base_url: str = _API_BASE,
169
+ timeout: float = 60.0) -> None:
170
+ self._http = httpx.Client(
171
+ base_url=base_url.rstrip("/"),
172
+ headers=_auth_headers(api_key),
173
+ timeout=timeout,
174
+ )
175
+ self._vcache: dict[str, str] = {}
176
+
177
+ def resolve(self, slug: str) -> str:
178
+ """``owner/slug`` veya ``owner/slug:tag`` -> version UUID."""
179
+ if slug in self._vcache:
180
+ return self._vcache[slug]
181
+
182
+ slug_part, _, tag = slug.partition(":")
183
+ tag = tag or None
184
+
185
+ r = self._http.get(f"/models/{slug_part}")
186
+ if r.status_code != 200:
187
+ _raise_for(r)
188
+ mid = _unwrap(r.json())["id"]
189
+
190
+ r2 = self._http.get(f"/models/{mid}/versions")
191
+ if r2.status_code != 200:
192
+ _raise_for(r2)
193
+
194
+ vid = _pick_version(_resolve_versions(r2.json()), tag, slug)
195
+ self._vcache[slug] = vid
196
+ return vid
197
+
198
+ def _vid(self, model: str) -> str:
199
+ return model if _is_uuid(model) else self.resolve(model)
200
+
201
+ def predict(self, model: str, image: str | Path | bytes, *,
202
+ confidence: float = 0.25, iou: float = 0.45,
203
+ image_size: int = 640,
204
+ classes: list[str] | None = None) -> PredictResult:
205
+ raw, name = _read_img(image)
206
+ r = self._http.post(
207
+ "/inference/predict",
208
+ data=_build_form(self._vid(model), confidence, iou, image_size, classes),
209
+ files={"file": (name, io.BytesIO(raw), "image/jpeg")},
210
+ )
211
+ if r.status_code != 200:
212
+ _raise_for(r)
213
+ return _to_result(_unwrap(r.json()))
214
+
215
+ def predict_batch(self, model: str, images: list[str | Path | bytes], *,
216
+ confidence: float = 0.25, iou: float = 0.45,
217
+ image_size: int = 640,
218
+ classes: list[str] | None = None) -> BatchResult:
219
+ vid = self._vid(model)
220
+ parts = []
221
+ for i, img in enumerate(images):
222
+ raw, fname = _read_img(img)
223
+ n = fname if fname != "image.jpg" else f"img_{i}.jpg"
224
+ parts.append(("files", (n, io.BytesIO(raw), "image/jpeg")))
225
+
226
+ r = self._http.post(
227
+ "/inference/predict/batch",
228
+ data=_build_form(vid, confidence, iou, image_size, classes),
229
+ files=parts, timeout=_BATCH_TIMEOUT,
230
+ )
231
+ if r.status_code != 200:
232
+ _raise_for(r)
233
+
234
+ body = _unwrap(r.json())
235
+ items = [_to_result(x) for x in body.get("results", [])]
236
+ return BatchResult(results=items,
237
+ total_ms=body.get("total_ms", 0),
238
+ count=body.get("count", len(items)))
239
+
240
+ def model_classes(self, model: str) -> ModelClasses:
241
+ vid = self._vid(model)
242
+ r = self._http.get(f"/inference/model-classes/{vid}")
243
+ if r.status_code != 200:
244
+ _raise_for(r)
245
+ b = _unwrap(r.json())
246
+ return ModelClasses(
247
+ model_version_id=b.get("model_version_id", vid),
248
+ classes=[ClassInfo(name=c["name"], color=c["color"])
249
+ for c in b.get("classes", [])],
250
+ model_name=b.get("model_name"),
251
+ architecture=b.get("architecture"),
252
+ total=b.get("total", 0),
253
+ imgsz=b.get("imgsz", 640),
254
+ )
255
+
256
+ def warmup(self, models: list[str]) -> dict:
257
+ ids = [self._vid(m) for m in models]
258
+ r = self._http.post("/inference/warmup", json=ids,
259
+ timeout=_BATCH_TIMEOUT)
260
+ if r.status_code != 200:
261
+ _raise_for(r)
262
+ return _unwrap(r.json())
263
+
264
+ def list_models(self, limit: int = 50) -> list[ModelInfo]:
265
+ r = self._http.get("/models",
266
+ params={"limit": limit, "include_public": "true"})
267
+ if r.status_code != 200:
268
+ _raise_for(r)
269
+ body = _unwrap(r.json())
270
+ rows = body if isinstance(body, list) else body.get("items", [])
271
+ return [_to_model_info(m) for m in rows]
272
+
273
+ def list_versions(self, model_id: str) -> list[ModelVersion]:
274
+ r = self._http.get(f"/models/{model_id}/versions")
275
+ if r.status_code != 200:
276
+ _raise_for(r)
277
+ return [_to_model_ver(v) for v in _resolve_versions(r.json())]
278
+
279
+ def close(self) -> None:
280
+ self._http.close()
281
+
282
+ def __enter__(self) -> EvrenClient:
283
+ return self
284
+
285
+ def __exit__(self, *exc: Any) -> None:
286
+ self.close()
287
+
288
+
289
+ # ---------------------------------------------------------------
290
+ # async
291
+ # ---------------------------------------------------------------
292
+
293
+
294
+ class AsyncEvrenClient:
295
+ """EVREN inference API asenkron istemcisi.
296
+
297
+ >>> async with AsyncEvrenClient(api_key="evren_xxx") as c:
298
+ ... r = await c.predict("owner/slug", "foto.jpg")
299
+ """
300
+
301
+ def __init__(self, api_key: str, *,
302
+ base_url: str = _API_BASE,
303
+ timeout: float = 60.0) -> None:
304
+ self._http = httpx.AsyncClient(
305
+ base_url=base_url.rstrip("/"),
306
+ headers=_auth_headers(api_key),
307
+ timeout=timeout,
308
+ )
309
+ self._vcache: dict[str, str] = {}
310
+
311
+ async def resolve(self, slug: str) -> str:
312
+ if slug in self._vcache:
313
+ return self._vcache[slug]
314
+
315
+ slug_part, _, tag = slug.partition(":")
316
+ tag = tag or None
317
+
318
+ r = await self._http.get(f"/models/{slug_part}")
319
+ if r.status_code != 200:
320
+ _raise_for(r)
321
+ mid = _unwrap(r.json())["id"]
322
+
323
+ r2 = await self._http.get(f"/models/{mid}/versions")
324
+ if r2.status_code != 200:
325
+ _raise_for(r2)
326
+
327
+ vid = _pick_version(_resolve_versions(r2.json()), tag, slug)
328
+ self._vcache[slug] = vid
329
+ return vid
330
+
331
+ async def _vid(self, model: str) -> str:
332
+ return model if _is_uuid(model) else await self.resolve(model)
333
+
334
+ async def predict(self, model: str, image: str | Path | bytes, *,
335
+ confidence: float = 0.25, iou: float = 0.45,
336
+ image_size: int = 640,
337
+ classes: list[str] | None = None) -> PredictResult:
338
+ raw, name = _read_img(image)
339
+ r = await self._http.post(
340
+ "/inference/predict",
341
+ data=_build_form(await self._vid(model), confidence, iou, image_size, classes),
342
+ files={"file": (name, io.BytesIO(raw), "image/jpeg")},
343
+ )
344
+ if r.status_code != 200:
345
+ _raise_for(r)
346
+ return _to_result(_unwrap(r.json()))
347
+
348
+ async def predict_batch(self, model: str, images: list[str | Path | bytes], *,
349
+ confidence: float = 0.25, iou: float = 0.45,
350
+ image_size: int = 640,
351
+ classes: list[str] | None = None) -> BatchResult:
352
+ vid = await self._vid(model)
353
+ parts = []
354
+ for i, img in enumerate(images):
355
+ raw, fname = _read_img(img)
356
+ n = fname if fname != "image.jpg" else f"img_{i}.jpg"
357
+ parts.append(("files", (n, io.BytesIO(raw), "image/jpeg")))
358
+
359
+ r = await self._http.post(
360
+ "/inference/predict/batch",
361
+ data=_build_form(vid, confidence, iou, image_size, classes),
362
+ files=parts, timeout=_BATCH_TIMEOUT,
363
+ )
364
+ if r.status_code != 200:
365
+ _raise_for(r)
366
+ body = _unwrap(r.json())
367
+ items = [_to_result(x) for x in body.get("results", [])]
368
+ return BatchResult(results=items,
369
+ total_ms=body.get("total_ms", 0),
370
+ count=body.get("count", len(items)))
371
+
372
+ async def model_classes(self, model: str) -> ModelClasses:
373
+ vid = await self._vid(model)
374
+ r = await self._http.get(f"/inference/model-classes/{vid}")
375
+ if r.status_code != 200:
376
+ _raise_for(r)
377
+ b = _unwrap(r.json())
378
+ return ModelClasses(
379
+ model_version_id=b.get("model_version_id", vid),
380
+ classes=[ClassInfo(name=c["name"], color=c["color"])
381
+ for c in b.get("classes", [])],
382
+ model_name=b.get("model_name"),
383
+ architecture=b.get("architecture"),
384
+ total=b.get("total", 0),
385
+ imgsz=b.get("imgsz", 640),
386
+ )
387
+
388
+ async def warmup(self, models: list[str]) -> dict:
389
+ ids = [await self._vid(m) for m in models]
390
+ r = await self._http.post("/inference/warmup", json=ids,
391
+ timeout=_BATCH_TIMEOUT)
392
+ if r.status_code != 200:
393
+ _raise_for(r)
394
+ return _unwrap(r.json())
395
+
396
+ async def list_models(self, limit: int = 50) -> list[ModelInfo]:
397
+ r = await self._http.get("/models",
398
+ params={"limit": limit, "include_public": "true"})
399
+ if r.status_code != 200:
400
+ _raise_for(r)
401
+ body = _unwrap(r.json())
402
+ rows = body if isinstance(body, list) else body.get("items", [])
403
+ return [_to_model_info(m) for m in rows]
404
+
405
+ async def close(self) -> None:
406
+ await self._http.aclose()
407
+
408
+ async def __aenter__(self) -> AsyncEvrenClient:
409
+ return self
410
+
411
+ async def __aexit__(self, *exc: Any) -> None:
412
+ await self.close()
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class EvrenError(Exception):
5
+
6
+ def __init__(self, message: str, status_code: int | None = None) -> None:
7
+ self.status_code = status_code
8
+ super().__init__(message)
9
+
10
+
11
+ class AuthenticationError(EvrenError):
12
+ """Gecersiz veya suresi dolmus API anahtari (HTTP 401/403)."""
13
+
14
+
15
+ class NotFoundError(EvrenError):
16
+ """Belirtilen model veya versiyon bulunamadi (HTTP 404)."""
17
+
18
+
19
+ class RateLimitError(EvrenError):
20
+ """Istek limiti asildi (HTTP 429).
21
+
22
+ ``retry_after`` saniye bekleyip tekrar deneyin.
23
+ """
24
+
25
+ def __init__(self, message: str, retry_after: int = 5) -> None:
26
+ self.retry_after = retry_after
27
+ super().__init__(message, 429)
28
+
29
+
30
+ class InferenceError(EvrenError):
31
+ """GPU cikarim sunucusu gecici olarak kullanilamiyor (HTTP 502/503)."""
32
+
33
+
34
+ class ValidationError(EvrenError):
35
+ """Istek parametrelerinde dogrulama hatasi (HTTP 422)."""
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+
6
+ @dataclass(slots=True)
7
+ class Prediction:
8
+ class_name: str = ""
9
+ confidence: float = 0.0
10
+ bbox: list[float] = field(default_factory=list)
11
+ color: str | None = None
12
+ keypoints: list[list[float]] | None = None
13
+ mask: list[list[float]] | None = None
14
+ obb: dict | None = None
15
+ task: str | None = None
16
+ probs: list[dict] | None = None
17
+
18
+ def __repr__(self) -> str:
19
+ return f"<Prediction {self.class_name} conf={self.confidence:.3f}>"
20
+
21
+
22
+ @dataclass(slots=True)
23
+ class PredictResult:
24
+ predictions: list[Prediction] = field(default_factory=list)
25
+ inference_ms: float = 0.0
26
+ image_width: int | None = None
27
+ image_height: int | None = None
28
+ model_version_id: str | None = None
29
+
30
+ @property
31
+ def count(self) -> int:
32
+ return len(self.predictions)
33
+
34
+
35
+ @dataclass(slots=True)
36
+ class BatchResult:
37
+ results: list[PredictResult] = field(default_factory=list)
38
+ total_ms: float = 0.0
39
+ count: int = 0
40
+
41
+ def __iter__(self):
42
+ return iter(self.results)
43
+
44
+ def __len__(self) -> int:
45
+ return self.count
46
+
47
+
48
+ @dataclass(slots=True, frozen=True)
49
+ class ClassInfo:
50
+ name: str
51
+ color: str
52
+
53
+
54
+ @dataclass(slots=True)
55
+ class ModelClasses:
56
+ model_version_id: str
57
+ classes: list[ClassInfo] = field(default_factory=list)
58
+ model_name: str | None = None
59
+ architecture: str | None = None
60
+ total: int = 0
61
+ imgsz: int = 640
62
+
63
+ def __contains__(self, name: str) -> bool:
64
+ return any(c.name == name for c in self.classes)
65
+
66
+ def names(self) -> list[str]:
67
+ return [c.name for c in self.classes]
68
+
69
+
70
+ @dataclass(slots=True)
71
+ class ModelInfo:
72
+ id: str
73
+ name: str
74
+ slug: str
75
+ architecture: str | None = None
76
+ owner_username: str | None = None
77
+
78
+ @property
79
+ def full_slug(self) -> str:
80
+ if self.owner_username:
81
+ return f"{self.owner_username}/{self.slug}"
82
+ return self.slug
83
+
84
+
85
+ @dataclass(slots=True)
86
+ class ModelVersion:
87
+ id: str
88
+ version_tag: str
89
+ weights_url: str | None = None
90
+ framework: str = "pytorch"
91
+ metrics: dict = field(default_factory=dict)
File without changes
@@ -0,0 +1,60 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "evren-sdk"
7
+ version = "0.3.0"
8
+ description = "EVREN MLOps Platform — Python inference SDK for object detection, classification, and segmentation models."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "Apache-2.0"
12
+ authors = [
13
+ { name = "Serkan Peker", email = "serkan.peker@crudfab.com" },
14
+ ]
15
+ maintainers = [
16
+ { name = "Serkan Peker", email = "serkan.peker@crudfab.com" },
17
+ ]
18
+ keywords = [
19
+ "evren", "mlops", "inference", "object-detection",
20
+ "yolo", "computer-vision", "sdk", "deep-learning",
21
+ ]
22
+ classifiers = [
23
+ "Development Status :: 4 - Beta",
24
+ "Intended Audience :: Developers",
25
+ "Intended Audience :: Science/Research",
26
+ "License :: OSI Approved :: Apache Software License",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
33
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
34
+ "Topic :: Scientific/Engineering :: Image Recognition",
35
+ "Typing :: Typed",
36
+ ]
37
+ dependencies = [
38
+ "httpx>=0.27",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ dev = ["pytest", "pytest-asyncio", "ruff", "mypy"]
43
+
44
+ [project.urls]
45
+ Homepage = "https://evren.ssyz.org.tr"
46
+ Documentation = "https://docs.ssyz.org.tr/sdk"
47
+ Repository = "https://gitlab.crudfab.com/ssb/ssyz/evren/platform"
48
+ Changelog = "https://gitlab.crudfab.com/ssb/ssyz/evren/platform/-/blob/main/sdk/CHANGELOG.md"
49
+ Issues = "https://gitlab.crudfab.com/ssb/ssyz/evren/platform/-/issues"
50
+
51
+ [tool.hatch.build.targets.wheel]
52
+ packages = ["evren_sdk"]
53
+
54
+ [tool.ruff]
55
+ target-version = "py310"
56
+ line-length = 100
57
+
58
+ [tool.mypy]
59
+ python_version = "3.10"
60
+ strict = true