Skip to content

Commit 6eb7e15

Browse files
authored
Disambiguate Python-keyword-named modules (#326)
E.g. import.proto turns into import_.py, not import.py This allows the module to be imported via the normal import mechanisms.
1 parent 002fa63 commit 6eb7e15

2 files changed

Lines changed: 54 additions & 1 deletion

File tree

packages/gapic-generator/gapic/schema/api.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919

2020
import collections
2121
import dataclasses
22+
import keyword
23+
import os
2224
import sys
2325
from itertools import chain
24-
from typing import Callable, Dict, FrozenSet, Mapping, Sequence, Set, Tuple
26+
from typing import Callable, Container, Dict, FrozenSet, Mapping, Sequence, Set, Tuple
2527

2628
from google.api_core import exceptions # type: ignore
2729
from google.longrunning import operations_pb2 # type: ignore
@@ -204,10 +206,24 @@ def build(cls,
204206
file_descriptors,
205207
), opts=opts)
206208

209+
def disambiguate_keyword_fname(
210+
full_path: str,
211+
visited_names: Container[str]) -> str:
212+
path, fname = os.path.split(full_path)
213+
name, ext = os.path.splitext(fname)
214+
if name in keyword.kwlist or full_path in visited_names:
215+
name += "_"
216+
full_path = os.path.join(path, name + ext)
217+
if full_path in visited_names:
218+
return disambiguate_keyword_fname(full_path, visited_names)
219+
220+
return full_path
221+
207222
# Iterate over each FileDescriptorProto and fill out a Proto
208223
# object describing it, and save these to the instance.
209224
protos: Dict[str, Proto] = {}
210225
for fd in file_descriptors:
226+
fd.name = disambiguate_keyword_fname(fd.name, protos)
211227
protos[fd.name] = _ProtoBuilder(
212228
file_descriptor=fd,
213229
file_to_generate=fd.package.startswith(package),

packages/gapic-generator/tests/unit/schema/test_api.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,43 @@ def test_proto_names():
142142
assert proto.disambiguate('foo') == '_foo'
143143

144144

145+
def test_proto_keyword_fname():
146+
# Protos with filenames that happen to be python keywords
147+
# cannot be directly imported.
148+
# Check that the file names are unspecialized when building the API object.
149+
fd = (
150+
make_file_pb2(
151+
name='import.proto',
152+
package='google.keywords.v1',
153+
messages=(make_message_pb2(name='ImportRequest', fields=()),),
154+
),
155+
make_file_pb2(
156+
name='import_.proto',
157+
package='google.keywords.v1',
158+
messages=(make_message_pb2(name='ImportUnderRequest', fields=()),),
159+
),
160+
make_file_pb2(
161+
name='class_.proto',
162+
package='google.keywords.v1',
163+
messages=(make_message_pb2(name='ClassUnderRequest', fields=()),),
164+
),
165+
make_file_pb2(
166+
name='class.proto',
167+
package='google.keywords.v1',
168+
messages=(make_message_pb2(name='ClassRequest', fields=()),),
169+
)
170+
)
171+
172+
# We can't create new collisions, so check that renames cascade.
173+
api_schema = api.API.build(fd, package='google.keywords.v1')
174+
assert set(api_schema.protos.keys()) == {
175+
'import_.proto',
176+
'import__.proto',
177+
'class_.proto',
178+
'class__.proto',
179+
}
180+
181+
145182
def test_proto_names_import_collision():
146183
# Put together a couple of minimal protos.
147184
fd = (

0 commit comments

Comments
 (0)