3lc-compute-plugin-sdk 0.2.3__tar.gz → 0.3.1__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 (50) hide show
  1. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/.github/workflows/release.yml +27 -1
  2. 3lc_compute_plugin_sdk-0.3.1/CHANGELOG.md +189 -0
  3. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/CLAUDE.md +22 -20
  4. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/PKG-INFO +20 -16
  5. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/README.md +18 -14
  6. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/docs/api.md +7 -13
  7. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/docs/plugin-guide.md +99 -45
  8. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/pyproject.toml +19 -24
  9. 3lc_compute_plugin_sdk-0.3.1/src/tlc_plugin_sdk/__init__.py +42 -0
  10. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/asgi_app.py +17 -9
  11. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/contract/plugin-api.d.ts +105 -27
  12. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/contract.py +29 -24
  13. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/job_context.py +48 -10
  14. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/data_source_routes.py +79 -6
  15. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/data_source_ui.py +35 -4
  16. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/job_tracker.py +22 -2
  17. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/labels.py +11 -9
  18. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/modality.py +7 -27
  19. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/worker.py +5 -2
  20. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/tests/test_data_source_routes.py +73 -0
  21. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/tests/test_import_light.py +2 -2
  22. 3lc_compute_plugin_sdk-0.3.1/tests/test_job_context.py +139 -0
  23. 3lc_compute_plugin_sdk-0.3.1/tests/test_js_contract_surface.py +50 -0
  24. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/uv.lock +3 -7
  25. 3lc_compute_plugin_sdk-0.2.3/CHANGELOG.md +0 -87
  26. 3lc_compute_plugin_sdk-0.2.3/src/tlc_plugin_sdk/__init__.py +0 -58
  27. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/.github/workflows/ci.yml +0 -0
  28. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/.github/workflows/docs.yml +0 -0
  29. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/.gitignore +0 -0
  30. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/LICENSE +0 -0
  31. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/docs/conf.py +0 -0
  32. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/docs/index.md +0 -0
  33. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/docs/package-lock.json +0 -0
  34. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/docs/package.json +0 -0
  35. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/docs/requirements.txt +0 -0
  36. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/py.typed +0 -0
  37. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/__init__.py +0 -0
  38. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/alias_override_ui.py +0 -0
  39. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/alias_ui.py +0 -0
  40. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/aliases.py +0 -0
  41. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/config_store.py +0 -0
  42. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/config_ui.py +0 -0
  43. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/generic_job.py +0 -0
  44. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/images.py +0 -0
  45. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/model_storage.py +0 -0
  46. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/naming.py +0 -0
  47. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/ui_inject.py +0 -0
  48. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/src/tlc_plugin_sdk/shared/url_utils.py +0 -0
  49. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/tests/test_url_utils.py +0 -0
  50. {3lc_compute_plugin_sdk-0.2.3 → 3lc_compute_plugin_sdk-0.3.1}/tests/test_worker_gpu_reclaim.py +0 -0
@@ -28,7 +28,7 @@ jobs:
28
28
  runs-on: ubuntu-latest
29
29
  environment: release
30
30
  permissions:
31
- contents: read
31
+ contents: write # create the GitHub Release (step below)
32
32
  id-token: write # OIDC token for PyPI Trusted Publishing
33
33
  steps:
34
34
  - uses: actions/checkout@v4
@@ -53,6 +53,32 @@ jobs:
53
53
  - name: Publish to PyPI
54
54
  run: uv publish --trusted-publishing always
55
55
 
56
+ # Publish the tagged version's changelog section as a GitHub Release (curated notes,
57
+ # not GitHub's commit list). The Release stays editable after publish for touch-up.
58
+ - name: Extract changelog section
59
+ id: changelog
60
+ run: |
61
+ VER="${GITHUB_REF_NAME#v}"
62
+ awk -v v="$VER" '$0 ~ "^## \\[" v "\\]" {f=1; next} /^## \[/ && f {exit} f' \
63
+ CHANGELOG.md > notes.md
64
+ echo "ver=$VER" >> "$GITHUB_OUTPUT"
65
+ { echo "notes<<EOF"; cat notes.md; echo EOF; } >> "$GITHUB_OUTPUT"
66
+
67
+ - name: Create GitHub Release
68
+ uses: softprops/action-gh-release@v2
69
+ with:
70
+ tag_name: ${{ github.ref_name }}
71
+ name: ${{ github.ref_name }}
72
+ body: |
73
+ ${{ steps.changelog.outputs.notes }}
74
+
75
+ ## Installation
76
+ ```bash
77
+ pip install 3lc-compute-plugin-sdk==${{ steps.changelog.outputs.ver }}
78
+ ```
79
+ draft: false
80
+ generate_release_notes: false
81
+
56
82
  # Manual (workflow_dispatch) runs → build-numbered prereleases to the legacy CloudRepo index.
