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.
Files changed (76) hide show
  1. retab/__init__.py +2 -1
  2. retab/client.py +26 -51
  3. retab/generate_types.py +180 -0
  4. retab/resources/consensus/client.py +1 -1
  5. retab/resources/consensus/responses.py +1 -1
  6. retab/resources/deployments/__init__.py +3 -0
  7. retab/resources/deployments/automations/__init__.py +9 -0
  8. retab/resources/deployments/automations/client.py +244 -0
  9. retab/resources/deployments/automations/endpoints.py +290 -0
  10. retab/resources/deployments/automations/links.py +303 -0
  11. retab/resources/deployments/automations/logs.py +222 -0
  12. retab/resources/deployments/automations/mailboxes.py +423 -0
  13. retab/resources/deployments/automations/outlook.py +377 -0
  14. retab/resources/deployments/automations/tests.py +161 -0
  15. retab/resources/deployments/client.py +148 -0
  16. retab/resources/documents/client.py +94 -68
  17. retab/resources/documents/extractions.py +55 -46
  18. retab/resources/evaluations/__init__.py +2 -2
  19. retab/resources/evaluations/client.py +61 -77
  20. retab/resources/evaluations/documents.py +48 -37
  21. retab/resources/evaluations/iterations.py +58 -40
  22. retab/resources/jsonlUtils.py +3 -4
  23. retab/resources/processors/automations/endpoints.py +49 -39
  24. retab/resources/processors/automations/links.py +52 -43
  25. retab/resources/processors/automations/mailboxes.py +74 -59
  26. retab/resources/processors/automations/outlook.py +104 -82
  27. retab/resources/processors/client.py +35 -30
  28. retab/resources/projects/__init__.py +3 -0
  29. retab/resources/projects/client.py +285 -0
  30. retab/resources/projects/documents.py +244 -0
  31. retab/resources/projects/iterations.py +470 -0
  32. retab/resources/usage.py +2 -0
  33. retab/types/ai_models.py +2 -1
  34. retab/types/deprecated_evals.py +195 -0
  35. retab/types/evaluations/__init__.py +5 -2
  36. retab/types/evaluations/iterations.py +9 -43
  37. retab/types/evaluations/model.py +19 -24
  38. retab/types/extractions.py +1 -0
  39. retab/types/jobs/base.py +1 -1
  40. retab/types/jobs/evaluation.py +1 -1
  41. retab/types/logs.py +5 -6
  42. retab/types/mime.py +1 -10
  43. retab/types/projects/__init__.py +34 -0
  44. retab/types/projects/documents.py +30 -0
  45. retab/types/projects/iterations.py +78 -0
  46. retab/types/projects/model.py +68 -0
  47. retab/types/schemas/enhance.py +22 -5
  48. retab/types/schemas/evaluate.py +2 -2
  49. retab/types/schemas/object.py +27 -25
  50. retab/types/standards.py +2 -2
  51. retab/utils/__init__.py +3 -0
  52. retab/utils/ai_models.py +127 -12
  53. retab/utils/hashing.py +24 -0
  54. retab/utils/json_schema.py +1 -26
  55. retab/utils/mime.py +0 -17
  56. retab/utils/usage/usage.py +0 -1
  57. {retab-0.0.42.dist-info → retab-0.0.44.dist-info}/METADATA +4 -6
  58. {retab-0.0.42.dist-info → retab-0.0.44.dist-info}/RECORD +60 -55
  59. retab/_utils/__init__.py +0 -0
  60. retab/_utils/_model_cards/anthropic.yaml +0 -59
  61. retab/_utils/_model_cards/auto.yaml +0 -43
  62. retab/_utils/_model_cards/gemini.yaml +0 -117
  63. retab/_utils/_model_cards/openai.yaml +0 -301
  64. retab/_utils/_model_cards/xai.yaml +0 -28
  65. retab/_utils/ai_models.py +0 -138
  66. retab/_utils/benchmarking.py +0 -484
  67. retab/_utils/chat.py +0 -327
  68. retab/_utils/display.py +0 -440
  69. retab/_utils/json_schema.py +0 -2156
  70. retab/_utils/mime.py +0 -165
  71. retab/_utils/responses.py +0 -169
  72. retab/_utils/stream_context_managers.py +0 -52
  73. retab/_utils/usage/__init__.py +0 -0
  74. retab/_utils/usage/usage.py +0 -301
  75. {retab-0.0.42.dist-info → retab-0.0.44.dist-info}/WHEEL +0 -0
  76. {retab-0.0.42.dist-info → retab-0.0.44.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,377 @@
1
+ from typing import Any, Literal, List
2
+
3
+ from ....types.standards import FieldUnset
4
+
5
+ from ...._resource import AsyncAPIResource, SyncAPIResource
6
+ from ....types.automations.outlook import (
7
+ FetchParams,
8
+ ListOutlooks,
9
+ MatchParams,
10
+ Outlook,
11
+ UpdateOutlookRequest,
12
+ )
13
+ from ....types.standards import PreparedRequest
14
+
15
+
16
+ class OutlooksMixin:
17
+ outlooks_base_url: str = "/v1/processors/automations/outlook"
18
+
19
+ def prepare_create(
20
+ self,
21
+ name: str,
22
+ processor_id: str,
23
+ webhook_url: str,
24
+ default_language: str = FieldUnset,
25
+ webhook_headers: dict[str, str] = FieldUnset,
26
+ need_validation: bool = FieldUnset,
27
+ authorized_domains: list[str] = FieldUnset,
28
+ authorized_emails: list[str] = FieldUnset,
29
+ layout_schema: dict[str, Any] = FieldUnset,
30
+ match_params: list[MatchParams] = FieldUnset,
31
+ fetch_params: list[FetchParams] = FieldUnset,
32
+ ) -> PreparedRequest:
33
+ # Build outlook dictionary with only provided fields
34
+ outlook_dict: dict[str, Any] = {
35
+ 'processor_id': processor_id,
36
+ 'name': name,
37
+ 'webhook_url': webhook_url,
38
+ }
39
+ if default_language is not FieldUnset:
40
+ outlook_dict['default_language'] = default_language
41
+ if webhook_headers is not FieldUnset:
42
+ outlook_dict['webhook_headers'] = webhook_headers
43
+ if need_validation is not FieldUnset:
44
+ outlook_dict['need_validation'] = need_validation
45
+ if authorized_domains is not FieldUnset:
46
+ outlook_dict['authorized_domains'] = authorized_domains
47
+ if authorized_emails is not FieldUnset:
48
+ outlook_dict['authorized_emails'] = authorized_emails
49
+ if layout_schema is not FieldUnset:
50
+ outlook_dict['layout_schema'] = layout_schema
51
+ if match_params is not FieldUnset:
52
+ outlook_dict['match_params'] = match_params
53
+ if fetch_params is not FieldUnset:
54
+ outlook_dict['fetch_params'] = fetch_params
55
+
56
+ # Validate the data
57
+ outlook_data = Outlook(**outlook_dict)
58
+ return PreparedRequest(
59
+ method="POST",
60
+ url=self.outlooks_base_url,
61
+ data=outlook_data.model_dump(mode="json", exclude_unset=True),
62
+ )
63
+
64
+ def prepare_list(
65
+ self,
66
+ processor_id: str,
67
+ before: str | None = None,
68
+ after: str | None = None,
69
+ limit: int = 10,
70
+ order: Literal["asc", "desc"] | None = "desc",
71
+ name: str | None = None,
72
+ webhook_url: str | None = None,
73
+ ) -> PreparedRequest:
74
+ params = {
75
+ "processor_id": processor_id,
76
+ "before": before,
77
+ "after": after,
78
+ "limit": limit,
79
+ "order": order,
80
+ "name": name,
81
+ "webhook_url": webhook_url,
82
+ }
83
+ # Remove None values
84
+ params = {k: v for k, v in params.items() if v is not None}
85
+
86
+ return PreparedRequest(method="GET", url=self.outlooks_base_url, params=params)
87
+
88
+ def prepare_get(self, outlook_id: str) -> PreparedRequest:
89
+ return PreparedRequest(method="GET", url=f"{self.outlooks_base_url}/{outlook_id}")
90
+
91
+ def prepare_update(
92
+ self,
93
+ outlook_id: str,
94
+ name: str = FieldUnset,
95
+ default_language: str = FieldUnset,
96
+ webhook_url: str = FieldUnset,
97
+ webhook_headers: dict[str, str] = FieldUnset,
98
+ need_validation: bool = FieldUnset,
99
+ authorized_domains: list[str] = FieldUnset,
100
+ authorized_emails: list[str] = FieldUnset,
101
+ match_params: list[MatchParams] = FieldUnset,
102
+ fetch_params: list[FetchParams] = FieldUnset,
103
+ layout_schema: dict[str, Any] = FieldUnset,
104
+ ) -> PreparedRequest:
105
+ update_dict: dict[str, Any] = {}
106
+ if name is not FieldUnset:
107
+ update_dict['name'] = name
108
+ if default_language is not FieldUnset:
109
+ update_dict['default_language'] = default_language
110
+ if webhook_url is not FieldUnset:
111
+ update_dict['webhook_url'] = webhook_url
112
+ if webhook_headers is not FieldUnset:
113
+ update_dict['webhook_headers'] = webhook_headers
114
+ if need_validation is not FieldUnset:
115
+ update_dict['need_validation'] = need_validation
116
+ if authorized_domains is not FieldUnset:
117
+ update_dict['authorized_domains'] = authorized_domains
118
+ if authorized_emails is not FieldUnset:
119
+ update_dict['authorized_emails'] = authorized_emails
120
+ if layout_schema is not FieldUnset:
121
+ update_dict['layout_schema'] = layout_schema
122
+ if match_params is not FieldUnset:
123
+ update_dict['match_params'] = match_params
124
+ if fetch_params is not FieldUnset:
125
+ update_dict['fetch_params'] = fetch_params
126
+
127
+ update_outlook_request = UpdateOutlookRequest(**update_dict)
128
+
129
+ return PreparedRequest(
130
+ method="PUT",
131
+ url=f"{self.outlooks_base_url}/{outlook_id}",
132
+ data=update_outlook_request.model_dump(mode="json", exclude_unset=True),
133
+ )
134
+
135
+ def prepare_delete(self, outlook_id: str) -> PreparedRequest:
136
+ return PreparedRequest(method="DELETE", url=f"{self.outlooks_base_url}/{outlook_id}")
137
+
138
+
139
+ class Outlooks(SyncAPIResource, OutlooksMixin):
140
+ """Outlook API wrapper for managing outlook automation configurations"""
141
+
142
+ def __init__(self, client: Any) -> None:
143
+ super().__init__(client=client)
144
+
145
+ def create(
146
+ self,
147
+ name: str,
148
+ processor_id: str,
149
+ webhook_url: str,
150
+ default_language: str = FieldUnset,
151
+ webhook_headers: dict[str, str] = FieldUnset,
152
+ need_validation: bool = FieldUnset,
153
+ authorized_domains: list[str] = FieldUnset,
154
+ authorized_emails: list[str] = FieldUnset,
155
+ layout_schema: dict[str, Any] = FieldUnset,
156
+ match_params: list[MatchParams] = FieldUnset,
157
+ fetch_params: list[FetchParams] = FieldUnset,
158
+ ) -> Outlook:
159
+ """Create a new outlook automation configuration.
160
+
161
+ Args:
162
+ name: Name of the outlook plugin
163
+ processor_id: ID of the processor to use for the automation
164
+ webhook_url: Webhook URL to receive processed data
165
+ webhook_headers: Webhook headers to send with processed data
166
+ authorized_domains: List of authorized domains
167
+ authorized_emails: List of authorized emails
168
+ layout_schema: Layout schema to display the data
169
+ match_params: List of match parameters for the outlook automation
170
+ fetch_params: List of fetch parameters for the outlook automation
171
+ Returns:
172
+ Outlook: The created outlook plugin configuration
173
+ """
174
+
175
+ request = self.prepare_create(
176
+ name=name,
177
+ processor_id=processor_id,
178
+ webhook_url=webhook_url,
179
+ default_language=default_language,
180
+ webhook_headers=webhook_headers,
181
+ need_validation=need_validation,
182
+ authorized_domains=authorized_domains,
183
+ authorized_emails=authorized_emails,
184
+ layout_schema=layout_schema,
185
+ match_params=match_params,
186
+ fetch_params=fetch_params,
187
+ )
188
+ response = self._client._prepared_request(request)
189
+
190
+ print(f"Outlook plugin created. Url: https://www.retab.com/dashboard/processors/automations/{response['id']}")
191
+
192
+ return Outlook.model_validate(response)
193
+
194
+ def list(
195
+ self,
196
+ processor_id: str,
197
+ before: str | None = None,
198
+ after: str | None = None,
199
+ limit: int = 10,
200
+ order: Literal["asc", "desc"] | None = "desc",
201
+ name: str | None = None,
202
+ webhook_url: str | None = None,
203
+ ) -> ListOutlooks:
204
+ """List all outlook automation configurations.
205
+
206
+ Args:
207
+ before: Optional cursor for pagination - get results before this log ID
208
+ after: Optional cursor for pagination - get results after this log ID
209
+ limit: Maximum number of logs to return (1-100, default 10)
210
+ order: Sort order by creation time - "asc" or "desc" (default "desc")
211
+ name: Optional name filter
212
+ webhook_url: Optional webhook URL filter
213
+ Returns:
214
+ List[Outlook]: List of outlook plugin configurations
215
+ """
216
+ request = self.prepare_list(processor_id, before, after, limit, order, name, webhook_url)
217
+ response = self._client._prepared_request(request)
218
+ return ListOutlooks.model_validate(response)
219
+
220
+ def get(self, outlook_id: str) -> Outlook:
221
+ """Get a specific outlook automation configuration.
222
+
223
+ Args:
224
+ id: ID of the outlook plugin
225
+
226
+ Returns:
227
+ Outlook: The outlook plugin configuration
228
+ """
229
+ request = self.prepare_get(outlook_id)
230
+ response = self._client._prepared_request(request)
231
+ return Outlook.model_validate(response)
232
+
233
+ def update(
234
+ self,
235
+ outlook_id: str,
236
+ name: str = FieldUnset,
237
+ default_language: str = FieldUnset,
238
+ webhook_url: str = FieldUnset,
239
+ webhook_headers: dict[str, str] = FieldUnset,
240
+ need_validation: bool = FieldUnset,
241
+ authorized_domains: List[str] = FieldUnset,
242
+ authorized_emails: List[str] = FieldUnset,
243
+ layout_schema: dict[str, Any] = FieldUnset,
244
+ match_params: List[MatchParams] = FieldUnset,
245
+ fetch_params: List[FetchParams] = FieldUnset,
246
+ ) -> Outlook:
247
+ """Update an outlook automation configuration.
248
+
249
+ Args:
250
+ outlook_id: ID of the outlook plugin to update
251
+ name: New name for the outlook plugin
252
+ webhook_url: New webhook URL
253
+ webhook_headers: New webhook headers
254
+ authorized_domains: New authorized domains
255
+ authorized_emails: New authorized emails
256
+ match_params: New match parameters for the outlook automation
257
+ fetch_params: New fetch parameters for the outlook automation
258
+ layout_schema: New layout schema for the outlook automation
259
+
260
+ Returns:
261
+ Outlook: The updated outlook plugin configuration
262
+ """
263
+ request = self.prepare_update(
264
+ outlook_id,
265
+ name=name,
266
+ default_language=default_language,
267
+ webhook_url=webhook_url,
268
+ webhook_headers=webhook_headers,
269
+ need_validation=need_validation,
270
+ authorized_domains=authorized_domains,
271
+ authorized_emails=authorized_emails,
272
+ layout_schema=layout_schema,
273
+ match_params=match_params,
274
+ fetch_params=fetch_params,
275
+ )
276
+ response = self._client._prepared_request(request)
277
+ return Outlook.model_validate(response)
278
+
279
+ def delete(self, outlook_id: str) -> None:
280
+ """Delete an outlook automation configuration.
281
+
282
+ Args:
283
+ outlook_id: ID of the outlook plugin to delete
284
+ """
285
+ request = self.prepare_delete(outlook_id)
286
+ self._client._prepared_request(request)
287
+ return None
288
+
289
+
290
+ class AsyncOutlooks(AsyncAPIResource, OutlooksMixin):
291
+ def __init__(self, client: Any) -> None:
292
+ super().__init__(client=client)
293
+
294
+ async def create(
295
+ self,
296
+ name: str,
297
+ processor_id: str,
298
+ webhook_url: str,
299
+ default_language: str = FieldUnset,
300
+ webhook_headers: dict[str, str] = FieldUnset,
301
+ need_validation: bool = FieldUnset,
302
+ authorized_domains: list[str] = FieldUnset,
303
+ authorized_emails: list[str] = FieldUnset,
304
+ layout_schema: dict[str, Any] = FieldUnset,
305
+ match_params: list[MatchParams] = FieldUnset,
306
+ fetch_params: list[FetchParams] = FieldUnset,
307
+ ) -> Outlook:
308
+ request = self.prepare_create(
309
+ name=name,
310
+ processor_id=processor_id,
311
+ webhook_url=webhook_url,
312
+ default_language=default_language,
313
+ webhook_headers=webhook_headers,
314
+ need_validation=need_validation,
315
+ authorized_domains=authorized_domains,
316
+ authorized_emails=authorized_emails,
317
+ layout_schema=layout_schema,
318
+ match_params=match_params,
319
+ fetch_params=fetch_params,
320
+ )
321
+ response = await self._client._prepared_request(request)
322
+ print(f"Outlook plugin created. Url: https://www.retab.com/dashboard/processors/automations/{response['id']}")
323
+ return Outlook.model_validate(response)
324
+
325
+ async def list(
326
+ self,
327
+ processor_id: str,
328
+ before: str | None = None,
329
+ after: str | None = None,
330
+ limit: int = 10,
331
+ order: Literal["asc", "desc"] | None = "desc",
332
+ name: str | None = None,
333
+ webhook_url: str | None = None,
334
+ ) -> ListOutlooks:
335
+ request = self.prepare_list(processor_id, before, after, limit, order, name, webhook_url)
336
+ response = await self._client._prepared_request(request)
337
+ return ListOutlooks.model_validate(response)
338
+
339
+ async def get(self, outlook_id: str) -> Outlook:
340
+ request = self.prepare_get(outlook_id)
341
+ response = await self._client._prepared_request(request)
342
+ return Outlook.model_validate(response)
343
+
344
+ async def update(
345
+ self,
346
+ outlook_id: str,
347
+ name: str = FieldUnset,
348
+ default_language: str = FieldUnset,
349
+ webhook_url: str = FieldUnset,
350
+ webhook_headers: dict[str, str] = FieldUnset,
351
+ need_validation: bool = FieldUnset,
352
+ authorized_domains: List[str] = FieldUnset,
353
+ authorized_emails: List[str] = FieldUnset,
354
+ layout_schema: dict[str, Any] = FieldUnset,
355
+ match_params: List[MatchParams] = FieldUnset,
356
+ fetch_params: List[FetchParams] = FieldUnset,
357
+ ) -> Outlook:
358
+ request = self.prepare_update(
359
+ outlook_id=outlook_id,
360
+ name=name,
361
+ default_language=default_language,
362
+ webhook_url=webhook_url,
363
+ webhook_headers=webhook_headers,
364
+ need_validation=need_validation,
365
+ authorized_domains=authorized_domains,
366
+ authorized_emails=authorized_emails,
367
+ layout_schema=layout_schema,
368
+ match_params=match_params,
369
+ fetch_params=fetch_params,
370
+ )
371
+ response = await self._client._prepared_request(request)
372
+ return Outlook.model_validate(response)
373
+
374
+ async def delete(self, outlook_id: str) -> None:
375
+ request = self.prepare_delete(outlook_id)
376
+ await self._client._prepared_request(request)
377
+ return None
@@ -0,0 +1,161 @@
1
+ import json
2
+ import base64
3
+ from io import IOBase
4
+ from pathlib import Path
5
+
6
+ from PIL.Image import Image
7
+ from pydantic import HttpUrl
8
+
9
+ from ...._resource import AsyncAPIResource, SyncAPIResource
10
+ from ....utils.mime import prepare_mime_document
11
+ from ....types.logs import AutomationLog
12
+ from ....types.mime import MIMEData
13
+ from ....types.standards import PreparedRequest
14
+
15
+
16
+ class TestsMixin:
17
+ def prepare_upload(self, automation_id: str, document: Path | str | IOBase | HttpUrl | Image | MIMEData) -> PreparedRequest:
18
+ mime_document = prepare_mime_document(document)
19
+
20
+ # Convert MIME document to file upload format (similar to processors client)
21
+ files = {"file": (mime_document.filename, base64.b64decode(mime_document.content), mime_document.mime_type)}
22
+
23
+ # Send as multipart form data with file upload
24
+ return PreparedRequest(method="POST", url=f"/v1/processors/automations/tests/upload/{automation_id}", files=files)
25
+
26
+ def prepare_webhook(self, automation_id: str) -> PreparedRequest:
27
+ return PreparedRequest(method="POST", url=f"/v1/processors/automations/tests/webhook/{automation_id}", data=None)
28
+
29
+ def print_upload_verbose(self, log: AutomationLog) -> None:
30
+ if log.external_request_log:
31
+ print("\nTEST FILE UPLOAD RESULTS:")
32
+ print("\n#########################")
33
+ print(f"Status Code: {log.external_request_log.status_code}")
34
+ print(f"Duration: {log.external_request_log.duration_ms:.2f}ms")
35
+
36
+ if log.external_request_log.error:
37
+ print(f"\nERROR: {log.external_request_log.error}")
38
+
39
+ if log.external_request_log.response_body:
40
+ print("\n--------------")
41
+ print("RESPONSE BODY:")
42
+ print("--------------")
43
+ print(json.dumps(log.external_request_log.response_body, indent=2))
44
+
45
+ if log.external_request_log.response_headers:
46
+ print("\n--------------")
47
+ print("RESPONSE HEADERS:")
48
+ print("--------------")
49
+ print(json.dumps(log.external_request_log.response_headers, indent=2))
50
+
51
+ def print_webhook_verbose(self, log: AutomationLog) -> None:
52
+ if log.external_request_log:
53
+ print("\nTEST WEBHOOK RESULTS:")
54
+ print("\n#########################")
55
+ print(f"Status Code: {log.external_request_log.status_code}")
56
+ print(f"Duration: {log.external_request_log.duration_ms:.2f}ms")
57
+
58
+ if log.external_request_log.error:
59
+ print(f"\nERROR: {log.external_request_log.error}")
60
+
61
+ if log.external_request_log.response_body:
62
+ print("\n--------------")
63
+ print("RESPONSE BODY:")
64
+ print("--------------")
65
+ print(json.dumps(log.external_request_log.response_body, indent=2))
66
+
67
+ if log.external_request_log.response_headers:
68
+ print("\n--------------")
69
+ print("RESPONSE HEADERS:")
70
+ print("--------------")
71
+ print(json.dumps(log.external_request_log.response_headers, indent=2))
72
+
73
+
74
+ class Tests(SyncAPIResource, TestsMixin):
75
+ """Test API wrapper for testing automation configurations"""
76
+
77
+ def upload(self, automation_id: str, document: Path | str | IOBase | HttpUrl | Image | MIMEData, verbose: bool = True) -> AutomationLog:
78
+ """Test endpoint that simulates the complete extraction process with the provided document.
79
+
80
+ Args:
81
+ automation_id: ID of the automation to test
82
+ document: Document to process
83
+ verbose: Whether to print verbose output
84
+
85
+ Returns:
86
+ AutomationLog: The automation log with extraction results
87
+ """
88
+ request = self.prepare_upload(automation_id, document)
89
+ response = self._client._prepared_request(request)
90
+
91
+ log = AutomationLog.model_validate(response)
92
+
93
+ if verbose:
94
+ self.print_upload_verbose(log)
95
+
96
+ return log
97
+
98
+ def webhook(self, automation_id: str, verbose: bool = True) -> AutomationLog:
99
+ """Test endpoint that simulates the complete webhook process with sample data.
100
+
101
+ Args:
102
+ automation_id: ID of the automation to test
103
+ verbose: Whether to print verbose output
104
+
105
+ Returns:
106
+ AutomationLog: The automation log with webhook results
107
+ """
108
+ request = self.prepare_webhook(automation_id)
109
+ response = self._client._prepared_request(request)
110
+
111
+ log = AutomationLog.model_validate(response)
112
+
113
+ if verbose:
114
+ self.print_webhook_verbose(log)
115
+
116
+ return log
117
+
118
+
119
+ class AsyncTests(AsyncAPIResource, TestsMixin):
120
+ """Async Test API wrapper for testing deployment configurations"""
121
+
122
+ async def upload(self, automation_id: str, document: Path | str | IOBase | HttpUrl | Image | MIMEData, verbose: bool = True) -> AutomationLog:
123
+ """Test endpoint that simulates the complete extraction process with the provided document.
124
+
125
+ Args:
126
+ automation_id: ID of the automation to test
127
+ document: Document to process
128
+ verbose: Whether to print verbose output
129
+
130
+ Returns:
131
+ AutomationLog: The automation log with extraction results
132
+ """
133
+ request = self.prepare_upload(automation_id, document)
134
+ response = await self._client._prepared_request(request)
135
+
136
+ log = AutomationLog.model_validate(response)
137
+
138
+ if verbose:
139
+ self.print_upload_verbose(log)
140
+
141
+ return log
142
+
143
+ async def webhook(self, automation_id: str, verbose: bool = True) -> AutomationLog:
144
+ """Test endpoint that simulates the complete webhook process with sample data.
145
+
146
+ Args:
147
+ automation_id: ID of the automation to test
148
+ verbose: Whether to print verbose output
149
+
150
+ Returns:
151
+ AutomationLog: The automation log with webhook results
152
+ """
153
+ request = self.prepare_webhook(automation_id)
154
+ response = await self._client._prepared_request(request)
155
+
156
+ log = AutomationLog.model_validate(response)
157
+
158
+ if verbose:
159
+ self.print_webhook_verbose(log)
160
+
161
+ return log
@@ -0,0 +1,148 @@
1
+ import base64
2
+ from io import IOBase
3
+ from pathlib import Path
4
+ from typing import Any, List
5
+
6
+ import PIL.Image
7
+ from pydantic import HttpUrl
8
+ from ..._resource import AsyncAPIResource, SyncAPIResource
9
+ from ...utils.mime import MIMEData, prepare_mime_document
10
+ from ...types.documents.extractions import RetabParsedChatCompletion
11
+ from ...types.standards import PreparedRequest
12
+
13
+
14
+ class DeploymentsMixin:
15
+ def prepare_submit(
16
+ self,
17
+ project_id: str,
18
+ iteration_id: str,
19
+ document: Path | str | bytes | IOBase | MIMEData | PIL.Image.Image | HttpUrl | None = None,
20
+ documents: list[Path | str | bytes | IOBase | MIMEData | PIL.Image.Image | HttpUrl] | None = None,
21
+ temperature: float | None = None,
22
+ seed: int | None = None,
23
+ store: bool = True,
24
+ ) -> PreparedRequest:
25
+ """Prepare a request to submit documents to a processor.
26
+
27
+ Args:
28
+ project_id: ID of the project
29
+ iteration_id: ID of the iteration
30
+ document: Single document to process (mutually exclusive with documents)
31
+ documents: List of documents to process (mutually exclusive with document)
32
+ temperature: Optional temperature override
33
+ seed: Optional seed for reproducibility
34
+ store: Whether to store the results
35
+
36
+ Returns:
37
+ PreparedRequest: The prepared request
38
+ """
39
+ # Validate that either document or documents is provided, but not both
40
+ if not document and not documents:
41
+ raise ValueError("Either 'document' or 'documents' must be provided")
42
+
43
+ if document and documents:
44
+ raise ValueError("Provide either 'document' (single) or 'documents' (multiple), not both")
45
+
46
+ # Prepare form data parameters
47
+ form_data = {
48
+ "temperature": temperature,
49
+ "seed": seed,
50
+ "store": store,
51
+ }
52
+ # Remove None values
53
+ form_data = {k: v for k, v in form_data.items() if v is not None}
54
+
55
+ # Prepare files for upload
56
+ files = {}
57
+ if document:
58
+ # Convert document to MIMEData if needed
59
+ mime_document = prepare_mime_document(document)
60
+ # Single document upload
61
+ files["document"] = (mime_document.filename, base64.b64decode(mime_document.content), mime_document.mime_type)
62
+ elif documents:
63
+ # Multiple documents upload - httpx supports multiple files with same field name using a list
64
+ files_list = []
65
+ for doc in documents:
66
+ # Convert each document to MIMEData if needed
67
+ mime_doc = prepare_mime_document(doc)
68
+ files_list.append(
69
+ (
70
+ "documents", # field name
71
+ (mime_doc.filename, base64.b64decode(mime_doc.content), mime_doc.mime_type),
72
+ )
73
+ )
74
+ files = files_list
75
+
76
+ url = f"/v1/deployments/{project_id}/{iteration_id}/submit"
77
+
78
+ return PreparedRequest(method="POST", url=url, form_data=form_data, files=files)
79
+
80
+
81
+ class Deployments(SyncAPIResource, DeploymentsMixin):
82
+ """Deployments API wrapper for managing deployment configurations"""
83
+
84
+ def __init__(self, client: Any) -> None:
85
+ super().__init__(client=client)
86
+
87
+ def submit(
88
+ self,
89
+ project_id: str,
90
+ iteration_id: str,
91
+ document: Path | str | bytes | IOBase | MIMEData | PIL.Image.Image | HttpUrl | None = None,
92
+ documents: List[Path | str | bytes | IOBase | MIMEData | PIL.Image.Image | HttpUrl] | None = None,
93
+ temperature: float | None = None,
94
+ seed: int | None = None,
95
+ store: bool = True,
96
+ ) -> RetabParsedChatCompletion:
97
+ """Submit documents to a deployment for processing.
98
+
99
+ Args:
100
+ project_id: ID of the project
101
+ iteration_id: ID of the iteration
102
+ document: Single document to process (mutually exclusive with documents)
103
+ documents: List of documents to process (mutually exclusive with document)
104
+ temperature: Optional temperature override
105
+ seed: Optional seed for reproducibility
106
+ store: Whether to store the results
107
+
108
+ Returns:
109
+ RetabParsedChatCompletion: The processing result
110
+ """
111
+ request = self.prepare_submit(project_id=project_id, iteration_id=iteration_id, document=document, documents=documents, temperature=temperature, seed=seed, store=store)
112
+ response = self._client._prepared_request(request)
113
+ return RetabParsedChatCompletion.model_validate(response)
114
+
115
+
116
+ class AsyncDeployments(AsyncAPIResource, DeploymentsMixin):
117
+ """Async Deployments API wrapper for managing deployment configurations"""
118
+
119
+ def __init__(self, client: Any) -> None:
120
+ super().__init__(client=client)
121
+
122
+ async def submit(
123
+ self,
124
+ project_id: str,
125
+ iteration_id: str,
126
+ document: Path | str | bytes | IOBase | MIMEData | PIL.Image.Image | HttpUrl | None = None,
127
+ documents: List[Path | str | bytes | IOBase | MIMEData | PIL.Image.Image | HttpUrl] | None = None,
128
+ temperature: float | None = None,
129
+ seed: int | None = None,
130
+ store: bool = True,
131
+ ) -> RetabParsedChatCompletion:
132
+ """Submit documents to a deployment for processing.
133
+
134
+ Args:
135
+ project_id: ID of the project
136
+ iteration_id: ID of the iteration
137
+ document: Single document to process (mutually exclusive with documents)
138
+ documents: List of documents to process (mutually exclusive with document)
139
+ temperature: Optional temperature override
140
+ seed: Optional seed for reproducibility
141
+ store: Whether to store the results
142
+
143
+ Returns:
144
+ RetabParsedChatCompletion: The processing result
145
+ """
146
+ request = self.prepare_submit(project_id=project_id, iteration_id=iteration_id, document=document, documents=documents, temperature=temperature, seed=seed, store=store)
147
+ response = await self._client._prepared_request(request)
148
+ return RetabParsedChatCompletion.model_validate(response)