gcp-pytemplate 0.1.0__tar.gz

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.
Files changed (138) hide show
  1. gcp_pytemplate-0.1.0/.gitattributes +2 -0
  2. gcp_pytemplate-0.1.0/.github/CODEOWNERS +1 -0
  3. gcp_pytemplate-0.1.0/.github/dependabot.yml +36 -0
  4. gcp_pytemplate-0.1.0/.github/workflows/release.yml +47 -0
  5. gcp_pytemplate-0.1.0/.github/workflows/tests.yml +32 -0
  6. gcp_pytemplate-0.1.0/.gitignore +223 -0
  7. gcp_pytemplate-0.1.0/.mcp.json +7 -0
  8. gcp_pytemplate-0.1.0/.pre-commit-config.yaml +17 -0
  9. gcp_pytemplate-0.1.0/.python-version +1 -0
  10. gcp_pytemplate-0.1.0/AGENTS.md +115 -0
  11. gcp_pytemplate-0.1.0/CHANGELOG.md +185 -0
  12. gcp_pytemplate-0.1.0/CLAUDE.md +1 -0
  13. gcp_pytemplate-0.1.0/LICENSE +21 -0
  14. gcp_pytemplate-0.1.0/Makefile +51 -0
  15. gcp_pytemplate-0.1.0/PKG-INFO +222 -0
  16. gcp_pytemplate-0.1.0/README.md +192 -0
  17. gcp_pytemplate-0.1.0/SECURITY.md +22 -0
  18. gcp_pytemplate-0.1.0/examples/api_project.yaml +12 -0
  19. gcp_pytemplate-0.1.0/examples/cli_project.yaml +12 -0
  20. gcp_pytemplate-0.1.0/examples/example-api-service/.gcp-pytemplate.yaml +8 -0
  21. gcp_pytemplate-0.1.0/examples/example-api-service/.gitattributes +2 -0
  22. gcp_pytemplate-0.1.0/examples/example-api-service/.gitignore +90 -0
  23. gcp_pytemplate-0.1.0/examples/example-api-service/.python-version +1 -0
  24. gcp_pytemplate-0.1.0/examples/example-api-service/AGENTS.md +89 -0
  25. gcp_pytemplate-0.1.0/examples/example-api-service/Makefile +58 -0
  26. gcp_pytemplate-0.1.0/examples/example-api-service/Procfile +2 -0
  27. gcp_pytemplate-0.1.0/examples/example-api-service/README.md +77 -0
  28. gcp_pytemplate-0.1.0/examples/example-api-service/deploy_configs/prod.deploy.env +16 -0
  29. gcp_pytemplate-0.1.0/examples/example-api-service/deploy_configs/stage.deploy.env +16 -0
  30. gcp_pytemplate-0.1.0/examples/example-api-service/pyproject.toml +64 -0
  31. gcp_pytemplate-0.1.0/examples/example-api-service/scripts/deploy_cloud_run.sh +101 -0
  32. gcp_pytemplate-0.1.0/examples/example-api-service/scripts/invoke_cloud_run.sh +116 -0
  33. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/__init__.py +0 -0
  34. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/api/__init__.py +0 -0
  35. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/api/router.py +44 -0
  36. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/app/__init__.py +0 -0
  37. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/app/models.py +36 -0
  38. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/config/app_config.py +64 -0
  39. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/config/app_configs/local.env +5 -0
  40. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/config/app_configs/prod.env +5 -0
  41. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/config/app_configs/stage.env +5 -0
  42. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/config/gcp_env.py +79 -0
  43. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/config/logging_config.py +96 -0
  44. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/main_api.py +70 -0
  45. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/utils/cache.py +55 -0
  46. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/utils/gcp_auth/__init__.py +0 -0
  47. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/utils/gcp_auth/exceptions.py +9 -0
  48. gcp_pytemplate-0.1.0/examples/example-api-service/src/example_api_service/utils/gcp_auth/service_account.py +169 -0
  49. gcp_pytemplate-0.1.0/examples/example-api-service/tests/conftest.py +5 -0
  50. gcp_pytemplate-0.1.0/examples/example-api-service/tests/integration/__init__.py +0 -0
  51. gcp_pytemplate-0.1.0/examples/example-api-service/tests/unit/__init__.py +0 -0
  52. gcp_pytemplate-0.1.0/examples/example-api-service/tests/unit/test_base_api.py +38 -0
  53. gcp_pytemplate-0.1.0/examples/example-api-service/tests/unit/test_cache.py +184 -0
  54. gcp_pytemplate-0.1.0/examples/example-api-service/uv.lock +976 -0
  55. gcp_pytemplate-0.1.0/examples/example-cli-job/.gcp-pytemplate.yaml +8 -0
  56. gcp_pytemplate-0.1.0/examples/example-cli-job/.gitattributes +2 -0
  57. gcp_pytemplate-0.1.0/examples/example-cli-job/.gitignore +90 -0
  58. gcp_pytemplate-0.1.0/examples/example-cli-job/.python-version +1 -0
  59. gcp_pytemplate-0.1.0/examples/example-cli-job/AGENTS.md +97 -0
  60. gcp_pytemplate-0.1.0/examples/example-cli-job/Makefile +51 -0
  61. gcp_pytemplate-0.1.0/examples/example-cli-job/Procfile +5 -0
  62. gcp_pytemplate-0.1.0/examples/example-cli-job/README.md +78 -0
  63. gcp_pytemplate-0.1.0/examples/example-cli-job/deploy_configs/prod.deploy.env +16 -0
  64. gcp_pytemplate-0.1.0/examples/example-cli-job/deploy_configs/stage.deploy.env +16 -0
  65. gcp_pytemplate-0.1.0/examples/example-cli-job/pyproject.toml +65 -0
  66. gcp_pytemplate-0.1.0/examples/example-cli-job/scripts/deploy_cloud_run_job.sh +109 -0
  67. gcp_pytemplate-0.1.0/examples/example-cli-job/scripts/execute_cloud_run_job.sh +92 -0
  68. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/__init__.py +0 -0
  69. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/app/__init__.py +0 -0
  70. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/app/models.py +36 -0
  71. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/cli/__init__.py +0 -0
  72. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/cli/commands.py +34 -0
  73. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/config/app_config.py +64 -0
  74. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/config/app_configs/local.env +5 -0
  75. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/config/app_configs/prod.env +5 -0
  76. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/config/app_configs/stage.env +5 -0
  77. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/config/gcp_env.py +79 -0
  78. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/config/logging_config.py +96 -0
  79. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/main_cli.py +14 -0
  80. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/utils/cache.py +55 -0
  81. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/utils/gcp_auth/__init__.py +0 -0
  82. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/utils/gcp_auth/exceptions.py +9 -0
  83. gcp_pytemplate-0.1.0/examples/example-cli-job/src/example_cli_job/utils/gcp_auth/service_account.py +169 -0
  84. gcp_pytemplate-0.1.0/examples/example-cli-job/tests/conftest.py +5 -0
  85. gcp_pytemplate-0.1.0/examples/example-cli-job/tests/integration/__init__.py +0 -0
  86. gcp_pytemplate-0.1.0/examples/example-cli-job/tests/unit/__init__.py +0 -0
  87. gcp_pytemplate-0.1.0/examples/example-cli-job/tests/unit/test_base_cli.py +34 -0
  88. gcp_pytemplate-0.1.0/examples/example-cli-job/tests/unit/test_cache.py +184 -0
  89. gcp_pytemplate-0.1.0/examples/example-cli-job/uv.lock +684 -0
  90. gcp_pytemplate-0.1.0/pyproject.toml +143 -0
  91. gcp_pytemplate-0.1.0/src/gcp_pytemplate/__init__.py +5 -0
  92. gcp_pytemplate-0.1.0/src/gcp_pytemplate/main.py +463 -0
  93. gcp_pytemplate-0.1.0/src/gcp_pytemplate/mcp_server.py +244 -0
  94. gcp_pytemplate-0.1.0/src/gcp_pytemplate/render.py +99 -0
  95. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/.gitattributes +2 -0
  96. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/.gitignore +90 -0
  97. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/.python-version +1 -0
  98. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/AGENTS.md +115 -0
  99. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/Makefile +84 -0
  100. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/Procfile +11 -0
  101. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/README.md +114 -0
  102. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/deploy_configs/prod.deploy.env +29 -0
  103. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/deploy_configs/stage.deploy.env +29 -0
  104. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/pyproject.toml.jinja +79 -0
  105. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/scripts/deploy_cloud_run.sh +101 -0
  106. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/scripts/deploy_cloud_run_job.sh +109 -0
  107. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/scripts/execute_cloud_run_job.sh +92 -0
  108. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/scripts/invoke_cloud_run.sh +116 -0
  109. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/__init__.py +0 -0
  110. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/api/__init__.py +0 -0
  111. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/api/router.py +44 -0
  112. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/app/__init__.py +0 -0
  113. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/app/models.py +36 -0
  114. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/cli/__init__.py +0 -0
  115. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/cli/commands.py +34 -0
  116. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/config/app_config.py +64 -0
  117. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/config/app_configs/local.env +5 -0
  118. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/config/app_configs/prod.env +5 -0
  119. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/config/app_configs/stage.env +5 -0
  120. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/config/gcp_env.py +79 -0
  121. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/config/logging_config.py +96 -0
  122. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/main_api.py +70 -0
  123. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/main_cli.py +14 -0
  124. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/utils/cache.py +55 -0
  125. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/utils/gcp_auth/__init__.py +0 -0
  126. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/utils/gcp_auth/exceptions.py +9 -0
  127. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/src/{{ project_module }}/utils/gcp_auth/service_account.py +169 -0
  128. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/tests/conftest.py +5 -0
  129. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/tests/integration/__init__.py +0 -0
  130. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/tests/unit/__init__.py +0 -0
  131. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/tests/unit/test_base_api.py +38 -0
  132. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/tests/unit/test_base_cli.py +34 -0
  133. gcp_pytemplate-0.1.0/src/gcp_pytemplate/templates/app/{{ project_slug }}/tests/unit/test_cache.py +184 -0
  134. gcp_pytemplate-0.1.0/tests/__init__.py +0 -0
  135. gcp_pytemplate-0.1.0/tests/test_mcp_server.py +200 -0
  136. gcp_pytemplate-0.1.0/tests/test_render.py +410 -0
  137. gcp_pytemplate-0.1.0/tests/test_update.py +211 -0
  138. gcp_pytemplate-0.1.0/uv.lock +1831 -0
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1 @@
1
+ * @jasonlopez01
@@ -0,0 +1,36 @@
1
+ version: 2
2
+
3
+ updates:
4
+ # Only the tool itself is listed here.
5
+ #
6
+ # Deliberately excluded:
7
+ # src/gcp_pytemplate/templates/ Jinja2 sources; the pyproject.toml there is not valid TOML
8
+ # until rendered, so nothing can resolve its dependencies.
9
+ # examples/ generated output. Its pyproject.toml files must keep matching
10
+ # what `make generate-examples` produces, so a bump there would
11
+ # desync them from the template.
12
+ - package-ecosystem: "uv"
13
+ directory: "/"
14
+ schedule:
15
+ interval: "weekly"
16
+ open-pull-requests-limit: 5
17
+ # chore is non-releasable under [tool.semantic_release], so a dependency bump never cuts a
18
+ # release on its own. Change to "fix" if you would rather they ship as patch releases.
19
+ commit-message:
20
+ prefix: "chore"
21
+ groups:
22
+ python-dependencies:
23
+ patterns:
24
+ - "*"
25
+
26
+ - package-ecosystem: "github-actions"
27
+ directory: "/"
28
+ schedule:
29
+ interval: "weekly"
30
+ open-pull-requests-limit: 5
31
+ commit-message:
32
+ prefix: "ci"
33
+ groups:
34
+ github-actions:
35
+ patterns:
36
+ - "*"
@@ -0,0 +1,47 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: [Tests]
6
+ types: [completed]
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: write # PSR: push version commit + tag, create GitHub release
11
+ id-token: write # PyPI: OIDC trusted publisher (no token needed)
12
+
13
+ jobs:
14
+ release:
15
+ runs-on: ubuntu-latest
16
+ if: github.event.workflow_run.conclusion == 'success'
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0 # PSR needs full git history to parse commits
21
+
22
+ - uses: astral-sh/setup-uv@v5
23
+
24
+ - name: Semantic Release
25
+ id: release
26
+ uses: python-semantic-release/python-semantic-release@v9
27
+ with:
28
+ github_token: ${{ secrets.GITHUB_TOKEN }}
29
+
30
+ # Built here rather than through PSR's build_command: that runs inside the PSR container,
31
+ # which has no uv, so it failed with exit 127. By this point PSR has created the version
32
+ # tag, so hatch-vcs resolves the real version rather than a .dev local one.
33
+ - name: Build distributions
34
+ if: steps.release.outputs.released == 'true'
35
+ run: uv build
36
+
37
+ - name: Publish to PyPI
38
+ # Note: no credentials needed, the OIDC trusted publisher handles auth
39
+ if: steps.release.outputs.released == 'true'
40
+ uses: pypa/gh-action-pypi-publish@release/v1
41
+
42
+ - name: Attach distributions to the GitHub Release
43
+ if: steps.release.outputs.released == 'true'
44
+ uses: python-semantic-release/publish-action@v9
45
+ with:
46
+ github_token: ${{ secrets.GITHUB_TOKEN }}
47
+ tag: ${{ steps.release.outputs.tag }}
@@ -0,0 +1,32 @@
1
+ name: Tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v5
14
+ - run: uv sync --group dev
15
+ - run: uv run ruff check .
16
+ - run: uv run ruff format --check .
17
+
18
+ test:
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ with:
27
+ fetch-tags: true # hatch-vcs needs tags to resolve the package version
28
+ - uses: astral-sh/setup-uv@v5
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ - run: uv sync --group dev
32
+ - run: uv run pytest
@@ -0,0 +1,223 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # pdm
105
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106
+ #pdm.lock
107
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108
+ # in version control.
109
+ # https://pdm.fming.dev/#use-with-ide
110
+ .pdm.toml
111
+
112
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113
+ __pypackages__/
114
+
115
+ # Celery stuff
116
+ celerybeat-schedule
117
+ celerybeat.pid
118
+
119
+ # SageMath parsed files
120
+ *.sage.py
121
+
122
+ # Environments
123
+ .venv
124
+ env/
125
+ venv/
126
+ ENV/
127
+ env.bak/
128
+ venv.bak/
129
+
130
+ # Spyder project settings
131
+ .spyderproject
132
+ .spyproject
133
+
134
+ # Rope project settings
135
+ .ropeproject
136
+
137
+ # mkdocs documentation
138
+ /site
139
+
140
+ # mypy
141
+ .mypy_cache/
142
+ .dmypy.json
143
+ dmypy.json
144
+
145
+ # Pyre type checker
146
+ .pyre/
147
+
148
+ # pytype static type analyzer
149
+ .pytype/
150
+
151
+ # Cython debug symbols
152
+ cython_debug/
153
+
154
+ # PyCharm
155
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
156
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
157
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
158
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
159
+ .idea/
160
+
161
+
162
+ # VSCode
163
+ .vscode/*
164
+ !.vscode/settings.json
165
+ !.vscode/tasks.json
166
+ !.vscode/launch.json
167
+ !.vscode/extensions.json
168
+ !.vscode/*.code-snippets
169
+
170
+ # Local History for Visual Studio Code
171
+ .history/
172
+
173
+ # Built Visual Studio Code Extensions
174
+ *.vsix
175
+
176
+ # Mac files
177
+ **/.DS_Store
178
+
179
+
180
+ # Terraform
181
+
182
+ # Local .terraform directories
183
+ **/.terraform/*
184
+
185
+ # .tfstate files
186
+ *.tfstate
187
+ *.tfstate.*
188
+
189
+ # Crash log files
190
+ crash.log
191
+
192
+ # Exclude all .tfvars files, which are likely to contain sentitive data, such as
193
+ # password, private keys, and other secrets. These should not be part of version
194
+ # control as they are data points which are potentially sensitive and subject
195
+ # to change depending on the environment.
196
+ #
197
+ *.tfvars
198
+
199
+ # Ignore override files as they are usually used to override resources locally and so
200
+ # are not checked in
201
+ override.tf
202
+ override.tf.json
203
+ *_override.tf
204
+ *_override.tf.json
205
+
206
+ # Include override files you do wish to add to version control using negated pattern
207
+ #
208
+ # !example_override.tf
209
+
210
+ # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
211
+ # example: *tfplan*
212
+
213
+ # Ignore CLI configuration files
214
+ .terraformrc
215
+ terraform.rc
216
+
217
+
218
+ # Added:
219
+ secrets/
220
+ TODO
221
+
222
+ # Per-machine Claude Code settings (contains local absolute paths)
223
+ .claude/settings.local.json
@@ -0,0 +1,7 @@
1
+ {
2
+ "mcpServers": {
3
+ "gcp-pytemplate": {
4
+ "command": "gcp-pytemplate-mcp"
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,17 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ # Files under templates/ contain Jinja2 syntax and are not valid TOML/YAML until rendered.
8
+ - id: check-yaml
9
+ exclude: ^src/gcp_pytemplate/templates/
10
+ - id: check-toml
11
+ exclude: ^src/gcp_pytemplate/templates/
12
+
13
+ - repo: https://github.com/commitizen-tools/commitizen
14
+ rev: v4.1.0
15
+ hooks:
16
+ - id: commitizen
17
+ stages: [commit-msg]
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,115 @@
1
+ # gcp-pytemplate — Agent Guide
2
+
3
+ A CLI tool that scaffolds production-ready GCP Python projects. It renders a Jinja2 template tree into a new project directory.
4
+
5
+ ## Commands
6
+
7
+ Use `make` for all core operations.
8
+
9
+ | Command | Description |
10
+ |---|---|
11
+ | `make setup` | Install dependencies and git hooks |
12
+ | `make install` | Install CLI and MCP server globally as editable (source changes apply immediately) |
13
+ | `make lint` | Lint and autoformat (ruff) |
14
+ | `make test` | Run tests |
15
+ | `make test-all` | Run tests against all supported Python versions (3.10–3.14) |
16
+ | `make generate-examples` | Regenerate `examples/` from the YAML files in `examples/*.yaml` |
17
+
18
+ Do not invoke `uv`, `ruff`, or `pytest` directly — use the make targets above.
19
+
20
+ ### MCP server
21
+
22
+ ```bash
23
+ gcp-pytemplate-mcp # starts the stdio MCP server (requires the [mcp] extra)
24
+ ```
25
+
26
+ See the **MCP Server** section in `README.md` for installation and client config.
27
+
28
+ ### Running the CLI
29
+
30
+ ```bash
31
+ uv run gcp-pytemplate new # interactive prompts
32
+ uv run gcp-pytemplate new --from-file examples/api_project.yaml
33
+ uv run gcp-pytemplate update <project-path> --components logging_config
34
+ ```
35
+
36
+ ## Project Layout
37
+
38
+ - `src/gcp_pytemplate/main.py` — Typer CLI (`new` and `update` commands), input validation, Jinja2 context assembly
39
+ - `src/gcp_pytemplate/render.py` — walks the template tree, renders file contents and paths with Jinja2
40
+ - `src/gcp_pytemplate/templates/app/{{ project_slug }}/` — the project template (Jinja2; not valid Python until rendered)
41
+ - `examples/` — pre-generated example projects (`example-api-service`, `example-cli-job`) plus the YAML inputs used to generate them
42
+ - `src/gcp_pytemplate/mcp_server.py` — MCP server (`create_project`, `update_project`, `list_components` tools)
43
+ - `tests/` — `test_render.py` (template output), `test_update.py` (update command), `test_mcp_server.py` (MCP tools)
44
+
45
+ ## Coding Standards
46
+
47
+ **Use Pydantic for data modeling.** Prefer `pydantic.BaseModel` over native `@dataclass` for any structured data (config objects, request/response shapes, context models). Use `model_validator` and `field_validator` for validation logic rather than `__post_init__`. Pydantic models are the single source of truth for schema, validation, and serialization.
48
+
49
+ **Use modern Python type hints.** Target Python 3.10+ syntax throughout:
50
+ - Built-in generics: `list[str]`, `dict[str, int]`, `tuple[str, ...]` — not `List`, `Dict`, `Tuple` from `typing`
51
+ - Union shorthand: `str | None` — not `Optional[str]` or `Union[str, None]`
52
+ - `typing.Any`, `typing.Literal`, `typing.TypeVar`, `typing.Protocol` are still fine where needed
53
+ - Annotate all function signatures (parameters and return types); omit only where the type is genuinely unknowable
54
+
55
+ **Write pytest-style tests.** Use plain functions (`def test_*`) and `assert` statements — no `unittest.TestCase`. Fixtures go in `conftest.py`. Parametrize with `@pytest.mark.parametrize` rather than looping inside a test. Use `tmp_path` for filesystem fixtures. Avoid mocking internals; test at the public API surface.
56
+
57
+ ## Key Rules
58
+
59
+ **CLI and MCP server must stay in sync.** When adding or changing inputs to the `new` command in `main.py` (parameters, defaults, validation, resolution logic), apply the equivalent change to `create_project` in `mcp_server.py`. Both share `_build_context()` for validation, but default resolution (e.g. git/gcloud config lookups) and the elicitation summary in `_format_summary` must be updated manually to match.
60
+
61
+ **Templates and examples must stay in sync.** When editing any file under `templates/`, apply the equivalent change to the corresponding file in `examples/example-api-service/` and/or `examples/example-cli-job/`. Alternatively, run `make generate-examples` to fully regenerate both examples (only appropriate when the change should affect all generated content, not for manual example-specific edits).
62
+
63
+ **Templates are excluded from ruff.** Files under `src/gcp_pytemplate/templates/` contain Jinja2 syntax and are not valid Python — ruff is configured to skip them. Don't run linting on template files.
64
+
65
+ **A `.jinja` suffix on a template file is stripped when rendered.** `pyproject.toml.jinja` renders to `pyproject.toml`. Use this for any template whose real filename would make external tooling try to parse it: under its real name, GitHub's dependency graph picked up the template `pyproject.toml` as a pip manifest and failed on the Jinja2 syntax. The suffix is stripped before the exclusion rules in `render.py` are evaluated, so those rules are written against the final path.
66
+
67
+ **Keep documentation in sync.** When making changes that affect functionality or interfaces (CLI flags, MCP tools, Python version support, make targets, project layout), update `README.md` and `AGENTS.md` to match. The Prerequisites, Usage, and Development sections of `README.md` and the Commands table in `AGENTS.md` are the primary places to check.
68
+
69
+ **Commit style: conventional commits.** Pre-commit hooks enforce the format. Prefixes and their effect on versioning and the changelog:
70
+
71
+ | Prefix | Version bump | In changelog |
72
+ |---|---|---|
73
+ | `feat:` | minor | yes |
74
+ | `fix:`, `perf:` | patch | yes |
75
+ | `docs:` | none | yes |
76
+ | `chore:`, `ci:`, `style:`, `test:`, `refactor:` | none | no |
77
+
78
+ ## SQL Conventions
79
+
80
+ **Use fully qualified resource paths.** Always reference tables with their full path — no bare table names:
81
+ - BigQuery: `project.dataset.table`
82
+ - Other databases: `database.schema.table`
83
+
84
+ **Use lowercase keywords.** Write all SQL keywords in lowercase: `select`, `from`, `where`, `join`, `group by`, `order by`, etc.
85
+
86
+ **Use single spaces around aliases.** No alignment padding — exactly one space on each side of `as`:
87
+ ```sql
88
+ -- correct
89
+ select
90
+ order_id as id,
91
+ customer_name as name
92
+ from project.dataset.orders
93
+
94
+ -- wrong
95
+ select
96
+ order_id as id,
97
+ customer_name as name
98
+ from project.dataset.orders
99
+ ```
100
+
101
+ **Use trailing commas.** Place commas at the end of each item in a select list, not the beginning.
102
+
103
+ **Indent with 4 spaces.** Indent column lists and `on` clauses 4 spaces relative to their clause keyword.
104
+
105
+
106
+ ## Writing Style
107
+
108
+ **Avoid AI tells.** Comments, docs, commit messages, and PR descriptions should read like they were written by a developer, not generated. Specific patterns to avoid:
109
+
110
+ - No em-dashes (`—`). Use a comma, semicolon, colon, or rewrite the sentence.
111
+ - No filler openers: "Certainly", "Sure", "Of course", "Absolutely", "Great", "Happy to help".
112
+ - No over-explanation of obvious things. If the code is clear, don't restate it in a comment.
113
+ - No "This commit..." or "This PR..." prefix in commit/PR titles. Start with the verb: "Add", "Fix", "Remove", "Update".
114
+ - No closing affirmations: "Let me know if you have questions", "Hope this helps", "Feel free to reach out".
115
+ - Prefer plain words over formal ones: "use" not "utilize", "show" not "demonstrate", "check" not "verify" when the meaning is the same.
@@ -0,0 +1,185 @@
1
+ # CHANGELOG
2
+
3
+
4
+ ## v0.1.0 (2026-08-06)
5
+
6
+ ### Bug Fixes
7
+
8
+ - Guard destructive scaffolding and repair generated project defects
9
+ ([`20be16b`](https://github.com/jasonlopez01/gcp-pytemplate/commit/20be16b8212ae0e155e9acfbf5787b9c2133102e))
10
+
11
+ The MCP create_project tool silently overwrote an existing project's files. It now refuses a
12
+ non-empty target unless overwrite=true, validates input before prompting, and warns in the
13
+ confirmation summary when it will replace a directory. The CLI gained a matching --overwrite flag.
14
+
15
+ A project name consisting only of punctuation slugified to "", which resolved the project root back
16
+ to the output directory itself; confirming the overwrite prompt then deleted that directory.
17
+ Project names are now rejected unless they produce an importable module name.
18
+
19
+ Jinja2 strips the final newline by default, so every rendered file was written without one and
20
+ generated projects failed their own ruff format check. Render with keep_trailing_newline and fix
21
+ the template lint errors this exposed, plus the trailing blank lines it surfaced in the Makefile
22
+ and deploy configs.
23
+
24
+ gcp_env raised a ValidationError at import when a runtime set a service name but no revision, and an
25
+ unreachable metadata server took the whole app down. It also stamped "not-set" over real
26
+ GCP_PROJECT and GCP_REGION values on local runs; the export is now gated on IS_DEPLOYED and never
27
+ overwrites.
28
+
29
+ Also corrects the documented Python version: the tool needs 3.10+, but generated projects pin 3.13+.
30
+
31
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
32
+
33
+ - Handle non-text files in the template tree
34
+ ([`d22c21c`](https://github.com/jasonlopez01/gcp-pytemplate/commit/d22c21ca1bf33b5a842a0ea572357b86c368371f))
35
+
36
+ A .DS_Store inside the template root aborted the whole render with an uncaught UnicodeDecodeError.
37
+ Because it sorts first, no files were written at all, and the traceback did not name the offending
38
+ file. Only jinja2.TemplateError was caught, so the decode error escaped.
39
+
40
+ Skip OS metadata by name, since it should never reach a generated project, and fall back to copying
41
+ any file that is not valid UTF-8 rather than trying to render it. Binary assets in the template
42
+ now pass through untouched.
43
+
44
+ Pin the read and write encoding to UTF-8 and write with newline="" so output does not depend on the
45
+ host locale, and so rendered shell scripts and Procfiles keep LF endings on Windows hosts instead
46
+ of gaining CRLF.
47
+
48
+ render_service takes an optional template_root so tests can render a fixture tree instead of the
49
+ packaged one; the five new tests each fail against the specific defect they cover.
50
+
51
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
52
+
53
+ - Keep per-machine Claude Code settings out of the sdist
54
+ ([`a975aa1`](https://github.com/jasonlopez01/gcp-pytemplate/commit/a975aa1b00cf449e7486afaa94af119b14822cad))
55
+
56
+ .claude/settings.local.json was reaching the published sdist, carrying local absolute paths. It is
57
+ untracked only because of a user-level global gitignore, which hatchling does not read.
58
+
59
+ Ignore it in the repo and exclude /.claude from the sdist target so the result does not depend on
60
+ how an individual clone is configured.
61
+
62
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
63
+
64
+ - Pin example authors so regeneration is user-independent
65
+ ([`850d24c`](https://github.com/jasonlopez01/gcp-pytemplate/commit/850d24c4dc592d87276be1be1636b6b882ca3756))
66
+
67
+ The example YAML inputs omitted author_name and author_email, so 'new' fell back to the git config
68
+ of whoever ran make generate-examples and wrote that identity into each example's pyproject.toml
69
+ and .gcp-pytemplate.yaml.
70
+
71
+ Set both fields explicitly to a documentation address (RFC 2606) so the examples regenerate
72
+ byte-identically for any contributor.
73
+
74
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
75
+
76
+ - Rename the template pyproject so tooling stops parsing it
77
+ ([`4854701`](https://github.com/jasonlopez01/gcp-pytemplate/commit/4854701cd3100663f7c317fa00c5a465d8f854dd))
78
+
79
+ GitHub's dependency graph scans the repo for manifests by filename, found
80
+ src/gcp_pytemplate/templates/app/{{ project_slug }}/pyproject.toml, and failed on the Jinja2
81
+ syntax. That produced a failing "Graph Update: pip in ..." job on every scan. Dependabot config
82
+ cannot exclude a path from the graph, so the file has to stop looking like a manifest.
83
+
84
+ render.py now strips a trailing .jinja from the rendered path, and the template is named
85
+ pyproject.toml.jinja. Stripping happens before the exclusion rules are evaluated so those rules
86
+ stay written against final paths.
87
+
88
+ Rendered output is unchanged: both examples regenerate byte-identically, and a project scaffolded
89
+ from the built wheel still produces a parseable pyproject.toml with no .jinja files left behind.
90
+
91
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
92
+
93
+ - Serve the health check at the configured route
94
+ ([`28193f8`](https://github.com/jasonlopez01/gcp-pytemplate/commit/28193f8b867b430debc974f72fea373f51292039))
95
+
96
+ HEALTH_CHECK_ROUTE was defined in app_config and set in the app config env files, but main_api
97
+ hardcoded "/healthcheck", so changing the setting had no effect. Bind the route to
98
+ APP_CONFIG.HEALTH_CHECK_ROUTE and read it from the same config in the generated test.
99
+
100
+ invoke_cloud_run.sh --health also assumed the default path. It now resolves the route from the app
101
+ config named by the deploy config, falling back to /healthcheck, so the shorthand keeps working on
102
+ a customized route.
103
+
104
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105
+
106
+ - Validate update paths and fail cleanly without a terminal
107
+ ([`3b8f40d`](https://github.com/jasonlopez01/gcp-pytemplate/commit/3b8f40d5871d56e92ec6092fb90421273e76e0eb))
108
+
109
+ The MCP update_project tool rejected absolute and parent-relative --files paths, but the CLI update
110
+ command did not, and an absolute path there crashed with an unhandled ValueError from relative_to
111
+ when printing results. Move the check into a shared _validate_rel_paths helper so both entry
112
+ points enforce it.
113
+
114
+ The questionary prompts also raised an opaque OSError with a full traceback when stdin was not a
115
+ terminal, which made scripted use of new and update unusable. Fall back to the documented defaults
116
+ for the interfaces and deploy target prompts, decline the overwrite prompt (pointing at
117
+ --overwrite), and require --components or --files for update. Aborting because the target exists
118
+ now exits 1 instead of 0.
119
+
120
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121
+
122
+ ### Documentation
123
+
124
+ - Add security and codeowners
125
+ ([`b3741d1`](https://github.com/jasonlopez01/gcp-pytemplate/commit/b3741d128d6240c17b9a4a0a8403571d96b55a01))
126
+
127
+ - Correct the deploy script referenced by execute_cloud_run_job
128
+ ([`81eccc1`](https://github.com/jasonlopez01/gcp-pytemplate/commit/81eccc1baaac666e0f3aafd6ae493d4fdc15c42e))
129
+
130
+ The requirements comment pointed at deploy_job_from_image.sh, which is not part of the template; the
131
+ script that deploys the job is deploy_cloud_run_job.sh.
132
+
133
+ Also record why args travel in an env var, and that --update-env-vars on 'jobs execute' is a
134
+ per-execution override rather than an edit to the deployed job definition.
135
+
136
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
137
+
138
+ - Update agent guides
139
+ ([`0730b28`](https://github.com/jasonlopez01/gcp-pytemplate/commit/0730b28848969191f562e67504520cbb3b6e28c5))
140
+
141
+ - Update docs
142
+ ([`d583193`](https://github.com/jasonlopez01/gcp-pytemplate/commit/d583193bca7784dc9451fb163633ef0393a5d1f6))
143
+
144
+ - Update examples
145
+ ([`b583a45`](https://github.com/jasonlopez01/gcp-pytemplate/commit/b583a45454b798eb0bb9ed6fd542b650614993bc))
146
+
147
+ - Update notes
148
+ ([`0c0404f`](https://github.com/jasonlopez01/gcp-pytemplate/commit/0c0404f84b1865d034aeef262e3ff8b96e51721e))
149
+
150
+ - Update readmes
151
+ ([`7d3ab3d`](https://github.com/jasonlopez01/gcp-pytemplate/commit/7d3ab3ddcbc8b2c29ef8bcca8c578f30d9c361d5))
152
+
153
+ ### Features
154
+
155
+ - Collect an author name only, never an email
156
+ ([`9bd8e7c`](https://github.com/jasonlopez01/gcp-pytemplate/commit/9bd8e7c7ffa53ef39079da12a9bc6fbfab86f94d))
157
+
158
+ Author was the one input resolved silently: gcp_project, gcp_region and gcp_service_account are all
159
+ prompted with a visible default, while name and email were read from git config and written into
160
+ the generated project without ever being shown. Prompt for the name with git config as the default
161
+ so the identity that lands in pyproject.toml is always something the user saw, and drop email
162
+ collection entirely. Users who want an email published can add the key to their pyproject.toml by
163
+ hand.
164
+
165
+ The generated authors block is now conditional. An empty entry is a hard build failure ("Author #1
166
+ of field project.authors must specify either name or email"), which create_project could already
167
+ produce on a machine with no git identity because it fell back to empty strings while the CLI fell
168
+ back to placeholder text. Both now agree, and the field is omitted rather than emitted blank.
169
+
170
+ Removes the --author-email flag and the author_email tool argument.
171
+
172
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
173
+
174
+ - Log unhandled request errors and report env in the health check
175
+ ([`8e2c6c3`](https://github.com/jasonlopez01/gcp-pytemplate/commit/8e2c6c38320f8fb8a25dfe7f10faeab03bbd49fb))
176
+
177
+ The request logging middleware wrapped call_next without a handler, so an unhandled error in a route
178
+ produced no request log line at all, only the server's own traceback. Wrap it, emit the failure
179
+ with its duration, and re-raise.
180
+
181
+ The health check hardcoded the project slug as the service name, which drifts from whatever APP_NAME
182
+ the loaded config sets. Source both the service name and the environment from APP_CONFIG so the
183
+ response follows APP_CONFIG_FILE.
184
+
185
+ Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@@ -0,0 +1 @@
1
+ @AGENTS.md