instant-python 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. instant_python/__init__.py +0 -0
  2. instant_python/cli.py +11 -0
  3. instant_python/folder_cli.py +50 -0
  4. instant_python/installer/__init__.py +0 -0
  5. instant_python/installer/dependency_manager.py +15 -0
  6. instant_python/installer/dependency_manager_factory.py +14 -0
  7. instant_python/installer/git_configurer.py +47 -0
  8. instant_python/installer/installer.py +18 -0
  9. instant_python/installer/managers.py +7 -0
  10. instant_python/installer/operating_systems.py +7 -0
  11. instant_python/installer/pdm_manager.py +72 -0
  12. instant_python/installer/uv_manager.py +73 -0
  13. instant_python/project_cli.py +100 -0
  14. instant_python/project_generator/__init__.py +0 -0
  15. instant_python/project_generator/custom_template_manager.py +20 -0
  16. instant_python/project_generator/default_template_manager.py +45 -0
  17. instant_python/project_generator/directory.py +28 -0
  18. instant_python/project_generator/file.py +20 -0
  19. instant_python/project_generator/folder_tree.py +40 -0
  20. instant_python/project_generator/jinja_custom_filters.py +18 -0
  21. instant_python/project_generator/node.py +14 -0
  22. instant_python/project_generator/project_generator.py +31 -0
  23. instant_python/project_generator/template_manager.py +7 -0
  24. instant_python/question_prompter/__init__.py +0 -0
  25. instant_python/question_prompter/question/__init__.py +0 -0
  26. instant_python/question_prompter/question/boolean_question.py +13 -0
  27. instant_python/question_prompter/question/choice_question.py +18 -0
  28. instant_python/question_prompter/question/conditional_question.py +25 -0
  29. instant_python/question_prompter/question/dependencies_question.py +43 -0
  30. instant_python/question_prompter/question/free_text_question.py +13 -0
  31. instant_python/question_prompter/question/multiple_choice_question.py +13 -0
  32. instant_python/question_prompter/question/question.py +15 -0
  33. instant_python/question_prompter/question_wizard.py +15 -0
  34. instant_python/question_prompter/step/__init__.py +0 -0
  35. instant_python/question_prompter/step/dependencies_step.py +20 -0
  36. instant_python/question_prompter/step/general_custom_template_project_step.py +45 -0
  37. instant_python/question_prompter/step/general_project_step.py +50 -0
  38. instant_python/question_prompter/step/git_step.py +23 -0
  39. instant_python/question_prompter/step/steps.py +16 -0
  40. instant_python/question_prompter/step/template_step.py +63 -0
  41. instant_python/question_prompter/template_types.py +7 -0
  42. instant_python/question_prompter/user_requirements.py +39 -0
  43. instant_python/templates/__init__.py +0 -0
  44. instant_python/templates/boilerplate/.gitignore +164 -0
  45. instant_python/templates/boilerplate/.pre-commit-config.yml +33 -0
  46. instant_python/templates/boilerplate/.python-version +1 -0
  47. instant_python/templates/boilerplate/LICENSE +896 -0
  48. instant_python/templates/boilerplate/event_bus/__init__.py +0 -0
  49. instant_python/templates/boilerplate/event_bus/aggregate_root.py +19 -0
  50. instant_python/templates/boilerplate/event_bus/domain_event.py +15 -0
  51. instant_python/templates/boilerplate/event_bus/domain_event_json_deserializer.py +28 -0
  52. instant_python/templates/boilerplate/event_bus/domain_event_json_serializer.py +17 -0
  53. instant_python/templates/boilerplate/event_bus/domain_event_subscriber.py +15 -0
  54. instant_python/templates/boilerplate/event_bus/event_bus.py +10 -0
  55. instant_python/templates/boilerplate/event_bus/exchange_type.py +7 -0
  56. instant_python/templates/boilerplate/event_bus/mock_event_bus.py +18 -0
  57. instant_python/templates/boilerplate/event_bus/rabbit_mq_configurer.py +54 -0
  58. instant_python/templates/boilerplate/event_bus/rabbit_mq_connection.py +77 -0
  59. instant_python/templates/boilerplate/event_bus/rabbit_mq_consumer.py +58 -0
  60. instant_python/templates/boilerplate/event_bus/rabbit_mq_event_bus.py +28 -0
  61. instant_python/templates/boilerplate/event_bus/rabbit_mq_queue_formatter.py +22 -0
  62. instant_python/templates/boilerplate/event_bus/rabbit_mq_settings.py +8 -0
  63. instant_python/templates/boilerplate/exceptions/__init__.py +0 -0
  64. instant_python/templates/boilerplate/exceptions/domain_error.py +17 -0
  65. instant_python/templates/boilerplate/exceptions/domain_event_type_not_found_error.py +17 -0
  66. instant_python/templates/boilerplate/exceptions/incorrect_value_type_error.py +21 -0
  67. instant_python/templates/boilerplate/exceptions/invalid_id_format_error.py +17 -0
  68. instant_python/templates/boilerplate/exceptions/invalid_negative_value_error.py +17 -0
  69. instant_python/templates/boilerplate/exceptions/required_value_error.py +17 -0
  70. instant_python/templates/boilerplate/fastapi/__init__.py +0 -0
  71. instant_python/templates/boilerplate/fastapi/application.py +25 -0
  72. instant_python/templates/boilerplate/fastapi/http_response.py +45 -0
  73. instant_python/templates/boilerplate/fastapi/lifespan.py +14 -0
  74. instant_python/templates/boilerplate/fastapi/status_code.py +9 -0
  75. instant_python/templates/boilerplate/github/action.yml +22 -0
  76. instant_python/templates/boilerplate/github/test_lint.yml +36 -0
  77. instant_python/templates/boilerplate/logger/__init__.py +0 -0
  78. instant_python/templates/boilerplate/logger/json_formatter.py +16 -0
  79. instant_python/templates/boilerplate/logger/logger.py +39 -0
  80. instant_python/templates/boilerplate/mypy.ini +41 -0
  81. instant_python/templates/boilerplate/persistence/__init__.py +0 -0
  82. instant_python/templates/boilerplate/persistence/alembic_migrator.py +20 -0
  83. instant_python/templates/boilerplate/persistence/async/README.md +1 -0
  84. instant_python/templates/boilerplate/persistence/async/__init__.py +0 -0
  85. instant_python/templates/boilerplate/persistence/async/alembic.ini +124 -0
  86. instant_python/templates/boilerplate/persistence/async/async_engine_fixture.py +21 -0
  87. instant_python/templates/boilerplate/persistence/async/env.py +95 -0
  88. instant_python/templates/boilerplate/persistence/async/models_metadata.py +11 -0
  89. instant_python/templates/boilerplate/persistence/async/postgres_settings.py +15 -0
  90. instant_python/templates/boilerplate/persistence/async/script.py.mako +26 -0
  91. instant_python/templates/boilerplate/persistence/async/sqlalchemy_repository.py +30 -0
  92. instant_python/templates/boilerplate/persistence/base.py +4 -0
  93. instant_python/templates/boilerplate/persistence/synchronous/__init__.py +0 -0
  94. instant_python/templates/boilerplate/persistence/synchronous/session_maker.py +22 -0
  95. instant_python/templates/boilerplate/persistence/synchronous/sqlalchemy_repository.py +35 -0
  96. instant_python/templates/boilerplate/pyproject.toml +29 -0
  97. instant_python/templates/boilerplate/pytest.ini +10 -0
  98. instant_python/templates/boilerplate/random_generator.py +9 -0
  99. instant_python/templates/boilerplate/scripts/add_dependency.sh +37 -0
  100. instant_python/templates/boilerplate/scripts/create_aggregate.py +33 -0
  101. instant_python/templates/boilerplate/scripts/insert_template.py +90 -0
  102. instant_python/templates/boilerplate/scripts/integration.sh +39 -0
  103. instant_python/templates/boilerplate/scripts/local_setup.sh +15 -0
  104. instant_python/templates/boilerplate/scripts/makefile +137 -0
  105. instant_python/templates/boilerplate/scripts/post-merge +11 -0
  106. instant_python/templates/boilerplate/scripts/pre-commit +4 -0
  107. instant_python/templates/boilerplate/scripts/pre-push +6 -0
  108. instant_python/templates/boilerplate/scripts/remove_dependency.sh +36 -0
  109. instant_python/templates/boilerplate/scripts/unit.sh +40 -0
  110. instant_python/templates/boilerplate/value_object/__init__.py +0 -0
  111. instant_python/templates/boilerplate/value_object/int_value_object.py +11 -0
  112. instant_python/templates/boilerplate/value_object/string_value_object.py +19 -0
  113. instant_python/templates/boilerplate/value_object/uuid.py +17 -0
  114. instant_python/templates/boilerplate/value_object/value_object.py +21 -0
  115. instant_python/templates/project_structure/alembic_migrator.yml.j2 +3 -0
  116. instant_python/templates/project_structure/async_alembic.yml.j2 +20 -0
  117. instant_python/templates/project_structure/async_sqlalchemy.yml.j2 +17 -0
  118. instant_python/templates/project_structure/clean_architecture/main_structure.yml.j2 +25 -0
  119. instant_python/templates/project_structure/clean_architecture/source.yml.j2 +51 -0
  120. instant_python/templates/project_structure/clean_architecture/test.yml.j2 +23 -0
  121. instant_python/templates/project_structure/domain_driven_design/bounded_context.yml.j2 +20 -0
  122. instant_python/templates/project_structure/domain_driven_design/main_structure.yml.j2 +25 -0
  123. instant_python/templates/project_structure/domain_driven_design/source.yml.j2 +55 -0
  124. instant_python/templates/project_structure/domain_driven_design/test.yml.j2 +26 -0
  125. instant_python/templates/project_structure/event_bus_domain.yml.j2 +26 -0
  126. instant_python/templates/project_structure/event_bus_infra.yml.j2 +32 -0
  127. instant_python/templates/project_structure/fastapi_app.yml.j2 +10 -0
  128. instant_python/templates/project_structure/fastapi_infra.yml.j2 +10 -0
  129. instant_python/templates/project_structure/github_action.yml.j2 +18 -0
  130. instant_python/templates/project_structure/gitignore.yml.j2 +2 -0
  131. instant_python/templates/project_structure/license.yml.j2 +2 -0
  132. instant_python/templates/project_structure/logger.yml.j2 +10 -0
  133. instant_python/templates/project_structure/macros.j2 +6 -0
  134. instant_python/templates/project_structure/makefile.yml.j2 +38 -0
  135. instant_python/templates/project_structure/mypy.yml.j2 +3 -0
  136. instant_python/templates/project_structure/pre_commit.yml.j2 +3 -0
  137. instant_python/templates/project_structure/pyproject.yml.j2 +3 -0
  138. instant_python/templates/project_structure/pytest.yml.j2 +3 -0
  139. instant_python/templates/project_structure/python_version.yml.j2 +2 -0
  140. instant_python/templates/project_structure/standard_project/main_structure.yml.j2 +25 -0
  141. instant_python/templates/project_structure/standard_project/source.yml.j2 +30 -0
  142. instant_python/templates/project_structure/standard_project/test.yml.j2 +16 -0
  143. instant_python/templates/project_structure/synchronous_sqlalchemy.yml.j2 +17 -0
  144. instant_python/templates/project_structure/value_objects.yml.j2 +35 -0
  145. instant_python-0.0.1.dist-info/METADATA +276 -0
  146. instant_python-0.0.1.dist-info/RECORD +149 -0
  147. instant_python-0.0.1.dist-info/WHEEL +4 -0
  148. instant_python-0.0.1.dist-info/entry_points.txt +2 -0
  149. instant_python-0.0.1.dist-info/licenses/LICENSE +201 -0
