retab 0.0.42__py3-none-any.whl → 0.0.44__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 +2 -1
- retab/client.py +26 -51
- retab/generate_types.py +180 -0
- retab/resources/consensus/client.py +1 -1
- retab/resources/consensus/responses.py +1 -1
- retab/resources/deployments/__init__.py +3 -0
- retab/resources/deployments/automations/__init__.py +9 -0
- retab/resources/deployments/automations/client.py +244 -0
- retab/resources/deployments/automations/endpoints.py +290 -0
- retab/resources/deployments/automations/links.py +303 -0
- retab/resources/deployments/automations/logs.py +222 -0
- retab/resources/deployments/automations/mailboxes.py +423 -0
- retab/resources/deployments/automations/outlook.py +377 -0
- retab/resources/deployments/automations/tests.py +161 -0
- retab/resources/deployments/client.py +148 -0
- retab/resources/documents/client.py +94 -68
- retab/resources/documents/extractions.py +55 -46
- retab/resources/evaluations/__init__.py +2 -2
- retab/resources/evaluations/client.py +61 -77
- retab/resources/evaluations/documents.py +48 -37
- retab/resources/evaluations/iterations.py +58 -40
- retab/resources/jsonlUtils.py +3 -4
- retab/resources/processors/automations/endpoints.py +49 -39
- retab/resources/processors/automations/links.py +52 -43
- retab/resources/processors/automations/mailboxes.py +74 -59
- retab/resources/processors/automations/outlook.py +104 -82
- retab/resources/processors/client.py +35 -30
- retab/resources/projects/__init__.py +3 -0
- retab/resources/projects/client.py +285 -0
- retab/resources/projects/documents.py +244 -0
- retab/resources/projects/iterations.py +470 -0
- retab/resources/usage.py +2 -0
- retab/types/ai_models.py +2 -1
- retab/types/deprecated_evals.py +195 -0
- retab/types/evaluations/__init__.py +5 -2
- retab/types/evaluations/iterations.py +9 -43
- retab/types/evaluations/model.py +19 -24
- retab/types/extractions.py +1 -0
- retab/types/jobs/base.py +1 -1
- retab/types/jobs/evaluation.py +1 -1
- retab/types/logs.py +5 -6
- retab/types/mime.py +1 -10
- retab/types/projects/__init__.py +34 -0
- retab/types/projects/documents.py +30 -0
- retab/types/projects/iterations.py +78 -0
- retab/types/projects/model.py +68 -0
- retab/types/schemas/enhance.py +22 -5
- retab/types/schemas/evaluate.py +2 -2
- retab/types/schemas/object.py +27 -25
- retab/types/standards.py +2 -2
- retab/utils/__init__.py +3 -0
- retab/utils/ai_models.py +127 -12
- retab/utils/hashing.py +24 -0
- retab/utils/json_schema.py +1 -26
- retab/utils/mime.py +0 -17
- retab/utils/usage/usage.py +0 -1
- {retab-0.0.42.dist-info → retab-0.0.44.dist-info}/METADATA +4 -6
- {retab-0.0.42.dist-info → retab-0.0.44.dist-info}/RECORD +60 -55
- retab/_utils/__init__.py +0 -0
- retab/_utils/_model_cards/anthropic.yaml +0 -59
- retab/_utils/_model_cards/auto.yaml +0 -43
- retab/_utils/_model_cards/gemini.yaml +0 -117
- retab/_utils/_model_cards/openai.yaml +0 -301
- retab/_utils/_model_cards/xai.yaml +0 -28
- retab/_utils/ai_models.py +0 -138
- retab/_utils/benchmarking.py +0 -484
- retab/_utils/chat.py +0 -327
- retab/_utils/display.py +0 -440
- retab/_utils/json_schema.py +0 -2156
- retab/_utils/mime.py +0 -165
- retab/_utils/responses.py +0 -169
- retab/_utils/stream_context_managers.py +0 -52
- retab/_utils/usage/__init__.py +0 -0
- retab/_utils/usage/usage.py +0 -301
- {retab-0.0.42.dist-info → retab-0.0.44.dist-info}/WHEEL +0 -0
- {retab-0.0.42.dist-info → retab-0.0.44.dist-info}/top_level.txt +0 -0
retab/__init__.py
CHANGED
retab/client.py
CHANGED
@@ -7,10 +7,9 @@ import backoff
|
|
7
7
|
import backoff.types
|
8
8
|
import httpx
|
9
9
|
import truststore
|
10
|
-
from pydantic_core import PydanticUndefined
|
11
10
|
|
12
|
-
from .resources import consensus,
|
13
|
-
from .types.standards import PreparedRequest
|
11
|
+
from .resources import consensus, deployments, documents, files, finetuning, models, processors, schemas, secrets, usage, projects
|
12
|
+
from .types.standards import PreparedRequest, FieldUnset
|
14
13
|
|
15
14
|
|
16
15
|
class MaxRetriesExceeded(Exception):
|
@@ -43,20 +42,15 @@ class BaseRetab:
|
|
43
42
|
ValueError: If no API key is provided through arguments or environment variables
|
44
43
|
"""
|
45
44
|
|
46
|
-
# claude_api_key (str, optional): Claude API key. Will look for CLAUDE_API_KEY env variable if not provided
|
47
|
-
# xai_api_key (str, optional): XAI API key. Will look for XAI_API_KEY env variable if not provided
|
48
|
-
# gemini_api_key (str, optional): Gemini API key. Will look for GEMINI_API_KEY env variable if not provided
|
49
|
-
|
50
45
|
def __init__(
|
51
46
|
self,
|
52
47
|
api_key: Optional[str] = None,
|
53
48
|
base_url: Optional[str] = None,
|
54
49
|
timeout: float = 240.0,
|
55
50
|
max_retries: int = 3,
|
56
|
-
openai_api_key: Optional[str] =
|
57
|
-
gemini_api_key: Optional[str] =
|
58
|
-
|
59
|
-
xai_api_key: Optional[str] = PydanticUndefined, # type: ignore[assignment]
|
51
|
+
openai_api_key: Optional[str] = FieldUnset,
|
52
|
+
gemini_api_key: Optional[str] = FieldUnset,
|
53
|
+
xai_api_key: Optional[str] = FieldUnset,
|
60
54
|
) -> None:
|
61
55
|
if api_key is None:
|
62
56
|
api_key = os.environ.get("RETAB_API_KEY")
|
@@ -80,30 +74,21 @@ class BaseRetab:
|
|
80
74
|
"Content-Type": "application/json",
|
81
75
|
}
|
82
76
|
|
83
|
-
# Only check environment variables if the value is
|
84
|
-
if openai_api_key is
|
77
|
+
# Only check environment variables if the value is FieldUnset
|
78
|
+
if openai_api_key is FieldUnset:
|
85
79
|
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
86
80
|
|
87
|
-
|
88
|
-
# claude_api_key = os.environ.get("CLAUDE_API_KEY")
|
89
|
-
|
90
|
-
# if xai_api_key is PydanticUndefined:
|
91
|
-
# xai_api_key = os.environ.get("XAI_API_KEY")
|
92
|
-
|
93
|
-
if gemini_api_key is PydanticUndefined:
|
81
|
+
if gemini_api_key is FieldUnset:
|
94
82
|
gemini_api_key = os.environ.get("GEMINI_API_KEY")
|
95
83
|
|
96
|
-
# Only add headers if the values are actual strings (not None or
|
97
|
-
if openai_api_key and openai_api_key is not
|
84
|
+
# Only add headers if the values are actual strings (not None or FieldUnset)
|
85
|
+
if openai_api_key and openai_api_key is not FieldUnset:
|
98
86
|
self.headers["OpenAI-Api-Key"] = openai_api_key
|
99
87
|
|
100
|
-
|
101
|
-
# self.headers["Anthropic-Api-Key"] = claude_api_key
|
102
|
-
|
103
|
-
if xai_api_key and xai_api_key is not PydanticUndefined:
|
88
|
+
if xai_api_key and xai_api_key is not FieldUnset:
|
104
89
|
self.headers["XAI-Api-Key"] = xai_api_key
|
105
90
|
|
106
|
-
if gemini_api_key and gemini_api_key is not
|
91
|
+
if gemini_api_key and gemini_api_key is not FieldUnset:
|
107
92
|
self.headers["Gemini-Api-Key"] = gemini_api_key
|
108
93
|
|
109
94
|
def _prepare_url(self, endpoint: str) -> str:
|
@@ -150,7 +135,7 @@ class Retab(BaseRetab):
|
|
150
135
|
"""Synchronous client for interacting with the Retab API.
|
151
136
|
|
152
137
|
This client provides synchronous access to all Retab API resources including files, fine-tuning,
|
153
|
-
prompt optimization, documents, models,
|
138
|
+
prompt optimization, documents, models, processors, deployments, and schemas.
|
154
139
|
|
155
140
|
Args:
|
156
141
|
api_key (str, optional): Retab API key. If not provided, will look for RETAB_API_KEY env variable.
|
@@ -158,8 +143,6 @@ class Retab(BaseRetab):
|
|
158
143
|
timeout (float): Request timeout in seconds. Defaults to 240.0
|
159
144
|
max_retries (int): Maximum number of retries for failed requests. Defaults to 3
|
160
145
|
openai_api_key (str, optional): OpenAI API key. Will look for OPENAI_API_KEY env variable if not provided
|
161
|
-
claude_api_key (str, optional): Claude API key. Will look for CLAUDE_API_KEY env variable if not provided
|
162
|
-
xai_api_key (str, optional): XAI API key. Will look for XAI_API_KEY env variable if not provided
|
163
146
|
gemini_api_key (str, optional): Gemini API key. Will look for GEMINI_API_KEY env variable if not provided
|
164
147
|
|
165
148
|
Attributes:
|
@@ -168,7 +151,8 @@ class Retab(BaseRetab):
|
|
168
151
|
prompt_optimization: Access to prompt optimization operations
|
169
152
|
documents: Access to document operations
|
170
153
|
models: Access to model operations
|
171
|
-
|
154
|
+
processors: Access to processor operations
|
155
|
+
deployments: Access to deployment operations
|
172
156
|
schemas: Access to schema operations
|
173
157
|
responses: Access to responses API (OpenAI Responses API compatible interface)
|
174
158
|
"""
|
@@ -179,10 +163,8 @@ class Retab(BaseRetab):
|
|
179
163
|
base_url: Optional[str] = None,
|
180
164
|
timeout: float = 240.0,
|
181
165
|
max_retries: int = 3,
|
182
|
-
openai_api_key: Optional[str] =
|
183
|
-
gemini_api_key: Optional[str] =
|
184
|
-
# claude_api_key: Optional[str] = PydanticUndefined, # type: ignore[assignment]
|
185
|
-
# xai_api_key: Optional[str] = PydanticUndefined, # type: ignore[assignment]
|
166
|
+
openai_api_key: Optional[str] = FieldUnset,
|
167
|
+
gemini_api_key: Optional[str] = FieldUnset,
|
186
168
|
) -> None:
|
187
169
|
super().__init__(
|
188
170
|
api_key=api_key,
|
@@ -191,20 +173,17 @@ class Retab(BaseRetab):
|
|
191
173
|
max_retries=max_retries,
|
192
174
|
openai_api_key=openai_api_key,
|
193
175
|
gemini_api_key=gemini_api_key,
|
194
|
-
# claude_api_key=claude_api_key,
|
195
|
-
# xai_api_key=xai_api_key,
|
196
176
|
)
|
197
177
|
|
198
178
|
self.client = httpx.Client(timeout=self.timeout)
|
199
|
-
self.
|
200
|
-
self.evaluations = evaluations.Evaluations(client=self)
|
179
|
+
self.projects = projects.Projects(client=self)
|
201
180
|
self.files = files.Files(client=self)
|
202
181
|
self.fine_tuning = finetuning.FineTuning(client=self)
|
203
|
-
# self.prompt_optimization = prompt_optimization.PromptOptimization(client=self)
|
204
182
|
self.documents = documents.Documents(client=self)
|
205
183
|
self.models = models.Models(client=self)
|
206
184
|
self.schemas = schemas.Schemas(client=self)
|
207
185
|
self.processors = processors.Processors(client=self)
|
186
|
+
self.deployments = deployments.Deployments(client=self)
|
208
187
|
self.secrets = secrets.Secrets(client=self)
|
209
188
|
self.usage = usage.Usage(client=self)
|
210
189
|
self.consensus = consensus.Consensus(client=self)
|
@@ -418,7 +397,7 @@ class AsyncRetab(BaseRetab):
|
|
418
397
|
"""Asynchronous client for interacting with the Retab API.
|
419
398
|
|
420
399
|
This client provides asynchronous access to all Retab API resources including files, fine-tuning,
|
421
|
-
prompt optimization, documents, models,
|
400
|
+
prompt optimization, documents, models, processors, deployments, and schemas.
|
422
401
|
|
423
402
|
Args:
|
424
403
|
api_key (str, optional): Retab API key. If not provided, will look for RETAB_API_KEY env variable.
|
@@ -436,7 +415,8 @@ class AsyncRetab(BaseRetab):
|
|
436
415
|
prompt_optimization: Access to asynchronous prompt optimization operations
|
437
416
|
documents: Access to asynchronous document operations
|
438
417
|
models: Access to asynchronous model operations
|
439
|
-
|
418
|
+
processors: Access to asynchronous processor operations
|
419
|
+
deployments: Access to asynchronous deployment operations
|
440
420
|
schemas: Access to asynchronous schema operations
|
441
421
|
responses: Access to responses API (OpenAI Responses API compatible interface)
|
442
422
|
"""
|
@@ -447,10 +427,8 @@ class AsyncRetab(BaseRetab):
|
|
447
427
|
base_url: Optional[str] = None,
|
448
428
|
timeout: float = 240.0,
|
449
429
|
max_retries: int = 3,
|
450
|
-
openai_api_key: Optional[str] =
|
451
|
-
gemini_api_key: Optional[str] =
|
452
|
-
# claude_api_key: Optional[str] = PydanticUndefined, # type: ignore[assignment]
|
453
|
-
# xai_api_key: Optional[str] = PydanticUndefined, # type: ignore[assignment]
|
430
|
+
openai_api_key: Optional[str] = FieldUnset,
|
431
|
+
gemini_api_key: Optional[str] = FieldUnset,
|
454
432
|
) -> None:
|
455
433
|
super().__init__(
|
456
434
|
api_key=api_key,
|
@@ -459,21 +437,18 @@ class AsyncRetab(BaseRetab):
|
|
459
437
|
max_retries=max_retries,
|
460
438
|
openai_api_key=openai_api_key,
|
461
439
|
gemini_api_key=gemini_api_key,
|
462
|
-
# claude_api_key=claude_api_key,
|
463
|
-
# xai_api_key=xai_api_key,
|
464
440
|
)
|
465
441
|
|
466
442
|
self.client = httpx.AsyncClient(timeout=self.timeout)
|
467
443
|
|
468
|
-
self.
|
469
|
-
self.evaluations = evaluations.AsyncEvaluations(client=self)
|
444
|
+
self.projects = projects.AsyncProjects(client=self)
|
470
445
|
self.files = files.AsyncFiles(client=self)
|
471
446
|
self.fine_tuning = finetuning.AsyncFineTuning(client=self)
|
472
|
-
# self.prompt_optimization = prompt_optimization.AsyncPromptOptimization(client=self)
|
473
447
|
self.documents = documents.AsyncDocuments(client=self)
|
474
448
|
self.models = models.AsyncModels(client=self)
|
475
449
|
self.schemas = schemas.AsyncSchemas(client=self)
|
476
450
|
self.processors = processors.AsyncProcessors(client=self)
|
451
|
+
self.deployments = deployments.AsyncDeployments(client=self)
|
477
452
|
self.secrets = secrets.AsyncSecrets(client=self)
|
478
453
|
self.usage = usage.AsyncUsage(client=self)
|
479
454
|
self.consensus = consensus.AsyncConsensus(client=self)
|
retab/generate_types.py
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
import collections.abc
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import types
|
5
|
+
import typing
|
6
|
+
import enum
|
7
|
+
import sys
|
8
|
+
import inspect
|
9
|
+
from datetime import datetime, date
|
10
|
+
from typing import Any, Type, get_args, get_origin, Union, Literal, is_typeddict
|
11
|
+
from typing_extensions import is_typeddict as is_typeddict_ext
|
12
|
+
import typing_extensions
|
13
|
+
from pydantic_core import PydanticUndefined
|
14
|
+
from pydantic import BaseModel, EmailStr
|
15
|
+
import PIL.Image
|
16
|
+
|
17
|
+
to_compile: list[tuple[str, Type, bool]] = []
|
18
|
+
|
19
|
+
def is_base_model(field_type: Type) -> bool:
|
20
|
+
return getattr(field_type, "__name__", None) in ["BaseModel", "GenericModel", "ConfigDict", "Generic"]
|
21
|
+
|
22
|
+
def type_to_zod(field_type: Any, put_names: bool = True, ts: bool = False) -> str:
|
23
|
+
origin = get_origin(field_type) or field_type
|
24
|
+
optional = False
|
25
|
+
|
26
|
+
def make_union(args):
|
27
|
+
return args[0] if len(args) <= 1 else "z.union([" + ", ".join(args) + "])"
|
28
|
+
|
29
|
+
def make_ts_union(args):
|
30
|
+
return args[0] if len(args) <= 1 else " | ".join(args)
|
31
|
+
|
32
|
+
if isinstance(field_type, typing.ForwardRef):
|
33
|
+
return type_to_zod(typing._eval_type(field_type, globals(), locals(), []), ts=ts)
|
34
|
+
elif origin is typing.Annotated or origin is typing.Required or origin is typing_extensions.Required:
|
35
|
+
return type_to_zod(get_args(field_type)[0], put_names, ts=ts)
|
36
|
+
if origin is Union or origin is types.UnionType:
|
37
|
+
args = [x for x in get_args(field_type)]
|
38
|
+
if types.NoneType in args:
|
39
|
+
args.remove(types.NoneType)
|
40
|
+
optional = True
|
41
|
+
typename = make_union([type_to_zod(x) for x in args])
|
42
|
+
ts_typename = make_ts_union([type_to_zod(x, ts=True) for x in args])
|
43
|
+
elif issubclass(origin, BaseModel) or is_typeddict(origin) or is_typeddict_ext(origin):
|
44
|
+
if put_names:
|
45
|
+
typename = "Z" + origin.__name__
|
46
|
+
ts_typename = origin.__name__
|
47
|
+
to_compile.append((origin.__name__, field_type, True))
|
48
|
+
else:
|
49
|
+
excluded_fields = set()
|
50
|
+
typename = "z.object({\n"
|
51
|
+
ts_typename = "{\n"
|
52
|
+
props = [(n, f.annotation, f.default) for n, f in origin.model_fields.items()] if issubclass(origin, BaseModel) else \
|
53
|
+
[(n, f, PydanticUndefined) for n, f in origin.__annotations__.items()]
|
54
|
+
|
55
|
+
for field_name, field, default in props:
|
56
|
+
if field_name in excluded_fields:
|
57
|
+
continue
|
58
|
+
ts_compiled = type_to_zod(field, ts=True)
|
59
|
+
default_str = ""
|
60
|
+
if default is not PydanticUndefined and default is not None:
|
61
|
+
if isinstance(default, BaseModel):
|
62
|
+
default_str = f".default({json.dumps(default.model_dump(mode="json", exclude_unset=True))})"
|
63
|
+
else:
|
64
|
+
default_str = f".default({json.dumps(default)})"
|
65
|
+
typename += f" {field_name}: {type_to_zod(field)}{default_str},\n"
|
66
|
+
ts_typename += f" {field_name}{"?" if ts_compiled.endswith(" | undefined") or default is not PydanticUndefined else ""}: {ts_compiled},\n"
|
67
|
+
typename += "})"
|
68
|
+
ts_typename += "}"
|
69
|
+
|
70
|
+
based = origin.__bases__
|
71
|
+
for i in range(0, len(based)):
|
72
|
+
if is_base_model(based[i]) or based[i] is dict:
|
73
|
+
break
|
74
|
+
if issubclass(based[i], BaseModel):
|
75
|
+
excluded_fields.update(based[i].model_fields.keys())
|
76
|
+
typename += ".merge(Z" + based[i].__name__ + ".schema)"
|
77
|
+
ts_typename += " & " + based[i].__name__
|
78
|
+
elif origin is list or origin is typing.List or origin is collections.abc.Sequence or origin is collections.abc.Iterable:
|
79
|
+
typename = "z.array(" + type_to_zod(get_args(field_type)[0]) + ")"
|
80
|
+
ts_typename = "Array<" + type_to_zod(get_args(field_type)[0], ts=True) + ">"
|
81
|
+
elif origin is tuple:
|
82
|
+
args = get_args(field_type)
|
83
|
+
typename = "z.tuple([" + ", ".join([type_to_zod(x) for x in args]) + "])"
|
84
|
+
ts_typename = "[" + ", ".join([type_to_zod(x, ts=True) for x in args]) + "]"
|
85
|
+
elif origin is dict:
|
86
|
+
if len(get_args(field_type)) == 2:
|
87
|
+
typename = "z.record(" + type_to_zod(get_args(field_type)[0]) + ", " + type_to_zod(get_args(field_type)[1]) + ")"
|
88
|
+
ts_typename = "{[key: " + type_to_zod(get_args(field_type)[0], ts=True) + "]: " + type_to_zod(get_args(field_type)[1], ts=True) + "}"
|
89
|
+
else:
|
90
|
+
typename = "z.record(z.any())"
|
91
|
+
ts_typename = "{[key: string]: any}"
|
92
|
+
elif origin is Literal:
|
93
|
+
typename = make_union(["z.literal(" + json.dumps(x) + ")" for x in get_args(field_type)])
|
94
|
+
ts_typename = make_ts_union([json.dumps(x) for x in get_args(field_type)])
|
95
|
+
elif isinstance(field_type, typing.TypeVar):
|
96
|
+
typename = "z.any()"
|
97
|
+
ts_typename = "any"
|
98
|
+
elif isinstance(field_type, type) and issubclass(field_type, enum.Enum):
|
99
|
+
typename = "z.any()"
|
100
|
+
ts_typename = "any"
|
101
|
+
elif field_type is str or field_type is date or field_type is datetime:
|
102
|
+
typename = "z.string()"
|
103
|
+
ts_typename = "string"
|
104
|
+
elif field_type is int or field_type is float:
|
105
|
+
typename = "z.number()"
|
106
|
+
ts_typename = "number"
|
107
|
+
elif field_type is bool:
|
108
|
+
typename = "z.boolean()"
|
109
|
+
ts_typename = "boolean"
|
110
|
+
elif field_type is typing.Any:
|
111
|
+
typename = "z.any()"
|
112
|
+
ts_typename = "any"
|
113
|
+
elif field_type is bytes or field_type is PIL.Image.Image or field_type is typing.BinaryIO or origin is typing.IO or origin is typing_extensions.IO:
|
114
|
+
typename = "z.instanceof(Uint8Array)"
|
115
|
+
ts_typename = "Uint8Array"
|
116
|
+
elif field_type is EmailStr:
|
117
|
+
typename = "z.string().email()"
|
118
|
+
ts_typename = "string"
|
119
|
+
elif field_type is os.PathLike:
|
120
|
+
typename = "z.string()"
|
121
|
+
ts_typename = "string"
|
122
|
+
elif field_type is object:
|
123
|
+
typename = "z.object({}).passthrough()"
|
124
|
+
ts_typename = "object"
|
125
|
+
else:
|
126
|
+
raise ValueError(f"Unsupported type: {field_type} ({origin})")
|
127
|
+
if ts:
|
128
|
+
return ts_typename if not optional else ts_typename + " | null | undefined"
|
129
|
+
else:
|
130
|
+
return typename if not optional else typename + ".nullable().optional()"
|
131
|
+
|
132
|
+
|
133
|
+
# SET of names of python builtin types starting with a capital
|
134
|
+
builtin_types = {
|
135
|
+
"Any",
|
136
|
+
"BaseModel",
|
137
|
+
"NoneType",
|
138
|
+
"Literal",
|
139
|
+
"Union",
|
140
|
+
"List",
|
141
|
+
"Sequence",
|
142
|
+
"ConfigDict",
|
143
|
+
"Optional",
|
144
|
+
}
|
145
|
+
|
146
|
+
if __name__ == "__main__":
|
147
|
+
modules = []
|
148
|
+
for root, dirs, files in os.walk("retab/types"):
|
149
|
+
for module in files:
|
150
|
+
if module[-3:] != '.py':
|
151
|
+
continue
|
152
|
+
full_name = os.path.join(root, module[:-3]).replace(os.path.sep, '.')
|
153
|
+
__import__(full_name, locals(), globals())
|
154
|
+
modules.append(full_name)
|
155
|
+
|
156
|
+
|
157
|
+
for module_name in modules:
|
158
|
+
for name, obj in inspect.getmembers(sys.modules[module_name]):
|
159
|
+
if name[0] != name[0].lower() and name not in builtin_types:
|
160
|
+
to_compile.append((name, obj, False))
|
161
|
+
|
162
|
+
print("import * as z from 'zod';\n")
|
163
|
+
|
164
|
+
defined = {}
|
165
|
+
while len(to_compile) > 0:
|
166
|
+
name, model, necessary = to_compile.pop(0)
|
167
|
+
if name in defined: continue
|
168
|
+
defined[name] = True
|
169
|
+
try:
|
170
|
+
compiled = type_to_zod(model, False)
|
171
|
+
compiled_ts = type_to_zod(model, False, ts=True)
|
172
|
+
except Exception as e:
|
173
|
+
if not necessary:
|
174
|
+
print(f"Skipping {name} {model} due to error: {e}", file=sys.stderr)
|
175
|
+
continue
|
176
|
+
print(f"Error compiling {name} {model}", file=sys.stderr)
|
177
|
+
raise e
|
178
|
+
print("export const Z" + name + " = z.lazy(() => " + compiled + ");")
|
179
|
+
print("export type " + name + " = z.infer<typeof Z" + name + ">;\n")
|
180
|
+
|
@@ -21,7 +21,7 @@ class BaseConsensusMixin:
|
|
21
21
|
mode=mode,
|
22
22
|
)
|
23
23
|
|
24
|
-
return PreparedRequest(method="POST", url="/v1/consensus/reconcile", data=request.model_dump(), idempotency_key=idempotency_key)
|
24
|
+
return PreparedRequest(method="POST", url="/v1/consensus/reconcile", data=request.model_dump(mode="json", exclude_unset=True), idempotency_key=idempotency_key)
|
25
25
|
|
26
26
|
|
27
27
|
class Consensus(SyncAPIResource, BaseConsensusMixin):
|
@@ -55,7 +55,7 @@ class BaseResponsesMixin:
|
|
55
55
|
instructions=instructions,
|
56
56
|
)
|
57
57
|
|
58
|
-
return PreparedRequest(method="POST", url="/v1/responses", data=request.model_dump(), idempotency_key=idempotency_key)
|
58
|
+
return PreparedRequest(method="POST", url="/v1/responses", data=request.model_dump(mode="json", exclude_unset=True), idempotency_key=idempotency_key)
|
59
59
|
|
60
60
|
def prepare_parse(
|
61
61
|
self,
|
@@ -0,0 +1,244 @@
|
|
1
|
+
import hashlib
|
2
|
+
import hmac
|
3
|
+
import json
|
4
|
+
from typing import Any, Literal, Optional, Union
|
5
|
+
|
6
|
+
from ...._resource import AsyncAPIResource, SyncAPIResource
|
7
|
+
from ....types.automations.endpoints import Endpoint, UpdateEndpointRequest
|
8
|
+
from ....types.automations.links import Link, UpdateLinkRequest
|
9
|
+
from ....types.automations.mailboxes import Mailbox, UpdateMailboxRequest
|
10
|
+
from ....types.automations.outlook import Outlook, UpdateOutlookRequest
|
11
|
+
from ....types.standards import PreparedRequest
|
12
|
+
from .endpoints import AsyncEndpoints, Endpoints
|
13
|
+
from .links import AsyncLinks, Links
|
14
|
+
from .logs import AsyncLogs, Logs
|
15
|
+
from .mailboxes import AsyncMailboxes, Mailboxes
|
16
|
+
from .outlook import AsyncOutlooks, Outlooks
|
17
|
+
from .tests import AsyncTests, Tests
|
18
|
+
|
19
|
+
|
20
|
+
class SignatureVerificationError(Exception):
|
21
|
+
"""Raised when webhook signature verification fails."""
|
22
|
+
|
23
|
+
pass
|
24
|
+
|
25
|
+
|
26
|
+
class AutomationsMixin:
|
27
|
+
def _verify_event(self, event_body: bytes, event_signature: str, secret: str) -> Any:
|
28
|
+
"""
|
29
|
+
Verify the signature of a webhook event.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
body: The raw request body
|
33
|
+
signature: The signature header
|
34
|
+
secret: The secret key used for signing
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
The parsed event payload
|
38
|
+
|
39
|
+
Raises:
|
40
|
+
SignatureVerificationError: If the signature verification fails
|
41
|
+
"""
|
42
|
+
expected_signature = hmac.new(secret.encode(), event_body, hashlib.sha256).hexdigest()
|
43
|
+
|
44
|
+
if not hmac.compare_digest(event_signature, expected_signature):
|
45
|
+
raise SignatureVerificationError("Invalid signature")
|
46
|
+
|
47
|
+
return json.loads(event_body.decode("utf-8"))
|
48
|
+
|
49
|
+
def prepare_list(
|
50
|
+
self,
|
51
|
+
processor_id: str,
|
52
|
+
before: Optional[str] = None,
|
53
|
+
after: Optional[str] = None,
|
54
|
+
limit: Optional[int] = 10,
|
55
|
+
order: Optional[Literal["asc", "desc"]] = "desc",
|
56
|
+
automation_id: Optional[str] = None,
|
57
|
+
webhook_url: Optional[str] = None,
|
58
|
+
name: Optional[str] = None,
|
59
|
+
) -> PreparedRequest:
|
60
|
+
params = {
|
61
|
+
"before": before,
|
62
|
+
"after": after,
|
63
|
+
"limit": limit,
|
64
|
+
"order": order,
|
65
|
+
"id": automation_id,
|
66
|
+
"webhook_url": webhook_url,
|
67
|
+
"name": name,
|
68
|
+
}
|
69
|
+
params = {k: v for k, v in params.items() if v is not None}
|
70
|
+
return PreparedRequest(method="GET", url=f"/v1/processors/{processor_id}/automations", params=params)
|
71
|
+
|
72
|
+
def prepare_get(self, processor_id: str, automation_id: str) -> PreparedRequest:
|
73
|
+
return PreparedRequest(method="GET", url=f"/v1/processors/{processor_id}/automations/{automation_id}")
|
74
|
+
|
75
|
+
def prepare_update(
|
76
|
+
self,
|
77
|
+
processor_id: str,
|
78
|
+
automation_id: str,
|
79
|
+
automation_data: Union[UpdateLinkRequest, UpdateMailboxRequest, UpdateEndpointRequest, UpdateOutlookRequest],
|
80
|
+
) -> PreparedRequest:
|
81
|
+
return PreparedRequest(method="PUT", url=f"/v1/processors/{processor_id}/automations/{automation_id}", data=automation_data.model_dump(mode="json"))
|
82
|
+
|
83
|
+
def prepare_delete(self, processor_id: str, automation_id: str) -> PreparedRequest:
|
84
|
+
return PreparedRequest(method="DELETE", url=f"/v1/processors/{processor_id}/automations/{automation_id}")
|
85
|
+
|
86
|
+
|
87
|
+
class Automations(SyncAPIResource, AutomationsMixin):
|
88
|
+
"""Automations API wrapper"""
|
89
|
+
|
90
|
+
def __init__(self, client: Any) -> None:
|
91
|
+
super().__init__(client=client)
|
92
|
+
self.mailboxes = Mailboxes(client=client)
|
93
|
+
self.links = Links(client=client)
|
94
|
+
self.outlook = Outlooks(client=client)
|
95
|
+
self.endpoints = Endpoints(client=client)
|
96
|
+
self.tests = Tests(client=client)
|
97
|
+
self.logs = Logs(client=client)
|
98
|
+
|
99
|
+
def verify_event(self, event_body: bytes, event_signature: str, secret: str) -> Any:
|
100
|
+
"""
|
101
|
+
Verify the signature of a webhook event.
|
102
|
+
"""
|
103
|
+
return self._verify_event(event_body, event_signature, secret)
|
104
|
+
|
105
|
+
def list_automations(
|
106
|
+
self,
|
107
|
+
processor_id: str,
|
108
|
+
before: Optional[str] = None,
|
109
|
+
after: Optional[str] = None,
|
110
|
+
limit: Optional[int] = 10,
|
111
|
+
order: Optional[Literal["asc", "desc"]] = "desc",
|
112
|
+
automation_id: Optional[str] = None,
|
113
|
+
webhook_url: Optional[str] = None,
|
114
|
+
name: Optional[str] = None,
|
115
|
+
):
|
116
|
+
"""List automations attached to this processor."""
|
117
|
+
request = self.prepare_list(processor_id, before, after, limit, order, automation_id, webhook_url, name)
|
118
|
+
response = self._client._prepared_request(request)
|
119
|
+
return response
|
120
|
+
|
121
|
+
def get_automation(self, processor_id: str, automation_id: str) -> Union[Link, Mailbox, Endpoint, Outlook]:
|
122
|
+
"""Get a specific automation attached to this processor."""
|
123
|
+
request = self.prepare_get(processor_id, automation_id)
|
124
|
+
response = self._client._prepared_request(request)
|
125
|
+
|
126
|
+
# Return the appropriate model based on the automation type
|
127
|
+
if response["object"] == "automation.link":
|
128
|
+
return Link.model_validate(response)
|
129
|
+
elif response["object"] == "automation.mailbox":
|
130
|
+
return Mailbox.model_validate(response)
|
131
|
+
elif response["object"] == "automation.endpoint":
|
132
|
+
return Endpoint.model_validate(response)
|
133
|
+
elif response["object"] == "automation.outlook":
|
134
|
+
return Outlook.model_validate(response)
|
135
|
+
else:
|
136
|
+
raise ValueError(f"Unknown automation type: {response.get('object')}")
|
137
|
+
|
138
|
+
def update_automation(
|
139
|
+
self,
|
140
|
+
processor_id: str,
|
141
|
+
automation_id: str,
|
142
|
+
automation_data: Union[UpdateLinkRequest, UpdateMailboxRequest, UpdateEndpointRequest, UpdateOutlookRequest],
|
143
|
+
) -> Union[Link, Mailbox, Endpoint, Outlook]:
|
144
|
+
"""Update an automation attached to this processor."""
|
145
|
+
request = self.prepare_update(processor_id, automation_id, automation_data)
|
146
|
+
response = self._client._prepared_request(request)
|
147
|
+
|
148
|
+
# Return the appropriate model based on the automation type
|
149
|
+
if response["object"] == "automation.link":
|
150
|
+
return Link.model_validate(response)
|
151
|
+
elif response["object"] == "automation.mailbox":
|
152
|
+
return Mailbox.model_validate(response)
|
153
|
+
elif response["object"] == "automation.endpoint":
|
154
|
+
return Endpoint.model_validate(response)
|
155
|
+
elif response["object"] == "automation.outlook":
|
156
|
+
return Outlook.model_validate(response)
|
157
|
+
else:
|
158
|
+
raise ValueError(f"Unknown automation type: {response.get('object')}")
|
159
|
+
|
160
|
+
def delete_automation(self, processor_id: str, automation_id: str) -> None:
|
161
|
+
"""Delete an automation attached to this processor."""
|
162
|
+
request = self.prepare_delete(processor_id, automation_id)
|
163
|
+
self._client._prepared_request(request)
|
164
|
+
print(f"Automation {automation_id} deleted from processor {processor_id}")
|
165
|
+
|
166
|
+
|
167
|
+
class AsyncAutomations(AsyncAPIResource, AutomationsMixin):
|
168
|
+
"""Async Automations API wrapper"""
|
169
|
+
|
170
|
+
def __init__(self, client: Any) -> None:
|
171
|
+
super().__init__(client=client)
|
172
|
+
self.mailboxes = AsyncMailboxes(client=client)
|
173
|
+
self.links = AsyncLinks(client=client)
|
174
|
+
self.outlook = AsyncOutlooks(client=client)
|
175
|
+
self.endpoints = AsyncEndpoints(client=client)
|
176
|
+
self.tests = AsyncTests(client=client)
|
177
|
+
self.logs = AsyncLogs(client=client)
|
178
|
+
|
179
|
+
async def verify_event(self, event_body: bytes, event_signature: str, secret: str) -> Any:
|
180
|
+
"""
|
181
|
+
Verify the signature of a webhook event.
|
182
|
+
"""
|
183
|
+
return self._verify_event(event_body, event_signature, secret)
|
184
|
+
|
185
|
+
async def list(
|
186
|
+
self,
|
187
|
+
processor_id: str,
|
188
|
+
before: Optional[str] = None,
|
189
|
+
after: Optional[str] = None,
|
190
|
+
limit: Optional[int] = 10,
|
191
|
+
order: Optional[Literal["asc", "desc"]] = "desc",
|
192
|
+
automation_id: Optional[str] = None,
|
193
|
+
webhook_url: Optional[str] = None,
|
194
|
+
name: Optional[str] = None,
|
195
|
+
):
|
196
|
+
"""List automations attached to this processor."""
|
197
|
+
request = self.prepare_list(processor_id, before, after, limit, order, automation_id, webhook_url, name)
|
198
|
+
response = await self._client._prepared_request(request)
|
199
|
+
return response
|
200
|
+
|
201
|
+
async def get(self, processor_id: str, automation_id: str) -> Union[Link, Mailbox, Endpoint, Outlook]:
|
202
|
+
"""Get a specific automation attached to this processor."""
|
203
|
+
request = self.prepare_get(processor_id, automation_id)
|
204
|
+
response = await self._client._prepared_request(request)
|
205
|
+
|
206
|
+
# Return the appropriate model based on the automation type
|
207
|
+
if response["object"] == "automation.link":
|
208
|
+
return Link.model_validate(response)
|
209
|
+
elif response["object"] == "automation.mailbox":
|
210
|
+
return Mailbox.model_validate(response)
|
211
|
+
elif response["object"] == "automation.endpoint":
|
212
|
+
return Endpoint.model_validate(response)
|
213
|
+
elif response["object"] == "automation.outlook":
|
214
|
+
return Outlook.model_validate(response)
|
215
|
+
else:
|
216
|
+
raise ValueError(f"Unknown automation type: {response.get('object')}")
|
217
|
+
|
218
|
+
async def update(
|
219
|
+
self,
|
220
|
+
processor_id: str,
|
221
|
+
automation_id: str,
|
222
|
+
automation_data: Union[UpdateLinkRequest, UpdateMailboxRequest, UpdateEndpointRequest, UpdateOutlookRequest],
|
223
|
+
) -> Union[Link, Mailbox, Endpoint, Outlook]:
|
224
|
+
"""Update an automation attached to this processor."""
|
225
|
+
request = self.prepare_update(processor_id, automation_id, automation_data)
|
226
|
+
response = await self._client._prepared_request(request)
|
227
|
+
|
228
|
+
# Return the appropriate model based on the automation type
|
229
|
+
if response["object"] == "automation.link":
|
230
|
+
return Link.model_validate(response)
|
231
|
+
elif response["object"] == "automation.mailbox":
|
232
|
+
return Mailbox.model_validate(response)
|
233
|
+
elif response["object"] == "automation.endpoint":
|
234
|
+
return Endpoint.model_validate(response)
|
235
|
+
elif response["object"] == "automation.outlook":
|
236
|
+
return Outlook.model_validate(response)
|
237
|
+
else:
|
238
|
+
raise ValueError(f"Unknown automation type: {response.get('object')}")
|
239
|
+
|
240
|
+
async def delete(self, processor_id: str, automation_id: str) -> None:
|
241
|
+
"""Delete an automation attached to this processor."""
|
242
|
+
request = self.prepare_delete(processor_id, automation_id)
|
243
|
+
await self._client._prepared_request(request)
|
244
|
+
print(f"Automation {automation_id} deleted from processor {processor_id}")
|