Skip to content

fix ruff on main; add typed XenAPIError with the structured error - #1

Merged
acefei merged 3 commits into
mainfrom
fix/xenapi-error-and-ruff
Aug 18, 2026
Merged

fix ruff on main; add typed XenAPIError with the structured error#1
acefei merged 3 commits into
mainfrom
fix/xenapi-error-and-ruff

Conversation

@acefei

@acefei acefei commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Two independent fixes, smallest first.

1. fix(ci) — the ruff job is red on main

Since examples/xscert.py landed, ruff@0.16.2 check . fails:

examples/xscert.py:29:1   I001 Import block is un-sorted or un-formatted
examples/xscert.py:262:13 B007 Loop control variable `ref` not used within loop body

Imports sorted; the loop now iterates .values() since the key is unused.
Only the ruff job was failing — build and all six import jobs were green.

2. feat(python) — keep the structured error

_call and login_with_password rendered the JSON-RPC error into a string and
discarded the object, so a caller had to substring-match to classify a failure:

except RuntimeError as e:
    if "RBAC_PERMISSION_DENIED" in str(e):   # sniffing the rendered text

XenAPIError preserves it:

except XenAPIError as e:
    if e.code == "RBAC_PERMISSION_DENIED":
attribute meaning
.code XAPI's error name (JSON-RPC carries it in message)
.params XAPI's error parameters (data)
.error the raw error object
.method the failing call

It subclasses RuntimeError, so existing except RuntimeError keeps working,
and a non-dict error degrades to code=None instead of raising.

Version bumped to 1.0.6 so this and the earlier 3.9 annotation fix can reach
PyPI — the published 1.0.5 still declares requires-python >=3.12 yet cannot be
imported on 3.12/3.13.

Verification

Ran the three CI jobs locally against this branch before pushing:

  • uvx ruff@0.16.2 check .All checks passed
  • uv venv --python 3.9 && uv pip install . && import — OK (also checked 3.14)
  • uv buildasync_xenapi-1.0.6.tar.gz + .whl

Motivation

Found while building a client-certificate auth demo against a real XS9 pool. The
demo currently subclasses the session to inject an SSL context and matches error
strings; with ssl_context=, session_ref and now XenAPIError, all three
workarounds go away.

Fei Su added 3 commits August 17, 2026 10:14
The ruff job has been red since xscert.py landed:

  examples/xscert.py:29:1   I001 Import block is un-sorted or un-formatted
  examples/xscert.py:262:13 B007 Loop control variable `ref` not used

Sort the imports, and iterate .values() since the ref is unused.
_call and login_with_password formatted the JSON-RPC error into a string and
threw the object away, so callers had to substring-match the message to tell an
RBAC denial from a bad reference:

    except RuntimeError as e:
        if 'RBAC_PERMISSION_DENIED' in str(e): ...

XenAPIError keeps it:

    except XenAPIError as e:
        if e.code == 'RBAC_PERMISSION_DENIED': ...

  .code   XAPI's error name, which JSON-RPC carries in `message`
  .params XAPI's error parameters (`data`)
  .error  the raw error object
  .method the failing call

It subclasses RuntimeError, so existing `except RuntimeError` keeps working,
and a non-dict error degrades to code=None rather than raising.

Bump to 1.0.6 so this and the 3.9 annotation fix can reach PyPI: the published
1.0.5 still declares requires-python >=3.12 and cannot be imported on 3.12/3.13.
Every client-certificate consumer repeats the same four lines, and gets the
ordering trap wrong at least once (check_hostname must be cleared before
verify_mode, or CPython raises):

    ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    ctx.load_cert_chain(cert, key)

Now:

    ctx = client_cert_context('client.crt', 'client.key')
    session = AsyncXenAPISession(url, ssl_context=ctx)

It also makes server verification a first-class option rather than an
afterthought: pass cafile= to verify the pool (CERT_REQUIRED + check_hostname),
omit it for a lab pool with a self-signed certificate. The docstring says
plainly that omitting it leaves the channel encrypted but unauthenticated.

Verified against a real XS9 pool: without cafile the certificate login
succeeds (client_certificate=True, subject=OpaqueRef:NULL); with cafile the
handshake fails as it should, since that CA does not sign the pool's server
certificate.
@acefei
acefei merged commit 4d02eea into main Aug 18, 2026
8 checks passed
@acefei
acefei deleted the fix/xenapi-error-and-ruff branch August 18, 2026 05:49
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.

1 participant