feat(anonymizer): expose jobs API via OpenAPI + web SDK [ASTD-326] - #879
Conversation
Opt the anonymizer plugin into OpenAPI spec generation and register it with the orval web-SDK generator so the frontend can consume typed job hooks. - [tool.nemo.openapi] opt-in in the plugin pyproject - anonymizer entry in orval serviceConfigs + serviceToConfig - gen:anonymizer / gen:anonymizer-zod scripts The generated openapi/openapi.yaml and web SDK output follow. Signed-off-by: mschwab <mschwab@nvidia.com>
Generate the anonymizer plugin OpenAPI spec and make the platform spec
tweaker handle Optional[Union[...]] fields.
The anonymizer AnonymizerConfig.replace field is an Optional union of the
four replace strategies (Annotate/Redact/Hash/Substitute), which renders
as anyOf: [{oneOf: [...]}, null]. _anyof_null_visitor only collapsed a
sole non-null member that was a scalar or $ref and raised on a bare
oneOf/anyOf. Broaden the guard so the union hoists onto the parent.
- fix + regression test in nmp_common tweak_spec
- committed plugins/nemo-anonymizer/openapi/openapi.yaml
Web SDK (gitignored) generates useAnonymizerListRunJobs et al. via the
orval wiring from the prior commit.
Signed-off-by: mschwab <mschwab@nvidia.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe PR adds the nemo-anonymizer OpenAPI 3.1 contract and entity-label route, expands nullable union normalization with regression coverage, and configures web SDK and Zod client generation. ChangesAnonymizer API and SDK
Sequence Diagram(s)sequenceDiagram
participant Client
participant AnonymizerService
participant EntityLabelsRoute
Client->>AnonymizerService: Request workspace entity labels
AnonymizerService->>EntityLabelsRoute: Route request under workspace prefix
EntityLabelsRoute-->>Client: Return DEFAULT_ENTITY_LABELS
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Expose GET /apis/anonymizer/v2/workspaces/{workspace}/entity-labels
returning the default GLiNER entity labels (anonymizer.DEFAULT_ENTITY_LABELS)
so the Studio entities picker stays in sync with the library instead of
hardcoding the label set.
- app/entity_labels.py: authz-gated route (anonymizer:read +
anonymizer.entity-labels.list), EntityLabelsResponse { data: list[str] }
- wired as a third RouterSpec in AnonymizerService.get_routers
- regenerated plugins/nemo-anonymizer/openapi/openapi.yaml
- SDK (gitignored): useAnonymizerListEntityLabels
Labels are sourced from the external anonymizer library constant, not
hardcoded in the plugin.
Signed-off-by: mschwab <mschwab@nvidia.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
plugins/nemo-anonymizer/tests/unit/test_routing.py (1)
68-76: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winTest the mounted, authorized endpoint rather than only its internals.
The path assertion inspects raw child routes, so it never verifies the
/v2/workspaces/{workspace}prefix. The direct handler call also cannot prove that@scope.readand@path_rulereject unauthorized callers. Use the existing app/authz test client to assert the full URL, an authorized response, and denied access.Also applies to: 79-85
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/nemo-anonymizer/tests/unit/test_routing.py` around lines 68 - 76, Update test_service_mounts_entity_labels_route to use the existing app/authz test client instead of inspecting raw routes or invoking the handler directly. Exercise the fully mounted /v2/workspaces/{workspace}/entity-labels URL, assert the authorized request succeeds, and add an unauthorized request asserting denial by the `@scope.read` and `@path_rule` protections.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@plugins/nemo-anonymizer/tests/unit/test_routing.py`:
- Around line 68-76: Update test_service_mounts_entity_labels_route to use the
existing app/authz test client instead of inspecting raw routes or invoking the
handler directly. Exercise the fully mounted
/v2/workspaces/{workspace}/entity-labels URL, assert the authorized request
succeeds, and add an unauthorized request asserting denial by the `@scope.read`
and `@path_rule` protections.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 2c68def5-db1b-44ca-96f7-ece43850d5e4
📒 Files selected for processing (4)
plugins/nemo-anonymizer/openapi/openapi.yamlplugins/nemo-anonymizer/src/nemo_anonymizer_plugin/app/entity_labels.pyplugins/nemo-anonymizer/src/nemo_anonymizer_plugin/service.pyplugins/nemo-anonymizer/tests/unit/test_routing.py
🚧 Files skipped from review as they are similar to previous changes (1)
- plugins/nemo-anonymizer/openapi/openapi.yaml
…STD-326] The studio-ui image build runs the @nemo/sdk postinstall (gen:all), which now iterates the anonymizer service and reads plugins/nemo-anonymizer/openapi/openapi.yaml. The Dockerfile selectively COPYs each plugin's openapi dir but was missing anonymizer, so the build failed with ENOENT. Add the COPY. Signed-off-by: mschwab <mschwab@nvidia.com>
…VIDIA-NeMo#879) * feat(anonymizer): wire OpenAPI generation and web SDK [ASTD-326] Opt the anonymizer plugin into OpenAPI spec generation and register it with the orval web-SDK generator so the frontend can consume typed job hooks. - [tool.nemo.openapi] opt-in in the plugin pyproject - anonymizer entry in orval serviceConfigs + serviceToConfig - gen:anonymizer / gen:anonymizer-zod scripts The generated openapi/openapi.yaml and web SDK output follow. Signed-off-by: mschwab <mschwab@nvidia.com> * feat(anonymizer): generate OpenAPI spec; fix tweaker for Optional[Union] Generate the anonymizer plugin OpenAPI spec and make the platform spec tweaker handle Optional[Union[...]] fields. The anonymizer AnonymizerConfig.replace field is an Optional union of the four replace strategies (Annotate/Redact/Hash/Substitute), which renders as anyOf: [{oneOf: [...]}, null]. _anyof_null_visitor only collapsed a sole non-null member that was a scalar or $ref and raised on a bare oneOf/anyOf. Broaden the guard so the union hoists onto the parent. - fix + regression test in nmp_common tweak_spec - committed plugins/nemo-anonymizer/openapi/openapi.yaml Web SDK (gitignored) generates useAnonymizerListRunJobs et al. via the orval wiring from the prior commit. Signed-off-by: mschwab <mschwab@nvidia.com> * feat(anonymizer): add entity-labels metadata endpoint [ASTD-326] Expose GET /apis/anonymizer/v2/workspaces/{workspace}/entity-labels returning the default GLiNER entity labels (anonymizer.DEFAULT_ENTITY_LABELS) so the Studio entities picker stays in sync with the library instead of hardcoding the label set. - app/entity_labels.py: authz-gated route (anonymizer:read + anonymizer.entity-labels.list), EntityLabelsResponse { data: list[str] } - wired as a third RouterSpec in AnonymizerService.get_routers - regenerated plugins/nemo-anonymizer/openapi/openapi.yaml - SDK (gitignored): useAnonymizerListEntityLabels Labels are sourced from the external anonymizer library constant, not hardcoded in the plugin. Signed-off-by: mschwab <mschwab@nvidia.com> * fix(anonymizer): copy anonymizer openapi spec into studio-ui image [ASTD-326] The studio-ui image build runs the @nemo/sdk postinstall (gen:all), which now iterates the anonymizer service and reads plugins/nemo-anonymizer/openapi/openapi.yaml. The Dockerfile selectively COPYs each plugin's openapi dir but was missing anonymizer, so the build failed with ENOENT. Add the COPY. Signed-off-by: mschwab <mschwab@nvidia.com> --------- Signed-off-by: mschwab <mschwab@nvidia.com>
What
Finishes the wiring so the Anonymizer plugin's jobs API is exposed as an OpenAPI spec and a generated web SDK, and adds an entity-labels metadata endpoint — unblocking the Anonymizer list page (ASTD-325) and the Entities picker.
Part of ASTD-215 · closes ASTD-326.
Context
The anonymizer plugin (
plugins/nemo-anonymizer/) already existed as a registered NeMo service with job routes (add_job_routes(RunJob)), CLI, and hand-written Python SDK. What was missing was the spec/SDK wiring — not a new service. Noservices/anonymizer-api/is introduced.Endpoint surface (all now exposed)
Create + full job lifecycle (all pre-existing, now in the spec/SDK):
POST /jobs/run·GET /jobs/run·GET /jobs/run/{name}·GET .../status·POST .../cancel·DELETE /jobs/run/{name}·GET .../logs·GET .../results[/{name}][/download]. PlusPOST /preview(live first-N-records preview).New:
GET /apis/anonymizer/v2/workspaces/{workspace}/entity-labels→ the default GLiNER entity labels for the Studio Entities picker.No update/edit endpoint by design — platform jobs are immutable (matches Safe Synthesizer & Data Designer).
Changes
OpenAPI + web-SDK wiring
[tool.nemo.openapi]opt-in inplugins/nemo-anonymizer/pyproject.toml.anonymizerregistered in orvalserviceConfigs+serviceToConfigandgen:anonymizer/gen:anonymizer-zodscripts.plugins/nemo-anonymizer/openapi/openapi.yaml.Entity-labels endpoint
app/entity_labels.py: authz-gated (anonymizer:read+anonymizer.entity-labels.list), returnsEntityLabelsResponse { data: list[str] }fromanonymizer.DEFAULT_ENTITY_LABELS. Labels are sourced from the external anonymizer library constant — not hardcoded in the plugin — so they track library updates.RouterSpecinAnonymizerService.get_routers; routing tests added.Platform tweaker fix (
nmp_common)AnonymizerConfig.replaceis anOptional[Union[Annotate, Redact, Hash, Substitute]](union lives in the externalanonymizerlibrary, unpatchable in-repo), rendering asanyOf: [{oneOf: [...]}, null]._anyof_null_visitoronly collapsed a scalar/$refnon-null member and raised on a bareoneOf/anyOf. Broadened the guard so the union hoists onto the parent. Regression test added.Resulting SDK (gitignored, regenerated in CI)
useAnonymizerListRunJobs(+getAnonymizerListRunJobsQueryKey), create/get/delete/cancel/status/logs/results, anduseAnonymizerListEntityLabels. Types:RunJob,RunJobRequest,RunJobsListFilter,RunJobsSortField,RunJobsPage,EntityLabelsResponse, plus domain models (Detect,Annotate/Redact/Hash/Substitute/Rewrite,RiskTolerance,PrivacyGoal). Mirrors the Data DesignerCreateJobfamily the Studio DataView already consumes.Testing
pytest packages/nmp_common/tests/api/test_utils_openapi_spec.py→ 14 passed;pytest plugins/nemo-anonymizer/tests/unit/test_routing.py→ 4 passed.make refresh-openapiregenerates all plugin specs successfully (8 plugins).@nemo/sdktsc --noEmitclean;ty checkclean on the new plugin files.Notes
tydiagnostic in an untouched test helper (test_routing.py:53,AnonymizerResource(SimpleNamespace)) and for Studio's pre-existing customizer-SDK typecheck failures — both unrelated to this branch. This branch's own checks pass.Summary by CodeRabbit
oneOf/anyOfstructures are preserved correctly.