File without changes
instant_python/cli.py ADDED
@@ -0,0 +1,11 @@
1
+ import typer
2
+
3
+ from instant_python import folder_cli, project_cli
4
+
5
+ app = typer.Typer()
6
+ app.add_typer(folder_cli.app, name="folder", help="Generate only the folder structure for a new project")
7
+ app.add_typer(project_cli.app, name="project", help="Generate a full project ready to be used")
8
+
9
+
10
+ if __name__ == "__main__":
11
+ app()
@@ -0,0 +1,50 @@
1
+ import typer
2
+
3
+ from instant_python.project_generator.custom_template_manager import CustomTemplateManager
4
+ from instant_python.project_generator.default_template_manager import DefaultTemplateManager
5
+ from instant_python.project_generator.folder_tree import FolderTree
6
+ from instant_python.project_generator.project_generator import ProjectGenerator
7
+ from instant_python.question_prompter.question.free_text_question import FreeTextQuestion
8
+ from instant_python.question_prompter.question_wizard import QuestionWizard
9
+ from instant_python.question_prompter.step.general_project_step import GeneralProjectStep
10
+ from instant_python.question_prompter.step.steps import Steps
11
+ from instant_python.question_prompter.step.template_step import TemplateStep
12
+
13
+ app = typer.Typer()
14
+
15
+
16
+ @app.command("template", help="Pass a custom template folder structure", hidden=True)
17
+ def create_folder_structure_from_template(template_name: str) -> None:
18
+ project_name = FreeTextQuestion(
19
+ key="project_slug",
20
+ message="Enter the name of the project (CANNOT CONTAIN SPACES)",
21
+ default="python-project",
22
+ ).ask()
23
+ project_generator = ProjectGenerator(
24
+ folder_tree=FolderTree(project_name["project_slug"]),
25
+ template_manager=CustomTemplateManager(template_name),
26
+ )
27
+
28
+ project_generator.generate()
29
+
30
+
31
+ @app.command("new", help="Use default built-in project structure templates")
32
+ def create_default_project_structure() -> None:
33
+ wizard = QuestionWizard(
34
+ steps=Steps(GeneralProjectStep(), TemplateStep())
35
+ )
36
+ user_requirements = wizard.run()
37
+ user_requirements.save_in_memory()
38
+
39
+ project_generator = ProjectGenerator(
40
+ folder_tree=FolderTree(user_requirements.project_slug),
41
+ template_manager=DefaultTemplateManager(),
42
+ )
43
+
44
+ project_generator.generate()
45
+
46
+ user_requirements.remove()
47
+
48
+
49
+ if __name__ == "__main__":
50
+ app()
File without changes
@@ -0,0 +1,15 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class DependencyManager(ABC):
5
+ @abstractmethod
6
+ def install(self) -> None:
7
+ raise NotImplementedError
8
+
9
+ @abstractmethod
10
+ def install_python(self, version: str) -> None:
11
+ raise NotImplementedError
12
+
13
+ @abstractmethod
14
+ def install_dependencies(self, dependencies: list[str]) -> None:
15
+ raise NotImplementedError
@@ -0,0 +1,14 @@
1
+ from instant_python.installer.dependency_manager import DependencyManager
2
+ from instant_python.installer.managers import Managers
3
+ from instant_python.installer.pdm_manager import PdmManager
4
+ from instant_python.installer.uv_manager import UvManager
5
+
6
+
7
+ class DependencyManagerFactory:
8
+ @staticmethod
9
+ def create(user_manager: str, project_path: str) -> DependencyManager:
10
+ managers = {
11
+ Managers.UV: UvManager,
12
+ Managers.PDM: PdmManager,
13
+ }
14
+ return managers[Managers(user_manager)](project_path)
@@ -0,0 +1,47 @@
1
+ import subprocess
2
+
3
+
4
+ class GitConfigurer:
5
+ def __init__(self, project_directory: str) -> None:
6
+ self._project_directory = project_directory
7
+
8
+ def configure(self, email: str, username: str) -> None:
9
+ self._initialize_repository()
10
+
11
+ if email and username:
12
+ self._set_user_information(email, username)
13
+
14
+ self._initial_commit()
15
+
16
+ def _initialize_repository(self) -> None:
17
+ print(">>> Initializing git repository...")
18
+ subprocess.run(
19
+ "git init",
20
+ shell=True,
21
+ check=True,
22
+ cwd=self._project_directory,
23
+ stdout=subprocess.DEVNULL,
24
+ )
25
+ print(">>> Git repository initialized successfully")
26
+
27
+ def _set_user_information(self, email: str | None, username: str | None) -> None:
28
+ print(">>> Configuring git user and email...")
29
+ subprocess.run(
30
+ f"git config user.name {username} && git config user.email {email}",
31
+ shell=True,
32
+ check=True,
33
+ cwd=self._project_directory,
34
+ stdout=subprocess.DEVNULL,
35
+ )
36
+ print(">>> Git user and email configured successfully")
37
+
38
+ def _initial_commit(self) -> None:
39
+ print(">>> Making initial commit...")
40
+ subprocess.run(
41
+ "git add . && git commit -m '🎉 chore: initial commit'",
42
+ shell=True,
43
+ check=True,
44
+ cwd=self._project_directory,
45
+ stdout=subprocess.DEVNULL,
46
+ )
47
+ print(">>> Initial commit made successfully")
@@ -0,0 +1,18 @@
1
+ from instant_python.installer.dependency_manager import DependencyManager
2
+
3
+
4
+ class Installer:
5
+ _dependency_manager: DependencyManager
6
+
7
+ def __init__(
8
+ self,
9
+ dependency_manager: DependencyManager,
10
+ ) -> None:
11
+ self._dependency_manager = dependency_manager
12
+
13
+ def perform_installation(
14
+ self, python_version: str, dependencies: list[str]
15
+ ) -> None:
16
+ self._dependency_manager.install()
17
+ self._dependency_manager.install_python(python_version)
18
+ self._dependency_manager.install_dependencies(dependencies)
@@ -0,0 +1,7 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class Managers(StrEnum):
5
+ PYENV = "pyenv"
6
+ UV = "uv"
7
+ PDM = "pdm"
@@ -0,0 +1,7 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class OperatingSystems(StrEnum):
5
+ WINDOWS = "windows"
6
+ MACOS = "macos"
7
+ LINUX = "linux"
@@ -0,0 +1,72 @@
1
+ import subprocess
2
+
3
+ from instant_python.installer.dependency_manager import DependencyManager
4
+ from instant_python.question_prompter.question.boolean_question import BooleanQuestion
5
+ from instant_python.question_prompter.question.free_text_question import FreeTextQuestion
6
+
7
+
8
+ class PdmManager(DependencyManager):
9
+ def __init__(self, project_directory: str) -> None:
10
+ self._project_directory = project_directory
11
+ self._pdm = "~/.local/bin/pdm"
12
+
13
+ def install(self) -> None:
14
+ print(">>> Installing pdm...")
15
+ subprocess.run(
16
+ "curl -sSL https://pdm-project.org/install-pdm.py | python3 -",
17
+ shell=True,
18
+ check=True,
19
+ )
20
+ print(">>> pdm installed successfully")
21
+
22
+ def install_python(self, version: str) -> None:
23
+ command = f"{self._pdm} python install {version}"
24
+ print(f">>> Installing Python {version}...")
25
+ subprocess.run(
26
+ command,
27
+ shell=True,
28
+ check=True,
29
+ cwd=self._project_directory,
30
+ )
31
+ print(f">>> Python {version} installed successfully")
32
+
33
+ def install_dependencies(self, dependencies: list[str]) -> None:
34
+ for dependency_name in dependencies:
35
+ self._install_dependency(dependency_name)
36
+
37
+ def _install_dependency(self, dependency_name: str) -> None:
38
+ is_dev = BooleanQuestion(
39
+ key="is_dev",
40
+ message=f"Do you want to install {dependency_name} as a dev dependency?",
41
+ default=False,
42
+ ).ask()["is_dev"]
43
+ add_to_group = BooleanQuestion(
44
+ key="add_to_group",
45
+ message=f"Do you want to install the {dependency_name} inside a group?",
46
+ default=False,
47
+ ).ask()["add_to_group"]
48
+
49
+ flag = self._generate_flag(add_to_group, is_dev)
50
+
51
+ command = f"{self._pdm} add {flag} {dependency_name}"
52
+ subprocess.run(
53
+ command,
54
+ shell=True,
55
+ check=True,
56
+ cwd=self._project_directory,
57
+ )
58
+
59
+ @staticmethod
60
+ def _generate_flag(add_to_group: bool, is_dev: bool) -> str:
61
+ dev_flag = "--dev" if is_dev else ""
62
+ group_flag = ""
63
+ if add_to_group:
64
+ group_name = FreeTextQuestion(
65
+ key="group_name", message="Enter the name of the group"
66
+ ).ask()["group_name"]
67
+ group_flag += f"--group {group_name}"
68
+ return f"{dev_flag} {group_flag}"
69
+
70
+
71
+
72
+
@@ -0,0 +1,73 @@
1
+ import subprocess
2
+
3
+ from instant_python.installer.dependency_manager import DependencyManager
4
+ from instant_python.question_prompter.question.boolean_question import BooleanQuestion
5
+ from instant_python.question_prompter.question.free_text_question import FreeTextQuestion
6
+
7
+
8
+ class UvManager(DependencyManager):
9
+ def __init__(self, project_directory: str) -> None:
10
+ self._project_directory = project_directory
11
+ self._uv = "~/.local/bin/uv"
12
+
13
+ def install(self) -> None:
14
+ print(">>> Installing uv...")
15
+ subprocess.run(
16
+ "curl -LsSf https://astral.sh/uv/install.sh | sh",
17
+ shell=True,
18
+ check=True,
19
+ stdout=subprocess.DEVNULL,
20
+ )
21
+ print(">>> uv installed successfully")
22
+
23
+ def install_python(self, version: str) -> None:
24
+ command = f"{self._uv} python install {version}"
25
+ print(f">>> Installing Python {version}...")
26
+ subprocess.run(
27
+ command,
28
+ shell=True,
29
+ check=True,
30
+ cwd=self._project_directory,
31
+ stdout=subprocess.DEVNULL,
32
+ )
33
+ print(f">>> Python {version} installed successfully")
34
+
35
+ def install_dependencies(self, dependencies: list[str]) -> None:
36
+ for dependency_name in dependencies:
37
+ self._install_dependency(dependency_name)
38
+
39
+ def _install_dependency(self, dependency_name: str) -> None:
40
+ is_dev = BooleanQuestion(
41
+ key="is_dev",
42
+ message=f"Do you want to install {dependency_name} as a dev dependency?",
43
+ default=False,
44
+ ).ask()["is_dev"]
45
+ add_to_group = BooleanQuestion(
46
+ key="add_to_group",
47
+ message=f"Do you want to install the {dependency_name} inside a group?",
48
+ default=False,
49
+ ).ask()["add_to_group"]
50
+
51
+ flag = self._generate_flag(add_to_group, is_dev)
52
+
53
+ command = f"{self._uv} add {flag} {dependency_name}"
54
+ subprocess.run(
55
+ command,
56
+ shell=True,
57
+ check=True,
58
+ cwd=self._project_directory,
59
+ stdout=subprocess.DEVNULL,
60
+ )
61
+ print(f">>> {dependency_name} installed successfully")
62
+
63
+ @staticmethod
64
+ def _generate_flag(add_to_group: bool, is_dev: bool) -> str:
65
+ flag = ""
66
+ if is_dev:
67
+ flag = "--dev"
68
+ if add_to_group:
69
+ group_name = FreeTextQuestion(
70
+ key="group_name", message="Enter the name of the group"
71
+ ).ask()["group_name"]
72
+ flag = f"--group {group_name}"
73
+ return flag
@@ -0,0 +1,100 @@
1
+ import typer
2
+
3
+ from instant_python.installer.dependency_manager_factory import DependencyManagerFactory
4
+ from instant_python.installer.git_configurer import GitConfigurer
5
+ from instant_python.installer.installer import Installer
6
+ from instant_python.project_generator.custom_template_manager import CustomTemplateManager
7
+ from instant_python.project_generator.default_template_manager import (
8
+ DefaultTemplateManager,
9
+ )
10
+ from instant_python.project_generator.folder_tree import FolderTree
11
+ from instant_python.project_generator.project_generator import ProjectGenerator
12
+ from instant_python.question_prompter.question_wizard import QuestionWizard
13
+ from instant_python.question_prompter.step.dependencies_step import DependenciesStep
14
+ from instant_python.question_prompter.step.general_custom_template_project_step import GeneralCustomTemplateProjectStep
15
+ from instant_python.question_prompter.step.general_project_step import (
16
+ GeneralProjectStep,
17
+ )
18
+ from instant_python.question_prompter.step.git_step import GitStep
19
+ from instant_python.question_prompter.step.steps import Steps
20
+ from instant_python.question_prompter.step.template_step import TemplateStep
21
+
22
+ app = typer.Typer()
23
+
24
+
25
+ @app.command("template", help="Pass a custom template folder structure", hidden=True)
26
+ def create_folder_structure_from_template(template_name: str) -> None:
27
+ wizard = QuestionWizard(steps=Steps(
28
+ GeneralCustomTemplateProjectStep(),
29
+ GitStep(),
30
+ DependenciesStep(),
31
+ ))
32
+ user_requirements = wizard.run()
33
+ user_requirements.save_in_memory()
34
+
35
+ project_generator = ProjectGenerator(
36
+ folder_tree=FolderTree(user_requirements.project_slug),
37
+ template_manager=CustomTemplateManager(template_name),
38
+ )
39
+ project_generator.generate()
40
+
41
+ installer = Installer(
42
+ dependency_manager=DependencyManagerFactory.create(
43
+ user_requirements.dependency_manager, project_generator.path
44
+ )
45
+ )
46
+ installer.perform_installation(
47
+ user_requirements.python_version, user_requirements.dependencies
48
+ )
49
+
50
+ if user_requirements.git:
51
+ git_configurer = GitConfigurer(project_generator.path)
52
+ git_configurer.configure(
53
+ user_requirements.git_email, user_requirements.git_user_name
54
+ )
55
+
56
+ user_requirements.remove()
57
+
58
+
59
+ @app.command(
60
+ "new",
61
+ help="Use default built-in template to create a new project",
62
+ )
63
+ def create_full_project() -> None:
64
+ wizard = QuestionWizard(
65
+ steps=Steps(
66
+ GeneralProjectStep(),
67
+ GitStep(),
68
+ TemplateStep(),
69
+ DependenciesStep(),
70
+ )
71
+ )
72
+ user_requirements = wizard.run()
73
+ user_requirements.save_in_memory()
74
+
75
+ project_generator = ProjectGenerator(
76
+ folder_tree=FolderTree(user_requirements.project_slug),
77
+ template_manager=DefaultTemplateManager(),
78
+ )
79
+ project_generator.generate()
80
+
81
+ installer = Installer(
82
+ dependency_manager=DependencyManagerFactory.create(
83
+ user_requirements.dependency_manager, project_generator.path
84
+ )
85
+ )
86
+ installer.perform_installation(
87
+ user_requirements.python_version, user_requirements.dependencies
88
+ )
89
+
90
+ if user_requirements.git:
91
+ git_configurer = GitConfigurer(project_generator.path)
92
+ git_configurer.configure(
93
+ user_requirements.git_email, user_requirements.git_user_name
94
+ )
95
+
96
+ user_requirements.remove()
97
+
98
+
99
+ if __name__ == "__main__":
100
+ app()
File without changes
@@ -0,0 +1,20 @@
1
+ from pathlib import Path
2
+ from typing import override
3
+
4
+ import yaml
5
+
6
+ from instant_python.project_generator.template_manager import TemplateManager
7
+
8
+
9
+ class CustomTemplateManager(TemplateManager):
10
+ def __init__(self, template_path: str) -> None:
11
+ self._template_path = Path(template_path).expanduser().resolve()
12
+
13
+ @override
14
+ def get_project(self, template_name: str) -> dict[str, str]:
15
+ if not self._template_path.is_file():
16
+ raise FileNotFoundError(
17
+ f"Could not find YAML file at: {self._template_path}"
18
+ )
19
+ with open(self._template_path, "r", encoding="utf-8") as f:
20
+ return yaml.safe_load(f)
@@ -0,0 +1,45 @@
1
+ from typing import override
2
+
3
+ import yaml
4
+ from jinja2 import Environment, Template, PackageLoader
5
+
6
+ from instant_python.project_generator.jinja_custom_filters import is_in, compute_base_path
7
+ from instant_python.project_generator.template_manager import TemplateManager
8
+ from instant_python.question_prompter.template_types import TemplateTypes
9
+ from instant_python.question_prompter.user_requirements import UserRequirements
10
+
11
+
12
+ class DefaultTemplateManager(TemplateManager):
13
+
14
+ def __init__(self) -> None:
15
+ self._requirements = self._load_memory_requirements()
16
+ self._env = Environment(loader=PackageLoader("instant_python",
17
+ "templates"),
18
+ trim_blocks=True,
19
+ lstrip_blocks=True)
20
+ self._env.filters["is_in"] = is_in
21
+ self._env.filters["compute_base_path"] = compute_base_path
22
+
23
+ @override
24
+ def get_project(self, template_name: str) -> dict:
25
+ template = self._get_template(
26
+ f"{template_name}/{self._requirements.template}/main_structure.yml.j2"
27
+ )
28
+ raw_project_structure = self._render(template)
29
+ return yaml.safe_load(raw_project_structure)
30
+
31
+ def get_boilerplate(self, template_name: str) -> str:
32
+ template = self._get_template(f"{template_name}")
33
+ return self._render(template)
34
+
35
+ def _get_template(self, name: str) -> Template:
36
+ return self._env.get_template(name)
37
+
38
+ def _render(self, template: Template) -> str:
39
+ return template.render(**self._requirements.to_dict(), template_types=TemplateTypes)
40
+
41
+ @staticmethod
42
+ def _load_memory_requirements() -> UserRequirements:
43
+ with open("user_requirements.yml") as file:
44
+ requirements = yaml.safe_load(file)
45
+ return UserRequirements(**requirements)
@@ -0,0 +1,28 @@
1
+ from pathlib import Path
2
+
3
+ from instant_python.project_generator.node import Node
4
+
5
+
6
+ class Directory(Node):
7
+ _INIT_FILE = "__init__.py"
8
+
9
+ def __init__(self, name: str, children: list[Node], python_module: bool) -> None:
10
+ self._name = name
11
+ self._python_module = python_module
12
+ self._children = children
13
+
14
+ def __repr__(self) -> str:
15
+ return (
16
+ f"{self.__class__.__name__}(name={self._name}, children={self._children})"
17
+ )
18
+
19
+ def create(self, base_path: Path) -> None:
20
+ directory_path = base_path / self._name
21
+ directory_path.mkdir(parents=True, exist_ok=True)
22
+
23
+ if self._python_module:
24
+ init_path = directory_path / self._INIT_FILE
25
+ init_path.touch(exist_ok=True)
26
+
27
+ for child in self._children:
28
+ child.create(base_path=directory_path)
@@ -0,0 +1,20 @@
1
+ from pathlib import Path
2
+
3
+ from instant_python.project_generator.default_template_manager import DefaultTemplateManager
4
+ from instant_python.project_generator.node import Node
5
+
6
+
7
+ class File(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 = DefaultTemplateManager()
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)
@@ -0,0 +1,40 @@
1
+ from pathlib import Path
2
+
3
+ from instant_python.project_generator.directory import Directory
4
+ from instant_python.project_generator.file import File
5
+ from instant_python.project_generator.node import Node, NodeType
6
+
7
+
8
+ class UnknownNodeType(ValueError):
9
+ def __init__(self, node_type: str) -> None:
10
+ self._message = f"Unknown node type: {node_type}"
11
+ super().__init__(self._message)
12
+
13
+
14
+ class FolderTree:
15
+ def __init__(self, project_directory: str) -> None:
16
+ self._project_directory = Path(project_directory)
17
+
18
+ def create(self, project_structure: dict) -> None:
19
+ tree = [self._build_tree(node) for node in project_structure.get("root", [])]
20
+ for node in tree:
21
+ node.create(base_path=self._project_directory)
22
+
23
+ @property
24
+ def project_directory(self) -> str:
25
+ return str(self._project_directory)
26
+
27
+ def _build_tree(self, node: dict) -> Node:
28
+ node_type = node.get("type")
29
+ name = node.get("name")
30
+
31
+ if node_type == NodeType.DIRECTORY:
32
+ children = node.get("children", [])
33
+ is_python_module = node.get("python", False)
34
+ directory_children = [self._build_tree(child) for child in children]
35
+ return Directory(name=name, children=directory_children, python_module=is_python_module)
36
+ elif node_type == NodeType.FILE:
37
+ extension = node.get("extension", "")
38
+ return File(name=name, extension=extension)
39
+ else:
40
+ raise UnknownNodeType(node_type)
@@ -0,0 +1,18 @@
1
+ from instant_python.question_prompter.template_types import TemplateTypes
2
+
3
+
4
+ def is_in(values: list[str], container: list) -> bool:
5
+ return any(value in container for value in values)
6
+
7
+
8
+ def compute_base_path(initial_path: str, template_type: str) -> str:
9
+ if template_type == TemplateTypes.DDD:
10
+ return initial_path
11
+
12
+ path_components = initial_path.split(".")
13
+ if template_type == TemplateTypes.CLEAN:
14
+ return ".".join(path_components[1:])
15
+ elif template_type == TemplateTypes.STANDARD:
16
+ return ".".join(path_components[2:])
17
+ else:
18
+ raise ValueError(f"Unknown template type: {template_type}")
@@ -0,0 +1,14 @@
1
+ from abc import ABC, abstractmethod
2
+ from enum import StrEnum
3
+ from pathlib import Path
4
+
5
+
6
+ class NodeType(StrEnum):
7
+ DIRECTORY = "directory"
8
+ FILE = "file"
9
+
10
+
11
+ class Node(ABC):
12
+ @abstractmethod
13
+ def create(self, base_path: Path) -> None:
14
+ raise NotImplementedError
@@ -0,0 +1,31 @@
1
+ import subprocess
2
+
3
+ from instant_python.project_generator.folder_tree import FolderTree
4
+ from instant_python.project_generator.default_template_manager import TemplateManager
5
+
6
+
7
+ class ProjectGenerator:
8
+
9
+ def __init__(self, folder_tree: FolderTree,
10
+ template_manager: TemplateManager) -> None:
11
+ self._folder_tree = folder_tree
12
+ self._template_manager = template_manager
13
+
14
+ def generate(self) -> None:
15
+ raw_project_structure = self._template_manager.get_project(
16
+ template_name="project_structure")
17
+ self._folder_tree.create(raw_project_structure)
18
+ self._format_project_files()
19
+
20
+ def _format_project_files(self) -> None:
21
+ subprocess.run(
22
+ "uvx ruff format",
23
+ shell=True,
24
+ check=True,
25
+ cwd=self._folder_tree.project_directory,
26
+ stdout=subprocess.DEVNULL,
27
+ )
28
+
29
+ @property
30
+ def path(self) -> str:
31
+ return self._folder_tree.project_directory
@@ -0,0 +1,7 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class TemplateManager(ABC):
5
+ @abstractmethod
6
+ def get_project(self, template_name: str) -> dict:
7
+ raise NotImplementedError
File without changes
File without changes