Skip to content

errors: model ExceptionResourceLockConflict and ExceptionResourceNoAccess as typed exceptions#173

Merged
jfilak merged 1 commit into
jfilak:masterfrom
mhoerisch:adt-errors-typed-lock-exceptions
Jun 12, 2026
Merged

errors: model ExceptionResourceLockConflict and ExceptionResourceNoAccess as typed exceptions#173
jfilak merged 1 commit into
jfilak:masterfrom
mhoerisch:adt-errors-typed-lock-exceptions

Conversation

@mhoerisch

Copy link
Copy Markdown
Contributor

What this PR does

new_adt_error_from_xml() in sap/adt/errors.py walks ADTError.__subclasses__() to map the SAP-side exception type id back to a Python class. Adding a typed wrapper for an ADT exception is therefore a one-line declaration — the dispatcher finds it by name automatically.

Four exception types were already typed (ExceptionResourceAlreadyExists, ExceptionResourceNotFound, ExceptionResourceCreationFailure, ExceptionResourceSaveFailure). Two more are reachable on ordinary CLI flows on modern S/4HANA (Cloud and gCTS-managed) systems but were falling through to the generic ADTError. This PR adds them.

The two new exceptions

ExceptionResourceLockConflict

Raised when the ADT backend rejects a create/modify because the lock state of the addressed resource — or one of its parents like a software component or package — does not allow the change.

Reproducer (live HTTP capture, just now):

HTTP 409 Conflict
<type id="ExceptionResourceLockConflict"/>
<message lang="EN">No suitable software component is modifiable; cannot create object</message>

Triggered by:

sapcli package create ZCDL_LOCK_REPRO "..." \
  --super-package CDL_BLACK_DUCK_MAIN --software-component INFRA_HOME \
  --transport-layer ZY6T --corrnr <T>

…on a S/4HANA system where the parent package is not modifiable for the calling user.

ExceptionResourceNoAccess

Raised when the calling user has no access to the addressed resource or transport request. On gCTS-managed systems the typical message form is Request <T> cannot be used since it is not assigned to repository <R>.

Reproducer (live HTTP capture, just now):

HTTP 403 Forbidden
<type id="ExceptionResourceNoAccess"/>
<message lang="EN">Request C50K900020 cannot be used since it is not assigned to repository <REPO></message>

Triggered by:

sapcli class write <CLASS_OWNED_BY_DIFFERENT_REPO_USER> ...

…on a gCTS-managed system where the class belongs to a repository the calling user is not in scope for.

The fixture in this PR uses sanitised placeholder names (DEVK900042, sapcli_test_repo) so no SAP-internal identifiers leak to the public repo.

Backward compatibility

Net additive. except ADTError: blocks continue to match — the new classes inherit from ADTError unchanged. Code that already does typed dispatch on the four older exceptions is unaffected.

Tests

New tests follow the existing test_parse_existing_package shape one-for-one in test/unit/test_sap_adt_errors.py.

python -m unittest discover -b -s test/unit: master 2699 → branch 2701 (+2 new tests). The 4 failures + 16 errors that exist on master are pre-existing environmental issues in test_sap_cli_config / test_sap_config (Python 3.14 / OS error-message wording in FileNotFoundError); they reproduce identically on unmodified master.

Lint

pylint --rcfile=.pylintrc sap/adt/errors.py10.00/10.
flake8 --config=.flake8 sap/adt/errors.py → clean.

The two new fixture entries in test/unit/fixtures_adt.py use the existing NAME='...' no-space convention of that file (rather than PEP-8 NAME = '...') for diff locality with the surrounding fixtures. Happy to switch on review request.

Net diff

sap/adt/errors.py                | 35 +++++++++++++++++++++++++++++++++++
test/unit/fixtures_adt.py        |  6 ++++++
test/unit/test_sap_adt_errors.py | 26 +++++++++++++++++++++++++-
3 files changed, 66 insertions(+), 1 deletion(-)

