Skip to content

Commit 945fcc4

Browse files
committed
Add option to dedot imports
Signed-off-by: Aidan Jensen <aidandj.github@gmail.com>
1 parent e8e1192 commit 945fcc4

File tree

4 files changed

+71
-15
lines changed

4 files changed

+71
-15
lines changed

.github/workflows/main.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99

1010
jobs:
1111
run_test:
12-
name: mypy-protobuf - ${{ matrix.py-ver-mypy-protobuf }} - unittest - ${{ matrix.py-ver-unit-tests }} - protobuf ${{ matrix.protobuf-version }}
12+
name: mypy-protobuf - ${{ matrix.py-ver-mypy-protobuf }} - unittest - ${{ matrix.py-ver-unit-tests }} - protobuf ${{ matrix.protobuf-version }} - dedot imports ${{ matrix.dedot-imports }}
1313
runs-on: ubuntu-24.04
1414
strategy:
1515
matrix:
@@ -32,6 +32,20 @@ jobs:
3232
protobuf-version:
3333
- 6.32.1
3434
- 6.33.1
35+
dedot-imports:
36+
- 0
37+
include:
38+
# Dedot imports has a smaller matrix, just validate that things still work
39+
- py-ver-mypy-protobuf: 3.14.0
40+
py-ver-unit-tests: 3.14.0
41+
protobuf-version: 6.33.1
42+
dedot-imports: 1
43+
- py-ver-mypy-protobuf: 3.14.0
44+
py-ver-unit-tests: 3.14.0
45+
protobuf-version: 6.32.1
46+
dedot-imports: 1
47+
48+
3549