57
83
  publish-cloudrepo-prerelease:
58
84
  if: github.event_name == 'workflow_dispatch'
@@ -0,0 +1,189 @@
1
+ # Changelog
2
+
3
+ All notable changes to `3lc-compute-plugin-sdk` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.3.1] - 2026-08-27
11
+
12
+ A patch on the 0.3 line, additive and pin-compatible (`>=0.3.0,<0.4.0` resolves it). The SDK now
13
+ brings the `3lc` data plane as a base dependency, and the shared data-source browse widget gains an
14
+ input/output picker split.
15
+
16
+ ### Added
17
+ - The shared data-source browse widget shows a small caption ("Showing files matching:
18
+ `<glob>`") when a field declares an `accept` glob, so users understand why some files in a
19
+ listed folder don't appear as selectable — folders themselves are never glob-filtered.
20
+
21
+ ### Changed
22
+ - **`3lc[pandas]` is now a base dependency.** Installing `3lc-compute-plugin-sdk` brings the 3LC
23
+ data plane, so a plugin pins the SDK alone and gets `tlc` and every `shared.*` helper with it.
24
+ The former `[shared]` extra is kept as an empty, deprecated alias — existing
25
+ `3lc-compute-plugin-sdk[shared]` pins keep resolving — and will be removed at 1.0. Drop the
26
+ suffix when you raise your floor to a release carrying this change. Nothing changes at import
27
+ time: `tlc` is still loaded lazily, only by the helpers that use it.
28
+ - **Docs:** clarified `runtime.provision_extra` in the plugin guide — it names the
29
+ optional-dependency group the host installs into your plugin's venv (keeping the base light for
30
+ discovery and letting one distribution carry several plugins), not an "umbrella-only, required"
31
+ key. Restated that isolation is venv-only: every plugin runs from a host-built managed venv,
32
+ with no in-process/host mode and no `.venv` beside the source. No contract or code change.
33
+ - **Data-source browse widget:** the widget now takes a `purpose` config axis (`'input'`, the
34
+ default, or `'output'`), mirrored by a `purpose` query param on `/browse`. Input pickers now
35
+ **hide** folders the compute process cannot open (rather than showing them disabled), so the
36
+ listing only offers places you can actually descend into. Output pickers show **all** folders
37
+ and instead flag the ones that are not writable — dimmed with a lock glyph and a tooltip, but
38
+ still navigable so you can descend to a writable subdirectory — and disable "Select This
39
+ Folder" when the current directory is not writable. In output mode `/browse` returns a
40
+ `writable` flag on each directory entry and for the listed directory itself (computed only in
41
+ output mode; an older host that omits the flag is treated as writable).
42
+
43
+ ### Fixed
44
+ - **Data-source browse widget:** folders the compute process cannot actually open (e.g. macOS
45
+ TCC-protected packages like `Photos Library.photoslibrary`) are now marked inaccessible and
46
+ rendered disabled — dimmed, with a lock glyph and a tooltip — instead of listed as navigable
47
+ and then failing with a red "Permission denied" panel when clicked. The `/browse` route probes
48
+ each directory's openability with `os.scandir` (not `os.access`, which TCC packages spuriously
49
+ pass) and tags directory entries with an `accessible` flag; an older host that omits the flag
50
+ keeps working (entries are treated as accessible).
51
+
52
+ ## [0.3.0] - 2026-08-25
53
+
54
+ A coordinated contract wave: breaking **for plugin authors** but **not on the wire** — the
55
+ worker→host NDJSON events are unchanged, so a host keeps running a plugin venv still on 0.2.x
56
+ (skew badge only). The whole first-party fleet re-pins `>=0.3.0,<0.4.0` in the same release.
57
+
58
+ ### Added
59
+ - `JobContext.fail(message)` raises `JobFailed` (exported from `tlc_plugin_sdk`); the worker
60
+ reports it as the terminal `error` event with the **bare** message, while any other exception
61
+ keeps its `TypeName: …` prefix.
62
+ - `PluginJobs.list(pluginId?)` — `GET {compute}/api/plugins/jobs`, filtered client-side by
63
+ `plugin_id`. Use it to **seed a fragment on mount** from the durable host job list (the
64
+ fragment is torn down on navigation and `job_update` is live-only). Declared in the `.d.ts`.
65
+ - `PluginJobs.connect(namespace)` — warm the namespace socket on mount. `track()`/`on()`
66
+ connect lazily and SocketIO does not replay events to a not-yet-connected client, so a
67
+ fragment listening for custom events from the first second (or re-attaching to a job it did
68
+ not start) calls this first; `run()` already connects for you.
69
+ - The host's `/ui` handler now **auto-injects** the `PluginJobs` client into every fragment; the
70
+ client is idempotent, so a plugin that still injects it by hand keeps working.
71
+ - `ComputePlugin.compute()` ships a default (returns an error dict), so only `get_ui_fragment()`
72
+ stays abstract.
73
+ - `.d.ts` truth pass: `PluginJobUpdate.error`, `PluginFetchOptions.allowErrorStatus`,
74
+ `PluginJobsApi.list`, `TlcLocationApi.rowsSpanLocations`/`shortLabel`, and a new
75
+ **Legacy globals** ambient block (`openTablePicker`/`closeTablePicker`/`CancelJob`/`cssVar`
76
+ and the injected `_tlc*` helpers).
77
+
78
+ ### Changed
79
+ - **One contract axis.** `PY_CONTRACT` and `JS_CONTRACT` are removed; `SDK_CONTRACT_VERSION`
80
+ (the package version) is the whole contract, compared on `MAJOR.MINOR`. `/health` reports
81
+ `sdk_version` only (no `py_contract`/`js_contract`).
82
+ - `JobContext.result` takes a **positional** `url` (`ctx.result(url)`); it opens the Open
83
+ button — a run *or* a table URL. The `run_url=` keyword is gone; the emitted `result` event
84
+ still carries `run_url` (wire unchanged).
85
+ - `JobContext.progress` documents `percent=-1` as indeterminate.
86
+
87
+ ### Removed
88
+ - `shared.modality.detect_modality_from_url` (zero users). `detect_modality_from_schema`,
89
+ `classify_metrics_columns`, `labels.candidate_label_paths`, and `labels.find_label_path`
90
+ are now internal (underscore-prefixed). Public alternatives: `detect_modality_from_table`
91
+ for modality; `get_label_map` / `get_label_names` / `find_label_column` for labels.
92
+ `classify_metrics_columns` was a plugin-specific column-name heuristic with no public
93
+ replacement — inline it (its one known user did).
94
+ - The orphan `_ICON_SVG` constant; the dead `params.setdefault("url", "")` in the `/compute`
95
+ handler.
96
+
97
+ ### Migration
98
+ - `ctx.result(run_url=x)` → `ctx.result(x)`.
99
+ - Validation failures: raise, or `ctx.fail("message")` for a clean user-facing card.
100
+ - Reading `tlc_plugin_sdk.PY_CONTRACT` / `JS_CONTRACT`, or a worker's `/health`
101
+ `py_contract`/`js_contract`: use `SDK_CONTRACT_VERSION` / `sdk_version` and compare on
102
+ `MAJOR.MINOR`.
103
+ - `shared.modality.detect_modality_from_url(url)` → load the table yourself and call
104
+ `detect_modality_from_table(table)`. `classify_metrics_columns(columns)` → copy the
105
+ heuristic into your plugin.
106
+ - A fragment that kept its own always-connected socket for custom events: switch to
107
+ `PluginJobs.on(namespace, event, handler)` and call `PluginJobs.connect(namespace)` on mount
108
+ so nothing emitted before the first `track()` is missed.
109
+ - Fragments may delete their manual `inject_scripts(raw, job_tracker_script())` — the host
110
+ injects `PluginJobs` now (harmless to keep).
111
+ - The manifest never declared the SocketIO namespace or `sdk_version`; both were dead and are
112
+ documented as such. The namespace is host-derived as `/<plugin-id>`.
113
+
114
+ ## [0.2.3] - 2026-08-21
115
+
116
+ ### Changed
117
+ - Docs: the plugin guide and API docstrings describe the single runtime — a plugin runs in
118
+ its own venv behind a worker; the manifest's `isolation` key accepts only `"venv"` (the
119
+ default when absent). The guide's isolation table, the in-tree host exception callout, and
120
+ the `unload` endpoint reference are gone; the dev-workflow section now documents the
121
+ folder-Source + editable-venv + `worker/stop` loop. No code or contract change.
122
+
123
+ ## [0.2.2] - 2026-08-18
124
+
125
+ ### Added
126
+ - Shared data-source picker (`shared.data_source_ui`) and its browse/upload route handlers
127
+ (`shared.data_source_routes`): plugins mount one consistent widget for choosing a local
128
+ path or uploading a file, instead of each fragment rolling its own (#11).
129
+
130
+ ### Changed
131
+ - **Distribution moved to PyPI**: `3lc-compute-plugin-sdk` is now published to public
132
+ [PyPI](https://pypi.org/project/3lc-compute-plugin-sdk/) via Trusted Publishing; the private
133
+ CloudRepo index (pypi.3lc.ai) is no longer needed to install the SDK. Manual prerelease
134
+ builds keep publishing to CloudRepo for a grace period (#13).
135
+ - The `[shared]` extra's `3lc` dependency resolves from public PyPI (its home since the 3.2
136
+ rust release), so the SDK no longer pins a custom package index (#12).
137
+
138
+ ### Security
139
+ - The data-source `/browse` route is confined to operator-configured roots
140
+ (`TLC_DATA_SOURCE_ROOTS`, `os.pathsep`-separated; default: the service user's home).
141
+ Every requested path is realpath-resolved before the containment check, so `..`
142
+ segments and symlinks pointing outside a root are denied rather than followed, and
143
+ the widget's breadcrumb stops at the root instead of offering the whole filesystem (#11).
144
+ - The data-source `/upload-temp` route strips directory components from the
145
+ client-supplied filename (closing a path traversal where a `../…` name chose the
146
+ write location), lands each upload in its own private temp directory so same-named
147
+ uploads never clobber each other, and rejects bodies larger than
148
+ `TLC_DATA_SOURCE_MAX_UPLOAD_MB` (default 512) (#11).
149
+
150
+ ## [0.2.1] - 2026-08-13
151
+
152
+ ### Added
153
+ - Tilde expansion and local-path normalization helpers in `shared.url_utils`
154
+ (`normalize_local_path`, `normalize_url`): user-supplied paths expand `~` and resolve
155
+ to absolute form at every ingress, so exported files and stored URLs never carry a
156
+ user-relative path (#10).
157
+
158
+ ## [0.2.0] - 2026-08-05
159
+
160
+ ### Added
161
+ - JS_CONTRACT 0.2: project locations (`TlcLocation`) are available to plugin UI fragments
162
+ through `PLUGIN_API`, so plugins can resolve and present where a project's data lives (#8).
163
+ - The plugin worker reports the SDK contract version from its `/health` endpoint, letting the
164
+ host enforce compatibility floors precisely instead of guessing (#6).
165
+
166
+ ### Changed
167
+ - The plugin worker releases cached GPU memory after every job, so a finished GPU job no longer
168
+ pins CUDA memory that other plugins need (#7).
169
+
170
+ ## [0.1.1] - 2026-07-03
171
+
172
+ ### Changed
173
+ - **Distribution renamed**: `3lc-plugin-sdk` is now published as `3lc-compute-plugin-sdk`
174
+ (the import name `tlc_plugin_sdk` is unchanged). Update your dependency declarations.
175
+
176
+ ### Fixed
177
+ - Regenerated `uv.lock` for the renamed distribution; release builds append the run number as a
178
+ build counter.
179
+
180
+ ## [0.1.0] - 2026-06-30
181
+
182
+ First public release of the plugin SDK for the 3LC compute service.
183
+
184
+ ### Added
185
+ - The plugin contract: `ComputePlugin`, `JobContext`, and the `PLUGIN_API` browser bridge
186
+ (JS_CONTRACT) that plugin UI fragments use to talk to the host.
187
+ - Slim base package with a `[shared]` extra for common plugin dependencies.
188
+ - Apache-2.0 license, public CI, and a documentation site including the plugin author guide and
189
+ a rendered browser-contract reference.
@@ -18,16 +18,16 @@ in a separate repository and is **not** a dependency of this SDK.
18
18
  host-side). The SDK is the root of the dependency graph; the host depends on the SDK, never the
19
19
  reverse. The whole point — a plugin built against just this wheel runs in its own isolated venv.
20
20
  2. **Import-light.** Importing `tlc_plugin_sdk` must not eagerly pull the server stack *or the data
21
- plane*. `litestar` and `uvicorn` are base deps but are imported **lazily** (only when a worker
22
- actually serves); `tlc` is an optional extra imported lazily only by `shared.*`; and the
23
- host-only SocketIO server must not be importable from here at all. `tests/test_import_light.py`
24
- enforces this (guards `litestar`/`socketio`/`uvicorn`/`tlc`) — keep it green.
25
- 3. **Dependencies stay minimal.** Base = `uvicorn` + `litestar` only. `3lc` is an optional extra
26
- (`3lc-compute-plugin-sdk[shared]`, named for the `shared.*` module it unlocks) used solely by those
27
- helpers — the contract core needs no data plane, so light consumers (e.g. the Hub frontend)
28
- install the bare SDK. Adding a base dep widens what every plugin venv must install — justify it.
29
- (Direction: as `shared.*` graduates into the core, `3lc` likely returns to base with `[shared]`
30
- kept as a no-op alias.)
21
+ plane*. `litestar`, `uvicorn`, and `tlc` are all base deps but are imported **lazily** — the
22
+ server stack only when a worker actually serves, `tlc` only inside the `shared.*` helpers that
23
+ use it; and the host-only SocketIO server must not be importable from here at all.
24
+ `tests/test_import_light.py` enforces this (guards `litestar`/`socketio`/`uvicorn`/`tlc`) —
25
+ keep it green. Import-light is about worker startup time, not about what is installed.
26
+ 3. **Dependencies stay minimal.** Base = `uvicorn` + `litestar` + `3lc[pandas]`, nothing else.
27
+ `3lc` is deliberately a base dep, not an extra: every real plugin uses the data plane, and making
28
+ it optional only created a way to build a venv that imports fine and fails on first use. (The
29
+ `[shared]` extra it used to live behind is an empty, deprecated alias — gone at 1.0.) Adding a
30
+ base dep widens what every plugin venv must install — justify it.
31
31
  4. **The contract is published — every public symbol is forever-ish.** The 0.x line is
32
32
  additive-only (see README → Status): additions are the safe move; reshaping
33
33
  `JobContext`/`ComputePlugin` waits for a major bump. The version is the contract version
@@ -50,9 +50,8 @@ and how to port an existing plugin.
50
50
 
51
51
  ## Dev setup
52
52
 
53
- `3lc` resolves from the public 3LC releases index (`[tool.uv.index]` in `pyproject.toml`). If
54
- your platform lacks a prebuilt wheel there, override locally (uncommitted) with an editable
55
- path source.
53
+ `3lc` resolves from public PyPI. To develop against an unreleased `3lc`, override locally
54
+ (uncommitted) with an editable path source in `[tool.uv.sources]`.
56
55
 
57
56
  ```bash
58
57
  uv sync # installs the SDK + dev tools (ruff, mypy, pytest)
@@ -83,10 +82,11 @@ section is the condensed working order.
83
82
  - **Config + GPU job + SocketIO**: sam3, yolo, or timm
84
83
  - **Inline data display (no standalone page)**: `tlc_plugin_table_statistics`
85
84
  - **Action on selected resources**: `tlc_plugin_merger`
86
- 3. Read `src/tlc_plugin_sdk/contract.py` — `ComputePlugin` is an `abc.ABC` with two abstract
87
- methods (`get_ui_fragment()`, `compute()`) and an `id` attribute the host stamps from the
88
- manifest. The optional hooks (`run_job`, `initialise_runtime`, `shutdown_runtime`,
89
- `get_route_handlers`) ship as no-op defaults, so you override only what you need. There is
85
+ 3. Read `src/tlc_plugin_sdk/contract.py` — `ComputePlugin` is an `abc.ABC` with one abstract
86
+ method (`get_ui_fragment()`) and an `id` attribute the host stamps from the manifest.
87
+ `compute()` and the other hooks (`run_job`, `initialise_runtime`, `shutdown_runtime`,
88
+ `get_route_handlers`) ship as defaults (`compute()` reports that the plugin doesn't
89
+ implement it), so you override only what you need. There is
90
90
  no `register()` and **no** `get_active_jobs`/`cancel_job` — the host owns job listing and
91
91
  cancellation.
92
92
  4. Read `tlc_plugin_exporter`'s `plugin.toml` + `__init__.py` — the canonical manifest +
@@ -154,9 +154,11 @@ section is the condensed working order.
154
154
  form fills call `_tlcSyncAliasFromForm(prefix, projectId, folderId)`.
155
155
 
156
156
  **SocketIO** (if real-time updates are needed):
157
- - `socketio_namespace` is optional — it defaults to `/<plugin-id>`, which the host
158
- auto-registers at startup; declare it in `[runtime]` only to override.
159
- - Prefer the `window.PluginJobs` client over a hand-rolled socket.
157
+ - The namespace is host-derived as `/<plugin-id>` and auto-registered at startup; the manifest
158
+ does not (and never did) declare it.
159
+ - Prefer the `window.PluginJobs` client over a hand-rolled socket. It connects a namespace
160
+ lazily; call `PluginJobs.connect('/<plugin-id>')` on mount if the fragment needs custom
161
+ events from the first second (`run()` connects for you).
160
162
  - Use `ctx.emit(name, payload)` only for telemetry the generic schema can't express
161
163
  (e.g. a loss curve); never re-emit the generic lifecycle by hand.
162
164
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: 3lc-compute-plugin-sdk
3
- Version: 0.2.3
3
+ Version: 0.3.1
4
4
  Summary: Public Python SDK for building 3LC compute-service plugins.
5
5
  Project-URL: Homepage, https://github.com/3lc-ai/3lc-compute-plugin-sdk
6
6
  Project-URL: Repository, https://github.com/3lc-ai/3lc-compute-plugin-sdk
@@ -18,10 +18,10 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Typing :: Typed
20
20
  Requires-Python: >=3.10
21
+ Requires-Dist: 3lc[pandas]<4.0.0,>=3.0.0
21
22
  Requires-Dist: litestar<3.0.0,>=2.22.0
22
23
  Requires-Dist: uvicorn>=0.34.0
23
24
  Provides-Extra: shared
24
- Requires-Dist: 3lc[pandas]<4.0.0,>=3.0.0; extra == 'shared'
25
25
  Description-Content-Type: text/markdown
26
26
 
27
27
  # 3lc-compute-plugin-sdk
@@ -44,9 +44,10 @@ pip install 3lc-compute-plugin-sdk # import name: tlc_plugin_sdk
44
44
 
45
45
  ## What it gives you
46
46
 
47
- - **`ComputePlugin`** — the base class you subclass. Implement `compute` / `get_ui_fragment`;
48
- job and lifecycle hooks ship as no-op defaults. There is no `register()` to call — a plugin's
49
- metadata lives in its `[tool.tlc-compute]` manifest, and the Hub discovers it from there.
47
+ - **`ComputePlugin`** — the base class you subclass. Implement `get_ui_fragment` (and `compute`
48
+ if you expose a synchronous endpoint); job and lifecycle hooks ship as safe defaults. There is
49
+ no `register()` to call — a plugin's metadata lives in its `plugin.toml` manifest (or a
50
+ `[tool.tlc-compute]` table in `pyproject.toml`), and the Hub discovers it from there.
50
51
  - **`JobContext`** — the surface a long-running job programs against: `progress` / `metric` /
51
52
  `log` / `result` for the generic job panel, `emit` for your plugin's own rich UI, and
52
53
  cooperative `cancelled`.
@@ -63,23 +64,26 @@ format, custom routes, the job model, UI fragment, checklist).
63
64
 
64
65
  ## The contract version
65
66
 
66
- `tlc_plugin_sdk.SDK_CONTRACT_VERSION` is this package's own version — one source of truth.
67
- A plugin pins the SDK (`3lc-compute-plugin-sdk>=X,<Y`) and declares the contract it targets via its
68
- manifest. The compute service implements a contract range, and the SDK version is the shared
69
- reference both sides agree on. Versions are SemVer; see **Status** below for the 0.x stability
67
+ `tlc_plugin_sdk.SDK_CONTRACT_VERSION` is this package's own version — one source of truth, one
68
+ contract axis (both the Python and the browser surface). A plugin pins the SDK
69
+ (`3lc-compute-plugin-sdk>=X,<Y`); the compute service and frontend implement a range and compare
70
+ compatibility on `MAJOR.MINOR`. Versions are SemVer; see **Status** below for the 0.x stability
70
71
  stance.
71
72
 
72
73
  ## How it fits
73
74
 
74
- This SDK sits at the root of the plugin dependency graph. Its only required dependencies are
75
- `uvicorn` and `litestar`; the `3lc` data plane is an optional extra
76
- (`3lc-compute-plugin-sdk[shared]`), pulled in only by the `shared.*` helpers. It **never** depends
77
- on the compute service itself or on any plugin. A plugin built against `tlc_plugin_sdk` alone is
78
- therefore portable across service versions and runs in its own isolated environment.
75
+ This SDK sits at the root of the plugin dependency graph. Its dependencies are `uvicorn`,
76
+ `litestar`, and the `3lc` data plane (`3lc[pandas]`) — so installing the SDK is all a plugin needs
77
+ to use `tlc` and the `shared.*` helpers built on it. All three are imported lazily; `import
78
+ tlc_plugin_sdk` stays cheap. It **never** depends on the compute service itself or on any plugin.
79
+ A plugin built against `tlc_plugin_sdk` alone is therefore portable across service versions and
80
+ runs in its own isolated environment.
79
81
 
80
82
  ## Status
81
83
 
82
- **0.2 is the released contract line.** Within 0.x the contract evolves **additively only**:
83
- symbols and schemas that exist keep working, and anything breaking waits for a major bump. In
84
+ **0.3 is the current contract line.** Within 0.x the contract still evolves — mostly additively,
85
+ but a coordinated breaking change may land on a MINOR bump while the fleet re-pins in lockstep (0.3
86
+ did: `JobContext.result` took a positional `url`, and the `PY_CONTRACT`/`JS_CONTRACT` axes
87
+ collapsed into `SDK_CONTRACT_VERSION`). Anything reshaping the core still waits for a major bump. In
84
88
  the browser bridge, `PLUGIN_API.libs.io` is a stable part of the contract; the other bundled
85
89
  libs are best-effort (see the guide).
@@ -18,9 +18,10 @@ pip install 3lc-compute-plugin-sdk # import name: tlc_plugin_sdk
18
18
 
19
19
  ## What it gives you
20
20
 
21
- - **`ComputePlugin`** — the base class you subclass. Implement `compute` / `get_ui_fragment`;
22
- job and lifecycle hooks ship as no-op defaults. There is no `register()` to call — a plugin's
23
- metadata lives in its `[tool.tlc-compute]` manifest, and the Hub discovers it from there.
21
+ - **`ComputePlugin`** — the base class you subclass. Implement `get_ui_fragment` (and `compute`
22
+ if you expose a synchronous endpoint); job and lifecycle hooks ship as safe defaults. There is
23
+ no `register()` to call — a plugin's metadata lives in its `plugin.toml` manifest (or a
24
+ `[tool.tlc-compute]` table in `pyproject.toml`), and the Hub discovers it from there.
24
25
  - **`JobContext`** — the surface a long-running job programs against: `progress` / `metric` /
25
26
  `log` / `result` for the generic job panel, `emit` for your plugin's own rich UI, and
26
27
  cooperative `cancelled`.
@@ -37,23 +38,26 @@ format, custom routes, the job model, UI fragment, checklist).
37
38
 
38
39
  ## The contract version
39
40
 
40
- `tlc_plugin_sdk.SDK_CONTRACT_VERSION` is this package's own version — one source of truth.
41
- A plugin pins the SDK (`3lc-compute-plugin-sdk>=X,<Y`) and declares the contract it targets via its
42
- manifest. The compute service implements a contract range, and the SDK version is the shared
43
- reference both sides agree on. Versions are SemVer; see **Status** below for the 0.x stability
41
+ `tlc_plugin_sdk.SDK_CONTRACT_VERSION` is this package's own version — one source of truth, one
42
+ contract axis (both the Python and the browser surface). A plugin pins the SDK
43
+ (`3lc-compute-plugin-sdk>=X,<Y`); the compute service and frontend implement a range and compare
44
+ compatibility on `MAJOR.MINOR`. Versions are SemVer; see **Status** below for the 0.x stability
44
45
  stance.
45
46
 
46
47
  ## How it fits
47
48
 
48
- This SDK sits at the root of the plugin dependency graph. Its only required dependencies are
49
- `uvicorn` and `litestar`; the `3lc` data plane is an optional extra
50
- (`3lc-compute-plugin-sdk[shared]`), pulled in only by the `shared.*` helpers. It **never** depends
51
- on the compute service itself or on any plugin. A plugin built against `tlc_plugin_sdk` alone is
52
- therefore portable across service versions and runs in its own isolated environment.
49
+ This SDK sits at the root of the plugin dependency graph. Its dependencies are `uvicorn`,
50
+ `litestar`, and the `3lc` data plane (`3lc[pandas]`) — so installing the SDK is all a plugin needs
51
+ to use `tlc` and the `shared.*` helpers built on it. All three are imported lazily; `import
52
+ tlc_plugin_sdk` stays cheap. It **never** depends on the compute service itself or on any plugin.
53
+ A plugin built against `tlc_plugin_sdk` alone is therefore portable across service versions and
54
+ runs in its own isolated environment.
53
55
 
54
56
  ## Status
55
57
 
56
- **0.2 is the released contract line.** Within 0.x the contract evolves **additively only**:
57
- symbols and schemas that exist keep working, and anything breaking waits for a major bump. In
58
+ **0.3 is the current contract line.** Within 0.x the contract still evolves — mostly additively,
59
+ but a coordinated breaking change may land on a MINOR bump while the fleet re-pins in lockstep (0.3
60
+ did: `JobContext.result` took a positional `url`, and the `PY_CONTRACT`/`JS_CONTRACT` axes
61
+ collapsed into `SDK_CONTRACT_VERSION`). Anything reshaping the core still waits for a major bump. In
58
62
  the browser bridge, `PLUGIN_API.libs.io` is a stable part of the contract; the other bundled
59
63
  libs are best-effort (see the guide).
@@ -13,29 +13,23 @@ package's own docstrings.
13
13
  :members:
14
14
 
15
15
  .. autodata:: tlc_plugin_sdk.SDK_CONTRACT_VERSION
16
-
17
- .. autodata:: tlc_plugin_sdk.PY_CONTRACT
18
-
19
- .. autodata:: tlc_plugin_sdk.JS_CONTRACT
20
16
  ```
21
17
 
22
- `SDK_CONTRACT_VERSION` is the wheel/SemVer — the dependency *pin* a plugin resolves
23
- against (`3lc-compute-plugin-sdk>=X,<Y`). `PY_CONTRACT` and `JS_CONTRACT` are finer-grained
24
- **capability markers** for runtime feature-detection: `PY_CONTRACT` covers the Python
25
- surface (`ComputePlugin` / `JobContext`), `JS_CONTRACT` the browser surface
18
+ `SDK_CONTRACT_VERSION` is the one contract axis: this package's own version — the
19
+ dependency *pin* a plugin resolves against (`3lc-compute-plugin-sdk>=X,<Y`) and the version
20
+ of both the Python surface (`ComputePlugin` / `JobContext`) and the browser surface
26
21
  (`PLUGIN_API` / `PluginJobs` / `TlcData`, declared in
27
- `tlc_plugin_sdk/contract/plugin-api.d.ts`). Both increment independently and are always
28
- `<=` the package version. See `docs/plugin-guide.md` → "Two version axes".
22
+ `tlc_plugin_sdk/contract/plugin-api.d.ts`). The host and frontend implement a range and
23
+ compare compatibility on `MAJOR.MINOR`. See `docs/plugin-guide.md` → "Version & Compatibility".
29
24
 
30
25
  The `.d.ts` ships in the wheel at
31
26
  `<site-packages>/tlc_plugin_sdk/contract/plugin-api.d.ts`; a plain-JS `ui.html` references
32
27
  it with `/// <reference types="3lc-compute-plugin-sdk/contract/plugin-api" />`.
33
28
 
34
- ## The browser contract (`JS_CONTRACT`)
29
+ ## The browser contract
35
30
 
36
31
  The browser surface a plugin's `ui.html` programs against — `PLUGIN_API`, `PluginJobs`,
37
- and the `TlcData` helper — is declared in `plugin-api.d.ts`, the source of truth for
38
- `JS_CONTRACT`.
32
+ and the `TlcData` helper — is declared in `plugin-api.d.ts`.
39
33
 
40
34
  ```{eval-rst}
41
35
  .. automodule documents every exported symbol in the file, so new contract