Conversation
0810789 to
2181fb5
Compare
The env was declared in tox.ini's env_list but the [testenv:static] block was missing. Add the block, a new optional poetry group with pyright, and a static CI job mirroring unit-test. Catches kwarg-name typos and similar regressions that ruff does not flag. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both workflows triggered only on PRs targeting main, but this repo ships from 9/edge -- so PRs to release branches skipped CLA verification (a legal-risk gap) and removed-URL detection. Extend the branch filter to "*/edge". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address all 49 pyright errors so `tox -e static` passes cleanly: - guard Optional lightkube .spec/.metadata and Juju relations before use - cast ops config values (bool|int|float|str) to str where strings are expected, matching the existing pattern - raise clear errors when the internal CA cert/key or k8s client are unexpectedly missing instead of dereferencing None - accept an Optional private key in TLSManager.write_certificate, keeping the existing key file when none is provided (preserves prior behaviour) - cast library returns to their concrete runtime types where the upstream signatures are too loose (ResourceProviderModel, certificate relation_id) - narrow the workload to the VM implementation for the snap-only install() - scope `# pyright: ignore[reportIncompatibleVariableOverride]` to the manager `state` assignments and the intended `on` CharmEvents override, which are sound but flagged due to mutable-attribute invariance Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mehdi-Bendriss
left a comment
There was a problem hiding this comment.
Thanks Mykola, I left some comments
| def bind_address(self) -> str: | ||
| """The network binding address from the peer relation.""" | ||
| if not (binding := self.model.get_binding(self.peer_relation)): | ||
| if not (peer_relation := self.peer_relation): |
There was a problem hiding this comment.
is this for linting or did you face this during testing?
There was a problem hiding this comment.
Same case. The only reason None is not considered is because the places we call the property are code paths that happen after the peer relation has been established. If we want to adopt typing 100% this is a valid check.
There was a problem hiding this comment.
@skourta the code paths should be predictable, we need to be able to know at any given time what is the expected state of our system. Being overly defensive masks that and makes reading and reasoning about code hard, and may allow things to fail silently (if defensive too gracefully, like (not) doing an operation conditionally etc)
Address Mehdi's PR #57 review: where type-checker errors were resolved by adding runtime behavior (silent skips, redundant guards, an unneeded cast), restore the original code and silence pyright with a targeted ignore. - k8s_client: drop `service.spec and` guard (was failing silently) - cluster_state: drop redundant peer_relation None-raise in bind/ingress_address - base_events: drop isinstance(ValkeyVmWorkload) guard around install() - external_clients: drop unreachable `relation is None` return; drop relation_id cast - config: drop overly-defensive sentinel-section isinstance fallback write_certificate's `private_key: PrivateKey | None` is left as-is: the None case is real (get_assigned_certificates returns PrivateKey | None and unit tests exercise it), so reverting it would reintroduce a latent crash. tox -e static / lint / unit all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mehdi clarified that in the charm flow the private key is always present when _on_certificate_available fires: the certificate_available hook can't arrive without a CSR, and a CSR requires a private key (user-provided or generated by the lib). get_assigned_certificates' PrivateKey | None return is the lib being defensive for other callers. Restore the required PrivateKey signature and original body so a missing key crashes on write instead of being silently skipped. The call site passes the lib's PrivateKey | None, so suppress the type error there with a targeted pyright ignore rather than adding a runtime check. Unit tests mock write_certificate, so they remain green; tox -e static / lint / unit all pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skourta
left a comment
There was a problem hiding this comment.
I really like the direction and intent of this PR. However, it brings up a larger architectural question: should we enforce 100% typing, or continue with partial adoption? I'm a bit hesitant about merging multiple pyright ignores, as it can make the codebase feel inconsistent and slightly harder to maintain. I think we need to discuss the best path forward for our typing standards.
| def bind_address(self) -> str: | ||
| """The network binding address from the peer relation.""" | ||
| if not (binding := self.model.get_binding(self.peer_relation)): | ||
| if not (peer_relation := self.peer_relation): |
There was a problem hiding this comment.
Same case. The only reason None is not considered is because the places we call the property are code paths that happen after the peer relation has been established. If we want to adopt typing 100% this is a valid check.
reneradoi
left a comment
There was a problem hiding this comment.
Thank you Mykola for the initivative! I've got only a few minor comments, but looks good otherwise.
| server_info = client.info_server(hostname=self.state.endpoint) | ||
|
|
||
| return server_info.get("valkey_version") | ||
| return server_info["valkey_version"] |
There was a problem hiding this comment.
The previous code was safe against KeyError, after the change this could raise and break the charm hook in case the version is not returned from the client command. I would rather keep it as it was previously.
| if self.k8s_client is None: | ||
| raise KubernetesClientError("K8s client is only available on the Kubernetes substrate") | ||
|
|
There was a problem hiding this comment.
No need for this safeguard, we check on substrate on event handler level.
| if self.k8s_client is None: | ||
| raise KubernetesClientError("K8s client is only available on the Kubernetes substrate") | ||
|
|
There was a problem hiding this comment.
Same comment: No need for this safeguard, we check on substrate on event handler level.
| extra_sans_config := self.state.config.get("certificate-extra-sans") | ||
| ): | ||
| extra_sans = [san.strip() for san in extra_sans_config.split(",")] | ||
| extra_sans = [san.strip() for san in str(extra_sans_config).split(",")] |
There was a problem hiding this comment.
The config for extra_sans is defined as string in config.yaml, no need to cast.
| extra_sans_config := self.state.config.get("certificate-extra-sans") | ||
| ): | ||
| extra_sans = [san.strip() for san in extra_sans_config.split(",")] | ||
| extra_sans = [san.strip() for san in str(extra_sans_config).split(",")] |
There was a problem hiding this comment.
Same comment: The config for extra_sans is defined as string in config.yaml, no need to cast.
Summary
(which had no matching [testenv:static] section).
Why
The repo already had a [tool.pyright] block in pyproject.toml (include = ["src//*.py", "lib//*.py"]) but nothing actually invoked pyright — no dev dependency, no tox
env, no CI job. Ruff handles lint/format but does not type-check (F rules catch undefined names, not type mismatches). This wires the existing config into the standard
developer and CI flow so type regressions become enforceable.
Changes
the imports it needs to resolve symbols (ops, charmlibs, valkey, lightkube, pytest, etc.); without them most files would surface as unresolved-import errors.
built from source), then tox run -e static.