gridlinegpu 0.1.4__tar.gz → 0.1.6__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 (61) hide show
  1. gridlinegpu-0.1.6/PKG-INFO +249 -0
  2. gridlinegpu-0.1.6/README.md +231 -0
  3. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/pyproject.toml +7 -1
  4. gridlinegpu-0.1.6/src/gridlinegpu/UNICODE-LICENSE.txt +39 -0
  5. gridlinegpu-0.1.6/src/gridlinegpu/__init__.py +26 -0
  6. gridlinegpu-0.1.6/src/gridlinegpu/_source_admission.py +297 -0
  7. gridlinegpu-0.1.6/src/gridlinegpu/_source_capture.py +1617 -0
  8. gridlinegpu-0.1.6/src/gridlinegpu/_source_casefold.json +1559 -0
  9. gridlinegpu-0.1.6/src/gridlinegpu/_source_contract.py +1633 -0
  10. gridlinegpu-0.1.6/src/gridlinegpu/_source_credits.py +61 -0
  11. gridlinegpu-0.1.6/src/gridlinegpu/_source_errors.py +648 -0
  12. gridlinegpu-0.1.6/src/gridlinegpu/_source_history.py +242 -0
  13. gridlinegpu-0.1.6/src/gridlinegpu/_source_interfaces.py +1348 -0
  14. gridlinegpu-0.1.6/src/gridlinegpu/_source_job.py +755 -0
  15. gridlinegpu-0.1.6/src/gridlinegpu/_source_outputs.py +834 -0
  16. gridlinegpu-0.1.6/src/gridlinegpu/_source_public.py +191 -0
  17. gridlinegpu-0.1.6/src/gridlinegpu/_source_release.py +76 -0
  18. gridlinegpu-0.1.6/src/gridlinegpu/_source_results.py +650 -0
  19. gridlinegpu-0.1.6/src/gridlinegpu/_source_run.py +466 -0
  20. gridlinegpu-0.1.6/src/gridlinegpu/_source_submission.py +341 -0
  21. gridlinegpu-0.1.6/src/gridlinegpu/_source_transfer.py +402 -0
  22. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/src/gridlinegpu/cli.py +63 -5
  23. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/src/gridlinegpu/client.py +178 -9
  24. gridlinegpu-0.1.6/src/gridlinegpu/errors.py +176 -0
  25. gridlinegpu-0.1.6/src/gridlinegpu.egg-info/PKG-INFO +249 -0
  26. gridlinegpu-0.1.6/src/gridlinegpu.egg-info/SOURCES.txt +51 -0
  27. gridlinegpu-0.1.6/src/gridlinegpu.egg-info/requires.txt +5 -0
  28. gridlinegpu-0.1.6/tests/test_cli.py +285 -0
  29. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/tests/test_client.py +276 -3
  30. gridlinegpu-0.1.6/tests/test_client_source.py +579 -0
  31. gridlinegpu-0.1.6/tests/test_source_admission.py +517 -0
  32. gridlinegpu-0.1.6/tests/test_source_canary.py +119 -0
  33. gridlinegpu-0.1.6/tests/test_source_capture.py +791 -0
  34. gridlinegpu-0.1.6/tests/test_source_capture_gaps.py +207 -0
  35. gridlinegpu-0.1.6/tests/test_source_contract.py +1228 -0
  36. gridlinegpu-0.1.6/tests/test_source_credits.py +73 -0
  37. gridlinegpu-0.1.6/tests/test_source_curated_run.py +182 -0
  38. gridlinegpu-0.1.6/tests/test_source_errors.py +183 -0
  39. gridlinegpu-0.1.6/tests/test_source_history.py +158 -0
  40. gridlinegpu-0.1.6/tests/test_source_job.py +702 -0
  41. gridlinegpu-0.1.6/tests/test_source_job_results.py +1066 -0
  42. gridlinegpu-0.1.6/tests/test_source_release.py +51 -0
  43. gridlinegpu-0.1.6/tests/test_source_results.py +278 -0
  44. gridlinegpu-0.1.6/tests/test_source_run.py +641 -0
  45. gridlinegpu-0.1.6/tests/test_source_submission.py +236 -0
  46. gridlinegpu-0.1.6/tests/test_source_transfer.py +432 -0
  47. gridlinegpu-0.1.4/PKG-INFO +0 -109
  48. gridlinegpu-0.1.4/README.md +0 -94
  49. gridlinegpu-0.1.4/src/gridlinegpu/__init__.py +0 -6
  50. gridlinegpu-0.1.4/src/gridlinegpu/errors.py +0 -20
  51. gridlinegpu-0.1.4/src/gridlinegpu.egg-info/PKG-INFO +0 -109
  52. gridlinegpu-0.1.4/src/gridlinegpu.egg-info/SOURCES.txt +0 -17
  53. gridlinegpu-0.1.4/src/gridlinegpu.egg-info/requires.txt +0 -1
  54. gridlinegpu-0.1.4/tests/test_cli.py +0 -100
  55. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/setup.cfg +0 -0
  56. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/src/gridlinegpu/__main__.py +0 -0
  57. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/src/gridlinegpu/cli_auth.py +0 -0
  58. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/src/gridlinegpu.egg-info/dependency_links.txt +0 -0
  59. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/src/gridlinegpu.egg-info/entry_points.txt +0 -0
  60. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/src/gridlinegpu.egg-info/top_level.txt +0 -0
  61. {gridlinegpu-0.1.4 → gridlinegpu-0.1.6}/tests/test_cli_auth.py +0 -0
