LabguruPython SDK 2.0 relaunch (token transport + resource layer)#24
LabguruPython SDK 2.0 relaunch (token transport + resource layer)#24jgross wants to merge 20 commits into
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pre-2.0 __init__ eagerly imported requests-based modules, which breaks importing any new labguru.* submodule under the httpx-only dev env. Emptied until the facade is wired in Phase 2.2. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors f909b67 — the prep commit neutralized labguru/__init__.py but missed tests/__init__.py which still imported labguru.core → labguru.api → requests, breaking pytest collection under the httpx-only venv. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce labguru/exceptions.py with LabguruError (base) and LabguruAPIError (carries status_code and a readable message string). Add tests/test_client.py with TDD test for Task 1.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce labguru/client.py with a synchronous httpx-based HTTP client.
Token is injected as a query parameter on every request (Labguru auth
convention). 4xx/5xx raises LabguruAPIError; 204/empty body returns
{"success": True}. Includes get_with_filters (Kendo filter params) and
post_multipart helpers. All four transport tests pass under respx mocks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Consolidate all imports at top of tests/test_client.py (fix E402, F811) - Extract httpx.Response locals to fix E501; remove inline `import json` - Add full type annotations to LabguruClient._request keyword params - Extend LabguruClient docstring noting per-request httpx.Client tradeoff - Add unit test for build_filter_params Kendo-style output (int value stringified) - Add respx integration test for get_with_filters token + filter query params Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add labguru/resources/__init__.py (empty) and labguru/resources/base.py with BaseResource providing list/get/create/update/delete methods over /api/v1/<resource_name>. Subclasses set resource_name; the client result is returned directly. Test-driven: wrote failing test first, then implemented. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ags, stocks, storages, projects) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…auth Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rch read-only, item-wrap docstrings (review) - biocollections.py: add missing update() override with item_key wrap; add empty-string guard in __init__ - facade.py: hoist legacy login/password rejection before the falsy url/token check (catches both ordering bugs and empty-string inputs) - search.py: rewrite as read-only resource using _path(); block create/update/delete with LabguruError - protocols/experiments/sections/elements/tags/stocks/storages/projects: add item-wrap docstring to create and update overrides - tests/test_resources.py: add 4 new tests covering all the above (12 total, all pass) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deletes the pre-2.0 requests/email-password modules and their tests/cassettes (superseded by client.py + resources/). Rewrites README quickstart to the token API, adds MIGRATION.md (with an unverified-assumptions ledger), CHANGELOG.md, a token-scrubbing tests/conftest.py, and a 2.0-accurate CLAUDE.md. Removes the Opentrons example, which relied on 1.x element helpers not yet ported. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…flow Release workflow publishes via PyPI trusted publishing and only triggers on a pushed v* tag. Not wired up yet: the PyPI 'labguru' project/trusted-publisher and the actual tag push are intentionally left to a human (see Phase 5/6 follow-ups). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cope (review) Adds respx tests for ProtocolsResource.tags(id) and StoragesResource.boxes(id) path building, and asserts the experiments-create item-wrap. Rewords README/CLAUDE so 'search' isn't claimed to be global_search-only (it inherits list/get); notes get_with_filters as the low-level Kendo-filter escape hatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewed every facade resource against the published API spec
(my.labguru.com/api-docs/v1/swagger.yaml, OpenAPI 3.0.0, retrieved 2026-06-16).
Affirmative corrections:
- members: /api/v1/members -> /api/v1/admin/members (GET index only; was 404)
- search.global_search: add required 'size' param (default 20); was missing
- tags: model as create + delete only (no list/show/update endpoints exist)
- remove protocols.tags(id) and storages.boxes(id) -- neither endpoint exists
(boxes is a top-level /api/v1/boxes resource)
- flag biocollections.find_by_external_uuid as UNVERIFIED ('external_uuid'
appears nowhere in the spec)
Verified correct and left unchanged: token-as-query-param, no .json suffix,
item-key write wrapping, global_search 'term' param, core CRUD paths.
Rewrites the MIGRATION.md ledger from assumptions to spec-verified/disproven and
adds a resource support matrix. Headline finding: the cross-instance re-discovery
capability (external_uuid + tag search + protocol tags) has no documented API
support -- a design issue for lg-copier, captured in the ledger.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds datasets, documents, notes, papers, reports, sops, workflows, boxes,
instruments, units, requests, measurements, visualizations, webhooks, attachments,
and comments; plus generic/custom collections via
biocollections.for_generic_collection (restores 1.x generic-inventory parity).
Each resource is shaped to the verbs the OpenAPI spec actually supports, not blind
CRUD:
- read-only: workflows
- create + delete only: visualizations (like tags)
- create only (custom body): measurements -> {input_name, experiment_id, item}
- multipart upload: attachments.create(files, data)
- standard item-wrapped CRUD for the rest; per-verb gaps (no DELETE, etc.) raise
LabguruAPIError(404) and are recorded in the support matrix.
Updates the MIGRATION.md support matrix (now 27 rows), README namespace overview,
CHANGELOG, and CLAUDE.md. 28 tests, ruff clean, package builds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
in many resources it is mentioned that delete action doesnt exits - which is not the case. is that on purpose or should i add the delete action in all missing places? |
| class BiocollectionsResource(BaseResource): | ||
| item_key = "item" | ||
|
|
||
| def __init__(self, client, collection: str = "plasmids"): |
There was a problem hiding this comment.
is there a reason for the hardcoded "plasmid" here?
There was a problem hiding this comment.
I'll fix it in the next release - no good reason.
|
|
||
|
|
||
| class DocumentsResource(BaseResource): | ||
| """Documents. list/get/create/update; the API has no delete endpoint.""" |
There was a problem hiding this comment.
@jgross i was able to use the delete action here - is that ok? or do we want to prevent for users to do it? there is a destroy action for this controller
There was a problem hiding this comment.
delete should be prevented by permssions not at the SDK level - keep it.
| """fields: inner payload, wrapped as {item_key: fields} before sending.""" | ||
| return self.client.put(self._path(f"/{resource_id}"), {self.item_key: fields}) | ||
|
|
||
| def find_by_external_uuid(self, external_uuid: str): |
There was a problem hiding this comment.
do we want to keep this? i'm not even sure this field is even populated
There was a problem hiding this comment.
it should (I think) this is the back-reference to UUIDs (not sure when it is populated)
Summary
Ground-up rewrite replacing the pre-2.0
requests/email-password client with a modern, namespaced SDK (sync-first). Implements Phases 0–5 of the LabguruPython SDK Relaunch plan.LabguruClient(base_url, token)over synchttpx; token as query param;LabguruAPIErroron HTTP ≥400;{"success": True}on 204/empty. NewLabguruError/LabguruAPIError.BaseResourceCRUD + 11 namespaces on theLabgurufacade:protocols, experiments, sections, elements, members(read-only),tags, search(read-onlyglobal_search),biocollections(for_collection/find_by_external_uuid),stocks, storages, projects.create/updatewrap inner fields under the API'sitemkey and forward arbitrary fields (external_uuid,custom1..N,tags).Labguru(url=, token=). Legacy kwargs raiseLabguruError.pyproject.toml(hatchling, Python ≥3.9); legacy modules deleted; builds tolabguru-2.0.0sdist+wheel.MIGRATION.md(incl. unverified-assumptions ledger),CHANGELOG.md, 2.0-accurateCLAUDE.md, token-APIexamples/example.py.ci.yml(ruff + pytest on 3.9/3.11/3.13) andrelease.yml(PyPI trusted-publishing onv*tag).Verification boundary
Spec-complete and mock-tested: the
respxtests assert request shaping (URL/params/body), not behavior against a live Labguru instance. Theitemwrapping, endpoint paths, theglobal_searchtermparam, and themembers/searchread-only assumptions are best-guess defaults — see the unverified-assumptions ledger in MIGRATION.md. Treat as ready to validate, not production-ready.Out of scope (follow-on)
v2.0.0tag; confirming thelabguruPyPI name +pypitrusted-publisher are owned by BioData.labguru-mcp/lg-copier, archivePyLabguru, triage open issues.get_elements_by_type,get_data,update_stock_amount,add_step,add_attachment) —lab.elementsis generic CRUD for now.Test plan
pytest— 21 passing (transport + resource respx tests)ruff check labguru tests examples— cleanpython -m build— sdist + wheel buildimport labguru; labguru.__version__ == "2.0.0"🤖 Generated with Claude Code