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

Commit ff09a8a

Browse files
committed
wip
1 parent d51a7a8 commit ff09a8a

File tree

5 files changed

+395
-19
lines changed

5 files changed

+395
-19
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "spanner",
3+
"name_pretty": "Cloud Spanner",
4+
"product_documentation": "https://cloud.google.com/spanner/docs/",
5+
"client_documentation": "https://cloud.google.com/python/docs/reference/spanner/latest",
6+
"issue_tracker": "https://issuetracker.google.com/issues?q=componentid:190851%2B%20status:open",
7+
"release_level": "stable",
8+
"language": "python",
9+
"library_type": "GAPIC_COMBO",
10+
"repo": "googleapis/python-spanner",
11+
"distribution_name": "google-cloud-spanner",
12+
"api_id": "spanner.googleapis.com",
13+
"requires_billing": true,
14+
"default_version": "v1",
15+
"codeowner_team": "@googleapis/spanner-client-libraries-python",
16+
"api_shortname": "spanner",
17+
"api_description": "is a fully managed, mission-critical, \nrelational database service that offers transactional consistency at global scale, \nschemas, SQL (ANSI 2011 with extensions), and automatic, synchronous replication \nfor high availability.\n\nBe sure to activate the Cloud Spanner API on the Developer's Console to\nuse Cloud Spanner from your project."
18+
}
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""This script is used to synthesize generated parts of this library."""
16+
17+
from pathlib import Path
18+
import shutil
19+
from typing import List, Optional
20+
import re
21+
22+
import synthtool as s
23+
from synthtool import gcp
24+
from synthtool.languages import python
25+
26+
common = gcp.CommonTemplates()
27+
28+
29+
def get_staging_dirs(
30+
# This is a customized version of the s.get_staging_dirs() function
31+
# from synthtool to # cater for copying 3 different folders from
32+
# googleapis-gen:
33+
# spanner, spanner/admin/instance and spanner/admin/database.
34+
# Source:
35+
# https://github.com/googleapis/synthtool/blob/master/synthtool/transforms.py#L280
36+
default_version: Optional[str] = None,
37+
sub_directory: Optional[str] = None,
38+
) -> List[Path]:
39+
"""Returns the list of directories, one per version, copied from
40+
https://github.com/googleapis/googleapis-gen. Will return in lexical sorting
41+
order with the exception of the default_version which will be last (if specified).
42+
43+
Args:
44+
default_version (str): the default version of the API. The directory for this version
45+
will be the last item in the returned list if specified.
46+
sub_directory (str): if a `sub_directory` is provided, only the directories within the
47+
specified `sub_directory` will be returned.
48+
49+
Returns: the empty list if no file were copied.
50+
"""
51+
52+
staging = Path("owl-bot-staging")
53+
54+
if sub_directory:
55+
staging /= sub_directory
56+
57+
if staging.is_dir():
58+
# Collect the subdirectories of the staging directory.
59+
versions = [v.name for v in staging.iterdir() if v.is_dir()]
60+
# Reorder the versions so the default version always comes last.
61+
versions = [v for v in versions if v != default_version]
62+
versions.sort()
63+
if default_version is not None:
64+
versions += [default_version]
65+
dirs = [staging / v for v in versions]
66+
for dir in dirs:
67+
s._tracked_paths.add(dir)
68+
return dirs
69+
else:
70+
return []
71+
72+
73+
spanner_default_version = "v1"
74+
spanner_admin_instance_default_version = "v1"
75+
spanner_admin_database_default_version = "v1"
76+
77+
clean_up_generated_samples = True
78+
79+
for library in get_staging_dirs(spanner_default_version, "spanner"):
80+
if clean_up_generated_samples:
81+
shutil.rmtree("samples/generated_samples", ignore_errors=True)
82+
clean_up_generated_samples = False
83+
84+
# Customization for MetricsInterceptor
85+
86+
assert 6 == s.replace(
87+
[
88+
library / "google/cloud/spanner_v1/services/spanner/transports/*.py",
89+
library / "google/cloud/spanner_v1/services/spanner/client.py",
90+
],
91+
"""from google.cloud.spanner_v1.types import transaction""",
92+
"""from google.cloud.spanner_v1.types import transaction
93+
from google.cloud.spanner_v1.metrics.metrics_interceptor import MetricsInterceptor""",
94+
)
95+
96+
assert 1 == s.replace(
97+
library / "google/cloud/spanner_v1/services/spanner/transports/*.py",
98+
"""api_audience: Optional\[str\] = None,
99+
\*\*kwargs,
100+
\) -> None:
101+
\"\"\"Instantiate the transport.""",
102+
"""api_audience: Optional[str] = None,
103+
metrics_interceptor: Optional[MetricsInterceptor] = None,
104+
**kwargs,
105+
) -> None:
106+
\"\"\"Instantiate the transport."""
107+
)
108+
109+
assert 4 == s.replace(
110+
library / "google/cloud/spanner_v1/services/spanner/transports/*.py",
111+
"""api_audience: Optional\[str\] = None,
112+
\) -> None:
113+
\"\"\"Instantiate the transport.""",
114+
"""api_audience: Optional[str] = None,
115+
metrics_interceptor: Optional[MetricsInterceptor] = None,
116+
) -> None:
117+
\"\"\"Instantiate the transport."""
118+
)
119+
120+
assert 1 == s.replace(
121+
library / "google/cloud/spanner_v1/services/spanner/transports/grpc.py",
122+
"""\)\n\n self._interceptor = _LoggingClientInterceptor\(\)""",
123+
""")
124+
125+
# Wrap the gRPC channel with the metric interceptor
126+
if metrics_interceptor is not None:
127+
self._metrics_interceptor = metrics_interceptor
128+
self._grpc_channel = grpc.intercept_channel(
129+
self._grpc_channel, metrics_interceptor
130+
)
131+
132+
self._interceptor = _LoggingClientInterceptor()"""
133+
)
134+
135+
assert 1 == s.replace(
136+
library / "google/cloud/spanner_v1/services/spanner/transports/grpc.py",
137+
"""self._stubs: Dict\[str, Callable\] = \{\}\n\n if api_mtls_endpoint:""",
138+
"""self._stubs: Dict[str, Callable] = {}
139+
self._metrics_interceptor = None
140+
141+
if api_mtls_endpoint:"""
142+
)
143+
144+
assert 1 == s.replace(
145+
library / "google/cloud/spanner_v1/services/spanner/client.py",
146+
"""# initialize with the provided callable or the passed in class
147+
self._transport = transport_init\(
148+
credentials=credentials,
149+
credentials_file=self._client_options.credentials_file,
150+
host=self._api_endpoint,
151+
scopes=self._client_options.scopes,
152+
client_cert_source_for_mtls=self._client_cert_source,
153+
quota_project_id=self._client_options.quota_project_id,
154+
client_info=client_info,
155+
always_use_jwt_access=True,
156+
api_audience=self._client_options.api_audience,
157+
\)""",
158+
"""# initialize with the provided callable or the passed in class
159+
self._transport = transport_init(
160+
credentials=credentials,
161+
credentials_file=self._client_options.credentials_file,
162+
host=self._api_endpoint,
163+
scopes=self._client_options.scopes,
164+
client_cert_source_for_mtls=self._client_cert_source,
165+
quota_project_id=self._client_options.quota_project_id,
166+
client_info=client_info,
167+
always_use_jwt_access=True,
168+
api_audience=self._client_options.api_audience,
169+
metrics_interceptor=MetricsInterceptor(),
170+
)""",
171+
)
172+
173+
assert 12 == s.replace(
174+
library / "tests/unit/gapic/spanner_v1/test_spanner.py",
175+
"""api_audience=None,\n(\s+)\)""",
176+
"""api_audience=None,
177+
metrics_interceptor=mock.ANY,
178+
)"""
179+
)
180+
181+
assert 1 == s.replace(
182+
library / "tests/unit/gapic/spanner_v1/test_spanner.py",
183+
"""api_audience="https://language.googleapis.com"\n(\s+)\)""",
184+
"""api_audience="https://language.googleapis.com",
185+
metrics_interceptor=mock.ANY,
186+
)"""
187+
)
188+
189+
count = s.replace(
190+
[
191+
library / "google/cloud/spanner_v1/services/*/transports/grpc*",
192+
library / "tests/unit/gapic/spanner_v1/*",
193+
],
194+
"^\s+options=\\[.*?\\]",
195+
"""options=[
196+
("grpc.max_send_message_length", -1),
197+
("grpc.max_receive_message_length", -1),
198+
("grpc.keepalive_time_ms", 120000),
199+
]""",
200+
flags=re.MULTILINE | re.DOTALL,
201+
)
202+
if count < 1:
203+
raise Exception("Expected replacements for gRPC channel options not made.")
204+
205+
s.move(
206+
library,
207+
excludes=[
208+
"google/cloud/spanner/**",
209+
"*.*",
210+
"noxfile.py",
211+
"docs/index.rst",
212+
"google/cloud/spanner_v1/__init__.py",
213+
"**/gapic_version.py",
214+
"testing/constraints-3.7.txt",
215+
],
216+
)
217+
218+
for library in get_staging_dirs(
219+
spanner_admin_instance_default_version, "spanner_admin_instance"
220+
):
221+
count = s.replace(
222+
[
223+
library / "google/cloud/spanner_admin_instance_v1/services/*/transports/grpc*",
224+
library / "tests/unit/gapic/spanner_admin_instance_v1/*",
225+
],
226+
"^\s+options=\\[.*?\\]",
227+
"""options=[
228+
("grpc.max_send_message_length", -1),
229+
("grpc.max_receive_message_length", -1),
230+
("grpc.keepalive_time_ms", 120000),
231+
]""",
232+
flags=re.MULTILINE | re.DOTALL,
233+
)
234+
if count < 1:
235+
raise Exception("Expected replacements for gRPC channel options not made.")
236+
s.move(
237+
library,
238+
excludes=["google/cloud/spanner_admin_instance/**", "*.*", "docs/index.rst", "noxfile.py", "**/gapic_version.py", "testing/constraints-3.7.txt",],
239+
)
240+
241+
for library in get_staging_dirs(
242+
spanner_admin_database_default_version, "spanner_admin_database"
243+
):
244+
count = s.replace(
245+
[
246+
library / "google/cloud/spanner_admin_database_v1/services/*/transports/grpc*",
247+
library / "tests/unit/gapic/spanner_admin_database_v1/*",
248+
],
249+
"^\s+options=\\[.*?\\]",
250+
"""options=[
251+
("grpc.max_send_message_length", -1),
252+
("grpc.max_receive_message_length", -1),
253+
("grpc.keepalive_time_ms", 120000),
254+
]""",
255+
flags=re.MULTILINE | re.DOTALL,
256+
)
257+
if count < 1:
258+
raise Exception("Expected replacements for gRPC channel options not made.")
259+
s.move(
260+
library,
261+
excludes=["google/cloud/spanner_admin_database/**", "*.*", "docs/index.rst", "noxfile.py", "**/gapic_version.py", "testing/constraints-3.7.txt",],
262+
)
263+
264+
s.remove_staging_dirs()
265+
266+
# ----------------------------------------------------------------------------
267+
# Add templated files
268+
# ----------------------------------------------------------------------------
269+
templated_files = common.py_library(
270+
microgenerator=True,
271+
samples=True,
272+
cov_level=98,
273+
split_system_tests=True,
274+
system_test_extras=["tracing"],
275+
system_test_python_versions=["3.12"]
276+
)
277+
s.move(
278+
templated_files,
279+
excludes=[
280+
".coveragerc",
281+
".github/workflows", # exclude gh actions as credentials are needed for tests
282+
"README.rst",
283+
".github/release-please.yml",
284+
".kokoro/test-samples-impl.sh",
285+
".kokoro/presubmit/presubmit.cfg",
286+
".kokoro/samples/python3.7/**",
287+
".kokoro/samples/python3.8/**",
288+
],
289+
)
290+
291+
# Ensure CI runs on a new instance each time
292+
s.replace(
293+
".kokoro/build.sh",
294+
"# Setup project id.",
295+
"""\
296+
# Set up creating a new instance for each system test run
297+
export GOOGLE_CLOUD_TESTS_CREATE_SPANNER_INSTANCE=true
298+
299+
# Setup project id.""",
300+
)
301+
302+
# Update samples folder in CONTRIBUTING.rst
303+
s.replace("CONTRIBUTING.rst", "samples/snippets", "samples/samples")
304+
305+
# ----------------------------------------------------------------------------
306+
# Samples templates
307+
# ----------------------------------------------------------------------------
308+
309+
python.py_samples()
310+
311+
s.replace(
312+
"samples/**/noxfile.py",
313+
'BLACK_VERSION = "black==22.3.0"',
314+
'BLACK_VERSION = "black==23.7.0"',
315+
)
316+
s.replace(
317+
"samples/**/noxfile.py",
318+
r'ALL_VERSIONS = \["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"\]',
319+
'ALL_VERSIONS = ["3.9", "3.10", "3.11", "3.12", "3.13"]',
320+
)
321+
322+
# Use a python runtime which is available in the owlbot post processor here
323+
# https://github.com/googleapis/synthtool/blob/master/docker/owlbot/python/Dockerfile
324+
s.shell.run(["nox", "-s", "blacken-3.10"], hide_output=False)

