Repository navigation
feat: add authz schema loading - #475
Conversation
|
Thanks for the pull request, @rodmgwgu! This repository is currently maintained by Once you've gone through the following steps feel free to tag them in a comment and let them know that your changes are ready for engineering review. 🔘 Get product approvalIf you haven't already, check this list to see if your contribution needs to go through the product review process.
🔘 Provide contextTo help your reviewers and other members of the community understand the purpose and larger context of your changes, feel free to add as much of the following information to the PR description as you can:
🔘 Get a green buildIf one or more checks are failing, continue working on your changes until this is no longer the case and your build turns green. DetailsWhere can I find more information?If you'd like to get more details on all aspects of the review process for open source pull requests (OSPRs), check out the following resources: When can I expect my changes to be merged?Our goal is to get community contributions seen and reviewed as efficiently as possible. However, the amount of time that it takes to review and merge a PR can vary significantly based on factors such as:
💡 As a result it may take up to several weeks or months to complete a review and merge your PR. |
mariajgrimaldi
left a comment
There was a problem hiding this comment.
A few comments for you to review! Thanks a lot for this :))
| except Exception: # noqa: BLE001 - defensive; metadata quirks across envs | ||
| mapping = {} | ||
| candidates = mapping.get(top_level) or [] | ||
| if candidates: |
There was a problem hiding this comment.
Could there be duplicates?
There was a problem hiding this comment.
In theory yes, I'm adding a way to deterministically choosing the candidate:
- First search for the distribution that actually has the discovered resource file
- If more than one matches exists, sort and return the first one as a deterministic fallback (and log a warning about it.)
There was a problem hiding this comment.
I wonder if these decisions should be recorded somehow. When we merge this stack, can we do like a reverse-engineering review to capture the deliberate decisions we're making here?
| ``hidden`` is tri-state: ``None`` leaves the current value untouched. | ||
| """ | ||
|
|
||
| role: str |
There was a problem hiding this comment.
Should we use some kind of role_id to reference the role we're extending?
There was a problem hiding this comment.
This also comes form the yaml definition as specified in the ADRs
There was a problem hiding this comment.
The reference mentions role with a description of role ID, which is very straightforward. By itself not so much. We can update the ADRs and references if needed. Let me know what you think!
c9a67dc to
bc3bd6b
Compare
6bbb22e to
442e19d
Compare
442e19d to
bc50391
Compare
mariajgrimaldi
left a comment
There was a problem hiding this comment.
LGTM! Thanks so much for addressing my comments!
(The comments left to address are only nits so I'll leave my approval! :))
ADR 0020 asks for the static schema's display_name/description fields to go through OEP-58 translation. Standard extraction tools can't see them because they live in YAML, not Python or JavaScript source. Add a management command that discovers every schema resource (reusing the already-merged SchemaDiscovery step), parses its YAML directly, and writes a generated Python module with one pgettext() call per field, for i18n_tool extract (Django's makemessages) to pick up. pgettext is used instead of plain gettext because ADR 0020 requires a field's display name and description to stay separate messages even when they're identical text; the context string also carries the field's stable id, so the same one-word name on two different roles stays separate too. Wired into the extract_translations Makefile target, ahead of the existing i18n_tool extract step. This only covers the extraction/translation-source side (ADR 0020 points 1-2). Routing the translated strings back out through an API (points 3-4) isn't buildable yet: the schema loader/model layer that would read these fields at runtime, and the role/permission catalog API itself, are both still open PRs (openedx#475-480, openedx#508), not the already-merged discovery step this extraction reuses. Left a comment on openedx-authz#432 with that split and will file a follow-up once those land. This doesn't touch the schema load/validate/compile pipeline at all (intentionally): it only needs raw display_name/description fields, not resolved role extensions or conflict handling, so it stays usable and testable independent of that still-unmerged pipeline.
* feat: extract translatable strings from the authz schema ADR 0020 asks for the static schema's display_name/description fields to go through OEP-58 translation. Standard extraction tools can't see them because they live in YAML, not Python or JavaScript source. Add a management command that discovers every schema resource (reusing the already-merged SchemaDiscovery step), parses its YAML directly, and writes a generated Python module with one pgettext() call per field, for i18n_tool extract (Django's makemessages) to pick up. pgettext is used instead of plain gettext because ADR 0020 requires a field's display name and description to stay separate messages even when they're identical text; the context string also carries the field's stable id, so the same one-word name on two different roles stays separate too. Wired into the extract_translations Makefile target, ahead of the existing i18n_tool extract step. This only covers the extraction/translation-source side (ADR 0020 points 1-2). Routing the translated strings back out through an API (points 3-4) isn't buildable yet: the schema loader/model layer that would read these fields at runtime, and the role/permission catalog API itself, are both still open PRs (#475-480, #508), not the already-merged discovery step this extraction reuses. Left a comment on openedx-authz#432 with that split and will file a follow-up once those land. This doesn't touch the schema load/validate/compile pipeline at all (intentionally): it only needs raw display_name/description fields, not resolved role extensions or conflict handling, so it stays usable and testable independent of that still-unmerged pipeline. * test: cover the extract_schema_translations command and empty-file case The management command had zero test coverage, which dropped overall project coverage and failed the codecov check on this PR. Add tests that invoke it through call_command for both the happy path (writes a valid generated module) and the failure path (a CommandError on extraction failure), and add the one remaining uncovered branch in translation.py: an empty YAML file parsing to None instead of a dict. * fix: validate schema shape and resolve conflicts by priority in extraction Addresses review feedback on the extraction mechanism: - A malformed section (not a list) or entry (not a mapping) now raises SchemaTranslationExtractionError instead of an AttributeError from deep inside a .get() call on a string or int. - An entry missing its identifying field(s) (a role without id, a permission without namespace/name, etc.) now raises, instead of silently producing duplicate or anonymous contexts. - Conflicts between two documents defining the same role/permission/ category now resolve by each document's priority, the same rule engine.schema.compilation uses: higher priority wins, identical content merges, and a disagreement at equal priority raises. This replaces resolving by file path order, which didn't necessarily match the winner the compiled schema would actually serve. - A display_name/description containing gettext's own msgctxt/msgid separator byte (U+0004) now raises instead of generating a module that's valid Python but breaks msguniq once makemessages runs it. - A missing display_name/description is now skipped for every entry kind, not just role_extensions, instead of generating a useless empty pgettext() call. - The four schema sections (permission_categories, permissions, roles, role_extensions) are now driven by one data table instead of repeated per-section code, since they only differ in three things: the document key, the context's field_kind label, and how to read the entry's stable id. - Added an end-to-end test exercising the real extract_schema_translations -> makemessages pipeline, skipped where the system gettext tools it shells out to aren't installed. Also updates the module docstring and PR description: the schema loader/validate/compile pipeline this module was written to stay independent of is merged now, and the role/permission catalog API has its own PR (#510). * fix: add polib as a direct test dependency The new end-to-end test in test_commands.py imports polib to parse the generated .po file. It was only ever present transitively through edx-i18n-tools, which is in the dev dependency group, not test, so CI's django52 and quality jobs failed importing test_commands.py with ModuleNotFoundError.
Problem
Discovered resources are still opaque bytes. They need to be parsed into typed schema documents, with the source identity and content digest that later phases and the provenance tables depend on (ADR 0018 §1).
Approach
The load phase, plus the shared vocabulary the remaining phases are written against.
openedx_authz/engine/schema/loading.py—SchemaLoaderopenedx_authz/engine/schema/types.py— the schema dataclassesopenedx_authz/engine/schema/exceptions.py— the pipeline exception hierarchyopenedx_authz/constants/schema.py—SchemaOriginKindopenedx_authz/tests/schema/{factories,test_loading,test_types}.pytypes.pyandexceptions.pyland whole rather than sliced across the stack: they are the shared vocabulary, and splitting them would make every later PR conflict on the same two files. Some names in them (CompiledSchema,CompiledDefinition,RelationshipSource, and the compile/validate/apply errors) are unused until later PRs.Manual testing instructions
Rollback plan
Revert this PR. Nothing consumes the loader yet, so the revert is inert.
Retro compatibility
No authorization behavior changes. No models, no migration, no automatic code path.
AI Usage
Kiro was used to assist on feature planning and implementation. Implementation was done step by step with human guidance and validation, based on the ADRs.
Stack (2/8) — #446 split into reviewable pieces. Bases chain bottom-up; merge in order.
rod/authz-schema-discovery)rod/authz-schema-compiler— schema compilationrod/authz-schema-validator— schema validationrod/authz-schema-models— definition models + migrationrod/authz-schema-renderer— policy rendererrod/authz-schema-applier— schema applierload_authz_schemacommand, version bump and changelogMerge checklist: