22LingoDotDevEngine implementation for Python SDK - Async version with httpx
33"""
44
5- # mypy: disable-error-code=unreachable
6-
75import asyncio
86import json
97from typing import Any , Callable , Dict , List , Optional
10- from urllib .parse import urljoin
118
129import httpx
1310from 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
4838class 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