instant-python 0.8.2__py3-none-any.whl → 0.9.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/shared/supported_built_in_features.py +4 -0
- instant_python/templates/boilerplate/.pre-commit-config.yml +56 -18
- instant_python/templates/boilerplate/CITATION.cff +13 -0
- instant_python/templates/boilerplate/SECURITY.md +43 -0
- instant_python/templates/boilerplate/event_bus/{aggregate_root.py → event_aggregate.py} +2 -1
- instant_python/templates/boilerplate/exceptions/domain_error.py +4 -20
- instant_python/templates/boilerplate/exceptions/error.py +24 -0
- instant_python/templates/boilerplate/fastapi/application.py +47 -10
- instant_python/templates/boilerplate/fastapi/error_handlers.py +64 -0
- instant_python/templates/boilerplate/fastapi/error_response.py +31 -0
- instant_python/templates/boilerplate/fastapi/fastapi_log_middleware.py +33 -0
- instant_python/templates/boilerplate/fastapi/success_response.py +13 -0
- instant_python/templates/boilerplate/github/action.yml +17 -4
- 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 +93 -0
- instant_python/templates/boilerplate/logger/file_logger.py +56 -0
- instant_python/templates/boilerplate/logger/file_rotating_handler.py +37 -0
- instant_python/templates/boilerplate/logger/json_formatter.py +2 -2
- instant_python/templates/boilerplate/pyproject.toml +47 -3
- instant_python/templates/boilerplate/scripts/add_dependency.py +45 -0
- instant_python/templates/boilerplate/scripts/local_setup.py +12 -0
- instant_python/templates/boilerplate/scripts/makefile +96 -37
- 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/remove_dependency.py +40 -0
- instant_python/templates/boilerplate/value_object/aggregate.py +99 -0
- instant_python/templates/boilerplate/value_object/int_value_object.py +16 -7
- instant_python/templates/boilerplate/value_object/string_value_object.py +10 -12
- instant_python/templates/boilerplate/value_object/uuid.py +19 -11
- instant_python/templates/boilerplate/value_object/validation.py +7 -0
- instant_python/templates/boilerplate/value_object/value_object.py +88 -17
- instant_python/templates/project_structure/citation.yml.j2 +3 -0
- instant_python/templates/project_structure/clean_architecture/main_structure.yml.j2 +17 -4
- instant_python/templates/project_structure/domain_driven_design/main_structure.yml.j2 +17 -4
- instant_python/templates/project_structure/event_bus_domain.yml.j2 +20 -4
- instant_python/templates/project_structure/fastapi_app.yml.j2 +16 -0
- instant_python/templates/project_structure/fastapi_domain.yml.j2 +4 -1
- instant_python/templates/project_structure/fastapi_infra.yml.j2 +2 -2
- instant_python/templates/project_structure/github_action.yml.j2 +2 -2
- instant_python/templates/project_structure/github_issues_template.yml.j2 +12 -0
- instant_python/templates/project_structure/logger.yml.j2 +4 -1
- instant_python/templates/project_structure/makefile.yml.j2 +7 -16
- instant_python/templates/project_structure/{pre_commit.yml.j2 → precommit_hook.yml.j2} +1 -1
- instant_python/templates/project_structure/security.yml.j2 +3 -0
- instant_python/templates/project_structure/standard_project/main_structure.yml.j2 +18 -5
- instant_python/templates/project_structure/value_objects.yml.j2 +26 -9
- {instant_python-0.8.2.dist-info → instant_python-0.9.0.dist-info}/METADATA +1 -1
- {instant_python-0.8.2.dist-info → instant_python-0.9.0.dist-info}/RECORD +54 -41
- instant_python/templates/boilerplate/fastapi/http_response.py +0 -67
- instant_python/templates/boilerplate/fastapi/status_code.py +0 -9
- instant_python/templates/boilerplate/github/lint.yml +0 -30
- instant_python/templates/boilerplate/github/test.yml +0 -30
- instant_python/templates/boilerplate/logger/logger.py +0 -48
- instant_python/templates/boilerplate/scripts/add_dependency.sh +0 -37
- instant_python/templates/boilerplate/scripts/local_setup.sh +0 -15
- instant_python/templates/boilerplate/scripts/post-merge +0 -11
- instant_python/templates/boilerplate/scripts/pre-commit +0 -4
- instant_python/templates/boilerplate/scripts/remove_dependency.sh +0 -36
- /instant_python/templates/boilerplate/scripts/{pre-push → pre-push.py} +0 -0
- {instant_python-0.8.2.dist-info → instant_python-0.9.0.dist-info}/WHEEL +0 -0
- {instant_python-0.8.2.dist-info → instant_python-0.9.0.dist-info}/entry_points.txt +0 -0
- {instant_python-0.8.2.dist-info → instant_python-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,12 +4,16 @@ from enum import Enum
|
|
|
4
4
|
class SupportedBuiltInFeatures(str, Enum):
|
|
5
5
|
VALUE_OBJECTS = "value_objects"
|
|
6
6
|
GITHUB_ACTIONS = "github_actions"
|
|
7
|
+
GITHUB_ISSUES_TEMPLATE = "github_issues_template"
|
|
7
8
|
MAKEFILE = "makefile"
|
|
8
9
|
LOGGER = "logger"
|
|
9
10
|
EVENT_BUS = "event_bus"
|
|
10
11
|
ASYNC_SQLALCHEMY = "async_sqlalchemy"
|
|
11
12
|
ASYNC_ALEMBIC = "async_alembic"
|
|
12
13
|
FASTAPI = "fastapi_application"
|
|
14
|
+
PRECOMMIT = "precommit_hook"
|
|
15
|
+
CITATION = "citation_file"
|
|
16
|
+
SECURITY = "security_file"
|
|
13
17
|
|
|
14
18
|
@classmethod
|
|
15
19
|
def get_supported_built_in_features(cls) -> list[str]:
|
|
@@ -1,33 +1,71 @@
|
|
|
1
|
+
fail_fast: true
|
|
2
|
+
|
|
3
|
+
default_language_version:
|
|
4
|
+
python: {{ general.python_version }}
|
|
5
|
+
|
|
1
6
|
repos:
|
|
2
7
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
3
|
-
rev: v5.0.0
|
|
8
|
+
rev: cef0300fd0fc4d2a87a85fa2093c6b283ea36f4b # v5.0.0
|
|
4
9
|
hooks:
|
|
5
10
|
- id: check-added-large-files
|
|
6
|
-
|
|
11
|
+
name: 📦 Large Files Checker
|
|
12
|
+
args:
|
|
13
|
+
- --maxkb=100
|
|
14
|
+
stages:
|
|
15
|
+
- pre-commit
|
|
16
|
+
|
|
7
17
|
- id: check-case-conflict
|
|
18
|
+
name: 🔠 Case Conflict Checker
|
|
19
|
+
stages:
|
|
20
|
+
- pre-commit
|
|
21
|
+
|
|
22
|
+
- id: check-toml
|
|
23
|
+
name: 📄 TOML Files Checker
|
|
24
|
+
files: '.*\.toml'
|
|
25
|
+
stages:
|
|
26
|
+
- pre-commit
|
|
27
|
+
|
|
28
|
+
- id: check-yaml
|
|
29
|
+
name: 📄 YAML Files Checker
|
|
30
|
+
files: '.*\.ya?ml'
|
|
31
|
+
args:
|
|
32
|
+
- --unsafe
|
|
33
|
+
stages:
|
|
34
|
+
- pre-commit
|
|
8
35
|
- id: check-merge-conflict
|
|
36
|
+
name: 🔀 Merge Conflict Checker
|
|
37
|
+
stages:
|
|
38
|
+
- pre-commit
|
|
39
|
+
|
|
40
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
41
|
+
rev: 782f3104786efdce0f809bce8a9ff31f2fa1c9ed # v8.27.0
|
|
42
|
+
hooks:
|
|
43
|
+
- id: gitleaks
|
|
44
|
+
name: 🔍 Secrets Checker
|
|
45
|
+
entry: gitleaks detect --no-git --redact --verbose
|
|
46
|
+
stages:
|
|
47
|
+
- pre-commit
|
|
48
|
+
|
|
49
|
+
- repo: https://github.com/commitizen-tools/commitizen
|
|
50
|
+
rev: a0cc4901b0faaced74c713a9e355555fc4de0880 # v4.7.1
|
|
51
|
+
hooks:
|
|
52
|
+
- id: commitizen
|
|
53
|
+
name: 📝 Conventional Commit Message Checker
|
|
54
|
+
stages:
|
|
55
|
+
- commit-msg
|
|
9
56
|
|
|
10
57
|
- repo: local
|
|
11
58
|
hooks:
|
|
12
|
-
- id: type-check
|
|
13
|
-
name: check typing
|
|
14
|
-
entry: make check-typing
|
|
15
|
-
language: system
|
|
16
59
|
- id: lint
|
|
17
|
-
name:
|
|
18
|
-
entry: make lint
|
|
60
|
+
name: 🧹 Code Linter
|
|
61
|
+
entry: make check-lint
|
|
19
62
|
language: system
|
|
63
|
+
stages:
|
|
64
|
+
- pre-push
|
|
65
|
+
|
|
20
66
|
- id: format
|
|
21
|
-
name:
|
|
22
|
-
entry: make format
|
|
23
|
-
language: system
|
|
24
|
-
- id: unit-test
|
|
25
|
-
name: run all unit test
|
|
26
|
-
entry: make all-unit
|
|
27
|
-
language: system
|
|
28
|
-
- id: pre-push
|
|
29
|
-
name: run integration and acceptance test
|
|
30
|
-
entry: make pre-push
|
|
67
|
+
name: 🎨 Code Formatter
|
|
68
|
+
entry: make check-format
|
|
31
69
|
language: system
|
|
32
70
|
stages:
|
|
33
71
|
- pre-push
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
title: {{ general.slug }}
|
|
3
|
+
message: If you use this software, please cite it using the metadata below.
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- given-names: {{ general.author }}
|
|
7
|
+
repository-code: https://github.com/{{ git.username }}/{{ general.slug }}/
|
|
8
|
+
url: https://pypi.org/project/{{ general.slug }}/
|
|
9
|
+
abstract: >
|
|
10
|
+
{{ general.description }}
|
|
11
|
+
keywords:
|
|
12
|
+
- python
|
|
13
|
+
license: {{ general.license }}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
Thank you for helping keep **{{ general.slug }}** package and its users safe.
|
|
4
|
+
We take security issues seriously and appreciate responsible disclosures.
|
|
5
|
+
|
|
6
|
+
## Reporting a Vulnerability
|
|
7
|
+
|
|
8
|
+
> [!NOTE]
|
|
9
|
+
> **Please do NOT open public issues for security reports.**
|
|
10
|
+
> Use one of the private channels below so we can coordinate a safe disclosure.
|
|
11
|
+
|
|
12
|
+
| Channel | How it works |
|
|
13
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
14
|
+
| **GitHub Security Advisory** | 1. Navigate to the repository's **“Security → Advisories”** tab<br>2. Click **“Report a vulnerability”** and fill in the form |
|
|
15
|
+
|
|
16
|
+
Include the following, if possible:
|
|
17
|
+
|
|
18
|
+
1. A **concise description** of the issue and its impact.
|
|
19
|
+
2. **Reproduction steps** or a proof-of-concept script.
|
|
20
|
+
3. Any **mitigation** ideas you've identified.
|
|
21
|
+
|
|
22
|
+
## Our Disclosure Process
|
|
23
|
+
|
|
24
|
+
1. **Acknowledge** report within 24–48 hours.
|
|
25
|
+
2. **Triage & validate** the issue; request additional info if needed.
|
|
26
|
+
3. **Fix & prepare**: develop a patch and regression tests.
|
|
27
|
+
4. **Coordinate release**:
|
|
28
|
+
- Agree on a disclosure date with the reporter (usually ≤ 30 days).
|
|
29
|
+
- Publish a CVE (if applicable) and a new PyPI release.
|
|
30
|
+
- Post a security advisory and update CHANGELOG.
|
|
31
|
+
5. **Credit** the reporter (optional & with consent).
|
|
32
|
+
|
|
33
|
+
## Responsible Disclosure
|
|
34
|
+
|
|
35
|
+
We kindly ask you to:
|
|
36
|
+
|
|
37
|
+
- Allow us reasonable time to remediate before any public disclosure.
|
|
38
|
+
- Avoid violating user privacy, destroying data, or disrupting production services while researching.
|
|
39
|
+
- Test only on your own instances or in minimal, isolated cases.
|
|
40
|
+
|
|
41
|
+
We are committed to keeping this project and its users safe and will strive to resolve all legitimate reports swiftly and transparently.
|
|
42
|
+
|
|
43
|
+
_Thank you for keeping the **{{ general.slug }}** package community secure!_
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{% set template_domain_import = "shared.domain"|compute_base_path(template.name) %}
|
|
2
2
|
from {{ general.source_name }}.{{ template_domain_import }}.event.domain_event import DomainEvent
|
|
3
|
+
from {{ general.source_name }}.{{ template_domain_import }}.value_objects.aggregate import Aggregate
|
|
3
4
|
|
|
4
5
|
|
|
5
|
-
class
|
|
6
|
+
class EventAggregate(Aggregate):
|
|
6
7
|
_domain_events: list[DomainEvent]
|
|
7
8
|
|
|
8
9
|
def __init__(self) -> None:
|
|
@@ -1,22 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
{% set template_domain_import = "shared.domain"|compute_base_path(template.name) %}
|
|
2
|
+
from {{ general.source_name }}.{{ template_domain_import }}.exceptions.error import Error
|
|
2
3
|
|
|
4
|
+
class DomainError(Error):
|
|
5
|
+
...
|
|
3
6
|
|
|
4
|
-
class DomainError(Exception, ABC):
|
|
5
|
-
def __init__(self, message: str, error_type: str) -> None:
|
|
6
|
-
self._message = message
|
|
7
|
-
self._type = error_type
|
|
8
|
-
super().__init__(self._message)
|
|
9
|
-
|
|
10
|
-
@property
|
|
11
|
-
def type(self) -> str:
|
|
12
|
-
return self._type
|
|
13
|
-
|
|
14
|
-
@property
|
|
15
|
-
def message(self) -> str:
|
|
16
|
-
return self._message
|
|
17
|
-
|
|
18
|
-
def to_primitives(self) -> dict[str, str]:
|
|
19
|
-
return {
|
|
20
|
-
"type": self.type,
|
|
21
|
-
"message": self.message,
|
|
22
|
-
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseError(Exception, ABC):
|
|
5
|
+
"""Base class for all controlled errors in the application."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, message: str, error_type: str) -> None:
|
|
8
|
+
self._message = message
|
|
9
|
+
self._type = error_type
|
|
10
|
+
super().__init__(self._message)
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def type(self) -> str:
|
|
14
|
+
return self._type
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def message(self) -> str:
|
|
18
|
+
return self._message
|
|
19
|
+
|
|
20
|
+
def to_primitives(self) -> dict[str, str]:
|
|
21
|
+
return {
|
|
22
|
+
"type": self.type,
|
|
23
|
+
"message": self.message,
|
|
24
|
+
}
|
|
@@ -1,7 +1,37 @@
|
|
|
1
1
|
{% set template_domain_import = "shared.domain"|compute_base_path(template.name) %}
|
|
2
2
|
{% set template_infra_import = "shared.infra"|compute_base_path(template.name) %}
|
|
3
|
-
from fastapi import FastAPI
|
|
4
|
-
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
{% if "logger" in template.built_in_features %}
|
|
5
|
+
from fastapi.exceptions import RequestValidationError
|
|
6
|
+
{% endif %}
|
|
7
|
+
|
|
8
|
+
{% if template.name == template_types.STANDARD %}
|
|
9
|
+
{% if "logger" in template.built_in_features %}
|
|
10
|
+
from {{ general.source_name }}.api.handlers.error_handlers import (
|
|
11
|
+
unexpected_exception_handler,
|
|
12
|
+
domain_error_handler,
|
|
13
|
+
validation_error_handler,
|
|
14
|
+
)
|
|
15
|
+
{% else %}
|
|
16
|
+
from {{ general.source_name }}.api.handlers.error_handlers import (
|
|
17
|
+
unexpected_exception_handler,
|
|
18
|
+
domain_error_handler,
|
|
19
|
+
)
|
|
20
|
+
{% endif %}
|
|
21
|
+
{% else %}
|
|
22
|
+
{% if "logger" in template.built_in_features %}
|
|
23
|
+
from {{ general.source_name }}.delivery.api.handlers.error_handlers import (
|
|
24
|
+
unexpected_exception_handler,
|
|
25
|
+
domain_error_handler,
|
|
26
|
+
validation_error_handler,
|
|
27
|
+
)
|
|
28
|
+
{% else %}
|
|
29
|
+
from {{ general.source_name }}.delivery.api.handlers.error_handlers import (
|
|
30
|
+
unexpected_exception_handler,
|
|
31
|
+
domain_error_handler,
|
|
32
|
+
)
|
|
33
|
+
{% endif %}
|
|
34
|
+
{% endif %}
|
|
5
35
|
|
|
6
36
|
{% if ["async_alembic"] | is_in(template.built_in_features) %}
|
|
7
37
|
{% if template.name == template_types.STANDARD %}
|
|
@@ -10,9 +40,15 @@ from {{ general.source_name }}.api.lifespan import lifespan
|
|
|
10
40
|
from {{ general.source_name }}.delivery.api.lifespan import lifespan
|
|
11
41
|
{% endif %}
|
|
12
42
|
{% endif %}
|
|
13
|
-
from {{ general.source_name }}.{{ template_infra_import }}.http.http_response import HttpResponse
|
|
14
|
-
from {{ general.source_name }}.{{ template_infra_import }}.http.status_code import StatusCode
|
|
15
43
|
from {{ general.source_name }}.{{ template_domain_import }}.exceptions.domain_error import DomainError
|
|
44
|
+
{% if "logger" in template.built_in_features %}
|
|
45
|
+
from {{ general.source_name }}.{{ template_infra_import }}.logger.file_logger import create_file_logger
|
|
46
|
+
{% if template.name == template_types.STANDARD %}
|
|
47
|
+
from {{ general.source_name }}.api.middleare.fast_api_log_middleware import FastapiLogMiddleware
|
|
48
|
+
{% else %}
|
|
49
|
+
from {{ general.source_name }}.delivery.api.middleare.fast_api_log_middleware import FastapiLogMiddleware
|
|
50
|
+
{% endif %}
|
|
51
|
+
{% endif %}
|
|
16
52
|
|
|
17
53
|
|
|
18
54
|
{% if ["async_alembic"] | is_in(template.built_in_features) %}
|
|
@@ -21,11 +57,12 @@ app = FastAPI(lifespan=lifespan)
|
|
|
21
57
|
app = FastAPI()
|
|
22
58
|
{% endif %}
|
|
23
59
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return HttpResponse.internal_error(exc)
|
|
60
|
+
{% if "logger" in template.built_in_features %}
|
|
61
|
+
logger = create_file_logger(name="{{ general.slug }}")
|
|
27
62
|
|
|
63
|
+
app.add_middleware(FastapiLogMiddleware, logger=logger)
|
|
64
|
+
app.add_exception_handler(RequestValidationError, validation_error_handler)
|
|
65
|
+
{% endif %}
|
|
66
|
+
app.add_exception_handler(Exception, unexpected_exception_handler)
|
|
67
|
+
app.add_exception_handler(DomainError, domain_error_handler)
|
|
28
68
|
|
|
29
|
-
@app.exception_handler(DomainError)
|
|
30
|
-
async def domain_error_handler(_: Request, exc: DomainError) -> JSONResponse:
|
|
31
|
-
return HttpResponse.domain_error(exc, status_code=StatusCode.BAD_REQUEST)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{% set template_domain_import = "shared.domain"|compute_base_path(template.name) %}
|
|
2
|
+
{% set template_infra_import = "shared.infra"|compute_base_path(template.name) %}
|
|
3
|
+
from fastapi import Request
|
|
4
|
+
from fastapi.responses import JSONResponse
|
|
5
|
+
{% if "logger" in template.built_in_features %}
|
|
6
|
+
from fastapi.exceptions import RequestValidationError
|
|
7
|
+
from fastapi.exception_handlers import request_validation_exception_handler
|
|
8
|
+
from {{ general.source_name }}.{{ template_infra_import }}.logger.file_logger import create_file_logger
|
|
9
|
+
{% endif %}
|
|
10
|
+
from {{ general.source_name }}.{{ template_infra_import }}.http.error_response import InternalServerError, UnprocessableEntityError
|
|
11
|
+
from {{ general.source_name }}.{{ template_domain_import }}.exceptions.domain_error import DomainError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
{% if "logger" in template.built_in_features %}
|
|
15
|
+
logger = create_file_logger(name="{{ general.slug }}")
|
|
16
|
+
|
|
17
|
+
async def unexpected_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
18
|
+
logger.error(
|
|
19
|
+
message=f"error - {request.url.path}",
|
|
20
|
+
details={
|
|
21
|
+
"error": {
|
|
22
|
+
"message": str(exc),
|
|
23
|
+
"type": "unexpected_error",
|
|
24
|
+
},
|
|
25
|
+
"method": request.method,
|
|
26
|
+
"source": request.url.path,
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
return InternalServerError().as_json()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def domain_error_handler(request: Request, exc: DomainError) -> JSONResponse:
|
|
33
|
+
logger.error(
|
|
34
|
+
message=f"error - {request.url.path}",
|
|
35
|
+
details={
|
|
36
|
+
"error": exc.to_primitives(),
|
|
37
|
+
"method": request.method,
|
|
38
|
+
"source": request.url.path,
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
return UnprocessableEntityError().as_json()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def validation_error_handler(
|
|
45
|
+
request: Request,
|
|
46
|
+
exc: RequestValidationError,
|
|
47
|
+
) -> JSONResponse:
|
|
48
|
+
logger.error(
|
|
49
|
+
message=f"error - {request.url.path}",
|
|
50
|
+
details={
|
|
51
|
+
"error": {"message": str(exc), "type": "validation_error"},
|
|
52
|
+
"method": request.method,
|
|
53
|
+
"source": request.url.path,
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
return await request_validation_exception_handler(request, exc)
|
|
57
|
+
{% else %}
|
|
58
|
+
async def unexpected_exception_handler(_: Request, __: Exception) -> JSONResponse:
|
|
59
|
+
return InternalServerError().as_json()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async def domain_error_handler(_: Request, __: DomainError) -> JSONResponse:
|
|
63
|
+
return UnprocessableEntityError().as_json()
|
|
64
|
+
{% endif %}
|
|
@@ -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,33 @@
|
|
|
1
|
+
{% set template_infra_import = "shared.infra"|compute_base_path(template.name) %}
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from fastapi import Request, Response, FastAPI
|
|
5
|
+
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
|
6
|
+
|
|
7
|
+
from {{ general.source_name }}.{{ template_infra_import }}.logger.file_logger import FileLogger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FastapiLogMiddleware(BaseHTTPMiddleware):
|
|
11
|
+
def __init__(self, app: FastAPI, logger: FileLogger) -> None:
|
|
12
|
+
super().__init__(app)
|
|
13
|
+
self._logger = logger
|
|
14
|
+
|
|
15
|
+
async def dispatch(
|
|
16
|
+
self, request: Request, call_next: RequestResponseEndpoint
|
|
17
|
+
) -> Response:
|
|
18
|
+
start_time = time.perf_counter()
|
|
19
|
+
response = await call_next(request)
|
|
20
|
+
process_time = time.perf_counter() - start_time
|
|
21
|
+
|
|
22
|
+
if response.status_code < 400:
|
|
23
|
+
self._logger.info(
|
|
24
|
+
message=f"success - {request.url.path}",
|
|
25
|
+
details={
|
|
26
|
+
"method": request.method,
|
|
27
|
+
"source": request.url.path,
|
|
28
|
+
"process_time": process_time,
|
|
29
|
+
"status_code": response.status_code,
|
|
30
|
+
},
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return response
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from starlette.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
|
+
)
|
|
@@ -9,14 +9,27 @@ outputs: {}
|
|
|
9
9
|
runs:
|
|
10
10
|
using: composite
|
|
11
11
|
steps:
|
|
12
|
-
-
|
|
12
|
+
- name: 🐍 Setup Python
|
|
13
|
+
uses: actions/setup-python@v5
|
|
13
14
|
with:
|
|
14
15
|
python-version: {% raw %}${{ inputs.python-version }}{% endraw %}
|
|
15
16
|
|
|
16
|
-
- name: Install dependency manager
|
|
17
|
+
- name: 🔨 Install dependency manager
|
|
17
18
|
run: python -m pip install {{ general.dependency_manager }}
|
|
18
19
|
shell: bash
|
|
19
|
-
- name: Install dependencies
|
|
20
|
-
run: {% if general.dependency_manager == "uv" %}uv sync --all-groups{% elif general.dependency_manager == "pdm" %}pdm install {% endif %}
|
|
21
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 %}
|
|
22
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?
|