Skip to content

Comments

Add SharedInformer implementation to python-client#2515

Draft
Copilot wants to merge 5 commits intomasterfrom
copilot/implement-informer-in-python-client
Draft

Add SharedInformer implementation to python-client#2515
Copilot wants to merge 5 commits intomasterfrom
copilot/implement-informer-in-python-client

Conversation

Copy link
Contributor

Copilot AI commented Feb 20, 2026

Python clients wanting a local cache of Kubernetes resources had to implement their own watch loops, reconnection logic, and thread management. This adds a SharedInformer analogous to the Java and JavaScript client implementations.

New package: kubernetes.informer

  • ObjectCache (cache.py) — thread-safe in-memory store keyed by namespace/name; exposes list(), get(), get_by_key(), list_keys()
  • SharedInformer (informer.py) — daemon thread running list-then-watch loop with:
    • Automatic reconnect on ApiException or other errors
    • Event handler callbacks for ADDED, MODIFIED, DELETED, BOOKMARK, and ERROR
    • BOOKMARK event support: handlers receive the raw bookmark object (containing the updated resourceVersion); the cache is not modified
    • resourceVersion tracking: the most recent resourceVersion (updated by every event including BOOKMARKs) is stored and reused on reconnect, avoiding unnecessary full re-lists; a 410 Gone response resets the stored version and triggers a fresh re-list
    • Optional periodic resync (fires MODIFIED for all cached objects)
    • Namespace, label selector, and field selector pass-through

Usage

from kubernetes import config
from kubernetes.client import CoreV1Api
from kubernetes.informer import ADDED, BOOKMARK, DELETED, MODIFIED, SharedInformer

config.load_kube_config()
v1 = CoreV1Api()

informer = SharedInformer(
    list_func=v1.list_namespaced_pod,
    namespace="default",
    resync_period=60,
)
informer.add_event_handler(ADDED, lambda pod: print("added", pod.metadata.name))
informer.add_event_handler(DELETED, lambda pod: print("deleted", pod.metadata.name))
informer.add_event_handler(BOOKMARK, lambda obj: print("bookmark rv", obj["metadata"]["resourceVersion"]))

informer.start()

# Main thread can query the cache at any time without blocking
pods = informer.cache.list()

SharedInformer is now exposed via the top-level kubernetes package (kubernetes.informer).

Testing

Unit tests are in kubernetes/test/test_informer.py and cover the cache, key function, event handlers, watch loop behaviour, BOOKMARK handling, and resourceVersion tracking including 410 Gone recovery.

End-to-end tests are in kubernetes/e2e_test/test_informer.py and run against a real cluster (automatically skipped when no cluster is available). They cover:

  • Pre-existing objects appearing in the cache after start()
  • ADDED event firing and cache population when a ConfigMap is created
  • MODIFIED event firing when a ConfigMap is patched
  • DELETED event firing and cache removal when a ConfigMap is deleted
  • resourceVersion advancing after real watch events are received
Original prompt

This section details on the original issue you should resolve

<issue_title>Implement an Informer in python-client</issue_title>
<issue_description>https://github.com/kubernetes/client-go has an Informer implementation. Internally it leverages a watcher of some collection of resources, continually streams changes (add/modify/delete events), reflects the resources into a downstream store (cache), handles connectivity drops, and periodically does a full resync. This all happens on some background thread (goroutine). A client of the informer is free to iterate over that stored cache without concern for how it's populated, and immediately get (possibly outdated) state.

Applications using https://github.com/kubernetes-client/python that want a local store of resources reflecting some in-cluster state need to concern themselves with those lower-level details. There's a lot of room for error.

On 2019-06-25, go#28 added a simple informer implementation to the openapi-generated client for Golang. It defines a Cache struct, with both a list of all objects and event handler callbacks that a consumer could register.

https://github.com/kubernetes-client/python should contain a similar implementation.

People have been talking about this a bit in various places.

<agent_instructions>Please use the Java informer code as a reference for the implementation you can find it in this directory https://github.com/kubernetes-client/java/tree/master/util/src/main/java/io/kubernetes/client/informer</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@roycaihw The kopf's has a [watch implementation](https://github.com/nolar/kopf/blob/main/kopf/clients/watching.py) that is claimed to be informer-equivalent, which may be interesting to look into. @brendandburns fwiw (since I just noticed this issue) there are Informers for both the Java (https://github.com/kubernetes-client/java/tree/master/util/src/main/java/io/kubernetes/client/informer) and JavaScript (https://github.com/kubernetes-client/javascript/blob/master/src/informer.ts) client libraries.

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@k8s-ci-robot
Copy link
Contributor

Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our release note process to remove it.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot k8s-ci-robot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Feb 20, 2026
…example

Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com>
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Copilot
Once this PR has been reviewed and has the lgtm label, please assign yliaog for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. labels Feb 20, 2026
Copilot AI changed the title [WIP] Add informer implementation to python-client Add SharedInformer implementation to python-client Feb 20, 2026
Copilot AI requested a review from brendandburns February 20, 2026 18:42
@brendandburns
Copy link
Contributor

@copilot can you add support for BOOKMARK events?

Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 20, 2026

@copilot can you add support for BOOKMARK events?

Done in 9c708ad. Added a BOOKMARK constant and registered it in the handlers dict so consumers can call add_event_handler(BOOKMARK, fn). When a BOOKMARK event arrives in the watch stream the informer fires the handler with the raw BOOKMARK object (which carries the updated resourceVersion). The cache is not modified — BOOKMARK events carry no object state change. Two new tests cover this: one verifies the handler is called with the bookmark object, the other confirms the cache is unaffected.

@brendandburns
Copy link
Contributor

@copilot can you implement it so that the most recent resourceVersion is stored and used for the re-list operation?

… on 410 Gone

Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 20, 2026

@copilot can you implement it so that the most recent resourceVersion is stored and used for the re-list operation?

Done in d21aa1b. SharedInformer now tracks _resource_version internally. After the initial list the RV is stored, and after each watch stream ends the Watch's latest RV (updated by every event including BOOKMARKs) is captured in finally. On reconnect the stored RV is passed directly to the next watch call — no full re-list is performed. The only exception is a 410 Gone response, which resets _resource_version to None so the next loop iteration triggers a fresh re-list. Three new tests cover: RV stored from Watch, reconnect skips re-list when RV is known, and 410 triggers re-list.

@brendandburns
Copy link
Contributor

@copilot can you add end-to-end tests against a real cluster

Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

@copilot can you add end-to-end tests against a real cluster

Done in 63bd3d5. Added kubernetes/e2e_test/test_informer.py with five tests that run against a real cluster (skipped automatically via base.get_e2e_configuration() when no cluster is reachable):

  • test_cache_populated_after_start — pre-existing ConfigMaps appear in the cache after start()
  • test_added_event_and_cache_entry — creating a ConfigMap fires ADDED and the object appears in the cache
  • test_modified_event_and_cache_refresh — patching a ConfigMap fires MODIFIED
  • test_deleted_event_removes_from_cache — deleting a ConfigMap fires DELETED and removes the object from the cache
  • test_resource_version_advances — the stored resourceVersion advances after receiving watch events

Each test uses a unique name (UUID suffix) and registers addCleanup to delete created ConfigMaps, so they are safe to run in parallel with other e2e tests.

@k8s-ci-robot k8s-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Feb 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement an Informer in python-client

3 participants