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.
- instant_python/__init__.py +1 -0
- instant_python/cli/__init__.py +0 -0
- instant_python/cli/cli.py +58 -0
- instant_python/cli/instant_python_typer.py +35 -0
- instant_python/config/__init__.py +0 -0
- instant_python/config/application/__init__.py +0 -0
- instant_python/config/application/config_generator.py +23 -0
- instant_python/config/delivery/__init__.py +0 -0
- instant_python/config/delivery/cli.py +19 -0
- instant_python/config/domain/__init__.py +0 -0
- instant_python/config/domain/question_wizard.py +7 -0
- instant_python/config/infra/__init__.py +0 -0
- instant_python/config/infra/question_wizard/__init__.py +0 -0
- instant_python/config/infra/question_wizard/questionary_console_wizard.py +25 -0
- instant_python/config/infra/question_wizard/step/__init__.py +0 -0
- instant_python/config/infra/question_wizard/step/dependencies_step.py +64 -0
- instant_python/config/infra/question_wizard/step/general_step.py +81 -0
- instant_python/config/infra/question_wizard/step/git_step.py +41 -0
- instant_python/config/infra/question_wizard/step/questionary.py +17 -0
- instant_python/config/infra/question_wizard/step/steps.py +21 -0
- instant_python/config/infra/question_wizard/step/template_step.py +63 -0
- instant_python/initialize/__init__.py +0 -0
- instant_python/initialize/application/__init__.py +0 -0
- instant_python/initialize/application/project_initializer.py +47 -0
- instant_python/initialize/delivery/__init__.py +0 -0
- instant_python/initialize/delivery/cli.py +48 -0
- instant_python/initialize/domain/__init__.py +0 -0
- instant_python/initialize/domain/env_manager.py +9 -0
- instant_python/initialize/domain/node.py +73 -0
- instant_python/initialize/domain/project_formatter.py +7 -0
- instant_python/initialize/domain/project_renderer.py +10 -0
- instant_python/initialize/domain/project_structure.py +77 -0
- instant_python/initialize/domain/project_writer.py +20 -0
- instant_python/initialize/domain/version_control_configurer.py +9 -0
- instant_python/initialize/infra/__init__.py +0 -0
- instant_python/initialize/infra/env_manager/__init__.py +0 -0
- instant_python/initialize/infra/env_manager/env_manager_factory.py +27 -0
- instant_python/initialize/infra/env_manager/pdm_env_manager.py +66 -0
- instant_python/initialize/infra/env_manager/system_console.py +65 -0
- instant_python/initialize/infra/env_manager/uv_env_manager.py +74 -0
- instant_python/initialize/infra/formatter/__init__.py +0 -0
- instant_python/initialize/infra/formatter/ruff_project_formatter.py +10 -0
- instant_python/initialize/infra/renderer/__init__.py +0 -0
- instant_python/initialize/infra/renderer/jinja_environment.py +71 -0
- instant_python/initialize/infra/renderer/jinja_project_renderer.py +57 -0
- instant_python/initialize/infra/version_control/__init__.py +0 -0
- instant_python/initialize/infra/version_control/git_configurer.py +29 -0
- instant_python/initialize/infra/writer/__init__.py +0 -0
- instant_python/initialize/infra/writer/file_system_project_writer.py +23 -0
- instant_python/shared/__init__.py +0 -0
- instant_python/shared/application_error.py +8 -0
- instant_python/shared/domain/__init__.py +0 -0
- instant_python/shared/domain/config_repository.py +18 -0
- instant_python/shared/domain/config_schema.py +113 -0
- instant_python/shared/domain/dependency_config.py +41 -0
- instant_python/shared/domain/general_config.py +71 -0
- instant_python/shared/domain/git_config.py +32 -0
- instant_python/shared/domain/template_config.py +76 -0
- instant_python/shared/infra/__init__.py +0 -0
- instant_python/shared/infra/persistence/__init__.py +0 -0
- instant_python/shared/infra/persistence/yaml_config_repository.py +39 -0
- instant_python/shared/supported_built_in_features.py +20 -0
- instant_python/shared/supported_licenses.py +11 -0
- instant_python/shared/supported_managers.py +10 -0
- instant_python/shared/supported_python_versions.py +12 -0
- instant_python/shared/supported_templates.py +12 -0
- instant_python/templates/boilerplate/.gitignore +164 -0
- instant_python/templates/boilerplate/.pre-commit-config.yml +73 -0
- instant_python/templates/boilerplate/.python-version +1 -0
- instant_python/templates/boilerplate/CITATION.cff +13 -0
- instant_python/templates/boilerplate/LICENSE +896 -0
- instant_python/templates/boilerplate/README.md +8 -0
- instant_python/templates/boilerplate/SECURITY.md +43 -0
- instant_python/templates/boilerplate/event_bus/__init__.py +0 -0
- instant_python/templates/boilerplate/event_bus/domain_event.py +15 -0
- instant_python/templates/boilerplate/event_bus/domain_event_json_deserializer.py +25 -0
- instant_python/templates/boilerplate/event_bus/domain_event_json_serializer.py +16 -0
- instant_python/templates/boilerplate/event_bus/domain_event_subscriber.py +33 -0
- instant_python/templates/boilerplate/event_bus/event_aggregate.py +19 -0
- instant_python/templates/boilerplate/event_bus/event_bus.py +9 -0
- instant_python/templates/boilerplate/event_bus/exchange_type.py +14 -0
- instant_python/templates/boilerplate/event_bus/mock_event_bus.py +16 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_configurer.py +45 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_connection.py +71 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_consumer.py +56 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_event_bus.py +26 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_queue_formatter.py +21 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_settings.py +8 -0
- instant_python/templates/boilerplate/exceptions/__init__.py +0 -0
- instant_python/templates/boilerplate/exceptions/base_error.py +13 -0
- instant_python/templates/boilerplate/exceptions/domain_error.py +6 -0
- instant_python/templates/boilerplate/exceptions/domain_event_type_not_found_error.py +6 -0
- instant_python/templates/boilerplate/exceptions/rabbit_mq_connection_not_established_error.py +7 -0
- instant_python/templates/boilerplate/exceptions/required_value_error.py +6 -0
- instant_python/templates/boilerplate/fastapi/__init__.py +0 -0
- instant_python/templates/boilerplate/fastapi/application.py +74 -0
- instant_python/templates/boilerplate/fastapi/error_handlers.py +88 -0
- instant_python/templates/boilerplate/fastapi/error_response.py +31 -0
- instant_python/templates/boilerplate/fastapi/fastapi_log_middleware.py +32 -0
- instant_python/templates/boilerplate/fastapi/lifespan.py +13 -0
- instant_python/templates/boilerplate/fastapi/success_response.py +13 -0
- instant_python/templates/boilerplate/github/action.yml +35 -0
- instant_python/templates/boilerplate/github/bug_report.yml +60 -0
- instant_python/templates/boilerplate/github/ci.yml +199 -0
- instant_python/templates/boilerplate/github/feature_request.yml +21 -0
- instant_python/templates/boilerplate/github/release.yml +94 -0
- instant_python/templates/boilerplate/logger/__init__.py +0 -0
- instant_python/templates/boilerplate/logger/file_logger.py +55 -0
- instant_python/templates/boilerplate/logger/file_rotating_handler.py +36 -0
- instant_python/templates/boilerplate/logger/json_formatter.py +16 -0
- instant_python/templates/boilerplate/mypy.ini +41 -0
- instant_python/templates/boilerplate/persistence/__init__.py +0 -0
- instant_python/templates/boilerplate/persistence/alembic_migrator.py +19 -0
- instant_python/templates/boilerplate/persistence/async/README.md +1 -0
- instant_python/templates/boilerplate/persistence/async/__init__.py +0 -0
- instant_python/templates/boilerplate/persistence/async/alembic.ini +124 -0
- instant_python/templates/boilerplate/persistence/async/async_engine_fixture.py +20 -0
- instant_python/templates/boilerplate/persistence/async/async_session.py +20 -0
- instant_python/templates/boilerplate/persistence/async/env.py +94 -0
- instant_python/templates/boilerplate/persistence/async/models_metadata.py +10 -0
- instant_python/templates/boilerplate/persistence/async/postgres_settings.py +15 -0
- instant_python/templates/boilerplate/persistence/async/script.py.mako +26 -0
- instant_python/templates/boilerplate/persistence/async/sqlalchemy_repository.py +28 -0
- instant_python/templates/boilerplate/persistence/base.py +4 -0
- instant_python/templates/boilerplate/persistence/synchronous/__init__.py +0 -0
- instant_python/templates/boilerplate/persistence/synchronous/session_maker.py +21 -0
- instant_python/templates/boilerplate/persistence/synchronous/sqlalchemy_repository.py +40 -0
- instant_python/templates/boilerplate/pyproject.toml +134 -0
- instant_python/templates/boilerplate/pytest.ini +10 -0
- instant_python/templates/boilerplate/scripts/add_dependency.py +45 -0
- instant_python/templates/boilerplate/scripts/create_aggregate.py +33 -0
- instant_python/templates/boilerplate/scripts/insert_template.py +90 -0
- instant_python/templates/boilerplate/scripts/integration.sh +39 -0
- instant_python/templates/boilerplate/scripts/local_setup.py +12 -0
- instant_python/templates/boilerplate/scripts/makefile +184 -0
- instant_python/templates/boilerplate/scripts/post-merge.py +40 -0
- instant_python/templates/boilerplate/scripts/pre-commit.py +15 -0
- instant_python/templates/boilerplate/scripts/pre-push.py +6 -0
- instant_python/templates/boilerplate/scripts/remove_dependency.py +40 -0
- instant_python/templates/boilerplate/scripts/unit.sh +40 -0
- instant_python/templates/project_structure/clean_architecture/layers/application.yml +3 -0
- instant_python/templates/project_structure/clean_architecture/layers/delivery.yml +8 -0
- instant_python/templates/project_structure/clean_architecture/layers/domain.yml +10 -0
- instant_python/templates/project_structure/clean_architecture/layers/infra.yml +12 -0
- instant_python/templates/project_structure/clean_architecture/layers/test_application.yml +3 -0
- instant_python/templates/project_structure/clean_architecture/layers/test_delivery.yml +3 -0
- instant_python/templates/project_structure/clean_architecture/layers/test_domain.yml +3 -0
- instant_python/templates/project_structure/clean_architecture/layers/test_infra.yml +9 -0
- instant_python/templates/project_structure/clean_architecture/main_structure.yml +38 -0
- instant_python/templates/project_structure/clean_architecture/source.yml +9 -0
- instant_python/templates/project_structure/clean_architecture/test.yml +9 -0
- instant_python/templates/project_structure/config_files/gitignore.yml +3 -0
- instant_python/templates/project_structure/config_files/mypy.yml +4 -0
- instant_python/templates/project_structure/config_files/pyproject.yml +4 -0
- instant_python/templates/project_structure/config_files/pytest.yml +4 -0
- instant_python/templates/project_structure/config_files/python_version.yml +3 -0
- instant_python/templates/project_structure/documentation/citation.yml +4 -0
- instant_python/templates/project_structure/documentation/license.yml +3 -0
- instant_python/templates/project_structure/documentation/readme.yml +4 -0
- instant_python/templates/project_structure/documentation/security.yml +4 -0
- instant_python/templates/project_structure/domain_driven_design/layers/bounded_context.yml +17 -0
- instant_python/templates/project_structure/domain_driven_design/layers/delivery.yml +8 -0
- instant_python/templates/project_structure/domain_driven_design/layers/shared.yml +18 -0
- instant_python/templates/project_structure/domain_driven_design/layers/shared_domain.yml +10 -0
- instant_python/templates/project_structure/domain_driven_design/layers/shared_infra.yml +12 -0
- instant_python/templates/project_structure/domain_driven_design/layers/test_shared.yml +8 -0
- instant_python/templates/project_structure/domain_driven_design/layers/test_shared_delivery.yml +3 -0
- instant_python/templates/project_structure/domain_driven_design/layers/test_shared_domain.yml +3 -0
- instant_python/templates/project_structure/domain_driven_design/layers/test_shared_infra.yml +9 -0
- instant_python/templates/project_structure/domain_driven_design/main_structure.yml +38 -0
- instant_python/templates/project_structure/domain_driven_design/source.yml +10 -0
- instant_python/templates/project_structure/domain_driven_design/test.yml +9 -0
- instant_python/templates/project_structure/errors.yml +12 -0
- instant_python/templates/project_structure/events/event_bus_domain.yml +48 -0
- instant_python/templates/project_structure/events/event_bus_infra.yml +40 -0
- instant_python/templates/project_structure/events/mock_event_bus.yml +4 -0
- instant_python/templates/project_structure/fastapi/fastapi_app.yml +32 -0
- instant_python/templates/project_structure/fastapi/fastapi_domain.yml +12 -0
- instant_python/templates/project_structure/fastapi/fastapi_infra.yml +12 -0
- instant_python/templates/project_structure/github/github_action.yml +24 -0
- instant_python/templates/project_structure/github/github_issues_template.yml +14 -0
- instant_python/templates/project_structure/github/makefile.yml +3 -0
- instant_python/templates/project_structure/github/precommit_hook.yml +4 -0
- instant_python/templates/project_structure/logger.yml +16 -0
- instant_python/templates/project_structure/macros.j2 +73 -0
- instant_python/templates/project_structure/persistence/alembic_migrator.yml +6 -0
- instant_python/templates/project_structure/persistence/async_alembic.yml +27 -0
- instant_python/templates/project_structure/persistence/async_engine_conftest.yml +4 -0
- instant_python/templates/project_structure/persistence/async_sqlalchemy.yml +14 -0
- instant_python/templates/project_structure/persistence/persistence.yml +8 -0
- instant_python/templates/project_structure/persistence/synchronous_sqlalchemy.yml +20 -0
- instant_python/templates/project_structure/standard_project/layers/source_features.yml +13 -0
- instant_python/templates/project_structure/standard_project/layers/test_event_bus.yml +6 -0
- instant_python/templates/project_structure/standard_project/layers/test_features.yml +6 -0
- instant_python/templates/project_structure/standard_project/main_structure.yml +38 -0
- instant_python/templates/project_structure/standard_project/source.yml +5 -0
- instant_python/templates/project_structure/standard_project/test.yml +5 -0
- instant_python-0.20.0.dist-info/METADATA +318 -0
- instant_python-0.20.0.dist-info/RECORD +202 -0
- instant_python-0.20.0.dist-info/WHEEL +4 -0
- instant_python-0.20.0.dist-info/entry_points.txt +2 -0
- 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
|
|
File without changes
|
|
@@ -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
|
|
File without changes
|
|
@@ -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.
|
|
File without changes
|