instant-python 0.24.0__py3-none-any.whl → 0.24.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- instant_python/__init__.py +1 -1
- instant_python/cli/instant_python_typer.py +2 -2
- instant_python/config/application/config_generator.py +1 -1
- instant_python/config/delivery/cli.py +1 -1
- instant_python/config/infra/question_wizard/questionary_console_wizard.py +5 -1
- instant_python/config/infra/question_wizard/step/dependencies_step.py +24 -15
- instant_python/config/infra/question_wizard/step/general_step.py +12 -8
- instant_python/config/infra/question_wizard/step/git_step.py +10 -8
- instant_python/config/infra/question_wizard/step/questionary.py +2 -4
- instant_python/config/infra/question_wizard/step/steps.py +8 -0
- instant_python/config/infra/question_wizard/step/template_step.py +15 -10
- instant_python/initialize/application/project_initializer.py +1 -1
- instant_python/initialize/delivery/cli.py +1 -1
- instant_python/initialize/domain/project_renderer.py +2 -2
- instant_python/initialize/domain/project_structure.py +1 -1
- instant_python/initialize/infra/env_manager/pdm_env_manager.py +2 -2
- instant_python/initialize/infra/env_manager/system_console.py +2 -2
- instant_python/initialize/infra/env_manager/uv_env_manager.py +2 -4
- instant_python/initialize/infra/renderer/jinja_environment.py +7 -6
- instant_python/initialize/infra/renderer/jinja_project_renderer.py +1 -1
- instant_python/initialize/infra/version_control/git_configurer.py +1 -1
- instant_python/initialize/infra/writer/file_system_project_writer.py +1 -1
- instant_python/metrics/delivery/metrics_middleware.py +1 -1
- instant_python/metrics/domain/error_metrics_event.py +1 -1
- instant_python/metrics/domain/usage_metrics_data.py +1 -1
- instant_python/metrics/infra/post_hog_config.py +1 -2
- instant_python/metrics/infra/post_hog_metrics_reporter.py +2 -2
- instant_python/shared/domain/config_schema.py +8 -10
- instant_python/shared/domain/dependency_config.py +2 -2
- instant_python/shared/domain/general_config.py +24 -18
- instant_python/shared/domain/git_config.py +3 -4
- instant_python/shared/domain/template_config.py +5 -5
- instant_python/shared/infra/persistence/yaml_config_repository.py +3 -3
- instant_python/templates/boilerplate/.gitignore +1 -1
- instant_python/templates/boilerplate/.pre-commit-config.yaml +0 -2
- instant_python/templates/boilerplate/agents/AGENTS.md +61 -147
- instant_python/templates/boilerplate/agents/agents/convention_keeper.md +11 -8
- instant_python/templates/boilerplate/agents/agents/judge.md +38 -9
- instant_python/templates/boilerplate/agents/agents/{craftsman_leader.md → leader.md} +33 -22
- instant_python/templates/boilerplate/agents/agents/spec_partner.md +55 -23
- instant_python/templates/boilerplate/agents/agents/tdd_craftsman.md +32 -16
- instant_python/templates/boilerplate/agents/convention_guidelines.md +10 -1
- instant_python/templates/boilerplate/agents/leader_workflow.md +20 -31
- instant_python/templates/boilerplate/agents/skills/mutation_testing/SKILL.md +79 -605
- instant_python/templates/boilerplate/agents/spec_guidelines.md +80 -0
- instant_python/templates/boilerplate/github/ci.yml +1 -1
- instant_python/templates/boilerplate/pyproject.toml +6 -0
- instant_python/templates/boilerplate/scripts/makefile +7 -2
- instant_python/templates/project_structure/agents/agents.yml +31 -35
- {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/METADATA +2 -1
- {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/RECORD +56 -57
- {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/WHEEL +1 -1
- instant_python/templates/boilerplate/agents/skills/convention/SKILL.md +0 -32
- instant_python/templates/boilerplate/agents/skills/spec/SKILL.md +0 -57
- /instant_python/templates/boilerplate/agents/{commands/code_review.md → skills/code_review/SKILL.md} +0 -0
- /instant_python/templates/boilerplate/agents/{commands/security_review.md → skills/security_review/SKILL.md} +0 -0
- {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/entry_points.txt +0 -0
- {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/licenses/LICENSE +0 -0
instant_python/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.24.
|
|
1
|
+
__version__ = "0.24.2"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from collections.abc import Callable
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
4
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@ ErrorHandlingCallback = Callable[[Exception], None]
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class InstantPythonTyper(typer.Typer):
|
|
12
|
-
error_handlers: dict[ExceptionType, ErrorHandlingCallback] = {}
|
|
12
|
+
error_handlers: ClassVar[dict[ExceptionType, ErrorHandlingCallback]] = {}
|
|
13
13
|
|
|
14
14
|
def error_handler(self, exc: ExceptionType) -> Callable[[Callable[[Exception], None]], Callable[[Exception], None]]:
|
|
15
15
|
"""Registers a callback function to be called when 'exc' (the given exception) is raised."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
from instant_python.config.domain.question_wizard import QuestionWizard
|
|
1
2
|
from instant_python.shared.domain.config_repository import ConfigRepository
|
|
2
3
|
from instant_python.shared.domain.config_schema import ConfigSchema
|
|
3
|
-
from instant_python.config.domain.question_wizard import QuestionWizard
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ConfigGenerator:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
|
|
3
3
|
from instant_python.config.application.config_generator import ConfigGenerator
|
|
4
|
-
from instant_python.config.infra.question_wizard.step.questionary import Questionary
|
|
5
4
|
from instant_python.config.infra.question_wizard.questionary_console_wizard import QuestionaryConsoleWizard
|
|
5
|
+
from instant_python.config.infra.question_wizard.step.questionary import Questionary
|
|
6
6
|
from instant_python.shared.infra.persistence.yaml_config_repository import YamlConfigRepository
|
|
7
7
|
|
|
8
8
|
app = typer.Typer()
|
|
@@ -18,8 +18,12 @@ class QuestionaryConsoleWizard(QuestionWizard):
|
|
|
18
18
|
self._answers = {}
|
|
19
19
|
|
|
20
20
|
def run(self) -> dict:
|
|
21
|
-
for step in self._steps:
|
|
21
|
+
for position, step in enumerate(self._steps, start=1):
|
|
22
|
+
self._show_section_heading(position, step.title)
|
|
22
23
|
answer = step.run()
|
|
23
24
|
self._answers.update(answer)
|
|
24
25
|
|
|
25
26
|
return self._answers
|
|
27
|
+
|
|
28
|
+
def _show_section_heading(self, position: int, section_title: str) -> None:
|
|
29
|
+
print(f"[{position}/{len(self._steps)}] {section_title}")
|
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
1
|
from instant_python.config.infra.question_wizard.step.questionary import Questionary
|
|
4
2
|
from instant_python.config.infra.question_wizard.step.steps import Step
|
|
5
3
|
|
|
6
4
|
|
|
7
5
|
class DependenciesStep(Step):
|
|
8
6
|
_KEY = "dependencies"
|
|
7
|
+
_EMPTY_GROUP_NAME = ""
|
|
9
8
|
|
|
10
9
|
def __init__(self, questionary: Questionary) -> None:
|
|
11
10
|
super().__init__(questionary)
|
|
12
11
|
self._dependencies = []
|
|
12
|
+
self._is_first_dependency_prompt = True
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def title(self) -> str:
|
|
16
|
+
return "Dependencies"
|
|
13
17
|
|
|
14
|
-
def run(self) -> dict[str, list[dict[str,
|
|
18
|
+
def run(self) -> dict[str, list[dict[str, str | bool]]]:
|
|
15
19
|
while True:
|
|
16
20
|
if not self._user_wants_to_install_dependencies():
|
|
17
21
|
break
|
|
18
22
|
|
|
19
|
-
name = self.
|
|
20
|
-
if not name:
|
|
21
|
-
print("Dependency name cannot be empty. Let's try again.")
|
|
22
|
-
continue
|
|
23
|
+
name = self._ask_dependency_name_until_valid()
|
|
23
24
|
version = self._ask_dependency_version()
|
|
24
25
|
is_for_development = self._ask_if_dependency_is_for_development_purpose(name)
|
|
25
|
-
group_name = self._ask_dev_dependency_group_name() if is_for_development else
|
|
26
|
+
group_name = self._ask_dev_dependency_group_name() if is_for_development else self._EMPTY_GROUP_NAME
|
|
26
27
|
|
|
27
28
|
self._dependencies.append(
|
|
28
29
|
{
|
|
@@ -37,7 +38,7 @@ class DependenciesStep(Step):
|
|
|
37
38
|
|
|
38
39
|
def _ask_dev_dependency_group_name(self) -> str:
|
|
39
40
|
return self._questionary.free_text_question(
|
|
40
|
-
message="
|
|
41
|
+
message="Which group should the dependency be installed in?",
|
|
41
42
|
default="",
|
|
42
43
|
)
|
|
43
44
|
|
|
@@ -49,16 +50,24 @@ class DependenciesStep(Step):
|
|
|
49
50
|
|
|
50
51
|
def _ask_dependency_version(self) -> str:
|
|
51
52
|
return self._questionary.free_text_question(
|
|
52
|
-
message="
|
|
53
|
+
message="What is the version of the dependency?",
|
|
53
54
|
default="latest",
|
|
54
55
|
)
|
|
55
56
|
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
message="
|
|
59
|
-
|
|
57
|
+
def _ask_dependency_name_until_valid(self) -> str:
|
|
58
|
+
while True:
|
|
59
|
+
dependency_name = self._questionary.free_text_question(message="What is the name of the dependency?")
|
|
60
|
+
if dependency_name:
|
|
61
|
+
return dependency_name
|
|
62
|
+
print("Dependency name cannot be empty. Let's try again.")
|
|
60
63
|
|
|
61
64
|
def _user_wants_to_install_dependencies(self) -> bool:
|
|
65
|
+
message = (
|
|
66
|
+
"Do you want to add initial dependencies?"
|
|
67
|
+
if self._is_first_dependency_prompt
|
|
68
|
+
else "Do you want to add another dependency?"
|
|
69
|
+
)
|
|
70
|
+
self._is_first_dependency_prompt = False
|
|
62
71
|
return self._questionary.boolean_question(
|
|
63
|
-
message=
|
|
72
|
+
message=message,
|
|
64
73
|
)
|
|
@@ -12,6 +12,10 @@ class GeneralStep(Step):
|
|
|
12
12
|
super().__init__(questionary)
|
|
13
13
|
self._answers = {}
|
|
14
14
|
|
|
15
|
+
@property
|
|
16
|
+
def title(self) -> str:
|
|
17
|
+
return "General"
|
|
18
|
+
|
|
15
19
|
def run(self) -> dict[str, dict[str, str]]:
|
|
16
20
|
self._ask_project_slug()
|
|
17
21
|
self._ask_source_folder_name()
|
|
@@ -26,55 +30,55 @@ class GeneralStep(Step):
|
|
|
26
30
|
|
|
27
31
|
def _ask_project_slug(self) -> None:
|
|
28
32
|
answer = self._questionary.free_text_question(
|
|
29
|
-
message="
|
|
33
|
+
message="What is the project name?",
|
|
30
34
|
default="python-project",
|
|
31
35
|
)
|
|
32
36
|
self._answers["slug"] = answer
|
|
33
37
|
|
|
34
38
|
def _ask_source_folder_name(self) -> None:
|
|
35
39
|
answer = self._questionary.free_text_question(
|
|
36
|
-
message="
|
|
40
|
+
message="What is the source folder name?",
|
|
37
41
|
default="src",
|
|
38
42
|
)
|
|
39
43
|
self._answers["source_name"] = answer
|
|
40
44
|
|
|
41
45
|
def _ask_project_description(self) -> None:
|
|
42
46
|
answer = self._questionary.free_text_question(
|
|
43
|
-
message="
|
|
47
|
+
message="What is the project description?",
|
|
44
48
|
default="Python Project Description",
|
|
45
49
|
)
|
|
46
50
|
self._answers["description"] = answer
|
|
47
51
|
|
|
48
52
|
def _ask_project_version(self) -> None:
|
|
49
53
|
answer = self._questionary.free_text_question(
|
|
50
|
-
message="
|
|
54
|
+
message="What is the initial project version?",
|
|
51
55
|
default="0.1.0",
|
|
52
56
|
)
|
|
53
57
|
self._answers["version"] = answer
|
|
54
58
|
|
|
55
59
|
def _ask_author_name(self) -> None:
|
|
56
60
|
answer = self._questionary.free_text_question(
|
|
57
|
-
message="
|
|
61
|
+
message="What is your name?",
|
|
58
62
|
)
|
|
59
63
|
self._answers["author"] = answer
|
|
60
64
|
|
|
61
65
|
def _ask_license(self) -> None:
|
|
62
66
|
answer = self._questionary.single_choice_question(
|
|
63
|
-
message="
|
|
67
|
+
message="Which license do you want to use?",
|
|
64
68
|
options=SupportedLicenses.get_supported_licenses(),
|
|
65
69
|
)
|
|
66
70
|
self._answers["license"] = answer
|
|
67
71
|
|
|
68
72
|
def _ask_python_version(self) -> None:
|
|
69
73
|
answer = self._questionary.single_choice_question(
|
|
70
|
-
message="
|
|
74
|
+
message="Which Python version do you want to use?",
|
|
71
75
|
options=SupportedPythonVersions.get_supported_versions(),
|
|
72
76
|
)
|
|
73
77
|
self._answers["python_version"] = answer
|
|
74
78
|
|
|
75
79
|
def _ask_dependency_manager(self) -> None:
|
|
76
80
|
answer = self._questionary.single_choice_question(
|
|
77
|
-
message="
|
|
81
|
+
message="Which dependency manager do you want to use?",
|
|
78
82
|
options=SupportedManagers.get_supported_managers(),
|
|
79
83
|
default=SupportedManagers.UV,
|
|
80
84
|
)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
1
|
from instant_python.config.infra.question_wizard.step.questionary import Questionary
|
|
4
2
|
from instant_python.config.infra.question_wizard.step.steps import Step
|
|
5
3
|
|
|
@@ -11,8 +9,12 @@ class GitStep(Step):
|
|
|
11
9
|
super().__init__(questionary)
|
|
12
10
|
self._answers = {}
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
@property
|
|
13
|
+
def title(self) -> str:
|
|
14
|
+
return "Git"
|
|
15
|
+
|
|
16
|
+
def run(self) -> dict[str, dict[str, str | bool]]:
|
|
17
|
+
if self._git_repository_initialization_is_skipped():
|
|
16
18
|
return {self._KEY: self._answers}
|
|
17
19
|
|
|
18
20
|
self._ask_git_username()
|
|
@@ -22,20 +24,20 @@ class GitStep(Step):
|
|
|
22
24
|
|
|
23
25
|
def _ask_git_email(self) -> None:
|
|
24
26
|
answer = self._questionary.free_text_question(
|
|
25
|
-
message="
|
|
27
|
+
message="What is your Git email?",
|
|
26
28
|
)
|
|
27
29
|
self._answers["email"] = answer
|
|
28
30
|
|
|
29
31
|
def _ask_git_username(self) -> None:
|
|
30
32
|
answer = self._questionary.free_text_question(
|
|
31
|
-
message="
|
|
33
|
+
message="What is your Git user name?",
|
|
32
34
|
)
|
|
33
35
|
self._answers["username"] = answer
|
|
34
36
|
|
|
35
|
-
def
|
|
37
|
+
def _git_repository_initialization_is_skipped(self) -> bool:
|
|
36
38
|
answer = self._questionary.boolean_question(
|
|
37
39
|
message="Do you want to initialize a git repository?",
|
|
38
40
|
default=True,
|
|
39
41
|
)
|
|
40
42
|
self._answers["initialize"] = answer
|
|
41
|
-
return answer
|
|
43
|
+
return not answer
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Optional
|
|
2
|
-
|
|
3
1
|
import questionary
|
|
4
2
|
|
|
5
3
|
|
|
@@ -7,10 +5,10 @@ class Questionary:
|
|
|
7
5
|
def boolean_question(self, message: str, default: bool = False) -> bool:
|
|
8
6
|
return questionary.confirm(message, default=default).ask()
|
|
9
7
|
|
|
10
|
-
def free_text_question(self, message: str, default:
|
|
8
|
+
def free_text_question(self, message: str, default: str | None = None) -> str:
|
|
11
9
|
return questionary.text(message, default=default if default else "").ask()
|
|
12
10
|
|
|
13
|
-
def single_choice_question(self, message: str, options: list[str], default:
|
|
11
|
+
def single_choice_question(self, message: str, options: list[str], default: str | None = None) -> str:
|
|
14
12
|
return questionary.select(message, choices=options, default=default).ask()
|
|
15
13
|
|
|
16
14
|
def multiselect_question(self, message: str, options: list[str]) -> list[str]:
|
|
@@ -8,6 +8,11 @@ class Step(ABC):
|
|
|
8
8
|
def __init__(self, questionary: Questionary) -> None:
|
|
9
9
|
self._questionary = questionary
|
|
10
10
|
|
|
11
|
+
@property
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def title(self) -> str:
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
11
16
|
@abstractmethod
|
|
12
17
|
def run(self) -> dict[str, dict]:
|
|
13
18
|
raise NotImplementedError
|
|
@@ -19,3 +24,6 @@ class Steps:
|
|
|
19
24
|
|
|
20
25
|
def __iter__(self) -> Iterator[Step]:
|
|
21
26
|
return iter(self._steps)
|
|
27
|
+
|
|
28
|
+
def __len__(self) -> int:
|
|
29
|
+
return len(self._steps)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Union
|
|
2
|
-
|
|
3
1
|
from instant_python.config.infra.question_wizard.step.questionary import Questionary
|
|
4
2
|
from instant_python.config.infra.question_wizard.step.steps import Step
|
|
5
3
|
from instant_python.shared.supported_built_in_features import SupportedBuiltInFeatures
|
|
@@ -13,36 +11,43 @@ class TemplateStep(Step):
|
|
|
13
11
|
super().__init__(questionary)
|
|
14
12
|
self._answers = {}
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
@property
|
|
15
|
+
def title(self) -> str:
|
|
16
|
+
return "Template"
|
|
17
|
+
|
|
18
|
+
def run(self) -> dict[str, dict[str, str | list[str]]]:
|
|
17
19
|
name = self._choose_template_name_from_options()
|
|
18
20
|
|
|
21
|
+
if name == SupportedTemplates.CUSTOM:
|
|
22
|
+
return {self._KEY: self._answers}
|
|
23
|
+
|
|
19
24
|
if name == SupportedTemplates.DDD and self._user_wants_to_specify_bounded_context():
|
|
20
25
|
self._ask_bounded_context_name()
|
|
21
26
|
self._ask_aggregate_name()
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
self._select_built_in_features()
|
|
28
|
+
self._select_built_in_features()
|
|
25
29
|
|
|
26
30
|
return {self._KEY: self._answers}
|
|
27
31
|
|
|
28
32
|
def _choose_template_name_from_options(self) -> str:
|
|
29
33
|
answer = self._questionary.single_choice_question(
|
|
30
|
-
message="
|
|
34
|
+
message="Which template do you want to use?",
|
|
31
35
|
options=SupportedTemplates.get_supported_templates(),
|
|
32
36
|
)
|
|
33
37
|
self._answers["name"] = answer
|
|
34
38
|
return answer
|
|
35
39
|
|
|
36
|
-
def _user_wants_to_specify_bounded_context(self) ->
|
|
40
|
+
def _user_wants_to_specify_bounded_context(self) -> bool:
|
|
37
41
|
answer = self._questionary.boolean_question(
|
|
38
42
|
message="Do you want to specify your first bounded context?",
|
|
39
43
|
default=True,
|
|
40
44
|
)
|
|
41
45
|
self._answers["specify_bounded_context"] = answer
|
|
46
|
+
return answer
|
|
42
47
|
|
|
43
48
|
def _ask_bounded_context_name(self) -> str:
|
|
44
49
|
answer = self._questionary.free_text_question(
|
|
45
|
-
message="
|
|
50
|
+
message="What is the bounded context name?",
|
|
46
51
|
default="backoffice",
|
|
47
52
|
)
|
|
48
53
|
self._answers["bounded_context"] = answer
|
|
@@ -50,14 +55,14 @@ class TemplateStep(Step):
|
|
|
50
55
|
|
|
51
56
|
def _ask_aggregate_name(self) -> None:
|
|
52
57
|
answer = self._questionary.free_text_question(
|
|
53
|
-
message="
|
|
58
|
+
message="What is the aggregate name?",
|
|
54
59
|
default="user",
|
|
55
60
|
)
|
|
56
61
|
self._answers["aggregate_name"] = answer
|
|
57
62
|
|
|
58
63
|
def _select_built_in_features(self) -> None:
|
|
59
64
|
answer = self._questionary.multiselect_question(
|
|
60
|
-
message="
|
|
65
|
+
message="Which built-in features do you want to include?",
|
|
61
66
|
options=SupportedBuiltInFeatures.get_supported_built_in_features(),
|
|
62
67
|
)
|
|
63
68
|
self._answers["built_in_features"] = answer
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
|
-
from instant_python.shared.domain.config_schema import ConfigSchema
|
|
4
3
|
from instant_python.initialize.domain.env_manager import EnvManager
|
|
5
4
|
from instant_python.initialize.domain.project_formatter import ProjectFormatter
|
|
6
5
|
from instant_python.initialize.domain.project_renderer import ProjectRenderer
|
|
7
6
|
from instant_python.initialize.domain.project_writer import ProjectWriter
|
|
8
7
|
from instant_python.initialize.domain.version_control_configurer import VersionControlConfigurer
|
|
8
|
+
from instant_python.shared.domain.config_schema import ConfigSchema
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class ProjectInitializer:
|
|
@@ -6,11 +6,11 @@ from instant_python.initialize.application.project_initializer import ProjectIni
|
|
|
6
6
|
from instant_python.initialize.infra.env_manager.env_manager_factory import EnvManagerFactory
|
|
7
7
|
from instant_python.initialize.infra.env_manager.system_console import SystemConsole
|
|
8
8
|
from instant_python.initialize.infra.formatter.ruff_project_formatter import RuffProjectFormatter
|
|
9
|
-
from instant_python.shared.infra.persistence.yaml_config_repository import YamlConfigRepository
|
|
10
9
|
from instant_python.initialize.infra.renderer.jinja_environment import JinjaEnvironment
|
|
11
10
|
from instant_python.initialize.infra.renderer.jinja_project_renderer import JinjaProjectRenderer
|
|
12
11
|
from instant_python.initialize.infra.version_control.git_configurer import GitConfigurer
|
|
13
12
|
from instant_python.initialize.infra.writer.file_system_project_writer import FileSystemProjectWriter
|
|
13
|
+
from instant_python.shared.infra.persistence.yaml_config_repository import YamlConfigRepository
|
|
14
14
|
|
|
15
15
|
app = typer.Typer()
|
|
16
16
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from abc import
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
2
|
|
|
3
|
-
from instant_python.shared.domain.config_schema import ConfigSchema
|
|
4
3
|
from instant_python.initialize.domain.project_structure import ProjectStructure
|
|
4
|
+
from instant_python.shared.domain.config_schema import ConfigSchema
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ProjectRenderer(ABC):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from collections.abc import Iterator
|
|
2
2
|
|
|
3
|
-
from instant_python.initialize.domain.node import
|
|
3
|
+
from instant_python.initialize.domain.node import Directory, File, Node, NodeType
|
|
4
4
|
from instant_python.shared.application_error import ApplicationError
|
|
5
5
|
|
|
6
6
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from instant_python.shared.domain.dependency_config import DependencyConfig
|
|
5
4
|
from instant_python.initialize.domain.env_manager import EnvManager
|
|
6
5
|
from instant_python.initialize.infra.env_manager.system_console import SystemConsole
|
|
6
|
+
from instant_python.shared.domain.dependency_config import DependencyConfig
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class PdmEnvManager(EnvManager):
|
|
@@ -29,7 +29,7 @@ class PdmEnvManager(EnvManager):
|
|
|
29
29
|
|
|
30
30
|
def _set_pdm_executable_based_on_os(self):
|
|
31
31
|
return (
|
|
32
|
-
f"{
|
|
32
|
+
f"{Path.home() / 'AppData' / 'Roaming' / 'Python' / 'Scripts' / 'pdm.exe'!s}"
|
|
33
33
|
if self._system_os.startswith("win")
|
|
34
34
|
else "~/.local/bin/pdm"
|
|
35
35
|
)
|
|
@@ -21,7 +21,7 @@ class SystemConsole:
|
|
|
21
21
|
def execute(self, command: str) -> CommandExecutionResult:
|
|
22
22
|
try:
|
|
23
23
|
return self._run_command(command)
|
|
24
|
-
except Exception as error:
|
|
24
|
+
except Exception as error: # noqa: BLE001
|
|
25
25
|
return self._unexpected_error_result(error)
|
|
26
26
|
|
|
27
27
|
def execute_or_raise(self, command: str) -> CommandExecutionResult:
|
|
@@ -58,7 +58,7 @@ class SystemConsole:
|
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
class CommandExecutionError(ApplicationError):
|
|
61
|
-
def __init__(self, exit_code: int, stderr_output: str = None) -> None:
|
|
61
|
+
def __init__(self, exit_code: int, stderr_output: str | None = None) -> None:
|
|
62
62
|
message = f"Unexpected error when executing a command, exit code {exit_code}"
|
|
63
63
|
if stderr_output:
|
|
64
64
|
message += f": {stderr_output}"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
|
-
from instant_python.shared.domain.dependency_config import DependencyConfig
|
|
5
4
|
from instant_python.initialize.domain.env_manager import EnvManager
|
|
6
5
|
from instant_python.initialize.infra.env_manager.system_console import SystemConsole
|
|
6
|
+
from instant_python.shared.domain.dependency_config import DependencyConfig
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class UvEnvManager(EnvManager):
|
|
@@ -39,9 +39,7 @@ class UvEnvManager(EnvManager):
|
|
|
39
39
|
|
|
40
40
|
def _set_uv_executable_based_on_os(self):
|
|
41
41
|
return (
|
|
42
|
-
f"{
|
|
43
|
-
if self._system_os.startswith("win")
|
|
44
|
-
else "~/.local/bin/uv"
|
|
42
|
+
f"{Path.home() / '.local' / 'bin' / 'uv.exe'!s}" if self._system_os.startswith("win") else "~/.local/bin/uv"
|
|
45
43
|
)
|
|
46
44
|
|
|
47
45
|
def _install_python(self, version: str) -> None:
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
from collections.abc import Callable
|
|
2
|
-
from typing import Any
|
|
2
|
+
from typing import Any, ClassVar
|
|
3
|
+
|
|
4
|
+
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PackageLoader
|
|
3
5
|
|
|
4
6
|
from instant_python.shared.application_error import ApplicationError
|
|
5
7
|
from instant_python.shared.supported_templates import SupportedTemplates
|
|
6
|
-
from jinja2 import Environment, FileSystemLoader, ChoiceLoader, PackageLoader
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class JinjaEnvironment:
|
|
10
|
-
_EMPTY_CONTEXT = {}
|
|
11
|
-
_BASE_PACKAGE_NAME = "instant_python"
|
|
12
|
-
_PROJECT_STRUCTURE_TEMPLATE_PATH = "templates/project_structure"
|
|
13
|
-
_BOILERPLATE_TEMPLATE_PATH = "templates/boilerplate"
|
|
11
|
+
_EMPTY_CONTEXT: ClassVar[dict] = {}
|
|
12
|
+
_BASE_PACKAGE_NAME: ClassVar[str] = "instant_python"
|
|
13
|
+
_PROJECT_STRUCTURE_TEMPLATE_PATH: ClassVar[str] = "templates/project_structure"
|
|
14
|
+
_BOILERPLATE_TEMPLATE_PATH: ClassVar[str] = "templates/boilerplate"
|
|
14
15
|
|
|
15
16
|
def __init__(self, user_template_path: str | None = None) -> None:
|
|
16
17
|
self._env = Environment(
|
|
@@ -3,11 +3,11 @@ from pathlib import Path
|
|
|
3
3
|
import yaml
|
|
4
4
|
from jinja2 import TemplateNotFound
|
|
5
5
|
|
|
6
|
-
from instant_python.shared.domain.config_schema import ConfigSchema
|
|
7
6
|
from instant_python.initialize.domain.node import NodeType
|
|
8
7
|
from instant_python.initialize.domain.project_renderer import ProjectRenderer
|
|
9
8
|
from instant_python.initialize.domain.project_structure import ProjectStructure
|
|
10
9
|
from instant_python.initialize.infra.renderer.jinja_environment import JinjaEnvironment
|
|
10
|
+
from instant_python.shared.domain.config_schema import ConfigSchema
|
|
11
11
|
from instant_python.shared.supported_templates import SupportedTemplates
|
|
12
12
|
|
|
13
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from instant_python.shared.domain.git_config import GitConfig
|
|
2
1
|
from instant_python.initialize.domain.version_control_configurer import VersionControlConfigurer
|
|
3
2
|
from instant_python.initialize.infra.env_manager.system_console import SystemConsole
|
|
3
|
+
from instant_python.shared.domain.git_config import GitConfig
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class GitConfigurer(VersionControlConfigurer):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
|
|
3
3
|
from instant_python.initialize.domain.project_structure import ProjectStructure
|
|
4
|
-
from instant_python.initialize.domain.project_writer import
|
|
4
|
+
from instant_python.initialize.domain.project_writer import NodeWriter, ProjectWriter
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class FileSystemNodeWriter(NodeWriter):
|
|
@@ -32,7 +32,7 @@ class MetricsMiddleware(TyperGroup):
|
|
|
32
32
|
self._execute_command(ctx)
|
|
33
33
|
except Exception as exception:
|
|
34
34
|
self._send_error_metrics(ctx, exception)
|
|
35
|
-
raise
|
|
35
|
+
raise
|
|
36
36
|
self._send_success_metrics(config_path, config_snapshot, ctx)
|
|
37
37
|
|
|
38
38
|
def _send_success_metrics(self, config_path: Path, config_snapshot: ConfigSnapshot, ctx: Context) -> None:
|
|
@@ -28,7 +28,7 @@ class PostHogMetricsReporter(MetricsReporter):
|
|
|
28
28
|
properties=metrics.to_primitives(),
|
|
29
29
|
)
|
|
30
30
|
self._client.flush()
|
|
31
|
-
except Exception:
|
|
31
|
+
except Exception: # noqa: BLE001, S110
|
|
32
32
|
pass # Fire and forget strategy to avoid impacting user experience
|
|
33
33
|
|
|
34
34
|
def send_error(self, error: Exception, metrics: ErrorMetricsEvent) -> None:
|
|
@@ -42,5 +42,5 @@ class PostHogMetricsReporter(MetricsReporter):
|
|
|
42
42
|
properties=metrics.to_primitives(),
|
|
43
43
|
)
|
|
44
44
|
self._client.flush()
|
|
45
|
-
except Exception:
|
|
45
|
+
except Exception: # noqa: BLE001, S110
|
|
46
46
|
pass # Fire and forget strategy to avoid impacting user experience
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import TypedDict
|
|
3
|
+
from typing import TypedDict
|
|
4
4
|
|
|
5
|
+
from instant_python.shared.application_error import ApplicationError
|
|
5
6
|
from instant_python.shared.domain.dependency_config import (
|
|
6
7
|
DependencyConfig,
|
|
7
8
|
)
|
|
@@ -12,7 +13,6 @@ from instant_python.shared.domain.git_config import GitConfig
|
|
|
12
13
|
from instant_python.shared.domain.template_config import (
|
|
13
14
|
TemplateConfig,
|
|
14
15
|
)
|
|
15
|
-
from instant_python.shared.application_error import ApplicationError
|
|
16
16
|
from instant_python.shared.supported_templates import SupportedTemplates
|
|
17
17
|
|
|
18
18
|
_GENERAL = "general"
|
|
@@ -33,9 +33,7 @@ class ConfigSchema:
|
|
|
33
33
|
_DEFAULT_CONFIG_PATH: Path = Path.cwd() / "ipy.yml"
|
|
34
34
|
|
|
35
35
|
@classmethod
|
|
36
|
-
def from_primitives(
|
|
37
|
-
cls, content: dict[str, Union[dict, list]], custom_config_path: Union[Path, None] = None
|
|
38
|
-
) -> "ConfigSchema":
|
|
36
|
+
def from_primitives(cls, content: dict[str, dict | list], custom_config_path: Path | None = None) -> "ConfigSchema":
|
|
39
37
|
cls._ensure_config_is_not_empty(content)
|
|
40
38
|
cls._ensure_all_required_sections_are_present(content)
|
|
41
39
|
return cls(
|
|
@@ -47,12 +45,12 @@ class ConfigSchema:
|
|
|
47
45
|
)
|
|
48
46
|
|
|
49
47
|
@classmethod
|
|
50
|
-
def _ensure_config_is_not_empty(cls, content: dict[str,
|
|
48
|
+
def _ensure_config_is_not_empty(cls, content: dict[str, dict | list]) -> None:
|
|
51
49
|
if not content:
|
|
52
50
|
raise EmptyConfigurationNotAllowed
|
|
53
51
|
|
|
54
52
|
@classmethod
|
|
55
|
-
def _ensure_all_required_sections_are_present(cls, content: dict[str,
|
|
53
|
+
def _ensure_all_required_sections_are_present(cls, content: dict[str, dict | list]):
|
|
56
54
|
missing_keys = [key for key in _REQUIRED_CONFIG_KEYS if key not in content]
|
|
57
55
|
if missing_keys:
|
|
58
56
|
raise ConfigKeyNotPresent(missing_keys, _REQUIRED_CONFIG_KEYS)
|
|
@@ -104,9 +102,9 @@ class ConfigSchema:
|
|
|
104
102
|
|
|
105
103
|
class ConfigSchemaPrimitives(TypedDict):
|
|
106
104
|
general: dict[str, str]
|
|
107
|
-
dependencies: list[dict[str,
|
|
108
|
-
template: dict[str,
|
|
109
|
-
git: dict[str,
|
|
105
|
+
dependencies: list[dict[str, str | bool]]
|
|
106
|
+
template: dict[str, str | list[str]]
|
|
107
|
+
git: dict[str, str | bool]
|
|
110
108
|
|
|
111
109
|
|
|
112
110
|
class ConfigKeyNotPresent(ApplicationError):
|