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.
- instant_python/__init__.py +0 -0
- instant_python/cli.py +11 -0
- instant_python/folder_cli.py +50 -0
- instant_python/installer/__init__.py +0 -0
- instant_python/installer/dependency_manager.py +15 -0
- instant_python/installer/dependency_manager_factory.py +14 -0
- instant_python/installer/git_configurer.py +47 -0
- instant_python/installer/installer.py +18 -0
- instant_python/installer/managers.py +7 -0
- instant_python/installer/operating_systems.py +7 -0
- instant_python/installer/pdm_manager.py +72 -0
- instant_python/installer/uv_manager.py +73 -0
- instant_python/project_cli.py +100 -0
- instant_python/project_generator/__init__.py +0 -0
- instant_python/project_generator/custom_template_manager.py +20 -0
- instant_python/project_generator/default_template_manager.py +45 -0
- instant_python/project_generator/directory.py +28 -0
- instant_python/project_generator/file.py +20 -0
- instant_python/project_generator/folder_tree.py +40 -0
- instant_python/project_generator/jinja_custom_filters.py +18 -0
- instant_python/project_generator/node.py +14 -0
- instant_python/project_generator/project_generator.py +31 -0
- instant_python/project_generator/template_manager.py +7 -0
- instant_python/question_prompter/__init__.py +0 -0
- instant_python/question_prompter/question/__init__.py +0 -0
- instant_python/question_prompter/question/boolean_question.py +13 -0
- instant_python/question_prompter/question/choice_question.py +18 -0
- instant_python/question_prompter/question/conditional_question.py +25 -0
- instant_python/question_prompter/question/dependencies_question.py +43 -0
- instant_python/question_prompter/question/free_text_question.py +13 -0
- instant_python/question_prompter/question/multiple_choice_question.py +13 -0
- instant_python/question_prompter/question/question.py +15 -0
- instant_python/question_prompter/question_wizard.py +15 -0
- instant_python/question_prompter/step/__init__.py +0 -0
- instant_python/question_prompter/step/dependencies_step.py +20 -0
- instant_python/question_prompter/step/general_custom_template_project_step.py +45 -0
- instant_python/question_prompter/step/general_project_step.py +50 -0
- instant_python/question_prompter/step/git_step.py +23 -0
- instant_python/question_prompter/step/steps.py +16 -0
- instant_python/question_prompter/step/template_step.py +63 -0
- instant_python/question_prompter/template_types.py +7 -0
- instant_python/question_prompter/user_requirements.py +39 -0
- instant_python/templates/__init__.py +0 -0
- instant_python/templates/boilerplate/.gitignore +164 -0
- instant_python/templates/boilerplate/.pre-commit-config.yml +33 -0
- instant_python/templates/boilerplate/.python-version +1 -0
- instant_python/templates/boilerplate/LICENSE +896 -0
- instant_python/templates/boilerplate/event_bus/__init__.py +0 -0
- instant_python/templates/boilerplate/event_bus/aggregate_root.py +19 -0
- instant_python/templates/boilerplate/event_bus/domain_event.py +15 -0
- instant_python/templates/boilerplate/event_bus/domain_event_json_deserializer.py +28 -0
- instant_python/templates/boilerplate/event_bus/domain_event_json_serializer.py +17 -0
- instant_python/templates/boilerplate/event_bus/domain_event_subscriber.py +15 -0
- instant_python/templates/boilerplate/event_bus/event_bus.py +10 -0
- instant_python/templates/boilerplate/event_bus/exchange_type.py +7 -0
- instant_python/templates/boilerplate/event_bus/mock_event_bus.py +18 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_configurer.py +54 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_connection.py +77 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_consumer.py +58 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_event_bus.py +28 -0
- instant_python/templates/boilerplate/event_bus/rabbit_mq_queue_formatter.py +22 -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/domain_error.py +17 -0
- instant_python/templates/boilerplate/exceptions/domain_event_type_not_found_error.py +17 -0
- instant_python/templates/boilerplate/exceptions/incorrect_value_type_error.py +21 -0
- instant_python/templates/boilerplate/exceptions/invalid_id_format_error.py +17 -0
- instant_python/templates/boilerplate/exceptions/invalid_negative_value_error.py +17 -0
- instant_python/templates/boilerplate/exceptions/required_value_error.py +17 -0
- instant_python/templates/boilerplate/fastapi/__init__.py +0 -0
- instant_python/templates/boilerplate/fastapi/application.py +25 -0
- instant_python/templates/boilerplate/fastapi/http_response.py +45 -0
- instant_python/templates/boilerplate/fastapi/lifespan.py +14 -0
- instant_python/templates/boilerplate/fastapi/status_code.py +9 -0
- instant_python/templates/boilerplate/github/action.yml +22 -0
- instant_python/templates/boilerplate/github/test_lint.yml +36 -0
- instant_python/templates/boilerplate/logger/__init__.py +0 -0
- instant_python/templates/boilerplate/logger/json_formatter.py +16 -0
- instant_python/templates/boilerplate/logger/logger.py +39 -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 +20 -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 +21 -0
- instant_python/templates/boilerplate/persistence/async/env.py +95 -0
- instant_python/templates/boilerplate/persistence/async/models_metadata.py +11 -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 +30 -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 +22 -0
- instant_python/templates/boilerplate/persistence/synchronous/sqlalchemy_repository.py +35 -0
- instant_python/templates/boilerplate/pyproject.toml +29 -0
- instant_python/templates/boilerplate/pytest.ini +10 -0
- instant_python/templates/boilerplate/random_generator.py +9 -0
- instant_python/templates/boilerplate/scripts/add_dependency.sh +37 -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.sh +15 -0
- instant_python/templates/boilerplate/scripts/makefile +137 -0
- instant_python/templates/boilerplate/scripts/post-merge +11 -0
- instant_python/templates/boilerplate/scripts/pre-commit +4 -0
- instant_python/templates/boilerplate/scripts/pre-push +6 -0
- instant_python/templates/boilerplate/scripts/remove_dependency.sh +36 -0
- instant_python/templates/boilerplate/scripts/unit.sh +40 -0
- instant_python/templates/boilerplate/value_object/__init__.py +0 -0
- instant_python/templates/boilerplate/value_object/int_value_object.py +11 -0
- instant_python/templates/boilerplate/value_object/string_value_object.py +19 -0
- instant_python/templates/boilerplate/value_object/uuid.py +17 -0
- instant_python/templates/boilerplate/value_object/value_object.py +21 -0
- instant_python/templates/project_structure/alembic_migrator.yml.j2 +3 -0
- instant_python/templates/project_structure/async_alembic.yml.j2 +20 -0
- instant_python/templates/project_structure/async_sqlalchemy.yml.j2 +17 -0
- instant_python/templates/project_structure/clean_architecture/main_structure.yml.j2 +25 -0
- instant_python/templates/project_structure/clean_architecture/source.yml.j2 +51 -0
- instant_python/templates/project_structure/clean_architecture/test.yml.j2 +23 -0
- instant_python/templates/project_structure/domain_driven_design/bounded_context.yml.j2 +20 -0
- instant_python/templates/project_structure/domain_driven_design/main_structure.yml.j2 +25 -0
- instant_python/templates/project_structure/domain_driven_design/source.yml.j2 +55 -0
- instant_python/templates/project_structure/domain_driven_design/test.yml.j2 +26 -0
- instant_python/templates/project_structure/event_bus_domain.yml.j2 +26 -0
- instant_python/templates/project_structure/event_bus_infra.yml.j2 +32 -0
- instant_python/templates/project_structure/fastapi_app.yml.j2 +10 -0
- instant_python/templates/project_structure/fastapi_infra.yml.j2 +10 -0
- instant_python/templates/project_structure/github_action.yml.j2 +18 -0
- instant_python/templates/project_structure/gitignore.yml.j2 +2 -0
- instant_python/templates/project_structure/license.yml.j2 +2 -0
- instant_python/templates/project_structure/logger.yml.j2 +10 -0
- instant_python/templates/project_structure/macros.j2 +6 -0
- instant_python/templates/project_structure/makefile.yml.j2 +38 -0
- instant_python/templates/project_structure/mypy.yml.j2 +3 -0
- instant_python/templates/project_structure/pre_commit.yml.j2 +3 -0
- instant_python/templates/project_structure/pyproject.yml.j2 +3 -0
- instant_python/templates/project_structure/pytest.yml.j2 +3 -0
- instant_python/templates/project_structure/python_version.yml.j2 +2 -0
- instant_python/templates/project_structure/standard_project/main_structure.yml.j2 +25 -0
- instant_python/templates/project_structure/standard_project/source.yml.j2 +30 -0
- instant_python/templates/project_structure/standard_project/test.yml.j2 +16 -0
- instant_python/templates/project_structure/synchronous_sqlalchemy.yml.j2 +17 -0
- instant_python/templates/project_structure/value_objects.yml.j2 +35 -0
- instant_python-0.0.1.dist-info/METADATA +276 -0
- instant_python-0.0.1.dist-info/RECORD +149 -0
- instant_python-0.0.1.dist-info/WHEEL +4 -0
- instant_python-0.0.1.dist-info/entry_points.txt +2 -0
- 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,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
|
|
File without changes
|
|
File without changes
|