|
| 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) |
0 commit comments