wherobots-python-sdk 0.3.0__tar.gz → 0.5.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.
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/PKG-INFO +105 -19
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/README.md +104 -11
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/pyproject.toml +42 -31
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_api.py +6 -6
- wherobots_python_sdk-0.5.0/tests/test_base_client.py +433 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_client.py +32 -0
- wherobots_python_sdk-0.5.0/tests/test_client_header.py +216 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_config.py +14 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_exceptions.py +2 -3
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_files_api.py +2 -2
- wherobots_python_sdk-0.5.0/tests/test_files_drive.py +811 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_init_exports.py +18 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_integration.py +1 -1
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_models.py +17 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_security.py +4 -4
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/__init__.py +16 -1
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/__version__.py +1 -1
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/api/__init__.py +2 -1
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/api/base.py +98 -34
- wherobots_python_sdk-0.5.0/wherobots/api/drive.py +481 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/api/files.py +11 -10
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/api/runs.py +8 -8
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/client.py +34 -22
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/config.py +15 -4
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/enums.py +10 -0
- wherobots_python_sdk-0.5.0/wherobots/exceptions.py +81 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/models.py +152 -20
- wherobots_python_sdk-0.5.0/wherobots/sdk.py +91 -0
- wherobots_python_sdk-0.5.0/wherobots/utils/client_header.py +205 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/utils/validation.py +1 -1
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots_python_sdk.egg-info/PKG-INFO +105 -19
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots_python_sdk.egg-info/SOURCES.txt +5 -0
- wherobots_python_sdk-0.5.0/wherobots_python_sdk.egg-info/requires.txt +2 -0
- wherobots_python_sdk-0.3.0/tests/test_base_client.py +0 -213
- wherobots_python_sdk-0.3.0/wherobots/exceptions.py +0 -47
- wherobots_python_sdk-0.3.0/wherobots_python_sdk.egg-info/requires.txt +0 -10
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/LICENSE +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/setup.cfg +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_enums.py +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_logger.py +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_regressions.py +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_utils.py +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/py.typed +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/utils/__init__.py +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/utils/logger.py +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots_python_sdk.egg-info/dependency_links.txt +0 -0
- {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots_python_sdk.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wherobots-python-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Python SDK for Wherobots (currently covers the Jobs REST API)
|
|
5
5
|
Author-email: Wherobots <support@wherobots.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -23,13 +23,6 @@ Description-Content-Type: text/markdown
|
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: requests>=2.33.0
|
|
25
25
|
Requires-Dist: boto3>=1.34.0
|
|
26
|
-
Provides-Extra: dev
|
|
27
|
-
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
28
|
-
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
29
|
-
Requires-Dist: pytest-mock>=3.11.1; extra == "dev"
|
|
30
|
-
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
31
|
-
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
32
|
-
Requires-Dist: types-requests>=2.31; extra == "dev"
|
|
33
26
|
Dynamic: license-file
|
|
34
27
|
|
|
35
28
|
# Wherobots Python SDK
|
|
@@ -52,6 +45,7 @@ Additional Wherobots surfaces will be added over time.
|
|
|
52
45
|
- List runs with status, name, and date filters
|
|
53
46
|
- Typed dataclass models for all API responses (no pydantic dependency)
|
|
54
47
|
- Environment-based configuration
|
|
48
|
+
- Files: list, create folders, upload, download, rename and delete in your Wherobots file area
|
|
55
49
|
|
|
56
50
|
## Installation
|
|
57
51
|
|
|
@@ -67,11 +61,11 @@ The simplest way to run a local script — only an API key is needed:
|
|
|
67
61
|
from wherobots import WherobotsJob
|
|
68
62
|
|
|
69
63
|
job = WherobotsJob(
|
|
70
|
-
script="my_analysis.py",
|
|
64
|
+
script="my_analysis.py", # local file — auto-uploaded via presigned URL
|
|
71
65
|
name="analysis-job-001",
|
|
72
66
|
runtime="tiny",
|
|
73
67
|
region="aws-us-west-2",
|
|
74
|
-
api_key="wbk_...",
|
|
68
|
+
api_key="wbk_...", # or set WHEROBOTS_API_KEY env var
|
|
75
69
|
)
|
|
76
70
|
|
|
77
71
|
run_id = job.submit()
|
|
@@ -135,6 +129,7 @@ The API key can also be set via the `WHEROBOTS_API_KEY` environment variable.
|
|
|
135
129
|
| `WHEROBOTS_API_BASE_URL` | API base URL | `https://api.cloud.wherobots.com` |
|
|
136
130
|
| `WHEROBOTS_VERSION` | Wherobots version | `latest` |
|
|
137
131
|
| `WHEROBOTS_REQUEST_TIMEOUT_SECONDS`| HTTP request timeout (seconds) | `30` |
|
|
132
|
+
| `WHEROBOTS_CLIENT_CHAIN` | Upstream client-attribution chain (see below) | *(none)* |
|
|
138
133
|
|
|
139
134
|
### WherobotsConfig
|
|
140
135
|
|
|
@@ -164,6 +159,35 @@ config = WherobotsConfig(
|
|
|
164
159
|
| `base_url` | `str` | `"https://api.cloud.wherobots.com"` |
|
|
165
160
|
| `version` | `str` | `"latest"` |
|
|
166
161
|
| `request_timeout_seconds` | `int` | `30` |
|
|
162
|
+
| `client_chain` | `str \| None` | `None` |
|
|
163
|
+
|
|
164
|
+
### Client attribution
|
|
165
|
+
|
|
166
|
+
Every request the SDK sends carries an advisory `X-Wherobots-Client` header so
|
|
167
|
+
Wherobots can attribute workloads to the client that started them:
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
X-Wherobots-Client: client=python-sdk;ver=0.3.0;plat=darwin
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The header is an ordered, comma-separated chain of hops — leftmost is the
|
|
174
|
+
origin client, and each component appends its own hop on the right. If your
|
|
175
|
+
tool embeds this SDK, pass your own hop as `client_chain` and the SDK appends
|
|
176
|
+
itself after it:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
job = WherobotsJob(
|
|
180
|
+
script="job.py",
|
|
181
|
+
name="my-job-name",
|
|
182
|
+
client_chain="client=airflow;ver=1.2.0",
|
|
183
|
+
)
|
|
184
|
+
# X-Wherobots-Client: client=airflow;ver=1.2.0, client=python-sdk;ver=0.3.0;plat=linux
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`client_chain` also works on `WherobotsJob.from_run_id(...)` and
|
|
188
|
+
`WherobotsConfig`, or can be set once via `WHEROBOTS_CLIENT_CHAIN`. The header
|
|
189
|
+
is advisory only: it is used for attribution and analytics and never affects
|
|
190
|
+
authentication or authorization.
|
|
167
191
|
|
|
168
192
|
## SDK Reference
|
|
169
193
|
|
|
@@ -192,6 +216,7 @@ WherobotsJob(
|
|
|
192
216
|
base_url: str | None = None, # Override API URL
|
|
193
217
|
request_timeout_seconds: int | None = None, # HTTP timeout
|
|
194
218
|
config: WherobotsConfig | None = None, # Full config object
|
|
219
|
+
client_chain: str | None = None, # Upstream X-Wherobots-Client chain
|
|
195
220
|
)
|
|
196
221
|
```
|
|
197
222
|
|
|
@@ -318,7 +343,7 @@ environment.
|
|
|
318
343
|
|
|
319
344
|
```python
|
|
320
345
|
job = WherobotsJob(
|
|
321
|
-
script="my_job.py",
|
|
346
|
+
script="my_job.py", # local script auto-upload still works
|
|
322
347
|
name="job-with-local-wheel",
|
|
323
348
|
dependencies=[
|
|
324
349
|
# Local wheel — uploaded to managed storage at submit() time
|
|
@@ -368,7 +393,7 @@ with FilesAPI.from_config(WherobotsConfig.from_env()) as files:
|
|
|
368
393
|
)
|
|
369
394
|
|
|
370
395
|
job = WherobotsJob(
|
|
371
|
-
script=script_uri,
|
|
396
|
+
script=script_uri, # already-uploaded URI
|
|
372
397
|
name="job-in-integration",
|
|
373
398
|
dependencies=[WherobotsJob.add_file_dependency(wheel_uri)],
|
|
374
399
|
)
|
|
@@ -394,6 +419,49 @@ job = WherobotsJob(
|
|
|
394
419
|
)
|
|
395
420
|
```
|
|
396
421
|
|
|
422
|
+
## Files
|
|
423
|
+
|
|
424
|
+
`Files` is your Wherobots file area ("My Files" in Studio) from Python. Start from a
|
|
425
|
+
`Wherobots` client; it reads `WHEROBOTS_API_KEY` and `WHEROBOTS_REGION`, or takes `api_key=` /
|
|
426
|
+
`region=` overrides, and closes its connection as a context manager.
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
import wherobots
|
|
430
|
+
from wherobots import Drive, Region
|
|
431
|
+
|
|
432
|
+
with wherobots.Wherobots() as wb:
|
|
433
|
+
drive = wb.files.my_files # same object as wb.files.drive(Drive.MY_FILES)
|
|
434
|
+
eu = wb.files.drive(Drive.MY_FILES, region=Region.EU_WEST_1) # another region
|
|
435
|
+
|
|
436
|
+
drive.make_directory("projects/2026/q3") # creates projects, then projects/2026, then .../q3
|
|
437
|
+
drive.upload_file("report.csv", "projects/2026/q3/report.csv")
|
|
438
|
+
for entry in drive.list_directory("projects/2026/q3"): # "" lists the root
|
|
439
|
+
print(entry.name, entry.is_dir, entry.size, entry.last_modified)
|
|
440
|
+
drive.download_file("projects/2026/q3/report.csv", "local-copy.csv")
|
|
441
|
+
drive.rename_file("projects/2026/q3/report.csv", "final.csv")
|
|
442
|
+
drive.delete_file("projects/2026/q3/final.csv")
|
|
443
|
+
drive.delete_directory("projects")
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
- Each path segment is encoded on its own; empty levels, `.` and `..` are refused before any
|
|
447
|
+
request.
|
|
448
|
+
- Uploads and downloads go straight to storage through short-lived presigned URLs. The API key
|
|
449
|
+
is never sent to storage, and no storage credentials are requested.
|
|
450
|
+
- Uploads are capped at 500 MiB per file, matching the CLI and the Studio web UI; a larger file
|
|
451
|
+
is refused before any request is sent.
|
|
452
|
+
- Listing follows the server's pages until the end and returns every entry.
|
|
453
|
+
- A region is required: with none passed or configured, `wb.files.my_files` raises
|
|
454
|
+
`WherobotsConfigError`.
|
|
455
|
+
|
|
456
|
+
| Failure | Raised as |
|
|
457
|
+
|---|---|
|
|
458
|
+
| Files is not enabled for this drive in this region | `WherobotsFilesNotEnabledError` (`.drive`, `.region`) |
|
|
459
|
+
| The API key was not accepted | `WherobotsNotAuthenticatedError` |
|
|
460
|
+
| No such file or folder | `WherobotsFileNotFoundError` |
|
|
461
|
+
| Transfer to or from storage failed | `WherobotsS3Error` |
|
|
462
|
+
|
|
463
|
+
The three `Wherobots*Error` classes above subclass `WherobotsAPIError`.
|
|
464
|
+
|
|
397
465
|
## Enums
|
|
398
466
|
|
|
399
467
|
### JobStatus
|
|
@@ -561,9 +629,12 @@ All exceptions inherit from `WherobotsJobError`.
|
|
|
561
629
|
| `WherobotsJobError` | Base exception for all SDK errors. |
|
|
562
630
|
| `WherobotsAPIError` | API returned an error response. Has `status_code`, `response`, `request_id` attributes. |
|
|
563
631
|
| `WherobotsValidationError` | Client-side validation failed (e.g., invalid name). |
|
|
564
|
-
| `WherobotsS3Error` |
|
|
632
|
+
| `WherobotsS3Error` | A transfer to or from storage failed, including Files presigned uploads and downloads. |
|
|
565
633
|
| `WherobotsConfigError` | Configuration is missing or invalid. |
|
|
566
634
|
| `WherobotsTimeoutError` | Job exceeded the specified timeout. |
|
|
635
|
+
| `WherobotsNotAuthenticatedError` | Files: the API key was not accepted (HTTP 401). Subclass of `WherobotsAPIError`. |
|
|
636
|
+
| `WherobotsFilesNotEnabledError` | Files is not enabled for a drive in a region. Has `drive`, `region`. Subclass of `WherobotsAPIError`. |
|
|
637
|
+
| `WherobotsFileNotFoundError` | Files: no such file or folder. Subclass of `WherobotsAPIError`. |
|
|
567
638
|
|
|
568
639
|
```python
|
|
569
640
|
from wherobots import WherobotsAPIError, WherobotsJobError
|
|
@@ -628,10 +699,8 @@ Base URL: `https://api.cloud.wherobots.com`
|
|
|
628
699
|
```bash
|
|
629
700
|
git clone https://github.com/wherobots/wherobots-python-sdk.git
|
|
630
701
|
cd wherobots-python-sdk
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
pip install -e ".[dev]"
|
|
634
|
-
pre-commit install
|
|
702
|
+
uv sync --group dev
|
|
703
|
+
uv run pre-commit install
|
|
635
704
|
```
|
|
636
705
|
|
|
637
706
|
### Quality checks
|
|
@@ -639,10 +708,27 @@ pre-commit install
|
|
|
639
708
|
```bash
|
|
640
709
|
make lint # ruff check + ruff format --check
|
|
641
710
|
make format # auto-fix lint issues and reformat
|
|
642
|
-
make typecheck #
|
|
643
|
-
make
|
|
711
|
+
make typecheck # pyrefly (strict config)
|
|
712
|
+
make rails # import-linter + no-suppression + ast-grep
|
|
713
|
+
make check # lint + typecheck + rails + tests + build
|
|
644
714
|
```
|
|
645
715
|
|
|
716
|
+
### Suppression baseline
|
|
717
|
+
|
|
718
|
+
Pre-existing debt is grandfathered in `.slop-suppressions.json` (CODEOWNED): the inline
|
|
719
|
+
suppressions already in the tree, plus the `# noqa: CODE` / `# pyrefly: ignore` comments that
|
|
720
|
+
`ruff check --add-noqa` and `pyrefly check --suppress-errors` wrote for pre-existing violations.
|
|
721
|
+
The suppressions are functional at the tool level; the keystone
|
|
722
|
+
(`.slop-repellant/check_no_suppression.py`) fails any net-new one.
|
|
723
|
+
|
|
724
|
+
- **Prune** after fixing baselined code (drops stale entries):
|
|
725
|
+
- ruff's `RUF100` auto-removes stale noqas via the pre-commit hook's `--fix`
|
|
726
|
+
- `uv run pyrefly suppress --remove-unused`
|
|
727
|
+
- then `uv run python .slop-repellant/check_no_suppression.py --update`
|
|
728
|
+
- **Reset** (re-record from scratch): delete `.slop-suppressions.json`, then run the same `--update`.
|
|
729
|
+
- Shrinking the baseline is always welcome; **growing it requires CODEOWNER sign-off** — fix the
|
|
730
|
+
code instead of widening the baseline.
|
|
731
|
+
|
|
646
732
|
### Releasing
|
|
647
733
|
|
|
648
734
|
See [`RELEASING.md`](RELEASING.md) for the release flow (version
|
|
@@ -18,6 +18,7 @@ Additional Wherobots surfaces will be added over time.
|
|
|
18
18
|
- List runs with status, name, and date filters
|
|
19
19
|
- Typed dataclass models for all API responses (no pydantic dependency)
|
|
20
20
|
- Environment-based configuration
|
|
21
|
+
- Files: list, create folders, upload, download, rename and delete in your Wherobots file area
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
@@ -33,11 +34,11 @@ The simplest way to run a local script — only an API key is needed:
|
|
|
33
34
|
from wherobots import WherobotsJob
|
|
34
35
|
|
|
35
36
|
job = WherobotsJob(
|
|
36
|
-
script="my_analysis.py",
|
|
37
|
+
script="my_analysis.py", # local file — auto-uploaded via presigned URL
|
|
37
38
|
name="analysis-job-001",
|
|
38
39
|
runtime="tiny",
|
|
39
40
|
region="aws-us-west-2",
|
|
40
|
-
api_key="wbk_...",
|
|
41
|
+
api_key="wbk_...", # or set WHEROBOTS_API_KEY env var
|
|
41
42
|
)
|
|
42
43
|
|
|
43
44
|
run_id = job.submit()
|
|
@@ -101,6 +102,7 @@ The API key can also be set via the `WHEROBOTS_API_KEY` environment variable.
|
|
|
101
102
|
| `WHEROBOTS_API_BASE_URL` | API base URL | `https://api.cloud.wherobots.com` |
|
|
102
103
|
| `WHEROBOTS_VERSION` | Wherobots version | `latest` |
|
|
103
104
|
| `WHEROBOTS_REQUEST_TIMEOUT_SECONDS`| HTTP request timeout (seconds) | `30` |
|
|
105
|
+
| `WHEROBOTS_CLIENT_CHAIN` | Upstream client-attribution chain (see below) | *(none)* |
|
|
104
106
|
|
|
105
107
|
### WherobotsConfig
|
|
106
108
|
|
|
@@ -130,6 +132,35 @@ config = WherobotsConfig(
|
|
|
130
132
|
| `base_url` | `str` | `"https://api.cloud.wherobots.com"` |
|
|
131
133
|
| `version` | `str` | `"latest"` |
|
|
132
134
|
| `request_timeout_seconds` | `int` | `30` |
|
|
135
|
+
| `client_chain` | `str \| None` | `None` |
|
|
136
|
+
|
|
137
|
+
### Client attribution
|
|
138
|
+
|
|
139
|
+
Every request the SDK sends carries an advisory `X-Wherobots-Client` header so
|
|
140
|
+
Wherobots can attribute workloads to the client that started them:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
X-Wherobots-Client: client=python-sdk;ver=0.3.0;plat=darwin
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The header is an ordered, comma-separated chain of hops — leftmost is the
|
|
147
|
+
origin client, and each component appends its own hop on the right. If your
|
|
148
|
+
tool embeds this SDK, pass your own hop as `client_chain` and the SDK appends
|
|
149
|
+
itself after it:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
job = WherobotsJob(
|
|
153
|
+
script="job.py",
|
|
154
|
+
name="my-job-name",
|
|
155
|
+
client_chain="client=airflow;ver=1.2.0",
|
|
156
|
+
)
|
|
157
|
+
# X-Wherobots-Client: client=airflow;ver=1.2.0, client=python-sdk;ver=0.3.0;plat=linux
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`client_chain` also works on `WherobotsJob.from_run_id(...)` and
|
|
161
|
+
`WherobotsConfig`, or can be set once via `WHEROBOTS_CLIENT_CHAIN`. The header
|
|
162
|
+
is advisory only: it is used for attribution and analytics and never affects
|
|
163
|
+
authentication or authorization.
|
|
133
164
|
|
|
134
165
|
## SDK Reference
|
|
135
166
|
|
|
@@ -158,6 +189,7 @@ WherobotsJob(
|
|
|
158
189
|
base_url: str | None = None, # Override API URL
|
|
159
190
|
request_timeout_seconds: int | None = None, # HTTP timeout
|
|
160
191
|
config: WherobotsConfig | None = None, # Full config object
|
|
192
|
+
client_chain: str | None = None, # Upstream X-Wherobots-Client chain
|
|
161
193
|
)
|
|
162
194
|
```
|
|
163
195
|
|
|
@@ -284,7 +316,7 @@ environment.
|
|
|
284
316
|
|
|
285
317
|
```python
|
|
286
318
|
job = WherobotsJob(
|
|
287
|
-
script="my_job.py",
|
|
319
|
+
script="my_job.py", # local script auto-upload still works
|
|
288
320
|
name="job-with-local-wheel",
|
|
289
321
|
dependencies=[
|
|
290
322
|
# Local wheel — uploaded to managed storage at submit() time
|
|
@@ -334,7 +366,7 @@ with FilesAPI.from_config(WherobotsConfig.from_env()) as files:
|
|
|
334
366
|
)
|
|
335
367
|
|
|
336
368
|
job = WherobotsJob(
|
|
337
|
-
script=script_uri,
|
|
369
|
+
script=script_uri, # already-uploaded URI
|
|
338
370
|
name="job-in-integration",
|
|
339
371
|
dependencies=[WherobotsJob.add_file_dependency(wheel_uri)],
|
|
340
372
|
)
|
|
@@ -360,6 +392,49 @@ job = WherobotsJob(
|
|
|
360
392
|
)
|
|
361
393
|
```
|
|
362
394
|
|
|
395
|
+
## Files
|
|
396
|
+
|
|
397
|
+
`Files` is your Wherobots file area ("My Files" in Studio) from Python. Start from a
|
|
398
|
+
`Wherobots` client; it reads `WHEROBOTS_API_KEY` and `WHEROBOTS_REGION`, or takes `api_key=` /
|
|
399
|
+
`region=` overrides, and closes its connection as a context manager.
|
|
400
|
+
|
|
401
|
+
```python
|
|
402
|
+
import wherobots
|
|
403
|
+
from wherobots import Drive, Region
|
|
404
|
+
|
|
405
|
+
with wherobots.Wherobots() as wb:
|
|
406
|
+
drive = wb.files.my_files # same object as wb.files.drive(Drive.MY_FILES)
|
|
407
|
+
eu = wb.files.drive(Drive.MY_FILES, region=Region.EU_WEST_1) # another region
|
|
408
|
+
|
|
409
|
+
drive.make_directory("projects/2026/q3") # creates projects, then projects/2026, then .../q3
|
|
410
|
+
drive.upload_file("report.csv", "projects/2026/q3/report.csv")
|
|
411
|
+
for entry in drive.list_directory("projects/2026/q3"): # "" lists the root
|
|
412
|
+
print(entry.name, entry.is_dir, entry.size, entry.last_modified)
|
|
413
|
+
drive.download_file("projects/2026/q3/report.csv", "local-copy.csv")
|
|
414
|
+
drive.rename_file("projects/2026/q3/report.csv", "final.csv")
|
|
415
|
+
drive.delete_file("projects/2026/q3/final.csv")
|
|
416
|
+
drive.delete_directory("projects")
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
- Each path segment is encoded on its own; empty levels, `.` and `..` are refused before any
|
|
420
|
+
request.
|
|
421
|
+
- Uploads and downloads go straight to storage through short-lived presigned URLs. The API key
|
|
422
|
+
is never sent to storage, and no storage credentials are requested.
|
|
423
|
+
- Uploads are capped at 500 MiB per file, matching the CLI and the Studio web UI; a larger file
|
|
424
|
+
is refused before any request is sent.
|
|
425
|
+
- Listing follows the server's pages until the end and returns every entry.
|
|
426
|
+
- A region is required: with none passed or configured, `wb.files.my_files` raises
|
|
427
|
+
`WherobotsConfigError`.
|
|
428
|
+
|
|
429
|
+
| Failure | Raised as |
|
|
430
|
+
|---|---|
|
|
431
|
+
| Files is not enabled for this drive in this region | `WherobotsFilesNotEnabledError` (`.drive`, `.region`) |
|
|
432
|
+
| The API key was not accepted | `WherobotsNotAuthenticatedError` |
|
|
433
|
+
| No such file or folder | `WherobotsFileNotFoundError` |
|
|
434
|
+
| Transfer to or from storage failed | `WherobotsS3Error` |
|
|
435
|
+
|
|
436
|
+
The three `Wherobots*Error` classes above subclass `WherobotsAPIError`.
|
|
437
|
+
|
|
363
438
|
## Enums
|
|
364
439
|
|
|
365
440
|
### JobStatus
|
|
@@ -527,9 +602,12 @@ All exceptions inherit from `WherobotsJobError`.
|
|
|
527
602
|
| `WherobotsJobError` | Base exception for all SDK errors. |
|
|
528
603
|
| `WherobotsAPIError` | API returned an error response. Has `status_code`, `response`, `request_id` attributes. |
|
|
529
604
|
| `WherobotsValidationError` | Client-side validation failed (e.g., invalid name). |
|
|
530
|
-
| `WherobotsS3Error` |
|
|
605
|
+
| `WherobotsS3Error` | A transfer to or from storage failed, including Files presigned uploads and downloads. |
|
|
531
606
|
| `WherobotsConfigError` | Configuration is missing or invalid. |
|
|
532
607
|
| `WherobotsTimeoutError` | Job exceeded the specified timeout. |
|
|
608
|
+
| `WherobotsNotAuthenticatedError` | Files: the API key was not accepted (HTTP 401). Subclass of `WherobotsAPIError`. |
|
|
609
|
+
| `WherobotsFilesNotEnabledError` | Files is not enabled for a drive in a region. Has `drive`, `region`. Subclass of `WherobotsAPIError`. |
|
|
610
|
+
| `WherobotsFileNotFoundError` | Files: no such file or folder. Subclass of `WherobotsAPIError`. |
|
|
533
611
|
|
|
534
612
|
```python
|
|
535
613
|
from wherobots import WherobotsAPIError, WherobotsJobError
|
|
@@ -594,10 +672,8 @@ Base URL: `https://api.cloud.wherobots.com`
|
|
|
594
672
|
```bash
|
|
595
673
|
git clone https://github.com/wherobots/wherobots-python-sdk.git
|
|
596
674
|
cd wherobots-python-sdk
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
pip install -e ".[dev]"
|
|
600
|
-
pre-commit install
|
|
675
|
+
uv sync --group dev
|
|
676
|
+
uv run pre-commit install
|
|
601
677
|
```
|
|
602
678
|
|
|
603
679
|
### Quality checks
|
|
@@ -605,10 +681,27 @@ pre-commit install
|
|
|
605
681
|
```bash
|
|
606
682
|
make lint # ruff check + ruff format --check
|
|
607
683
|
make format # auto-fix lint issues and reformat
|
|
608
|
-
make typecheck #
|
|
609
|
-
make
|
|
684
|
+
make typecheck # pyrefly (strict config)
|
|
685
|
+
make rails # import-linter + no-suppression + ast-grep
|
|
686
|
+
make check # lint + typecheck + rails + tests + build
|
|
610
687
|
```
|
|
611
688
|
|
|
689
|
+
### Suppression baseline
|
|
690
|
+
|
|
691
|
+
Pre-existing debt is grandfathered in `.slop-suppressions.json` (CODEOWNED): the inline
|
|
692
|
+
suppressions already in the tree, plus the `# noqa: CODE` / `# pyrefly: ignore` comments that
|
|
693
|
+
`ruff check --add-noqa` and `pyrefly check --suppress-errors` wrote for pre-existing violations.
|
|
694
|
+
The suppressions are functional at the tool level; the keystone
|
|
695
|
+
(`.slop-repellant/check_no_suppression.py`) fails any net-new one.
|
|
696
|
+
|
|
697
|
+
- **Prune** after fixing baselined code (drops stale entries):
|
|
698
|
+
- ruff's `RUF100` auto-removes stale noqas via the pre-commit hook's `--fix`
|
|
699
|
+
- `uv run pyrefly suppress --remove-unused`
|
|
700
|
+
- then `uv run python .slop-repellant/check_no_suppression.py --update`
|
|
701
|
+
- **Reset** (re-record from scratch): delete `.slop-suppressions.json`, then run the same `--update`.
|
|
702
|
+
- Shrinking the baseline is always welcome; **growing it requires CODEOWNER sign-off** — fix the
|
|
703
|
+
code instead of widening the baseline.
|
|
704
|
+
|
|
612
705
|
### Releasing
|
|
613
706
|
|
|
614
707
|
See [`RELEASING.md`](RELEASING.md) for the release flow (version
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=
|
|
2
|
+
requires = ["setuptools>=83.0.0", "wheel"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -35,13 +35,19 @@ Repository = "https://github.com/wherobots/wherobots-python-sdk"
|
|
|
35
35
|
Issues = "https://github.com/wherobots/wherobots-python-sdk/issues"
|
|
36
36
|
Documentation = "https://docs.wherobots.com"
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
# PEP 735 dev dependencies — installed with `uv sync --group dev`.
|
|
39
|
+
[dependency-groups]
|
|
39
40
|
dev = [
|
|
40
|
-
"pytest>=
|
|
41
|
+
"pytest>=8.0",
|
|
41
42
|
"pytest-cov>=4.1.0",
|
|
42
43
|
"pytest-mock>=3.11.1",
|
|
43
|
-
"ruff>=0.
|
|
44
|
-
"
|
|
44
|
+
"ruff>=0.15,<0.17",
|
|
45
|
+
"pyrefly>=0.16",
|
|
46
|
+
"import-linter>=2.1",
|
|
47
|
+
"pre-commit>=3.8",
|
|
48
|
+
"hypothesis>=6.100",
|
|
49
|
+
"cosmic-ray>=8.4",
|
|
50
|
+
"ast-grep-cli>=0.39",
|
|
45
51
|
"types-requests>=2.31",
|
|
46
52
|
]
|
|
47
53
|
|
|
@@ -54,44 +60,49 @@ wherobots = ["py.typed"]
|
|
|
54
60
|
[tool.setuptools.dynamic]
|
|
55
61
|
version = {attr = "wherobots.__version__.__version__"}
|
|
56
62
|
|
|
63
|
+
# SLOP REPELLANT ruff rail: line-length 100, py310 target, select = ["ALL"]
|
|
64
|
+
# plus the typing.Any/typing.cast ban all come from the base config.
|
|
65
|
+
# NOTE: `lint.select`/`lint.ignore` would REPLACE the base's — tune only via
|
|
66
|
+
# the extend-* keys.
|
|
57
67
|
[tool.ruff]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
extend = ".slop-repellant/ruff-base.toml"
|
|
69
|
+
# .slop-repellant/ is vendored, kit-MANAGED tooling (re-rendered on
|
|
70
|
+
# slop-repellant-update) — linting it locally would fork the managed files.
|
|
71
|
+
extend-exclude = ["env", ".venv", "venv", "build", "dist", ".slop-repellant"]
|
|
61
72
|
|
|
62
73
|
[tool.ruff.lint]
|
|
63
|
-
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
|
|
64
74
|
# SIM117 merges nested with-statements — often hurts readability when
|
|
65
75
|
# the context managers are logically distinct. Disable.
|
|
66
|
-
|
|
76
|
+
# EM101/EM102/TRY003 (exception-message-as-literal trio): style position,
|
|
77
|
+
# not debt — we keep messages inline at the raise site. Disable globally.
|
|
78
|
+
# PLR0917 (too-many-positional-arguments): newly enabled in ruff 0.16's ALL
|
|
79
|
+
# set. It is the positional-only variant of PLR0913 (too-many-arguments),
|
|
80
|
+
# which the few wide signatures here already carry an accepted `# noqa:
|
|
81
|
+
# PLR0913` for — so PLR0917 is redundant with a complaint the codebase has
|
|
82
|
+
# already signed off on. Ignored globally rather than making those args
|
|
83
|
+
# keyword-only, which would be a semver-breaking public-API change.
|
|
84
|
+
extend-ignore = ["SIM117", "EM101", "EM102", "TRY003", "PLR0917"]
|
|
67
85
|
|
|
68
|
-
[tool.ruff.lint.per-file-ignores]
|
|
86
|
+
[tool.ruff.lint.extend-per-file-ignores]
|
|
69
87
|
# Tests may assert freely, re-raise without chaining, shadow builtins
|
|
70
88
|
# for fixtures, and use mock call patterns that look like unused
|
|
71
|
-
# locals.
|
|
72
|
-
|
|
89
|
+
# locals. N803/N806 fire on @patch-injected MockSession-style names and
|
|
90
|
+
# PLC0415 on deliberate deferred imports — idiomatic in tests, not debt.
|
|
91
|
+
# (Extends the base's tests/** ignore set.)
|
|
92
|
+
"tests/**/*.py" = ["B011", "B904", "F841", "RUF059", "RUF013", "N803", "N806", "PLC0415"]
|
|
93
|
+
# e2e/ holds standalone CLI scripts: printing is their job (T201), and
|
|
94
|
+
# they are intentionally not a package (INP001) with exec bits (EXE001).
|
|
95
|
+
"e2e/**/*.py" = ["T201", "INP001", "EXE001"]
|
|
73
96
|
# Package __init__ groups exports by category with comment headers;
|
|
74
97
|
# don't let ruff re-sort them alphabetically.
|
|
75
98
|
"wherobots/__init__.py" = ["RUF022"]
|
|
76
99
|
|
|
77
|
-
[tool.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
[[tool.mypy.overrides]]
|
|
85
|
-
# Test code is not held to strict typing — productionize incrementally.
|
|
86
|
-
module = "tests.*"
|
|
87
|
-
ignore_errors = true
|
|
88
|
-
|
|
89
|
-
[[tool.mypy.overrides]]
|
|
90
|
-
# boto3 / botocore ship runtime types in some versions but the
|
|
91
|
-
# pre-commit mypy env doesn't install them. Declare the policy once
|
|
92
|
-
# here rather than scattering per-import ``# type: ignore`` comments.
|
|
93
|
-
module = ["boto3", "boto3.*", "botocore", "botocore.*"]
|
|
94
|
-
ignore_missing_imports = true
|
|
100
|
+
[tool.pyrefly]
|
|
101
|
+
project-includes = ["wherobots"]
|
|
102
|
+
check-unannotated-defs = true
|
|
103
|
+
strict-callable-subtyping = true
|
|
104
|
+
permissive-ignores = false
|
|
105
|
+
ignore-errors-in-generated-code = false
|
|
95
106
|
|
|
96
107
|
[tool.pytest.ini_options]
|
|
97
108
|
testpaths = ["tests"]
|
|
@@ -49,7 +49,7 @@ def _make_response(json_data, status_code=200):
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
# =========================================================================== #
|
|
52
|
-
# list()
|
|
52
|
+
# list() # noqa: ERA001
|
|
53
53
|
# =========================================================================== #
|
|
54
54
|
|
|
55
55
|
|
|
@@ -120,7 +120,7 @@ class TestList:
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
# =========================================================================== #
|
|
123
|
-
# create()
|
|
123
|
+
# create() # noqa: ERA001
|
|
124
124
|
# =========================================================================== #
|
|
125
125
|
|
|
126
126
|
|
|
@@ -177,7 +177,7 @@ class TestCreate:
|
|
|
177
177
|
|
|
178
178
|
|
|
179
179
|
# =========================================================================== #
|
|
180
|
-
# get()
|
|
180
|
+
# get() # noqa: ERA001
|
|
181
181
|
# =========================================================================== #
|
|
182
182
|
|
|
183
183
|
|
|
@@ -197,7 +197,7 @@ class TestGet:
|
|
|
197
197
|
|
|
198
198
|
|
|
199
199
|
# =========================================================================== #
|
|
200
|
-
# cancel()
|
|
200
|
+
# cancel() # noqa: ERA001
|
|
201
201
|
# =========================================================================== #
|
|
202
202
|
|
|
203
203
|
|
|
@@ -213,7 +213,7 @@ class TestCancel:
|
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
# =========================================================================== #
|
|
216
|
-
# get_logs()
|
|
216
|
+
# get_logs() # noqa: ERA001
|
|
217
217
|
# =========================================================================== #
|
|
218
218
|
|
|
219
219
|
|
|
@@ -241,7 +241,7 @@ class TestGetLogs:
|
|
|
241
241
|
|
|
242
242
|
|
|
243
243
|
# =========================================================================== #
|
|
244
|
-
# get_metrics()
|
|
244
|
+
# get_metrics() # noqa: ERA001
|
|
245
245
|
# =========================================================================== #
|
|
246
246
|
|
|
247
247
|
|