instant-python 0.5.2__py3-none-any.whl → 0.6.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/cli.py +5 -5
- instant_python/commands/config.py +28 -0
- instant_python/commands/init.py +53 -0
- instant_python/configuration/configuration_schema.py +85 -0
- instant_python/configuration/dependency/dependency_configuration.py +36 -0
- instant_python/configuration/dependency/not_dev_dependency_included_in_group.py +8 -0
- instant_python/configuration/general/general_configuration.py +60 -0
- instant_python/configuration/general/invalid_dependency_manager_value.py +8 -0
- instant_python/configuration/general/invalid_license_value.py +8 -0
- instant_python/configuration/general/invalid_python_version_value.py +8 -0
- instant_python/configuration/git/git_configuration.py +23 -0
- instant_python/configuration/git/git_user_or_email_not_present.py +8 -0
- instant_python/configuration/parser/config_key_not_present.py +8 -0
- instant_python/configuration/parser/configuration_file_not_found.py +8 -0
- instant_python/configuration/parser/empty_configuration_not_allowed.py +8 -0
- instant_python/configuration/parser/missing_mandatory_fields.py +10 -0
- instant_python/configuration/parser/parser.py +148 -0
- instant_python/configuration/question/boolean_question.py +12 -0
- instant_python/configuration/question/choice_question.py +19 -0
- instant_python/{question_prompter → configuration}/question/conditional_question.py +6 -3
- instant_python/configuration/question/free_text_question.py +14 -0
- instant_python/configuration/question/multiple_choice_question.py +12 -0
- instant_python/{question_prompter → configuration}/question/question.py +6 -2
- instant_python/configuration/question/questionary.py +17 -0
- instant_python/configuration/question_wizard.py +14 -0
- instant_python/configuration/step/__init__.py +0 -0
- instant_python/configuration/step/dependencies_step.py +70 -0
- instant_python/configuration/step/general_step.py +67 -0
- instant_python/configuration/step/git_step.py +41 -0
- instant_python/{question_prompter → configuration}/step/steps.py +6 -1
- instant_python/configuration/step/template_step.py +63 -0
- instant_python/configuration/template/__init__.py +0 -0
- instant_python/configuration/template/bounded_context_not_applicable.py +8 -0
- instant_python/configuration/template/bounded_context_not_especified.py +8 -0
- instant_python/configuration/template/invalid_built_in_features_values.py +10 -0
- instant_python/configuration/template/invalid_template_value.py +8 -0
- instant_python/configuration/template/template_configuration.py +59 -0
- instant_python/dependency_manager/__init__.py +0 -0
- instant_python/dependency_manager/command_execution_error.py +10 -0
- instant_python/dependency_manager/dependency_manager.py +23 -0
- instant_python/dependency_manager/dependency_manager_factory.py +18 -0
- instant_python/dependency_manager/pdm_dependency_manager.py +46 -0
- instant_python/dependency_manager/unknown_dependency_manager_error.py +8 -0
- instant_python/dependency_manager/uv_dependency_manager.py +45 -0
- instant_python/git/__init__.py +0 -0
- instant_python/git/git_configurer.py +42 -0
- instant_python/{intant_python_typer.py → instant_python_typer.py} +5 -4
- instant_python/project_creator/__init__.py +0 -0
- instant_python/project_creator/directory.py +26 -0
- instant_python/project_creator/file.py +31 -0
- instant_python/project_creator/file_has_not_been_created.py +8 -0
- instant_python/project_creator/file_system.py +46 -0
- instant_python/project_creator/unknown_node_typer_error.py +8 -0
- instant_python/render/__init__.py +0 -0
- instant_python/{project_generator/custom_template_manager.py → render/custom_project_renderer.py} +6 -5
- instant_python/render/jinja_custom_filters.py +23 -0
- instant_python/render/jinja_environment.py +28 -0
- instant_python/render/jinja_project_renderer.py +30 -0
- instant_python/render/template_file_not_found_error.py +10 -0
- instant_python/render/unknown_template_error.py +8 -0
- instant_python/shared/__init__.py +0 -0
- instant_python/shared/application_error.py +16 -0
- instant_python/shared/error_types.py +7 -0
- instant_python/shared/supported_built_in_features.py +16 -0
- instant_python/shared/supported_licenses.py +11 -0
- instant_python/shared/supported_managers.py +10 -0
- instant_python/shared/supported_python_versions.py +12 -0
- instant_python/shared/supported_templates.py +12 -0
- instant_python/templates/boilerplate/.python-version +1 -1
- instant_python/templates/boilerplate/LICENSE +6 -6
- instant_python/templates/boilerplate/README.md +5 -2
- instant_python/templates/boilerplate/event_bus/aggregate_root.py +2 -2
- instant_python/templates/boilerplate/event_bus/domain_event_json_deserializer.py +4 -4
- instant_python/templates/boilerplate/event_bus/domain_event_json_serializer.py +2 -2
- instant_python/templates/boilerplate/event_bus/domain_event_subscriber.py +2 -2
- instant_python/templates/boilerplate/event_bus/event_bus.py +2 -2
- instant_python/templates/boilerplate/event_bus/exchange_type.py +1 -1
- instant_python/templates/boilerplate/event_bus/mock_event_bus.py +3 -3
- instant_python/templates/boilerplate/event_bus/rabbit_mq_configurer.py +6 -6
- instant_python/templates/boilerplate/event_bus/rabbit_mq_connection.py +5 -5
- instant_python/templates/boilerplate/event_bus/rabbit_mq_consumer.py +7 -7
- instant_python/templates/boilerplate/event_bus/rabbit_mq_event_bus.py +6 -6
- instant_python/templates/boilerplate/event_bus/rabbit_mq_queue_formatter.py +3 -3
- instant_python/templates/boilerplate/exceptions/domain_error.py +17 -12
- instant_python/templates/boilerplate/exceptions/domain_event_type_not_found_error.py +3 -11
- instant_python/templates/boilerplate/exceptions/incorrect_value_type_error.py +3 -11
- instant_python/templates/boilerplate/exceptions/invalid_id_format_error.py +3 -11
- instant_python/templates/boilerplate/exceptions/invalid_negative_value_error.py +3 -11
- instant_python/templates/boilerplate/exceptions/rabbit_mq_connection_not_established_error.py +3 -11
- instant_python/templates/boilerplate/exceptions/required_value_error.py +3 -11
- instant_python/templates/boilerplate/fastapi/application.py +8 -8
- instant_python/templates/boilerplate/fastapi/http_response.py +7 -7
- instant_python/templates/boilerplate/fastapi/lifespan.py +2 -2
- instant_python/templates/boilerplate/github/action.yml +3 -3
- instant_python/templates/boilerplate/logger/logger.py +2 -2
- instant_python/templates/boilerplate/mypy.ini +1 -1
- instant_python/templates/boilerplate/persistence/alembic_migrator.py +2 -2
- instant_python/templates/boilerplate/persistence/async/async_engine_fixture.py +2 -2
- instant_python/templates/boilerplate/persistence/async/env.py +2 -2
- instant_python/templates/boilerplate/persistence/async/models_metadata.py +2 -2
- instant_python/templates/boilerplate/persistence/async/sqlalchemy_repository.py +4 -4
- instant_python/templates/boilerplate/persistence/synchronous/session_maker.py +2 -2
- instant_python/templates/boilerplate/persistence/synchronous/sqlalchemy_repository.py +7 -7
- instant_python/templates/boilerplate/pyproject.toml +21 -10
- instant_python/templates/boilerplate/scripts/add_dependency.sh +2 -2
- instant_python/templates/boilerplate/scripts/integration.sh +1 -1
- instant_python/templates/boilerplate/scripts/makefile +26 -26
- instant_python/templates/boilerplate/scripts/remove_dependency.sh +2 -2
- instant_python/templates/boilerplate/scripts/unit.sh +1 -1
- instant_python/templates/boilerplate/value_object/int_value_object.py +3 -3
- instant_python/templates/boilerplate/value_object/string_value_object.py +4 -4
- instant_python/templates/boilerplate/value_object/uuid.py +3 -3
- instant_python/templates/boilerplate/value_object/value_object.py +1 -1
- instant_python/templates/project_structure/clean_architecture/main_structure.yml.j2 +24 -25
- instant_python/templates/project_structure/clean_architecture/source.yml.j2 +12 -12
- instant_python/templates/project_structure/clean_architecture/test.yml.j2 +1 -1
- instant_python/templates/project_structure/domain_driven_design/bounded_context.yml.j2 +2 -2
- instant_python/templates/project_structure/domain_driven_design/main_structure.yml.j2 +24 -25
- instant_python/templates/project_structure/domain_driven_design/source.yml.j2 +14 -14
- instant_python/templates/project_structure/domain_driven_design/test.yml.j2 +2 -2
- instant_python/templates/project_structure/makefile.yml.j2 +1 -1
- instant_python/templates/project_structure/standard_project/main_structure.yml.j2 +24 -25
- instant_python/templates/project_structure/standard_project/source.yml.j2 +9 -9
- instant_python/templates/project_structure/standard_project/test.yml.j2 +1 -1
- {instant_python-0.5.2.dist-info → instant_python-0.6.1.dist-info}/METADATA +69 -36
- instant_python-0.6.1.dist-info/RECORD +186 -0
- instant_python/errors/application_error.py +0 -11
- instant_python/errors/command_execution_error.py +0 -20
- instant_python/errors/error_types.py +0 -6
- instant_python/errors/template_file_not_found_error.py +0 -18
- instant_python/errors/unknown_dependency_manager_error.py +0 -18
- instant_python/errors/unknown_node_typer_error.py +0 -16
- instant_python/errors/unknown_template_error.py +0 -16
- instant_python/folder_cli.py +0 -50
- instant_python/installer/dependency_manager.py +0 -15
- instant_python/installer/dependency_manager_factory.py +0 -18
- instant_python/installer/git_configurer.py +0 -50
- instant_python/installer/installer.py +0 -24
- instant_python/installer/managers.py +0 -6
- instant_python/installer/pdm_manager.py +0 -86
- instant_python/installer/uv_manager.py +0 -88
- instant_python/project_cli.py +0 -100
- instant_python/project_generator/boilerplate_file.py +0 -20
- instant_python/project_generator/directory.py +0 -28
- instant_python/project_generator/file.py +0 -16
- instant_python/project_generator/folder_tree.py +0 -39
- instant_python/project_generator/jinja_custom_filters.py +0 -19
- instant_python/project_generator/jinja_environment.py +0 -20
- instant_python/project_generator/jinja_template_manager.py +0 -37
- instant_python/project_generator/project_generator.py +0 -36
- instant_python/project_generator/template_manager.py +0 -7
- instant_python/question_prompter/question/boolean_question.py +0 -13
- instant_python/question_prompter/question/choice_question.py +0 -18
- instant_python/question_prompter/question/dependencies_question.py +0 -43
- instant_python/question_prompter/question/free_text_question.py +0 -13
- instant_python/question_prompter/question/multiple_choice_question.py +0 -13
- instant_python/question_prompter/question_wizard.py +0 -15
- instant_python/question_prompter/requirements_configuration.py +0 -40
- instant_python/question_prompter/step/dependencies_step.py +0 -20
- instant_python/question_prompter/step/general_custom_template_project_step.py +0 -45
- instant_python/question_prompter/step/general_project_step.py +0 -50
- instant_python/question_prompter/step/git_step.py +0 -23
- instant_python/question_prompter/step/template_step.py +0 -71
- instant_python/question_prompter/template_types.py +0 -7
- instant_python-0.5.2.dist-info/RECORD +0 -162
- /instant_python/{errors → commands}/__init__.py +0 -0
- /instant_python/{installer → configuration}/__init__.py +0 -0
- /instant_python/{project_generator → configuration/dependency}/__init__.py +0 -0
- /instant_python/{question_prompter → configuration/general}/__init__.py +0 -0
- /instant_python/{question_prompter/question → configuration/git}/__init__.py +0 -0
- /instant_python/{question_prompter/step → configuration/parser}/__init__.py +0 -0
- /instant_python/{templates → configuration/question}/__init__.py +0 -0
- /instant_python/{project_generator → project_creator}/node.py +0 -0
- {instant_python-0.5.2.dist-info → instant_python-0.6.1.dist-info}/WHEEL +0 -0
- {instant_python-0.5.2.dist-info → instant_python-0.6.1.dist-info}/entry_points.txt +0 -0
- {instant_python-0.5.2.dist-info → instant_python-0.6.1.dist-info}/licenses/LICENSE +0 -0
instant_python/cli.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from rich.console import Console
|
|
2
2
|
from rich.panel import Panel
|
|
3
3
|
|
|
4
|
-
from instant_python import
|
|
5
|
-
from instant_python.
|
|
6
|
-
from instant_python.
|
|
4
|
+
from instant_python.commands import init, config
|
|
5
|
+
from instant_python.shared.application_error import ApplicationError
|
|
6
|
+
from instant_python.instant_python_typer import InstantPythonTyper
|
|
7
7
|
|
|
8
8
|
app = InstantPythonTyper()
|
|
9
9
|
console = Console()
|
|
10
10
|
|
|
11
|
-
app.add_typer(
|
|
12
|
-
app.add_typer(
|
|
11
|
+
app.add_typer(init.app)
|
|
12
|
+
app.add_typer(config.app)
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
@app.error_handler(ApplicationError)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from instant_python.configuration.parser.parser import Parser
|
|
4
|
+
from instant_python.configuration.question.questionary import Questionary
|
|
5
|
+
from instant_python.configuration.question_wizard import QuestionWizard
|
|
6
|
+
from instant_python.configuration.step.dependencies_step import DependenciesStep
|
|
7
|
+
from instant_python.configuration.step.general_step import GeneralStep
|
|
8
|
+
from instant_python.configuration.step.git_step import GitStep
|
|
9
|
+
from instant_python.configuration.step.steps import Steps
|
|
10
|
+
from instant_python.configuration.step.template_step import TemplateStep
|
|
11
|
+
|
|
12
|
+
app = typer.Typer()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command("config", help="Generate the configuration file for a new project")
|
|
16
|
+
def create_new_project() -> None:
|
|
17
|
+
questionary = Questionary()
|
|
18
|
+
steps = Steps(
|
|
19
|
+
GeneralStep(questionary=questionary),
|
|
20
|
+
TemplateStep(questionary=questionary),
|
|
21
|
+
GitStep(questionary=questionary),
|
|
22
|
+
DependenciesStep(questionary=questionary),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
question_wizard = QuestionWizard(steps=steps)
|
|
26
|
+
configuration = question_wizard.run()
|
|
27
|
+
validated_configuration = Parser.parse_from_answers(configuration)
|
|
28
|
+
validated_configuration.save_on_current_directory()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from instant_python.configuration.parser.parser import Parser
|
|
4
|
+
from instant_python.dependency_manager.dependency_manager_factory import DependencyManagerFactory
|
|
5
|
+
from instant_python.git.git_configurer import GitConfigurer
|
|
6
|
+
from instant_python.project_creator.file_system import FileSystem
|
|
7
|
+
from instant_python.render.custom_project_renderer import CustomProjectRenderer
|
|
8
|
+
from instant_python.render.jinja_environment import JinjaEnvironment
|
|
9
|
+
from instant_python.render.jinja_project_renderer import JinjaProjectRenderer
|
|
10
|
+
|
|
11
|
+
app = typer.Typer()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("init", help="Create a new project")
|
|
15
|
+
def create_new_project(
|
|
16
|
+
config_file: str = typer.Option("ipy.yml", "--config", "-c", help="Path to yml configuration file"),
|
|
17
|
+
template: str | None = typer.Option(None, "--template", "-t", help="Path to custom template file"),
|
|
18
|
+
) -> None:
|
|
19
|
+
configuration = Parser.parse_from_file(config_file_path=config_file)
|
|
20
|
+
environment = JinjaEnvironment(package_name="instant_python", template_directory="templates")
|
|
21
|
+
|
|
22
|
+
if template:
|
|
23
|
+
project_renderer = CustomProjectRenderer(template_path=template)
|
|
24
|
+
project_structure = project_renderer.render_project_structure()
|
|
25
|
+
else:
|
|
26
|
+
project_renderer = JinjaProjectRenderer(jinja_environment=environment)
|
|
27
|
+
project_structure = project_renderer.render_project_structure(
|
|
28
|
+
context_config=configuration,
|
|
29
|
+
template_base_dir="project_structure",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
file_system = FileSystem(project_structure=project_structure)
|
|
33
|
+
file_system.write_on_disk(
|
|
34
|
+
file_renderer=environment,
|
|
35
|
+
context=configuration,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
dependency_manager = DependencyManagerFactory.create(
|
|
39
|
+
dependency_manager=configuration.dependency_manager,
|
|
40
|
+
project_directory=configuration.project_folder_name,
|
|
41
|
+
)
|
|
42
|
+
dependency_manager.setup_environment(
|
|
43
|
+
python_version=configuration.python_version,
|
|
44
|
+
dependencies=configuration.dependencies,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
git_configurer = GitConfigurer(project_directory=configuration.project_folder_name)
|
|
48
|
+
git_configurer.setup_repository(configuration.git)
|
|
49
|
+
configuration.save_on_project_folder()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
app()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TypedDict, Self, Union
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
|
|
8
|
+
from instant_python.configuration.dependency.dependency_configuration import (
|
|
9
|
+
DependencyConfiguration,
|
|
10
|
+
)
|
|
11
|
+
from instant_python.configuration.general.general_configuration import (
|
|
12
|
+
GeneralConfiguration,
|
|
13
|
+
)
|
|
14
|
+
from instant_python.configuration.git.git_configuration import GitConfiguration
|
|
15
|
+
from instant_python.configuration.template.template_configuration import (
|
|
16
|
+
TemplateConfiguration,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class ConfigurationSchema:
|
|
22
|
+
general: GeneralConfiguration
|
|
23
|
+
dependencies: list[DependencyConfiguration]
|
|
24
|
+
template: TemplateConfiguration
|
|
25
|
+
git: GitConfiguration
|
|
26
|
+
_config_file_path: Path = field(default_factory=lambda: Path("ipy.yml"))
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def from_file(
|
|
30
|
+
cls,
|
|
31
|
+
config_file_path: str,
|
|
32
|
+
general: GeneralConfiguration,
|
|
33
|
+
dependencies: list[DependencyConfiguration],
|
|
34
|
+
template: TemplateConfiguration,
|
|
35
|
+
git: GitConfiguration,
|
|
36
|
+
) -> Self:
|
|
37
|
+
return cls(
|
|
38
|
+
general=general,
|
|
39
|
+
dependencies=dependencies,
|
|
40
|
+
template=template,
|
|
41
|
+
git=git,
|
|
42
|
+
_config_file_path=Path(config_file_path),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
def save_on_project_folder(self) -> None:
|
|
46
|
+
destination_folder = Path.cwd() / self.project_folder_name
|
|
47
|
+
destination_path = destination_folder / self._config_file_path.name
|
|
48
|
+
|
|
49
|
+
shutil.move(self._config_file_path, destination_path)
|
|
50
|
+
|
|
51
|
+
def save_on_current_directory(self) -> None:
|
|
52
|
+
destination_folder = Path.cwd() / self._config_file_path
|
|
53
|
+
with open(destination_folder, "w") as file:
|
|
54
|
+
yaml.dump(self.to_primitives(), file)
|
|
55
|
+
|
|
56
|
+
def to_primitives(self) -> "ConfigurationSchemaPrimitives":
|
|
57
|
+
return ConfigurationSchemaPrimitives(
|
|
58
|
+
general=self.general.to_primitives(),
|
|
59
|
+
dependencies=[dependency.to_primitives() for dependency in self.dependencies],
|
|
60
|
+
template=self.template.to_primitives(),
|
|
61
|
+
git=self.git.to_primitives(),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def template_type(self) -> str:
|
|
66
|
+
return self.template.name
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def project_folder_name(self) -> str:
|
|
70
|
+
return self.general.slug
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def dependency_manager(self) -> str:
|
|
74
|
+
return self.general.dependency_manager
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def python_version(self) -> str:
|
|
78
|
+
return self.general.python_version
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ConfigurationSchemaPrimitives(TypedDict):
|
|
82
|
+
general: dict[str, str]
|
|
83
|
+
dependencies: list[dict[str, Union[str, bool]]]
|
|
84
|
+
template: dict[str, Union[str, list[str]]]
|
|
85
|
+
git: dict[str, Union[str, bool]]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, asdict
|
|
2
|
+
|
|
3
|
+
from instant_python.configuration.dependency.not_dev_dependency_included_in_group import (
|
|
4
|
+
NotDevDependencyIncludedInGroup,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class DependencyConfiguration:
|
|
10
|
+
name: str
|
|
11
|
+
version: str
|
|
12
|
+
is_dev: bool = field(default=False)
|
|
13
|
+
group: str = field(default_factory=str)
|
|
14
|
+
|
|
15
|
+
def __post_init__(self) -> None:
|
|
16
|
+
self.version = str(self.version)
|
|
17
|
+
self._ensure_dependency_is_dev_if_group_is_set()
|
|
18
|
+
|
|
19
|
+
def to_primitives(self) -> dict[str, str | bool]:
|
|
20
|
+
return asdict(self)
|
|
21
|
+
|
|
22
|
+
def get_installation_flag(self) -> tuple[str, ...]:
|
|
23
|
+
if self.group:
|
|
24
|
+
return (f"--group {self.group}",)
|
|
25
|
+
elif self.is_dev:
|
|
26
|
+
return ("--dev",)
|
|
27
|
+
return tuple()
|
|
28
|
+
|
|
29
|
+
def get_specification(self) -> str:
|
|
30
|
+
if self.version == "latest":
|
|
31
|
+
return self.name
|
|
32
|
+
return f"{self.name}=={self.version}"
|
|
33
|
+
|
|
34
|
+
def _ensure_dependency_is_dev_if_group_is_set(self) -> None:
|
|
35
|
+
if self.group and not self.is_dev:
|
|
36
|
+
raise NotDevDependencyIncludedInGroup(self.name, self.group)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class NotDevDependencyIncludedInGroup(ApplicationError):
|
|
6
|
+
def __init__(self, dependency_name: str, dependency_group: str) -> None:
|
|
7
|
+
message = f"Dependency '{dependency_name}' has been included in group '{dependency_group}' but it is not a development dependency. Please ensure that only development dependencies are included in groups."
|
|
8
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from dataclasses import dataclass, asdict, field
|
|
3
|
+
from typing import ClassVar
|
|
4
|
+
|
|
5
|
+
from instant_python.configuration.general.invalid_dependency_manager_value import (
|
|
6
|
+
InvalidDependencyManagerValue,
|
|
7
|
+
)
|
|
8
|
+
from instant_python.configuration.general.invalid_license_value import (
|
|
9
|
+
InvalidLicenseValue,
|
|
10
|
+
)
|
|
11
|
+
from instant_python.configuration.general.invalid_python_version_value import (
|
|
12
|
+
InvalidPythonVersionValue,
|
|
13
|
+
)
|
|
14
|
+
from instant_python.shared.supported_licenses import SupportedLicenses
|
|
15
|
+
from instant_python.shared.supported_managers import SupportedManagers
|
|
16
|
+
from instant_python.shared.supported_python_versions import SupportedPythonVersions
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class GeneralConfiguration:
|
|
21
|
+
slug: str
|
|
22
|
+
source_name: str
|
|
23
|
+
description: str
|
|
24
|
+
version: str
|
|
25
|
+
author: str
|
|
26
|
+
license: str
|
|
27
|
+
python_version: str
|
|
28
|
+
dependency_manager: str
|
|
29
|
+
year: int = field(default=datetime.now().year)
|
|
30
|
+
|
|
31
|
+
_SUPPORTED_DEPENDENCY_MANAGERS: ClassVar[list[str]] = SupportedManagers.get_supported_managers()
|
|
32
|
+
_SUPPORTED_PYTHON_VERSIONS: ClassVar[list[str]] = SupportedPythonVersions.get_supported_versions()
|
|
33
|
+
_SUPPORTED_LICENSES: ClassVar[list[str]] = SupportedLicenses.get_supported_licenses()
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
self.version = str(self.version)
|
|
37
|
+
self.python_version = str(self.python_version)
|
|
38
|
+
self._remove_white_spaces_from_slug_if_present()
|
|
39
|
+
self._ensure_license_is_supported()
|
|
40
|
+
self._ensure_python_version_is_supported()
|
|
41
|
+
self._ensure_dependency_manager_is_supported()
|
|
42
|
+
|
|
43
|
+
def _remove_white_spaces_from_slug_if_present(self) -> None:
|
|
44
|
+
if " " in self.slug:
|
|
45
|
+
self.slug = self.slug.replace(" ", "")
|
|
46
|
+
|
|
47
|
+
def _ensure_license_is_supported(self) -> None:
|
|
48
|
+
if self.license not in self._SUPPORTED_LICENSES:
|
|
49
|
+
raise InvalidLicenseValue(self.license, self._SUPPORTED_LICENSES)
|
|
50
|
+
|
|
51
|
+
def _ensure_python_version_is_supported(self) -> None:
|
|
52
|
+
if self.python_version not in self._SUPPORTED_PYTHON_VERSIONS:
|
|
53
|
+
raise InvalidPythonVersionValue(self.python_version, self._SUPPORTED_PYTHON_VERSIONS)
|
|
54
|
+
|
|
55
|
+
def _ensure_dependency_manager_is_supported(self) -> None:
|
|
56
|
+
if self.dependency_manager not in self._SUPPORTED_DEPENDENCY_MANAGERS:
|
|
57
|
+
raise InvalidDependencyManagerValue(self.dependency_manager, self._SUPPORTED_DEPENDENCY_MANAGERS)
|
|
58
|
+
|
|
59
|
+
def to_primitives(self) -> dict[str, str]:
|
|
60
|
+
return asdict(self)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InvalidDependencyManagerValue(ApplicationError):
|
|
6
|
+
def __init__(self, value: str, supported_values: list[str]) -> None:
|
|
7
|
+
message = f"Invalid dependency manager: {value}. Allowed values are {', '.join(supported_values)}."
|
|
8
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InvalidLicenseValue(ApplicationError):
|
|
6
|
+
def __init__(self, value: str, supported_values: list[str]) -> None:
|
|
7
|
+
message = f"Invalid license: {value}. Allowed values are {', '.join(supported_values)}."
|
|
8
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InvalidPythonVersionValue(ApplicationError):
|
|
6
|
+
def __init__(self, value: str, supported_values: list[str]) -> None:
|
|
7
|
+
message = f"Invalid Python version: {value}. Allowed versions are {', '.join(supported_values)}."
|
|
8
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, asdict
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from instant_python.configuration.git.git_user_or_email_not_present import (
|
|
5
|
+
GitUserOrEmailNotPresent,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class GitConfiguration:
|
|
11
|
+
initialize: bool
|
|
12
|
+
username: Optional[str] = field(default=None)
|
|
13
|
+
email: Optional[str] = field(default=None)
|
|
14
|
+
|
|
15
|
+
def __post_init__(self) -> None:
|
|
16
|
+
self._ensure_username_and_email_are_set_if_initializing()
|
|
17
|
+
|
|
18
|
+
def _ensure_username_and_email_are_set_if_initializing(self) -> None:
|
|
19
|
+
if self.initialize and (self.username is None or self.email is None):
|
|
20
|
+
raise GitUserOrEmailNotPresent()
|
|
21
|
+
|
|
22
|
+
def to_primitives(self) -> dict[str, str | bool]:
|
|
23
|
+
return asdict(self)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GitUserOrEmailNotPresent(ApplicationError):
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
message = "When initializing a git repository, both username and email must be provided."
|
|
8
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ConfigKeyNotPresent(ApplicationError):
|
|
6
|
+
def __init__(self, missing_keys: list[str], required_keys: list[str]) -> None:
|
|
7
|
+
message = f"The following required keys are missing from the configuration file: {', '.join(missing_keys)}. Required keys are: {', '.join(required_keys)}."
|
|
8
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ConfigurationFileNotFound(ApplicationError):
|
|
6
|
+
def __init__(self, path: str) -> None:
|
|
7
|
+
message = f"Configuration file not found at '{path}'."
|
|
8
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class EmptyConfigurationNotAllowed(ApplicationError):
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
message = "Configuration file cannot be empty."
|
|
8
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from instant_python.shared.application_error import ApplicationError
|
|
2
|
+
from instant_python.shared.error_types import ErrorTypes
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MissingMandatoryFields(ApplicationError):
|
|
6
|
+
def __init__(self, missing_field: str, config_section: str) -> None:
|
|
7
|
+
message = (
|
|
8
|
+
f"Mandatory field '{missing_field}' is missing in the '{config_section}' section of the configuration file."
|
|
9
|
+
)
|
|
10
|
+
super().__init__(message=message, error_type=ErrorTypes.CONFIGURATION.value)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
|
|
5
|
+
from instant_python.configuration.parser.config_key_not_present import ConfigKeyNotPresent
|
|
6
|
+
from instant_python.configuration.configuration_schema import ConfigurationSchema
|
|
7
|
+
from instant_python.configuration.dependency.dependency_configuration import (
|
|
8
|
+
DependencyConfiguration,
|
|
9
|
+
)
|
|
10
|
+
from instant_python.configuration.general.general_configuration import (
|
|
11
|
+
GeneralConfiguration,
|
|
12
|
+
)
|
|
13
|
+
from instant_python.configuration.git.git_configuration import GitConfiguration
|
|
14
|
+
from instant_python.configuration.parser.configuration_file_not_found import (
|
|
15
|
+
ConfigurationFileNotFound,
|
|
16
|
+
)
|
|
17
|
+
from instant_python.configuration.parser.empty_configuration_not_allowed import (
|
|
18
|
+
EmptyConfigurationNotAllowed,
|
|
19
|
+
)
|
|
20
|
+
from instant_python.configuration.parser.missing_mandatory_fields import (
|
|
21
|
+
MissingMandatoryFields,
|
|
22
|
+
)
|
|
23
|
+
from instant_python.configuration.template.template_configuration import TemplateConfiguration
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Parser:
|
|
27
|
+
REQUIRED_CONFIG_KEYS = ["general", "dependencies", "template", "git"]
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def parse_from_file(cls, config_file_path: str) -> ConfigurationSchema:
|
|
31
|
+
"""Parses the configuration file and validates its content.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config_file_path: The path to the configuration file to be parsed.
|
|
35
|
+
Returns:
|
|
36
|
+
ConfigurationSchema: An instance of ConfigurationSchema containing the parsed configuration.
|
|
37
|
+
Raises:
|
|
38
|
+
ConfigurationFileNotFound: If the configuration file does not exist in that path.
|
|
39
|
+
EmptyConfigurationNotAllowed: If the configuration file is empty.
|
|
40
|
+
ConfigKeyNotPresent: If any of the required keys are missing from the configuration.
|
|
41
|
+
MissingMandatoryFields: If any mandatory fields are missing in the configuration sections.
|
|
42
|
+
"""
|
|
43
|
+
content = cls._get_config_file_content(config_file_path)
|
|
44
|
+
general_configuration, dependencies_configuration, template_configuration, git_configuration = (
|
|
45
|
+
cls._parse_configuration(content=content)
|
|
46
|
+
)
|
|
47
|
+
return ConfigurationSchema.from_file(
|
|
48
|
+
config_file_path=config_file_path,
|
|
49
|
+
general=general_configuration,
|
|
50
|
+
dependencies=dependencies_configuration,
|
|
51
|
+
template=template_configuration,
|
|
52
|
+
git=git_configuration,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def parse_from_answers(cls, content: dict[str, dict]) -> ConfigurationSchema:
|
|
57
|
+
general_configuration, dependencies_configuration, template_configuration, git_configuration = (
|
|
58
|
+
cls._parse_configuration(content=content)
|
|
59
|
+
)
|
|
60
|
+
return ConfigurationSchema(
|
|
61
|
+
general=general_configuration,
|
|
62
|
+
dependencies=dependencies_configuration,
|
|
63
|
+
template=template_configuration,
|
|
64
|
+
git=git_configuration,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@classmethod
|
|
68
|
+
def _parse_configuration(cls, content: dict[str, dict]) -> tuple:
|
|
69
|
+
general_configuration = cls._parse_general_configuration(content["general"])
|
|
70
|
+
dependencies_configuration = cls._parse_dependencies_configuration(content["dependencies"])
|
|
71
|
+
template_configuration = cls._parse_template_configuration(content["template"])
|
|
72
|
+
git_configuration = cls._parse_git_configuration(content["git"])
|
|
73
|
+
return general_configuration, dependencies_configuration, template_configuration, git_configuration
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def _get_config_file_content(cls, config_file_path: str) -> dict[str, dict]:
|
|
77
|
+
content = cls._read_config_file(config_file_path)
|
|
78
|
+
cls._ensure_config_file_is_not_empty(content)
|
|
79
|
+
cls._ensure_all_required_keys_are_present(content)
|
|
80
|
+
return content
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _ensure_config_file_is_not_empty(content: dict[str, dict]) -> None:
|
|
84
|
+
if not content:
|
|
85
|
+
raise EmptyConfigurationNotAllowed()
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _read_config_file(config_file_path: str) -> dict[str, dict]:
|
|
89
|
+
try:
|
|
90
|
+
with open(config_file_path, "r") as file:
|
|
91
|
+
return yaml.safe_load(file)
|
|
92
|
+
except FileNotFoundError:
|
|
93
|
+
raise ConfigurationFileNotFound(config_file_path)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _ensure_all_required_keys_are_present(content: dict[str, dict]) -> None:
|
|
97
|
+
missing_keys = [key for key in Parser.REQUIRED_CONFIG_KEYS if key not in content]
|
|
98
|
+
if missing_keys:
|
|
99
|
+
raise ConfigKeyNotPresent(missing_keys, Parser.REQUIRED_CONFIG_KEYS)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def _parse_general_configuration(fields: dict[str, str]) -> GeneralConfiguration:
|
|
103
|
+
try:
|
|
104
|
+
return GeneralConfiguration(**fields)
|
|
105
|
+
except TypeError as error:
|
|
106
|
+
_ensure_error_is_for_missing_fields(error)
|
|
107
|
+
raise MissingMandatoryFields(error.args[0], "general") from error
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _parse_dependencies_configuration(
|
|
111
|
+
fields: list[dict[str, Union[str, bool]]],
|
|
112
|
+
) -> list[DependencyConfiguration]:
|
|
113
|
+
dependencies = []
|
|
114
|
+
|
|
115
|
+
if not fields:
|
|
116
|
+
return dependencies
|
|
117
|
+
|
|
118
|
+
for dependency_fields in fields:
|
|
119
|
+
try:
|
|
120
|
+
dependency = DependencyConfiguration(**dependency_fields)
|
|
121
|
+
except TypeError as error:
|
|
122
|
+
_ensure_error_is_for_missing_fields(error)
|
|
123
|
+
raise MissingMandatoryFields(error.args[0], "dependencies") from error
|
|
124
|
+
|
|
125
|
+
dependencies.append(dependency)
|
|
126
|
+
|
|
127
|
+
return dependencies
|
|
128
|
+
|
|
129
|
+
@staticmethod
|
|
130
|
+
def _parse_template_configuration(fields: dict[str, Union[str, bool, list[str]]]) -> TemplateConfiguration:
|
|
131
|
+
try:
|
|
132
|
+
return TemplateConfiguration(**fields)
|
|
133
|
+
except TypeError as error:
|
|
134
|
+
_ensure_error_is_for_missing_fields(error)
|
|
135
|
+
raise MissingMandatoryFields(error.args[0], "template") from error
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _parse_git_configuration(fields: dict[str, Union[str, bool]]) -> GitConfiguration:
|
|
139
|
+
try:
|
|
140
|
+
return GitConfiguration(**fields)
|
|
141
|
+
except TypeError as error:
|
|
142
|
+
_ensure_error_is_for_missing_fields(error)
|
|
143
|
+
raise MissingMandatoryFields(error.args[0], "git") from error
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _ensure_error_is_for_missing_fields(error: TypeError) -> None:
|
|
147
|
+
if ".__init__() missing" not in str(error):
|
|
148
|
+
raise error
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from instant_python.configuration.question.question import Question
|
|
2
|
+
from instant_python.configuration.question.questionary import Questionary
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BooleanQuestion(Question[bool]):
|
|
6
|
+
def __init__(self, key: str, message: str, default: bool, questionary: Questionary) -> None:
|
|
7
|
+
super().__init__(key, message, questionary)
|
|
8
|
+
self._default = default
|
|
9
|
+
|
|
10
|
+
def ask(self) -> dict[str, bool]:
|
|
11
|
+
answer = self._questionary.boolean_question(self._message, default=self._default)
|
|
12
|
+
return {self._key: answer}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from instant_python.configuration.question.question import Question
|
|
4
|
+
from instant_python.configuration.question.questionary import Questionary
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ChoiceQuestion(Question[str]):
|
|
8
|
+
def __init__(self, key: str, message: str, questionary: Questionary, options: Optional[list[str]] = None) -> None:
|
|
9
|
+
super().__init__(key, message, questionary)
|
|
10
|
+
self._default = options[0] if options else ""
|
|
11
|
+
self._options = options if options else []
|
|
12
|
+
|
|
13
|
+
def ask(self) -> dict[str, str]:
|
|
14
|
+
answer = self._questionary.single_choice_question(
|
|
15
|
+
self._message,
|
|
16
|
+
options=self._options,
|
|
17
|
+
default=self._default,
|
|
18
|
+
)
|
|
19
|
+
return {self._key: answer}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from typing import Union
|
|
2
2
|
|
|
3
|
-
from instant_python.
|
|
3
|
+
from instant_python.configuration.question.question import Question
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ConditionalQuestion:
|
|
7
7
|
def __init__(
|
|
8
|
-
self,
|
|
8
|
+
self,
|
|
9
|
+
base_question: Question,
|
|
10
|
+
subquestions: Union[list[Question], "ConditionalQuestion"],
|
|
11
|
+
condition: Union[str, bool],
|
|
9
12
|
) -> None:
|
|
10
13
|
self._base_question = base_question
|
|
11
14
|
self._subquestions = subquestions
|
|
@@ -18,7 +21,7 @@ class ConditionalQuestion:
|
|
|
18
21
|
return base_answer
|
|
19
22
|
|
|
20
23
|
answers = base_answer
|
|
21
|
-
|
|
24
|
+
|
|
22
25
|
if isinstance(self._subquestions, ConditionalQuestion):
|
|
23
26
|
answers.update(self._subquestions.ask())
|
|
24
27
|
else:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from instant_python.configuration.question.question import Question
|
|
4
|
+
from instant_python.configuration.question.questionary import Questionary
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class FreeTextQuestion(Question[str]):
|
|
8
|
+
def __init__(self, key: str, message: str, questionary: Questionary, default: Optional[str] = None) -> None:
|
|
9
|
+
super().__init__(key, message, questionary)
|
|
10
|
+
self._default = default if default else ""
|
|
11
|
+
|
|
12
|
+
def ask(self) -> dict[str, str]:
|
|
13
|
+
answer = self._questionary.free_text_question(self._message, default=self._default)
|
|
14
|
+
return {self._key: answer}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from instant_python.configuration.question.question import Question
|
|
2
|
+
from instant_python.configuration.question.questionary import Questionary
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MultipleChoiceQuestion(Question[list[str]]):
|
|
6
|
+
def __init__(self, key: str, message: str, options: list[str], questionary: Questionary) -> None:
|
|
7
|
+
super().__init__(key, message, questionary)
|
|
8
|
+
self._options = options
|
|
9
|
+
|
|
10
|
+
def ask(self) -> dict[str, list[str]]:
|
|
11
|
+
answer = self._questionary.multiselect_question(self._message, options=self._options)
|
|
12
|
+
return {self._key: answer}
|
|
@@ -2,17 +2,21 @@ from abc import ABC, abstractmethod
|
|
|
2
2
|
|
|
3
3
|
from typing import TypeVar, Generic
|
|
4
4
|
|
|
5
|
+
from instant_python.configuration.question.questionary import Questionary
|
|
6
|
+
|
|
5
7
|
T = TypeVar("T")
|
|
6
8
|
|
|
9
|
+
|
|
7
10
|
class Question(Generic[T], ABC):
|
|
8
|
-
def __init__(self, key: str, message: str) -> None:
|
|
11
|
+
def __init__(self, key: str, message: str, questionary: Questionary) -> None:
|
|
9
12
|
self._key = key
|
|
10
13
|
self._message = message
|
|
14
|
+
self._questionary = questionary
|
|
11
15
|
|
|
12
16
|
@abstractmethod
|
|
13
17
|
def ask(self) -> dict[str, T]:
|
|
14
18
|
raise NotImplementedError
|
|
15
|
-
|
|
19
|
+
|
|
16
20
|
@property
|
|
17
21
|
def key(self) -> str:
|
|
18
22
|
return self._key
|