django-spire 0.16.12__py3-none-any.whl → 0.17.0__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 (170) hide show
  1. django_spire/ai/admin.py +3 -1
  2. django_spire/ai/apps.py +2 -0
  3. django_spire/ai/chat/admin.py +15 -9
  4. django_spire/ai/chat/apps.py +4 -1
  5. django_spire/ai/chat/auth/controller.py +3 -1
  6. django_spire/ai/chat/choices.py +2 -0
  7. django_spire/ai/chat/intelligence/maps/intent_llm_map.py +8 -5
  8. django_spire/ai/chat/intelligence/prompts.py +4 -2
  9. django_spire/ai/chat/intelligence/workflows/chat_workflow.py +27 -28
  10. django_spire/ai/chat/message_intel.py +7 -4
  11. django_spire/ai/chat/models.py +8 -9
  12. django_spire/ai/chat/querysets.py +3 -1
  13. django_spire/ai/chat/responses.py +19 -10
  14. django_spire/ai/chat/tools.py +20 -15
  15. django_spire/ai/chat/urls/message_urls.py +2 -1
  16. django_spire/ai/chat/urls/page_urls.py +1 -0
  17. django_spire/ai/chat/views/message_request_views.py +2 -0
  18. django_spire/ai/chat/views/message_response_views.py +4 -4
  19. django_spire/ai/chat/views/message_views.py +2 -0
  20. django_spire/ai/chat/views/page_views.py +7 -2
  21. django_spire/ai/chat/views/template_views.py +2 -0
  22. django_spire/ai/decorators.py +13 -7
  23. django_spire/ai/mixins.py +4 -2
  24. django_spire/ai/models.py +7 -2
  25. django_spire/ai/prompt/bots.py +14 -32
  26. django_spire/ai/prompt/intel.py +1 -1
  27. django_spire/ai/prompt/prompts.py +7 -1
  28. django_spire/ai/prompt/system/bots.py +42 -75
  29. django_spire/ai/prompt/system/intel.py +5 -4
  30. django_spire/ai/prompt/system/prompts.py +5 -1
  31. django_spire/ai/prompt/system/system_prompt_cli.py +15 -9
  32. django_spire/ai/prompt/tests/test_bots.py +14 -11
  33. django_spire/ai/prompt/text_to_prompt_cli.py +5 -2
  34. django_spire/ai/prompt/tuning/bot_tuning_cli.py +14 -13
  35. django_spire/ai/prompt/tuning/bots.py +68 -116
  36. django_spire/ai/prompt/tuning/intel.py +1 -1
  37. django_spire/ai/prompt/tuning/mixins.py +2 -0
  38. django_spire/ai/prompt/tuning/prompt_tuning_cli.py +8 -8
  39. django_spire/ai/prompt/tuning/prompts.py +4 -2
  40. django_spire/ai/sms/admin.py +3 -1
  41. django_spire/ai/sms/apps.py +2 -0
  42. django_spire/ai/sms/decorators.py +2 -0
  43. django_spire/ai/sms/intel.py +4 -2
  44. django_spire/ai/sms/intelligence/workflows/sms_conversation_workflow.py +8 -8
  45. django_spire/ai/sms/models.py +16 -14
  46. django_spire/ai/sms/querysets.py +4 -1
  47. django_spire/ai/sms/tools.py +18 -16
  48. django_spire/ai/sms/urls.py +1 -1
  49. django_spire/ai/sms/views.py +2 -0
  50. django_spire/ai/tests/test_ai.py +3 -5
  51. django_spire/ai/urls.py +1 -0
  52. django_spire/consts.py +1 -1
  53. django_spire/contrib/seeding/field/django/seeder.py +6 -4
  54. django_spire/contrib/seeding/field/llm.py +1 -2
  55. django_spire/contrib/seeding/intelligence/bots/field_seeding_bots.py +7 -8
  56. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +15 -16
  57. django_spire/contrib/seeding/intelligence/intel.py +1 -1
  58. django_spire/contrib/seeding/intelligence/prompts/factory.py +5 -7
  59. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +3 -5
  60. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +1 -2
  61. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +3 -5
  62. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +1 -1
  63. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +3 -3
  64. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +1 -1
  65. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +2 -4
  66. django_spire/contrib/seeding/management/commands/seeding.py +5 -2
  67. django_spire/contrib/seeding/model/base.py +12 -10
  68. django_spire/contrib/seeding/model/django/seeder.py +13 -10
  69. django_spire/contrib/seeding/tests/test_seeding.py +1 -1
  70. django_spire/core/management/commands/spire_startapp.py +84 -46
  71. django_spire/core/management/commands/spire_startapp_pkg/__init__.py +60 -0
  72. django_spire/core/management/commands/spire_startapp_pkg/builder.py +91 -0
  73. django_spire/core/management/commands/spire_startapp_pkg/config.py +115 -0
  74. django_spire/core/management/commands/spire_startapp_pkg/filesystem.py +125 -0
  75. django_spire/core/management/commands/spire_startapp_pkg/generator.py +167 -0
  76. django_spire/core/management/commands/spire_startapp_pkg/maps.py +783 -25
  77. django_spire/core/management/commands/spire_startapp_pkg/permissions.py +147 -0
  78. django_spire/core/management/commands/spire_startapp_pkg/processor.py +144 -57
  79. django_spire/core/management/commands/spire_startapp_pkg/registry.py +89 -0
  80. django_spire/core/management/commands/spire_startapp_pkg/reporter.py +245 -108
  81. django_spire/core/management/commands/spire_startapp_pkg/resolver.py +86 -0
  82. django_spire/core/management/commands/spire_startapp_pkg/user_input.py +252 -0
  83. django_spire/core/management/commands/spire_startapp_pkg/validator.py +96 -0
  84. django_spire/core/middleware/__init__.py +1 -2
  85. django_spire/knowledge/admin.py +2 -0
  86. django_spire/knowledge/apps.py +2 -0
  87. django_spire/knowledge/auth/controller.py +2 -0
  88. django_spire/knowledge/collection/admin.py +3 -0
  89. django_spire/knowledge/collection/forms.py +2 -0
  90. django_spire/knowledge/collection/models.py +4 -0
  91. django_spire/knowledge/collection/querysets.py +3 -3
  92. django_spire/knowledge/collection/seeding/seed.py +2 -0
  93. django_spire/knowledge/collection/tests/factories.py +2 -0
  94. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +2 -0
  95. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +2 -0
  96. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +2 -0
  97. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +2 -0
  98. django_spire/knowledge/collection/views/json_views.py +2 -0
  99. django_spire/knowledge/collection/views/page_views.py +2 -0
  100. django_spire/knowledge/context_processors.py +2 -0
  101. django_spire/knowledge/entry/services/tool_service.py +1 -0
  102. django_spire/knowledge/entry/services/transformation_services.py +0 -1
  103. django_spire/knowledge/entry/tests/factories.py +3 -0
  104. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +2 -0
  105. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +2 -0
  106. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +2 -0
  107. django_spire/knowledge/entry/urls/form_urls.py +1 -0
  108. django_spire/knowledge/entry/urls/json_urls.py +1 -0
  109. django_spire/knowledge/entry/urls/page_urls.py +1 -0
  110. django_spire/knowledge/entry/urls/template_urls.py +1 -0
  111. django_spire/knowledge/entry/version/admin.py +2 -0
  112. django_spire/knowledge/entry/version/block/admin.py +2 -0
  113. django_spire/knowledge/entry/version/block/blocks/heading_block.py +2 -0
  114. django_spire/knowledge/entry/version/block/blocks/sub_heading_block.py +2 -0
  115. django_spire/knowledge/entry/version/block/blocks/text_block.py +2 -0
  116. django_spire/knowledge/entry/version/block/maps.py +2 -0
  117. django_spire/knowledge/entry/version/block/models.py +2 -0
  118. django_spire/knowledge/entry/version/block/tests/factories.py +2 -0
  119. django_spire/knowledge/entry/version/block/tests/test_urls/test_json_urls.py +2 -0
  120. django_spire/knowledge/entry/version/block/views/json_views.py +2 -0
  121. django_spire/knowledge/entry/version/intelligence/bots/markdown_format_llm_bot.py +12 -9
  122. django_spire/knowledge/entry/version/maps.py +2 -0
  123. django_spire/knowledge/entry/version/models.py +2 -0
  124. django_spire/knowledge/entry/version/querysets.py +2 -0
  125. django_spire/knowledge/entry/version/seeding/seeder.py +1 -0
  126. django_spire/knowledge/entry/version/tests/factories.py +2 -0
  127. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +3 -0
  128. django_spire/knowledge/entry/version/tests/test_urls/test_form_urls.py +2 -0
  129. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +2 -0
  130. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +2 -0
  131. django_spire/knowledge/entry/version/urls/form_urls.py +1 -0
  132. django_spire/knowledge/entry/version/urls/json_urls.py +1 -0
  133. django_spire/knowledge/entry/version/urls/page_urls.py +1 -0
  134. django_spire/knowledge/entry/version/urls/redirect_urls.py +1 -0
  135. django_spire/knowledge/entry/version/views/form_views.py +2 -0
  136. django_spire/knowledge/entry/version/views/json_views.py +2 -0
  137. django_spire/knowledge/entry/version/views/page_views.py +2 -0
  138. django_spire/knowledge/entry/version/views/redirect_views.py +2 -0
  139. django_spire/knowledge/exceptions.py +2 -0
  140. django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +5 -16
  141. django_spire/knowledge/intelligence/intel/collection_intel.py +3 -1
  142. django_spire/knowledge/intelligence/intel/entry_intel.py +3 -3
  143. django_spire/knowledge/intelligence/intel/message_intel.py +2 -0
  144. django_spire/knowledge/intelligence/maps/collection_map.py +9 -10
  145. django_spire/knowledge/intelligence/maps/entry_map.py +8 -9
  146. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +8 -6
  147. django_spire/knowledge/models.py +2 -0
  148. django_spire/knowledge/seeding/seed.py +2 -0
  149. django_spire/knowledge/templatetags/spire_knowledge_tags.py +3 -0
  150. django_spire/knowledge/urls/__init__.py +1 -0
  151. django_spire/profiling/__init__.py +13 -0
  152. django_spire/profiling/middleware/__init__.py +6 -0
  153. django_spire/{core → profiling}/middleware/profiling.py +63 -58
  154. django_spire/profiling/panel.py +345 -0
  155. django_spire/profiling/templates/panel.html +166 -0
  156. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/METADATA +5 -4
  157. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/RECORD +160 -157
  158. django_spire/core/management/commands/spire_startapp_pkg/constants.py +0 -4
  159. django_spire/core/management/commands/spire_startapp_pkg/manager.py +0 -176
  160. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_detail_card.html +0 -24
  161. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_form_card.html +0 -9
  162. django_spire/core/management/commands/spire_startapp_pkg/template/templates/card/spirechildapp_list_card.html +0 -18
  163. django_spire/core/management/commands/spire_startapp_pkg/template/templates/form/spirechildapp_form.html +0 -22
  164. django_spire/core/management/commands/spire_startapp_pkg/template/templates/item/spirechildapp_item.html +0 -24
  165. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_detail_page.html +0 -13
  166. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_form_page.html +0 -13
  167. django_spire/core/management/commands/spire_startapp_pkg/template/templates/page/spirechildapp_list_page.html +0 -9
  168. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/WHEEL +0 -0
  169. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/licenses/LICENSE.md +0 -0
  170. {django_spire-0.16.12.dist-info → django_spire-0.17.0.dist-info}/top_level.txt +0 -0
