Skip to content

feat!: adapt core and storage clients to the FLAME Hub 0.13.0 API - #129

Open
tada5hi wants to merge 1 commit into
feat/authup-single-resource-envelopefrom
feat/hub-camelcase-record-envelope
Open

feat!: adapt core and storage clients to the FLAME Hub 0.13.0 API#129
tada5hi wants to merge 1 commit into
feat/authup-single-resource-envelopefrom
feat/hub-camelcase-record-envelope

Conversation

@tada5hi

@tada5hi tada5hi commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Adapts CoreClient and StorageClient to FLAME Hub 0.13.0, which landed two breaking changes together:

Stacked on #128, whose _unwrap_single_resource hook this builds on. Review/merge #117#128 → this.

camelCase

Same treatment AuthClient got in #117: model fields, method keyword arguments and the rapiq filter/sort/fields/include vocabulary all move to camelCase. A pydantic field name is the wire key, so renaming the field is the whole change — no aliases, no compatibility shim, matching hub, which shipped none either.

# before
core_client.create_node(name="my-node", realm_id=master_realm)
core_client.find_nodes(sort={"by": "created_at"})
node.registry_project.external_name

# after
core_client.create_node(name="my-node", realmId=master_realm)
core_client.find_nodes(sort={"by": "createdAt"})
node.registryProject.externalName

What deliberately stays snake_case

Identifier Why
node_type, bucket_type, registry_project_type Python-local disambiguators for a hub field called type — there is no hub field by these names
is_entrypoint Same, for a hub field called root
base_url, chunk_size, UploadFile.file_name / content_type Transport-level, never on the wire
all of _auth_flows (client_id, client_secret, grant_type, …) OAuth2/OIDC protocol surface — hub and authup both keep it snake_case
ErrorResponse.status_code Already accepts both spellings via AliasChoices

Two filter keys that would have failed silently

delete_analysis_logs and delete_analysis_node_logs built filter[analysis_id] / filter[node_id] as string literals. rapiq drops an unknown filter key rather than rejecting it, so against 0.13.0 these would not have raised — they would have widened both DELETEs to an unfiltered selection. Renamed to filter[analysisId] / filter[nodeId].

Record envelope

Hub wraps records as {"data": …, "meta": …} but keeps a handful of endpoints deliberately flat, so unlike authup the envelope is per-endpoint, not per-client. An unconditional client-level override would have broken the credential routes.

  • _create_resource, _get_single_resource and _update_resource take a new envelope keyword, defaulting to True.
  • The five flat routes pass envelope=False: get_node_registry_credentials, get/update_node_client_credentials, get/update_analysis_client_credentials. Verified against hub's controllers, which declare Promise<ClientCredentials> / Promise<RegistryCredentials> with no envelope.
  • send_analysis_command reads the body itself rather than going through the helpers; POST /analyses/:id/command is enveloped (Promise<EntityRecordResponse<Analysis>>), so it now unwraps too.
  • CoreClient and StorageClient override _unwrap_single_resource. All three overrides now delegate to a shared unwrap_enveloped_resource() helper that names the required service version in the ValueError, so an outdated deployment is recognizable from the exception alone. BaseClient still returns the body unchanged.

_delete_resource needed no change — it never reads the body, and hub still answers DELETE with 202. Collection responses were already validated as an envelope by ResourceList, and POST /buckets/:id/upload answers with a collection rather than a record, so it is untouched.

Downstream

PrivateAIM/node-hub-api-adapter pins flame-hub-client==0.4.1 and declares FastAPI response_model=list[Project] against these models, so bumping it propagates the camelCase field names to its own wire shape with no model changes on its side. That in turn is what lets PrivateAIM/node-ui consume @privateaim/core-kit types directly instead of generating them from a checked-in swagger.json.

Testing

pytest -m "not integration" — 80 passed (10 new). ruff check and ruff format --check clean. Integration tests need a live 0.13.0 Hub and have not been run here.

New unit coverage in tests/test_base_client.py: core/storage unwrapping across 200/201/202, rejection of a flat body with the version-named ValueError, the credential routes staying flat, list responses untouched, and send_analysis_command going through the hook.

Not included

The models are missing some fields hub 0.13.0 returns — Node.robotId, Project.clientId / robotId, AnalysisBucketFile.robotId. Pre-existing (pydantic ignores extras), unrelated to this rename, and left for a follow-up. One stale robot_id line was dropped from the README dump, which the Node model never actually emitted.

The FLAME Hub renamed its entity/HTTP-API vocabulary from snake_case to
camelCase (PrivateAIM/hub@de5770437, "refactor!: camelCase entity properties,
domain types & HTTP API", #1806) and wrapped every entity record response in a
data/meta envelope (PrivateAIM/hub@a509e932c, "feat!: record data/meta envelope,
meta.schema discovery and dependency bump", #1801). Both ship in 0.13.0. Its
database column names, permission names, OAuth2/OIDC parameters and token
introspection payloads stay snake_case.

camelCase

Mirror the rename on the CoreClient and the StorageClient exactly as
c356a64 did for the AuthClient: model fields, method keyword arguments and the
rapiq filter/sort/field/include vocabulary move to camelCase (display_name ->
displayName, realm_id -> realmId, master_image -> masterImage, build_status ->
buildStatus, group_virtual_path -> groupVirtualPath, the *_realm_id fields ->
*RealmId, ...). A pydantic field name is the wire key, so renaming the field is
the whole change; there are no aliases.

Four parameters deliberately keep snake_case because they name no hub field --
node_type, bucket_type and registry_project_type disambiguate a hub field called
`type`, and is_entrypoint disambiguates one called `root`. So do the transport
level ones (base_url, chunk_size, UploadFile's file_name/content_type) and the
whole _auth_flows OAuth2 surface (client_id, client_secret, grant_type).

Two hard-coded filter keys moved with the models: delete_analysis_logs and
delete_analysis_node_logs built filter[analysis_id] / filter[node_id]. rapiq
drops an unknown filter key silently rather than rejecting it, so against 0.13.0
the stale keys would have widened both DELETEs to an unfiltered selection.

Record envelope

Hub wraps records as {"data": ..., "meta": ...} but keeps a handful of endpoints
flat, so unlike authup the envelope is per-endpoint rather than per-client.
_create_resource, _get_single_resource and _update_resource take a new
`envelope` keyword defaulting to True; the five credential routes
(get_node_registry_credentials, get/update_node_client_credentials,
get/update_analysis_client_credentials) pass envelope=False.
send_analysis_command reads the body itself and now unwraps it too.

CoreClient and StorageClient override _unwrap_single_resource. The three
overrides now delegate to a shared unwrap_enveloped_resource() helper, which
names the required service version in the ValueError so an outdated deployment
is recognizable from the exception alone. BaseClient keeps returning the body
unchanged.

_delete_resource is unaffected: it never reads the body, and hub still answers
DELETE with 202. Collection responses were already validated as an envelope by
ResourceList, and the bucket upload endpoint answers with a collection rather
than a record, so it stays as it is.

BREAKING CHANGE: CoreClient and StorageClient require FLAME Hub 0.13.0 or newer.
Their model attributes, method keyword arguments and rapiq
filter/sort/field/include keys are now camelCase.
Copilot AI review requested due to automatic review settings July 30, 2026 15:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aa46d4fc-c109-4926-8b8d-88c5da9c9df2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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.

2 participants