instant-python 0.1.1__py3-none-any.whl → 0.3.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/folder_cli.py +9 -9
- instant_python/installer/pdm_manager.py +10 -3
- instant_python/installer/uv_manager.py +11 -0
- instant_python/project_cli.py +3 -3
- instant_python/project_generator/boilerplate_file.py +20 -0
- instant_python/project_generator/file.py +4 -8
- instant_python/project_generator/folder_tree.py +4 -0
- instant_python/project_generator/jinja_environment.py +20 -0
- instant_python/project_generator/{default_template_manager.py → jinja_template_manager.py} +8 -13
- instant_python/project_generator/node.py +1 -0
- instant_python/project_generator/project_generator.py +1 -1
- instant_python/question_prompter/question/conditional_question.py +9 -3
- instant_python/question_prompter/question_wizard.py +3 -3
- instant_python/question_prompter/{user_requirements.py → requirements_configuration.py} +3 -2
- instant_python/question_prompter/step/template_step.py +21 -13
- instant_python/templates/boilerplate/event_bus/aggregate_root.py +0 -1
- instant_python/templates/boilerplate/event_bus/domain_event_json_deserializer.py +3 -3
- instant_python/templates/boilerplate/event_bus/domain_event_subscriber.py +19 -0
- instant_python/templates/boilerplate/event_bus/event_bus.py +1 -1
- instant_python/templates/boilerplate/exceptions/domain_event_type_not_found_error.py +1 -1
- instant_python/templates/boilerplate/exceptions/rabbit_mq_connection_not_established_error.py +17 -0
- instant_python/templates/boilerplate/persistence/synchronous/sqlalchemy_repository.py +1 -1
- instant_python/templates/boilerplate/value_object/int_value_object.py +8 -5
- instant_python/templates/boilerplate/value_object/string_value_object.py +1 -1
- instant_python/templates/boilerplate/value_object/uuid.py +1 -1
- instant_python/templates/project_structure/alembic_migrator.yml.j2 +1 -1
- instant_python/templates/project_structure/async_alembic.yml.j2 +5 -5
- instant_python/templates/project_structure/async_sqlalchemy.yml.j2 +3 -3
- instant_python/templates/project_structure/clean_architecture/source.yml.j2 +2 -2
- instant_python/templates/project_structure/clean_architecture/test.yml.j2 +2 -2
- instant_python/templates/project_structure/domain_driven_design/source.yml.j2 +5 -3
- instant_python/templates/project_structure/domain_driven_design/test.yml.j2 +4 -2
- instant_python/templates/project_structure/event_bus_domain.yml.j2 +9 -6
- instant_python/templates/project_structure/event_bus_infra.yml.j2 +8 -8
- instant_python/templates/project_structure/fastapi_app.yml.j2 +2 -2
- 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/gitignore.yml.j2 +1 -1
- instant_python/templates/project_structure/license.yml.j2 +1 -1
- instant_python/templates/project_structure/logger.yml.j2 +2 -2
- instant_python/templates/project_structure/makefile.yml.j2 +10 -10
- instant_python/templates/project_structure/mypy.yml.j2 +1 -1
- instant_python/templates/project_structure/pre_commit.yml.j2 +1 -1
- instant_python/templates/project_structure/pyproject.yml.j2 +1 -1
- instant_python/templates/project_structure/pytest.yml.j2 +1 -1
- instant_python/templates/project_structure/python_version.yml.j2 +1 -1
- instant_python/templates/project_structure/standard_project/source.yml.j2 +2 -2
- instant_python/templates/project_structure/standard_project/test.yml.j2 +2 -2
- instant_python/templates/project_structure/synchronous_sqlalchemy.yml.j2 +3 -3
- instant_python/templates/project_structure/value_objects.yml.j2 +9 -9
- {instant_python-0.1.1.dist-info → instant_python-0.3.0.dist-info}/METADATA +9 -3
- {instant_python-0.1.1.dist-info → instant_python-0.3.0.dist-info}/RECORD +55 -52
- {instant_python-0.1.1.dist-info → instant_python-0.3.0.dist-info}/WHEEL +0 -0
- {instant_python-0.1.1.dist-info → instant_python-0.3.0.dist-info}/entry_points.txt +0 -0
- {instant_python-0.1.1.dist-info → instant_python-0.3.0.dist-info}/licenses/LICENSE +0 -0
instant_python/folder_cli.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
|
|
3
3
|
from instant_python.project_generator.custom_template_manager import CustomTemplateManager
|
|
4
|
-
from instant_python.project_generator.
|
|
4
|
+
from instant_python.project_generator.jinja_template_manager import JinjaTemplateManager
|
|
5
5
|
from instant_python.project_generator.folder_tree import FolderTree
|
|
6
6
|
from instant_python.project_generator.project_generator import ProjectGenerator
|
|
7
7
|
from instant_python.question_prompter.question.free_text_question import FreeTextQuestion
|
|
@@ -13,8 +13,8 @@ from instant_python.question_prompter.step.template_step import TemplateStep
|
|
|
13
13
|
app = typer.Typer()
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@app.command("template", help="Pass a custom template folder structure"
|
|
17
|
-
def create_folder_structure_from_template(
|
|
16
|
+
@app.command("template", help="Pass a custom template folder structure")
|
|
17
|
+
def create_folder_structure_from_template(template_path: str) -> None:
|
|
18
18
|
project_name = FreeTextQuestion(
|
|
19
19
|
key="project_slug",
|
|
20
20
|
message="Enter the name of the project (CANNOT CONTAIN SPACES)",
|
|
@@ -22,7 +22,7 @@ def create_folder_structure_from_template(template_name: str) -> None:
|
|
|
22
22
|
).ask()
|
|
23
23
|
project_generator = ProjectGenerator(
|
|
24
24
|
folder_tree=FolderTree(project_name["project_slug"]),
|
|
25
|
-
template_manager=CustomTemplateManager(
|
|
25
|
+
template_manager=CustomTemplateManager(template_path),
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
project_generator.generate()
|
|
@@ -33,17 +33,17 @@ def create_default_project_structure() -> None:
|
|
|
33
33
|
wizard = QuestionWizard(
|
|
34
34
|
steps=Steps(GeneralProjectStep(), TemplateStep())
|
|
35
35
|
)
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
requirements = wizard.run()
|
|
37
|
+
requirements.save_in_memory()
|
|
38
38
|
|
|
39
39
|
project_generator = ProjectGenerator(
|
|
40
|
-
folder_tree=FolderTree(
|
|
41
|
-
template_manager=
|
|
40
|
+
folder_tree=FolderTree(requirements.project_slug),
|
|
41
|
+
template_manager=JinjaTemplateManager(),
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
project_generator.generate()
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
requirements.remove()
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
if __name__ == "__main__":
|
|
@@ -31,6 +31,7 @@ class PdmManager(DependencyManager):
|
|
|
31
31
|
print(f">>> Python {version} installed successfully")
|
|
32
32
|
|
|
33
33
|
def install_dependencies(self, dependencies: list[str]) -> None:
|
|
34
|
+
self._create_virtual_environment()
|
|
34
35
|
for dependency_name in dependencies:
|
|
35
36
|
self._install_dependency(dependency_name)
|
|
36
37
|
|
|
@@ -67,6 +68,12 @@ class PdmManager(DependencyManager):
|
|
|
67
68
|
group_flag += f"--group {group_name}"
|
|
68
69
|
return f"{dev_flag} {group_flag}"
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
def _create_virtual_environment(self) -> None:
|
|
72
|
+
command = f"{self._pdm} install"
|
|
73
|
+
subprocess.run(
|
|
74
|
+
command,
|
|
75
|
+
shell=True,
|
|
76
|
+
check=True,
|
|
77
|
+
cwd=self._project_directory,
|
|
78
|
+
stdout=subprocess.DEVNULL,
|
|
79
|
+
)
|
|
@@ -33,6 +33,7 @@ class UvManager(DependencyManager):
|
|
|
33
33
|
print(f">>> Python {version} installed successfully")
|
|
34
34
|
|
|
35
35
|
def install_dependencies(self, dependencies: list[str]) -> None:
|
|
36
|
+
self._create_virtual_environment()
|
|
36
37
|
for dependency_name in dependencies:
|
|
37
38
|
self._install_dependency(dependency_name)
|
|
38
39
|
|
|
@@ -71,3 +72,13 @@ class UvManager(DependencyManager):
|
|
|
71
72
|
).ask()["group_name"]
|
|
72
73
|
flag = f"--group {group_name}"
|
|
73
74
|
return flag
|
|
75
|
+
|
|
76
|
+
def _create_virtual_environment(self) -> None:
|
|
77
|
+
command = f"{self._uv} sync"
|
|
78
|
+
subprocess.run(
|
|
79
|
+
command,
|
|
80
|
+
shell=True,
|
|
81
|
+
check=True,
|
|
82
|
+
cwd=self._project_directory,
|
|
83
|
+
stdout=subprocess.DEVNULL,
|
|
84
|
+
)
|
instant_python/project_cli.py
CHANGED
|
@@ -4,8 +4,8 @@ from instant_python.installer.dependency_manager_factory import DependencyManage
|
|
|
4
4
|
from instant_python.installer.git_configurer import GitConfigurer
|
|
5
5
|
from instant_python.installer.installer import Installer
|
|
6
6
|
from instant_python.project_generator.custom_template_manager import CustomTemplateManager
|
|
7
|
-
from instant_python.project_generator.
|
|
8
|
-
|
|
7
|
+
from instant_python.project_generator.jinja_template_manager import (
|
|
8
|
+
JinjaTemplateManager,
|
|
9
9
|
)
|
|
10
10
|
from instant_python.project_generator.folder_tree import FolderTree
|
|
11
11
|
from instant_python.project_generator.project_generator import ProjectGenerator
|
|
@@ -74,7 +74,7 @@ def create_full_project() -> None:
|
|
|
74
74
|
|
|
75
75
|
project_generator = ProjectGenerator(
|
|
76
76
|
folder_tree=FolderTree(user_requirements.project_slug),
|
|
77
|
-
template_manager=
|
|
77
|
+
template_manager=JinjaTemplateManager(),
|
|
78
78
|
)
|
|
79
79
|
project_generator.generate()
|
|
80
80
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from instant_python.project_generator.jinja_template_manager import JinjaTemplateManager
|
|
4
|
+
from instant_python.project_generator.node import Node
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BoilerplateFile(Node):
|
|
8
|
+
|
|
9
|
+
def __init__(self, name: str, extension: str) -> None:
|
|
10
|
+
self._file_name = f"{name.split('/')[-1]}{extension}"
|
|
11
|
+
self._template_path = f"boilerplate/{name}{extension}"
|
|
12
|
+
self._template_manager = JinjaTemplateManager()
|
|
13
|
+
|
|
14
|
+
def __repr__(self) -> str:
|
|
15
|
+
return f"{self.__class__.__name__}(name={self._file_name})"
|
|
16
|
+
|
|
17
|
+
def create(self, base_path: Path) -> None:
|
|
18
|
+
file_path = base_path / self._file_name
|
|
19
|
+
content = self._template_manager.get_boilerplate(self._template_path)
|
|
20
|
+
file_path.write_text(content)
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from instant_python.project_generator.default_template_manager import DefaultTemplateManager
|
|
4
3
|
from instant_python.project_generator.node import Node
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
class File(Node):
|
|
8
|
-
|
|
9
7
|
def __init__(self, name: str, extension: str) -> None:
|
|
10
|
-
self._file_name =
|
|
11
|
-
self.
|
|
12
|
-
self._template_manager = DefaultTemplateManager()
|
|
8
|
+
self._file_name = name
|
|
9
|
+
self._extension = extension
|
|
13
10
|
|
|
14
11
|
def __repr__(self) -> str:
|
|
15
12
|
return f"{self.__class__.__name__}(name={self._file_name})"
|
|
16
13
|
|
|
17
14
|
def create(self, base_path: Path) -> None:
|
|
18
|
-
file_path = base_path / self._file_name
|
|
19
|
-
|
|
20
|
-
file_path.write_text(content)
|
|
15
|
+
file_path = base_path / f"{self._file_name}{self._extension}"
|
|
16
|
+
file_path.touch(exist_ok=True)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from instant_python.project_generator.directory import Directory
|
|
4
|
+
from instant_python.project_generator.boilerplate_file import BoilerplateFile
|
|
4
5
|
from instant_python.project_generator.file import File
|
|
5
6
|
from instant_python.project_generator.node import Node, NodeType
|
|
6
7
|
|
|
@@ -33,6 +34,9 @@ class FolderTree:
|
|
|
33
34
|
is_python_module = node.get("python", False)
|
|
34
35
|
directory_children = [self._build_tree(child) for child in children]
|
|
35
36
|
return Directory(name=name, children=directory_children, python_module=is_python_module)
|
|
37
|
+
elif node_type == NodeType.BOILERPLATE:
|
|
38
|
+
extension = node.get("extension", "")
|
|
39
|
+
return BoilerplateFile(name=name, extension=extension)
|
|
36
40
|
elif node_type == NodeType.FILE:
|
|
37
41
|
extension = node.get("extension", "")
|
|
38
42
|
return File(name=name, extension=extension)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from jinja2 import Environment, Template, PackageLoader
|
|
2
|
+
|
|
3
|
+
from instant_python.project_generator.jinja_custom_filters import (
|
|
4
|
+
is_in,
|
|
5
|
+
compute_base_path,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class JinjaEnvironment:
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
self._env = Environment(
|
|
12
|
+
loader=PackageLoader("instant_python", "templates"),
|
|
13
|
+
trim_blocks=True,
|
|
14
|
+
lstrip_blocks=True,
|
|
15
|
+
)
|
|
16
|
+
self._env.filters["is_in"] = is_in
|
|
17
|
+
self._env.filters["compute_base_path"] = compute_base_path
|
|
18
|
+
|
|
19
|
+
def get_template(self, name: str) -> Template:
|
|
20
|
+
return self._env.get_template(name)
|
|
@@ -1,22 +1,17 @@
|
|
|
1
1
|
import yaml
|
|
2
|
-
from jinja2 import
|
|
2
|
+
from jinja2 import Template
|
|
3
3
|
|
|
4
|
-
from instant_python.project_generator.
|
|
4
|
+
from instant_python.project_generator.jinja_environment import JinjaEnvironment
|
|
5
5
|
from instant_python.project_generator.template_manager import TemplateManager
|
|
6
6
|
from instant_python.question_prompter.template_types import TemplateTypes
|
|
7
|
-
from instant_python.question_prompter.
|
|
7
|
+
from instant_python.question_prompter.requirements_configuration import RequirementsConfiguration
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
class
|
|
10
|
+
class JinjaTemplateManager(TemplateManager):
|
|
11
11
|
|
|
12
12
|
def __init__(self) -> None:
|
|
13
13
|
self._requirements = self._load_memory_requirements()
|
|
14
|
-
self._env =
|
|
15
|
-
"templates"),
|
|
16
|
-
trim_blocks=True,
|
|
17
|
-
lstrip_blocks=True)
|
|
18
|
-
self._env.filters["is_in"] = is_in
|
|
19
|
-
self._env.filters["compute_base_path"] = compute_base_path
|
|
14
|
+
self._env = JinjaEnvironment()
|
|
20
15
|
|
|
21
16
|
def get_project(self, template_name: str) -> dict:
|
|
22
17
|
template = self._get_template(
|
|
@@ -36,7 +31,7 @@ class DefaultTemplateManager(TemplateManager):
|
|
|
36
31
|
return template.render(**self._requirements.to_dict(), template_types=TemplateTypes)
|
|
37
32
|
|
|
38
33
|
@staticmethod
|
|
39
|
-
def _load_memory_requirements() ->
|
|
40
|
-
with open("
|
|
34
|
+
def _load_memory_requirements() -> RequirementsConfiguration:
|
|
35
|
+
with open("ipy.yml") as file:
|
|
41
36
|
requirements = yaml.safe_load(file)
|
|
42
|
-
return
|
|
37
|
+
return RequirementsConfiguration(**requirements)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import subprocess
|
|
2
2
|
|
|
3
3
|
from instant_python.project_generator.folder_tree import FolderTree
|
|
4
|
-
from instant_python.project_generator.
|
|
4
|
+
from instant_python.project_generator.jinja_template_manager import TemplateManager
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ProjectGenerator:
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
1
3
|
from instant_python.question_prompter.question.question import Question
|
|
2
4
|
|
|
3
5
|
|
|
4
6
|
class ConditionalQuestion:
|
|
5
7
|
def __init__(
|
|
6
|
-
self, base_question: Question, subquestions: list[Question], condition: str | bool
|
|
8
|
+
self, base_question: Question, subquestions: Union[list[Question], "ConditionalQuestion"], condition: str | bool
|
|
7
9
|
) -> None:
|
|
8
10
|
self._base_question = base_question
|
|
9
11
|
self._subquestions = subquestions
|
|
@@ -16,8 +18,12 @@ class ConditionalQuestion:
|
|
|
16
18
|
return base_answer
|
|
17
19
|
|
|
18
20
|
answers = base_answer
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
|
|
22
|
+
if isinstance(self._subquestions, ConditionalQuestion):
|
|
23
|
+
answers.update(self._subquestions.ask())
|
|
24
|
+
else:
|
|
25
|
+
for question in self._subquestions:
|
|
26
|
+
answers.update(question.ask())
|
|
21
27
|
return answers
|
|
22
28
|
|
|
23
29
|
def _base_answer_does_not_satisfies_condition(self, base_answer: dict[str, str]) -> bool:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from instant_python.question_prompter.step.steps import Steps
|
|
2
|
-
from instant_python.question_prompter.
|
|
2
|
+
from instant_python.question_prompter.requirements_configuration import RequirementsConfiguration
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class QuestionWizard:
|
|
@@ -7,9 +7,9 @@ class QuestionWizard:
|
|
|
7
7
|
self._steps = steps
|
|
8
8
|
self._answers = {}
|
|
9
9
|
|
|
10
|
-
def run(self) ->
|
|
10
|
+
def run(self) -> RequirementsConfiguration:
|
|
11
11
|
for step in self._steps:
|
|
12
12
|
answer = step.run(self._answers)
|
|
13
13
|
self._answers.update(answer)
|
|
14
14
|
|
|
15
|
-
return
|
|
15
|
+
return RequirementsConfiguration(**self._answers)
|
|
@@ -6,7 +6,7 @@ import yaml
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@dataclass
|
|
9
|
-
class
|
|
9
|
+
class RequirementsConfiguration:
|
|
10
10
|
project_slug: str
|
|
11
11
|
license: str
|
|
12
12
|
version: str
|
|
@@ -20,13 +20,14 @@ class UserRequirements:
|
|
|
20
20
|
git_email: str = field(default_factory=str)
|
|
21
21
|
git_user_name: str = field(default_factory=str)
|
|
22
22
|
dependencies: list[str] = field(default_factory=list)
|
|
23
|
+
specify_bounded_context: bool = field(default=False)
|
|
23
24
|
bounded_context: str = field(default_factory=str)
|
|
24
25
|
aggregate_name: str = field(default_factory=str)
|
|
25
26
|
built_in_features: list[str] = field(default_factory=list)
|
|
26
27
|
year: int = field(default=datetime.now().year)
|
|
27
28
|
|
|
28
29
|
def __post_init__(self) -> None:
|
|
29
|
-
self._file_path = "
|
|
30
|
+
self._file_path = "ipy.yml"
|
|
30
31
|
|
|
31
32
|
def to_dict(self) -> dict:
|
|
32
33
|
return asdict(self)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from instant_python.question_prompter.question.boolean_question import BooleanQuestion
|
|
1
2
|
from instant_python.question_prompter.question.choice_question import ChoiceQuestion
|
|
2
3
|
from instant_python.question_prompter.question.conditional_question import (
|
|
3
4
|
ConditionalQuestion,
|
|
@@ -17,12 +18,11 @@ class TemplateStep(Step):
|
|
|
17
18
|
self._questions = [
|
|
18
19
|
MultipleChoiceQuestion(
|
|
19
20
|
key="built_in_features",
|
|
20
|
-
message="Select the built-in features you want to include
|
|
21
|
+
message="Select the built-in features you want to include",
|
|
21
22
|
options=[
|
|
22
23
|
"value_objects",
|
|
23
24
|
"github_actions",
|
|
24
25
|
"makefile",
|
|
25
|
-
"synchronous_sqlalchemy",
|
|
26
26
|
"logger",
|
|
27
27
|
"event_bus",
|
|
28
28
|
"async_sqlalchemy",
|
|
@@ -40,18 +40,26 @@ class TemplateStep(Step):
|
|
|
40
40
|
"standard_project",
|
|
41
41
|
],
|
|
42
42
|
),
|
|
43
|
-
subquestions=
|
|
44
|
-
|
|
45
|
-
key="
|
|
46
|
-
message="
|
|
47
|
-
default=
|
|
43
|
+
subquestions=ConditionalQuestion(
|
|
44
|
+
base_question=BooleanQuestion(
|
|
45
|
+
key="specify_bounded_context",
|
|
46
|
+
message="Do you want to specify your first bounded context?",
|
|
47
|
+
default=True,
|
|
48
48
|
),
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
subquestions=[
|
|
50
|
+
FreeTextQuestion(
|
|
51
|
+
key="bounded_context",
|
|
52
|
+
message="Enter the bounded context name",
|
|
53
|
+
default="backoffice",
|
|
54
|
+
),
|
|
55
|
+
FreeTextQuestion(
|
|
56
|
+
key="aggregate_name",
|
|
57
|
+
message="Enter the aggregate name",
|
|
58
|
+
default="user",
|
|
59
|
+
),
|
|
60
|
+
],
|
|
61
|
+
condition=True,
|
|
62
|
+
),
|
|
55
63
|
condition=TemplateTypes.DDD,
|
|
56
64
|
),
|
|
57
65
|
]
|
|
@@ -5,8 +5,8 @@ from {{ source_name }}.{{ template_domain_import }}.event.domain_event import Do
|
|
|
5
5
|
from {{ source_name }}.{{ template_domain_import }}.event.domain_event_subscriber import (
|
|
6
6
|
DomainEventSubscriber,
|
|
7
7
|
)
|
|
8
|
-
from {{ source_name }}.{{ template_domain_import }}.exceptions.
|
|
9
|
-
|
|
8
|
+
from {{ source_name }}.{{ template_domain_import }}.exceptions.domain_event_type_not_found_error import (
|
|
9
|
+
DomainEventTypeNotFoundError,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
12
|
|
|
@@ -23,6 +23,6 @@ class DomainEventJsonDeserializer:
|
|
|
23
23
|
event_class = self._events_mapping.get(content["data"]["type"])
|
|
24
24
|
|
|
25
25
|
if not event_class:
|
|
26
|
-
raise
|
|
26
|
+
raise DomainEventTypeNotFoundError(content["data"]["type"])
|
|
27
27
|
|
|
28
28
|
return event_class(**content["data"]["attributes"])
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{% set template_domain_import = "shared.domain"|compute_base_path(template) %}
|
|
2
|
+
{% if pythono_version in ["3.12", "3.13"] %}
|
|
2
3
|
from abc import ABC, abstractmethod
|
|
3
4
|
|
|
4
5
|
from {{ source_name }}.{{ template_domain_import }}.event.domain_event import DomainEvent
|
|
@@ -13,3 +14,21 @@ class DomainEventSubscriber[EventType: DomainEvent](ABC):
|
|
|
13
14
|
@abstractmethod
|
|
14
15
|
def on(self, event: EventType) -> None:
|
|
15
16
|
raise NotImplementedError
|
|
17
|
+
{% else %}
|
|
18
|
+
from abc import ABC, abstractmethod
|
|
19
|
+
from typing import Generic, TypeVar
|
|
20
|
+
|
|
21
|
+
from {{ source_name }}.{{ template_domain_import }}.event.domain_event import DomainEvent
|
|
22
|
+
|
|
23
|
+
EventType = TypeVar("EventType", bound=DomainEvent)
|
|
24
|
+
|
|
25
|
+
class DomainEventSubscriber(Generic[EventType], ABC):
|
|
26
|
+
@staticmethod
|
|
27
|
+
@abstractmethod
|
|
28
|
+
def subscribed_to() -> list[type[EventType]]:
|
|
29
|
+
raise NotImplementedError
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def on(self, event: EventType) -> None:
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
{% endif %}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
from {{ source_name }}.{{ template_domain_import }}.exceptions.domain_error import DomainError
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class
|
|
5
|
+
class DomainEventTypeNotFoundError(DomainError):
|
|
6
6
|
def __init__(self, name: str) -> None:
|
|
7
7
|
self._message = f"Event type {name} not found among subscriber."
|
|
8
8
|
self._type = "domain_event_type_not_found"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{% set template_domain_import = "shared.domain"|compute_base_path(template) %}
|
|
2
|
+
from {{ source_name }}.{{ template_domain_import }}.exceptions.domain_error import DomainError
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RabbitMqConnectionNotEstablishedError(DomainError):
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
self._message = "RabbitMQ connection not established."
|
|
8
|
+
self._type = "rabbit_mq_connection"
|
|
9
|
+
super().__init__(self._message)
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def type(self) -> str:
|
|
13
|
+
return self._type
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def message(self) -> str:
|
|
17
|
+
return self._message
|
|
@@ -6,7 +6,7 @@ from typing import TypeVar
|
|
|
6
6
|
from typing import TypeVar, Generic
|
|
7
7
|
{% endif %}
|
|
8
8
|
|
|
9
|
-
from {{ source_name }}.{{ template_domain_import }}.
|
|
9
|
+
from {{ source_name }}.{{ template_domain_import }}.value_object.uuid import Uuid
|
|
10
10
|
from {{ source_name }}.{{ template_infra_import }}.persistence.sqlalchemy.base import Base
|
|
11
11
|
from {{ source_name }}.{{ template_infra_import }}.persistence.sqlalchemy.session_maker import (
|
|
12
12
|
SessionMaker,
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{% set template_domain_import = "shared.domain"|compute_base_path(template) %}
|
|
2
2
|
from {{ source_name }}.{{ template_domain_import }}.exceptions.invalid_negative_value_error import (
|
|
3
|
-
|
|
3
|
+
InvalidNegativeValueError,
|
|
4
4
|
)
|
|
5
|
-
from {{ source_name }}.{{ template_domain_import }}.
|
|
5
|
+
from {{ source_name }}.{{ template_domain_import }}.value_object.value_object import ValueObject
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class IntValueObject(ValueObject[int]):
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
def __init__(self, value: int) -> None:
|
|
10
|
+
super().__init__(value)
|
|
11
|
+
|
|
12
|
+
def _validate(self, value: int) -> None:
|
|
13
|
+
if value < 0:
|
|
14
|
+
raise InvalidNegativeValueError(value)
|
|
@@ -5,7 +5,7 @@ from {{ source_name }}.{{ template_domain_import }}.exceptions.incorrect_value_t
|
|
|
5
5
|
from {{ source_name }}.{{ template_domain_import }}.exceptions.required_value_error import (
|
|
6
6
|
RequiredValueError,
|
|
7
7
|
)
|
|
8
|
-
from {{ source_name }}.{{ template_domain_import }}.
|
|
8
|
+
from {{ source_name }}.{{ template_domain_import }}.value_object.value_object import ValueObject
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class StringValueObject(ValueObject[str]):
|
|
@@ -4,7 +4,7 @@ from uuid import UUID
|
|
|
4
4
|
from {{ source_name }}.{{ template_domain_import }}.exceptions.required_value_error import (
|
|
5
5
|
RequiredValueError,
|
|
6
6
|
)
|
|
7
|
-
from {{ source_name }}.{{ template_domain_import }}.
|
|
7
|
+
from {{ source_name }}.{{ template_domain_import }}.value_object.value_object import ValueObject
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Uuid(ValueObject[str]):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
- name: persistence/async/alembic
|
|
2
|
-
type:
|
|
2
|
+
type: boilerplate_file
|
|
3
3
|
extension: .ini
|
|
4
4
|
- name: migrations
|
|
5
5
|
type: directory
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
- name: versions
|
|
8
8
|
type: directory
|
|
9
9
|
- name: persistence/async/README
|
|
10
|
-
type:
|
|
10
|
+
type: boilerplate_file
|
|
11
11
|
extension: .md
|
|
12
12
|
- name: persistence/async/env
|
|
13
|
-
type:
|
|
13
|
+
type: boilerplate_file
|
|
14
14
|
extension: .py
|
|
15
15
|
- name: persistence/async/models_metadata
|
|
16
|
-
type:
|
|
16
|
+
type: boilerplate_file
|
|
17
17
|
extension: .py
|
|
18
18
|
- name: persistence/async/script
|
|
19
|
-
type:
|
|
19
|
+
type: boilerplate_file
|
|
20
20
|
extension: .py.mako
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
python: True
|
|
8
8
|
children:
|
|
9
9
|
- name: persistence/base
|
|
10
|
-
type:
|
|
10
|
+
type: boilerplate_file
|
|
11
11
|
extension: .py
|
|
12
12
|
- name: persistence/async/sqlalchemy_repository
|
|
13
|
-
type:
|
|
13
|
+
type: boilerplate_file
|
|
14
14
|
extension: .py
|
|
15
15
|
- name: persistence/async/postgres_settings
|
|
16
|
-
type:
|
|
16
|
+
type: boilerplate_file
|
|
17
17
|
extension: .py
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
{% if "event_bus" in built_in_features %}
|
|
37
37
|
{{ macros.include_and_indent("project_structure/event_bus_infra.yml.j2", 8) }}
|
|
38
38
|
{% endif %}
|
|
39
|
-
{% if "logger"
|
|
39
|
+
{% if ["logger", "fastapi_application"] | is_in(built_in_features) %}
|
|
40
40
|
{{ macros.include_and_indent("project_structure/logger.yml.j2", 8) }}
|
|
41
41
|
{% endif %}
|
|
42
42
|
{% if "async_sqlalchemy" in built_in_features %}
|
|
43
43
|
{{ macros.include_and_indent("project_structure/async_sqlalchemy.yml.j2", 8) }}
|
|
44
44
|
{% endif %}
|
|
45
|
-
{% if "async_alembic"
|
|
45
|
+
{% if ["async_alembic", "fastapi_application"] | is_in(built_in_features) %}
|
|
46
46
|
{{ macros.include_and_indent("project_structure/alembic_migrator.yml.j2", 8) }}
|
|
47
47
|
{% endif %}
|
|
48
48
|
{% if "fastapi_application" in built_in_features %}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{% if "event_bus" in built_in_features %}
|
|
10
10
|
children:
|
|
11
11
|
- name: event_bus/mock_event_bus
|
|
12
|
-
type:
|
|
12
|
+
type: boilerplate_file
|
|
13
13
|
extension: .py
|
|
14
14
|
{% endif %}
|
|
15
15
|
- name: integration
|
|
@@ -19,5 +19,5 @@
|
|
|
19
19
|
type: directory
|
|
20
20
|
python: True
|
|
21
21
|
- name: random_generator
|
|
22
|
-
type:
|
|
22
|
+
type: boilerplate_file
|
|
23
23
|
extension: .py
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
{% if "event_bus" in built_in_features %}
|
|
39
39
|
{{ macros.include_and_indent("project_structure/event_bus_infra.yml.j2", 12) }}
|
|
40
40
|
{% endif %}
|
|
41
|
-
{% if "logger"
|
|
41
|
+
{% if ["logger", "fastapi_application"] | is_in(built_in_features) %}
|
|
42
42
|
{{ macros.include_and_indent("project_structure/logger.yml.j2", 12) }}
|
|
43
43
|
{% endif %}
|
|
44
44
|
{% if "async_sqlalchemy" in built_in_features %}
|
|
45
45
|
{{ macros.include_and_indent("project_structure/async_sqlalchemy.yml.j2", 12) }}
|
|
46
46
|
{% endif %}
|
|
47
|
-
{% if "async_alembic"
|
|
47
|
+
{% if ["async_alembic", "fastapi_application"] | is_in(built_in_features) %}
|
|
48
48
|
{{ macros.include_and_indent("project_structure/alembic_migrator.yml.j2", 12) }}
|
|
49
49
|
{% endif %}
|
|
50
50
|
{% if "fastapi_application" in built_in_features %}
|
|
@@ -52,4 +52,6 @@
|
|
|
52
52
|
{% endif %}
|
|
53
53
|
{% endif %}
|
|
54
54
|
{% endif %}
|
|
55
|
-
{
|
|
55
|
+
{% if specify_bounded_context %}
|
|
56
|
+
{{ macros.include_and_indent("project_structure/domain_driven_design/bounded_context.yml.j2", 4) }}
|
|
57
|
+
{% endif %}
|