Skip to content

Commit f18c5f5

Browse files
committed
feat(gapic-generator): add pure-python package initialization and versioning codegen
1 parent 7cbcd5a commit f18c5f5

4 files changed

Lines changed: 134 additions & 5 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Copyright 2026 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+
# https://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+
from gapic.codegen.writer import CodeWriter
16+
from gapic.schema.api import API
17+
18+
19+
class PackageInitGenerator:
20+
"""Pure Python generator for package initialization files (__init__.py, gapic_version.py, py.typed)."""
21+
22+
@staticmethod
23+
def generate_gapic_version(api_schema: API) -> str:
24+
"""Generates gapic_version.py content."""
25+
writer = CodeWriter()
26+
writer.write_license_header()
27+
writer.write_line(f'__version__ = "{api_schema.gapic_version}" # {{x-release-please-version}}')
28+
return writer.dump()
29+
30+
@staticmethod
31+
def generate_py_typed(api_schema: API) -> str:
32+
"""Generates py.typed file (PEP 561 marker)."""
33+
pkg_name = api_schema.naming.warehouse_package_name
34+
return f"# Marker file for PEP 561.\n# The {pkg_name} package uses inline types.\n"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2026 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+
# https://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+
from typing import Any, Optional
16+
from gapic.codegen.components.package_init import PackageInitGenerator
17+
18+
19+
class PurePythonEngine:
20+
"""Pure-Python template-free code generation engine."""
21+
22+
@staticmethod
23+
def render(template_name: str, context: dict[str, Any]) -> Optional[str]:
24+
"""Dispatches template rendering to pure-Python component generators.
25+
26+
Returns None to fall back to Jinja rendering if the template is not yet migrated.
27+
"""
28+
api_schema = context.get("api")
29+
if not api_schema:
30+
return None
31+
32+
# Package Init & Versioning Component
33+
if template_name.endswith("gapic_version.py.j2"):
34+
return PackageInitGenerator.generate_gapic_version(api_schema)
35+
if template_name.endswith("py.typed.j2"):
36+
return PackageInitGenerator.generate_py_typed(api_schema)
37+
38+
# Fallback to Jinja for un-migrated templates
39+
return None

packages/gapic-generator/gapic/generator/generator.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from typing import Any, DefaultDict, Dict, Mapping, Optional, Tuple
2323
from hashlib import sha256
2424
from collections import OrderedDict, defaultdict
25+
from gapic.codegen.engine import PurePythonEngine
2526
from gapic.samplegen_utils.utils import (
2627
coerce_response_name,
2728
is_valid_sample_cfg,
@@ -402,12 +403,16 @@ def _get_file(
402403
)
403404

404405
# Render the file contents.
406+
rendered = PurePythonEngine.render(
407+
template_name, {"api": api_schema, "opts": opts, **context}
408+
)
409+
if rendered is None:
410+
rendered = self._env.get_template(template_name).render(
411+
api=api_schema, opts=opts, **context
412+
)
413+
405414
cgr_file = CodeGeneratorResponse.File(
406-
content=formatter.fix_whitespace(
407-
self._env.get_template(template_name).render(
408-
api=api_schema, opts=opts, **context
409-
),
410-
),
415+
content=formatter.fix_whitespace(rendered),
411416
name=fn,
412417
)
413418

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2026 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+
# https://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+
from unittest.mock import MagicMock
16+
from gapic.codegen.components.package_init import PackageInitGenerator
17+
18+
19+
def test_generate_gapic_version():
20+
mock_api = MagicMock()
21+
mock_api.gapic_version = "0.1.0"
22+
content = PackageInitGenerator.generate_gapic_version(mock_api)
23+
24+
assert "# Copyright 2026 Google LLC" in content
25+
assert '__version__ = "0.1.0" # {x-release-please-version}' in content
26+
27+
28+
def test_generate_py_typed():
29+
mock_api = MagicMock()
30+
mock_api.naming.warehouse_package_name = "google-iam-credentials"
31+
content = PackageInitGenerator.generate_py_typed(mock_api)
32+
assert "# Marker file for PEP 561." in content
33+
assert "google-iam-credentials" in content
34+
35+
36+
def test_pure_python_engine_render():
37+
from gapic.codegen.engine import PurePythonEngine
38+
39+
assert PurePythonEngine.render("gapic_version.py.j2", {}) is None
40+
41+
mock_api = MagicMock()
42+
mock_api.gapic_version = "0.1.0"
43+
mock_api.naming.warehouse_package_name = "google-iam-credentials"
44+
45+
v_out = PurePythonEngine.render("gapic_version.py.j2", {"api": mock_api})
46+
assert '__version__ = "0.1.0"' in v_out
47+
48+
t_out = PurePythonEngine.render("py.typed.j2", {"api": mock_api})
49+
assert "google-iam-credentials" in t_out
50+
51+
assert PurePythonEngine.render("other_file.py.j2", {"api": mock_api}) is None

0 commit comments

Comments
 (0)