instant-python 0.20.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 (202) hide show
  1. instant_python/__init__.py +1 -0
  2. instant_python/cli/__init__.py +0 -0
  3. instant_python/cli/cli.py +58 -0
  4. instant_python/cli/instant_python_typer.py +35 -0
  5. instant_python/config/__init__.py +0 -0
  6. instant_python/config/application/__init__.py +0 -0
  7. instant_python/config/application/config_generator.py +23 -0
  8. instant_python/config/delivery/__init__.py +0 -0
  9. instant_python/config/delivery/cli.py +19 -0
  10. instant_python/config/domain/__init__.py +0 -0
  11. instant_python/config/domain/question_wizard.py +7 -0
  12. instant_python/config/infra/__init__.py +0 -0
  13. instant_python/config/infra/question_wizard/__init__.py +0 -0
  14. instant_python/config/infra/question_wizard/questionary_console_wizard.py +25 -0
  15. instant_python/config/infra/question_wizard/step/__init__.py +0 -0
  16. instant_python/config/infra/question_wizard/step/dependencies_step.py +64 -0
  17. instant_python/config/infra/question_wizard/step/general_step.py +81 -0
  18. instant_python/config/infra/question_wizard/step/git_step.py +41 -0
  19. instant_python/config/infra/question_wizard/step/questionary.py +17 -0
  20. instant_python/config/infra/question_wizard/step/steps.py +21 -0
  21. instant_python/config/infra/question_wizard/step/template_step.py +63 -0
  22. instant_python/initialize/__init__.py +0 -0
  23. instant_python/initialize/application/__init__.py +0 -0
  24. instant_python/initialize/application/project_initializer.py +47 -0
  25. instant_python/initialize/delivery/__init__.py +0 -0
  26. instant_python/initialize/delivery/cli.py +48 -0
  27. instant_python/initialize/domain/__init__.py +0 -0
  28. instant_python/initialize/domain/env_manager.py +9 -0
  29. instant_python/initialize/domain/node.py +73 -0
  30. instant_python/initialize/domain/project_formatter.py +7 -0
  31. instant_python/initialize/domain/project_renderer.py +10 -0
  32. instant_python/initialize/domain/project_structure.py +77 -0
  33. instant_python/initialize/domain/project_writer.py +20 -0
  34. instant_python/initialize/domain/version_control_configurer.py +9 -0
  35. instant_python/initialize/infra/__init__.py +0 -0
  36. instant_python/initialize/infra/env_manager/__init__.py +0 -0
  37. instant_python/initialize/infra/env_manager/env_manager_factory.py +27 -0
  38. instant_python/initialize/infra/env_manager/pdm_env_manager.py +66 -0
  39. instant_python/initialize/infra/env_manager/system_console.py +65 -0
  40. instant_python/initialize/infra/env_manager/uv_env_manager.py +74 -0
  41. instant_python/initialize/infra/formatter/__init__.py +0 -0
  42. instant_python/initialize/infra/formatter/ruff_project_formatter.py +10 -0
  43. instant_python/initialize/infra/renderer/__init__.py +0 -0
  44. instant_python/initialize/infra/renderer/jinja_environment.py +71 -0
  45. instant_python/initialize/infra/renderer/jinja_project_renderer.py +57 -0
  46. instant_python/initialize/infra/version_control/__init__.py +0 -0
  47. instant_python/initialize/infra/version_control/git_configurer.py +29 -0
  48. instant_python/initialize/infra/writer/__init__.py +0 -0
  49. instant_python/initialize/infra/writer/file_system_project_writer.py +23 -0
  50. instant_python/shared/__init__.py +0 -0
  51. instant_python/shared/application_error.py +8 -0
  52. instant_python/shared/domain/__init__.py +0 -0
  53. instant_python/shared/domain/config_repository.py +18 -0
  54. instant_python/shared/domain/config_schema.py +113 -0
  55. instant_python/shared/domain/dependency_config.py +41 -0
  56. instant_python/shared/domain/general_config.py +71 -0
  57. instant_python/shared/domain/git_config.py +32 -0
  58. instant_python/shared/domain/template_config.py +76 -0
  59. instant_python/shared/infra/__init__.py +0 -0
  60. instant_python/shared/infra/persistence/__init__.py +0 -0
  61. instant_python/shared/infra/persistence/yaml_config_repository.py +39 -0
  62. instant_python/shared/supported_built_in_features.py +20 -0
  63. instant_python/shared/supported_licenses.py +11 -0
  64. instant_python/shared/supported_managers.py +10 -0
  65. instant_python/shared/supported_python_versions.py +12 -0
  66. instant_python/shared/supported_templates.py +12 -0
  67. instant_python/templates/boilerplate/.gitignore +164 -0
  68. instant_python/templates/boilerplate/.pre-commit-config.yml +73 -0
  69. instant_python/templates/boilerplate/.python-version +1 -0
  70. instant_python/templates/boilerplate/CITATION.cff +13 -0
  71. instant_python/templates/boilerplate/LICENSE +896 -0
  72. instant_python/templates/boilerplate/README.md +8 -0
  73. instant_python/templates/boilerplate/SECURITY.md +43 -0
  74. instant_python/templates/boilerplate/event_bus/__init__.py +0 -0
  75. instant_python/templates/boilerplate/event_bus/domain_event.py +15 -0
  76. instant_python/templates/boilerplate/event_bus/domain_event_json_deserializer.py +25 -0
  77. instant_python/templates/boilerplate/event_bus/domain_event_json_serializer.py +16 -0
  78. instant_python/templates/boilerplate/event_bus/domain_event_subscriber.py +33 -0
  79. instant_python/templates/boilerplate/event_bus/event_aggregate.py +19 -0
  80. instant_python/templates/boilerplate/event_bus/event_bus.py +9 -0
  81. instant_python/templates/boilerplate/event_bus/exchange_type.py +14 -0
  82. instant_python/templates/boilerplate/event_bus/mock_event_bus.py +16 -0
  83. instant_python/templates/boilerplate/event_bus/rabbit_mq_configurer.py +45 -0
  84. instant_python/templates/boilerplate/event_bus/rabbit_mq_connection.py +71 -0
  85. instant_python/templates/boilerplate/event_bus/rabbit_mq_consumer.py +56 -0
  86. instant_python/templates/boilerplate/event_bus/rabbit_mq_event_bus.py +26 -0
  87. instant_python/templates/boilerplate/event_bus/rabbit_mq_queue_formatter.py +21 -0
  88. instant_python/templates/boilerplate/event_bus/rabbit_mq_settings.py +8 -0
  89. instant_python/templates/boilerplate/exceptions/__init__.py +0 -0
  90. instant_python/templates/boilerplate/exceptions/base_error.py +13 -0
  91. instant_python/templates/boilerplate/exceptions/domain_error.py +6 -0
  92. instant_python/templates/boilerplate/exceptions/domain_event_type_not_found_error.py +6 -0
  93. instant_python/templates/boilerplate/exceptions/rabbit_mq_connection_not_established_error.py +7 -0
  94. instant_python/templates/boilerplate/exceptions/required_value_error.py +6 -0
  95. instant_python/templates/boilerplate/fastapi/__init__.py +0 -0
  96. instant_python/templates/boilerplate/fastapi/application.py +74 -0
  97. instant_python/templates/boilerplate/fastapi/error_handlers.py +88 -0
  98. instant_python/templates/boilerplate/fastapi/error_response.py +31 -0
  99. instant_python/templates/boilerplate/fastapi/fastapi_log_middleware.py +32 -0
  100. instant_python/templates/boilerplate/fastapi/lifespan.py +13 -0
  101. instant_python/templates/boilerplate/fastapi/success_response.py +13 -0
  102. instant_python/templates/boilerplate/github/action.yml +35 -0
  103. instant_python/templates/boilerplate/github/bug_report.yml +60 -0
  104. instant_python/templates/boilerplate/github/ci.yml +199 -0
  105. instant_python/templates/boilerplate/github/feature_request.yml +21 -0
  106. instant_python/templates/boilerplate/github/release.yml +94 -0
  107. instant_python/templates/boilerplate/logger/__init__.py +0 -0
  108. instant_python/templates/boilerplate/logger/file_logger.py +55 -0
  109. instant_python/templates/boilerplate/logger/file_rotating_handler.py +36 -0
  110. instant_python/templates/boilerplate/logger/json_formatter.py +16 -0
  111. instant_python/templates/boilerplate/mypy.ini +41 -0
  112. instant_python/templates/boilerplate/persistence/__init__.py +0 -0
  113. instant_python/templates/boilerplate/persistence/alembic_migrator.py +19 -0
  114. instant_python/templates/boilerplate/persistence/async/README.md +1 -0
  115. instant_python/templates/boilerplate/persistence/async/__init__.py +0 -0
  116. instant_python/templates/boilerplate/persistence/async/alembic.ini +124 -0
  117. instant_python/templates/boilerplate/persistence/async/async_engine_fixture.py +20 -0
  118. instant_python/templates/boilerplate/persistence/async/async_session.py +20 -0
  119. instant_python/templates/boilerplate/persistence/async/env.py +94 -0
  120. instant_python/templates/boilerplate/persistence/async/models_metadata.py +10 -0
  121. instant_python/templates/boilerplate/persistence/async/postgres_settings.py +15 -0
  122. instant_python/templates/boilerplate/persistence/async/script.py.mako +26 -0
  123. instant_python/templates/boilerplate/persistence/async/sqlalchemy_repository.py +28 -0
  124. instant_python/templates/boilerplate/persistence/base.py +4 -0
  125. instant_python/templates/boilerplate/persistence/synchronous/__init__.py +0 -0
  126. instant_python/templates/boilerplate/persistence/synchronous/session_maker.py +21 -0
  127. instant_python/templates/boilerplate/persistence/synchronous/sqlalchemy_repository.py +40 -0
  128. instant_python/templates/boilerplate/pyproject.toml +134 -0
  129. instant_python/templates/boilerplate/pytest.ini +10 -0
  130. instant_python/templates/boilerplate/scripts/add_dependency.py +45 -0
  131. instant_python/templates/boilerplate/scripts/create_aggregate.py +33 -0
  132. instant_python/templates/boilerplate/scripts/insert_template.py +90 -0
  133. instant_python/templates/boilerplate/scripts/integration.sh +39 -0
  134. instant_python/templates/boilerplate/scripts/local_setup.py +12 -0
  135. instant_python/templates/boilerplate/scripts/makefile +184 -0
  136. instant_python/templates/boilerplate/scripts/post-merge.py +40 -0
  137. instant_python/templates/boilerplate/scripts/pre-commit.py +15 -0
  138. instant_python/templates/boilerplate/scripts/pre-push.py +6 -0
  139. instant_python/templates/boilerplate/scripts/remove_dependency.py +40 -0
  140. instant_python/templates/boilerplate/scripts/unit.sh +40 -0
  141. instant_python/templates/project_structure/clean_architecture/layers/application.yml +3 -0
  142. instant_python/templates/project_structure/clean_architecture/layers/delivery.yml +8 -0
  143. instant_python/templates/project_structure/clean_architecture/layers/domain.yml +10 -0
  144. instant_python/templates/project_structure/clean_architecture/layers/infra.yml +12 -0
  145. instant_python/templates/project_structure/clean_architecture/layers/test_application.yml +3 -0
  146. instant_python/templates/project_structure/clean_architecture/layers/test_delivery.yml +3 -0
  147. instant_python/templates/project_structure/clean_architecture/layers/test_domain.yml +3 -0
  148. instant_python/templates/project_structure/clean_architecture/layers/test_infra.yml +9 -0
  149. instant_python/templates/project_structure/clean_architecture/main_structure.yml +38 -0
  150. instant_python/templates/project_structure/clean_architecture/source.yml +9 -0
  151. instant_python/templates/project_structure/clean_architecture/test.yml +9 -0
  152. instant_python/templates/project_structure/config_files/gitignore.yml +3 -0
  153. instant_python/templates/project_structure/config_files/mypy.yml +4 -0
  154. instant_python/templates/project_structure/config_files/pyproject.yml +4 -0
  155. instant_python/templates/project_structure/config_files/pytest.yml +4 -0
  156. instant_python/templates/project_structure/config_files/python_version.yml +3 -0
  157. instant_python/templates/project_structure/documentation/citation.yml +4 -0
  158. instant_python/templates/project_structure/documentation/license.yml +3 -0
  159. instant_python/templates/project_structure/documentation/readme.yml +4 -0
  160. instant_python/templates/project_structure/documentation/security.yml +4 -0
  161. instant_python/templates/project_structure/domain_driven_design/layers/bounded_context.yml +17 -0
  162. instant_python/templates/project_structure/domain_driven_design/layers/delivery.yml +8 -0
  163. instant_python/templates/project_structure/domain_driven_design/layers/shared.yml +18 -0
  164. instant_python/templates/project_structure/domain_driven_design/layers/shared_domain.yml +10 -0
  165. instant_python/templates/project_structure/domain_driven_design/layers/shared_infra.yml +12 -0
  166. instant_python/templates/project_structure/domain_driven_design/layers/test_shared.yml +8 -0
  167. instant_python/templates/project_structure/domain_driven_design/layers/test_shared_delivery.yml +3 -0
  168. instant_python/templates/project_structure/domain_driven_design/layers/test_shared_domain.yml +3 -0
  169. instant_python/templates/project_structure/domain_driven_design/layers/test_shared_infra.yml +9 -0
  170. instant_python/templates/project_structure/domain_driven_design/main_structure.yml +38 -0
  171. instant_python/templates/project_structure/domain_driven_design/source.yml +10 -0
  172. instant_python/templates/project_structure/domain_driven_design/test.yml +9 -0
  173. instant_python/templates/project_structure/errors.yml +12 -0
  174. instant_python/templates/project_structure/events/event_bus_domain.yml +48 -0
  175. instant_python/templates/project_structure/events/event_bus_infra.yml +40 -0
  176. instant_python/templates/project_structure/events/mock_event_bus.yml +4 -0
  177. instant_python/templates/project_structure/fastapi/fastapi_app.yml +32 -0
  178. instant_python/templates/project_structure/fastapi/fastapi_domain.yml +12 -0
  179. instant_python/templates/project_structure/fastapi/fastapi_infra.yml +12 -0
  180. instant_python/templates/project_structure/github/github_action.yml +24 -0
  181. instant_python/templates/project_structure/github/github_issues_template.yml +14 -0
  182. instant_python/templates/project_structure/github/makefile.yml +3 -0
  183. instant_python/templates/project_structure/github/precommit_hook.yml +4 -0
  184. instant_python/templates/project_structure/logger.yml +16 -0
  185. instant_python/templates/project_structure/macros.j2 +73 -0
  186. instant_python/templates/project_structure/persistence/alembic_migrator.yml +6 -0
  187. instant_python/templates/project_structure/persistence/async_alembic.yml +27 -0
  188. instant_python/templates/project_structure/persistence/async_engine_conftest.yml +4 -0
  189. instant_python/templates/project_structure/persistence/async_sqlalchemy.yml +14 -0
  190. instant_python/templates/project_structure/persistence/persistence.yml +8 -0
  191. instant_python/templates/project_structure/persistence/synchronous_sqlalchemy.yml +20 -0
  192. instant_python/templates/project_structure/standard_project/layers/source_features.yml +13 -0
  193. instant_python/templates/project_structure/standard_project/layers/test_event_bus.yml +6 -0
  194. instant_python/templates/project_structure/standard_project/layers/test_features.yml +6 -0
  195. instant_python/templates/project_structure/standard_project/main_structure.yml +38 -0
  196. instant_python/templates/project_structure/standard_project/source.yml +5 -0
  197. instant_python/templates/project_structure/standard_project/test.yml +5 -0
  198. instant_python-0.20.0.dist-info/METADATA +318 -0
  199. instant_python-0.20.0.dist-info/RECORD +202 -0
  200. instant_python-0.20.0.dist-info/WHEEL +4 -0
  201. instant_python-0.20.0.dist-info/entry_points.txt +2 -0
  202. instant_python-0.20.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,31 @@
