Summary
Make mypy src/ clean enough to enforce as a blocking CI check. Split out of #29, where a baseline scan found that enforcing it today is not viable.
Current state
mypy src/ reports 175 errors across 19 files. Almost none are real defects:
| Error code |
Count |
What it actually is |
name-defined |
112 |
Name "query_pb2.ColumnTable" is not defined and similar |
attr-defined |
31 |
Module has no attribute "PvSelector" and similar |
import-untyped |
30 |
missing stubs for grpc and yaml |
assignment |
2 |
genuine |
The 143 name-defined / attr-defined errors are all the same root cause: the generated _pb2.py files in src/dp_python_lib/grpc/ carry no type information, so mypy cannot see any protobuf message class. The runtime is fine — this is purely a type-checking blind spot.
By file, the noise concentrates in the clients that touch protobuf most heavily: machine_config_client.py (70), query_client.py (31), pv_metadata_client.py (29), query_conversions.py (13).
The 2 real errors
Both in src/dp_python_lib/client/mldp_client.py:
mldp_client.py:101: error: Incompatible types in assignment (expression has type "None", variable has type "AnnotationClient")
mldp_client.py:111: error: Incompatible types in assignment (expression has type "None", variable has type "QueryClient")
self.annotation and self.query are each assigned a client in one branch and None in the other, so mypy infers the non-optional type from the first assignment and rejects the None. CLAUDE.md already documents that client.query is None when no query channel is configured, so the behavior is intended — the annotations just need to say so explicitly (Optional[AnnotationClient] / Optional[QueryClient], or the X | None spelling once the ruff modernization in #29 lands).
Proposed work
Near term — make CI enforcement possible
This is a blunt instrument — suppressing the protobuf module means client code touching protobuf messages is effectively unchecked — but it converts mypy from "175 errors, unusable" into a check that catches regressions in hand-written logic.
Real fix — type the protobuf stubs
This resolves all 143 errors properly rather than hiding them, and gives genuine type checking against the wire format — catching wrong field names and wrong message types at check time instead of at runtime. It touches the generation process described in CLAUDE.md ("Import Fix Process"), including whatever post-processing fixes relative imports, so it needs coordinating with how stubs are produced from the upstream dp-grpc project.
Worth noting this would have real payoff beyond silencing errors: the client code builds protobuf requests by hand throughout, and that is exactly the code the current setup cannot check at all.
Context
Split out of #29 (GHA workflows). That ticket ships CI with ruff, the unit test suite, and the cookbook snippet checker; mypy stays scoped to the cookbook checker (.dev/tools/check-cookbook-snippets.py), where it already works today because it type-checks usage of the library rather than the library's internals.
Summary
Make
mypy src/clean enough to enforce as a blocking CI check. Split out of #29, where a baseline scan found that enforcing it today is not viable.Current state
mypy src/reports 175 errors across 19 files. Almost none are real defects:name-definedName "query_pb2.ColumnTable" is not definedand similarattr-definedModule has no attribute "PvSelector"and similarimport-untypedgrpcandyamlassignmentThe 143
name-defined/attr-definederrors are all the same root cause: the generated_pb2.pyfiles insrc/dp_python_lib/grpc/carry no type information, so mypy cannot see any protobuf message class. The runtime is fine — this is purely a type-checking blind spot.By file, the noise concentrates in the clients that touch protobuf most heavily:
machine_config_client.py(70),query_client.py(31),pv_metadata_client.py(29),query_conversions.py(13).The 2 real errors
Both in
src/dp_python_lib/client/mldp_client.py:self.annotationandself.queryare each assigned a client in one branch andNonein the other, so mypy infers the non-optional type from the first assignment and rejects theNone. CLAUDE.md already documents thatclient.queryisNonewhen no query channel is configured, so the behavior is intended — the annotations just need to say so explicitly (Optional[AnnotationClient]/Optional[QueryClient], or theX | Nonespelling once the ruff modernization in #29 lands).Proposed work
Near term — make CI enforcement possible
types-PyYAMLto thedevextra (clears theyamlimport errors)grpc(no official stub package exists)src/dp_python_lib/grpc/modulemypy src/toci.ymlonce the above brings it to zeroThis is a blunt instrument — suppressing the protobuf module means client code touching protobuf messages is effectively unchecked — but it converts mypy from "175 errors, unusable" into a check that catches regressions in hand-written logic.
Real fix — type the protobuf stubs
mypy-protobufto the stub-generation pipeline so.pyifiles are emitted alongside each_pb2.pyThis resolves all 143 errors properly rather than hiding them, and gives genuine type checking against the wire format — catching wrong field names and wrong message types at check time instead of at runtime. It touches the generation process described in CLAUDE.md ("Import Fix Process"), including whatever post-processing fixes relative imports, so it needs coordinating with how stubs are produced from the upstream
dp-grpcproject.Worth noting this would have real payoff beyond silencing errors: the client code builds protobuf requests by hand throughout, and that is exactly the code the current setup cannot check at all.
Context
Split out of #29 (GHA workflows). That ticket ships CI with ruff, the unit test suite, and the cookbook snippet checker; mypy stays scoped to the cookbook checker (
.dev/tools/check-cookbook-snippets.py), where it already works today because it type-checks usage of the library rather than the library's internals.