@@ -1,29 +1,28 @@
1
- from dandy.llm import BaseLlmBot, LlmConfigOptions
2
-
1
+ from dandy import Bot, LlmConfigOptions
3
2
 
4
3
  from django_spire.contrib.seeding.intelligence.intel import SourceIntel
4
+ from django_spire.contrib.seeding.intelligence.prompts.generate_django_model_seeder_prompts import (
5
+ generate_django_model_seeder_user_prompt,
6
+ generate_django_model_seeder_system_prompt
7
+ )
5
8
 
6
9
 
7
- from django_spire.contrib.seeding.intelligence.prompts.generate_django_model_seeder_prompts import \
8
- generate_django_model_seeder_user_prompt, generate_django_model_seeder_system_prompt
9
-
10
-
11
- class SeederGeneratorBot(BaseLlmBot):
10
+ class SeederGeneratorBot(Bot):
11
+ llm_config = 'PYTHON_MODULE'
12
12
 
13
- config = 'PYTHON_MODULE'
14
-
15
- config_options = LlmConfigOptions(
13
+ llm_config_options = LlmConfigOptions(
16
14
  temperature=0.3,
17
15
  randomize_seed=True
18
16
  )
19
17
 
20
- @classmethod
18
+ llm_role = 'You are an expert Python developer specializing in Django model seeders.'
19
+
21
20
  def process(
22
- cls,
23
- model_import: str,
24
- model_description: str
25
- ):
26
- return cls.process_prompt_to_intel(
21
+ self,
22
+ model_import: str,
23
+ model_description: str
24
+ ) -> SourceIntel:
25
+ return self.llm.prompt_to_intel(
27
26
  prompt=generate_django_model_seeder_user_prompt(
28
27
  model_import,
29
28
  model_description,
@@ -1,4 +1,4 @@
1
- from dandy.intel import BaseIntel
1
+ from dandy import BaseIntel
2
2
 
3
3
 
4
4
  class SeedingIntel(BaseIntel):
@@ -1,19 +1,17 @@
1
- from typing import Type
2
-
3
1
  from django.db.models.base import Model
4
2
 
5
- from dandy.llm import Prompt
3
+ from dandy import Prompt
6
4
 
7
5
 
8
6
  class SeedingModelClassPromptFactory:
9
7
 
10
- def __init__(self, model_class: Type[Model]):
8
+ def __init__(self, model_class: type[Model]):
11
9
  self.model_class = model_class
12
10
 
13
11
  def objective_prompt(
14
- self,
15
- model_description: str,
16
- sector_description: str,
12
+ self,
13
+ model_description: str,
14
+ sector_description: str
17
15
  ) -> Prompt:
18
16
  model_name = self.model_class._meta.verbose_name_plural.title()
19
17
 
@@ -1,13 +1,11 @@
1
- from typing_extensions import Type
2
-
3
1
  from django.db.models import Model
4
2
 
5
- from dandy.llm import Prompt
3
+ from dandy import Prompt
6
4
 
7
5
 
8
6
  def foreign_key_selection_prompt(
9
- model_class: Type[Model],
10
- related_model_class: Type[Model]
7
+ model_class: type[Model],
8
+ related_model_class: type[Model]
11
9
  ) -> Prompt:
12
10
  model_name = model_class._meta.verbose_name.title()
13
11
  related_model_name = related_model_class._meta.verbose_name.title()
@@ -1,7 +1,6 @@
1
- import os
2
1
  from pathlib import Path
3
2
 
4
- from dandy.llm import Prompt
3
+ from dandy import Prompt
5
4
 
6
5
 
7
6
  _RELATIVE_BASE_DIR = Path(Path(__file__).parent.parent.parent.parent.parent.parent.resolve())
@@ -1,14 +1,12 @@
1
- from typing_extensions import List, Type
2
-
3
1
  from django.contrib.contenttypes.models import ContentType
4
2
  from django.db.models import Model
5
3
 
6
- from dandy.llm import Prompt
4
+ from dandy import Prompt
7
5
 
8
6
 
9
7
  def generic_relationship_selection_prompt(
10
- model_class: Type[Model],
11
- related_model_classes: List[Type[Model]]
8
+ model_class: type[Model],
9
+ related_model_classes: list[type[Model]]
12
10
  ) -> Prompt:
13
11
  related_model_classes_map = {
14
12
  related_model_class._meta.verbose_name.title(): {
@@ -2,7 +2,7 @@ from random import shuffle
2
2
 
3
3
  from django.db.models import Model
4
4
 
5
- from dandy.llm import Prompt
5
+ from dandy import Prompt
6
6
 
7
7
 
8
8
  def hierarchical_selection_prompt(
@@ -1,11 +1,11 @@
1
- from dandy.llm import Prompt
1
+ from dandy import Prompt
2
2
  from django.db.models.enums import TextChoices
3
3
  from django.db.models.fields import Field
4
4
 
5
5
 
6
6
  def model_field_choices_prompt(
7
- model_field: Field,
8
- choices: TextChoices,
7
+ model_field: Field,
8
+ choices: TextChoices
9
9
  ) -> Prompt:
10
10
  model_name = model_field.field.model.__name__
11
11
  field_name = model_field.field.name
@@ -1,4 +1,4 @@
1
- from dandy.llm import Prompt
1
+ from dandy import Prompt
2
2
 
3
3
 
4
4
  def negation_prompt(
@@ -1,12 +1,10 @@
1
1
  from django.db.models import Model
2
2
 
3
- from typing_extensions import Type
4
-
5
- from dandy.llm import Prompt
3
+ from dandy import Prompt
6
4
 
7
5
 
8
6
  def objective_prompt(
9
- model_class: Type[Model],
7
+ model_class: type[Model],
10
8
  model_description: str,
11
9
  sector_description: str,
12
10
  ) -> Prompt:
@@ -31,12 +31,15 @@ class Command(BaseCommand):
31
31
  # @recorder_to_html_file('seeding_generator')
32
32
  def handle(self, *args, **kwargs):
33
33
  if not kwargs['model_import'] or not kwargs['model_description']:
34
- raise CommandError('You must provide a model import path and a model description')
34
+ message = 'You must provide a model import path and a model description'
35
+ raise CommandError(message)
35
36
 
36
37
  model_import = kwargs['model_import']
37
38
  model_description = ' '.join(kwargs['model_description'])
38
39
 
39
- source_intel = SeederGeneratorBot.process(model_import, model_description)
40
+ # Instantiate the bot
41
+ bot = SeederGeneratorBot()
42
+ source_intel = bot.process(model_import, model_description)
40
43
 
41
44
  Path(_SEEDING_OUTPUT_PATH).mkdir(parents=True, exist_ok=True)
42
45
 
@@ -1,8 +1,8 @@
1
1
  from abc import ABC, abstractmethod
2
2
 
3
3
  from dandy.recorder import recorder_to_html_file
4
- from dandy.cache import SqliteCache
5
- from dandy.cache.utils import generate_hash_key
4
+ from dandy import SqliteCache
5
+ from dandy.cache.utils import generate_cache_key
6
6
 
7
7
  from django_spire.contrib.seeding.field.override import FieldOverride
8
8
  from django_spire.contrib.seeding.model.config import FieldsConfig
@@ -59,10 +59,7 @@ class BaseModelSeeder(ABC):
59
59
 
60
60
  @classmethod
61
61
  def clear_cache(cls):
62
- SqliteCache(
63
- cache_name=cls.cache_name,
64
- limit=cls.cache_limit
65
- ).clear(cache_name=cls.cache_name)
62
+ SqliteCache.clear(cache_name=cls.cache_name)
66
63
 
67
64
  @classmethod
68
65
  @abstractmethod
@@ -80,9 +77,14 @@ class BaseModelSeeder(ABC):
80
77
  field_config = cls.get_field_config().override(fields) if fields else cls.get_field_config()
81
78
 
82
79
  if cls.cache_seed:
83
- hash_key = generate_hash_key(cls.seed_data, count=count, field_config=field_config.fields)
80
+ cache_key = generate_cache_key(
81
+ cls.seed_data,
82
+ count=count,
83
+ fields=field_config.fields
84
+ )
85
+
84
86
  cache = SqliteCache(cache_name=cls.cache_name, limit=cls.cache_limit)
85
- formatted_seed_data = cache.get(hash_key)
87
+ formatted_seed_data = cache.get(cache_key)
86
88
 
87
89
  if formatted_seed_data:
88
90
  return formatted_seed_data
@@ -95,13 +97,13 @@ class BaseModelSeeder(ABC):
95
97
  if len(seeder.seeder_fields) > 0:
96
98
  seed_data.append(seeder.seed(cls, count))
97
99
 
98
- formatted_seed_data = [dict() for _ in range(max(len(sublist) for sublist in seed_data))]
100
+ formatted_seed_data = [{} for _ in range(max(len(sublist) for sublist in seed_data))]
99
101
  for sublist in seed_data:
100
102
  for i, d in enumerate(sublist):
101
103
  formatted_seed_data[i].update(d)
102
104
 
103
105
  if cls.cache_seed:
104
- cache.set(hash_key, formatted_seed_data)
106
+ cache.set(cache_key, formatted_seed_data)
105
107
 
106
108
  return formatted_seed_data
107
109
 
@@ -1,10 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Type, TypeVar
3
+ from typing import TypeVar, TYPE_CHECKING
4
4
 
5
- from dandy.llm import Prompt
6
5
  from django.db.models import ForeignKey
7
-
8
6
  from django.db.models.base import Model
9
7
 
10
8
  from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
@@ -14,6 +12,9 @@ from django_spire.contrib.seeding.field.static import StaticFieldSeeder
14
12
  from django_spire.contrib.seeding.model.base import BaseModelSeeder
15
13
  from django_spire.contrib.seeding.model.django.config import DjangoModelFieldsConfig
16
14
 
15
+ if TYPE_CHECKING:
16
+ from dandy import Prompt
17
+
17
18
 
18
19
  TypeModel = TypeVar('TypeModel', bound=Model)
19
20
 
@@ -21,7 +22,7 @@ TypeModel = TypeVar('TypeModel', bound=Model)
21
22
  class DjangoModelSeeder(BaseModelSeeder):
22
23
  field_config_class = DjangoModelFieldsConfig
23
24
 
24
- model_class: Type[Model]
25
+ model_class: type[Model]
25
26
  prompt: Prompt = None
26
27
  _field_seeders = [
27
28
  CustomFieldSeeder,
@@ -32,14 +33,16 @@ class DjangoModelSeeder(BaseModelSeeder):
32
33
  ]
33
34
 
34
35
  @classmethod
35
- def __init_subclass__(cls, **kwargs):
36
+ def __init_subclass__(cls, **kwargs) -> None:
36
37
  super().__init_subclass__(**kwargs)
37
38
 
38
39
  if cls.model_class is None:
39
- raise ValueError("Seeds must have a model class")
40
+ message = "Seeds must have a model class"
41
+ raise ValueError(message)
40
42
 
41
43
  if cls.fields is None:
42
- raise ValueError("Seeds must have fields")
44
+ message = "Seeds must have fields"
45
+ raise ValueError(message)
43
46
 
44
47
  @classmethod
45
48
  def field_names(cls) -> list[str]:
@@ -51,9 +54,9 @@ class DjangoModelSeeder(BaseModelSeeder):
51
54
 
52
55
  @classmethod
53
56
  def seed_database(
54
- cls,
55
- count=1,
56
- fields: dict | None = None
57
+ cls,
58
+ count=1,
59
+ fields: dict | None = None
57
60
  ) -> list[TypeModel]:
58
61
  model_objects = cls.seed(count, fields)
59
62
  return cls.model_class.objects.bulk_create(model_objects)
@@ -1,4 +1,4 @@
1
- # from dandy.llm import Prompt
1
+ # from dandy import Prompt
2
2
  # from django.test import TestCase
3
3
  #
4
4
  # from django_spire.seeding.processor import SeedingProcessor
@@ -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
+ )