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.
Files changed (58) hide show
  1. instant_python/__init__.py +1 -1
  2. instant_python/cli/instant_python_typer.py +2 -2
  3. instant_python/config/application/config_generator.py +1 -1
  4. instant_python/config/delivery/cli.py +1 -1
  5. instant_python/config/infra/question_wizard/questionary_console_wizard.py +5 -1
  6. instant_python/config/infra/question_wizard/step/dependencies_step.py +24 -15
  7. instant_python/config/infra/question_wizard/step/general_step.py +12 -8
  8. instant_python/config/infra/question_wizard/step/git_step.py +10 -8
  9. instant_python/config/infra/question_wizard/step/questionary.py +2 -4
  10. instant_python/config/infra/question_wizard/step/steps.py +8 -0
  11. instant_python/config/infra/question_wizard/step/template_step.py +15 -10
  12. instant_python/initialize/application/project_initializer.py +1 -1
  13. instant_python/initialize/delivery/cli.py +1 -1
  14. instant_python/initialize/domain/project_renderer.py +2 -2
  15. instant_python/initialize/domain/project_structure.py +1 -1
  16. instant_python/initialize/infra/env_manager/pdm_env_manager.py +2 -2
  17. instant_python/initialize/infra/env_manager/system_console.py +2 -2
  18. instant_python/initialize/infra/env_manager/uv_env_manager.py +2 -4
  19. instant_python/initialize/infra/renderer/jinja_environment.py +7 -6
  20. instant_python/initialize/infra/renderer/jinja_project_renderer.py +1 -1
  21. instant_python/initialize/infra/version_control/git_configurer.py +1 -1
  22. instant_python/initialize/infra/writer/file_system_project_writer.py +1 -1
  23. instant_python/metrics/delivery/metrics_middleware.py +1 -1
  24. instant_python/metrics/domain/error_metrics_event.py +1 -1
  25. instant_python/metrics/domain/usage_metrics_data.py +1 -1
  26. instant_python/metrics/infra/post_hog_config.py +1 -2
  27. instant_python/metrics/infra/post_hog_metrics_reporter.py +2 -2
  28. instant_python/shared/domain/config_schema.py +8 -10
  29. instant_python/shared/domain/dependency_config.py +2 -2
  30. instant_python/shared/domain/general_config.py +24 -18
  31. instant_python/shared/domain/git_config.py +3 -4
  32. instant_python/shared/domain/template_config.py +5 -5
  33. instant_python/shared/infra/persistence/yaml_config_repository.py +3 -3
  34. instant_python/templates/boilerplate/.gitignore +1 -1
  35. instant_python/templates/boilerplate/.pre-commit-config.yaml +0 -2
  36. instant_python/templates/boilerplate/agents/AGENTS.md +61 -147
  37. instant_python/templates/boilerplate/agents/agents/convention_keeper.md +11 -8
  38. instant_python/templates/boilerplate/agents/agents/judge.md +38 -9
  39. instant_python/templates/boilerplate/agents/agents/{craftsman_leader.md → leader.md} +33 -22
  40. instant_python/templates/boilerplate/agents/agents/spec_partner.md +55 -23
  41. instant_python/templates/boilerplate/agents/agents/tdd_craftsman.md +32 -16
  42. instant_python/templates/boilerplate/agents/convention_guidelines.md +10 -1
  43. instant_python/templates/boilerplate/agents/leader_workflow.md +20 -31
  44. instant_python/templates/boilerplate/agents/skills/mutation_testing/SKILL.md +79 -605
  45. instant_python/templates/boilerplate/agents/spec_guidelines.md +80 -0
  46. instant_python/templates/boilerplate/github/ci.yml +1 -1
  47. instant_python/templates/boilerplate/pyproject.toml +6 -0
  48. instant_python/templates/boilerplate/scripts/makefile +7 -2
  49. instant_python/templates/project_structure/agents/agents.yml +31 -35
  50. {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/METADATA +2 -1
  51. {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/RECORD +56 -57
  52. {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/WHEEL +1 -1
  53. instant_python/templates/boilerplate/agents/skills/convention/SKILL.md +0 -32
  54. instant_python/templates/boilerplate/agents/skills/spec/SKILL.md +0 -57
  55. /instant_python/templates/boilerplate/agents/{commands/code_review.md → skills/code_review/SKILL.md} +0 -0
  56. /instant_python/templates/boilerplate/agents/{commands/security_review.md → skills/security_review/SKILL.md} +0 -0
  57. {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/entry_points.txt +0 -0
  58. {instant_python-0.24.0.dist-info → instant_python-0.24.2.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1 @@
1
- __version__ = "0.24.0"
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, Union[str, bool]]]]:
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._ask_dependency_name()
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="Specify the name of the group where to install the dependency (leave empty if not applicable)",
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="Enter the version of the dependency you want to install",
53
+ message="What is the version of the dependency?",
53
54
  default="latest",
54
55
  )
55
56
 
56
- def _ask_dependency_name(self) -> str:
57
- return self._questionary.free_text_question(
58
- message="Enter the name of the dependency you want to install",
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="Do you want to install dependencies?",
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="Enter the name of the project (CANNOT CONTAIN SPACES)",
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="Enter the name of the source folder",
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="Enter the project description",
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="Enter the project initial version",
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="Enter your name",
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="Select a license",
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="Enter the python version",
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="Select a dependency manager",
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
- def run(self) -> dict[str, dict[str, Union[str, bool]]]:
15
- if not self._user_wants_initialize_git_repository():
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="Type your git email",
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="Type your git user name",
33
+ message="What is your Git user name?",
32
34
  )
33
35
  self._answers["username"] = answer
34
36
 
35
- def _user_wants_initialize_git_repository(self) -> bool:
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: Optional[str] = None) -> str:
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: Optional[str] = None) -> str:
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
- def run(self) -> dict[str, dict[str, Union[str, list[str]]]]:
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
- if name != SupportedTemplates.CUSTOM:
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="Select a template",
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) -> None:
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="Enter the bounded context name",
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="Enter the aggregate name",
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="Select the built-in features you want to include",
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 abstractmethod, ABC
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 Node, NodeType, Directory, File
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"{str(Path.home() / 'AppData' / 'Roaming' / 'Python' / 'Scripts' / 'pdm.exe')}"
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"{str(Path.home() / '.local' / 'bin' / 'uv.exe')}"
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 ProjectWriter, NodeWriter
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 exception
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:
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass, asdict
1
+ from dataclasses import asdict, dataclass
2
2
 
3
3
 
4
4
  @dataclass(frozen=True)
@@ -1,4 +1,4 @@
1
- from dataclasses import dataclass, asdict
1
+ from dataclasses import asdict, dataclass
2
2
 
3
3
 
4
4
  @dataclass(frozen=True)
@@ -1,6 +1,5 @@
1
1
  from pydantic import Field
2
-
3
- from pydantic_settings import SettingsConfigDict, BaseSettings
2
+ from pydantic_settings import BaseSettings, SettingsConfigDict
4
3
 
5
4
 
6
5
  class PostHogConfig(BaseSettings):
@@ -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, Union
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, Union[dict, list]]) -> None:
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, Union[dict, list]]):
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, Union[str, bool]]]
108
- template: dict[str, Union[str, list[str]]]
109
- git: dict[str, Union[str, bool]]
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):