byteit 1.2.0__tar.gz → 1.3.0__tar.gz
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.
- {byteit-1.2.0 → byteit-1.3.0}/CHANGELOG.md +10 -0
- {byteit-1.2.0/byteit.egg-info → byteit-1.3.0}/PKG-INFO +3 -3
- {byteit-1.2.0 → byteit-1.3.0}/README.md +2 -2
- {byteit-1.2.0 → byteit-1.3.0}/byteit/ByteITClient.py +2 -2
- {byteit-1.2.0 → byteit-1.3.0}/byteit/__init__.py +3 -3
- byteit-1.3.0/byteit/models/ParseType.py +11 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/ProcessingOptions.py +18 -18
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/__init__.py +2 -3
- {byteit-1.2.0 → byteit-1.3.0}/byteit/validations.py +1 -1
- {byteit-1.2.0 → byteit-1.3.0/byteit.egg-info}/PKG-INFO +3 -3
- {byteit-1.2.0 → byteit-1.3.0}/byteit.egg-info/SOURCES.txt +9 -9
- {byteit-1.2.0 → byteit-1.3.0}/pyproject.toml +1 -1
- {byteit-1.2.0/tests → byteit-1.3.0/tests/unit}/test_client.py +32 -54
- {byteit-1.2.0/tests → byteit-1.3.0/tests/unit}/test_models.py +18 -12
- byteit-1.2.0/byteit/models/ExtractionType.py +0 -10
- {byteit-1.2.0 → byteit-1.3.0}/LICENSE +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/MANIFEST.in +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/_http.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/_polling.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/_rate_limit.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/connectors/LocalFileInputConnector.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/connectors/LocalFileOutputConnector.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/connectors/S3InputConnector.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/connectors/S3OutputConnector.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/connectors/__init__.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/connectors/base.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/exceptions.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/CustomJob.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/CustomJobList.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/DocumentMetadata.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/DocumentType.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/ExtractJob.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/ExtractJobList.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/ExtractionSchema.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/JobList.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/JobStatus.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/OutputFormat.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/ParseJob.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/SavedSchema.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/models/SavedSchemaList.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit/progress.py +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit.egg-info/dependency_links.txt +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit.egg-info/requires.txt +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/byteit.egg-info/top_level.txt +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/setup.cfg +0 -0
- {byteit-1.2.0 → byteit-1.3.0}/setup.py +0 -0
- {byteit-1.2.0/tests → byteit-1.3.0/tests/unit}/__init__.py +0 -0
- {byteit-1.2.0/tests → byteit-1.3.0/tests/unit}/test_connectors.py +0 -0
- {byteit-1.2.0/tests → byteit-1.3.0/tests/unit}/test_exceptions.py +0 -0
- {byteit-1.2.0/tests → byteit-1.3.0/tests/unit}/test_integration.py +0 -0
- {byteit-1.2.0/tests → byteit-1.3.0/tests/unit}/test_package_init.py +0 -0
- {byteit-1.2.0/tests → byteit-1.3.0/tests/unit}/test_progress.py +0 -0
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.3.0] - 2026-07-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `ParseType.HANDWRITTEN` (`"handwritten"`) parse mode for handwritten documents.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Renamed `ExtractionType` to `ParseType`.
|
|
17
|
+
- Renamed `ProcessingOptions.extraction_type` to `ProcessingOptions.parse_type`
|
|
18
|
+
(API payload key is now `parse_type`).
|
|
19
|
+
|
|
10
20
|
## [1.2.0] - 2026-07-28
|
|
11
21
|
|
|
12
22
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: byteit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: AI-powered document intelligence platform - Turn your data into structured data with a single line of code.
|
|
5
5
|
Author-email: ByteIT GmbH <support@byteit.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -302,7 +302,7 @@ result = client.parse(
|
|
|
302
302
|
processing_options={
|
|
303
303
|
"languages": ["de", "en"],
|
|
304
304
|
"page_range": "1-5",
|
|
305
|
-
"
|
|
305
|
+
"parse_type": "complex",
|
|
306
306
|
},
|
|
307
307
|
)
|
|
308
308
|
```
|
|
@@ -313,7 +313,7 @@ Supported fields:
|
|
|
313
313
|
|---|---|---|---|
|
|
314
314
|
| `languages` | `list[str]` | `["en"]` | Language codes for OCR/parsing |
|
|
315
315
|
| `page_range` | `str` | `""` (all pages) | Pages to process, e.g. `"1-5"` or `"1,3,5"` |
|
|
316
|
-
| `
|
|
316
|
+
| `parse_type` | `str` | `"auto"` | One of `"auto"`, `"complex"`, `"handwritten"` |
|
|
317
317
|
| `image_annotations` | `bool` | `False` | Enable image annotation extraction |
|
|
318
318
|
| `force_image_annotations` | `bool` | `False` | Force annotation extraction even when the image is detected as useless |
|
|
319
319
|
| `table_enrichment` | `bool` | `False` | Enable table enrichment |
|
|
@@ -264,7 +264,7 @@ result = client.parse(
|
|
|
264
264
|
processing_options={
|
|
265
265
|
"languages": ["de", "en"],
|
|
266
266
|
"page_range": "1-5",
|
|
267
|
-
"
|
|
267
|
+
"parse_type": "complex",
|
|
268
268
|
},
|
|
269
269
|
)
|
|
270
270
|
```
|
|
@@ -275,7 +275,7 @@ Supported fields:
|
|
|
275
275
|
|---|---|---|---|
|
|
276
276
|
| `languages` | `list[str]` | `["en"]` | Language codes for OCR/parsing |
|
|
277
277
|
| `page_range` | `str` | `""` (all pages) | Pages to process, e.g. `"1-5"` or `"1,3,5"` |
|
|
278
|
-
| `
|
|
278
|
+
| `parse_type` | `str` | `"auto"` | One of `"auto"`, `"complex"`, `"handwritten"` |
|
|
279
279
|
| `image_annotations` | `bool` | `False` | Enable image annotation extraction |
|
|
280
280
|
| `force_image_annotations` | `bool` | `False` | Force annotation extraction even when the image is detected as useless |
|
|
281
281
|
| `table_enrichment` | `bool` | `False` | Enable table enrichment |
|
|
@@ -160,7 +160,7 @@ class ByteITClient:
|
|
|
160
160
|
output: Optional file path to save the result to disk.
|
|
161
161
|
processing_options: ProcessingOptions or dict with keys:
|
|
162
162
|
``languages`` (list[str]), ``page_range`` (str), and
|
|
163
|
-
``
|
|
163
|
+
``parse_type`` (str or ParseType).
|
|
164
164
|
``image_annotations`` (bool), ``force_image_annotations`` (bool),
|
|
165
165
|
``table_enrichment`` (bool)
|
|
166
166
|
output_format: Optional output format override. When omitted, the
|
|
@@ -230,7 +230,7 @@ class ByteITClient:
|
|
|
230
230
|
paths.
|
|
231
231
|
processing_options: ProcessingOptions or dict with keys:
|
|
232
232
|
``languages`` (list[str]), ``page_range`` (str), and
|
|
233
|
-
``
|
|
233
|
+
``parse_type`` (str or ParseType).
|
|
234
234
|
queue_for_batch: When ``True``, the job is queued for batch
|
|
235
235
|
processing at a reduced credit cost. Processing is not
|
|
236
236
|
immediate.
|
|
@@ -28,13 +28,13 @@ from .models.CustomJob import CustomJob
|
|
|
28
28
|
from .models.CustomJobList import CustomJobList
|
|
29
29
|
from .models.DocumentMetadata import DocumentMetadata
|
|
30
30
|
from .models.DocumentType import DocumentType
|
|
31
|
-
from .models.ExtractionType import ExtractionType
|
|
32
31
|
from .models.ExtractJob import ExtractJob
|
|
33
32
|
from .models.ExtractJobList import ExtractJobList
|
|
34
33
|
from .models.JobList import JobList
|
|
35
34
|
from .models.JobStatus import JobStatus
|
|
36
35
|
from .models.OutputFormat import OutputFormat
|
|
37
36
|
from .models.ParseJob import ParseJob
|
|
37
|
+
from .models.ParseType import ParseType
|
|
38
38
|
from .models.ProcessingOptions import ProcessingOptions
|
|
39
39
|
from .models.SavedSchema import SavedSchema
|
|
40
40
|
from .models.SavedSchemaList import SavedSchemaList
|
|
@@ -49,7 +49,7 @@ except ImportError:
|
|
|
49
49
|
try:
|
|
50
50
|
__version__ = version("byteit")
|
|
51
51
|
except PackageNotFoundError:
|
|
52
|
-
__version__ = "1.
|
|
52
|
+
__version__ = "1.3.0" # fallback, keep in sync with pyproject.toml
|
|
53
53
|
|
|
54
54
|
try:
|
|
55
55
|
from .models.ExtractionSchema import ExtractionSchema # noqa: F401
|
|
@@ -104,7 +104,7 @@ __all__ = [
|
|
|
104
104
|
"DocumentMetadata",
|
|
105
105
|
"DocumentType",
|
|
106
106
|
"ProcessingOptions",
|
|
107
|
-
"
|
|
107
|
+
"ParseType",
|
|
108
108
|
"ExtractJob",
|
|
109
109
|
"ExtractJobList",
|
|
110
110
|
"OutputFormat",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from byteit.models.
|
|
6
|
+
from byteit.models.ParseType import ParseType
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def _default_list() -> list[str]:
|
|
@@ -24,7 +24,7 @@ class ProcessingOptions:
|
|
|
24
24
|
force_image_annotations: Force image annotation extraction even when
|
|
25
25
|
image is detected as useless
|
|
26
26
|
table_enrichment: Enable table enrichment
|
|
27
|
-
|
|
27
|
+
parse_type: Parse mode used by the backend parser
|
|
28
28
|
"""
|
|
29
29
|
|
|
30
30
|
languages: list[str] = field(default_factory=_default_list)
|
|
@@ -32,26 +32,26 @@ class ProcessingOptions:
|
|
|
32
32
|
image_annotations: bool = field(default=False)
|
|
33
33
|
force_image_annotations: bool = field(default=False)
|
|
34
34
|
table_enrichment: bool = field(default=False)
|
|
35
|
-
|
|
35
|
+
parse_type: ParseType | str = field(default=ParseType.AUTO)
|
|
36
36
|
|
|
37
37
|
@staticmethod
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
) ->
|
|
41
|
-
"""Parse
|
|
42
|
-
if isinstance(
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
for value in
|
|
47
|
-
if value.value ==
|
|
38
|
+
def _parse_parse_type(
|
|
39
|
+
parse_type: ParseType | str,
|
|
40
|
+
) -> ParseType:
|
|
41
|
+
"""Parse parse type into an enum value."""
|
|
42
|
+
if isinstance(parse_type, ParseType):
|
|
43
|
+
return parse_type
|
|
44
|
+
|
|
45
|
+
normalized_parse_type = parse_type.lower()
|
|
46
|
+
for value in ParseType:
|
|
47
|
+
if value.value == normalized_parse_type:
|
|
48
48
|
return value
|
|
49
49
|
|
|
50
|
-
raise ValueError(f"Invalid
|
|
50
|
+
raise ValueError(f"Invalid parse type: {parse_type}")
|
|
51
51
|
|
|
52
52
|
def __post_init__(self) -> None:
|
|
53
53
|
"""Normalize processing option values after initialization."""
|
|
54
|
-
self.
|
|
54
|
+
self.parse_type = self._parse_parse_type(self.parse_type)
|
|
55
55
|
|
|
56
56
|
if self.force_image_annotations:
|
|
57
57
|
self.image_annotations = True
|
|
@@ -79,7 +79,7 @@ class ProcessingOptions:
|
|
|
79
79
|
if self.table_enrichment:
|
|
80
80
|
result["table_enrichment"] = self.table_enrichment
|
|
81
81
|
|
|
82
|
-
result["
|
|
82
|
+
result["parse_type"] = self.parse_type.value
|
|
83
83
|
|
|
84
84
|
return result
|
|
85
85
|
|
|
@@ -98,7 +98,7 @@ class ProcessingOptions:
|
|
|
98
98
|
image_annotations = data.get("image_annotations", False)
|
|
99
99
|
force_image_annotations = data.get("force_image_annotations", False)
|
|
100
100
|
table_enrichment = data.get("table_enrichment", False)
|
|
101
|
-
|
|
101
|
+
parse_type = data.get("parse_type", ParseType.AUTO)
|
|
102
102
|
|
|
103
103
|
return cls(
|
|
104
104
|
languages=languages,
|
|
@@ -106,5 +106,5 @@ class ProcessingOptions:
|
|
|
106
106
|
image_annotations=image_annotations,
|
|
107
107
|
force_image_annotations=force_image_annotations,
|
|
108
108
|
table_enrichment=table_enrichment,
|
|
109
|
-
|
|
109
|
+
parse_type=parse_type,
|
|
110
110
|
)
|
|
@@ -4,13 +4,13 @@ from .CustomJob import CustomJob
|
|
|
4
4
|
from .CustomJobList import CustomJobList
|
|
5
5
|
from .DocumentMetadata import DocumentMetadata
|
|
6
6
|
from .DocumentType import DocumentType
|
|
7
|
-
from .ExtractionType import ExtractionType
|
|
8
7
|
from .ExtractJob import ExtractJob
|
|
9
8
|
from .ExtractJobList import ExtractJobList
|
|
10
9
|
from .JobList import JobList
|
|
11
10
|
from .JobStatus import JobStatus
|
|
12
11
|
from .OutputFormat import OutputFormat
|
|
13
12
|
from .ParseJob import ParseJob
|
|
13
|
+
from .ParseType import ParseType
|
|
14
14
|
from .ProcessingOptions import ProcessingOptions
|
|
15
15
|
from .SavedSchema import SavedSchema
|
|
16
16
|
from .SavedSchemaList import SavedSchemaList
|
|
@@ -22,12 +22,11 @@ __all__ = [
|
|
|
22
22
|
"DocumentType",
|
|
23
23
|
"ExtractJob",
|
|
24
24
|
"ExtractJobList",
|
|
25
|
-
"ExtractionType",
|
|
26
|
-
"ExtractionType",
|
|
27
25
|
"JobList",
|
|
28
26
|
"JobStatus",
|
|
29
27
|
"OutputFormat",
|
|
30
28
|
"ParseJob",
|
|
29
|
+
"ParseType",
|
|
31
30
|
"ProcessingOptions",
|
|
32
31
|
"SavedSchema",
|
|
33
32
|
"SavedSchemaList",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: byteit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: AI-powered document intelligence platform - Turn your data into structured data with a single line of code.
|
|
5
5
|
Author-email: ByteIT GmbH <support@byteit.ai>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -302,7 +302,7 @@ result = client.parse(
|
|
|
302
302
|
processing_options={
|
|
303
303
|
"languages": ["de", "en"],
|
|
304
304
|
"page_range": "1-5",
|
|
305
|
-
"
|
|
305
|
+
"parse_type": "complex",
|
|
306
306
|
},
|
|
307
307
|
)
|
|
308
308
|
```
|
|
@@ -313,7 +313,7 @@ Supported fields:
|
|
|
313
313
|
|---|---|---|---|
|
|
314
314
|
| `languages` | `list[str]` | `["en"]` | Language codes for OCR/parsing |
|
|
315
315
|
| `page_range` | `str` | `""` (all pages) | Pages to process, e.g. `"1-5"` or `"1,3,5"` |
|
|
316
|
-
| `
|
|
316
|
+
| `parse_type` | `str` | `"auto"` | One of `"auto"`, `"complex"`, `"handwritten"` |
|
|
317
317
|
| `image_annotations` | `bool` | `False` | Enable image annotation extraction |
|
|
318
318
|
| `force_image_annotations` | `bool` | `False` | Force annotation extraction even when the image is detected as useless |
|
|
319
319
|
| `table_enrichment` | `bool` | `False` | Enable table enrichment |
|
|
@@ -30,20 +30,20 @@ byteit/models/DocumentType.py
|
|
|
30
30
|
byteit/models/ExtractJob.py
|
|
31
31
|
byteit/models/ExtractJobList.py
|
|
32
32
|
byteit/models/ExtractionSchema.py
|
|
33
|
-
byteit/models/ExtractionType.py
|
|
34
33
|
byteit/models/JobList.py
|
|
35
34
|
byteit/models/JobStatus.py
|
|
36
35
|
byteit/models/OutputFormat.py
|
|
37
36
|
byteit/models/ParseJob.py
|
|
37
|
+
byteit/models/ParseType.py
|
|
38
38
|
byteit/models/ProcessingOptions.py
|
|
39
39
|
byteit/models/SavedSchema.py
|
|
40
40
|
byteit/models/SavedSchemaList.py
|
|
41
41
|
byteit/models/__init__.py
|
|
42
|
-
tests/__init__.py
|
|
43
|
-
tests/test_client.py
|
|
44
|
-
tests/test_connectors.py
|
|
45
|
-
tests/test_exceptions.py
|
|
46
|
-
tests/test_integration.py
|
|
47
|
-
tests/test_models.py
|
|
48
|
-
tests/test_package_init.py
|
|
49
|
-
tests/test_progress.py
|
|
42
|
+
tests/unit/__init__.py
|
|
43
|
+
tests/unit/test_client.py
|
|
44
|
+
tests/unit/test_connectors.py
|
|
45
|
+
tests/unit/test_exceptions.py
|
|
46
|
+
tests/unit/test_integration.py
|
|
47
|
+
tests/unit/test_models.py
|
|
48
|
+
tests/unit/test_package_init.py
|
|
49
|
+
tests/unit/test_progress.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "byteit"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "AI-powered document intelligence platform - Turn your data into structured data with a single line of code."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -9,6 +9,7 @@ import requests
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
11
11
|
from byteit import ByteITClient
|
|
12
|
+
from byteit._rate_limit import RateLimitedSubmitter
|
|
12
13
|
from byteit.connectors import LocalFileInputConnector
|
|
13
14
|
from byteit.exceptions import (
|
|
14
15
|
APIKeyError,
|
|
@@ -20,10 +21,10 @@ from byteit.exceptions import (
|
|
|
20
21
|
ValidationError,
|
|
21
22
|
)
|
|
22
23
|
from byteit.models.ExtractionSchema import ExtractionSchema
|
|
23
|
-
from byteit.models.ExtractionType import ExtractionType
|
|
24
24
|
from byteit.models.ExtractJob import ExtractJob
|
|
25
25
|
from byteit.models.OutputFormat import OutputFormat
|
|
26
26
|
from byteit.models.ParseJob import ParseJob
|
|
27
|
+
from byteit.models.ParseType import ParseType
|
|
27
28
|
from byteit.models.ProcessingOptions import ProcessingOptions
|
|
28
29
|
from byteit.models.SavedSchema import SavedSchema
|
|
29
30
|
from byteit.models.SavedSchemaList import SavedSchemaList
|
|
@@ -289,7 +290,7 @@ class TestDownloadParseResult:
|
|
|
289
290
|
class TestCreateJob:
|
|
290
291
|
"""Test _create_job method."""
|
|
291
292
|
|
|
292
|
-
@patch.object(
|
|
293
|
+
@patch.object(RateLimitedSubmitter, "_request")
|
|
293
294
|
@patch.object(ByteITClient, "_get_parse_job_details")
|
|
294
295
|
def test_create_job_with_local_file(self, mock_get_status, mock_request):
|
|
295
296
|
"""Create job with local file uploads correctly."""
|
|
@@ -305,13 +306,13 @@ class TestCreateJob:
|
|
|
305
306
|
output_connector = Mock()
|
|
306
307
|
output_connector.to_dict.return_value = {"type": "localfile"}
|
|
307
308
|
|
|
308
|
-
result = client._create_job(connector, output_connector
|
|
309
|
+
result = client._create_job(connector, output_connector)
|
|
309
310
|
|
|
310
311
|
assert result == mock_job
|
|
311
312
|
mock_request.assert_called_once()
|
|
312
313
|
mock_get_status.assert_called_once_with("job_123")
|
|
313
314
|
|
|
314
|
-
@patch.object(
|
|
315
|
+
@patch.object(RateLimitedSubmitter, "_request")
|
|
315
316
|
def test_create_job_with_s3(self, mock_request):
|
|
316
317
|
"""Create job with S3 connector."""
|
|
317
318
|
client = ByteITClient("test_key")
|
|
@@ -336,44 +337,24 @@ class TestCreateJob:
|
|
|
336
337
|
output_connector = Mock()
|
|
337
338
|
output_connector.to_dict.return_value = {"type": "localfile"}
|
|
338
339
|
|
|
339
|
-
result = client._create_job(connector, output_connector
|
|
340
|
+
result = client._create_job(connector, output_connector)
|
|
340
341
|
|
|
341
342
|
assert isinstance(result, ParseJob)
|
|
342
343
|
assert result.id == "job_123"
|
|
343
344
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
"""Serialize Excel output format as zip in the job creation payload."""
|
|
345
|
+
def test_create_job_serializes_excel_format_as_zip(self):
|
|
346
|
+
"""Serialize Excel output format as zip in the job payload."""
|
|
347
347
|
client = ByteITClient("test_key")
|
|
348
|
-
mock_request.return_value = {
|
|
349
|
-
"job": {
|
|
350
|
-
"id": "job_excel",
|
|
351
|
-
"created_at": "2024-01-01T00:00:00Z",
|
|
352
|
-
"updated_at": "2024-01-01T00:00:00Z",
|
|
353
|
-
"processing_status": "pending",
|
|
354
|
-
"result_format": "zip",
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
file_obj = Mock()
|
|
359
|
-
file_obj.closed = False
|
|
360
|
-
|
|
361
|
-
connector = Mock()
|
|
362
|
-
connector.to_dict.return_value = {"type": "localfile"}
|
|
363
|
-
connector.get_file_data.return_value = ("test.pdf", file_obj)
|
|
364
348
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
349
|
+
data = client._build_localfile_job_data(
|
|
350
|
+
processing_options=None,
|
|
351
|
+
result_format=OutputFormat.EXCEL,
|
|
352
|
+
queue_for_batch=False,
|
|
353
|
+
)
|
|
369
354
|
|
|
370
|
-
assert
|
|
371
|
-
assert result.result_format == "zip"
|
|
372
|
-
request_kwargs = mock_request.call_args.kwargs
|
|
373
|
-
assert request_kwargs["data"]["output_format"] == "zip"
|
|
374
|
-
file_obj.close.assert_called_once()
|
|
355
|
+
assert data["output_format"] == "zip"
|
|
375
356
|
|
|
376
|
-
@patch.object(
|
|
357
|
+
@patch.object(RateLimitedSubmitter, "_request")
|
|
377
358
|
@patch.object(ByteITClient, "_get_parse_job_details")
|
|
378
359
|
def test_create_job_uses_parse_jobs_collection_path(
|
|
379
360
|
self, mock_get_status, mock_request
|
|
@@ -390,14 +371,14 @@ class TestCreateJob:
|
|
|
390
371
|
output_connector = Mock()
|
|
391
372
|
output_connector.to_dict.return_value = {"type": "localfile"}
|
|
392
373
|
|
|
393
|
-
client._create_job(connector, output_connector
|
|
374
|
+
client._create_job(connector, output_connector)
|
|
394
375
|
|
|
395
376
|
assert mock_request.call_args.args[:2] == (
|
|
396
377
|
"POST",
|
|
397
378
|
"/v1/jobs/parse-jobs/",
|
|
398
379
|
)
|
|
399
380
|
|
|
400
|
-
@patch.object(
|
|
381
|
+
@patch.object(RateLimitedSubmitter, "_request")
|
|
401
382
|
@patch.object(ByteITClient, "_get_parse_job_details")
|
|
402
383
|
def test_create_job_sends_queue_for_batch_when_true(
|
|
403
384
|
self, mock_get_status, mock_request
|
|
@@ -417,14 +398,14 @@ class TestCreateJob:
|
|
|
417
398
|
client._create_job(
|
|
418
399
|
connector,
|
|
419
400
|
output_connector,
|
|
420
|
-
|
|
401
|
+
None,
|
|
421
402
|
queue_for_batch=True,
|
|
422
403
|
)
|
|
423
404
|
|
|
424
405
|
request_kwargs = mock_request.call_args.kwargs
|
|
425
406
|
assert request_kwargs["data"]["queue_for_batch"] == "true"
|
|
426
407
|
|
|
427
|
-
@patch.object(
|
|
408
|
+
@patch.object(RateLimitedSubmitter, "_request")
|
|
428
409
|
@patch.object(ByteITClient, "_get_parse_job_details")
|
|
429
410
|
def test_create_job_omits_queue_for_batch_by_default(
|
|
430
411
|
self, mock_get_status, mock_request
|
|
@@ -441,7 +422,7 @@ class TestCreateJob:
|
|
|
441
422
|
output_connector = Mock()
|
|
442
423
|
output_connector.to_dict.return_value = {"type": "localfile"}
|
|
443
424
|
|
|
444
|
-
client._create_job(connector, output_connector
|
|
425
|
+
client._create_job(connector, output_connector)
|
|
445
426
|
|
|
446
427
|
request_kwargs = mock_request.call_args.kwargs
|
|
447
428
|
assert "queue_for_batch" not in request_kwargs["data"]
|
|
@@ -451,7 +432,7 @@ class TestCreateJobRateLimitHandling:
|
|
|
451
432
|
"""Test adaptive rate-limit handling during parse job submission."""
|
|
452
433
|
|
|
453
434
|
@patch("time.sleep")
|
|
454
|
-
@patch.object(
|
|
435
|
+
@patch.object(RateLimitedSubmitter, "_request")
|
|
455
436
|
@patch.object(ByteITClient, "_get_parse_job_details")
|
|
456
437
|
def test_create_job_retries_after_rate_limit(
|
|
457
438
|
self, mock_get_status, mock_request, mock_sleep
|
|
@@ -471,14 +452,14 @@ class TestCreateJobRateLimitHandling:
|
|
|
471
452
|
output_connector = Mock()
|
|
472
453
|
output_connector.to_dict.return_value = {"type": "localfile"}
|
|
473
454
|
|
|
474
|
-
client._create_job(connector, output_connector
|
|
455
|
+
client._create_job(connector, output_connector)
|
|
475
456
|
|
|
476
457
|
assert mock_request.call_count == 2
|
|
477
458
|
mock_sleep.assert_called_once_with(2.0)
|
|
478
|
-
assert client._submission_delay == 1.0
|
|
459
|
+
assert client._rate_limiter._submission_delay == 1.0
|
|
479
460
|
|
|
480
461
|
@patch("time.sleep")
|
|
481
|
-
@patch.object(
|
|
462
|
+
@patch.object(RateLimitedSubmitter, "_request")
|
|
482
463
|
@patch.object(ByteITClient, "_get_parse_job_details")
|
|
483
464
|
def test_create_job_spaces_subsequent_submissions_after_rate_limit(
|
|
484
465
|
self, mock_get_status, mock_request, mock_sleep
|
|
@@ -495,16 +476,16 @@ class TestCreateJobRateLimitHandling:
|
|
|
495
476
|
output_connector = Mock()
|
|
496
477
|
output_connector.to_dict.return_value = {"type": "localfile"}
|
|
497
478
|
|
|
498
|
-
client._submission_delay = 2.0
|
|
499
|
-
client._last_submission_at = 100.0
|
|
479
|
+
client._rate_limiter._submission_delay = 2.0
|
|
480
|
+
client._rate_limiter._last_submission_at = 100.0
|
|
500
481
|
|
|
501
482
|
with patch("time.monotonic", side_effect=[101.0, 102.5]):
|
|
502
|
-
client._create_job(connector, output_connector
|
|
483
|
+
client._create_job(connector, output_connector)
|
|
503
484
|
|
|
504
485
|
mock_sleep.assert_called_once_with(1.0)
|
|
505
486
|
|
|
506
487
|
@patch("time.sleep")
|
|
507
|
-
@patch.object(
|
|
488
|
+
@patch.object(RateLimitedSubmitter, "_request")
|
|
508
489
|
def test_create_job_raises_after_max_rate_limit_retries(
|
|
509
490
|
self, mock_request, mock_sleep
|
|
510
491
|
):
|
|
@@ -522,7 +503,7 @@ class TestCreateJobRateLimitHandling:
|
|
|
522
503
|
output_connector.to_dict.return_value = {"type": "localfile"}
|
|
523
504
|
|
|
524
505
|
with pytest.raises(RateLimitError, match="Too many requests"):
|
|
525
|
-
client._create_job(connector, output_connector
|
|
506
|
+
client._create_job(connector, output_connector)
|
|
526
507
|
|
|
527
508
|
assert mock_request.call_count == 3
|
|
528
509
|
assert mock_sleep.call_count == 2
|
|
@@ -691,7 +672,7 @@ class TestParse:
|
|
|
691
672
|
mock_wait.assert_called_once_with(
|
|
692
673
|
"job_123", input_connector=mock_connector, job=mock_job
|
|
693
674
|
)
|
|
694
|
-
mock_download.assert_called_once_with("job_123")
|
|
675
|
+
mock_download.assert_called_once_with("job_123", result_format=None)
|
|
695
676
|
|
|
696
677
|
@patch.object(ByteITClient, "_download_parse_result")
|
|
697
678
|
@patch.object(ByteITClient, "_wait_for_completion")
|
|
@@ -854,7 +835,6 @@ class TestSubmitJob:
|
|
|
854
835
|
input_connector=mock_input_conn,
|
|
855
836
|
output_connector=mock_output_conn,
|
|
856
837
|
processing_options=None,
|
|
857
|
-
result_format=OutputFormat.JSON,
|
|
858
838
|
queue_for_batch=False,
|
|
859
839
|
)
|
|
860
840
|
|
|
@@ -879,14 +859,12 @@ class TestSubmitJob:
|
|
|
879
859
|
|
|
880
860
|
client._submit_job(
|
|
881
861
|
"test.pdf",
|
|
882
|
-
{"
|
|
862
|
+
{"parse_type": "complex"},
|
|
883
863
|
OutputFormat.JSON,
|
|
884
864
|
)
|
|
885
865
|
|
|
886
866
|
call_kwargs = mock_create.call_args[1]
|
|
887
|
-
assert call_kwargs["processing_options"].
|
|
888
|
-
ExtractionType.COMPLEX
|
|
889
|
-
)
|
|
867
|
+
assert call_kwargs["processing_options"].parse_type is (ParseType.COMPLEX)
|
|
890
868
|
|
|
891
869
|
@patch.object(ByteITClient, "_create_job")
|
|
892
870
|
@patch.object(ByteITClient, "_to_output_connector")
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
5
|
from byteit.models.DocumentMetadata import DocumentMetadata
|
|
6
|
-
from byteit.models.ExtractionType import ExtractionType
|
|
7
6
|
from byteit.models.JobList import JobList
|
|
8
7
|
from byteit.models.JobStatus import JobStatus
|
|
9
8
|
from byteit.models.ParseJob import ParseJob
|
|
9
|
+
from byteit.models.ParseType import ParseType
|
|
10
10
|
from byteit.models.ProcessingOptions import ProcessingOptions
|
|
11
11
|
from byteit.models.SavedSchema import SavedSchema
|
|
12
12
|
from byteit.models.SavedSchemaList import SavedSchemaList
|
|
@@ -164,27 +164,27 @@ class TestProcessingOptions:
|
|
|
164
164
|
|
|
165
165
|
assert options.languages == ["en"]
|
|
166
166
|
assert options.page_range == ""
|
|
167
|
-
assert options.
|
|
167
|
+
assert options.parse_type is ParseType.AUTO
|
|
168
168
|
|
|
169
169
|
def test_to_dict(self):
|
|
170
170
|
"""to_dict serializes options."""
|
|
171
171
|
options = ProcessingOptions(
|
|
172
172
|
languages=["en", "es"],
|
|
173
173
|
page_range="1-5",
|
|
174
|
-
|
|
174
|
+
parse_type=ParseType.COMPLEX,
|
|
175
175
|
)
|
|
176
176
|
|
|
177
177
|
result = options.to_dict()
|
|
178
178
|
|
|
179
179
|
assert result["languages"] == ["en", "es"]
|
|
180
180
|
assert result["page_range"] == "1-5"
|
|
181
|
-
assert result["
|
|
181
|
+
assert result["parse_type"] == "complex"
|
|
182
182
|
|
|
183
|
-
def
|
|
184
|
-
"""from_dict converts
|
|
185
|
-
options = ProcessingOptions.from_dict({"
|
|
183
|
+
def test_from_dict_parses_parse_type(self):
|
|
184
|
+
"""from_dict converts parse_type strings into enums."""
|
|
185
|
+
options = ProcessingOptions.from_dict({"parse_type": "complex"})
|
|
186
186
|
|
|
187
|
-
assert options.
|
|
187
|
+
assert options.parse_type is ParseType.COMPLEX
|
|
188
188
|
|
|
189
189
|
def test_force_image_annotations_enables_image_annotations(self):
|
|
190
190
|
"""force_image_annotations implies image_annotations."""
|
|
@@ -195,10 +195,16 @@ class TestProcessingOptions:
|
|
|
195
195
|
assert options.to_dict()["force_image_annotations"] is True
|
|
196
196
|
assert options.to_dict()["image_annotations"] is True
|
|
197
197
|
|
|
198
|
-
def
|
|
199
|
-
"""
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
def test_accepts_handwritten_parse_type(self):
|
|
199
|
+
"""HANDWRITTEN is a valid parse type."""
|
|
200
|
+
options = ProcessingOptions(parse_type="handwritten")
|
|
201
|
+
|
|
202
|
+
assert options.parse_type is ParseType.HANDWRITTEN
|
|
203
|
+
|
|
204
|
+
def test_invalid_parse_type_raises_error(self):
|
|
205
|
+
"""Invalid parse type values are rejected."""
|
|
206
|
+
with pytest.raises(ValueError, match="Invalid parse type"):
|
|
207
|
+
ProcessingOptions(parse_type="invalid")
|
|
202
208
|
|
|
203
209
|
|
|
204
210
|
class TestSavedSchema:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|