pygenkit 0.2.0__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.
Files changed (95) hide show
  1. pygenkit/__init__.py +2 -0
  2. pygenkit/ai/__init__.py +11 -0
  3. pygenkit/ai/prompts.py +27 -0
  4. pygenkit/ai/provider.py +57 -0
  5. pygenkit/ai/review.py +59 -0
  6. pygenkit/cli/__init__.py +3 -0
  7. pygenkit/cli/app.py +53 -0
  8. pygenkit/cli/commands/__init__.py +21 -0
  9. pygenkit/cli/commands/build.py +41 -0
  10. pygenkit/cli/commands/doctor.py +119 -0
  11. pygenkit/cli/commands/generate.py +52 -0
  12. pygenkit/cli/commands/health.py +64 -0
  13. pygenkit/cli/commands/init.py +23 -0
  14. pygenkit/cli/commands/inspect.py +58 -0
  15. pygenkit/cli/commands/new.py +37 -0
  16. pygenkit/cli/commands/publish.py +30 -0
  17. pygenkit/cli/commands/release.py +33 -0
  18. pygenkit/cli/commands/release_check.py +17 -0
  19. pygenkit/cli/commands/review.py +58 -0
  20. pygenkit/cli/commands/validate.py +29 -0
  21. pygenkit/cli.py +4 -0
  22. pygenkit/config/__init__.py +3 -0
  23. pygenkit/generators/__init__.py +13 -0
  24. pygenkit/generators/base.py +31 -0
  25. pygenkit/generators/deploy.py +48 -0
  26. pygenkit/generators/docker.py +43 -0
  27. pygenkit/generators/github_actions.py +68 -0
  28. pygenkit/generators/orchestrator.py +33 -0
  29. pygenkit/generators/project.py +110 -0
  30. pygenkit/health/__init__.py +3 -0
  31. pygenkit/health/api.py +57 -0
  32. pygenkit/health/checks.py +343 -0
  33. pygenkit/inspector/__init__.py +3 -0
  34. pygenkit/inspector/api.py +154 -0
  35. pygenkit/inspector/debian.py +51 -0
  36. pygenkit/inspector/detect.py +84 -0
  37. pygenkit/inspector/git.py +55 -0
  38. pygenkit/inspector/pyproject.py +74 -0
  39. pygenkit/models/__init__.py +25 -0
  40. pygenkit/models/config.py +266 -0
  41. pygenkit/models/health.py +22 -0
  42. pygenkit/models/inspection.py +56 -0
  43. pygenkit/render/__init__.py +3 -0
  44. pygenkit/render/engine.py +41 -0
  45. pygenkit/services/__init__.py +0 -0
  46. pygenkit/templates/deploy/Procfile.j2 +1 -0
  47. pygenkit/templates/deploy/fly.toml.j2 +14 -0
  48. pygenkit/templates/deploy/railway.json.j2 +12 -0
  49. pygenkit/templates/docker/Dockerfile.j2 +15 -0
  50. pygenkit/templates/docker/docker-compose.yml.j2 +17 -0
  51. pygenkit/templates/github/workflows/ci.yml.j2 +33 -0
  52. pygenkit/templates/github/workflows/publish-launchpad.yml.j2 +37 -0
  53. pygenkit/templates/github/workflows/publish-pypi.yml.j2 +32 -0
  54. pygenkit/templates/github/workflows/release.yml.j2 +28 -0
  55. pygenkit/templates/project/LICENSE.j2 +21 -0
  56. pygenkit/templates/project/README.md.j2 +21 -0
  57. pygenkit/templates/project/pyproject.toml.j2 +51 -0
  58. pygenkit/templates/project/src/__init__.py.j2 +2 -0
  59. pygenkit/templates/project/src/cli.py.j2 +5 -0
  60. pygenkit/templates/project/tests/__init__.py.j2 +0 -0
  61. pygenkit/templates/project/tests/test_cli.py.j2 +5 -0
  62. pygenkit/templates/python-cli/CHANGELOG.md.jinja +7 -0
  63. pygenkit/templates/python-cli/LICENSE.jinja +204 -0
  64. pygenkit/templates/python-cli/Makefile.jinja +31 -0
  65. pygenkit/templates/python-cli/README.md.jinja +86 -0
  66. pygenkit/templates/python-cli/debian/changelog.jinja +5 -0
  67. pygenkit/templates/python-cli/debian/control.jinja +23 -0
  68. pygenkit/templates/python-cli/debian/copyright.jinja +23 -0
  69. pygenkit/templates/python-cli/debian/install.jinja +1 -0
  70. pygenkit/templates/python-cli/debian/links.jinja +1 -0
  71. pygenkit/templates/python-cli/debian/postinst.jinja +15 -0
  72. pygenkit/templates/python-cli/debian/prerm.jinja +14 -0
  73. pygenkit/templates/python-cli/debian/rules.jinja +11 -0
  74. pygenkit/templates/python-cli/debian/source/options.jinja +2 -0
  75. pygenkit/templates/python-cli/debian/{{module_name}}.service.jinja +13 -0
  76. pygenkit/templates/python-cli/pygenkit.yaml.jinja +19 -0
  77. pygenkit/templates/python-cli/pyproject.toml.jinja +53 -0
  78. pygenkit/templates/python-cli/src/{{module_name}}/__init__.py.jinja +4 -0
  79. pygenkit/templates/python-cli/src/{{module_name}}/cli.py.jinja +28 -0
  80. pygenkit/templates/python-cli/tests/__init__.py +0 -0
  81. pygenkit/templates/python-cli/tests/test_cli.py.jinja +21 -0
  82. pygenkit/utils/__init__.py +4 -0
  83. pygenkit/utils/files.py +29 -0
  84. pygenkit/utils/filters.py +43 -0
  85. pygenkit/validators/__init__.py +3 -0
  86. pygenkit/validators/api.py +30 -0
  87. pygenkit/validators/security.py +102 -0
  88. pygenkit/validators/version.py +77 -0
  89. pygenkit/validators/workflow.py +141 -0
  90. pygenkit-0.2.0.dist-info/METADATA +350 -0
  91. pygenkit-0.2.0.dist-info/RECORD +95 -0
  92. pygenkit-0.2.0.dist-info/WHEEL +5 -0
  93. pygenkit-0.2.0.dist-info/entry_points.txt +2 -0
  94. pygenkit-0.2.0.dist-info/licenses/LICENSE +674 -0
  95. pygenkit-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ import tomllib
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from pygenkit.models.inspection import BuildBackend
8
+
9
+ BACKEND_MAP: dict[str, str] = {
10
+ "setuptools.build_meta": "setuptools",
11
+ "hatchling.build": "hatchling",
12
+ "poetry.core.masonry.api": "poetry",
13
+ "flit_core.buildapi": "flit",
14
+ "pdm.buildapi": "pdm",
15
+ "maturin": "maturin",
16
+ "mesonpy": "meson-python",
17
+ "scikit_build_core.build": "scikit-build-core",
18
+ }
19
+
20
+
21
+ def read_pyproject(root: str | Path) -> dict[str, Any]:
22
+ path = Path(root) / "pyproject.toml"
23
+ if not path.exists():
24
+ raise FileNotFoundError(f"pyproject.toml not found in {root}")
25
+ raw = path.read_bytes()
26
+ return tomllib.loads(raw.decode("utf-8"))
27
+
28
+
29
+ def detect_build_backend(data: dict[str, Any]) -> BuildBackend | None:
30
+ build_system = data.get("build-system", {})
31
+ backend_path = build_system.get("build-backend", "")
32
+ requires = build_system.get("requires", [])
33
+
34
+ name = BACKEND_MAP.get(backend_path, backend_path.split(".")[0] if backend_path else "unknown")
35
+ if not backend_path:
36
+ return None
37
+
38
+ return BuildBackend(name=name, backend=backend_path, requires=requires)
39
+
40
+
41
+ def _get_project_field(data: dict[str, Any], field: str) -> str | None:
42
+ project = data.get("project", {})
43
+ if not isinstance(project, dict):
44
+ return None
45
+ val = project.get(field)
46
+ return str(val) if val is not None else None
47
+
48
+
49
+ def detect_project_name(data: dict[str, Any]) -> str | None:
50
+ return _get_project_field(data, "name")
51
+
52
+
53
+ def detect_project_version(data: dict[str, Any]) -> str | None:
54
+ return _get_project_field(data, "version")
55
+
56
+
57
+ def detect_python_requires(data: dict[str, Any]) -> str | None:
58
+ return _get_project_field(data, "requires-python")
59
+
60
+
61
+ def detect_dependencies(data: dict[str, Any]) -> list[str]:
62
+ project = data.get("project", {})
63
+ if not isinstance(project, dict):
64
+ return []
65
+ deps = project.get("dependencies", [])
66
+ return [str(d) for d in deps] if isinstance(deps, list) else []
67
+
68
+
69
+ def detect_test_deps(data: dict[str, Any]) -> list[str]:
70
+ optional = data.get("project", {}).get("optional-dependencies", {})
71
+ deps: list[str] = []
72
+ for group in optional.values():
73
+ deps.extend(group)
74
+ return deps
@@ -0,0 +1,25 @@
1
+ from pygenkit.models.config import (
2
+ CIConfig,
3
+ DebianConfig,
4
+ DeployConfig,
5
+ DockerConfig,
6
+ GitHubConfig,
7
+ ProjectConfig,
8
+ PyGenKitConfig,
9
+ PyPIConfig,
10
+ ReleaseConfig,
11
+ )
12
+ from pygenkit.models.inspection import ProjectInspection
13
+
14
+ __all__ = [
15
+ "PyGenKitConfig",
16
+ "ProjectConfig",
17
+ "CIConfig",
18
+ "ReleaseConfig",
19
+ "PyPIConfig",
20
+ "DebianConfig",
21
+ "GitHubConfig",
22
+ "DockerConfig",
23
+ "DeployConfig",
24
+ "ProjectInspection",
25
+ ]
@@ -0,0 +1,266 @@
1
+ from __future__ import annotations
2
+
3
+ import tomllib
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+
7
+
8
+ @dataclass
9
+ class ProjectConfig:
10
+ name: str
11
+ version: str = "0.1.0"
12
+ extras: str = ""
13
+
14
+
15
+ @dataclass
16
+ class CIConfig:
17
+ python_versions: list[str] = field(default_factory=lambda: ["3.12"])
18
+ runner: str = "ubuntu-latest"
19
+ lint: bool = True
20
+ type_check: bool = True
21
+
22
+
23
+ @dataclass
24
+ class ReleaseConfig:
25
+ branch: str = "main"
26
+ tag_prefix: str = "v"
27
+ changelog: str = "CHANGELOG.md"
28
+
29
+
30
+ @dataclass
31
+ class PyPIConfig:
32
+ enabled: bool = True
33
+ environment: str = "pypi"
34
+ trusted_publishing: bool = True
35
+
36
+
37
+ @dataclass
38
+ class DebianConfig:
39
+ enabled: bool = True
40
+ email: str = ""
41
+ name: str = ""
42
+ owner: str = ""
43
+ ppa: str = "tools"
44
+ distributions: list[str] = field(default_factory=lambda: ["noble"])
45
+ revision: str = "1"
46
+
47
+
48
+ @dataclass
49
+ class GitHubConfig:
50
+ ci: bool = True
51
+ release: bool = True
52
+ publish_pypi: bool = True
53
+ publish_launchpad: bool = True
54
+
55
+
56
+ @dataclass
57
+ class DockerConfig:
58
+ enabled: bool = False
59
+ base_image: str = "3.12"
60
+ port: int = 8000
61
+ volumes: list[str] = field(default_factory=list)
62
+
63
+
64
+ @dataclass
65
+ class DeployConfig:
66
+ enabled: bool = False
67
+ fly: bool = True
68
+ heroku: bool = True
69
+ railway: bool = True
70
+ primary_region: str = "iad"
71
+ port: int = 8000
72
+ memory: str = "512mb"
73
+ cpu_kind: str = "shared"
74
+ cpus: int = 1
75
+
76
+
77
+ @dataclass
78
+ class PyGenKitConfig:
79
+ project: ProjectConfig
80
+ ci: CIConfig = field(default_factory=CIConfig)
81
+ release: ReleaseConfig = field(default_factory=ReleaseConfig)
82
+ pypi: PyPIConfig = field(default_factory=PyPIConfig)
83
+ debian: DebianConfig = field(default_factory=DebianConfig)
84
+ github: GitHubConfig = field(default_factory=GitHubConfig)
85
+ docker: DockerConfig = field(default_factory=DockerConfig)
86
+ deploy: DeployConfig = field(default_factory=DeployConfig)
87
+
88
+ @classmethod
89
+ def load(cls, path: str | Path) -> PyGenKitConfig:
90
+ path = Path(path)
91
+ if not path.exists():
92
+ raise FileNotFoundError(f"Config not found: {path}")
93
+
94
+ raw = path.read_bytes()
95
+ data = tomllib.loads(raw.decode("utf-8"))
96
+
97
+ return cls(
98
+ project=ProjectConfig(
99
+ name=data.get("project", {}).get("name", ""),
100
+ version=data.get("project", {}).get("version", "0.1.0"),
101
+ extras=data.get("project", {}).get("extras", ""),
102
+ ),
103
+ ci=CIConfig(
104
+ python_versions=data.get("ci", {}).get("python_versions", ["3.12"]),
105
+ runner=data.get("ci", {}).get("runner", "ubuntu-latest"),
106
+ lint=data.get("ci", {}).get("lint", True),
107
+ type_check=data.get("ci", {}).get("type_check", True),
108
+ ),
109
+ release=ReleaseConfig(
110
+ branch=data.get("release", {}).get("branch", "main"),
111
+ tag_prefix=data.get("release", {}).get("tag_prefix", "v"),
112
+ changelog=data.get("release", {}).get("changelog", "CHANGELOG.md"),
113
+ ),
114
+ pypi=PyPIConfig(
115
+ enabled=data.get("pypi", {}).get("enabled", True),
116
+ environment=data.get("pypi", {}).get("environment", "pypi"),
117
+ trusted_publishing=data.get("pypi", {}).get("trusted_publishing", True),
118
+ ),
119
+ debian=DebianConfig(
120
+ enabled=data.get("debian", {}).get("enabled", True),
121
+ email=data.get("debian", {}).get("email", ""),
122
+ name=data.get("debian", {}).get("name", ""),
123
+ owner=data.get("debian", {}).get("owner", ""),
124
+ ppa=data.get("debian", {}).get("ppa", "tools"),
125
+ distributions=data.get("debian", {}).get("distributions", ["noble"]),
126
+ revision=data.get("debian", {}).get("revision", "1"),
127
+ ),
128
+ github=GitHubConfig(
129
+ ci=data.get("github", {}).get("ci", True),
130
+ release=data.get("github", {}).get("release", True),
131
+ publish_pypi=data.get("github", {}).get("publish_pypi", True),
132
+ publish_launchpad=data.get("github", {}).get("publish_launchpad", True),
133
+ ),
134
+ docker=DockerConfig(
135
+ enabled=data.get("docker", {}).get("enabled", False),
136
+ base_image=data.get("docker", {}).get("base_image", "3.12"),
137
+ port=data.get("docker", {}).get("port", 8000),
138
+ volumes=data.get("docker", {}).get("volumes", []),
139
+ ),
140
+ deploy=DeployConfig(
141
+ enabled=data.get("deploy", {}).get("enabled", False),
142
+ fly=data.get("deploy", {}).get("fly", True),
143
+ heroku=data.get("deploy", {}).get("heroku", True),
144
+ railway=data.get("deploy", {}).get("railway", True),
145
+ primary_region=data.get("deploy", {}).get("primary_region", "iad"),
146
+ port=data.get("deploy", {}).get("port", 8000),
147
+ memory=data.get("deploy", {}).get("memory", "512mb"),
148
+ cpu_kind=data.get("deploy", {}).get("cpu_kind", "shared"),
149
+ cpus=data.get("deploy", {}).get("cpus", 1),
150
+ ),
151
+ )
152
+
153
+ @classmethod
154
+ def generate_default(cls, name: str) -> str:
155
+ return f"""[project]
156
+ name = "{name}"
157
+ version = "0.1.0"
158
+ extras = ""
159
+
160
+ [ci]
161
+ python_versions = ["3.12", "3.13"]
162
+ runner = "ubuntu-latest"
163
+ lint = true
164
+ type_check = true
165
+
166
+ [release]
167
+ branch = "main"
168
+ tag_prefix = "v"
169
+ changelog = "CHANGELOG.md"
170
+
171
+ [pypi]
172
+ enabled = true
173
+ environment = "pypi"
174
+ trusted_publishing = true
175
+
176
+ [debian]
177
+ enabled = true
178
+ email = ""
179
+ name = ""
180
+ owner = ""
181
+ ppa = "tools"
182
+ distributions = ["noble"]
183
+ revision = "1"
184
+
185
+ [github]
186
+ ci = true
187
+ release = true
188
+ publish_pypi = true
189
+ publish_launchpad = true
190
+
191
+ [docker]
192
+ enabled = false
193
+ base_image = "3.12"
194
+ port = 8000
195
+ volumes = []
196
+
197
+ [deploy]
198
+ enabled = false
199
+ fly = true
200
+ heroku = true
201
+ railway = true
202
+ primary_region = "iad"
203
+ port = 8000
204
+ memory = "512mb"
205
+ cpu_kind = "shared"
206
+ cpus = 1
207
+ """
208
+
209
+ def save(self, path: str | Path) -> None:
210
+ path = Path(path)
211
+ path.write_text(self._to_toml(), encoding="utf-8")
212
+
213
+ def _to_toml(self) -> str:
214
+ return f"""[project]
215
+ name = "{self.project.name}"
216
+ version = "{self.project.version}"
217
+ extras = "{self.project.extras}"
218
+
219
+ [ci]
220
+ python_versions = {self.ci.python_versions}
221
+ runner = "{self.ci.runner}"
222
+ lint = {str(self.ci.lint).lower()}
223
+ type_check = {str(self.ci.type_check).lower()}
224
+
225
+ [release]
226
+ branch = "{self.release.branch}"
227
+ tag_prefix = "{self.release.tag_prefix}"
228
+ changelog = "{self.release.changelog}"
229
+
230
+ [pypi]
231
+ enabled = {str(self.pypi.enabled).lower()}
232
+ environment = "{self.pypi.environment}"
233
+ trusted_publishing = {str(self.pypi.trusted_publishing).lower()}
234
+
235
+ [debian]
236
+ enabled = {str(self.debian.enabled).lower()}
237
+ email = "{self.debian.email}"
238
+ name = "{self.debian.name}"
239
+ owner = "{self.debian.owner}"
240
+ ppa = "{self.debian.ppa}"
241
+ distributions = {self.debian.distributions}
242
+ revision = "{self.debian.revision}"
243
+
244
+ [github]
245
+ ci = {str(self.github.ci).lower()}
246
+ release = {str(self.github.release).lower()}
247
+ publish_pypi = {str(self.github.publish_pypi).lower()}
248
+ publish_launchpad = {str(self.github.publish_launchpad).lower()}
249
+
250
+ [docker]
251
+ enabled = {str(self.docker.enabled).lower()}
252
+ base_image = "{self.docker.base_image}"
253
+ port = {self.docker.port}
254
+ volumes = {self.docker.volumes}
255
+
256
+ [deploy]
257
+ enabled = {str(self.deploy.enabled).lower()}
258
+ fly = {str(self.deploy.fly).lower()}
259
+ heroku = {str(self.deploy.heroku).lower()}
260
+ railway = {str(self.deploy.railway).lower()}
261
+ primary_region = "{self.deploy.primary_region}"
262
+ port = {self.deploy.port}
263
+ memory = "{self.deploy.memory}"
264
+ cpu_kind = "{self.deploy.cpu_kind}"
265
+ cpus = {self.deploy.cpus}
266
+ """
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+
6
+ @dataclass
7
+ class CategoryScore:
8
+ name: str
9
+ weight: float # contribution to total (0.0 to 1.0, sum = 1.0)
10
+ score: float = 0.0 # 0.0 to 1.0
11
+ passed: int = 0
12
+ total: int = 0
13
+ details: list[str] = field(default_factory=list)
14
+ issues: list[str] = field(default_factory=list)
15
+
16
+
17
+ @dataclass
18
+ class HealthReport:
19
+ score: int # 0-100
20
+ categories: dict[str, CategoryScore] = field(default_factory=dict)
21
+ issues: list[str] = field(default_factory=list)
22
+ suggestions: list[str] = field(default_factory=list)
@@ -0,0 +1,56 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+
6
+ @dataclass
7
+ class BuildBackend:
8
+ name: str
9
+ backend: str # setuptools, hatchling, poetry, etc.
10
+ requires: list[str] = field(default_factory=list)
11
+
12
+
13
+ @dataclass
14
+ class DebianInspection:
15
+ present: bool = False
16
+ has_control: bool = False
17
+ has_changelog: bool = False
18
+ version_in_changelog: str | None = None
19
+ issues: list[str] = field(default_factory=list)
20
+
21
+
22
+ @dataclass
23
+ class WorkflowInspection:
24
+ has_ci: bool = False
25
+ has_pypi_publish: bool = False
26
+ has_launchpad: bool = False
27
+ files: list[str] = field(default_factory=list)
28
+
29
+
30
+ @dataclass
31
+ class VersionInspection:
32
+ pyproject_version: str | None = None
33
+ init_version: str | None = None
34
+ changelog_version: str | None = None
35
+ git_tags: list[str] = field(default_factory=list)
36
+ consistent: bool = False
37
+ issues: list[str] = field(default_factory=list)
38
+
39
+
40
+ @dataclass
41
+ class ProjectInspection:
42
+ name: str | None = None
43
+ version: str | None = None
44
+ python_requires: str | None = None
45
+ module: str | None = None
46
+ root: str = ""
47
+ build_backend: BuildBackend | None = None
48
+ has_tests: bool = False
49
+ has_license: bool = False
50
+ license_type: str | None = None
51
+ has_debian: DebianInspection = field(default_factory=DebianInspection)
52
+ has_workflows: WorkflowInspection = field(default_factory=WorkflowInspection)
53
+ versions: VersionInspection = field(default_factory=VersionInspection)
54
+ github_remote: str | None = None
55
+ errors: list[str] = field(default_factory=list)
56
+ warnings: list[str] = field(default_factory=list)
@@ -0,0 +1,3 @@
1
+ from pygenkit.render.engine import RenderEngine
2
+
3
+ __all__ = ["RenderEngine"]
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from jinja2 import Environment, FileSystemLoader, StrictUndefined
7
+
8
+ from pygenkit.utils.filters import TemplateFilters
9
+
10
+
11
+ class RenderEngine:
12
+ def __init__(self, templates_dir: str | Path) -> None:
13
+ self.templates_dir = Path(templates_dir)
14
+ self._env = Environment(
15
+ loader=FileSystemLoader(str(self.templates_dir)),
16
+ undefined=StrictUndefined,
17
+ trim_blocks=True,
18
+ lstrip_blocks=True,
19
+ keep_trailing_newline=True,
20
+ )
21
+ self._env.filters.update(TemplateFilters.get_filters())
22
+
23
+ def render_string(self, template: str, context: dict[str, Any]) -> str:
24
+ t = self._env.from_string(template)
25
+ return t.render(**context)
26
+
27
+ def render_file(self, template_path: str, context: dict[str, Any]) -> str:
28
+ t = self._env.get_template(template_path)
29
+ return t.render(**context)
30
+
31
+ def render_to_file(
32
+ self,
33
+ template_path: str,
34
+ output_path: str | Path,
35
+ context: dict[str, Any],
36
+ ) -> Path:
37
+ content = self.render_file(template_path, context)
38
+ out = Path(output_path)
39
+ out.parent.mkdir(parents=True, exist_ok=True)
40
+ out.write_text(content, encoding="utf-8")
41
+ return out
File without changes
@@ -0,0 +1 @@
1
+ web: {{ project.name }}
@@ -0,0 +1,14 @@
1
+ app = "{{ project.name }}"
2
+ primary_region = "{{ deploy.primary_region }}"
3
+
4
+ [build]
5
+ dockerfile = "Dockerfile"
6
+
7
+ [http_service]
8
+ internal_port = {{ deploy.port }}
9
+ force_https = true
10
+
11
+ [[vm]]
12
+ memory = "{{ deploy.memory }}"
13
+ cpu_kind = "{{ deploy.cpu_kind }}"
14
+ cpus = {{ deploy.cpus }}
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://railway.app/railway.schema.json",
3
+ "build": {
4
+ "builder": "NIXPACKS",
5
+ "buildCommand": "pip install build && python -m build --wheel"
6
+ },
7
+ "deploy": {
8
+ "numReplicas": 1,
9
+ "restartPolicyType": "ON_FAILURE",
10
+ "restartPolicyMaxRetries": 10
11
+ }
12
+ }
@@ -0,0 +1,15 @@
1
+ FROM python:{{ docker.base_image }}-slim AS builder
2
+
3
+ WORKDIR /build
4
+ COPY . .
5
+ RUN pip install build && python -m build --wheel
6
+
7
+ FROM python:{{ docker.base_image }}-slim
8
+
9
+ WORKDIR /app
10
+ COPY --from=builder /build/dist/*.whl /tmp/
11
+ RUN pip install /tmp/*.whl && rm /tmp/*.whl
12
+
13
+ EXPOSE {{ docker.port }}
14
+
15
+ CMD ["{{ project.name }}"]
@@ -0,0 +1,17 @@
1
+ version: "3.9"
2
+
3
+ services:
4
+ app:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ ports:
9
+ - "{{ docker.port }}:{{ docker.port }}"
10
+ environment:
11
+ - PYTHONUNBUFFERED=1
12
+ {% if docker.volumes %}
13
+ volumes:
14
+ {% for vol in docker.volumes %}
15
+ - {{ vol }}
16
+ {% endfor %}
17
+ {% endif %}
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [{{ release.branch }}]
6
+ pull_request:
7
+ branches: [{{ release.branch }}]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: {{ ci.runner }}
12
+ strategy:
13
+ matrix:
14
+ python-version: {{ ci.python_versions }}
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+ - uses: actions/setup-python@v6
18
+ with:
19
+ python-version: {% raw %}${{ matrix.python-version }}{% endraw %}
20
+ - name: Install dependencies
21
+ run: |
22
+ python -m pip install --upgrade pip
23
+ pip install .{% if project.extras %}["{{ project.extras }}"]{% endif %}
24
+ {% if ci.lint %}
25
+ - name: Lint
26
+ run: pip install ruff && ruff check .
27
+ {% endif %}
28
+ {% if ci.type_check %}
29
+ - name: Type check
30
+ run: pip install mypy && mypy src/
31
+ {% endif %}
32
+ - name: Test
33
+ run: pip install pytest && pytest
@@ -0,0 +1,37 @@
1
+ name: Publish to Launchpad
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ launchpad:
9
+ runs-on: {{ ci.runner }}
10
+ environment: launchpad
11
+ steps:
12
+ - uses: actions/checkout@v6
13
+ - uses: actions/setup-python@v6
14
+ with:
15
+ python-version: "{{ ci.python_versions[0] }}"
16
+ - name: Install build deps
17
+ run: |
18
+ sudo apt-get update
19
+ sudo apt-get install -y devscripts debhelper dput
20
+ - name: Import GPG signing key
21
+ env:
22
+ GPG_PRIVATE_KEY: {% raw %}${{ secrets.GPG_PRIVATE_KEY }}{% endraw %}
23
+ GPG_PASSPHRASE: {% raw %}${{ secrets.GPG_PASSPHRASE }}{% endraw %}
24
+ GPG_KEY_ID: {% raw %}${{ secrets.GPG_KEY_ID }}{% endraw %}
25
+ run: |
26
+ printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
27
+ printf '%s' "$GPG_PASSPHRASE" | gpg \
28
+ --batch --yes --pinentry-mode loopback --passphrase-fd 0 \
29
+ --local-user "$GPG_KEY_ID" --sign /dev/null
30
+ - name: Build source package
31
+ env:
32
+ GPG_KEY_ID: {% raw %}${{ secrets.GPG_KEY_ID }}{% endraw %}
33
+ run: |
34
+ debuild -S -sa -d -k"$GPG_KEY_ID"
35
+ - name: Upload to Launchpad
36
+ run: |
37
+ dput ppa:{{ debian.owner }}/{{ debian.ppa }} ../*.changes
@@ -0,0 +1,32 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: {{ ci.runner }}
13
+ environment: {{ pypi.environment }}
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ - uses: actions/setup-python@v6
17
+ with:
18
+ python-version: "{{ ci.python_versions[0] }}"
19
+ - name: Build
20
+ run: |
21
+ pip install build
22
+ python -m build
23
+ {% if pypi.trusted_publishing %}
24
+ - name: Publish (trusted)
25
+ uses: pypa/gh-action-pypi-publish@release/v1
26
+ {% else %}
27
+ - name: Publish
28
+ uses: pypa/gh-action-pypi-publish@release/v1
29
+ with:
30
+ user: __token__
31
+ password: {% raw %}${{ secrets.PYPI_TOKEN }}{% endraw %}
32
+ {% endif %}
@@ -0,0 +1,28 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["{{ release.tag_prefix }}*"]
6
+
7
+ permissions:
8
+ contents: write
9
+
10
+ jobs:
11
+ release:
12
+ runs-on: {{ ci.runner }}
13
+ steps:
14
+ - uses: actions/checkout@v6
15
+ with:
16
+ fetch-depth: 0
17
+ - uses: actions/setup-python@v6
18
+ with:
19
+ python-version: "{{ ci.python_versions[0] }}"
20
+ - name: Build
21
+ run: |
22
+ pip install build
23
+ python -m build
24
+ - name: Create Release
25
+ uses: softprops/action-gh-release@v2
26
+ with:
27
+ generate_release_notes: true
28
+ files: dist/*