retab 0.0.67__py3-none-any.whl → 0.0.69__py3-none-any.whl

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.
retab/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- from .client import AsyncRetab, Retab
1
+ from .client import AsyncRetab, Retab, SignatureVerificationError
2
2
  from . import utils
3
3
  from . import types
4
- __all__ = ["Retab", "AsyncRetab", "utils", "types"]
4
+ __all__ = ["Retab", "AsyncRetab", "SignatureVerificationError", "utils", "types"]
retab/client.py CHANGED
@@ -1,3 +1,5 @@
1
+ import hashlib
2
+ import hmac
1
3
  import json
2
4
  import os
3
5
  from types import TracebackType
@@ -8,10 +10,15 @@ import backoff.types
8
10
  import httpx
9
11
  import truststore
10
12
 
11
- from .resources import documents, models, schemas, projects
13
+ from .resources import documents, models, schemas, projects, extractions
12
14
  from .types.standards import PreparedRequest, FieldUnset
13
15
 
14
16
 
17
+ class SignatureVerificationError(Exception):
18
+ """Raised when webhook signature verification fails."""
19
+ pass
20
+
21
+
15
22
  class MaxRetriesExceeded(Exception):
16
23
  pass
17
24
 
@@ -34,7 +41,7 @@ class BaseRetab:
34
41
  Args:
35
42
  api_key (str, optional): Retab API key. If not provided, will look for RETAB_API_KEY env variable.
36
43
  base_url (str, optional): Base URL for API requests. Defaults to https://api.retab.com
37
- timeout (float): Request timeout in seconds. Defaults to 240.0
44
+ timeout (float): Request timeout in seconds. Defaults to 1800.0 (30 minutes)
38
45
  max_retries (int): Maximum number of retries for failed requests. Defaults to 3
39
46
  openai_api_key (str, optional): OpenAI API key. Will look for OPENAI_API_KEY env variable if not provided
40
47
 
@@ -46,7 +53,7 @@ class BaseRetab:
46
53
  self,
47
54
  api_key: Optional[str] = None,
48
55
  base_url: Optional[str] = None,
49
- timeout: float = 800.0,
56
+ timeout: float = 1800.0,
50
57
  max_retries: int = 3,
51
58
  openai_api_key: Optional[str] = FieldUnset,
52
59
  gemini_api_key: Optional[str] = FieldUnset,
@@ -140,7 +147,7 @@ class Retab(BaseRetab):
140
147
  Args:
141
148
  api_key (str, optional): Retab API key. If not provided, will look for RETAB_API_KEY env variable.
142
149
  base_url (str, optional): Base URL for API requests. Defaults to https://api.retab.com
143
- timeout (float): Request timeout in seconds. Defaults to 240.0
150
+ timeout (float): Request timeout in seconds. Defaults to 1800.0 (30 minutes)
144
151
  max_retries (int): Maximum number of retries for failed requests. Defaults to 3
145
152
  openai_api_key (str, optional): OpenAI API key. Will look for OPENAI_API_KEY env variable if not provided
146
153
  gemini_api_key (str, optional): Gemini API key. Will look for GEMINI_API_KEY env variable if not provided
@@ -161,7 +168,7 @@ class Retab(BaseRetab):
161
168
  self,
162
169
  api_key: Optional[str] = None,
163
170
  base_url: Optional[str] = None,
164
- timeout: float = 240.0,
171
+ timeout: float = 1800.0,
165
172
  max_retries: int = 3,
166
173
  openai_api_key: Optional[str] = FieldUnset,
167
174
  gemini_api_key: Optional[str] = FieldUnset,
@@ -177,6 +184,7 @@ class Retab(BaseRetab):
177
184
 
178
185
  self.client = httpx.Client(timeout=self.timeout)
179
186
  self.projects = projects.Projects(client=self)
187
+ self.extractions = extractions.Extractions(client=self)
180
188
  self.documents = documents.Documents(client=self)
181
189
  self.models = models.Models(client=self)
182
190
  self.schemas = schemas.Schemas(client=self)
@@ -385,6 +393,44 @@ class Retab(BaseRetab):
385
393
  """
386
394
  self.close()
387
395
 
396
+ @staticmethod
397
+ def verify_event(event_body: bytes, event_signature: str, secret: str) -> Any:
398
+ """Verify the signature of a webhook event.
399
+
400
+ Args:
401
+ event_body: The raw request body as bytes
402
+ event_signature: The signature from the request header (x-retab-signature)
403
+ secret: The webhook secret key used for signing
404
+
405
+ Returns:
406
+ Any: The parsed event payload (JSON)
407
+
408
+ Raises:
409
+ SignatureVerificationError: If the signature verification fails
410
+
411
+ Example:
412
+ ```python
413
+ from retab import Retab
414
+
415
+ # In your webhook handler
416
+ secret = "your_webhook_secret"
417
+ body = request.body # Raw bytes
418
+ signature = request.headers.get("x-retab-signature")
419
+
420
+ try:
421
+ event = Retab.verify_event(body, signature, secret)
422
+ print(f"Verified event: {event}")
423
+ except SignatureVerificationError:
424
+ print("Invalid signature!")
425
+ ```
426
+ """
427
+ expected_signature = hmac.new(secret.encode(), event_body, hashlib.sha256).hexdigest()
428
+
429
+ if not hmac.compare_digest(event_signature, expected_signature):
430
+ raise SignatureVerificationError("Invalid signature")
431
+
432
+ return json.loads(event_body.decode("utf-8"))
433
+
388
434
 
389
435
  class AsyncRetab(BaseRetab):
390
436
  """Asynchronous client for interacting with the Retab API.
