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.
Files changed (47) hide show
  1. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/PKG-INFO +105 -19
  2. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/README.md +104 -11
  3. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/pyproject.toml +42 -31
  4. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_api.py +6 -6
  5. wherobots_python_sdk-0.5.0/tests/test_base_client.py +433 -0
  6. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_client.py +32 -0
  7. wherobots_python_sdk-0.5.0/tests/test_client_header.py +216 -0
  8. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_config.py +14 -0
  9. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_exceptions.py +2 -3
  10. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_files_api.py +2 -2
  11. wherobots_python_sdk-0.5.0/tests/test_files_drive.py +811 -0
  12. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_init_exports.py +18 -0
  13. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_integration.py +1 -1
  14. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_models.py +17 -0
  15. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_security.py +4 -4
  16. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/__init__.py +16 -1
  17. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/__version__.py +1 -1
  18. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/api/__init__.py +2 -1
  19. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/api/base.py +98 -34
  20. wherobots_python_sdk-0.5.0/wherobots/api/drive.py +481 -0
  21. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/api/files.py +11 -10
  22. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/api/runs.py +8 -8
  23. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/client.py +34 -22
  24. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/config.py +15 -4
  25. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/enums.py +10 -0
  26. wherobots_python_sdk-0.5.0/wherobots/exceptions.py +81 -0
  27. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/models.py +152 -20
  28. wherobots_python_sdk-0.5.0/wherobots/sdk.py +91 -0
  29. wherobots_python_sdk-0.5.0/wherobots/utils/client_header.py +205 -0
  30. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/utils/validation.py +1 -1
  31. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots_python_sdk.egg-info/PKG-INFO +105 -19
  32. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots_python_sdk.egg-info/SOURCES.txt +5 -0
  33. wherobots_python_sdk-0.5.0/wherobots_python_sdk.egg-info/requires.txt +2 -0
  34. wherobots_python_sdk-0.3.0/tests/test_base_client.py +0 -213
  35. wherobots_python_sdk-0.3.0/wherobots/exceptions.py +0 -47
  36. wherobots_python_sdk-0.3.0/wherobots_python_sdk.egg-info/requires.txt +0 -10
  37. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/LICENSE +0 -0
  38. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/setup.cfg +0 -0
  39. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_enums.py +0 -0
  40. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_logger.py +0 -0
  41. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_regressions.py +0 -0
  42. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/tests/test_utils.py +0 -0
  43. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/py.typed +0 -0
  44. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/utils/__init__.py +0 -0
  45. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots/utils/logger.py +0 -0
  46. {wherobots_python_sdk-0.3.0 → wherobots_python_sdk-0.5.0}/wherobots_python_sdk.egg-info/dependency_links.txt +0 -0
  47. {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.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", # local file — auto-uploaded via presigned URL
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_...", # or set WHEROBOTS_API_KEY env var
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", # local script auto-upload still works
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, # already-uploaded 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` | S3 upload or access operation failed. Deprecated — presigned uploads raise `WherobotsAPIError` instead. |
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
- python -m venv .venv
632
- source .venv/bin/activate
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 # mypy (strict)
643
- make check # lint + typecheck + tests + build
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", # local file — auto-uploaded via presigned URL
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_...", # or set WHEROBOTS_API_KEY env var
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", # local script auto-upload still works
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, # already-uploaded 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` | S3 upload or access operation failed. Deprecated — presigned uploads raise `WherobotsAPIError` instead. |
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
- python -m venv .venv
598
- source .venv/bin/activate
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 # mypy (strict)
609
- make check # lint + typecheck + tests + build
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>=45", "wheel"]
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
- [project.optional-dependencies]
38
+ # PEP 735 dev dependencies — installed with `uv sync --group dev`.
39
+ [dependency-groups]
39
40
  dev = [
40
- "pytest>=7.4.0",
41
+ "pytest>=8.0",
41
42
  "pytest-cov>=4.1.0",
42
43
  "pytest-mock>=3.11.1",
43
- "ruff>=0.6.0",
44
- "mypy>=1.5.0",
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
- line-length = 100
59
- target-version = "py310"
60
- extend-exclude = ["env", ".venv", "venv", "build", "dist"]
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
- ignore = ["SIM117"]
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
- "tests/**/*.py" = ["B011", "B904", "F841", "RUF059", "RUF013"]
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.mypy]
78
- python_version = "3.10"
79
- strict = true
80
- files = ["wherobots"]
81
- warn_unused_ignores = true
82
- show_error_codes = true
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