datalab-python-sdk 0.1.14__tar.gz → 0.1.15__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.
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/PKG-INFO +1 -1
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/datalab_sdk/cli.py +79 -33
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/datalab_sdk/client.py +214 -2
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/datalab_sdk/mimetypes.py +4 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/datalab_sdk/models.py +35 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/pyproject.toml +1 -1
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/uv.lock +2 -2
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/.github/workflows/ci.yml +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/.github/workflows/publish.yml +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/.gitignore +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/.pre-commit-config.yaml +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/.python-version +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/LICENSE +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/README.md +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/08-Lambda-Calculus.pptx +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/adversarial.pdf +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/bid_evaluation.docx +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/book_review.ppt +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/book_store.xls +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/chi_hind.png +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/how_to_read.doc +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/normandy.epub +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/sample-1-sheet.xlsx +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/thinkpython.pdf +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/data/vibe.html +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/datalab_sdk/__init__.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/datalab_sdk/exceptions.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/datalab_sdk/settings.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/integration/README.md +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/integration/__init__.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/integration/test_live_api.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/integration/test_readme_examples.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/poetry.lock +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/pytest.ini +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/README.md +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/end_to_end_workflow.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_api_tutorial/1_get_step_types.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_api_tutorial/2_get_workflows.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_api_tutorial/3_create_workflow.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_api_tutorial/4_execute_workflow.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_api_tutorial/5_poll_workflow.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_api_tutorial/README.md +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_definitions/README.md +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_definitions/eval_segmentation.json +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_definitions/parse_segment.json +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_definitions/segment_parallel_extract.json +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/workflow_definitions/slack_alert.json +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/tests/__init__.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/tests/conftest.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/tests/test_cli_simple.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/tests/test_client_methods.py +0 -0
- {datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/tests/test_workflows.py +0 -0
|
@@ -93,6 +93,9 @@ def marker_options(func):
|
|
|
93
93
|
func = click.option(
|
|
94
94
|
"--page_schema", help="Schema to set to do structured extraction"
|
|
95
95
|
)(func)
|
|
96
|
+
func = click.option(
|
|
97
|
+
"--add_block_ids", is_flag=True, help="Add block IDs to HTML output"
|
|
98
|
+
)(func)
|
|
96
99
|
return func
|
|
97
100
|
|
|
98
101
|
|
|
@@ -139,13 +142,19 @@ async def process_files_async(
|
|
|
139
142
|
) as client:
|
|
140
143
|
if method == "convert":
|
|
141
144
|
result = await client.convert(
|
|
142
|
-
file_path,
|
|
143
|
-
|
|
145
|
+
file_path,
|
|
146
|
+
options=options,
|
|
147
|
+
save_output=output_path,
|
|
148
|
+
max_polls=max_polls,
|
|
149
|
+
poll_interval=poll_interval,
|
|
144
150
|
)
|
|
145
151
|
else: # method == 'ocr'
|
|
146
152
|
result = await client.ocr(
|
|
147
|
-
file_path,
|
|
148
|
-
|
|
153
|
+
file_path,
|
|
154
|
+
options=options,
|
|
155
|
+
save_output=output_path,
|
|
156
|
+
max_polls=max_polls,
|
|
157
|
+
poll_interval=poll_interval,
|
|
149
158
|
)
|
|
150
159
|
|
|
151
160
|
return {
|
|
@@ -247,6 +256,7 @@ def process_documents(
|
|
|
247
256
|
disable_image_extraction: bool = False,
|
|
248
257
|
block_correction_prompt: Optional[str] = None,
|
|
249
258
|
page_schema: Optional[str] = None,
|
|
259
|
+
add_block_ids: bool = False,
|
|
250
260
|
):
|
|
251
261
|
"""Unified document processing function"""
|
|
252
262
|
try:
|
|
@@ -290,6 +300,7 @@ def process_documents(
|
|
|
290
300
|
block_correction_prompt=block_correction_prompt,
|
|
291
301
|
skip_cache=skip_cache,
|
|
292
302
|
page_schema=page_schema,
|
|
303
|
+
add_block_ids=add_block_ids,
|
|
293
304
|
)
|
|
294
305
|
else: # method == "ocr"
|
|
295
306
|
options = OCROptions(
|
|
@@ -352,6 +363,7 @@ def convert(
|
|
|
352
363
|
disable_image_extraction: bool,
|
|
353
364
|
block_correction_prompt: Optional[str],
|
|
354
365
|
page_schema: Optional[str],
|
|
366
|
+
add_block_ids: bool,
|
|
355
367
|
):
|
|
356
368
|
"""Convert documents to markdown, HTML, or JSON"""
|
|
357
369
|
process_documents(
|
|
@@ -376,6 +388,7 @@ def convert(
|
|
|
376
388
|
disable_image_extraction=disable_image_extraction,
|
|
377
389
|
block_correction_prompt=block_correction_prompt,
|
|
378
390
|
page_schema=page_schema,
|
|
391
|
+
add_block_ids=add_block_ids,
|
|
379
392
|
)
|
|
380
393
|
|
|
381
394
|
|
|
@@ -465,7 +478,7 @@ def create_workflow(
|
|
|
465
478
|
name=name, team_id=team_id, steps=workflow_steps
|
|
466
479
|
)
|
|
467
480
|
|
|
468
|
-
click.echo(
|
|
481
|
+
click.echo("Workflow created successfully!")
|
|
469
482
|
click.echo(f" ID: {workflow.id}")
|
|
470
483
|
click.echo(f" Name: {workflow.name}")
|
|
471
484
|
click.echo(f" Team ID: {workflow.team_id}")
|
|
@@ -497,7 +510,7 @@ def get_workflow(workflow_id: int, api_key: Optional[str], base_url: str):
|
|
|
497
510
|
client = DatalabClient(api_key=api_key, base_url=base_url)
|
|
498
511
|
workflow = client.get_workflow(workflow_id)
|
|
499
512
|
|
|
500
|
-
click.echo(
|
|
513
|
+
click.echo("Workflow Details:")
|
|
501
514
|
click.echo(f" ID: {workflow.id}")
|
|
502
515
|
click.echo(f" Name: {workflow.name}")
|
|
503
516
|
click.echo(f" Team ID: {workflow.team_id}")
|
|
@@ -642,13 +655,15 @@ def execute_workflow(
|
|
|
642
655
|
input_config=input_cfg,
|
|
643
656
|
)
|
|
644
657
|
|
|
645
|
-
click.echo(
|
|
658
|
+
click.echo("\nSuccessfully triggered workflow execution!")
|
|
646
659
|
click.echo(f" Execution ID: {execution.id}")
|
|
647
660
|
click.echo(f" Status: {execution.status}")
|
|
648
|
-
click.echo(
|
|
661
|
+
click.echo("\nTo check the status, run:")
|
|
649
662
|
click.echo(f" datalab get-execution-status --execution_id {execution.id}")
|
|
650
|
-
click.echo(
|
|
651
|
-
click.echo(
|
|
663
|
+
click.echo("\n Or poll until complete:")
|
|
664
|
+
click.echo(
|
|
665
|
+
f" datalab get-execution-status --execution_id {execution.id} --max_polls 300 --poll_interval 2"
|
|
666
|
+
)
|
|
652
667
|
|
|
653
668
|
except DatalabError as e:
|
|
654
669
|
click.echo(f"Error: {e}", err=True)
|
|
@@ -667,13 +682,18 @@ def execute_workflow(
|
|
|
667
682
|
help="Output file path to save execution results",
|
|
668
683
|
)
|
|
669
684
|
@click.option(
|
|
670
|
-
"--max_polls",
|
|
685
|
+
"--max_polls",
|
|
686
|
+
default=1,
|
|
687
|
+
type=int,
|
|
688
|
+
help="Maximum number of polling attempts (1 for single check)",
|
|
689
|
+
)
|
|
690
|
+
@click.option(
|
|
691
|
+
"--poll_interval", default=1, type=int, help="Polling interval in seconds"
|
|
671
692
|
)
|
|
672
|
-
@click.option("--poll_interval", default=1, type=int, help="Polling interval in seconds")
|
|
673
693
|
@click.option(
|
|
674
694
|
"--download",
|
|
675
695
|
is_flag=True,
|
|
676
|
-
help="Download actual results from presigned URLs (default: just show URLs)"
|
|
696
|
+
help="Download actual results from presigned URLs (default: just show URLs)",
|
|
677
697
|
)
|
|
678
698
|
@click.option("--api_key", required=False, help="Datalab API key")
|
|
679
699
|
@click.option("--base_url", default=settings.DATALAB_HOST, help="API base URL")
|
|
@@ -704,7 +724,7 @@ def get_execution_status(
|
|
|
704
724
|
download_results=download,
|
|
705
725
|
)
|
|
706
726
|
|
|
707
|
-
click.echo(
|
|
727
|
+
click.echo("Execution Status:")
|
|
708
728
|
click.echo(f" Execution ID: {execution.id}")
|
|
709
729
|
click.echo(f" Workflow ID: {execution.workflow_id}")
|
|
710
730
|
click.echo(f" Status: {execution.status}")
|
|
@@ -712,7 +732,7 @@ def get_execution_status(
|
|
|
712
732
|
click.echo(f" Created: {execution.created}")
|
|
713
733
|
|
|
714
734
|
if execution.steps:
|
|
715
|
-
click.echo(
|
|
735
|
+
click.echo("\n Step Results:")
|
|
716
736
|
for step_name, step_data in execution.steps.items():
|
|
717
737
|
click.echo(f"\n [{step_name}]")
|
|
718
738
|
|
|
@@ -720,20 +740,34 @@ def get_execution_status(
|
|
|
720
740
|
for file_key, file_step_data in step_data.items():
|
|
721
741
|
if isinstance(file_step_data, dict):
|
|
722
742
|
click.echo(f"\n File/Group: {file_key}")
|
|
723
|
-
click.echo(
|
|
724
|
-
|
|
743
|
+
click.echo(
|
|
744
|
+
f" Step ID: {file_step_data.get('id', 'N/A')}"
|
|
745
|
+
)
|
|
746
|
+
click.echo(
|
|
747
|
+
f" Status: {file_step_data.get('status', 'N/A')}"
|
|
748
|
+
)
|
|
725
749
|
|
|
726
|
-
if file_step_data.get(
|
|
727
|
-
click.echo(
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if file_step_data.get(
|
|
731
|
-
click.echo(
|
|
750
|
+
if file_step_data.get("started_at"):
|
|
751
|
+
click.echo(
|
|
752
|
+
f" Started: {file_step_data.get('started_at')}"
|
|
753
|
+
)
|
|
754
|
+
if file_step_data.get("finished_at"):
|
|
755
|
+
click.echo(
|
|
756
|
+
f" Finished: {file_step_data.get('finished_at')}"
|
|
757
|
+
)
|
|
758
|
+
if file_step_data.get("file_ids"):
|
|
759
|
+
click.echo(
|
|
760
|
+
f" Files: {', '.join(file_step_data.get('file_ids'))}"
|
|
761
|
+
)
|
|
732
762
|
|
|
733
763
|
if "output_url" in file_step_data and not download:
|
|
734
|
-
click.echo(
|
|
764
|
+
click.echo(
|
|
765
|
+
f" Output URL: {file_step_data.get('output_url')}"
|
|
766
|
+
)
|
|
735
767
|
elif download and "downloaded_data" in file_step_data:
|
|
736
|
-
click.echo(
|
|
768
|
+
click.echo(
|
|
769
|
+
f" Results: {json.dumps(file_step_data['downloaded_data'], indent=12)}"
|
|
770
|
+
)
|
|
737
771
|
else:
|
|
738
772
|
click.echo(f" {file_step_data}")
|
|
739
773
|
|
|
@@ -750,7 +784,9 @@ def get_execution_status(
|
|
|
750
784
|
|
|
751
785
|
|
|
752
786
|
@click.command()
|
|
753
|
-
@click.option(
|
|
787
|
+
@click.option(
|
|
788
|
+
"--definition", required=True, help="Path to workflow definition JSON file"
|
|
789
|
+
)
|
|
754
790
|
def visualize_workflow(definition: str):
|
|
755
791
|
"""Visualize workflow DAG from a JSON definition file"""
|
|
756
792
|
try:
|
|
@@ -763,7 +799,7 @@ def visualize_workflow(definition: str):
|
|
|
763
799
|
if not definition_path.exists():
|
|
764
800
|
raise DatalabError(f"Definition file not found: {definition}")
|
|
765
801
|
|
|
766
|
-
with open(definition_path,
|
|
802
|
+
with open(definition_path, "r") as f:
|
|
767
803
|
workflow_def = json.load(f)
|
|
768
804
|
|
|
769
805
|
name = workflow_def.get("name", "Unnamed Workflow")
|
|
@@ -776,9 +812,9 @@ def visualize_workflow(definition: str):
|
|
|
776
812
|
# Build dependency graph
|
|
777
813
|
step_map = {step["unique_name"]: step for step in steps}
|
|
778
814
|
|
|
779
|
-
click.echo(f"\n{'='*70}")
|
|
815
|
+
click.echo(f"\n{'=' * 70}")
|
|
780
816
|
click.echo(f"Workflow: {name}")
|
|
781
|
-
click.echo(f"{'='*70}\n")
|
|
817
|
+
click.echo(f"{'=' * 70}\n")
|
|
782
818
|
|
|
783
819
|
# Build parent/child maps
|
|
784
820
|
children = defaultdict(list)
|
|
@@ -788,9 +824,13 @@ def visualize_workflow(definition: str):
|
|
|
788
824
|
children[dep].append(step_name)
|
|
789
825
|
|
|
790
826
|
# Topological sort to determine layers
|
|
791
|
-
in_degree = {
|
|
827
|
+
in_degree = {
|
|
828
|
+
step["unique_name"]: len(step.get("depends_on", [])) for step in steps
|
|
829
|
+
}
|
|
792
830
|
layers = []
|
|
793
|
-
queue = deque(
|
|
831
|
+
queue = deque(
|
|
832
|
+
[s["unique_name"] for s in steps if in_degree[s["unique_name"]] == 0]
|
|
833
|
+
)
|
|
794
834
|
|
|
795
835
|
while queue:
|
|
796
836
|
layer = []
|
|
@@ -844,11 +884,17 @@ def _render_dag_simple(layers, children, step_map):
|
|
|
844
884
|
|
|
845
885
|
# Show the step - color code by step type
|
|
846
886
|
step_color = "blue" if "marker" in step_key else "magenta"
|
|
847
|
-
click.echo(
|
|
887
|
+
click.echo(
|
|
888
|
+
f" • {click.style(step_name, fg=step_color)} ({click.style(step_key, fg='white', dim=True)})"
|
|
889
|
+
)
|
|
848
890
|
|
|
849
891
|
# Show dependencies if any
|
|
850
892
|
if depends:
|
|
851
|
-
click.echo(
|
|
893
|
+
click.echo(
|
|
894
|
+
click.style(
|
|
895
|
+
f" ← depends on: {', '.join(depends)}", fg="white", dim=True
|
|
896
|
+
)
|
|
897
|
+
)
|
|
852
898
|
|
|
853
899
|
# Show what comes next
|
|
854
900
|
if layer_idx < len(layers) - 1:
|
|
@@ -109,7 +109,8 @@ class AsyncDatalabClient:
|
|
|
109
109
|
except aiohttp.ClientResponseError as e:
|
|
110
110
|
try:
|
|
111
111
|
error_data = await response.json()
|
|
112
|
-
|
|
112
|
+
# FastAPI returns errors in "detail" field, but some APIs use "error"
|
|
113
|
+
error_message = error_data.get("detail") or error_data.get("error") or str(e)
|
|
113
114
|
except Exception:
|
|
114
115
|
error_message = str(e)
|
|
115
116
|
raise DatalabAPIError(
|
|
@@ -180,6 +181,15 @@ class AsyncDatalabClient:
|
|
|
180
181
|
if not file_path.exists():
|
|
181
182
|
raise DatalabFileError(f"File not found: {file_path}")
|
|
182
183
|
|
|
184
|
+
# Read file content
|
|
185
|
+
file_data = file_path.read_bytes()
|
|
186
|
+
|
|
187
|
+
# Check if file is empty
|
|
188
|
+
if not file_data:
|
|
189
|
+
raise DatalabFileError(
|
|
190
|
+
f"File is empty: {file_path}. Please provide a file with content."
|
|
191
|
+
)
|
|
192
|
+
|
|
183
193
|
# Determine MIME type
|
|
184
194
|
mime_type, _ = mimetypes.guess_type(str(file_path))
|
|
185
195
|
if not mime_type:
|
|
@@ -187,7 +197,7 @@ class AsyncDatalabClient:
|
|
|
187
197
|
extension = file_path.suffix.lower()
|
|
188
198
|
mime_type = MIMETYPE_MAP.get(extension, "application/octet-stream")
|
|
189
199
|
|
|
190
|
-
return file_path.name,
|
|
200
|
+
return file_path.name, file_data, mime_type
|
|
191
201
|
|
|
192
202
|
def get_form_params(self, file_path=None, file_url=None, options=None):
|
|
193
203
|
form_data = aiohttp.FormData()
|
|
@@ -267,11 +277,17 @@ class AsyncDatalabClient:
|
|
|
267
277
|
json=result_data.get("json"),
|
|
268
278
|
chunks=result_data.get("chunks"),
|
|
269
279
|
extraction_schema_json=result_data.get("extraction_schema_json"),
|
|
280
|
+
segmentation_results=result_data.get("segmentation_results"),
|
|
270
281
|
images=result_data.get("images"),
|
|
271
282
|
metadata=result_data.get("metadata"),
|
|
272
283
|
error=result_data.get("error"),
|
|
273
284
|
page_count=result_data.get("page_count"),
|
|
274
285
|
status=result_data.get("status", "complete"),
|
|
286
|
+
checkpoint_id=result_data.get("checkpoint_id"),
|
|
287
|
+
versions=result_data.get("versions"),
|
|
288
|
+
parse_quality_score=result_data.get("parse_quality_score"),
|
|
289
|
+
runtime=result_data.get("runtime"),
|
|
290
|
+
cost_breakdown=result_data.get("cost_breakdown"),
|
|
275
291
|
)
|
|
276
292
|
|
|
277
293
|
# Save output if requested
|
|
@@ -317,6 +333,8 @@ class AsyncDatalabClient:
|
|
|
317
333
|
error=result_data.get("error"),
|
|
318
334
|
page_count=result_data.get("page_count"),
|
|
319
335
|
status=result_data.get("status", "complete"),
|
|
336
|
+
versions=result_data.get("versions"),
|
|
337
|
+
cost_breakdown=result_data.get("cost_breakdown"),
|
|
320
338
|
)
|
|
321
339
|
|
|
322
340
|
# Save output if requested
|
|
@@ -464,6 +482,31 @@ class AsyncDatalabClient:
|
|
|
464
482
|
|
|
465
483
|
return workflows
|
|
466
484
|
|
|
485
|
+
async def delete_workflow(self, workflow_id: int) -> Dict[str, Any]:
|
|
486
|
+
"""
|
|
487
|
+
Delete a workflow definition
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
workflow_id: ID of the workflow to delete
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
Dictionary containing:
|
|
494
|
+
- success: Whether the deletion was successful
|
|
495
|
+
- message: Confirmation message
|
|
496
|
+
|
|
497
|
+
Raises:
|
|
498
|
+
DatalabAPIError: If workflow has executions or cannot be deleted
|
|
499
|
+
"""
|
|
500
|
+
response = await self._make_request(
|
|
501
|
+
"DELETE",
|
|
502
|
+
f"/api/v1/workflows/workflows/{workflow_id}",
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
"success": response.get("success", True),
|
|
507
|
+
"message": response.get("message", f"Workflow {workflow_id} deleted successfully"),
|
|
508
|
+
}
|
|
509
|
+
|
|
467
510
|
async def execute_workflow(
|
|
468
511
|
self,
|
|
469
512
|
workflow_id: int,
|
|
@@ -800,6 +843,95 @@ class AsyncDatalabClient:
|
|
|
800
843
|
"offset": response.get("offset", offset),
|
|
801
844
|
}
|
|
802
845
|
|
|
846
|
+
async def get_file_metadata(
|
|
847
|
+
self,
|
|
848
|
+
file_id: Union[int, str],
|
|
849
|
+
) -> UploadedFileMetadata:
|
|
850
|
+
"""
|
|
851
|
+
Get metadata for an uploaded file
|
|
852
|
+
|
|
853
|
+
Args:
|
|
854
|
+
file_id: File ID (integer or hashid string)
|
|
855
|
+
|
|
856
|
+
Returns:
|
|
857
|
+
UploadedFileMetadata object with file information
|
|
858
|
+
"""
|
|
859
|
+
response = await self._make_request(
|
|
860
|
+
"GET",
|
|
861
|
+
f"/api/v1/files/{file_id}",
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
return UploadedFileMetadata(
|
|
865
|
+
file_id=response["file_id"],
|
|
866
|
+
original_filename=response["original_filename"],
|
|
867
|
+
content_type=response["content_type"],
|
|
868
|
+
reference=response["reference"],
|
|
869
|
+
upload_status=response["upload_status"],
|
|
870
|
+
file_size=response.get("file_size"),
|
|
871
|
+
created=response.get("created"),
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
async def get_file_download_url(
|
|
875
|
+
self,
|
|
876
|
+
file_id: Union[int, str],
|
|
877
|
+
expires_in: int = 3600,
|
|
878
|
+
) -> Dict[str, Any]:
|
|
879
|
+
"""
|
|
880
|
+
Generate presigned URL for downloading a file
|
|
881
|
+
|
|
882
|
+
Args:
|
|
883
|
+
file_id: File ID (integer or hashid string)
|
|
884
|
+
expires_in: URL expiry time in seconds (default: 3600, max: 86400)
|
|
885
|
+
|
|
886
|
+
Returns:
|
|
887
|
+
Dictionary containing:
|
|
888
|
+
- download_url: Presigned URL for downloading the file
|
|
889
|
+
- expires_in: URL expiry time in seconds
|
|
890
|
+
- file_id: File ID
|
|
891
|
+
- original_filename: Original filename
|
|
892
|
+
"""
|
|
893
|
+
if expires_in < 60 or expires_in > 86400:
|
|
894
|
+
raise ValueError("expires_in must be between 60 and 86400 seconds")
|
|
895
|
+
|
|
896
|
+
response = await self._make_request(
|
|
897
|
+
"GET",
|
|
898
|
+
f"/api/v1/files/{file_id}/download?expires_in={expires_in}",
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
return {
|
|
902
|
+
"download_url": response["download_url"],
|
|
903
|
+
"expires_in": response["expires_in"],
|
|
904
|
+
"file_id": response["file_id"],
|
|
905
|
+
"original_filename": response["original_filename"],
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
async def delete_file(
|
|
909
|
+
self,
|
|
910
|
+
file_id: Union[int, str],
|
|
911
|
+
) -> Dict[str, Any]:
|
|
912
|
+
"""
|
|
913
|
+
Delete an uploaded file
|
|
914
|
+
|
|
915
|
+
Removes the file from both storage and the database.
|
|
916
|
+
|
|
917
|
+
Args:
|
|
918
|
+
file_id: File ID (integer or hashid string)
|
|
919
|
+
|
|
920
|
+
Returns:
|
|
921
|
+
Dictionary containing:
|
|
922
|
+
- success: Whether the deletion was successful
|
|
923
|
+
- message: Confirmation message
|
|
924
|
+
"""
|
|
925
|
+
response = await self._make_request(
|
|
926
|
+
"DELETE",
|
|
927
|
+
f"/api/v1/files/{file_id}",
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
return {
|
|
931
|
+
"success": response.get("success", True),
|
|
932
|
+
"message": response.get("message", f"File {file_id} deleted successfully"),
|
|
933
|
+
}
|
|
934
|
+
|
|
803
935
|
|
|
804
936
|
class DatalabClient:
|
|
805
937
|
"""Synchronous wrapper around AsyncDatalabClient"""
|
|
@@ -993,3 +1125,83 @@ class DatalabClient:
|
|
|
993
1125
|
return self._run_async(
|
|
994
1126
|
self._async_client.list_files(limit=limit, offset=offset)
|
|
995
1127
|
)
|
|
1128
|
+
|
|
1129
|
+
def get_file_metadata(
|
|
1130
|
+
self,
|
|
1131
|
+
file_id: Union[int, str],
|
|
1132
|
+
) -> UploadedFileMetadata:
|
|
1133
|
+
"""
|
|
1134
|
+
Get metadata for an uploaded file (sync version)
|
|
1135
|
+
|
|
1136
|
+
Args:
|
|
1137
|
+
file_id: File ID (integer or hashid string)
|
|
1138
|
+
|
|
1139
|
+
Returns:
|
|
1140
|
+
UploadedFileMetadata object with file information
|
|
1141
|
+
"""
|
|
1142
|
+
return self._run_async(
|
|
1143
|
+
self._async_client.get_file_metadata(file_id=file_id)
|
|
1144
|
+
)
|
|
1145
|
+
|
|
1146
|
+
def get_file_download_url(
|
|
1147
|
+
self,
|
|
1148
|
+
file_id: Union[int, str],
|
|
1149
|
+
expires_in: int = 3600,
|
|
1150
|
+
) -> Dict[str, Any]:
|
|
1151
|
+
"""
|
|
1152
|
+
Generate presigned URL for downloading a file (sync version)
|
|
1153
|
+
|
|
1154
|
+
Args:
|
|
1155
|
+
file_id: File ID (integer or hashid string)
|
|
1156
|
+
expires_in: URL expiry time in seconds (default: 3600, max: 86400)
|
|
1157
|
+
|
|
1158
|
+
Returns:
|
|
1159
|
+
Dictionary containing:
|
|
1160
|
+
- download_url: Presigned URL for downloading the file
|
|
1161
|
+
- expires_in: URL expiry time in seconds
|
|
1162
|
+
- file_id: File ID
|
|
1163
|
+
- original_filename: Original filename
|
|
1164
|
+
"""
|
|
1165
|
+
return self._run_async(
|
|
1166
|
+
self._async_client.get_file_download_url(file_id=file_id, expires_in=expires_in)
|
|
1167
|
+
)
|
|
1168
|
+
|
|
1169
|
+
def delete_file(
|
|
1170
|
+
self,
|
|
1171
|
+
file_id: Union[int, str],
|
|
1172
|
+
) -> Dict[str, Any]:
|
|
1173
|
+
"""
|
|
1174
|
+
Delete an uploaded file (sync version)
|
|
1175
|
+
|
|
1176
|
+
Removes the file from both storage and the database.
|
|
1177
|
+
|
|
1178
|
+
Args:
|
|
1179
|
+
file_id: File ID (integer or hashid string)
|
|
1180
|
+
|
|
1181
|
+
Returns:
|
|
1182
|
+
Dictionary containing:
|
|
1183
|
+
- success: Whether the deletion was successful
|
|
1184
|
+
- message: Confirmation message
|
|
1185
|
+
"""
|
|
1186
|
+
return self._run_async(
|
|
1187
|
+
self._async_client.delete_file(file_id=file_id)
|
|
1188
|
+
)
|
|
1189
|
+
|
|
1190
|
+
def delete_workflow(self, workflow_id: int) -> Dict[str, Any]:
|
|
1191
|
+
"""
|
|
1192
|
+
Delete a workflow definition (sync version)
|
|
1193
|
+
|
|
1194
|
+
Args:
|
|
1195
|
+
workflow_id: ID of the workflow to delete
|
|
1196
|
+
|
|
1197
|
+
Returns:
|
|
1198
|
+
Dictionary containing:
|
|
1199
|
+
- success: Whether the deletion was successful
|
|
1200
|
+
- message: Confirmation message
|
|
1201
|
+
|
|
1202
|
+
Raises:
|
|
1203
|
+
DatalabAPIError: If workflow has executions or cannot be deleted
|
|
1204
|
+
"""
|
|
1205
|
+
return self._run_async(
|
|
1206
|
+
self._async_client.delete_workflow(workflow_id=workflow_id)
|
|
1207
|
+
)
|
|
@@ -9,7 +9,11 @@ MIMETYPE_MAP = {
|
|
|
9
9
|
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
10
10
|
".doc": "application/msword",
|
|
11
11
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
12
|
+
".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12",
|
|
13
|
+
".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
|
12
14
|
".xls": "application/vnd.ms-excel",
|
|
15
|
+
".csv": "text/csv",
|
|
16
|
+
".ods": "application/vnd.oasis.opendocument.spreadsheet",
|
|
13
17
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
14
18
|
".ppt": "application/vnd.ms-powerpoint",
|
|
15
19
|
".html": "text/html",
|
|
@@ -44,13 +44,40 @@ class ConvertOptions(ProcessingOptions):
|
|
|
44
44
|
use_llm: bool = False
|
|
45
45
|
strip_existing_ocr: bool = False
|
|
46
46
|
disable_image_extraction: bool = False
|
|
47
|
+
disable_ocr_math: bool = False
|
|
47
48
|
block_correction_prompt: Optional[str] = None
|
|
48
49
|
additional_config: Optional[Dict[str, Any]] = None
|
|
49
50
|
page_schema: Optional[Dict[str, Any]] = None
|
|
51
|
+
segmentation_schema: Optional[str] = None # JSON string for document segmentation
|
|
52
|
+
save_checkpoint: bool = False
|
|
53
|
+
extras: Optional[str] = (
|
|
54
|
+
None # Comma-separated list: 'track_changes', 'chart_understanding'
|
|
55
|
+
)
|
|
50
56
|
output_format: str = "markdown" # markdown, json, html, chunks
|
|
51
57
|
mode: str = "fast" # fast, balanced, accurate
|
|
58
|
+
keep_spreadsheet_formatting: bool = False
|
|
52
59
|
webhook_url: Optional[str] = None
|
|
53
60
|
extras: Optional[str] = None # comma-separated extras
|
|
61
|
+
add_block_ids: bool = False # add block IDs to HTML output
|
|
62
|
+
|
|
63
|
+
def to_form_data(self) -> Dict[str, Any]:
|
|
64
|
+
"""Convert to form data format for API requests"""
|
|
65
|
+
# Start with parent's form data
|
|
66
|
+
form_data = super().to_form_data()
|
|
67
|
+
|
|
68
|
+
# Remove keep_spreadsheet_formatting from top-level (it goes in additional_config)
|
|
69
|
+
form_data.pop("keep_spreadsheet_formatting", None)
|
|
70
|
+
|
|
71
|
+
additional_config_dict = {}
|
|
72
|
+
if self.additional_config:
|
|
73
|
+
additional_config_dict.update(self.additional_config)
|
|
74
|
+
if self.keep_spreadsheet_formatting:
|
|
75
|
+
additional_config_dict["keep_spreadsheet_formatting"] = True
|
|
76
|
+
|
|
77
|
+
if additional_config_dict:
|
|
78
|
+
form_data["additional_config"] = (None, json.dumps(additional_config_dict))
|
|
79
|
+
|
|
80
|
+
return form_data
|
|
54
81
|
|
|
55
82
|
|
|
56
83
|
@dataclass
|
|
@@ -69,11 +96,17 @@ class ConversionResult:
|
|
|
69
96
|
json: Optional[Dict[str, Any]] = None
|
|
70
97
|
chunks: Optional[Dict[str, Any]] = None
|
|
71
98
|
extraction_schema_json: Optional[str] = None
|
|
99
|
+
segmentation_results: Optional[Dict[str, Any]] = None
|
|
72
100
|
images: Optional[Dict[str, str]] = None
|
|
73
101
|
metadata: Optional[Dict[str, Any]] = None
|
|
74
102
|
error: Optional[str] = None
|
|
75
103
|
page_count: Optional[int] = None
|
|
76
104
|
status: str = "complete"
|
|
105
|
+
checkpoint_id: Optional[str] = None
|
|
106
|
+
versions: Optional[Union[Dict[str, Any], str]] = None
|
|
107
|
+
parse_quality_score: Optional[float] = None
|
|
108
|
+
runtime: Optional[float] = None
|
|
109
|
+
cost_breakdown: Optional[Dict[str, Any]] = None
|
|
77
110
|
|
|
78
111
|
def save_output(
|
|
79
112
|
self, output_path: Union[str, Path], save_images: bool = True
|
|
@@ -270,6 +303,8 @@ class OCRResult:
|
|
|
270
303
|
error: Optional[str] = None
|
|
271
304
|
page_count: Optional[int] = None
|
|
272
305
|
status: str = "complete"
|
|
306
|
+
versions: Optional[Union[Dict[str, Any], str]] = None
|
|
307
|
+
cost_breakdown: Optional[Dict[str, Any]] = None
|
|
273
308
|
|
|
274
309
|
def get_text(self, page_num: Optional[int] = None) -> str:
|
|
275
310
|
"""Extract text from OCR results"""
|
|
@@ -7,7 +7,7 @@ readme = "README.md"
|
|
|
7
7
|
license = "MIT"
|
|
8
8
|
repository = "https://github.com/datalab-to/sdk"
|
|
9
9
|
keywords = ["datalab", "sdk", "document-intelligence", "api"]
|
|
10
|
-
version = "0.1.
|
|
10
|
+
version = "0.1.15"
|
|
11
11
|
description = "SDK for the Datalab document intelligence API"
|
|
12
12
|
requires-python = ">=3.10"
|
|
13
13
|
dependencies = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
version = 1
|
|
2
|
-
revision =
|
|
2
|
+
revision = 3
|
|
3
3
|
requires-python = ">=3.10"
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
@@ -212,7 +212,7 @@ wheels = [
|
|
|
212
212
|
|
|
213
213
|
[[package]]
|
|
214
214
|
name = "datalab-python-sdk"
|
|
215
|
-
version = "0.1.
|
|
215
|
+
version = "0.1.15"
|
|
216
216
|
source = { editable = "." }
|
|
217
217
|
dependencies = [
|
|
218
218
|
{ name = "aiohttp" },
|
|
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
|
{datalab_python_sdk-0.1.14 → datalab_python_sdk-0.1.15}/recipes/workflows/end_to_end_workflow.py
RENAMED
|
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
|