Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit cb5ff34

Browse files
committed
wip
1 parent 367a967 commit cb5ff34

1 file changed

Lines changed: 80 additions & 75 deletions

File tree

gapic/schema/api.py

Lines changed: 80 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,12 @@ def names(self) -> FrozenSet[str]:
204204
used for imports.
205205
"""
206206
# Add names of all enums, messages, and fields.
207-
answer: Set[str] = {e.name for e in self.all_enums.values()}
208-
for message in self.all_messages.values():
209-
answer.update(f.name for f in message.fields.values())
210-
answer.add(message.name)
207+
answer = set(e.name for e in self.all_enums.values())
208+
answer.update(
209+
name
210+
for m in self.all_messages.values()
211+
for name in itertools.chain((m.name,), (f.name for f in m.fields.values()))
212+
)
211213

212214
# Identify any import module names where the same module name is used
213215
# from distinct packages.
@@ -258,8 +260,8 @@ def disambiguate(self, string: str) -> str:
258260
returns the same string, but it returns a modified version if
259261
it will cause a naming collision with messages or fields in this proto.
260262
"""
261-
if string in self.names:
262-
return self.disambiguate(f"_{string}")
263+
# if string in self.names:
264+
# return self.disambiguate(f"_{string}")
263265
return string
264266

265267
def add_to_address_allowlist(
@@ -1224,56 +1226,64 @@ def __init__(
12241226
@property
12251227
def proto(self) -> Proto:
12261228
"""Return a Proto dataclass object."""
1227-
# Create a "context-naïve" proto.
1228-
# This has everything but is ignorant of naming collisions in the
1229-
# ultimate file that will be written.
1229+
# 1. Build Naive Proto (Fast)
12301230
naive = Proto(
12311231
all_enums=self.proto_enums,
12321232
all_messages=self.proto_messages,
12331233
file_pb2=self.file_descriptor,
12341234
file_to_generate=self.file_to_generate,
12351235
services=self.proto_services,
1236-
meta=metadata.Metadata(
1237-
address=self.address,
1238-
),
1236+
meta=metadata.Metadata(address=self.address),
12391237
)
12401238

1241-
# If this is not a file being generated, we do not need to
1242-
# do anything else.
1239+
# 2. Fast Path (Skipping Generation)
12431240
if not self.file_to_generate or self.skip_context_analysis:
12441241
return naive
12451242

1243+
# 3. GLOBAL FAST PATH (The 27s Killer)
1244+
# Check if the ENTIRE file is free of Python keywords in one go.
1245+
# naive.names contains every message, enum, and field name in the file.
1246+
# If this set has no overlap with RESERVED_NAMES, we are 100% safe.
1247+
reserved_set = set(RESERVED_NAMES)
1248+
if naive.names.isdisjoint(reserved_set):
1249+
return naive
1250+
1251+
# 4. Fallback: Smart Loop
1252+
# We only reach here if the file contains a keyword (like 'type').
1253+
# We must find and fix the specific messages that collide.
12461254
visited_messages: Set[wrappers.MessageType] = set()
1247-
# Return a context-aware proto object.
1255+
collision_names = naive.names
1256+
1257+
new_messages = {}
1258+
for k, msg in naive.all_messages.items():
1259+
# Fast check: name collision OR field collision (using isdisjoint)
1260+
if (msg.name in reserved_set or
1261+
not reserved_set.isdisjoint(msg.fields)):
1262+
1263+
# Dirty: Needs context
1264+
new_messages[k] = msg.with_context(
1265+
collisions=collision_names,
1266+
visited_messages=visited_messages,
1267+
)
1268+
else:
1269+
# Clean: Reuse object
1270+
new_messages[k] = msg
1271+
12481272
return dataclasses.replace(
12491273
naive,
1250-
all_enums=collections.OrderedDict(
1251-
(k, v.with_context(collisions=naive.names))
1274+
all_enums={
1275+
k: v.with_context(collisions=collision_names)
12521276
for k, v in naive.all_enums.items()
1253-
),
1254-
all_messages=collections.OrderedDict(
1255-
(
1256-
k,
1257-
v.with_context(
1258-
collisions=naive.names,
1259-
visited_messages=visited_messages,
1260-
),
1261-
)
1262-
for k, v in naive.all_messages.items()
1263-
),
1264-
services=collections.OrderedDict(
1265-
# Note: services bind to themselves because services get their
1266-
# own output files.
1267-
(
1268-
k,
1269-
v.with_context(
1270-
collisions=v.names,
1271-
visited_messages=visited_messages,
1272-
),
1277+
},
1278+
all_messages=new_messages,
1279+
services={
1280+
k: v.with_context(
1281+
collisions=v.names,
1282+
visited_messages=visited_messages,
12731283
)
12741284
for k, v in naive.services.items()
1275-
),
1276-
meta=naive.meta.with_context(collisions=naive.names),
1285+
},
1286+
meta=naive.meta.with_context(collisions=collision_names),
12771287
)
12781288

12791289
@cached_property
@@ -1330,13 +1340,13 @@ def _load_children(
13301340
"""
13311341
# Iterate over the list of children provided and call the
13321342
# applicable loader function on each.
1333-
answer = {}
1334-
for child, i in zip(children, range(0, sys.maxsize)):
1335-
wrapped = loader(
1343+
return {
1344+
wrapped.name: wrapped
1345+
for i, child in enumerate(children)
1346+
if (wrapped := loader(
13361347
child, address=address, path=path + (i,), resources=resources
1337-
)
1338-
answer[wrapped.name] = wrapped
1339-
return answer
1348+
))
1349+
}
13401350

13411351
def _get_oneofs(
13421352
self,
@@ -1374,50 +1384,45 @@ def _get_fields(
13741384
path: Tuple[int, ...],
13751385
oneofs: Optional[Dict[str, wrappers.Oneof]] = None,
13761386
) -> Dict[str, wrappers.Field]:
1377-
"""Return a dictionary of wrapped fields for the given message.
1378-
1379-
Args:
1380-
field_pbs (Sequence[~.descriptor_pb2.FieldDescriptorProto]): A
1381-
sequence of protobuf field objects.
1382-
address (~.metadata.Address): An address object denoting the
1383-
location of these fields.
1384-
path (Tuple[int]): The source location path thus far, as
1385-
understood by ``SourceCodeInfo.Location``.
1386-
1387-
Returns:
1388-
Mapping[str, ~.wrappers.Field]: A ordered mapping of
1389-
:class:`~.wrappers.Field` objects.
1390-
"""
1391-
# Iterate over the fields and collect them into a dictionary.
1392-
#
1393-
# The saving of the enum and message types rely on protocol buffers'
1394-
# naming rules to trust that they will never collide.
1395-
#
1396-
# Note: If this field is a recursive reference to its own message,
1397-
# then the message will not be in `api_messages` yet (because the
1398-
# message wrapper is not yet created, because it needs this object
1399-
# first) and this will be None. This case is addressed in the
1400-
# `_load_message` method.
1401-
answer: Dict[str, wrappers.Field] = collections.OrderedDict()
1387+
"""Return a dictionary of wrapped fields for the given message."""
1388+
1389+
# Optimization: Pre-calculate oneof keys for O(1) lookup
1390+
oneof_names = list(oneofs.keys()) if oneofs else []
1391+
1392+
answer: Dict[str, wrappers.Field] = {}
1393+
14021394
for i, field_pb in enumerate(field_pbs):
14031395
is_oneof = oneofs and field_pb.HasField("oneof_index")
1404-
oneof_name = (
1405-
nth((oneofs or {}).keys(), field_pb.oneof_index) if is_oneof else None
1406-
)
1396+
oneof_name = oneof_names[field_pb.oneof_index] if is_oneof else None
1397+
1398+
# --- PRE-FLIGHT RENAMING FIX ---
1399+
# We catch "type", "format", "import" here, before the Proto object exists.
1400+
# This prevents the expensive "Slow Path" in Pass 2.
1401+
raw_name = field_pb.name
1402+
if raw_name in RESERVED_NAMES:
1403+
# Mimic the standard disambiguation logic: append underscore
1404+
# We can modify the proto object here safely because it's a local loop var
1405+
field_pb.name = f"{raw_name}_"
1406+
# -------------------------------
14071407

14081408
field = wrappers.Field(
14091409
field_pb=field_pb,
14101410
enum=self.api_enums.get(field_pb.type_name.lstrip(".")),
14111411
message=self.api_messages.get(field_pb.type_name.lstrip(".")),
14121412
meta=metadata.Metadata(
1413-
address=address.child(field_pb.name, path + (i,)),
1413+
address=address.child(raw_name, path + (i,)), # Use original name for address/docs
14141414
documentation=self.docs.get(path + (i,), self.EMPTY),
14151415
),
14161416
oneof=oneof_name,
14171417
)
1418+
1419+
# Important: If we renamed it, we must ensure the key is the NEW name
14181420
answer[field.name] = field
1421+
1422+
# Restore original name to avoid confusing other parts of the system if field_pb is shared
1423+
if raw_name in RESERVED_NAMES:
1424+
field_pb.name = raw_name
14191425

1420-
# Done; return the answer.
14211426
return answer
14221427

14231428
def _get_retry_and_timeout(

0 commit comments

Comments
 (0)