gapic-generator 1.30.5__py3-none-any.whl → 1.30.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gapic/cli/generate.py +17 -8
- gapic/schema/imp.py +12 -2
- gapic/schema/metadata.py +3 -0
- gapic/schema/wrappers.py +8 -0
- gapic/templates/%namespace/%name_%version/%sub/services/%service/_shared_macros.j2 +4 -3
- gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest.py.j2 +1 -1
- gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2 +1 -1
- gapic/templates/docs/common_setup.py.j2 +35 -0
- gapic/templates/docs/conf.py.j2 +7 -2
- gapic/utils/__init__.py +2 -0
- gapic/utils/cache.py +92 -0
- {gapic_generator-1.30.5.dist-info → gapic_generator-1.30.6.dist-info}/METADATA +1 -1
- {gapic_generator-1.30.5.dist-info → gapic_generator-1.30.6.dist-info}/RECORD +108 -107
- tests/integration/goldens/asset/docs/conf.py +35 -3
- tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/async_client.py +6 -6
- tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/client.py +6 -6
- tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/base.py +1 -1
- tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/grpc.py +1 -1
- tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/grpc_asyncio.py +1 -1
- tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest.py +5 -5
- tests/integration/goldens/asset/google/cloud/asset_v1/services/asset_service/transports/rest_base.py +1 -1
- tests/integration/goldens/asset/google/cloud/asset_v1/types/asset_service.py +7 -7
- tests/integration/goldens/asset/google/cloud/asset_v1/types/assets.py +9 -9
- tests/integration/goldens/asset/tests/unit/gapic/asset_v1/test_asset_service.py +6 -6
- tests/integration/goldens/credentials/docs/conf.py +35 -3
- tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/async_client.py +2 -2
- tests/integration/goldens/credentials/google/iam/credentials_v1/services/iam_credentials/client.py +2 -2
- tests/integration/goldens/credentials/google/iam/credentials_v1/types/common.py +2 -2
- tests/integration/goldens/credentials/tests/unit/gapic/credentials_v1/test_iam_credentials.py +2 -2
- tests/integration/goldens/eventarc/docs/conf.py +35 -3
- tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/async_client.py +4 -4
- tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/client.py +4 -4
- tests/integration/goldens/eventarc/google/cloud/eventarc_v1/services/eventarc/transports/rest.py +8 -8
- tests/integration/goldens/eventarc/google/cloud/eventarc_v1/types/channel.py +1 -1
- tests/integration/goldens/eventarc/google/cloud/eventarc_v1/types/channel_connection.py +1 -1
- tests/integration/goldens/eventarc/google/cloud/eventarc_v1/types/eventarc.py +2 -2
- tests/integration/goldens/eventarc/google/cloud/eventarc_v1/types/google_channel_config.py +1 -1
- tests/integration/goldens/eventarc/google/cloud/eventarc_v1/types/trigger.py +2 -2
- tests/integration/goldens/eventarc/tests/unit/gapic/eventarc_v1/test_eventarc.py +4 -4
- tests/integration/goldens/logging/docs/conf.py +35 -3
- tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/async_client.py +5 -5
- tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/client.py +5 -5
- tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/transports/base.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/transports/grpc.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/config_service_v2/transports/grpc_asyncio.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/async_client.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/client.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/pagers.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/transports/base.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/transports/grpc.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/logging_service_v2/transports/grpc_asyncio.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/async_client.py +3 -3
- tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/client.py +3 -3
- tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc_asyncio.py +1 -1
- tests/integration/goldens/logging/google/cloud/logging_v2/types/log_entry.py +6 -6
- tests/integration/goldens/logging/google/cloud/logging_v2/types/logging.py +3 -3
- tests/integration/goldens/logging/google/cloud/logging_v2/types/logging_config.py +2 -2
- tests/integration/goldens/logging/google/cloud/logging_v2/types/logging_metrics.py +3 -3
- tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_config_service_v2.py +4 -4
- tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_logging_service_v2.py +7 -7
- tests/integration/goldens/logging/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +6 -6
- tests/integration/goldens/logging_internal/docs/conf.py +35 -3
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/async_client.py +5 -5
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/client.py +5 -5
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/transports/base.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/transports/grpc.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/config_service_v2/transports/grpc_asyncio.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/async_client.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/client.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/pagers.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/transports/base.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/transports/grpc.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/logging_service_v2/transports/grpc_asyncio.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/async_client.py +3 -3
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/client.py +3 -3
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/transports/base.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/services/metrics_service_v2/transports/grpc_asyncio.py +1 -1
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/types/log_entry.py +6 -6
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/types/logging.py +3 -3
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/types/logging_config.py +2 -2
- tests/integration/goldens/logging_internal/google/cloud/logging_v2/types/logging_metrics.py +3 -3
- tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_config_service_v2.py +4 -4
- tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_logging_service_v2.py +7 -7
- tests/integration/goldens/logging_internal/tests/unit/gapic/logging_v2/test_metrics_service_v2.py +6 -6
- tests/integration/goldens/redis/docs/conf.py +35 -3
- tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/async_client.py +5 -5
- tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/client.py +5 -5
- tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest.py +8 -8
- tests/integration/goldens/redis/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py +8 -8
- tests/integration/goldens/redis/google/cloud/redis_v1/types/cloud_redis.py +5 -5
- tests/integration/goldens/redis/tests/unit/gapic/redis_v1/test_cloud_redis.py +7 -7
- tests/integration/goldens/redis_selective/docs/conf.py +35 -3
- tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/async_client.py +5 -5
- tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/client.py +5 -5
- tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest.py +3 -3
- tests/integration/goldens/redis_selective/google/cloud/redis_v1/services/cloud_redis/transports/rest_asyncio.py +3 -3
- tests/integration/goldens/redis_selective/google/cloud/redis_v1/types/cloud_redis.py +5 -5
- tests/integration/goldens/redis_selective/tests/unit/gapic/redis_v1/test_cloud_redis.py +7 -7
- tests/unit/samplegen/test_samplegen.py +1 -1
- tests/unit/schema/test_imp.py +2 -2
- tests/unit/utils/test_cache.py +43 -0
- {gapic_generator-1.30.5.dist-info → gapic_generator-1.30.6.dist-info}/WHEEL +0 -0
- {gapic_generator-1.30.5.dist-info → gapic_generator-1.30.6.dist-info}/entry_points.txt +0 -0
- {gapic_generator-1.30.5.dist-info → gapic_generator-1.30.6.dist-info}/licenses/LICENSE +0 -0
- {gapic_generator-1.30.5.dist-info → gapic_generator-1.30.6.dist-info}/top_level.txt +0 -0
gapic/cli/generate.py
CHANGED
|
@@ -23,6 +23,7 @@ from google.protobuf.compiler import plugin_pb2
|
|
|
23
23
|
from gapic import generator
|
|
24
24
|
from gapic.schema import api
|
|
25
25
|
from gapic.utils import Options
|
|
26
|
+
from gapic.utils.cache import generation_cache_context
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
@click.command()
|
|
@@ -56,15 +57,23 @@ def generate(request: typing.BinaryIO, output: typing.BinaryIO) -> None:
|
|
|
56
57
|
[p.package for p in req.proto_file if p.name in req.file_to_generate]
|
|
57
58
|
).rstrip(".")
|
|
58
59
|
|
|
59
|
-
#
|
|
60
|
-
# This
|
|
61
|
-
#
|
|
62
|
-
|
|
60
|
+
# Create the generation cache context.
|
|
61
|
+
# This provides the shared storage for the @cached_proto_context decorator.
|
|
62
|
+
# 1. Performance: Memoizes `with_context` calls, speeding up generation significantly.
|
|
63
|
+
# 2. Safety: The decorator uses this storage to "pin" Proto objects in memory.
|
|
64
|
+
# This prevents Python's Garbage Collector from deleting objects created during
|
|
65
|
+
# `API.build` while `Generator.get_response` is still using their IDs.
|
|
66
|
+
# (See `gapic.utils.cache.cached_proto_context` for the specific pinning logic).
|
|
67
|
+
with generation_cache_context():
|
|
68
|
+
# Build the API model object.
|
|
69
|
+
# This object is a frozen representation of the whole API, and is sent
|
|
70
|
+
# to each template in the rendering step.
|
|
71
|
+
api_schema = api.API.build(req.proto_file, opts=opts, package=package)
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
# Translate into a protobuf CodeGeneratorResponse; this reads the
|
|
74
|
+
# individual templates and renders them.
|
|
75
|
+
# If there are issues, error out appropriately.
|
|
76
|
+
res = generator.Generator(opts).get_response(api_schema, opts)
|
|
68
77
|
|
|
69
78
|
# Output the serialized response.
|
|
70
79
|
output.write(res.SerializeToString())
|
gapic/schema/imp.py
CHANGED
|
@@ -26,11 +26,21 @@ class Import:
|
|
|
26
26
|
return self.package == other.package and self.module == other.module
|
|
27
27
|
|
|
28
28
|
def __str__(self) -> str:
|
|
29
|
+
# Determine if we need to suppress type checking for this import.
|
|
30
|
+
# We do this for protobuf generated files (_pb2) and api_core
|
|
31
|
+
# internals where type information might be missing or incomplete.
|
|
32
|
+
needs_type_ignore = self.module.endswith("_pb2") or "api_core" in self.package
|
|
33
|
+
if needs_type_ignore:
|
|
34
|
+
# Use 'import absolute.path as module' syntax to prevent Ruff/isort
|
|
35
|
+
# from combining this with other imports. This ensures the
|
|
36
|
+
# '# type: ignore' comment remains effective for this specific import.
|
|
37
|
+
full_module = ".".join(self.package + (self.module,))
|
|
38
|
+
alias = self.alias or self.module
|
|
39
|
+
return f"import {full_module} as {alias} # type: ignore"
|
|
40
|
+
|
|
29
41
|
answer = f"import {self.module}"
|
|
30
42
|
if self.package:
|
|
31
43
|
answer = f"from {'.'.join(self.package)} {answer}"
|
|
32
44
|
if self.alias:
|
|
33
45
|
answer += f" as {self.alias}"
|
|
34
|
-
if self.module.endswith("_pb2") or "api_core" in self.package:
|
|
35
|
-
answer += " # type: ignore"
|
|
36
46
|
return answer
|
gapic/schema/metadata.py
CHANGED
|
@@ -35,6 +35,7 @@ from google.protobuf import descriptor_pb2
|
|
|
35
35
|
from gapic.schema import imp
|
|
36
36
|
from gapic.schema import naming
|
|
37
37
|
from gapic.utils import cached_property
|
|
38
|
+
from gapic.utils import cached_proto_context
|
|
38
39
|
from gapic.utils import RESERVED_NAMES
|
|
39
40
|
|
|
40
41
|
# This class is a minor hack to optimize Address's __eq__ method.
|
|
@@ -359,6 +360,7 @@ class Address(BaseAddress):
|
|
|
359
360
|
return f'{".".join(self.package)}.{selector}'
|
|
360
361
|
return selector
|
|
361
362
|
|
|
363
|
+
@cached_proto_context
|
|
362
364
|
def with_context(self, *, collisions: Set[str]) -> "Address":
|
|
363
365
|
"""Return a derivative of this address with the provided context.
|
|
364
366
|
|
|
@@ -398,6 +400,7 @@ class Metadata:
|
|
|
398
400
|
return "\n\n".join(self.documentation.leading_detached_comments)
|
|
399
401
|
return ""
|
|
400
402
|
|
|
403
|
+
@cached_proto_context
|
|
401
404
|
def with_context(self, *, collisions: Set[str]) -> "Metadata":
|
|
402
405
|
"""Return a derivative of this metadata with the provided context.
|
|
403
406
|
|
gapic/schema/wrappers.py
CHANGED
|
@@ -67,6 +67,7 @@ from google.protobuf.json_format import MessageToDict # type: ignore
|
|
|
67
67
|
|
|
68
68
|
from gapic import utils
|
|
69
69
|
from gapic.schema import metadata
|
|
70
|
+
from gapic.utils import cached_proto_context
|
|
70
71
|
from gapic.utils import uri_sample
|
|
71
72
|
from gapic.utils import make_private
|
|
72
73
|
|
|
@@ -410,6 +411,7 @@ class Field:
|
|
|
410
411
|
"This code should not be reachable; please file a bug."
|
|
411
412
|
)
|
|
412
413
|
|
|
414
|
+
@cached_proto_context
|
|
413
415
|
def with_context(
|
|
414
416
|
self,
|
|
415
417
|
*,
|
|
@@ -805,6 +807,7 @@ class MessageType:
|
|
|
805
807
|
# message.
|
|
806
808
|
return cursor.message.get_field(*field_path[1:], collisions=collisions)
|
|
807
809
|
|
|
810
|
+
@cached_proto_context
|
|
808
811
|
def with_context(
|
|
809
812
|
self,
|
|
810
813
|
*,
|
|
@@ -937,6 +940,7 @@ class EnumType:
|
|
|
937
940
|
"""Return the identifier data to be used in templates."""
|
|
938
941
|
return self.meta.address
|
|
939
942
|
|
|
943
|
+
@cached_proto_context
|
|
940
944
|
def with_context(self, *, collisions: Set[str]) -> "EnumType":
|
|
941
945
|
"""Return a derivative of this enum with the provided context.
|
|
942
946
|
|
|
@@ -1058,6 +1062,7 @@ class ExtendedOperationInfo:
|
|
|
1058
1062
|
request_type: MessageType
|
|
1059
1063
|
operation_type: MessageType
|
|
1060
1064
|
|
|
1065
|
+
@cached_proto_context
|
|
1061
1066
|
def with_context(
|
|
1062
1067
|
self,
|
|
1063
1068
|
*,
|
|
@@ -1127,6 +1132,7 @@ class OperationInfo:
|
|
|
1127
1132
|
response_type: MessageType
|
|
1128
1133
|
metadata_type: MessageType
|
|
1129
1134
|
|
|
1135
|
+
@cached_proto_context
|
|
1130
1136
|
def with_context(
|
|
1131
1137
|
self,
|
|
1132
1138
|
*,
|
|
@@ -1937,6 +1943,7 @@ class Method:
|
|
|
1937
1943
|
"""Return True if this method has no return value, False otherwise."""
|
|
1938
1944
|
return self.output.ident.proto == "google.protobuf.Empty"
|
|
1939
1945
|
|
|
1946
|
+
@cached_proto_context
|
|
1940
1947
|
def with_context(
|
|
1941
1948
|
self,
|
|
1942
1949
|
*,
|
|
@@ -2357,6 +2364,7 @@ class Service:
|
|
|
2357
2364
|
def is_internal(self) -> bool:
|
|
2358
2365
|
return any(m.is_internal for m in self.methods.values())
|
|
2359
2366
|
|
|
2367
|
+
@cached_proto_context
|
|
2360
2368
|
def with_context(
|
|
2361
2369
|
self,
|
|
2362
2370
|
*,
|
|
@@ -196,8 +196,9 @@ def _get_http_options():
|
|
|
196
196
|
body_spec (str): The http options body i.e. method.http_options[0].body
|
|
197
197
|
method_name (str): The method name.
|
|
198
198
|
service: The service.
|
|
199
|
-
is_async (bool): Used to determine the code path i.e. whether for sync or async call.
|
|
200
|
-
|
|
199
|
+
is_async (bool): Used to determine the code path i.e. whether for sync or async call.
|
|
200
|
+
is_request_message_proto_plus_type (bool): Used to determine whether the request message is a proto-plus type. #}
|
|
201
|
+
{% macro rest_call_method_common(body_spec, method_name, service, is_async=False, is_request_message_proto_plus_type=False) %}
|
|
201
202
|
{% set service_name = service.name %}
|
|
202
203
|
{% set await_prefix = "await " if is_async else "" %}
|
|
203
204
|
{% set async_class_prefix = "Async" if is_async else "" %}
|
|
@@ -218,7 +219,7 @@ def _get_http_options():
|
|
|
218
219
|
request_url = "{host}{uri}".format(host=self._host, uri=transcoded_request['uri'])
|
|
219
220
|
method = transcoded_request['method']
|
|
220
221
|
try:
|
|
221
|
-
request_payload = {% if
|
|
222
|
+
request_payload = {% if is_request_message_proto_plus_type %}type(request).to_json(request){% else %}json_format.MessageToJson(request){% endif %}
|
|
222
223
|
|
|
223
224
|
except:
|
|
224
225
|
{# TODO(https://github.com/googleapis/gapic-generator-python/issues/2282): Remove try/except and correctly parse request payload. #}
|
|
@@ -240,7 +240,7 @@ class {{service.name}}RestTransport(_Base{{ service.name }}RestTransport):
|
|
|
240
240
|
{% endif %}
|
|
241
241
|
"""
|
|
242
242
|
|
|
243
|
-
{{ shared_macros.rest_call_method_common(body_spec, method.name, service, False, method.
|
|
243
|
+
{{ shared_macros.rest_call_method_common(body_spec, method.name, service, False, method.input.ident.is_proto_plus_type)|indent(8) }}
|
|
244
244
|
|
|
245
245
|
{% if not method.void %}
|
|
246
246
|
# Return the response
|
gapic/templates/%namespace/%name_%version/%sub/services/%service/transports/rest_asyncio.py.j2
CHANGED
|
@@ -202,7 +202,7 @@ class Async{{service.name}}RestTransport(_Base{{ service.name }}RestTransport):
|
|
|
202
202
|
{% endif %}
|
|
203
203
|
"""
|
|
204
204
|
|
|
205
|
-
{{ shared_macros.rest_call_method_common(body_spec, method.name, service, True, method.
|
|
205
|
+
{{ shared_macros.rest_call_method_common(body_spec, method.name, service, True, method.input.ident.is_proto_plus_type)|indent(8) }}
|
|
206
206
|
|
|
207
207
|
{% if not method.void %}
|
|
208
208
|
# Return the response
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{% macro sphinx_imports() -%}
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any
|
|
4
|
+
{%- endmacro %}
|
|
5
|
+
|
|
6
|
+
{% macro sphinx_setup() -%}
|
|
7
|
+
class UnexpectedUnindentFilter(logging.Filter):
|
|
8
|
+
"""Filter out warnings about unexpected unindentation following bullet lists."""
|
|
9
|
+
|
|
10
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
11
|
+
"""Filter the log record.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
record (logging.LogRecord): The log record.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
bool: False to suppress the warning, True to allow it.
|
|
18
|
+
"""
|
|
19
|
+
msg = record.getMessage()
|
|
20
|
+
if "Bullet list ends without a blank line" in msg:
|
|
21
|
+
return False
|
|
22
|
+
return True
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def setup(app: Any) -> None:
|
|
26
|
+
"""Setup the Sphinx application.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
app (Any): The Sphinx application.
|
|
30
|
+
"""
|
|
31
|
+
# Sphinx's logger is hierarchical. Adding a filter to the
|
|
32
|
+
# root 'sphinx' logger will catch warnings from all sub-loggers.
|
|
33
|
+
logger = logging.getLogger('sphinx')
|
|
34
|
+
logger.addFilter(UnexpectedUnindentFilter())
|
|
35
|
+
{%- endmacro %}
|
gapic/templates/docs/conf.py.j2
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{% extends '_base.py.j2' %}
|
|
2
2
|
|
|
3
3
|
{% block content %}
|
|
4
|
-
|
|
4
|
+
{% from "docs/common_setup.py.j2" import sphinx_imports, sphinx_setup %}
|
|
5
5
|
#
|
|
6
6
|
# {{ api.naming.warehouse_package_name }} documentation build configuration file
|
|
7
7
|
#
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
# All configuration values have a default; values that are commented out
|
|
15
15
|
# serve to show the default.
|
|
16
16
|
|
|
17
|
-
import
|
|
17
|
+
import logging
|
|
18
18
|
import os
|
|
19
19
|
import shlex
|
|
20
|
+
import sys
|
|
21
|
+
{{ sphinx_imports() }}
|
|
20
22
|
|
|
21
23
|
# If extensions (or modules to document with autodoc) are in another directory,
|
|
22
24
|
# add these directories to sys.path here. If the directory is relative to the
|
|
@@ -372,4 +374,7 @@ napoleon_use_admonition_for_references = False
|
|
|
372
374
|
napoleon_use_ivar = False
|
|
373
375
|
napoleon_use_param = True
|
|
374
376
|
napoleon_use_rtype = True
|
|
377
|
+
|
|
378
|
+
# Setup for sphinx behaviors such as warning filters.
|
|
379
|
+
{{ sphinx_setup() }}
|
|
375
380
|
{% endblock %}
|
gapic/utils/__init__.py
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
from gapic.utils.cache import cached_property
|
|
16
|
+
from gapic.utils.cache import cached_proto_context
|
|
16
17
|
from gapic.utils.case import to_snake_case
|
|
17
18
|
from gapic.utils.case import to_camel_case
|
|
18
19
|
from gapic.utils.checks import is_msg_field_pb
|
|
@@ -34,6 +35,7 @@ from gapic.utils.uri_conv import convert_uri_fieldnames
|
|
|
34
35
|
|
|
35
36
|
__all__ = (
|
|
36
37
|
"cached_property",
|
|
38
|
+
"cached_proto_context",
|
|
37
39
|
"convert_uri_fieldnames",
|
|
38
40
|
"doc",
|
|
39
41
|
"empty",
|
gapic/utils/cache.py
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
import functools
|
|
16
|
+
import contextlib
|
|
17
|
+
import threading
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
def cached_property(fx):
|
|
@@ -43,3 +45,93 @@ def cached_property(fx):
|
|
|
43
45
|
return self._cached_values[fx.__name__]
|
|
44
46
|
|
|
45
47
|
return property(inner)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Thread-local storage for the simple cache dictionary.
|
|
51
|
+
# This ensures that parallel generation tasks (if any) do not corrupt each other's cache.
|
|
52
|
+
_proto_collisions_cache_state = threading.local()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@contextlib.contextmanager
|
|
56
|
+
def generation_cache_context():
|
|
57
|
+
"""Context manager to explicitly manage the lifecycle of the generation cache.
|
|
58
|
+
|
|
59
|
+
This manager initializes a fresh dictionary in thread-local storage when entering
|
|
60
|
+
the context and strictly deletes it when exiting.
|
|
61
|
+
|
|
62
|
+
**Memory Management:**
|
|
63
|
+
The cache stores strong references to Proto objects to "pin" them in memory
|
|
64
|
+
(see `cached_proto_context`). It is critical that this context manager deletes
|
|
65
|
+
the dictionary in the `finally` block. Deleting the dictionary breaks the
|
|
66
|
+
reference chain, allowing Python's Garbage Collector to finally free all the
|
|
67
|
+
large Proto objects that were pinned during generation.
|
|
68
|
+
"""
|
|
69
|
+
# Initialize the cache as a standard dictionary.
|
|
70
|
+
_proto_collisions_cache_state.resolved_collisions = {}
|
|
71
|
+
try:
|
|
72
|
+
yield
|
|
73
|
+
finally:
|
|
74
|
+
# Delete the dictionary to free all memory and pinned objects.
|
|
75
|
+
# This is essential to prevent memory leaks in long-running processes.
|
|
76
|
+
del _proto_collisions_cache_state.resolved_collisions
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cached_proto_context(func):
|
|
80
|
+
"""Decorator to memoize `with_context` calls based on object identity and collisions.
|
|
81
|
+
|
|
82
|
+
This mechanism provides a significant performance boost by preventing
|
|
83
|
+
redundant recalculations of naming collisions during template rendering.
|
|
84
|
+
|
|
85
|
+
Since the Proto wrapper objects are unhashable (mutable), we use `id(self)` as
|
|
86
|
+
the primary cache key. Normally, this is dangerous: if the object is garbage
|
|
87
|
+
collected, Python might reuse its memory address for a *new* object, leading to
|
|
88
|
+
a cache collision (the "Zombie ID" bug).
|
|
89
|
+
|
|
90
|
+
To prevent this, this decorator stores the value as a tuple: `(result, self)`.
|
|
91
|
+
By keeping a reference to `self` in the cache value, we "pin" the object in
|
|
92
|
+
memory. This forces the Garbage Collector to keep the object alive, guaranteeing
|
|
93
|
+
that `id(self)` remains unique for the entire lifespan of the `generation_cache_context`.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
func (Callable): The function to decorate (usually `with_context`).
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Callable: The wrapped function with caching and pinning logic.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
@functools.wraps(func)
|
|
103
|
+
def wrapper(self, *, collisions, **kwargs):
|
|
104
|
+
|
|
105
|
+
# 1. Check for active cache (returns None if context is not active)
|
|
106
|
+
context_cache = getattr(
|
|
107
|
+
_proto_collisions_cache_state, "resolved_collisions", None
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# If we are not inside a generation_cache_context (e.g. unit tests),
|
|
111
|
+
# bypass the cache entirely.
|
|
112
|
+
if context_cache is None:
|
|
113
|
+
return func(self, collisions=collisions, **kwargs)
|
|
114
|
+
|
|
115
|
+
# 2. Create the cache key
|
|
116
|
+
# We use frozenset for collisions to make it hashable.
|
|
117
|
+
# We use id(self) because 'self' is not hashable.
|
|
118
|
+
collisions_key = frozenset(collisions) if collisions else None
|
|
119
|
+
key = (id(self), collisions_key)
|
|
120
|
+
|
|
121
|
+
# 3. Check Cache
|
|
122
|
+
if key in context_cache:
|
|
123
|
+
# The cache stores (result, pinned_object). We return just the result.
|
|
124
|
+
return context_cache[key][0]
|
|
125
|
+
|
|
126
|
+
# 4. Execute the actual function
|
|
127
|
+
# We ensure context_cache is passed down to the recursive calls
|
|
128
|
+
result = func(self, collisions=collisions, **kwargs)
|
|
129
|
+
|
|
130
|
+
# 5. Update Cache & Pin Object
|
|
131
|
+
# We store (result, self). The reference to 'self' prevents garbage collection,
|
|
132
|
+
# ensuring that 'id(self)' cannot be reused for a new object while this
|
|
133
|
+
# cache entry exists.
|
|
134
|
+
context_cache[key] = (result, self)
|
|
135
|
+
return result
|
|
136
|
+
|
|
137
|
+
return wrapper
|