Skip to content

SOF-8051: samples, measurements and files endpoints - #46

Open
VsevolodX wants to merge 7 commits into
feature/SOF-8050from
feature/SOF-8051
Open

VsevolodX wants to merge 7 commits into
feature/SOF-8050from
feature/SOF-8051

Conversation

@VsevolodX

Copy link
Copy Markdown
Member

The experimental entities on the client, mirroring materials.py: client.samples and client.measurements (list, get, create, create-set, move-to-set, update-set; measurements also files(id)), and client.files (create, signed_urls, put — a presigned PUT straight to the store, returning the stored key, size and sha256, raising on refusal). update_set lives on its own mixin because only samples and measurements have the route on the server (set_routes.ts:107). list_accounts() now includes the account slug. The files methods take an explicit account_id, since the server's fallback is the caller's default account, not the header's.

Consumer: the SPM run uploader and its notebook (mat3ra/web-app#2976, public/upload_run.py; api-examples examples/measurement/upload_spm_run.ipynb). Until this merges the branch is installed by URL.

Tests: pytest tests/py/unit — 52 passed (37 on main). Review and dispositions: GREEN/code-review reviews/mat3ra/api-client/pr-SOF-8051/.

🤖 Generated with Claude Code

VsevolodX and others added 7 commits September 21, 2026 11:23
The lab-data intake needs three surfaces the client did not have. Samples and measurements are
entity-plus-set endpoints, so they are materials with a different name; measurements add files,
the n+1 of jobs/:_id/files, which returns a key and a signed url per file.

Files is not an entity endpoint: the web-app serves POST files, DELETE files and
POST files/signed-urls and nothing addressed by an id, so it derives from BaseEndpoint directly.
put asks for one putObject url, reads the file once and sends those bytes, and answers with the
key, the size and the sha256 the caller records against what it uploaded.

update_set sits on the set mixin next to create_set and move_to_set; samples and measurements
register the route, materials does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pre-signed URL can be refused - an expired signature, a key outside the account's folder - and
the object storage answers 403 to the PUT itself, not to the call that issued the URL. Without the
check put returned a key, a size and a digest for a file that was never written, and an uploader
would report success for a lost file. Every other call this client makes raises on an HTTP error
through BaseConnection; this one now does too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The slug is the name people see in a platform URL and the one the uploader's --account takes, and
the account document carries it: AccountDAO writes it at creation and users/me serialises the
account as it is stored (serializeMyAccount returns the document). The projection dropped it, so a
caller resolving a slug had to guess it from the name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An uploader running with --account writes its documents into the named account, and the files
have to follow them. FilesEndpoints.getAccountId reads accountId from the body and only falls
back to the caller's default account when it is absent, so passing it is the whole fix; put hands
it to signed_urls, since the prefix is resolved when the URL is signed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…turns the stored key

Review, should-fix 1: the server registers update-set from samples and measurements alone, and
set_routes.ts says so where it defines the route, so jobs and materials inheriting update_set
could only ever have produced a 404. It moves to its own mixin beside the shared one and is
composed into the two endpoints that can serve it, the way a material alone is defaultable.

Review, should-fix 3: signed_urls already answers with the key the object is stored under, which
is the account's prefix plus the name. put echoed the caller's relative name back instead, so a
caller naming the uploaded object had to rebuild the prefix - the one thing put exists to hide.

Review, should-fix 4, declined with a reason: the presigned PUT stays on requests.put rather than
BaseConnection. The URL is object storage, not the API, and BaseConnection rewrites every failure
through _extract_server_message, which returns "" for an XML body - a refusal would read
"Error 403: HTTP Error." instead of naming the URL that was refused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant