django-spire 0.16.13__py3-none-any.whl → 0.17.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. django_spire/consts.py +1 -1
  2. django_spire/core/management/commands/spire_startapp.py +84 -46
  3. django_spire/core/management/commands/spire_startapp_pkg/__init__.py +60 -0
  4. django_spire/core/management/commands/spire_startapp_pkg/builder.py +91 -0
  5. django_spire/core/management/commands/spire_startapp_pkg/config.py +115 -0
  6. django_spire/core/management/commands/spire_startapp_pkg/filesystem.py +125 -0
  7. django_spire/core/management/commands/spire_startapp_pkg/generator.py +167 -0
  8. django_spire/core/management/commands/spire_startapp_pkg/maps.py +783 -25
  9. django_spire/core/management/commands/spire_startapp_pkg/permissions.py +147 -0
  10. django_spire/core/management/commands/spire_startapp_pkg/processor.py +144 -57
  11. django_spire/core/management/commands/spire_startapp_pkg/registry.py +89 -0
  12. django_spire/core/management/commands/spire_startapp_pkg/reporter.py +245 -108
  13. django_spire/core/management/commands/spire_startapp_pkg/resolver.py +86 -0
  14. django_spire/core/management/commands/spire_startapp_pkg/template/app/__init__.py.template +0 -0
  15. django_spire/core/management/commands/spire_startapp_pkg/template/app/apps.py.template +15 -0
  16. django_spire/core/management/commands/spire_startapp_pkg/template/app/forms.py.template +18 -0
  17. django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/__init__.py.template +0 -0
  18. django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/bots.py.template +18 -0
  19. django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/intel.py.template +7 -0
  20. django_spire/core/management/commands/spire_startapp_pkg/template/app/intelligence/prompts.py.template +32 -0
  21. django_spire/core/management/commands/spire_startapp_pkg/template/app/migrations/__init__.py.template +0 -0
  22. django_spire/core/management/commands/spire_startapp_pkg/template/app/models.py.template +52 -0
  23. django_spire/core/management/commands/spire_startapp_pkg/template/app/querysets.py.template +20 -0
  24. django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/__init__.py.template +0 -0
  25. django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/seed.py.template +6 -0
  26. django_spire/core/management/commands/spire_startapp_pkg/template/app/seeding/seeder.py.template +26 -0
  27. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/__init__.py.template +0 -0
  28. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/factory_service.py.template +12 -0
  29. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/intelligence_service.py.template +12 -0
  30. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/processor_service.py.template +12 -0
  31. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/service.py.template +22 -0
  32. django_spire/core/management/commands/spire_startapp_pkg/template/app/services/transformation_service.py.template +12 -0
  33. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/__init__.py.template +0 -0
  34. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/__init__.py.template +0 -0
  35. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_intelligence/test_bots.py.template +8 -0
  36. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_models.py.template +8 -0
  37. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/__init__.py.template +0 -0
  38. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_factory_service.py.template +8 -0
  39. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_intelligence_service.py.template +8 -0
  40. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_processor_service.py.template +8 -0
  41. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_service.py.template +8 -0
  42. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_services/test_transformation_service.py.template +8 -0
  43. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/__init__.py.template +0 -0
  44. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/test_form_urls.py.template +8 -0
  45. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_urls/test_page_urls.py.template +8 -0
  46. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/__init__.py.template +0 -0
  47. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_form_views.py.template +8 -0
  48. django_spire/core/management/commands/spire_startapp_pkg/template/app/tests/test_views/test_page_views.py.template +8 -0
  49. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/__init__.py.template +9 -0
  50. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/form_urls.py.template +15 -0
  51. django_spire/core/management/commands/spire_startapp_pkg/template/app/urls/page_urls.py.template +11 -0
  52. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/__init__.py.template +0 -0
  53. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/form_views.py.template +134 -0
  54. django_spire/core/management/commands/spire_startapp_pkg/template/app/views/page_views.py.template +44 -0
  55. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/{spirechildapp_detail_card.html → ${detail_card_template_name}.html.template} +7 -7
  56. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/{spirechildapp_form_card.html → ${form_card_template_name}.html.template} +2 -2
  57. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/${list_card_template_name}.html.template +18 -0
  58. django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/{spirechildapp_form.html → ${form_template_name}.html.template} +4 -4
  59. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/${item_template_name}.html.template +24 -0
  60. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_detail_page.html → ${detail_page_template_name}.html.template} +3 -3
  61. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_form_page.html → ${form_page_template_name}.html.template} +1 -1
  62. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/{spirechildapp_list_page.html → ${list_page_template_name}.html.template} +2 -2
  63. django_spire/core/management/commands/spire_startapp_pkg/user_input.py +252 -0
  64. django_spire/core/management/commands/spire_startapp_pkg/validator.py +96 -0
  65. django_spire/core/middleware/__init__.py +1 -2
  66. django_spire/profiling/__init__.py +13 -0
  67. django_spire/profiling/middleware/__init__.py +6 -0
  68. django_spire/{core → profiling}/middleware/profiling.py +63 -58
  69. django_spire/profiling/panel.py +345 -0
  70. django_spire/profiling/templates/panel.html +166 -0
  71. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/METADATA +1 -1
  72. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/RECORD +75 -23
  73. django_spire/core/management/commands/spire_startapp_pkg/constants.py +0 -4
  74. django_spire/core/management/commands/spire_startapp_pkg/manager.py +0 -176
  75. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_list_card.html +0 -18
  76. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html +0 -24
  77. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/WHEEL +0 -0
  78. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/licenses/LICENSE.md +0 -0
  79. {django_spire-0.16.13.dist-info → django_spire-0.17.1.dist-info}/top_level.txt +0 -0
