Skip to content

Tolerate objects that do not fit the model in list() and watch() - #35

Merged
codemageddon merged 2 commits into
mainfrom
tolerant-watch-list
Oct 28, 2026
Merged

codemageddon merged 2 commits into
mainfrom
tolerant-watch-list

Conversation

@codemageddon

Copy link
Copy Markdown
Owner

Summary

One object that fails validation (typically a custom resource stored before its CRD schema changed) currently fails a whole list() and ends a watch(). Reconnecting doesn't help because the same object comes back. This adds an opt-in mode that keeps such objects instead of raising.

pods = await api.list(invalid=InvalidObjects.INCLUDE)   # ListWithInvalid[Pod]
pods.items      # objects that fit the model
pods.invalid    # InvalidObject(raw, error, metadata) for the rest

async for ev in api.watch(invalid=InvalidObjects.INCLUDE):  # keeps streaming past bad objects
    if ev.is_initial_events_end: ...   # end bookmark of a send_initial_events watch
    ev.object.metadata.name            # works on WatchEvent and InvalidWatchEvent

Changes

  • InvalidObjects enum (RAISE default, INCLUDE) in kubex.core.params; the strings "raise"/"include" are accepted too, matching VersionMatch and friends. Overloads on Api.list()/Api.watch() pick the return type, so existing callers' types are unchanged.
  • InvalidObject (raw, error, parsed metadata) and ListWithInvalid (a ListEntity subclass with invalid, which is not serialized) in the new kubex.core.invalid_object.
  • InvalidWatchEvent: has type and object.metadata like WatchEvent, so code that tracks names or resume points works on both.
  • What still raises with INCLUDE: ERROR events, bookmarks that don't parse, lines that aren't JSON, and objects whose metadata has no name (they can't be identified, e.g. to drop from a cache).
  • WatchEvent.is_initial_events_end for the k8s.io/initial-events-end bookmark.
  • MetadataAccessor.watch is unchanged: PartialObjectMetadata has only optional fields.
  • Docs: watch page (new section, plus where to resume after initial events), list section, API reference, CHANGELOG, CLAUDE.md.

Not in this PR

  • get/delete/patch/replace still parse with the model, so on a bad object they raise ValidationError, the last three after the server has applied the change. Documented, with api.metadata as the workaround.
  • The default mode still raises pydantic's ValidationError unwrapped; wrapping it in a kubex exception would be a breaking change.

Testing

  • 36 unit tests (test/test_invalid_objects.py): parsing rules, Api.list/Api.watch via the stub client, is_initial_events_end, and assert_type checks of the overloads.
  • 4 e2e tests against K3S (test/e2e/test_invalid_objects.py): a CRD object the model rejects, through watch and list (including a DELETED event for it), and resuming from the end bookmark's resource version.
  • Full suite locally: 2725 passed, 63 skipped. mypy strict, ruff, pre-commit and mkdocs build --strict are clean.

🤖 Generated with Claude Code

Add invalid=InvalidObjects.INCLUDE to Api.list() and Api.watch(). An object
that fails validation comes back as an InvalidObject (raw data, the
ValidationError, parsed metadata): in ListWithInvalid.invalid for list(), or
as an InvalidWatchEvent for watch(), which keeps streaming. Only
ADDED/MODIFIED/DELETED objects whose metadata parses with a name are
tolerated; ERROR events, unparseable bookmarks and corrupt lines still raise.
Overloads keep the return types of existing calls unchanged.

Add WatchEvent.is_initial_events_end for the bookmark that ends a
send_initial_events watch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Test PyPI packages published

The following packages have been published to Test PyPI:

Install with:

pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ kubex==0.1.0b3.dev601

The tolerant list path parsed the body with json.loads, so a body that is
not JSON or not UTF-8 raised JSONDecodeError/UnicodeDecodeError instead of
the ValidationError the default mode and the Api.list() docstring promise.
Fall back to the strict list model's model_validate_json in that case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Test PyPI packages published

The following packages have been published to Test PyPI:

Install with:

pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ kubex==0.1.0b3.dev611

@codemageddon
codemageddon merged commit 6dd6bbc into main Oct 28, 2026
14 checks passed
@codemageddon
codemageddon deleted the tolerant-watch-list branch November 1, 2026 14:23

This branch was successfully deployed

1 active deployment
test-pypi — 7d35035c Deployed Oct 27, 2026 by codemageddon via publish #61
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