datalab-python-sdk 0.5.0__py3-none-any.whl → 0.7.0__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.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.5
2
+ Name: datalab-python-sdk
3
+ Version: 0.7.0
4
+ Summary: SDK for the Datalab document intelligence API
5
+ Project-URL: Homepage, https://www.datalab.to
6
+ Project-URL: Documentation, https://documentation.datalab.to
7
+ Project-URL: Repository, https://github.com/datalab-to/sdk
8
+ Author-email: Datalab Team <hi@datalab.to>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: api,datalab,document-intelligence,sdk
12
+ Requires-Python: >=3.11
13
+ Requires-Dist: aiohttp>=3.12.14
14
+ Requires-Dist: click>=8.2.1
15
+ Requires-Dist: ijson>=3.3.0
16
+ Requires-Dist: pydantic-settings>=2.10.1
17
+ Requires-Dist: pydantic>=2.11.7
18
+ Requires-Dist: tenacity>=8.2.3
19
+ Requires-Dist: tqdm>=4.66.0
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Datalab SDK
23
+
24
+ A Python SDK for the [Datalab API](https://www.datalab.to/?utm_source=gh-sdk) - it turns documents into verifiable knowledge your agents act on. Datalab is the team behind [Chandra](https://github.com/datalab-to/chandra), [marker](https://github.com/datalab-to/marker), [surya](https://github.com/datalab-to/surya), and [lift](https://github.com/datalab-to/lift).
25
+
26
+ See the full documentation at [https://documentation.datalab.to](https://documentation.datalab.to).
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install datalab-python-sdk
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### Authentication
37
+
38
+ [Sign up](https://www.datalab.to/?utm_source=gh-sdk), then get your API key from [https://www.datalab.to/app/keys](https://www.datalab.to/app/keys):
39
+
40
+ ```bash
41
+ export DATALAB_API_KEY="your_api_key_here"
42
+ ```
43
+
44
+ Or sign in from a terminal, which also works for coding agents:
45
+
46
+ ```bash
47
+ datalab login
48
+ ```
49
+
50
+ It shows a code and a link. Open the link, sign in or sign up, approve the code, and type the 6-digit code that we email you. The SDK saves the key to `~/.config/datalab/config.json`, readable only by your user, and uses it when `DATALAB_API_KEY` is not set. `datalab logout` deletes it.
51
+
52
+ ### Basic Usage
53
+
54
+ ```python
55
+ from datalab_sdk import DatalabClient
56
+
57
+ client = DatalabClient() # use env var from above, or pass api_key="your_api_key_here"
58
+
59
+ # Convert PDF to markdown
60
+ result = client.convert("document.pdf")
61
+ print(result.markdown)
62
+ ```
63
+
64
+ `AsyncDatalabClient` has the same methods as `DatalabClient`, as coroutines.
65
+
66
+ ## Conversion options
67
+
68
+ ```python
69
+ from datalab_sdk import ConvertOptions
70
+
71
+ options = ConvertOptions(
72
+ mode="balanced", # lite, fast, balanced, accurate (SDK default: fast)
73
+ output_format="markdown,html", # markdown, html, json, chunks (comma-separated)
74
+ page_range="0-4",
75
+ extras="chart_understanding,table_cell_bboxes",
76
+ merge_cross_page=True, # merge tables, paragraphs and lists split across pages
77
+ word_bboxes=True, # per-word bounding boxes in the HTML output
78
+ save_checkpoint=True, # reuse the parse in extract() or segment()
79
+ )
80
+ result = client.convert("document.pdf", options=options)
81
+ ```
82
+
83
+ Other options include `paginate`, `add_block_ids`, `disable_html_prettify`, `exact_transcription`, `token_efficient_markdown`, `include_markdown_in_chunks`, `disable_image_extraction`, `disable_image_captions`, `fence_synthetic_captions`, `keep_spreadsheet_formatting`, `keep_pageheader_in_output`, `keep_pagefooter_in_output`, `additional_config`, `webhook_url`, `skip_cache` and `processing_location`. The [API reference](https://documentation.datalab.to/api-reference/convert-document) describes each option.
84
+
85
+ ## Structured extraction
86
+
87
+ ```python
88
+ from datalab_sdk import ExtractOptions
89
+
90
+ result = client.extract(
91
+ "invoice.pdf",
92
+ options=ExtractOptions(
93
+ page_schema='{"properties": {"total": {"type": "number"}}}', # or schema_id="sch_..."
94
+ extraction_mode="balanced", # turbo, fast, balanced, accurate
95
+ ),
96
+ )
97
+ print(result.extraction_schema_json, result.extraction_score_average)
98
+ ```
99
+
100
+ When you do not set the parse `mode`, the API picks the parse mode that matches `extraction_mode`.
101
+
102
+ ## Other endpoints
103
+
104
+ | Method | Endpoint |
105
+ | --- | --- |
106
+ | `run_agent()` | **Beta:** run an existing published processor (`AgentOptions(processor="cp_...")`) |
107
+ | `segment()` | Split a document into sections (`SegmentOptions`) |
108
+ | `fill()` | Fill a PDF or image form (`FormFillingOptions`) |
109
+ | `form_fields()` | Find the fillable fields in a PDF or image |
110
+ | `track_changes()` | Read tracked changes and comments from DOCX or PDF |
111
+ | `create_document()` | Make a DOCX from markdown with track changes markup |
112
+ | `generate_extraction_schemas()` | Suggest extraction schemas for a saved checkpoint |
113
+ | `delete_result()` | Result deletion, by `result.request_id` |
114
+ | `upload_files()`, `list_files()`, ... | File storage. Pass the returned `datalab://file-...` reference as `file_url` |
115
+ | `create_extraction_schema()`, ... | Saved extraction schemas |
116
+ | `create_pipeline()`, `run_pipeline()`, ... | [Pipelines](https://documentation.datalab.to/docs/welcome/sdk/pipelines) |
117
+
118
+ `run_custom_processor()` and `ocr()` call deprecated API endpoints. Use `run_agent()` and `convert()` instead.
119
+
120
+ Published-agent execution is in open beta; the API may change. Processor design and raw-prompt execution are not included.
121
+
122
+ ## Inputs, polling and errors
123
+
124
+ - Send a local file with `file_path`, or a URL or a `datalab://file-...` reference with `file_url`. When you set `processing_location`, send the document as `file_url`.
125
+ - Processing methods poll until the result is ready. Set `max_polls` and `poll_interval` to change the limit (default: 300 polls, 1 second apart). Use `stream_response_to` to write a large result straight to disk.
126
+ - The SDK retries a submission that gets HTTP 429 or 503, and polls that get 408, 429, 5xx or a connection error. It waits for the `Retry-After` header when the API sends one.
127
+ - API errors raise `DatalabAPIError` (with `status_code` and `response_data`) or one of its subclasses: `DatalabAuthenticationError` (401, 403), `DatalabPaymentRequiredError` (402), `DatalabNotFoundError` (404), `DatalabRateLimitError` (429) and `DatalabServerError` (5xx). Invalid arguments raise `DatalabValidationError`, which is also a `ValueError`.
128
+
129
+ ## Workflows
130
+
131
+ The Datalab API removed the Workflows API. The workflow methods and CLI commands in this SDK are deprecated, and the API does not answer them. Use pipelines instead.
132
+
133
+ ## CLI Usage
134
+
135
+ The SDK includes a command-line interface:
136
+
137
+ ```bash
138
+ # Convert a document or a directory of documents
139
+ datalab convert document.pdf --mode balanced --format markdown,html
140
+
141
+ # Extract with a saved schema
142
+ datalab extract invoice.pdf --schema_id sch_XXXXX --extraction_mode fast
143
+
144
+ # Other commands
145
+ datalab agent --help
146
+ datalab segment --help
147
+ datalab fill --help
148
+ datalab form-fields --help
149
+ datalab track-changes --help
150
+ datalab create-document --help
151
+ ```
152
+
153
+ ## Contributing
154
+
155
+ Issues and bug reports are welcome here. We develop the SDK in a private repository and mirror each release to this one, so we do not merge pull requests here directly. Open an issue, or open a pull request to show the change, and we will port it and credit you.
156
+
157
+ ## License
158
+
159
+ MIT License
@@ -0,0 +1,13 @@
1
+ datalab_sdk/__init__.py,sha256=BLAqVuqjxhdjKabkDUePLSmGeVMo0BnWfVbLDt8haP4,2397
2
+ datalab_sdk/auth.py,sha256=Z7eaKSxhDFt2uUbSIlU8yuOC2myDWXl-rY9JLuThg4A,4278
3
+ datalab_sdk/cli.py,sha256=icUy91brz0J8hzvC1G-pBtZPszc5oRd4H3sEpYhKr7Y,54186
4
+ datalab_sdk/client.py,sha256=aNPW4_gUdyW7UbLHI3YRzPA_Ae2dQmlQwCV38qsw0hA,125702
5
+ datalab_sdk/exceptions.py,sha256=5-lzlfvB7FKGOhvgyacceTFRRUxton_JqcIyuB8k_mU,2625
6
+ datalab_sdk/mimetypes.py,sha256=a4ULJmHxoR_RMsD0j51hx0t2Q7In_PzNm7UlWX07eEE,932
7
+ datalab_sdk/models.py,sha256=OpiIAdOVcL2tdfBkzRNHZ8MjGjRiBSjaKtFVf_U1l8w,32494
8
+ datalab_sdk/settings.py,sha256=B_B42QQADAz6ePXQTOqENZllcppFR4JpwonEk2vlzhw,693
9
+ datalab_python_sdk-0.7.0.dist-info/METADATA,sha256=z3hdjWxjWWjORewPlI58rO_stgNdHKUkwvf0Y4JemRg,6963
10
+ datalab_python_sdk-0.7.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
11
+ datalab_python_sdk-0.7.0.dist-info/entry_points.txt,sha256=rqWxO21UV57JxxyeF20iBTxHpzBYOA69hCHIqwdn-Ok,48
12
+ datalab_python_sdk-0.7.0.dist-info/licenses/LICENSE,sha256=XGjT-Uz6t7UZzr3JCZfilsGcZEgMm_1aONcRunohxWg,1064
13
+ datalab_python_sdk-0.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.33.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
datalab_sdk/__init__.py CHANGED
@@ -6,7 +6,18 @@ supporting document conversion, extraction, segmentation, and more.
6
6
  """
7
7
 
8
8
  from .client import DatalabClient, AsyncDatalabClient
9
- from .exceptions import DatalabError, DatalabAPIError, DatalabTimeoutError
9
+ from .exceptions import (
10
+ DatalabError,
11
+ DatalabAPIError,
12
+ DatalabAuthenticationError,
13
+ DatalabPaymentRequiredError,
14
+ DatalabNotFoundError,
15
+ DatalabRateLimitError,
16
+ DatalabServerError,
17
+ DatalabTimeoutError,
18
+ DatalabFileError,
19
+ DatalabValidationError,
20
+ )
10
21
  from .models import (
11
22
  ConversionResult,
12
23
  CreateDocumentResult,
@@ -21,6 +32,11 @@ from .models import (
21
32
  OCROptions,
22
33
  FormFillingOptions,
23
34
  FormFillingResult,
35
+ FormFieldsOptions,
36
+ FormFieldsResult,
37
+ AgentOptions,
38
+ AgentResult,
39
+ ExtractionSchemaSuggestions,
24
40
  Workflow,
25
41
  WorkflowStep,
26
42
  WorkflowExecution,
@@ -43,7 +59,14 @@ __all__ = [
43
59
  "AsyncDatalabClient",
44
60
  "DatalabError",
45
61
  "DatalabAPIError",
62
+ "DatalabAuthenticationError",
63
+ "DatalabPaymentRequiredError",
64
+ "DatalabNotFoundError",
65
+ "DatalabRateLimitError",
66
+ "DatalabServerError",
46
67
  "DatalabTimeoutError",
68
+ "DatalabFileError",
69
+ "DatalabValidationError",
47
70
  "ConversionResult",
48
71
  "CreateDocumentResult",
49
72
  "FileResult",
@@ -57,6 +80,11 @@ __all__ = [
57
80
  "OCROptions",
58
81
  "FormFillingOptions",
59
82
  "FormFillingResult",
83
+ "FormFieldsOptions",
84
+ "FormFieldsResult",
85
+ "AgentOptions",
86
+ "AgentResult",
87
+ "ExtractionSchemaSuggestions",
60
88
  "Workflow",
61
89
  "WorkflowStep",
62
90
  "WorkflowExecution",
datalab_sdk/auth.py ADDED
@@ -0,0 +1,130 @@
1
+ """Sign in from a terminal with a device code (`datalab login`), and keep the API key on disk.
2
+
3
+ The flow (server side: /api/auth/device/code, /app/device, /api/auth/device/token):
4
+ 1. Ask the server for a code. Show the human the code and a URL, and open the browser.
5
+ 2. The human approves in the browser, where they are signed in as usual.
6
+ 3. Poll until the server returns a new API key, then save it to the config file.
7
+
8
+ The key is saved to $XDG_CONFIG_HOME/datalab/config.json (default ~/.config/datalab/config.json)
9
+ with mode 0600. DATALAB_API_KEY in the environment always wins over the saved key.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import os
16
+ import stat
17
+ import time
18
+ import urllib.error
19
+ import urllib.request
20
+ import webbrowser
21
+ from pathlib import Path
22
+ from typing import Callable, Optional
23
+
24
+ from datalab_sdk.exceptions import DatalabAPIError
25
+
26
+ KEYS_PAGE = "https://www.datalab.to/app/keys"
27
+
28
+
29
+ def config_path() -> Path:
30
+ base = os.environ.get("XDG_CONFIG_HOME") or os.path.join(Path.home(), ".config")
31
+ return Path(base) / "datalab" / "config.json"
32
+
33
+
34
+ def load_saved_api_key() -> Optional[str]:
35
+ try:
36
+ return json.loads(config_path().read_text()).get("api_key") or None
37
+ except (OSError, ValueError):
38
+ return None
39
+
40
+
41
+ def save_api_key(api_key: str, host: str, team_name: Optional[str] = None) -> Path:
42
+ path = config_path()
43
+ path.parent.mkdir(parents=True, exist_ok=True)
44
+ path.write_text(
45
+ json.dumps({"api_key": api_key, "host": host, "team_name": team_name}, indent=2)
46
+ )
47
+ os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
48
+ return path
49
+
50
+
51
+ def delete_saved_api_key() -> bool:
52
+ try:
53
+ config_path().unlink()
54
+ return True
55
+ except FileNotFoundError:
56
+ return False
57
+
58
+
59
+ def _post(url: str, payload: dict, headers: dict) -> tuple[int, dict]:
60
+ req = urllib.request.Request(
61
+ url,
62
+ data=json.dumps(payload).encode(),
63
+ headers={
64
+ "Content-Type": "application/json",
65
+ "Accept": "application/json",
66
+ **headers,
67
+ },
68
+ method="POST",
69
+ )
70
+ try:
71
+ with urllib.request.urlopen(req, timeout=30) as res:
72
+ return res.status, json.loads(res.read() or b"{}")
73
+ except urllib.error.HTTPError as e:
74
+ try:
75
+ return e.code, json.loads(e.read() or b"{}")
76
+ except ValueError:
77
+ return e.code, {}
78
+
79
+
80
+ def device_login(
81
+ host: str = "https://www.datalab.to",
82
+ client_name: str = "Datalab CLI",
83
+ open_browser: bool = True,
84
+ extra_headers: Optional[dict] = None,
85
+ on_code: Optional[Callable[[dict], None]] = None,
86
+ sleep: Callable[[float], None] = time.sleep,
87
+ ) -> dict:
88
+ """Run the device flow and return {"api_key", "team_name", "key_name"}. Raises DatalabAPIError."""
89
+ host = host.rstrip("/")
90
+ headers = extra_headers or {}
91
+ status, code = _post(
92
+ f"{host}/api/auth/device/code", {"client_name": client_name}, headers
93
+ )
94
+ if status != 200:
95
+ raise DatalabAPIError(
96
+ f"Could not start the sign-in ({status}): {code.get('detail', code)}"
97
+ )
98
+ if on_code:
99
+ on_code(code)
100
+ if open_browser:
101
+ try:
102
+ webbrowser.open(code["verification_uri_complete"])
103
+ except Exception:
104
+ pass
105
+
106
+ interval = float(code.get("interval", 5))
107
+ deadline = time.monotonic() + float(code.get("expires_in", 600))
108
+ while time.monotonic() < deadline:
109
+ sleep(interval)
110
+ status, body = _post(
111
+ f"{host}/api/auth/device/token",
112
+ {"device_code": code["device_code"]},
113
+ headers,
114
+ )
115
+ if status == 200 and body.get("api_key"):
116
+ return body
117
+ error = body.get("error")
118
+ if error == "authorization_pending":
119
+ continue
120
+ if error == "slow_down":
121
+ interval += 5
122
+ continue
123
+ if error == "access_denied":
124
+ raise DatalabAPIError("The sign-in was denied in the browser.")
125
+ if error == "expired_token":
126
+ raise DatalabAPIError(
127
+ "The sign-in code expired. Run `datalab login` again."
128
+ )
129
+ raise DatalabAPIError(f"Sign-in failed ({status}): {body}")
130
+ raise DatalabAPIError("The sign-in code expired. Run `datalab login` again.")