.librarian/state.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator@sha256:27eae7edfe88876a47cbdb89ec6491192706e94ce46ac0e3ff4d202ef3220fed
2+
libraries:
3+
- id: google-cloud-spanner
4+
version: 3.59.0
5+
last_generated_commit: 53f97391f3451398f7b53c7f86dabd325d205677
6+
apis:
7+
- path: google/spanner/v1
8+
service_config: spanner.yaml
9+
- path: google/spanner/admin/database/v1
10+
service_config: spanner.yaml
11+
- path: google/spanner/admin/instance/v1
12+
service_config: spanner.yaml
13+
source_roots:
14+
- .
15+
preserve_regex: []
16+
remove_regex:
17+
- google/spanner_v1
18+
tag_format: v{version}

noxfile.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@
3232
ISORT_VERSION = "isort==5.11.0"
3333
LINT_PATHS = ["docs", "google", "tests", "noxfile.py", "setup.py"]
3434

35-
DEFAULT_PYTHON_VERSION = "3.12"
35+
DEFAULT_PYTHON_VERSION = "3.14"
3636

37-
DEFAULT_MOCK_SERVER_TESTS_PYTHON_VERSION = "3.12"
38-
SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ["3.12"]
37+
DEFAULT_MOCK_SERVER_TESTS_PYTHON_VERSION = "3.14"
38+
SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ["3.14"]
3939

4040
UNIT_TEST_PYTHON_VERSIONS: List[str] = [
4141
"3.9",

0 commit comments

Comments
 (0)