django_spire/consts.py CHANGED
@@ -1,4 +1,4 @@
1
- __VERSION__ = '0.16.13'
1
+ __VERSION__ = '0.17.1'
2
2
 
3
3
 
4
4
  AI_CHAT_WORKFLOW_SENDER_SETTINGS_NAME = 'AI_CHAT_WORKFLOW_NAME'
@@ -1,80 +1,118 @@
1
1
  from __future__ import annotations
2
2
 
3
- import django_spire
3
+ from typing import TYPE_CHECKING
4
4
 
5
- from pathlib import Path
5
+ from django.core.management.base import BaseCommand
6
6
 
7
- from django.conf import settings
8
- from django.core.management.base import BaseCommand, CommandError
9
-
10
- from django_spire.core.management.commands.spire_startapp_pkg.manager import (
11
- AppManager,
12
- HTMLTemplateManager,
7
+ from django_spire.core.management.commands.spire_startapp_pkg.builder import TemplateBuilder
8
+ from django_spire.core.management.commands.spire_startapp_pkg.config import (
9
+ AppConfigFactory,
10
+ PathConfig,
11
+ )
12
+ from django_spire.core.management.commands.spire_startapp_pkg.filesystem import FileSystem
13
+ from django_spire.core.management.commands.spire_startapp_pkg.generator import (
14
+ AppGenerator,
15
+ # TemplateGenerator,
13
16
  )
14
17
  from django_spire.core.management.commands.spire_startapp_pkg.processor import (
15
- AppTemplateProcessor,
16
- HTMLTemplateProcessor
18
+ TemplateEngine,
19
+ TemplateProcessor,
17
20
  )
21
+ from django_spire.core.management.commands.spire_startapp_pkg.registry import AppRegistry
18
22
  from django_spire.core.management.commands.spire_startapp_pkg.reporter import Reporter
23
+ from django_spire.core.management.commands.spire_startapp_pkg.resolver import PathResolver
24
+ from django_spire.core.management.commands.spire_startapp_pkg.user_input import UserInputCollector
25
+ from django_spire.core.management.commands.spire_startapp_pkg.validator import AppValidator
26
+
27
+ if TYPE_CHECKING:
28
+ from typing import Any
19
29
 
20
30
 
21
31
  class Command(BaseCommand):
32
+ """
33
+ Django management command for creating custom Spire apps.
34
+
35
+ This command guides users through an interactive wizard to create a new Django app
36
+ with a pre-configured structure including models, views, forms, services, and templates.
37
+ It handles nested app structures (e.g., app.parent.child) and validates that all
38
+ parent apps are properly registered before creating child apps.
39
+ """
40
+
22
41
  help = 'Create a custom Spire app.'
23
42
 
24
- def __init__(self):
25
- super().__init__()
43
+ def handle(self, *args: Any, **kwargs: Any) -> None:
44
+ """
45
+ Main entry point for the management command.
46
+
47
+ Orchestrates the entire app creation process by initializing helper classes,
48
+ collecting user input through an interactive wizard, validating the app structure,
49
+ generating app files and templates, and providing feedback to the user.
26
50
 
27
- self.app_base = Path(settings.BASE_DIR)
28
- self.app_template = Path(django_spire.__file__).parent / 'core/management/commands/spire_startapp_pkg/template/app'
51
+ :param args: Positional arguments (not used).
52
+ :param kwargs: Keyword arguments (not used).
53
+ """
29
54
 
30
- self.template_base = self.app_base / 'templates'
31
- self.html_template = Path(django_spire.__file__).parent / 'template/templates'
55
+ filesystem = FileSystem()
56
+ path_config = PathConfig.default()
57
+ path_resolver = PathResolver()
58
+ registry = AppRegistry()
59
+ reporter = Reporter(self)
32
60
 
33
- self.app_manager = AppManager(self.app_base, self.app_template)
34
- self.app_processor = AppTemplateProcessor()
61
+ template_engine = TemplateEngine()
62
+ template_processor = TemplateProcessor(template_engine, filesystem)
35
63
 
36
- self.html_manager = HTMLTemplateManager(self.template_base, self.html_template)
37
- self.html_processor = HTMLTemplateProcessor()
64
+ validator = AppValidator(reporter, registry, path_resolver, filesystem)
38
65
 
39
- self.reporter = Reporter(self)
66
+ user_input_collector = UserInputCollector(reporter, validator)
40
67
 
41
- def get_app_names(self) -> list[str]:
42
- from django.apps import apps
43
- return [config.name for config in apps.get_app_configs()]
68
+ config_factory = AppConfigFactory(path_resolver)
44
69
 
45
- def handle(self, *_args, **kwargs) -> None:
46
- app = input('Enter the path of the app (e.g., "app.maintenance.work_order"):')
70
+ template_builder = TemplateBuilder(reporter)
47
71
 
48
- if not app:
49
- raise CommandError(self.style.ERROR('The app name is missing'))
72
+ app_generator = AppGenerator(filesystem, template_processor, reporter, path_config)
73
+ # template_generator = TemplateGenerator(filesystem, template_processor, reporter, path_config)
50
74
 
51
- self.app_manager.validate_app_name_format(app)
75
+ user_inputs = user_input_collector.collect_all_inputs()
76
+ app_path = user_inputs['app_path']
52
77
 
53
- components = self.app_manager.parse_app_name(app)
54
- self.app_manager.is_valid_root_apps(components)
78
+ validator.validate_app_format(app_path)
55
79
 
56
- registry = self.get_app_names()
80
+ config = config_factory.create_config(app_path, user_inputs)
81
+ validator.validate_root_app(config.components)
57
82
 
58
- self.reporter.write(
59
- f'Checking app components: {components}\n\n',
60
- self.style.NOTICE
83
+ reporter.write(
84
+ f'\nChecking app components: {config.components}\n',
85
+ reporter.style_notice
61
86
  )
62
87
 
63
- missing = self.app_manager.get_missing_components(components, registry)
88
+ missing = registry.get_missing_components(config.components)
64
89
 
65
90
  if missing:
66
- self.reporter.report_missing_components(missing)
67
- self.reporter.report_app_tree_structure(self.app_base, components, registry, self.app_template)
68
- # self.reporter.report_html_tree_structure(self.template_base, components, registry, self.html_template)
69
-
70
- if not self.reporter.prompt_for_confirmation('\nProceed with app creation? (y/n): '):
71
- self.reporter.write('App creation aborted.', self.style.ERROR)
91
+ reporter.report_missing_components(missing)
92
+
93
+ template_builder.build_app_tree_structure(
94
+ path_resolver.get_base_dir(),
95
+ config.components,
96
+ registry.get_installed_apps(),
97
+ path_config.app_template
98
+ )
99
+
100
+ # template_builder.build_html_tree_structure(
101
+ # path_resolver.get_base_dir(),
102
+ # config.components,
103
+ # registry.get_installed_apps(),
104
+ # path_config.html_template
105
+ # )
106
+
107
+ if not reporter.prompt_confirmation('\nProceed with app creation? (y/n): '):
108
+ reporter.write('App creation aborted.', reporter.style_error)
72
109
  return
73
110
 
74
111
  for module in [missing[-1]]:
75
- self.app_manager.create_custom_app(module, self.app_processor, self.reporter)
76
- # self.html_manager.create_custom_templates(module, self.html_processor, self.reporter)
112
+ module_config = config_factory.create_config(module, user_inputs)
113
+ app_generator.generate(module_config)
114
+ # template_generator.generate(module_config)
77
115
 
78
- self.reporter.report_installed_apps_suggestion(missing)
116
+ reporter.report_installed_apps_suggestion(missing)
79
117
  else:
80
- self.reporter.write('All component(s) exist.', self.style.SUCCESS)
118
+ reporter.write('All component(s) exist.', reporter.style_success)
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.core.management.commands.spire_startapp_pkg.builder import TemplateBuilder
4
+ from django_spire.core.management.commands.spire_startapp_pkg.config import (
5
+ AppConfig,
6
+ AppConfigFactory,
7
+ PathConfig,
8
+ )
9
+ from django_spire.core.management.commands.spire_startapp_pkg.filesystem import (
10
+ FileSystem,
11
+ FileSystemInterface,
12
+ )
13
+ from django_spire.core.management.commands.spire_startapp_pkg.generator import (
14
+ AppGenerator,
15
+ TemplateGenerator,
16
+ )
17
+ from django_spire.core.management.commands.spire_startapp_pkg.maps import generate_replacement_map
18
+ from django_spire.core.management.commands.spire_startapp_pkg.permissions import PermissionInheritanceHandler
19
+ from django_spire.core.management.commands.spire_startapp_pkg.processor import (
20
+ TemplateEngine,
21
+ TemplateProcessor,
22
+ )
23
+ from django_spire.core.management.commands.spire_startapp_pkg.registry import (
24
+ AppRegistry,
25
+ AppRegistryInterface,
26
+ )
27
+ from django_spire.core.management.commands.spire_startapp_pkg.reporter import (
28
+ Reporter,
29
+ ReporterInterface,
30
+ )
31
+ from django_spire.core.management.commands.spire_startapp_pkg.resolver import (
32
+ PathResolver,
33
+ PathResolverInterface,
34
+ )
35
+ from django_spire.core.management.commands.spire_startapp_pkg.user_input import UserInputCollector
36
+ from django_spire.core.management.commands.spire_startapp_pkg.validator import AppValidator
37
+
38
+
39
+ __all__ = [
40
+ 'AppConfig',
41
+ 'AppConfigFactory',
42
+ 'AppGenerator',
43
+ 'AppRegistry',
44
+ 'AppRegistryInterface',
45
+ 'AppValidator',
46
+ 'FileSystem',
47
+ 'FileSystemInterface',
48
+ 'PathConfig',
49
+ 'PathResolver',
50
+ 'PathResolverInterface',
51
+ 'PermissionInheritanceHandler',
52
+ 'Reporter',
53
+ 'ReporterInterface',
54
+ 'TemplateBuilder',
55
+ 'TemplateEngine',
56
+ 'TemplateGenerator',
57
+ 'TemplateProcessor',
58
+ 'UserInputCollector',
59
+ 'generate_replacement_map',
60
+ ]
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from django_spire.core.management.commands.spire_startapp_pkg.maps import generate_replacement_map
6
+
7
+ if TYPE_CHECKING:
8
+ from pathlib import Path
9
+
10
+ from django_spire.core.management.commands.spire_startapp_pkg.reporter import ReporterInterface
11
+
12
+
13
+ class TemplateBuilder:
14
+ """
15
+ Builds and displays tree structures for app and template creation.
16
+
17
+ This class generates visual representations of the file structure
18
+ that will be created for new apps and their associated templates.
19
+ """
20
+
21
+ def __init__(self, reporter: ReporterInterface):
22
+ """
23
+ Initializes the TemplateBuilder with a reporter for output.
24
+
25
+ :param reporter: Reporter instance for displaying output to the user.
26
+ """
27
+
28
+ self._reporter = reporter
29
+
30
+ def build_app_tree_structure(
31
+ self,
32
+ base: Path,
33
+ components: list[str],
34
+ registry: list[str],
35
+ template: Path
36
+ ) -> None:
37
+ """
38
+ Displays a tree structure of the app files that will be created.
39
+
40
+ This method shows the user what Python files, directories, and modules
41
+ will be generated for the new Django app before creation.
42
+
43
+ :param base: Base directory where the app will be created.
44
+ :param components: List of app path components (e.g., ['app', 'human_resource', 'employee']).
45
+ :param registry: List of already registered apps in the Django project.
46
+ :param template: Path to the app template directory.
47
+ """
48
+
49
+ self._reporter.report_tree_structure(
50
+ title='\nThe following app(s) will be created:\n\n',
51
+ base=base,
52
+ components=components,
53
+ registry=registry,
54
+ template=template,
55
+ formatter=self._reporter.format_app_item,
56
+ transformation=self._reporter.transform_app_component,
57
+ )
58
+
59
+ def build_html_tree_structure(
60
+ self,
61
+ base: Path,
62
+ components: list[str],
63
+ registry: list[str],
64
+ template: Path
65
+ ) -> None:
66
+ """
67
+ Displays a tree structure of the HTML template files that will be created.
68
+
69
+ This method shows the user what HTML templates, cards, forms, and pages
70
+ will be generated for the new Django app before creation.
71
+
72
+ :param base: Base directory where templates will be created.
73
+ :param components: List of app path components.
74
+ :param registry: List of already registered apps in the Django project.
75
+ :param template: Path to the HTML template directory.
76
+ """
77
+
78
+ replacement = generate_replacement_map(components)
79
+
80
+ def html_formatter_with_replacement(item: Path) -> str:
81
+ return self._reporter.format_html_item(item, replacement)
82
+
83
+ self._reporter.report_tree_structure(
84
+ title='\nThe following template(s) will be created:\n\n',
85
+ base=base,
86
+ components=components,
87
+ registry=registry,
88
+ template=template,
89
+ formatter=html_formatter_with_replacement,
90
+ transformation=self._reporter.transform_html_component,
91
+ )
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ import django_spire
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from django_spire.core.management.commands.spire_startapp_pkg.resolver import PathResolverInterface
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class PathConfig:
15
+ """
16
+ Configuration for template directory paths.
17
+
18
+ This class holds the paths to the app and HTML template directories
19
+ used for generating new Django apps.
20
+ """
21
+
22
+ app_template: Path
23
+ html_template: Path
24
+
25
+ @classmethod
26
+ def default(cls) -> PathConfig:
27
+ """
28
+ Creates a default PathConfig with standard template locations.
29
+
30
+ :return: PathConfig instance with paths to default app and HTML templates.
31
+ """
32
+
33
+ base = Path(django_spire.__file__).parent
34
+
35
+ return cls(
36
+ app_template=base / 'core/management/commands/spire_startapp_pkg/template/app',
37
+ html_template=base / 'core/management/commands/spire_startapp_pkg/template/templates'
38
+ )
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class AppConfig:
43
+ """
44
+ Configuration for a new Django app being created.
45
+
46
+ This class contains all the information needed to generate a new app,
47
+ including its name, path, components, destinations, and user-provided inputs.
48
+ """
49
+
50
+ app_name: str
51
+ app_path: str
52
+ components: list[str]
53
+ destination: Path
54
+ template_destination: Path
55
+ user_inputs: dict[str, str]
56
+
57
+ @property
58
+ def app_label(self) -> str:
59
+ """
60
+ Gets the Django app label.
61
+
62
+ :return: The app label, either from user input or derived from app name.
63
+ """
64
+
65
+ return self.user_inputs.get('app_label', self.app_name.lower())
66
+
67
+ @property
68
+ def model_name(self) -> str:
69
+ """
70
+ Gets the model class name.
71
+
72
+ :return: The model name, either from user input or TitleCase version of app name.
73
+ """
74
+
75
+ default = ''.join(word.title() for word in self.app_name.split('_'))
76
+ return self.user_inputs.get('model_name', default)
77
+
78
+
79
+ class AppConfigFactory:
80
+ """
81
+ Factory class for creating AppConfig instances.
82
+
83
+ This class handles the creation of AppConfig objects by resolving
84
+ paths and processing user inputs.
85
+ """
86
+
87
+ def __init__(self, path_resolver: PathResolverInterface):
88
+ """
89
+ Initializes the factory with a path resolver.
90
+
91
+ :param path_resolver: Path resolver for determining file system locations.
92
+ """
93
+
94
+ self._path_resolver = path_resolver
95
+
96
+ def create_config(self, app_path: str, user_inputs: dict[str, str]) -> AppConfig:
97
+ """
98
+ Creates an AppConfig from an app path and user inputs.
99
+
100
+ :param app_path: Dot-separated app path (e.g., 'app.human_resource.employee').
101
+ :param user_inputs: Dictionary of user-provided configuration values.
102
+ :return: Configured AppConfig instance ready for app generation.
103
+ """
104
+
105
+ components = app_path.split('.')
106
+ app_name = user_inputs.get('app_name', components[-1])
107
+
108
+ return AppConfig(
109
+ app_name=app_name,
110
+ app_path=app_path,
111
+ components=components,
112
+ destination=self._path_resolver.get_app_destination(components),
113
+ template_destination=self._path_resolver.get_template_destination(components),
114
+ user_inputs=user_inputs
115
+ )
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+
3
+ import shutil
4
+
5
+ from typing import Protocol, TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from pathlib import Path
9
+ from typing import Iterator
10
+
11
+
12
+ class FileSystemInterface(Protocol):
13
+ """
14
+ Protocol defining the interface for file system operations.
15
+
16
+ This protocol specifies the methods required for interacting with
17
+ the file system during app creation.
18
+ """
19
+
20
+ def copy_tree(self, src: Path, dst: Path) -> None: ...
21
+ def create_directory(self, path: Path) -> None: ...
22
+ def exists(self, path: Path) -> bool: ...
23
+ def has_content(self, path: Path) -> bool: ...
24
+ def iterate_files(self, path: Path, pattern: str) -> Iterator[Path]: ...
25
+ def read_file(self, path: Path) -> str: ...
26
+ def rename(self, old: Path, new: Path) -> None: ...
27
+ def write_file(self, path: Path, content: str) -> None: ...
28
+
29
+
30
+ class FileSystem:
31
+ """
32
+ Implementation of file system operations for app generation.
33
+
34
+ This class provides concrete implementations of file system operations
35
+ needed to create directories, copy templates, and manage files.
36
+ """
37
+
38
+ def copy_tree(self, src: Path, dst: Path) -> None:
39
+ """
40
+ Copies an entire directory tree from source to destination.
41
+
42
+ Ignores Python cache directories and compiled files during the copy.
43
+
44
+ :param src: Source directory path to copy from.
45
+ :param dst: Destination directory path to copy to.
46
+ """
47
+
48
+ shutil.copytree(
49
+ src,
50
+ dst,
51
+ dirs_exist_ok=True,
52
+ ignore=shutil.ignore_patterns('__pycache__', '*.pyc')
53
+ )
54
+
55
+ def create_directory(self, path: Path) -> None:
56
+ """
57
+ Creates a directory and all necessary parent directories.
58
+
59
+ :param path: Directory path to create.
60
+ """
61
+
62
+ path.mkdir(parents=True, exist_ok=True)
63
+
64
+ def exists(self, path: Path) -> bool:
65
+ """
66
+ Checks if a path exists on the file system.
67
+
68
+ :param path: Path to check for existence.
69
+ :return: True if the path exists, False otherwise.
70
+ """
71
+
72
+ return path.exists()
73
+
74
+ def has_content(self, path: Path) -> bool:
75
+ """
76
+ Checks if a directory exists and contains any files or subdirectories.
77
+
78
+ :param path: Directory path to check.
79
+ :return: True if the directory exists and has content, False otherwise.
80
+ """
81
+
82
+ return self.exists(path) and any(path.iterdir())
83
+
84
+ def iterate_files(self, path: Path, pattern: str) -> Iterator[Path]:
85
+ """
86
+ Recursively finds all files matching a pattern in a directory.
87
+
88
+ :param path: Directory path to search within.
89
+ :param pattern: Glob pattern to match files (e.g., '*.py', '*.template').
90
+ :return: Iterator of matching file paths.
91
+ """
92
+
93
+ return path.rglob(pattern)
94
+
95
+ def read_file(self, path: Path) -> str:
96
+ """
97
+ Reads the entire contents of a text file.
98
+
99
+ :param path: File path to read.
100
+ :return: File contents as a string.
101
+ """
102
+
103
+ with open(path, 'r', encoding='utf-8') as handle:
104
+ return handle.read()
105
+
106
+ def rename(self, old: Path, new: Path) -> None:
107
+ """
108
+ Renames a file or directory.
109
+
110
+ :param old: Current path of the file or directory.
111
+ :param new: New path for the file or directory.
112
+ """
113
+
114
+ old.rename(new)
115
+
116
+ def write_file(self, path: Path, content: str) -> None:
117
+ """
118
+ Writes content to a text file.
119
+
120
+ :param path: File path to write to.
121
+ :param content: String content to write to the file.
122
+ """
123
+
124
+ with open(path, 'w', encoding='utf-8') as handle:
125
+ handle.write(content)