1
+ from abc import ABC
2
+
3
+ from fastapi import status
4
+ from fastapi.responses import JSONResponse
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class ErrorResponse(ABC, BaseModel):
9
+ status_code: int
10
+ detail: str
11
+
12
+ def as_json(self) -> JSONResponse:
13
+ return JSONResponse(
14
+ content={"detail": self.detail},
15
+ status_code=self.status_code,
16
+ )
17
+
18
+
19
+ class UnprocessableEntityError(ErrorResponse):
20
+ status_code: int = Field(default=status.HTTP_422_UNPROCESSABLE_ENTITY)
21
+ detail: str = Field(default="Unprocessable Entity")
22
+
23
+
24
+ class ResourceNotFoundError(ErrorResponse):
25
+ status_code: int = Field(default=status.HTTP_404_NOT_FOUND)
26
+ detail: str = Field(default="Not Found")
27
+
28
+
29
+ class InternalServerError(ErrorResponse):
30
+ status_code: int = Field(default=status.HTTP_500_INTERNAL_SERVER_ERROR)
31
+ detail: str = Field(default="An unexpected error occurred.")
@@ -0,0 +1,32 @@
1
+ import time
2
+
3
+ from fastapi import Request, Response, FastAPI
4
+ from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
5
+
6
+ from {{ general.source_name }}{{ "shared.infra.logger.file_logger" | resolve_import_path(template.name) }} import FileLogger
7
+
8
+
9
+ class FastapiLogMiddleware(BaseHTTPMiddleware):
10
+ def __init__(self, app: FastAPI, logger: FileLogger) -> None:
11
+ super().__init__(app)
12
+ self._logger = logger
13
+
14
+ async def dispatch(
15
+ self, request: Request, call_next: RequestResponseEndpoint
16
+ ) -> Response:
17
+ start_time = time.perf_counter()
18
+ response = await call_next(request)
19
+ process_time = time.perf_counter() - start_time
20
+
21
+ if response.status_code < 400:
22
+ self._logger.info(
23
+ message=f"success - {request.url.path}",
24
+ details={
25
+ "method": request.method,
26
+ "source": request.url.path,
27
+ "process_time": process_time,
28
+ "status_code": response.status_code,
29
+ },
30
+ )
31
+
32
+ return response
@@ -0,0 +1,13 @@
1
+ from collections.abc import AsyncGenerator
2
+ from contextlib import asynccontextmanager
3
+
4
+ from fastapi import FastAPI
5
+
6
+ from {{ general.source_name }}{{ "shared.infra.alembic_migrator" | resolve_import_path(template.name) }} import AlembicMigrator
7
+
8
+
9
+ @asynccontextmanager
10
+ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
11
+ migrator = AlembicMigrator()
12
+ await migrator.migrate()
13
+ yield
@@ -0,0 +1,13 @@
1
+ from pydantic import BaseModel
2
+ from fastapi.responses import JSONResponse
3
+
4
+
5
+ class SuccessResponse(BaseModel):
6
+ status_code: int
7
+ data: dict
8
+
9
+ def as_json(self) -> JSONResponse:
10
+ return JSONResponse(
11
+ content=self.data,
12
+ status_code=self.status_code,
13
+ )
@@ -0,0 +1,35 @@
1
+ name: Install Python and setup environment
2
+
3
+ inputs:
4
+ python-version:
5
+ description: 'The version of Python to use'
6
+ required: false
7
+ default: {{ general.python_version }}
8
+ outputs: {}
9
+ runs:
10
+ using: composite
11
+ steps:
12
+ - name: 🐍 Setup Python
13
+ uses: actions/setup-python@v5
14
+ with:
15
+ python-version: {% raw %}${{ inputs.python-version }}{% endraw %}
16
+
17
+ - name: 🔨 Install dependency manager
18
+ run: python -m pip install {{ general.dependency_manager }}
19
+ shell: bash
20
+
21
+ - name: 📦 Install dependencies
22
+ run: |
23
+ {% if general.dependency_manager == "uv" %}
24
+ uv sync --all-groups
25
+ {% elif general.dependency_manager == "pdm" %}
26
+ pdm install
27
+ {% endif %}
28
+ {% if "precommit_hook" in template.built_in_features %}
29
+ {% if general.dependency_manager == "uv" %}
30
+ uv run -m pre_commit install
31
+ {% elif general.dependency_manager == "pdm" %}
32
+ pdm run pre-commit install
33
+ {% endif %}
34
+ {% endif %}
35
+ shell: bash
@@ -0,0 +1,60 @@
1
+ name: 🐛 Bug Report
2
+ description: Create a report to help us improve.
3
+ labels:
4
+ - bug
5
+ - pending
6
+
7
+ body:
8
+ - type: textarea
9
+ id: description
10
+ validations:
11
+ required: true
12
+ attributes:
13
+ label: ✏️ Description
14
+ description: |
15
+ Please provide a clear and concise description of the bug you are experiencing.
16
+
17
+ Specify what is the expected behavior and what is actually happening. You can add
18
+ screenshots to help illustrate the issue.
19
+
20
+ Please provide as much detail as possible to make understanding and solving your problem as quick as possible. 🙏
21
+
22
+ - type: textarea
23
+ id: reproduce
24
+ attributes:
25
+ label: ✅ Steps To Reproduce
26
+ render: Python
27
+ description: >
28
+ Please list the steps needed to reproduce the bug you are experiencing.
29
+
30
+ If applicable, please add a self-contained,
31
+ [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example)
32
+ demonstrating the bug.\
33
+
34
+ placeholder: >
35
+ # 1. Install the package using pip
36
+ # 2. Run the script with the following command:
37
+ # python script.py
38
+ # 3. Observe the output\
39
+
40
+ - type: dropdown
41
+ id: python-version
42
+ attributes:
43
+ label: 🐍 Which version of Python are you using?
44
+ options:
45
+ - 3.8
46
+ - 3.9
47
+ - 3.10
48
+ - 3.11
49
+ - 3.12
50
+ - 3.13
51
+ - other
52
+ validations:
53
+ required: true
54
+
55
+ - type: input
56
+ id: os
57
+ validations:
58
+ required: true
59
+ attributes:
60
+ label: 🖥️ Which operating system are you using?
@@ -0,0 +1,199 @@
1
+ name: Testing and Code analysis
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ lint:
14
+ name: lint
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: 🛡️ Harden runner
18
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
19
+ with:
20
+ egress-policy: audit
21
+
22
+ - name: 📥 Checkout the repository
23
+ uses: actions/checkout@v4
24
+ with:
25
+ ref: {% raw %}${{ github.head_ref }}{% endraw %}
26
+
27
+ fetch-depth: 0
28
+ persist-credentials: false
29
+
30
+ - name: 🛠️ Setup environment
31
+ uses: ./.github/actions/python_setup
32
+
33
+ - name: 🧐 Check linting
34
+ run: make check-lint
35
+
36
+ format:
37
+ name: format
38
+ runs-on: ubuntu-latest
39
+ steps:
40
+ - name: 🛡️ Harden runner
41
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
42
+ with:
43
+ egress-policy: audit
44
+
45
+ - name: 📥 Checkout the repository
46
+ uses: actions/checkout@v4
47
+ with:
48
+ ref: {% raw %}${{ github.head_ref }}{% endraw %}
49
+
50
+ fetch-depth: 0
51
+ persist-credentials: false
52
+
53
+ - name: 🛠️ Setup environment
54
+ uses: ./.github/actions/python_setup
55
+
56
+ - name: 🧐 Check code format
57
+ run: make check-format
58
+
59
+ typing:
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - name: 🛡️ Harden runner
63
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
64
+ with:
65
+ egress-policy: audit
66
+
67
+ - name: 📥 Checkout the repository
68
+ uses: actions/checkout@v4
69
+ with:
70
+ ref: {% raw %}${{ github.head_ref }}{% endraw %}
71
+
72
+ fetch-depth: 0
73
+ persist-credentials: false
74
+
75
+ - name: 🛠️ Setup environment
76
+ uses: ./.github/actions/python_setup
77
+
78
+ - name: 🧐 Check static types
79
+ run: make check-typing
80
+
81
+ analyze-code-quality:
82
+ name: analyze-code-quality
83
+ runs-on: ubuntu-latest
84
+ permissions:
85
+ contents: read
86
+ security-events: write
87
+
88
+ steps:
89
+ - name: 🛡️ Harden runner
90
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
91
+ with:
92
+ egress-policy: audit
93
+
94
+ - name: 📥 Checkout the repository
95
+ uses: actions/checkout@v4
96
+ with:
97
+ ref: {% raw %}${{ github.head_ref }}{% endraw %}
98
+
99
+ fetch-depth: 0
100
+ persist-credentials: false
101
+
102
+ - name: ▶️ CodeQL Initialization
103
+ uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
104
+ with:
105
+ languages: python
106
+ build-mode: none
107
+ queries: +security-extended,security-and-quality
108
+ # config-file: ./codeql-config.yml
109
+
110
+ - name: 🧐 CodeQL Analysis
111
+ uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
112
+ with:
113
+ category: '/language:python'
114
+
115
+ {% if "precommit_hooks" in template.built_in_features %}
116
+ secrets:
117
+ name: secrets-scan
118
+ runs-on: ubuntu-latest
119
+ permissions:
120
+ contents: read
121
+ security-events: write
122
+ pull-requests: write
123
+
124
+ steps:
125
+ - name: 🛡️ Harden runner
126
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
127
+ with:
128
+ egress-policy: audit
129
+
130
+ - name: 📥 Checkout the repository
131
+ uses: actions/checkout@v4
132
+ with:
133
+ fetch-depth: 0
134
+ persist-credentials: false
135
+
136
+ - name: 🛠️ Setup environment
137
+ uses: ./.github/actions/python_setup
138
+
139
+ - name: 🏃 Run secrets scanner
140
+ run: make secrets
141
+ {% endif %}
142
+
143
+ audit:
144
+ name: audit-dependencies
145
+ runs-on: ubuntu-latest
146
+ permissions:
147
+ contents: read
148
+
149
+ steps:
150
+ - name: 🛡️ Harden runner
151
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
152
+ with:
153
+ egress-policy: audit
154
+
155
+ - name: 📥 Checkout the repository
156
+ uses: actions/checkout@v4
157
+ with:
158
+ fetch-depth: 0
159
+ persist-credentials: false
160
+
161
+ - name: 🛠️ Setup environment
162
+ uses: ./.github/actions/python_setup
163
+
164
+ - name: 🏃 Run audit
165
+ run: make audit
166
+
167
+ test:
168
+ name: test
169
+ runs-on: ubuntu-latest
170
+ steps:
171
+ - name: 🛡️ Harden runner
172
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
173
+ with:
174
+ egress-policy: audit
175
+
176
+ - name: 📥 Checkout the repository
177
+ uses: actions/checkout@v4
178
+ with:
179
+ fetch-depth: 0
180
+ persist-credentials: false
181
+
182
+ - name: 🛠️ Setup environment
183
+ uses: ./.github/actions/python_setup
184
+
185
+ - name: 📦 Install test dependencies
186
+ run: {{ general.dependency_manager }} add pytest pytest-cov
187
+
188
+ - name: 🏃 Run tests
189
+ run: {{ general.dependency_manager }} run pytest --cov --cov-report=xml --cov-branch test -ra -s
190
+
191
+ - name: 📥 Upload coverage report to Codecov
192
+ uses: codecov/codecov-action@v5
193
+ with:
194
+ files: coverage.xml
195
+ flags: unittests
196
+ name: codecov-coverage
197
+ token: {% raw %}${{ secrets.CODECOV_TOKEN }}{% endraw %}
198
+
199
+ slug: {{ git.username }}/{{ general.slug }}
@@ -0,0 +1,21 @@
1
+ name: 🚀 Feature Request
2
+ description: Suggest a new idea for {{ general.slug }}.
3
+ labels:
4
+ - enhancement
5
+ - pending
6
+
7
+ body:
8
+ - type: textarea
9
+ id: description
10
+ validations:
11
+ required: true
12
+ attributes:
13
+ label: ✏️ Description
14
+ description: |
15
+ Please give as much detail as possible about the feature you would like to suggest.
16
+
17
+ You might like to add:
18
+ * A clear and concise description of the idea that should be implemented
19
+ * An example of how the feature should work
20
+ * Any relevant links or resources that can help understand the feature better
21
+ * A list of scenarios or validations that the feature should cover as well as any restrictions or limitations that should be considered
@@ -0,0 +1,94 @@
1
+ # To be able to use this GitHub workflow, you need to follow these steps:
2
+ # Prerequisites:
3
+ # 1. Generate a SSH key pair for signing releases: ssh-keygen -t ed25519 -C "<your-email>" -f ~/.ssh/github_actions
4
+ # Configuration:
5
+ # 1. In your GitHub repository, go to Settings > Secrets and variables > Actions.
6
+ # 2. In the _Secrets_ tab, create a new repository secret named `SSH_PRIVATE_SIGNING_KEY` and paste the contents of your private key file (e.g., `cat ~/.ssh/github_actions`).
7
+ # 3. In the _Variables_ tab, create the following repository variables:
8
+ # - `GIT_COMMITTER_EMAIL`: The email address you want to use for committing changes (e.g., `github-actions[bot]@users.noreply.github.com`).
9
+ # - `GIT_COMMITTER_NAME`: The name you want to use for committing changes (e.g., `github-actions[bot]`).
10
+ # - `SSH_PUBLIC_SIGNING_KEY`: The contents of your public key file (e.g., `cat ~/.ssh/github_actions.pub`).
11
+ name: Release and Publish
12
+
13
+ on:
14
+ workflow_dispatch:
15
+
16
+ jobs:
17
+ release:
18
+ if: "!startsWith(github.event.head_commit.message, 'bump:')"
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ contents: write
22
+ outputs:
23
+ released: {% raw %}${{ steps.released.outputs.released }}{% endraw %}
24
+
25
+ tag: {% raw %}${{ steps.released.outputs.tag }}{% endraw %}
26
+
27
+
28
+ steps:
29
+ - name: 🛡️ Harden runner
30
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
31
+ with:
32
+ egress-policy: audit
33
+
34
+ - name: 📥 Checkout the repository
35
+ uses: actions/checkout@v4
36
+ with:
37
+ fetch-depth: 0
38
+ persist-credentials: false
39
+
40
+ - name: 🔖 Create release
41
+ id: released
42
+ uses: python-semantic-release/python-semantic-release@2896129e02bb7809d2cf0c1b8e9e795ee27acbcf # v10.0.2
43
+ with:
44
+ build: true
45
+ push: true
46
+ changelog: true
47
+ commit: true
48
+ tag: true
49
+ vcs_release: true
50
+ config_file: pyproject.toml
51
+ github_token: {% raw %}${{ secrets.GITHUB_TOKEN }}{% endraw %}
52
+
53
+ git_committer_email: {% raw %}${{ vars.GIT_COMMITTER_EMAIL }}{% endraw %}
54
+
55
+ git_committer_name: {% raw %}${{ vars.GIT_COMMITTER_NAME }}{% endraw %}
56
+
57
+ ssh_public_signing_key: {% raw %}${{ vars.SSH_PUBLIC_SIGNING_KEY }}{% endraw %}
58
+
59
+ ssh_private_signing_key: {% raw %}${{ secrets.SSH_PRIVATE_SIGNING_KEY }}{% endraw %}
60
+
61
+
62
+ build-and-publish:
63
+ name: Publish to PyPI
64
+ needs:
65
+ - release
66
+ if: {% raw %}needs.release.outputs.released == 'true'{% endraw %}
67
+
68
+ runs-on: ubuntu-latest
69
+ environment: release
70
+ permissions:
71
+ id-token: write
72
+
73
+ steps:
74
+ - name: 🛡️ Harden runner
75
+ uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
76
+ with:
77
+ egress-policy: audit
78
+
79
+ - name: 📥 Checkout the repository
80
+ uses: actions/checkout@v4
81
+ with:
82
+ fetch-depth: 0
83
+ persist-credentials: false
84
+ ref: refs/tags/{% raw %}${{ needs.release.outputs.tag }}{% endraw %}
85
+
86
+
87
+ - name: 🛠️ Setup environment
88
+ uses: ./.github/actions/python_setup
89
+
90
+ - name: 🏃 Build package
91
+ run: {{ general.dependency_manager }} build
92
+
93
+ - name: 🚀 Publish
94
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,55 @@
1
+ import logging
2
+
3
+ from {{ general.source_name }}{{ "shared.infra.logger.file_rotating_handler" | resolve_import_path(template.name) }} import TimeRotatingFileHandler
4
+
5
+
6
+ class FileLogger:
7
+ def __init__(self, name: str, handlers: list[logging.Handler]) -> None:
8
+ self._logger = logging.getLogger(name)
9
+ self._logger.setLevel(logging.DEBUG)
10
+
11
+ if not self._logger.hasHandlers():
12
+ self._logger.handlers.extend(handlers)
13
+
14
+ def debug(self, message: str, details: dict) -> None:
15
+ self._logger.debug(
16
+ msg=message,
17
+ extra={"details": details},
18
+ )
19
+
20
+ def info(self, message: str, details: dict) -> None:
21
+ self._logger.info(
22
+ msg=message,
23
+ extra={"details": details},
24
+ )
25
+
26
+ def warning(self, message: str, details: dict) -> None:
27
+ raise NotImplementedError
28
+
29
+ def error(self, message: str, details: dict) -> None:
30
+ self._logger.error(
31
+ msg=message,
32
+ extra={"details": details},
33
+ )
34
+
35
+ def critical(self, message: str, details: dict) -> None:
36
+ self._logger.critical(
37
+ msg=message,
38
+ extra={"details": details},
39
+ )
40
+
41
+
42
+ def create_file_logger(name: str) -> FileLogger:
43
+ return FileLogger(
44
+ name=name,
45
+ handlers=[
46
+ TimeRotatingFileHandler.create(
47
+ file_name="production",
48
+ level_to_record=logging.ERROR,
49
+ ),
50
+ TimeRotatingFileHandler.create(
51
+ file_name="dev",
52
+ level_to_record=logging.DEBUG,
53
+ ),
54
+ ],
55
+ )
@@ -0,0 +1,36 @@
1
+ import logging
2
+ from collections.abc import Sequence
3
+ from datetime import date
4
+ from logging.handlers import TimedRotatingFileHandler
5
+ from pathlib import Path
6
+ from typing import Self
7
+
8
+ from {{ general.source_name }}{{ "shared.infra.logger.json_formatter" | resolve_import_path(template.name) }} import JSONFormatter
9
+
10
+
11
+ class TimeRotatingFileHandler(logging.Handler):
12
+ @classmethod
13
+ def create(cls, file_name: str, level_to_record: int) -> Self:
14
+ root_project_path = cls.find_project_root(markers=["pyproject.toml"])
15
+ log_folder = root_project_path / "logs"
16
+ log_folder.mkdir(parents=True, exist_ok=True)
17
+
18
+ handler = TimedRotatingFileHandler(
19
+ filename=f"{log_folder}/{file_name}_{date.today().isoformat()}.log",
20
+ when="midnight",
21
+ interval=1,
22
+ backupCount=7,
23
+ encoding="utf-8",
24
+ )
25
+ handler.setFormatter(JSONFormatter())
26
+ handler.setLevel(level_to_record)
27
+
28
+ return handler
29
+
30
+ @classmethod
31
+ def find_project_root(cls, markers: Sequence[str]) -> Path:
32
+ start = Path(__file__).resolve()
33
+ for parent in (start, *start.parents):
34
+ if any((parent / marker).exists() for marker in markers):
35
+ return parent
36
+ raise FileNotFoundError(f"Could not find project root (markers: {markers}).")
@@ -0,0 +1,16 @@
1
+ import json
2
+ import logging
3
+
4
+
5
+ class JSONFormatter(logging.Formatter):
6
+ def format(self, record: logging.LogRecord) -> str:
7
+ log_record = {
8
+ "time": self.formatTime(record, self.datefmt),
9
+ "level": record.levelname,
10
+ "name": record.name,
11
+ "message": record.getMessage(),
12
+ }
13
+ if hasattr(record, "details"):
14
+ log_record["details"] = record.details
15
+
16
+ return json.dumps(log_record)
@@ -0,0 +1,41 @@
1
+ [mypy]
2
+ files = src, tests
3
+ python_version = {{ general.python_version }}
4
+ mypy_path = .
5
+ disable_error_code = override,attr-defined
6
+ check_untyped_defs = true
7
+ disallow_any_explicit = false
8
+
9
+ # None and Optional handling
10
+ no_implicit_optional = true
11
+
12
+ # Configuring warnings
13
+ warn_redundant_casts = true
14
+ warn_unused_ignores = false
15
+ warn_no_return = true
16
+ warn_return_any = true
17
+ warn_unreachable = true
18
+
19
+ # Miscellaneous strictness flags
20
+ implicit_reexport = true
21
+ strict_equality = true
22
+
23
+ # Configuring error messages
24
+ show_error_context = true
25
+ show_column_numbers = true
26
+ show_error_codes = true
27
+ pretty = true
28
+ show_absolute_path = false
29
+
30
+ disallow_untyped_defs = true
31
+
32
+ [mypy-expects.*]
33
+ ignore_missing_imports = True
34
+ [mypy-doublex.*]
35
+ ignore_missing_imports = True
36
+ [mypy-src.*]
37
+ ignore_missing_imports = True
38
+ [mypy-tests.*]
39
+ disallow_untyped_defs = False
40
+ [mypy-doublex_expects.*]
41
+ ignore_missing_imports = True
@@ -0,0 +1,19 @@
1
+ import asyncio
2
+
3
+ from {{ general.source_name }}{{ "shared.infra.persistence.postgres_settings" | resolve_import_path(template.name) }} import PostgresSettings
4
+ from alembic import command
5
+ from alembic.config import Config
6
+
7
+
8
+ class AlembicMigrator:
9
+ def __init__(self) -> None:
10
+ self._settings = PostgresSettings() # type: ignore
11
+ self._alembic_config = Config()
12
+
13
+ async def migrate(self) -> None:
14
+ self._alembic_config.set_main_option(
15
+ "sqlalchemy.url", self._settings.postgres_url
16
+ )
17
+ self._alembic_config.set_main_option("script_location", "migrations")
18
+ loop = asyncio.get_event_loop()
19
+ await loop.run_in_executor(None, command.upgrade, self._alembic_config, "head") # type: ignore
@@ -0,0 +1 @@
1
+ Generic single-database configuration with an async dbapi.