diff --git a/.changeset/fix-pyi-relative-imports.md b/.changeset/fix-pyi-relative-imports.md new file mode 100644 index 0000000..26c1620 --- /dev/null +++ b/.changeset/fix-pyi-relative-imports.md @@ -0,0 +1,5 @@ +--- +"@teslemetry/tesla-protocol": patch +--- + +Fix cross-file imports left unrewritten in generated `.pyi` stubs (`car_server_pb2.pyi`, `universal_message_pb2.pyi`, `vcsec_pb2.pyi`, `vehicle_pb2.pyi`, and three `energy_device` stubs). protoc's pyi generator aliases same-package imports differently than its `.py` generator (`_foo_pb2` vs `foo__pb2`), so protoletariat's import rewriter - which matches whole import statements including the alias - left the pyi side as a bare top-level `import foo_pb2 as _foo_pb2` instead of the package-relative form the `.py` sibling already got. Static type checkers (e.g. pyright) can't resolve those imports, breaking type information for any field typed through them. `scripts/generate.sh` now runs a small `scripts/fix_pyi_imports.py` pass after `protol` to patch the remaining bare pyi imports. diff --git a/packages/python/tesla_protocol/command/car_server_pb2.pyi b/packages/python/tesla_protocol/command/car_server_pb2.pyi index def5a8a..8c2e2e2 100644 --- a/packages/python/tesla_protocol/command/car_server_pb2.pyi +++ b/packages/python/tesla_protocol/command/car_server_pb2.pyi @@ -1,10 +1,10 @@ import datetime -import common_pb2 as _common_pb2 +from . import common_pb2 as _common_pb2 from google.protobuf import timestamp_pb2 as _timestamp_pb2 -import keys_pb2 as _keys_pb2 -import signatures_pb2 as _signatures_pb2 -import vcsec_pb2 as _vcsec_pb2 -import vehicle_pb2 as _vehicle_pb2 +from . import keys_pb2 as _keys_pb2 +from . import signatures_pb2 as _signatures_pb2 +from . import vcsec_pb2 as _vcsec_pb2 +from . import vehicle_pb2 as _vehicle_pb2 from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor diff --git a/packages/python/tesla_protocol/command/universal_message_pb2.pyi b/packages/python/tesla_protocol/command/universal_message_pb2.pyi index 5bb4bf7..03b453b 100644 --- a/packages/python/tesla_protocol/command/universal_message_pb2.pyi +++ b/packages/python/tesla_protocol/command/universal_message_pb2.pyi @@ -1,4 +1,4 @@ -import signatures_pb2 as _signatures_pb2 +from . import signatures_pb2 as _signatures_pb2 from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message diff --git a/packages/python/tesla_protocol/command/vcsec_pb2.pyi b/packages/python/tesla_protocol/command/vcsec_pb2.pyi index abb9f97..167fdf4 100644 --- a/packages/python/tesla_protocol/command/vcsec_pb2.pyi +++ b/packages/python/tesla_protocol/command/vcsec_pb2.pyi @@ -1,5 +1,5 @@ -import keys_pb2 as _keys_pb2 -import errors_pb2 as _errors_pb2 +from . import keys_pb2 as _keys_pb2 +from . import errors_pb2 as _errors_pb2 from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor diff --git a/packages/python/tesla_protocol/command/vehicle_pb2.pyi b/packages/python/tesla_protocol/command/vehicle_pb2.pyi index 82d2ab4..6d727ff 100644 --- a/packages/python/tesla_protocol/command/vehicle_pb2.pyi +++ b/packages/python/tesla_protocol/command/vehicle_pb2.pyi @@ -1,8 +1,8 @@ import datetime from google.protobuf import timestamp_pb2 as _timestamp_pb2 -import vcsec_pb2 as _vcsec_pb2 -import common_pb2 as _common_pb2 -import managed_charging_pb2 as _managed_charging_pb2 +from . import vcsec_pb2 as _vcsec_pb2 +from . import common_pb2 as _common_pb2 +from . import managed_charging_pb2 as _managed_charging_pb2 from google.protobuf.internal import containers as _containers from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor diff --git a/packages/python/tesla_protocol/energy_device/authorization_api_pb2.pyi b/packages/python/tesla_protocol/energy_device/authorization_api_pb2.pyi index 63940cd..79cf624 100644 --- a/packages/python/tesla_protocol/energy_device/authorization_api_pb2.pyi +++ b/packages/python/tesla_protocol/energy_device/authorization_api_pb2.pyi @@ -1,6 +1,6 @@ import datetime from google.protobuf import timestamp_pb2 as _timestamp_pb2 -import authorization_types_pb2 as _authorization_types_pb2 +from . import authorization_types_pb2 as _authorization_types_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message diff --git a/packages/python/tesla_protocol/energy_device/common_api_pb2.pyi b/packages/python/tesla_protocol/energy_device/common_api_pb2.pyi index be9fd3e..970ade2 100644 --- a/packages/python/tesla_protocol/energy_device/common_api_pb2.pyi +++ b/packages/python/tesla_protocol/energy_device/common_api_pb2.pyi @@ -1,7 +1,7 @@ -import networking_pb2 as _networking_pb2 -import device_pb2 as _device_pb2 -import update_pb2 as _update_pb2 -import error_pb2 as _error_pb2 +from . import networking_pb2 as _networking_pb2 +from . import device_pb2 as _device_pb2 +from . import update_pb2 as _update_pb2 +from . import error_pb2 as _error_pb2 from google.protobuf.internal import containers as _containers from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message diff --git a/packages/python/tesla_protocol/energy_device/transport_pb2.pyi b/packages/python/tesla_protocol/energy_device/transport_pb2.pyi index d91ccb7..e3efcf1 100644 --- a/packages/python/tesla_protocol/energy_device/transport_pb2.pyi +++ b/packages/python/tesla_protocol/energy_device/transport_pb2.pyi @@ -1,7 +1,7 @@ -import authorization_types_pb2 as _authorization_types_pb2 -import authorization_api_pb2 as _authorization_api_pb2 -import common_api_pb2 as _common_api_pb2 -import teg_api_pb2 as _teg_api_pb2 +from . import authorization_types_pb2 as _authorization_types_pb2 +from . import authorization_api_pb2 as _authorization_api_pb2 +from . import common_api_pb2 as _common_api_pb2 +from . import teg_api_pb2 as _teg_api_pb2 from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper from google.protobuf import descriptor as _descriptor from google.protobuf import message as _message diff --git a/scripts/fix_pyi_imports.py b/scripts/fix_pyi_imports.py new file mode 100644 index 0000000..1af740c --- /dev/null +++ b/scripts/fix_pyi_imports.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""Rewrite bare cross-file imports left in protoc-generated .pyi stubs. + +protoc's built-in pyi generator emits `import foo_pb2 as _foo_pb2` for a +same-package dependency, using a different alias convention than the regular +.py output (`from . import foo_pb2 as foo__pb2`). protoletariat's import +rewriter matches whole import statements including the alias, so it silently +leaves the pyi import unrewritten - the .py sibling ends up package-relative +while the .pyi stays a bare top-level import that doesn't resolve, breaking +static type checkers (e.g. pyright) for every downstream consumer. + +Usage: fix_pyi_imports.py +""" + +from __future__ import annotations + +import re +import sys +from pathlib import Path + +_BARE_IMPORT = re.compile(r"^import (\w+_pb2) as (\w+)$", re.MULTILINE) + + +def fix_file(path: Path, sibling_names: set[str]) -> None: + text = path.read_text() + + def repl(match: re.Match[str]) -> str: + module, alias = match.group(1), match.group(2) + if module not in sibling_names: + return match.group(0) + return f"from . import {module} as {alias}" + + new_text = _BARE_IMPORT.sub(repl, text) + if new_text != text: + path.write_text(new_text) + + +def main(directory: str) -> None: + root = Path(directory) + sibling_names = {p.stem for p in root.glob("*_pb2.py")} + for pyi_file in root.glob("*_pb2.pyi"): + fix_file(pyi_file, sibling_names) + + +if __name__ == "__main__": + main(sys.argv[1]) diff --git a/scripts/generate.sh b/scripts/generate.sh index 1468636..8e68db3 100755 --- a/scripts/generate.sh +++ b/scripts/generate.sh @@ -49,6 +49,12 @@ for group in $GROUPS_LIST; do --exclude-imports-glob 'google/rpc/*' \ protoc --protoc-path="$PROTOC" \ --proto-path="proto/$group" proto/"$group"/*.proto + + # protol's rewriter matches whole import statements including the alias, + # and protoc's pyi generator aliases same-package imports differently than + # its .py generator, so protol leaves those .pyi imports bare. Patch them + # up separately so the checked-in stubs are import-resolvable too. + "$VENV/bin/python" scripts/fix_pyi_imports.py "$PY_PKG/$group" done echo "generate: OK"