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

Commit bae2f79

Browse files
committed
Address and Metadata have slots
Default values for frozen dataclass attrs are reused sometimes
1 parent e3ebc7f commit bae2f79

3 files changed

Lines changed: 68 additions & 20 deletions

File tree

gapic/schema/metadata.py

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,58 @@
3737
from gapic.utils import RESERVED_NAMES
3838

3939

40-
@dataclasses.dataclass(frozen=True)
40+
@dataclasses.dataclass(init=False, frozen=True)
4141
class Address:
42-
name: str = ''
43-
module: str = ''
44-
module_path: Tuple[int, ...] = dataclasses.field(default_factory=tuple)
45-
package: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
46-
parent: Tuple[str, ...] = dataclasses.field(default_factory=tuple)
47-
api_naming: naming.Naming = dataclasses.field(
48-
default_factory=naming.NewNaming,
42+
# There are lots of addresses and for large APIs they take up a lot of memory.
43+
# See
44+
# https://stackoverflow.com/questions/50180735/how-can-dataclasses-be-made-to-work-better-with-slots
45+
# for the current workaround for using slots with dataclasses.
46+
# If this becomes a common pattern (i.e. we need to reduce memory even more)
47+
# it may be worthwhile to look at https://github.com/starhel/dataslots
48+
__slots__ = (
49+
"name",
50+
"module",
51+
"module_path",
52+
"package",
53+
"parent",
54+
"api_naming",
55+
"collisions",
56+
"_cached_values",
4957
)
50-
collisions: FrozenSet[str] = dataclasses.field(default_factory=frozenset)
58+
name: str
59+
module: str
60+
module_path: Tuple[int, ...]
61+
package: Tuple[str, ...]
62+
parent: Tuple[str, ...]
63+
api_naming: naming.Naming
64+
collisions: FrozenSet[str]
65+
66+
def __init__(
67+
self,
68+
name="",
69+
module="",
70+
module_path=(),
71+
package=(),
72+
parent=(),
73+
api_naming=naming.NewNaming(),
74+
collisions=frozenset(),
75+
):
76+
super().__init__()
77+
super().__setattr__("name", name)
78+
super().__setattr__("module", module)
79+
super().__setattr__("module_path", module_path)
80+
super().__setattr__("package", package)
81+
super().__setattr__("parent", parent)
82+
super().__setattr__("api_naming", api_naming)
83+
super().__setattr__("collisions", collisions)
5184

5285
def __eq__(self, other) -> bool:
53-
return all([getattr(self, i) == getattr(other, i) for i
54-
in ('name', 'module', 'module_path', 'package', 'parent')])
86+
# Ignore collisions and api_naming.
87+
# Collisions are just a name disambiguation mechanism.
88+
return all(
89+
getattr(self, i) == getattr(other, i)
90+
for i in ('name', 'module', 'module_path', 'package', 'parent')
91+
)
5592

5693
def __hash__(self):
5794
# Do NOT include collisions; they are not relevant.
@@ -111,6 +148,8 @@ def module_alias(self) -> str:
111148
while still providing names that are fundamentally readable
112149
to users (albeit looking auto-generated).
113150
"""
151+
# Don't keep RESERVED_NAMES in self.collisions,
152+
# just combine the two when necessary.
114153
if self.module in self.collisions | RESERVED_NAMES:
115154
return '_'.join(
116155
(
@@ -283,12 +322,23 @@ def with_context(self, *, collisions: FrozenSet[str]) -> 'Address':
283322
return dataclasses.replace(self, collisions=collisions)
284323

285324

286-
@dataclasses.dataclass(frozen=True)
325+
@dataclasses.dataclass(init=False, frozen=True)
287326
class Metadata:
288-
address: Address = dataclasses.field(default_factory=Address)
289-
documentation: descriptor_pb2.SourceCodeInfo.Location = dataclasses.field(
290-
default_factory=descriptor_pb2.SourceCodeInfo.Location,
291-
)
327+
# See the comment for Address that describes the hoops necessary for
328+
# __slots__ with dataclasses.
329+
__slots__ = ("address", "documentation")
330+
331+
address: Address
332+
documentation: descriptor_pb2.SourceCodeInfo.Location
333+
334+
def __init__(
335+
self,
336+
address=Address(),
337+
documentation=descriptor_pb2.SourceCodeInfo.Location()
338+
):
339+
super().__init__()
340+
super().__setattr__("address", address)
341+
super().__setattr__("documentation", documentation)
292342

293343
@property
294344
def doc(self):

gapic/schema/wrappers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,7 @@ def with_context(self, *, collisions: FrozenSet[str]) -> 'Service':
11111111
self,
11121112
methods=collections.OrderedDict(
11131113
(k, v.with_context(
1114-
# A methodd's flattened fields create additional names
1114+
# A method's flattened fields create additional names
11151115
# that may conflict with module imports.
11161116
collisions=collisions | frozenset(v.flattened_fields.keys()))
11171117
)

tests/unit/schema/wrappers/test_python.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@ def test_python_eq():
2222
meta = metadata.Metadata(address=metadata.Address(
2323
name='Foo', module='bar', package=('google', 'api'),
2424
))
25-
assert wrappers.PythonType(meta=meta) == wrappers.PythonType(
26-
meta=copy.copy(meta),
27-
)
25+
assert wrappers.PythonType(meta=meta) == wrappers.PythonType(meta=meta)
2826
assert wrappers.PythonType(meta=metadata.Metadata(
2927
address=metadata.Address(name='Baz', module='bar', package=()),
3028
)) != wrappers.PythonType(meta=meta)

0 commit comments

Comments
 (0)