Skip to content

Commit 13d8efd

Browse files
committed
feat: vNext migration
1 parent 6c85587 commit 13d8efd

File tree

4 files changed

+147
-235
lines changed

4 files changed

+147
-235
lines changed

README.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ async def main():
3939
result = await LingoDotDevEngine.quick_translate(
4040
"Hello, world!",
4141
api_key="your-api-key",
42+
engine_id="your-engine-id",
4243
target_locale="es"
4344
)
4445
print(result) # "¡Hola, mundo!"
@@ -55,9 +56,9 @@ from lingodotdev import LingoDotDevEngine
5556
async def main():
5657
config = {
5758
"api_key": "your-api-key",
58-
"api_url": "https://engine.lingo.dev" # Optional, defaults to this
59+
"engine_id": "your-engine-id",
5960
}
60-
61+
6162
async with LingoDotDevEngine(config) as engine:
6263
# Translate text
6364
text_result = await engine.localize_text(
@@ -89,6 +90,7 @@ async def batch_example():
8990
results = await LingoDotDevEngine.quick_batch_translate(
9091
"Welcome to our application",
9192
api_key="your-api-key",
93+
engine_id="your-engine-id",
9294
target_locales=["es", "fr", "de", "it"]
9395
)
9496
# Results: ["Bienvenido...", "Bienvenue...", "Willkommen...", "Benvenuto..."]
@@ -103,7 +105,7 @@ async def progress_example():
103105

104106
large_content = {f"item_{i}": f"Content {i}" for i in range(1000)}
105107

106-
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
108+
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
107109
result = await engine.localize_object(
108110
large_content,
109111
{"target_locale": "es"},
@@ -122,7 +124,7 @@ async def chat_example():
122124
{"name": "Charlie", "text": "Great to see you all!"}
123125
]
124126

125-
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
127+
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
126128
translated_chat = await engine.localize_chat(
127129
chat_messages,
128130
{"source_locale": "en", "target_locale": "es"}
@@ -140,7 +142,7 @@ async def concurrent_objects_example():
140142
{"success": "Account created", "next": "Continue to dashboard"}
141143
]
142144

143-
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
145+
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
144146
results = await engine.batch_localize_objects(
145147
objects,
146148
{"target_locale": "fr"}
@@ -152,7 +154,7 @@ async def concurrent_objects_example():
152154

153155
```python
154156
async def detection_example():
155-
async with LingoDotDevEngine({"api_key": "your-api-key"}) as engine:
157+
async with LingoDotDevEngine({"api_key": "your-api-key", "engine_id": "your-engine-id"}) as engine:
156158
detected = await engine.recognize_locale("Bonjour le monde")
157159
print(detected) # "fr"
158160
```
@@ -162,7 +164,8 @@ async def detection_example():
162164
```python
163165
config = {
164166
"api_key": "your-api-key", # Required: Your API key
165-
"api_url": "https://engine.lingo.dev", # Optional: API endpoint
167+
"engine_id": "your-engine-id", # Required: Your engine ID
168+
"api_url": "https://api.lingo.dev", # Optional: API endpoint
166169
"batch_size": 25, # Optional: Items per batch (1-250)
167170
"ideal_batch_item_size": 250 # Optional: Target words per batch (1-2500)
168171
}
@@ -186,7 +189,7 @@ config = {
186189
```python
187190
async def error_handling_example():
188191
try:
189-
async with LingoDotDevEngine({"api_key": "invalid-key"}) as engine:
192+
async with LingoDotDevEngine({"api_key": "invalid-key", "engine_id": "your-engine-id"}) as engine:
190193
result = await engine.localize_text("Hello", {"target_locale": "es"})
191194
except ValueError as e:
192195
print(f"Invalid request: {e}")
@@ -221,8 +224,8 @@ The async version is a drop-in replacement with these changes:
221224
- `whoami()` - Get API account info
222225

223226
### Convenience Methods
224-
- `quick_translate(content, api_key, target_locale, ...)` - One-off translation
225-
- `quick_batch_translate(content, api_key, target_locales, ...)` - Batch translation
227+
- `quick_translate(content, api_key, engine_id, target_locale, ...)` - One-off translation
228+
- `quick_batch_translate(content, api_key, engine_id, target_locales, ...)` - Batch translation
226229

227230
## 📄 License
228231

src/lingodotdev/engine.py

Lines changed: 33 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@
22
LingoDotDevEngine implementation for Python SDK - Async version with httpx
33
"""
44

5-
# mypy: disable-error-code=unreachable
6-
75
import asyncio
86
import json
97
from typing import Any, Callable, Dict, List, Optional
10-
from urllib.parse import urljoin
118

129
import httpx
1310
from nanoid import generate
@@ -18,31 +15,24 @@ class EngineConfig(BaseModel):
1815
"""Configuration for the LingoDotDevEngine"""
1916

2017
api_key: str
21-
engine_id: Optional[str] = None
22-
api_url: str = "https://engine.lingo.dev"
18+
engine_id: str
19+
api_url: str = "https://api.lingo.dev"
2320
batch_size: int = Field(default=25, ge=1, le=250)
2421
ideal_batch_item_size: int = Field(default=250, ge=1, le=2500)
2522

2623
@validator("engine_id", pre=True, always=True)
2724
@classmethod
28-
def validate_engine_id(cls, v: Optional[str]) -> Optional[str]:
29-
if v is None:
30-
return None
31-
v = v.strip()
32-
return v if v else None
25+
def validate_engine_id(cls, v: str) -> str:
26+
if not v.strip():
27+
raise ValueError("engine_id is required and cannot be empty")
28+
return v.strip()
3329

3430
@validator("api_url", pre=True, always=True)
3531
@classmethod
36-
def validate_api_url(cls, v: Optional[str], values: Dict[str, Any]) -> str:
37-
default_url = "https://engine.lingo.dev"
38-
if v is None:
39-
v = default_url
32+
def validate_api_url(cls, v: str) -> str:
4033
if not v.startswith(("http://", "https://")):
4134
raise ValueError("API URL must be a valid HTTP/HTTPS URL")
42-
v = v.rstrip("/")
43-
if v == default_url and values.get("engine_id"):
44-
return "https://api.lingo.dev"
45-
return v
35+
return v.rstrip("/")
4636

4737

4838
class LocalizationParams(BaseModel):
@@ -72,10 +62,6 @@ def __init__(self, config: Dict[str, Any]):
7262
self._client: Optional[httpx.AsyncClient] = None
7363
self._session_id: str = generate()
7464

75-
@property
76-
def _is_vnext(self) -> bool:
77-
return self.config.engine_id is not None
78-
7965
async def __aenter__(self):
8066
"""Async context manager entry"""
8167
await self._ensure_client()
@@ -88,14 +74,10 @@ async def __aexit__(self, exc_type, exc_val, exc_tb):
8874
async def _ensure_client(self):
8975
"""Ensure the httpx client is initialized"""
9076
if self._client is None or self._client.is_closed:
91-
if self._is_vnext:
92-
auth_header = {"X-API-Key": self.config.api_key}
93-
else:
94-
auth_header = {"Authorization": f"Bearer {self.config.api_key}"}
9577
self._client = httpx.AsyncClient(
9678
headers={
9779
"Content-Type": "application/json; charset=utf-8",
98-
**auth_header,
80+
"X-API-Key": self.config.api_key,
9981
},
10082
timeout=60.0,
10183
)
@@ -158,7 +140,6 @@ async def _localize_raw(
158140
"""
159141
await self._ensure_client()
160142
chunked_payload = self._extract_payload_chunks(payload)
161-
workflow_id = generate()
162143

163144
if concurrent and not progress_callback:
164145
# Process chunks concurrently for better performance
@@ -168,7 +149,6 @@ async def _localize_raw(
168149
params.source_locale,
169150
params.target_locale,
170151
{"data": chunk, "reference": params.reference},
171-
workflow_id,
172152
params.fast or False,
173153
)
174154
tasks.append(task)
@@ -184,7 +164,6 @@ async def _localize_raw(
184164
params.source_locale,
185165
params.target_locale,
186166
{"data": chunk, "reference": params.reference},
187-
workflow_id,
188167
params.fast or False,
189168
)
190169

@@ -206,7 +185,6 @@ async def _localize_chunk(
206185
source_locale: Optional[str],
207186
target_locale: str,
208187
payload: Dict[str, Any],
209-
workflow_id: str,
210188
fast: bool,
211189
) -> Dict[str, str]:
212190
"""
@@ -216,7 +194,6 @@ async def _localize_chunk(
216194
source_locale: Source locale
217195
target_locale: Target locale
218196
payload: Payload containing the chunk to be localized
219-
workflow_id: Workflow ID for tracking
220197
fast: Whether to use fast mode
221198
222199
Returns:
@@ -225,26 +202,16 @@ async def _localize_chunk(
225202
await self._ensure_client()
226203
assert self._client is not None # Type guard for mypy
227204

228-
if self._is_vnext:
229-
url = f"{self.config.api_url}/process/{self.config.engine_id}/localize"
230-
request_data: Dict[str, Any] = {
231-
"params": {"fast": fast},
232-
"sourceLocale": source_locale,
233-
"targetLocale": target_locale,
234-
"data": payload["data"],
235-
"sessionId": self._session_id,
236-
}
237-
if payload.get("reference"):
238-
request_data["reference"] = payload["reference"]
239-
else:
240-
url = urljoin(self.config.api_url, "/i18n")
241-
request_data = {
242-
"params": {"workflowId": workflow_id, "fast": fast},
243-
"locale": {"source": source_locale, "target": target_locale},
244-
"data": payload["data"],
245-
}
246-
if payload.get("reference"):
247-
request_data["reference"] = payload["reference"]
205+
url = f"{self.config.api_url}/process/{self.config.engine_id}/localize"
206+
request_data: Dict[str, Any] = {
207+
"params": {"fast": fast},
208+
"sourceLocale": source_locale,
209+
"targetLocale": target_locale,
210+
"data": payload["data"],
211+
"sessionId": self._session_id,
212+
}
213+
if payload.get("reference"):
214+
request_data["reference"] = payload["reference"]
248215

249216
try:
250217
response = await self._client.post(url, json=request_data)
@@ -491,10 +458,7 @@ async def recognize_locale(self, text: str) -> str:
491458
await self._ensure_client()
492459
assert self._client is not None # Type guard for mypy
493460

494-
if self._is_vnext:
495-
url = f"{self.config.api_url}/process/recognize"
496-
else:
497-
url = urljoin(self.config.api_url, "/recognize")
461+
url = f"{self.config.api_url}/process/recognize"
498462

499463
try:
500464
response = await self._client.post(url, json={"text": text})
@@ -527,16 +491,10 @@ async def whoami(self) -> Optional[Dict[str, str]]:
527491
await self._ensure_client()
528492
assert self._client is not None # Type guard for mypy
529493

530-
if self._is_vnext:
531-
url = f"{self.config.api_url}/users/me"
532-
else:
533-
url = urljoin(self.config.api_url, "/whoami")
494+
url = f"{self.config.api_url}/users/me"
534495

535496
try:
536-
if self._is_vnext:
537-
response = await self._client.get(url)
538-
else:
539-
response = await self._client.post(url)
497+
response = await self._client.get(url)
540498

541499
if response.is_success:
542500
payload = self._safe_parse_json(response)
@@ -583,11 +541,11 @@ async def quick_translate(
583541
cls,
584542
content: Any,
585543
api_key: str,
544+
engine_id: str,
586545
target_locale: str,
587546
source_locale: Optional[str] = None,
588-
api_url: str = "https://engine.lingo.dev",
547+
api_url: str = "https://api.lingo.dev",
589548
fast: bool = True,
590-
engine_id: Optional[str] = None,
591549
) -> Any:
592550
"""
593551
Quick one-off translation without manual context management.
@@ -596,11 +554,11 @@ async def quick_translate(
596554
Args:
597555
content: Text string or dict to translate
598556
api_key: Your Lingo.dev API key
557+
engine_id: Engine ID for the API
599558
target_locale: Target language code (e.g., 'es', 'fr')
600559
source_locale: Source language code (optional, auto-detected if None)
601560
api_url: API endpoint URL
602561
fast: Enable fast mode for quicker translations
603-
engine_id: Optional engine ID for vNext API.
604562
605563
Returns:
606564
Translated content (same type as input)
@@ -610,22 +568,23 @@ async def quick_translate(
610568
result = await LingoDotDevEngine.quick_translate(
611569
"Hello world",
612570
"your-api-key",
571+
"your-engine-id",
613572
"es"
614573
)
615574
616575
# Translate object
617576
result = await LingoDotDevEngine.quick_translate(
618577
{"greeting": "Hello", "farewell": "Goodbye"},
619578
"your-api-key",
579+
"your-engine-id",
620580
"es"
621581
)
622582
"""
623583
config = {
624584
"api_key": api_key,
585+
"engine_id": engine_id,
625586
"api_url": api_url,
626587
}
627-
if engine_id:
628-
config["engine_id"] = engine_id
629588

630589
async with cls(config) as engine:
631590
params = {
@@ -646,11 +605,11 @@ async def quick_batch_translate(
646605
cls,
647606
content: Any,
648607
api_key: str,
608+
engine_id: str,
649609
target_locales: List[str],
650610
source_locale: Optional[str] = None,
651-
api_url: str = "https://engine.lingo.dev",
611+
api_url: str = "https://api.lingo.dev",
652612
fast: bool = True,
653-
engine_id: Optional[str] = None,
654613
) -> List[Any]:
655614
"""
656615
Quick batch translation to multiple target locales.
@@ -659,11 +618,11 @@ async def quick_batch_translate(
659618
Args:
660619
content: Text string or dict to translate
661620
api_key: Your Lingo.dev API key
621+
engine_id: Engine ID for the API
662622
target_locales: List of target language codes (e.g., ['es', 'fr', 'de'])
663623
source_locale: Source language code (optional, auto-detected if None)
664624
api_url: API endpoint URL
665625
fast: Enable fast mode for quicker translations
666-
engine_id: Optional engine ID for vNext API.
667626
668627
Returns:
669628
List of translated content (one for each target locale)
@@ -672,16 +631,16 @@ async def quick_batch_translate(
672631
results = await LingoDotDevEngine.quick_batch_translate(
673632
"Hello world",
674633
"your-api-key",
634+
"your-engine-id",
675635
["es", "fr", "de"]
676636
)
677637
# Results: ["Hola mundo", "Bonjour le monde", "Hallo Welt"]
678638
"""
679639
config = {
680640
"api_key": api_key,
641+
"engine_id": engine_id,
681642
"api_url": api_url,
682643
}
683-
if engine_id:
684-
config["engine_id"] = engine_id
685644

686645
async with cls(config) as engine:
687646
if isinstance(content, str):

0 commit comments

Comments
 (0)