3650
steps:
3751
- uses: actions/checkout@v4
@@ -52,6 +66,7 @@ jobs:
5266
PY_VER_MYPY: ${{matrix.py-ver-mypy-protobuf}}
5367
PYTHON_PROTOBUF_VERSION: ${{matrix.protobuf-version}}
5468
VALIDATE: 1
69+
DEDOT_IMPORTS: ${{matrix.dedot-imports}}
5570
run: |
5671
./run_test.sh
5772

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,26 @@ By default mypy-protobuf will output servicer stubs with abstract methods. To ou
347347
protoc --python_out=output/location --mypy_grpc_out=generate_concrete_servicer_stubs:output/location
348348
```
349349

350+
### `dedot_imports`
351+
352+
This option attempts to match the standard generated python files import pattern by replacing the `.` separated imports.
353+
354+
```
355+
protoc --python_out=output/location --mypy_grpc_out=dedot_imports:output/location
356+
```
357+
358+
```
359+
# before:
360+
import testproto.inner.inner_pb2
361+
def a_inner(self) -> testproto.inner.inner_pb2.Inner: ...
362+
363+
# after:
364+
import testproto.inner.inner_pb2 as testproto_dot_inner_dot_inner__pb2
365+
def a_inner(self) -> testproto_dot_inner_dot_inner__pb2.Inner: ...
366+
```
367+
368+
*NOTE*: This is currently not compatible with `readable_stubs`
369+
350370
### Output suppression
351371

352372
To suppress output, you can run

mypy_protobuf/main.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def __init__(
152152
relax_strict_optional_primitives: bool,
153153
use_default_deprecation_warnings: bool,
154154
generate_concrete_servicer_stubs: bool,
155+
dedot_imports: bool,
155156
grpc: bool,
156157
) -> None:
157158
self.fd = fd
@@ -160,6 +161,7 @@ def __init__(
160161
self.relax_strict_optional_primitives = relax_strict_optional_primitives
161162
self.use_default_depreaction_warnings = use_default_deprecation_warnings
162163
self.generate_concrete_servicer_stubs = generate_concrete_servicer_stubs
164+
self.dedot_imports = dedot_imports
163165
self.grpc = grpc
164166
self.lines: List[str] = []
165167
self.indent = ""
@@ -175,7 +177,7 @@ def __init__(
175177
# Comments
176178
self.source_code_info_by_scl = {tuple(location.path): location for location in fd.source_code_info.location}
177179

178-
def _import(self, path: str, name: str) -> str:
180+
def _import(self, path: str, name: str, dedot: bool = False) -> str:
179181
"""Imports a stdlib path and returns a handle to it
180182
eg. self._import("typing", "Literal") -> "Literal"
181183
"""
@@ -196,8 +198,13 @@ def _import(self, path: str, name: str) -> str:
196198
self.from_imports[imp].add((name, None))
197199
return name
198200
else:
201+
dedoted: str | None = None
202+
if dedot:
203+
# Mangle name like protoc does, replace '.' with '_dot_' and '_' with '__'
204+
dedoted = imp.replace("_", "__").replace(".", "_dot_")
205+
imp = f"{imp} as {dedoted}"
199206
self.imports.add(imp)
200-
return imp + "." + name
207+
return (dedoted or imp) + "." + name
201208

202209
def _import_message(self, name: str) -> str:
203210
"""Import a referenced message and return a handle"""
@@ -226,7 +233,7 @@ def _import_message(self, name: str) -> str:
226233
# Not in file. Must import
227234
# Python generated code ignores proto packages, so the only relevant factor is
228235
# whether it is in the file or not.
229-
import_name = self._import(message_fd.name[:-6].replace("-", "_") + "_pb2", split[0])
236+
import_name = self._import(message_fd.name[:-6].replace("-", "_") + "_pb2", split[0], dedot=self.dedot_imports)
230237

231238
remains = ".".join(split[1:])
232239
if not remains:
@@ -1137,6 +1144,7 @@ def generate_mypy_stubs(
11371144
relax_strict_optional_primitives: bool,
11381145
use_default_deprecation_warnings: bool,
11391146
generate_concrete_servicer_stubs: bool,
1147+
dedot_imports: bool,
11401148
) -> None:
11411149
for name, fd in descriptors.to_generate.items():
11421150
pkg_writer = PkgWriter(
@@ -1146,6 +1154,7 @@ def generate_mypy_stubs(
11461154
relax_strict_optional_primitives,
11471155
use_default_deprecation_warnings,
11481156
generate_concrete_servicer_stubs,
1157+
dedot_imports=dedot_imports,
11491158
grpc=False,
11501159
)
11511160

@@ -1171,6 +1180,7 @@ def generate_mypy_grpc_stubs(
11711180
relax_strict_optional_primitives: bool,
11721181
use_default_deprecation_warnings: bool,
11731182
generate_concrete_servicer_stubs: bool,
1183+
dedot_imports: bool,
11741184
) -> None:
11751185
for name, fd in descriptors.to_generate.items():
11761186
pkg_writer = PkgWriter(
@@ -1180,6 +1190,7 @@ def generate_mypy_grpc_stubs(
11801190
relax_strict_optional_primitives,
11811191
use_default_deprecation_warnings,
11821192
generate_concrete_servicer_stubs,
1193+
dedot_imports=dedot_imports,
11831194
grpc=True,
11841195
)
11851196
pkg_writer.write_grpc_async_hacks()
@@ -1237,6 +1248,7 @@ def main() -> None:
12371248
"relax_strict_optional_primitives" in request.parameter,
12381249
"use_default_deprecation_warnings" in request.parameter,
12391250
"generate_concrete_servicer_stubs" in request.parameter,
1251+
"dedot_imports" in request.parameter,
12401252
)
12411253

12421254

@@ -1251,6 +1263,7 @@ def grpc() -> None:
12511263
"relax_strict_optional_primitives" in request.parameter,
12521264
"use_default_deprecation_warnings" in request.parameter,
12531265
"generate_concrete_servicer_stubs" in request.parameter,
1266+
"dedot_imports" in request.parameter,
12541267
)
12551268

12561269

run_test.sh

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ PY_VER_MYPY_PROTOBUF_SHORT=$(echo "$PY_VER_MYPY_PROTOBUF" | cut -d. -f1-2)
88
PY_VER_MYPY=${PY_VER_MYPY:=3.12.12}
99
PY_VER_UNIT_TESTS="${PY_VER_UNIT_TESTS:=3.9.17 3.10.12 3.11.4 3.12.12 3.13.9 3.14.0}"
1010
PYTHON_PROTOBUF_VERSION=${PYTHON_PROTOBUF_VERSION:=6.32.1}
11+
DEDOT_IMPORTS=${DEDOT_IMPORTS:=0}
1112

1213
# Confirm UV installed
1314
if ! command -v uv &> /dev/null; then
@@ -125,9 +126,12 @@ MYPY_PROTOBUF_VENV=venv_$PY_VER_MYPY_PROTOBUF
125126
find proto -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_out=relax_strict_optional_primitives:test/generated
126127
find proto -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_out=use_default_deprecation_warnings:test/generated
127128
find proto -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_out=generate_concrete_servicer_stubs:test/generated
128-
# Overwrite w/ run with mypy-protobuf without flags
129-
find proto -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_out=test/generated
130-
129+
# Overwrite w/ run with mypy-protobuf without flags unless DEDOT_IMPORTS is set
130+
if [[ "$DEDOT_IMPORTS" -eq 1 ]]; then
131+
find proto -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_out=dedot_imports:test/generated
132+
else
133+
find proto -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_out=test/generated
134+
fi
131135
# Generate grpc protos
132136
find proto/testproto/grpc -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_grpc_out=test/generated
133137

@@ -136,13 +140,17 @@ MYPY_PROTOBUF_VENV=venv_$PY_VER_MYPY_PROTOBUF
136140
find proto/testproto/grpc -name "*.proto" -print0 | xargs -0 "$PROTOC" "${PROTOC_ARGS[@]}" --mypy_grpc_out=generate_concrete_servicer_stubs:test/generated-concrete
137141

138142

139-
if [[ -n $VALIDATE ]] && ! diff <(echo "$SHA_BEFORE") <(find test/generated -name "*.pyi" -print0 | xargs -0 sha1sum); then
140-
echo -e "${RED}Some .pyi files did not match. Please commit those files${NC}"
141-
exit 1
143+
# Don't validate if DEDOT_IMPORTS is set, as it changes the generated files
144+
if [[ $VALIDATE == 1 ]] && [[ $DEDOT_IMPORTS == 0 ]]; then
145+
if ! diff <(echo "$SHA_BEFORE") <(find test/generated -name "*.pyi" -print0 | xargs -0 sha1sum); then
146+
echo -e "${RED}Some .pyi files did not match. Please commit those files${NC}"
147+
exit 1
148+
fi
142149
fi
143150
)
144151

145-
ERRORS=()
152+
ERROR_FILE=$(mktemp)
153+
trap 'rm -f "$ERROR_FILE"' EXIT
146154

147155
for PY_VER in $PY_VER_UNIT_TESTS; do
148156
UNIT_TESTS_VENV=venv_$PY_VER
@@ -202,7 +210,7 @@ for PY_VER in $PY_VER_UNIT_TESTS; do
202210
cp "$MYPY_OUTPUT/mypy_output.omit_linenos" "test_negative/output.expected.$PY_VER_MYPY_TARGET.omit_linenos"
203211

204212
# Record error instead of echoing and exiting
205-
ERRORS+=("test_negative/output.expected.$PY_VER_MYPY_TARGET didnt match. Copying over for you.")
213+
echo "test_negative/output.expected.$PY_VER_MYPY_TARGET didnt match. Copying over for you." >> "$ERROR_FILE"
206214
fi
207215
)
208216

@@ -214,11 +222,11 @@ for PY_VER in $PY_VER_UNIT_TESTS; do
214222
done
215223

216224
# Report all errors at the end
217-
if [ ${#ERRORS[@]} -gt 0 ]; then
225+
if [ -s "$ERROR_FILE" ]; then
218226
echo -e "\n${RED}===============================================${NC}"
219-
for error in "${ERRORS[@]}"; do
227+
while IFS= read -r error; do
220228
echo -e "${RED}$error${NC}"
221-
done
229+
done < "$ERROR_FILE"
222230
echo -e "${RED}Now rerun${NC}"
223231
exit 1
224232
fi

0 commit comments

Comments
 (0)