@@ -0,0 +1,249 @@
1
+ Metadata-Version: 2.4
2
+ Name: gridlinegpu
3
+ Version: 0.1.6
4
+ Summary: Python client for Gridline YAML workload routing
5
+ Author: Gridline GPU
6
+ License: Proprietary
7
+ Project-URL: Documentation, https://gridlinegpu.com/cli
8
+ Project-URL: Repository, https://github.com/GridlineGPU/gridline-gpu
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: PyYAML<7,>=6.0.2
15
+ Provides-Extra: source-contract
16
+ Requires-Dist: rfc8785==0.1.4; extra == "source-contract"
17
+ Requires-Dist: unicodedata2==16.0.0; extra == "source-contract"
18
+
19
+ # Gridline Python SDK
20
+
21
+ Submit one YAML workload through Gridline's authenticated API. The Python client stages
22
+ files referenced by YAML, returns a route for review, then launches only that exact
23
+ fresh plan when you supply a cost ceiling and idempotency key. The package also installs
24
+ the `gridline` command in the same Python environment on macOS and Linux.
25
+
26
+ ## Install
27
+
28
+ ```sh
29
+ python -m pip install 'gridlinegpu>=0.1.4,<0.2'
30
+ ```
31
+
32
+ Run browser setup once. It creates a 30-day scoped token in a private file, attempts to
33
+ revoke the temporary browser session, and never prints the token. Keep the token file out of
34
+ source control and AI chats.
35
+
36
+ ```sh
37
+ gridline setup --apply
38
+ ```
39
+
40
+ Use a YAML file beside its referenced inputs. Task, model, engine, providers, output,
41
+ budget, and deadline belong in YAML rather than Python arguments.
42
+ Server-advertised artifact limits govern each submission. Newer Gridline deployments
43
+ use direct, signed resumable Storage uploads for large local inputs; older deployments
44
+ may still cap inputs at 40 MiB and outputs at 8 MiB.
45
+
46
+ ```python
47
+ from gridlinegpu import GridlineClient
48
+
49
+ client = GridlineClient()
50
+ search = client.search("workload.yaml")
51
+ print(search["coverage"], search["offers"]) # Catalog facts may be unknown; coverage may be partial.
52
+ plan = client.plan("workload.yaml")
53
+ print(plan.review) # Check selected GPU, exclusions, estimate, and maximum.
54
+
55
+ # Launch only after reviewing the fresh plan and accepting its maximum spend.
56
+ launch = client.launch(plan, max_cost_usd="5.00", idempotency_key="my-job-2026-09-27")
57
+ job_id = launch["jobId"]
58
+ ready = client.wait(job_id, timeout=7200)
59
+ print(ready["job"]["outputs"], ready["job"]["outcome"])
60
+ print(client.outputs(job_id, "results"))
61
+ ```
62
+
63
+ The same installed package supports a terminal flow:
64
+
65
+ ```sh
66
+ gridline workload search workload.yaml
67
+ gridline workload validate --file workload.yaml
68
+ gridline workload plan --file workload.yaml
69
+ gridline workload run --file workload.yaml --apply --max-cost-usd 5 \
70
+ --idempotency-key my-job-2026-09-27
71
+ gridline workload status JOB_ID
72
+ gridline workload outputs JOB_ID --directory results
73
+ ```
74
+
75
+ The paid `run` command plans a fresh quote, prints it, and asks for explicit confirmation.
76
+ For an approved noninteractive run, add `--yes` and explicit `--profile production`
77
+ alongside `--apply`, a cost ceiling, and an idempotency key. Cancellation similarly
78
+ requires `--apply` and confirmation. A custom
79
+ token path may be passed as global `--token-file` or through `GRIDLINE_API_TOKEN_FILE`.
80
+ Workload commands also honor `GRIDLINE_API_TOKEN` when no token file is selected.
81
+ The existing standalone Bun CLI remains available during transition; check which
82
+ `gridline` executable your shell selects when both are installed. Run
83
+ `python -m gridlinegpu` to choose this package's command explicitly.
84
+ Use `gridline auth token list` to find a token ID and
85
+ `gridline auth token revoke TOKEN_ID --apply` to revoke it. Both require fresh browser
86
+ approval; the Python CLI never stores a privileged refresh token.
87
+ After revoking a token, remove its local file before running setup again; setup never
88
+ overwrites an existing file.
89
+
90
+ `plan()` validates YAML and checks advertised task readiness before reserving or
91
+ uploading inputs. An explicitly unavailable managed ASR/LLM runtime, minimal managed
92
+ model resolver, or custom finite path raises `GridlineError` with code
93
+ `planning_unavailable`. Qualify the custom container and enable its launch path, or
94
+ use available managed runtime/full YAML settings as indicated by the error. Missing
95
+ task readiness fields on older servers preserve existing behavior. Custom endpoint
96
+ support remains a server planning decision; finite readiness does not describe it.
97
+ `plan()` stages input bytes but does not allocate a GPU. `launch()` never silently
98
+ changes a plan or retries an ambiguous paid request. If launch outcome is unknown,
99
+ inspect `LaunchOutcomeUnknown.workload_id` with the same idempotency key; do not use a
100
+ new key. `outputs()` verifies byte count and SHA-256 before creating private files.
101
+ Cancellation requests stop; cleanup and final provider billing can remain pending.
102
+ For finite jobs, `wait()` returns when outputs are complete, even if cleanup and billing
103
+ leave `outcome` pending. Check `status()` later for final settlement.
104
+
105
+ `search()` reads only YAML and connected-provider catalogs. It does not inspect or
106
+ upload referenced inputs, resolve a model or image, create a workload, or allocate a
107
+ GPU. `availability()["searchAvailable"]` is separate from planning and execution
108
+ readiness. Missing price, VRAM, capacity, or topology facts remain unknown. Partial
109
+ catalog coverage and unsupported execution remain visible; a search result is not a
110
+ launch quote. `scopeStatus: "empty_scope"` with `catalogCompleteness: "not_applicable"`
111
+ means no accounts were searched. Servers with catalog search disabled return a
112
+ search availability error. The CLI prints the same JSON response and also accepts
113
+ `gridline workload search --file workload.yaml`.
114
+
115
+ When worker progress is enabled on the hosted API/control service and supported by the
116
+ deployed worker image, `status()` also returns optional `progress`:
117
+
118
+ ```python
119
+ status = client.status(job_id)
120
+ progress = status.get("progress")
121
+ if progress:
122
+ for event in progress["events"]:
123
+ print(event["stage"], event["observedAt"])
124
+ ```
125
+
126
+ Events are bounded, ordered worker reports for `progress["attemptId"]` and its fence.
127
+ Stages include input staging, model preparation/loading, inference start/completion,
128
+ and output upload. `observedAt` is the server's receipt time. Poll no faster than every
129
+ five seconds; deduplicate by attempt/fence/stage. Missing progress or skipped stages
130
+ mean unavailable evidence, not failure or an inferred stage. Inference completion
131
+ does not imply stored outputs, job success, cleanup, or settled billing. `wait()` keeps
132
+ its existing completion behavior and does not stream progress callbacks.
133
+
134
+ ## Source runs (beta)
135
+
136
+ Source runs send your own Python code and inputs to one GPU, run it as a finite job on an
137
+ operator-qualified **curated runtime**, and return your declared output files. They are in
138
+ an invite-only beta, funded by credits Gridline grants to design partners: there is no
139
+ top-up or payment step in the SDK. Install the source extra and turn the beta on in your
140
+ shell:
141
+
142
+ ```sh
143
+ python -m pip install 'gridlinegpu[source-contract]>=0.1.6,<0.2'
144
+ export GRIDLINE_SOURCE_SDK_ENABLED=true
145
+ gridline setup --apply # once: saves a private, scoped token file
146
+ ```
147
+
148
+ Without `GRIDLINE_SOURCE_SDK_ENABLED=true`, `run()` raises `GridlineError` with code
149
+ `source_lane_disabled` and touches no file, token or network.
150
+
151
+ **The workload.** Put a `gridline.yaml` beside your code. Name a curated runtime instead of
152
+ declaring dependencies: `gridline/hf-torch-2.5@r1` provides CPython 3.11 and CUDA 12.4 with
153
+ `torch==2.5.1`, `transformers==4.46.3`, `accelerate==1.1.1`, `safetensors==0.4.5`,
154
+ `tokenizers==0.20.3`, `datasets==3.1.0`, `pandas==2.2.3`, `pyarrow==18.0.0` and
155
+ `numpy==1.26.4`. Do not add `python`, `requirements` or a lockfile next to a curated runtime
156
+ (refused as `source_runtime_curated_conflict`). A curated runtime named before this
157
+ SDK release or the server offers it is refused before anything is uploaded
158
+ (`curated_runtimes_unavailable`).
159
+
160
+ ```yaml
161
+ version: 2
162
+ name: batch-generate
163
+ task:
164
+ kind: application
165
+ class: inference
166
+ source:
167
+ include: [job.py]
168
+ command: [python, job.py]
169
+ runtime: gridline/hf-torch-2.5@r1
170
+ outputs:
171
+ results:
172
+ path: outputs/results.json
173
+ max_bytes: 1048576
174
+ resources:
175
+ gpu:
176
+ count: 1
177
+ minimum_vram_gib: 8
178
+ ```
179
+
180
+ To set environment variables, use the mapping form:
181
+ `runtime: {curated: gridline/hf-torch-2.5@r1, environment: {MY_FLAG: "1"}}`. Temp, cache
182
+ and home directories are the platform's and cannot be redirected (`TMPDIR`,
183
+ `XDG_CACHE_HOME`, `HOME` and `HF_HOME` are refused).
184
+
185
+ **Limits during the beta.**
186
+ - Inputs: each local input file is at most 512 MiB.
187
+ - Outputs: declare each one under `outputs`. Each file is at most 128 MiB (`max_bytes`;
188
+ 8 MiB when omitted), at most 1 GiB in total, and at most 32 outputs per job.
189
+ - No network at run time (details below): the code, inputs and models it needs must
190
+ arrive with the job.
191
+ - Leave out `budget` and `execution.deadline_seconds`: beta routing places jobs best-effort
192
+ and refuses either constraint (`budget_feasibility_unknown` /
193
+ `deadline_feasibility_unknown`).
194
+
195
+ **Run it.** `run()` validates, captures your selected files, uploads them and returns a
196
+ `JobHandle` once the job is admitted. Everything after that happens server-side.
197
+
198
+ ```python
199
+ from gridlinegpu import GridlineClient, SubmissionOutcomeUnknown
200
+
201
+ client = GridlineClient()
202
+ try:
203
+ job = client.run("gridline.yaml")
204
+ except SubmissionOutcomeUnknown as error:
205
+ job = client.resume(error.submission_id) # never run() again for the same submission
206
+ print(job.job_id)
207
+
208
+ result = job.wait() # polls status; a local timeout never cancels the job
209
+ print(result.outcome, result.failure_code)
210
+ if result.succeeded:
211
+ print(job.outputs("results")) # verified files, written under results/
212
+ print(job.report()["settlement"]) # reserved, held, charged and released credits for this job
213
+ ```
214
+
215
+ `job.status()` reads the current state without waiting, and `job.cancel()` requests a stop.
216
+ `client.history(limit=20)` lists your recent jobs, newest first. `report()` and `history()`
217
+ raise `report_unavailable` / `history_unavailable` until those reads are switched on for
218
+ the beta; the job itself is unaffected. A complete minimal
219
+ job is in `examples/workloads/curated-beta/`.
220
+
221
+ The rest of this section describes how source runs behave.
222
+
223
+ **Network.** Source runs have no network access, only Unix domain sockets. No TCP or UDP,
224
+ localhost included. So these fail: local HTTP servers (FastAPI, TGI), Ray, and any pip or
225
+ Hugging Face download at run time. vLLM (offline `LLM()` included) and NCCL or
226
+ torch.distributed (whatever the init method) are expected to fail. The error is typically
227
+ `PermissionError: [Errno 1] Operation not permitted`, or a library's own connection or
228
+ offline error. Declare models and inputs: they are fetched by the platform, not by your
229
+ code, and staged before your code starts. io_uring is not available either. If the
230
+ machine cannot enforce this isolation, your code does not start and the job ends with
231
+ `isolation_unsupported`: "The allocated machine cannot enforce the workload's required
232
+ isolation." Submit a new run; contact support if it repeats.
233
+
234
+ **Pinned models.** Gridline fetches each declared model at its pinned commit. If a pinned
235
+ model file has changed or is missing when it is fetched for your run, the run stops and
236
+ that attempt is used up; it is not retried. A machine was already started, so your
237
+ provider bills its time. During the canary, a used-up attempt costs at most about $0.50:
238
+ up to roughly 20 minutes (15 minutes of startup plus one 5-minute stop cycle) at the $1.50
239
+ per hour price ceiling, disk included.
240
+ Check that pinned models are still public, then run again.
241
+
242
+ **No GPU capacity.** If the selected GPU is not available, no machine is started and that
243
+ attempt is not charged. The status reads "The selected GPU was not available, so no
244
+ machine was started and this attempt was not charged. Trying the next matching offer." If
245
+ no matching GPU is left and no machine was started for the job, it ends with
246
+ `routing_attempts_exhausted`: "No matching GPU was available right now. No machine was
247
+ started for this job, so these attempts were not charged. Try again later or widen the GPU
248
+ choice." A machine that did start is billed by your provider for the time it ran, whatever
249
+ the outcome.
@@ -0,0 +1,231 @@
1
+ # Gridline Python SDK
2
+
3
+ Submit one YAML workload through Gridline's authenticated API. The Python client stages
4
+ files referenced by YAML, returns a route for review, then launches only that exact
5
+ fresh plan when you supply a cost ceiling and idempotency key. The package also installs
6
+ the `gridline` command in the same Python environment on macOS and Linux.
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ python -m pip install 'gridlinegpu>=0.1.4,<0.2'
12
+ ```
13
+
14
+ Run browser setup once. It creates a 30-day scoped token in a private file, attempts to
15
+ revoke the temporary browser session, and never prints the token. Keep the token file out of
16
+ source control and AI chats.
17
+
18
+ ```sh
19
+ gridline setup --apply
20
+ ```
21
+
22
+ Use a YAML file beside its referenced inputs. Task, model, engine, providers, output,
23
+ budget, and deadline belong in YAML rather than Python arguments.
24
+ Server-advertised artifact limits govern each submission. Newer Gridline deployments
25
+ use direct, signed resumable Storage uploads for large local inputs; older deployments
26
+ may still cap inputs at 40 MiB and outputs at 8 MiB.
27
+
28
+ ```python
29
+ from gridlinegpu import GridlineClient
30
+
31
+ client = GridlineClient()
32
+ search = client.search("workload.yaml")
33
+ print(search["coverage"], search["offers"]) # Catalog facts may be unknown; coverage may be partial.
34
+ plan = client.plan("workload.yaml")
35
+ print(plan.review) # Check selected GPU, exclusions, estimate, and maximum.
36
+
37
+ # Launch only after reviewing the fresh plan and accepting its maximum spend.
38
+ launch = client.launch(plan, max_cost_usd="5.00", idempotency_key="my-job-2026-09-27")
39
+ job_id = launch["jobId"]
40
+ ready = client.wait(job_id, timeout=7200)
41
+ print(ready["job"]["outputs"], ready["job"]["outcome"])
42
+ print(client.outputs(job_id, "results"))
43
+ ```
44
+
45
+ The same installed package supports a terminal flow:
46
+
47
+ ```sh
48
+ gridline workload search workload.yaml
49
+ gridline workload validate --file workload.yaml
50
+ gridline workload plan --file workload.yaml
51
+ gridline workload run --file workload.yaml --apply --max-cost-usd 5 \
52
+ --idempotency-key my-job-2026-09-27
53
+ gridline workload status JOB_ID
54
+ gridline workload outputs JOB_ID --directory results
55
+ ```
56
+
57
+ The paid `run` command plans a fresh quote, prints it, and asks for explicit confirmation.
58
+ For an approved noninteractive run, add `--yes` and explicit `--profile production`
59
+ alongside `--apply`, a cost ceiling, and an idempotency key. Cancellation similarly
60
+ requires `--apply` and confirmation. A custom
61
+ token path may be passed as global `--token-file` or through `GRIDLINE_API_TOKEN_FILE`.
62
+ Workload commands also honor `GRIDLINE_API_TOKEN` when no token file is selected.
63
+ The existing standalone Bun CLI remains available during transition; check which
64
+ `gridline` executable your shell selects when both are installed. Run
65
+ `python -m gridlinegpu` to choose this package's command explicitly.
66
+ Use `gridline auth token list` to find a token ID and
67
+ `gridline auth token revoke TOKEN_ID --apply` to revoke it. Both require fresh browser
68
+ approval; the Python CLI never stores a privileged refresh token.
69
+ After revoking a token, remove its local file before running setup again; setup never
70
+ overwrites an existing file.
71
+
72
+ `plan()` validates YAML and checks advertised task readiness before reserving or
73
+ uploading inputs. An explicitly unavailable managed ASR/LLM runtime, minimal managed
74
+ model resolver, or custom finite path raises `GridlineError` with code
75
+ `planning_unavailable`. Qualify the custom container and enable its launch path, or
76
+ use available managed runtime/full YAML settings as indicated by the error. Missing
77
+ task readiness fields on older servers preserve existing behavior. Custom endpoint
78
+ support remains a server planning decision; finite readiness does not describe it.
79
+ `plan()` stages input bytes but does not allocate a GPU. `launch()` never silently
80
+ changes a plan or retries an ambiguous paid request. If launch outcome is unknown,
81
+ inspect `LaunchOutcomeUnknown.workload_id` with the same idempotency key; do not use a
82
+ new key. `outputs()` verifies byte count and SHA-256 before creating private files.
83
+ Cancellation requests stop; cleanup and final provider billing can remain pending.
84
+ For finite jobs, `wait()` returns when outputs are complete, even if cleanup and billing
85
+ leave `outcome` pending. Check `status()` later for final settlement.
86
+
87
+ `search()` reads only YAML and connected-provider catalogs. It does not inspect or
88
+ upload referenced inputs, resolve a model or image, create a workload, or allocate a
89
+ GPU. `availability()["searchAvailable"]` is separate from planning and execution
90
+ readiness. Missing price, VRAM, capacity, or topology facts remain unknown. Partial
91
+ catalog coverage and unsupported execution remain visible; a search result is not a
92
+ launch quote. `scopeStatus: "empty_scope"` with `catalogCompleteness: "not_applicable"`
93
+ means no accounts were searched. Servers with catalog search disabled return a
94
+ search availability error. The CLI prints the same JSON response and also accepts
95
+ `gridline workload search --file workload.yaml`.
96
+
97
+ When worker progress is enabled on the hosted API/control service and supported by the
98
+ deployed worker image, `status()` also returns optional `progress`:
99
+
100
+ ```python
101
+ status = client.status(job_id)
102
+ progress = status.get("progress")
103
+ if progress:
104
+ for event in progress["events"]:
105
+ print(event["stage"], event["observedAt"])
106
+ ```
107
+
108
+ Events are bounded, ordered worker reports for `progress["attemptId"]` and its fence.
109
+ Stages include input staging, model preparation/loading, inference start/completion,
110
+ and output upload. `observedAt` is the server's receipt time. Poll no faster than every
111
+ five seconds; deduplicate by attempt/fence/stage. Missing progress or skipped stages
112
+ mean unavailable evidence, not failure or an inferred stage. Inference completion
113
+ does not imply stored outputs, job success, cleanup, or settled billing. `wait()` keeps
114
+ its existing completion behavior and does not stream progress callbacks.
115
+
116
+ ## Source runs (beta)
117
+
118
+ Source runs send your own Python code and inputs to one GPU, run it as a finite job on an
119
+ operator-qualified **curated runtime**, and return your declared output files. They are in
120
+ an invite-only beta, funded by credits Gridline grants to design partners: there is no
121
+ top-up or payment step in the SDK. Install the source extra and turn the beta on in your
122
+ shell:
123
+
124
+ ```sh
125
+ python -m pip install 'gridlinegpu[source-contract]>=0.1.6,<0.2'
126
+ export GRIDLINE_SOURCE_SDK_ENABLED=true
127
+ gridline setup --apply # once: saves a private, scoped token file
128
+ ```
129
+
130
+ Without `GRIDLINE_SOURCE_SDK_ENABLED=true`, `run()` raises `GridlineError` with code
131
+ `source_lane_disabled` and touches no file, token or network.
132
+
133
+ **The workload.** Put a `gridline.yaml` beside your code. Name a curated runtime instead of
134
+ declaring dependencies: `gridline/hf-torch-2.5@r1` provides CPython 3.11 and CUDA 12.4 with
135
+ `torch==2.5.1`, `transformers==4.46.3`, `accelerate==1.1.1`, `safetensors==0.4.5`,
136
+ `tokenizers==0.20.3`, `datasets==3.1.0`, `pandas==2.2.3`, `pyarrow==18.0.0` and
137
+ `numpy==1.26.4`. Do not add `python`, `requirements` or a lockfile next to a curated runtime
138
+ (refused as `source_runtime_curated_conflict`). A curated runtime named before this
139
+ SDK release or the server offers it is refused before anything is uploaded
140
+ (`curated_runtimes_unavailable`).
141
+
142
+ ```yaml
143
+ version: 2
144
+ name: batch-generate
145
+ task:
146
+ kind: application
147
+ class: inference
148
+ source:
149
+ include: [job.py]
150
+ command: [python, job.py]
151
+ runtime: gridline/hf-torch-2.5@r1
152
+ outputs:
153
+ results:
154
+ path: outputs/results.json
155
+ max_bytes: 1048576
156
+ resources:
157
+ gpu:
158
+ count: 1
159
+ minimum_vram_gib: 8
160
+ ```
161
+
162
+ To set environment variables, use the mapping form:
163
+ `runtime: {curated: gridline/hf-torch-2.5@r1, environment: {MY_FLAG: "1"}}`. Temp, cache
164
+ and home directories are the platform's and cannot be redirected (`TMPDIR`,
165
+ `XDG_CACHE_HOME`, `HOME` and `HF_HOME` are refused).
166
+
167
+ **Limits during the beta.**
168
+ - Inputs: each local input file is at most 512 MiB.
169
+ - Outputs: declare each one under `outputs`. Each file is at most 128 MiB (`max_bytes`;
170
+ 8 MiB when omitted), at most 1 GiB in total, and at most 32 outputs per job.
171
+ - No network at run time (details below): the code, inputs and models it needs must
172
+ arrive with the job.
173
+ - Leave out `budget` and `execution.deadline_seconds`: beta routing places jobs best-effort
174
+ and refuses either constraint (`budget_feasibility_unknown` /
175
+ `deadline_feasibility_unknown`).
176
+
177
+ **Run it.** `run()` validates, captures your selected files, uploads them and returns a
178
+ `JobHandle` once the job is admitted. Everything after that happens server-side.
179
+
180
+ ```python
181
+ from gridlinegpu import GridlineClient, SubmissionOutcomeUnknown
182
+
183
+ client = GridlineClient()
184
+ try:
185
+ job = client.run("gridline.yaml")
186
+ except SubmissionOutcomeUnknown as error:
187
+ job = client.resume(error.submission_id) # never run() again for the same submission
188
+ print(job.job_id)
189
+
190
+ result = job.wait() # polls status; a local timeout never cancels the job
191
+ print(result.outcome, result.failure_code)
192
+ if result.succeeded:
193
+ print(job.outputs("results")) # verified files, written under results/
194
+ print(job.report()["settlement"]) # reserved, held, charged and released credits for this job
195
+ ```
196
+
197
+ `job.status()` reads the current state without waiting, and `job.cancel()` requests a stop.
198
+ `client.history(limit=20)` lists your recent jobs, newest first. `report()` and `history()`
199
+ raise `report_unavailable` / `history_unavailable` until those reads are switched on for
200
+ the beta; the job itself is unaffected. A complete minimal
201
+ job is in `examples/workloads/curated-beta/`.
202
+
203
+ The rest of this section describes how source runs behave.
204
+
205
+ **Network.** Source runs have no network access, only Unix domain sockets. No TCP or UDP,
206
+ localhost included. So these fail: local HTTP servers (FastAPI, TGI), Ray, and any pip or
207
+ Hugging Face download at run time. vLLM (offline `LLM()` included) and NCCL or
208
+ torch.distributed (whatever the init method) are expected to fail. The error is typically
209
+ `PermissionError: [Errno 1] Operation not permitted`, or a library's own connection or
210
+ offline error. Declare models and inputs: they are fetched by the platform, not by your
211
+ code, and staged before your code starts. io_uring is not available either. If the
212
+ machine cannot enforce this isolation, your code does not start and the job ends with
213
+ `isolation_unsupported`: "The allocated machine cannot enforce the workload's required
214
+ isolation." Submit a new run; contact support if it repeats.
215
+
216
+ **Pinned models.** Gridline fetches each declared model at its pinned commit. If a pinned
217
+ model file has changed or is missing when it is fetched for your run, the run stops and
218
+ that attempt is used up; it is not retried. A machine was already started, so your
219
+ provider bills its time. During the canary, a used-up attempt costs at most about $0.50:
220
+ up to roughly 20 minutes (15 minutes of startup plus one 5-minute stop cycle) at the $1.50
221
+ per hour price ceiling, disk included.
222
+ Check that pinned models are still public, then run again.
223
+
224
+ **No GPU capacity.** If the selected GPU is not available, no machine is started and that
225
+ attempt is not charged. The status reads "The selected GPU was not available, so no
226
+ machine was started and this attempt was not charged. Trying the next matching offer." If
227
+ no matching GPU is left and no machine was started for the job, it ends with
228
+ `routing_attempts_exhausted`: "No matching GPU was available right now. No machine was
229
+ started for this job, so these attempts were not charged. Try again later or widen the GPU
230
+ choice." A machine that did start is billed by your provider for the time it ran, whatever
231
+ the outcome.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gridlinegpu"
7
- version = "0.1.4"
7
+ version = "0.1.6"
8
8
  description = "Python client for Gridline YAML workload routing"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -17,6 +17,9 @@ classifiers = [
17
17
  "Operating System :: OS Independent",
18
18
  ]
19
19
 
20
+ [project.optional-dependencies]
21
+ source-contract = ["rfc8785==0.1.4", "unicodedata2==16.0.0"]
22
+
20
23
  [project.scripts]
21
24
  gridline = "gridlinegpu.cli:main"
22
25
 
@@ -26,3 +29,6 @@ Repository = "https://github.com/GridlineGPU/gridline-gpu"
26
29
 
27
30
  [tool.setuptools.packages.find]
28
31
  where = ["src"]
32
+
33
+ [tool.setuptools.package-data]
34
+ gridlinegpu = ["_source_casefold.json", "UNICODE-LICENSE.txt"]
@@ -0,0 +1,39 @@
1
+ UNICODE LICENSE V3
2
+
3
+ COPYRIGHT AND PERMISSION NOTICE
4
+
5
+ Copyright © 1991-2026 Unicode, Inc.
6
+
7
+ NOTICE TO USER: Carefully read the following legal agreement. BY
8
+ DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
9
+ SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
10
+ TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
11
+ DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a
14
+ copy of data files and any associated documentation (the "Data Files") or
15
+ software and any associated documentation (the "Software") to deal in the
16
+ Data Files or Software without restriction, including without limitation
17
+ the rights to use, copy, modify, merge, publish, distribute, and/or sell
18
+ copies of the Data Files or Software, and to permit persons to whom the
19
+ Data Files or Software are furnished to do so, provided that either (a)
20
+ this copyright and permission notice appear with all copies of the Data
21
+ Files or Software, or (b) this copyright and permission notice appear in
22
+ associated Documentation.
23
+
24
+ THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
25
+ KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
27
+ THIRD PARTY RIGHTS.
28
+
29
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
30
+ BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
31
+ OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
32
+ WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
33
+ ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
34
+ FILES OR SOFTWARE.
35
+
36
+ Except as contained in this notice, the name of a copyright holder shall
37
+ not be used in advertising or otherwise to promote the sale, use or other
38
+ dealings in these Data Files or Software without prior written
39
+ authorization of the copyright holder.
@@ -0,0 +1,26 @@
1
+ """Gridline YAML workload client."""
2
+
3
+ from .client import GridlineClient, PlannedWorkload
4
+ from .errors import (
5
+ GridlineError, LaunchOutcomeUnknown, OutputsIncomplete, ReceiptPersistenceError, SubmissionOutcomeUnknown,
6
+ WaitTimeout, WorkloadValidationError,
7
+ )
8
+
9
+ __all__ = [
10
+ "GridlineClient", "GridlineError", "LaunchOutcomeUnknown", "OutputsIncomplete", "PlannedWorkload",
11
+ "ReceiptPersistenceError", "SubmissionOutcomeUnknown", "WaitTimeout", "WorkloadValidationError",
12
+ ]
13
+
14
+
15
+ def __getattr__(name):
16
+ """PEP 562: ``JobHandle`` needs the optional ``source-contract`` extra, so it is imported on
17
+ first use. Without the extra, ``gridlinegpu.JobHandle`` raises GridlineError
18
+ ``source_dependencies_missing``, which is also an AttributeError (``hasattr`` is False; a
19
+ ``from gridlinegpu import JobHandle`` raises Python's ImportError). It joins ``__all__``
20
+ at activation, when the extra becomes a base dependency."""
21
+ if name == "JobHandle":
22
+ from ._source_public import job_handle_type
23
+
24
+ value = globals()["JobHandle"] = job_handle_type()
25
+ return value
26
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")