Why this matters in practice

LLM coding agents using sapcli (e.g. Claude Code with the sapcli plugin) currently have to regex on ADTError message text to differentiate "no suitable SC modifiable" from "wrong gCTS repo binding" from "object not found". With these subclasses, agents can branch by Python type and offer the right remediation (add --record-changes, escalate to repo admin, create-then-write, etc.) deterministically.

This PR is the upstream prerequisite for a transport-pick skill update on the plugin side.

…cess as typed exceptions

new_adt_error_from_xml() in sap/adt/errors.py walks ADTError.__subclasses__()
to map the SAP-side exception type id back to a Python class, so adding a
typed subclass is enough to make callers able to write a typed
'except ExceptionResourceLockConflict:' instead of regexing the message
text of a plain ADTError.

Two ADT exception types were missing from the existing four typed
wrappers (AlreadyExists, NotFound, CreationFailure, SaveFailure). Both
are reachable by ordinary CLI flows on modern S/4HANA (Cloud and gCTS-
managed) systems:

  ExceptionResourceLockConflict
    Raised when the ADT backend rejects a create/modify because the
    lock state of the addressed resource - or one of its parents like
    a software component or package - does not allow the change.
    Common message text:
      'No suitable software component is modifiable; cannot create object'
    Hit on package or DDIC create calls under a parent that is not
    modifiable for the current user.

  ExceptionResourceNoAccess
    Raised when the calling user has no access to the addressed
    resource or transport request. On gCTS-managed systems the
    common message is:
      'Request <T> cannot be used since it is not assigned to repository <R>'
    meaning the object's gCTS repository binding rejects the transport
    that was picked (or the implicit one).

Both are net-additive: existing 'except ADTError:' blocks continue to
match (the new classes inherit from ADTError unchanged). Code that
wants type-based dispatch can now do it; code that doesn't can keep
ignoring the change.

Tests follow the existing test_parse_existing_package shape one-for-one
and use new fixtures in test/unit/fixtures_adt.py with sanitised placeholder
transport / repository names.

The fixture style (NAME='...') matches the pre-existing convention in
fixtures_adt.py rather than PEP-8 spaces around '=' - kept for diff
locality. Happy to switch on review request.
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 48571bf2-62d9-4c1f-9b15-fb5142b848e3

📥 Commits

Reviewing files that changed from the base of the PR and between 62288c9 and 80c11c7.

📒 Files selected for processing (3)
  • sap/adt/errors.py
  • test/unit/fixtures_adt.py
  • test/unit/test_sap_adt_errors.py

📝 Walkthrough

Walkthrough

This PR introduces two new ADT-specific exception classes (ExceptionResourceLockConflict and ExceptionResourceNoAccess) to handle resource lock and access denial scenarios. The changes include exception class definitions, XML test fixtures, and corresponding unit tests validating exception parsing and behavior.

Changes

ADT Exception Classes and Tests

Layer / File(s) Summary
Exception class definitions
sap/adt/errors.py
ExceptionResourceLockConflict and ExceptionResourceNoAccess are added as ADT error subclasses extending ADTError. Both initialize with com.sap.adt namespace and class name as type, and override __str__ to return the message.
XML error payload fixtures
test/unit/fixtures_adt.py
ERROR_XML_LOCK_CONFLICT and ERROR_XML_RESOURCE_NO_ACCESS module-level constants define exc:exception XML response payloads for testing these error scenarios.
Test imports and verification cases
test/unit/test_sap_adt_errors.py
Fixture imports are expanded to include the new error payloads. Unit tests parse each XML error and assert correct namespace, type, message, str(), repr() values, and exception class type.

🎯 1 (Trivial) | ⏱️ ~3 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding two new typed exception classes to the ADT errors module.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the new exceptions, their use cases, backward compatibility, and testing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@jfilak jfilak merged commit 8e0b41b into jfilak:master Jun 12, 2026
3 checks passed
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