Extract create/update/delete helpers to api/mutation - #97
Open
ramonski wants to merge 2 commits into
Open
Conversation
Move the biggest remaining slice of api/__init__.py into a focused module: the three top-level route orchestrators (create_items, update_items, delete_items) plus their patch/put aliases, the low-level building blocks (create_object, update_object_with_data, deactivate_object, create_analysisrequest, find_target_container, validate_object) and the two permission checks (is_creation_allowed, is_update_allowed). All names remain importable from senaite.jsonapi.api via explicit re-exports at the bottom of __init__.py. v1/routes/content.action looks up 'api.create_items' / 'api.update_items' / 'api.delete_items' via getattr and continues to work unchanged. Semantic tightening while moving: - Every 'fail(401, ...)' inside the moved code becomes a proper ForbiddenError. HTTP 401 means 'log in and try again'; the fail sites (denied by permission gate, denied by adapter, container disallows type, ...) all mean 'you are authenticated but may not do this' -- which is 403. The two affected doctests (create.rst, update.rst) update their expected status from 401 to 403 to match. - 'fail(400, ...)' becomes BadRequestError, 'fail(404, ...)' becomes NotFoundError. Backward compat is preserved through the shared APIError base. Drop now-unused imports from __init__.py: copy, transaction, AccessControl.Unauthorized, create_ar, ICreate, IUpdate, IInfo, getAdapters, zope.deprecation.deprecate.
ramonski
force-pushed
the
refactor/api-mutation
branch
from
July 25, 2026 20:08
8bd3cf6 to
4a53157
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of the issue/feature this PR addresses
Continues the incremental split of
senaite.jsonapi.api(previous: #93 users, #94 settings, #95 serialization, #96 typed exceptions). This PR extracts the biggest remaining slice — the create/update/delete surface — soapi/__init__.pyshrinks to the utility helpers only and the mutation code lives in one focused place. Prerequisite for wrapping these into MCP tools cleanly.What this PR does
Moves out of
api/__init__.pyinto a newapi/mutation.py:Route orchestrators (called by
v1/routes/content.actionviagetattr(api, "<action>_items")):create_items,update_items,patch_items,put_items,delete_itemsLow-level building blocks:
create_object— dispatches toICreateadapter / AnalysisRequest special-case / standardbika_api.create+update_object_with_datacreate_analysisrequest— thin wrapper aroundbika.lims.utils.analysisrequest.create_analysisrequestupdate_object_with_data—IUpdateadapter orIDataManagerset-loop + validation + optional transitiondeactivate_object— SENAITE's soft-delete viadeactivateworkflow transitionfind_target_container— resolvesparent_uid/parent_pathfrom the recordvalidate_object(still@deprecated — kept for compat)Permission checks:
is_creation_allowed— portal/setup/senaite_setup denylist + adapter opinionis_update_allowed— same denylist against the object's parent + adapter opinionAll 13 names remain importable from
senaite.jsonapi.apivia explicit re-exports at the bottom of__init__.py. Route code (v1/routes/content.action) that doesgetattr(api, "create_items")etc. keeps working unchanged.Semantic tightening while moving
Every
fail(401, ...)inside the moved code becomes a properForbiddenError(HTTP 403). HTTP 401 means "log in and try again"; the fail sites (denied by permission gate, denied by adapter, container disallows type, ...) all mean "you are authenticated but may not do this" — which is 403.The two affected doctests (
create.rst,update.rst) update their expected status from 401 to 403 to match.fail(400, ...)becomesBadRequestError,fail(404, ...)becomesNotFoundError. Backward compat is preserved through the sharedAPIErrorbase.Drop unused imports from init.py
Nine imports were only used by the extracted functions:
copy,transactionAccessControl.Unauthorizedbika.lims.utils.analysisrequest.create_analysisrequest as create_arsenaite.jsonapi.interfaces.ICreate,IUpdate,IInfozope.component.getAdapterszope.deprecation.deprecateBase branch
Stacked on top of #96 (
refactor/typed-exceptions). Merge order: #92 → #93 → #94 → #95 → #96 → #97.Verify
--
I confirm I have tested the PR thoroughly and coded it according to PEP8 standards.