@@ -395,7 +441,7 @@ class AsyncRetab(BaseRetab):
395
441
  Args:
396
442
  api_key (str, optional): Retab API key. If not provided, will look for RETAB_API_KEY env variable.
397
443
  base_url (str, optional): Base URL for API requests. Defaults to https://api.retab.com
398
- timeout (float): Request timeout in seconds. Defaults to 240.0
444
+ timeout (float): Request timeout in seconds. Defaults to 1800.0 (30 minutes)
399
445
  max_retries (int): Maximum number of retries for failed requests. Defaults to 3
400
446
  openai_api_key (str, optional): OpenAI API key. Will look for OPENAI_API_KEY env variable if not provided
401
447
  claude_api_key (str, optional): Claude API key. Will look for CLAUDE_API_KEY env variable if not provided
@@ -418,7 +464,7 @@ class AsyncRetab(BaseRetab):
418
464
  self,
419
465
  api_key: Optional[str] = None,
420
466
  base_url: Optional[str] = None,
421
- timeout: float = 240.0,
467
+ timeout: float = 1800.0,
422
468
  max_retries: int = 3,
423
469
  openai_api_key: Optional[str] = FieldUnset,
424
470
  gemini_api_key: Optional[str] = FieldUnset,
@@ -435,6 +481,7 @@ class AsyncRetab(BaseRetab):
435
481
  self.client = httpx.AsyncClient(timeout=self.timeout)
436
482
 
437
483
  self.projects = projects.AsyncProjects(client=self)
484
+ self.extractions = extractions.AsyncExtractions(client=self)
438
485
  self.documents = documents.AsyncDocuments(client=self)
439
486
  self.models = models.AsyncModels(client=self)
440
487
  self.schemas = schemas.AsyncSchemas(client=self)
@@ -661,3 +708,41 @@ class AsyncRetab(BaseRetab):
661
708
  traceback: The traceback of the exception that was raised, if any
662
709
  """
663
710
  await self.close()
711
+ @staticmethod
712
+ def verify_event(event_body: bytes, event_signature: str, secret: str) -> Any:
713
+ """Verify the signature of a webhook event.
714
+
715
+ Args:
716
+ event_body: The raw request body as bytes
717
+ event_signature: The signature from the request header (x-retab-signature)
718
+ secret: The webhook secret key used for signing
719
+
720
+ Returns:
721
+ Any: The parsed event payload (JSON)
722
+
723
+ Raises:
724
+ SignatureVerificationError: If the signature verification fails
725
+
726
+ Example:
727
+ ```python
728
+ from retab import AsyncRetab
729
+
730
+ # In your async webhook handler
731
+ secret = "your_webhook_secret"
732
+ body = await request.body() # Raw bytes
733
+ signature = request.headers.get("x-retab-signature")
734
+
735
+ try:
736
+ event = AsyncRetab.verify_event(body, signature, secret)
737
+ print(f"Verified event: {event}")
738
+ except SignatureVerificationError:
739
+ print("Invalid signature!")
740
+ ```
741
+ """
742
+ expected_signature = hmac.new(secret.encode(), event_body, hashlib.sha256).hexdigest()
743
+
744
+ if not hmac.compare_digest(event_signature, expected_signature):
745
+ raise SignatureVerificationError("Invalid signature")
746
+
747
+ return json.loads(event_body.decode("utf-8"))
748
+
retab/generate_types.py CHANGED
@@ -59,7 +59,7 @@ def type_to_zod(field_type: Any, put_names: bool = True, ts: bool = False) -> st
59
59
  optional = True
60
60
  typename = make_union([type_to_zod(x) for x in args])
61
61
  ts_typename = make_ts_union([type_to_zod(x, ts=True) for x in args])
62
- elif issubclass(origin, BaseModel) or is_typeddict(origin) or is_typeddict_ext(origin):
62
+ elif isinstance(origin, type) and (issubclass(origin, BaseModel) or is_typeddict(origin) or is_typeddict_ext(origin)):
63
63
  if put_names:
64
64
  name = get_class_name(origin)
65
65
  typename = "Z" + name
@@ -77,7 +77,7 @@ def type_to_zod(field_type: Any, put_names: bool = True, ts: bool = False) -> st
77
77
 
78
78
  typename += "z.object({\n"
79
79
  ts_typename += "{\n"
80
- props = [(n, f.annotation, f.default) for n, f in origin.model_fields.items() if not f.exclude] if issubclass(origin, BaseModel) else \
80
+ props = [(n, f.annotation, f.default) for n, f in origin.model_fields.items() if not f.exclude] if isinstance(origin, type) and issubclass(origin, BaseModel) else \
81
81
  [(n, f, PydanticUndefined) for n, f in origin.__annotations__.items()]
82
82
 
83
83
  for field_name, field, default in props: