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,19 +1,22 @@
1
- from dandy.llm import BaseLlmBot, Prompt
1
+ from __future__ import annotations
2
2
 
3
+ from dandy import BaseIntel, Bot, Prompt
3
4
 
4
- class MarkdownFormatLlmBot(BaseLlmBot):
5
5
 
6
- @classmethod
7
- def process(
8
- cls,
9
- markdown_content: str
10
- ) -> str:
6
+ class MarkdownTextIntel(BaseIntel):
7
+ text: str
8
+
9
+
10
+ class MarkdownFormatLlmBot(Bot):
11
+ llm_intel_class = MarkdownTextIntel
12
+
13
+ def process(self, markdown_content: str) -> str:
11
14
  markdown_prompt = Prompt()
12
15
  markdown_prompt.text(
13
16
  'Can you improve the markdown formatting? Do NOT add or change any of the '
14
17
  'content.'
15
18
  )
16
- markdown_prompt.line_break()
17
19
  markdown_prompt.text(markdown_content)
18
20
 
19
- return cls.process_prompt_to_intel(prompt=markdown_prompt).text
21
+ result = self.llm.prompt_to_intel(prompt=markdown_prompt)
22
+ return result.text
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.knowledge.entry.version.converters.docx_converter import \
2
4
  DocxConverter
3
5
  from django_spire.knowledge.entry.version.converters.markdown_converter import \
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.db import models
2
4
  from django.utils.timezone import now
3
5
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.history.querysets import HistoryQuerySet
2
4
 
3
5
 
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import random
4
+
4
5
  from datetime import timedelta
5
6
 
6
7
  from django.utils.timezone import localtime
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from keyring.testing.util import random_string
2
4
 
3
5
  from django_spire.auth.user.tests.factories import create_user
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import time
4
+
2
5
  from unittest.mock import MagicMock, patch
3
6
 
4
7
  from django_spire.core.tests.test_cases import BaseTestCase
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import reverse
2
4
 
3
5
  from django_spire.core.tests.test_cases import BaseTestCase
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import reverse
2
4
 
3
5
  from django_spire.core.tests.test_cases import BaseTestCase
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.urls import reverse
2
4
 
3
5
  from django_spire.core.tests.test_cases import BaseTestCase
@@ -2,6 +2,7 @@ from django.urls import path
2
2
 
3
3
  from django_spire.knowledge.entry.version.views import form_views
4
4
 
5
+
5
6
  app_name = 'form'
6
7
 
7
8
  urlpatterns = [
@@ -2,6 +2,7 @@ from django.urls import path
2
2
 
3
3
  from django_spire.knowledge.entry.version.views import json_views
4
4
 
5
+
5
6
  app_name = 'json'
6
7
 
7
8
  urlpatterns = [
@@ -2,6 +2,7 @@ from django.urls import path
2
2
 
3
3
  from django_spire.knowledge.entry.version.views import page_views
4
4
 
5
+
5
6
  app_name = 'page'
6
7
 
7
8
  urlpatterns = [
@@ -2,6 +2,7 @@ from django.urls import path
2
2
 
3
3
  from django_spire.knowledge.entry.version.views import redirect_views
4
4
 
5
+
5
6
  app_name = 'redirect'
6
7
 
7
8
  urlpatterns = [
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
 
3
5
  from django.core.handlers.wsgi import WSGIRequest
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
 
3
5
  from django.core.handlers.wsgi import WSGIRequest
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.core.handlers.wsgi import WSGIRequest
2
4
  from django.shortcuts import get_object_or_404
3
5
  from django.template.response import TemplateResponse
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django.core.handlers.wsgi import WSGIRequest
2
4
  from django.http import HttpResponseRedirect
3
5
  from django.shortcuts import get_object_or_404
@@ -1,2 +1,4 @@
1
+ from __future__ import annotations
2
+
1
3
  class KnowledgeBaseConversionException(Exception):
2
4
  pass
@@ -1,30 +1,19 @@
1
- from typing import Any
1
+ from __future__ import annotations
2
2
 
3
- from dandy.llm import BaseLlmBot, Prompt, DefaultLlmIntel
3
+ from dandy import Bot, Prompt
4
4
 
5
5
  from django_spire.knowledge.entry.models import Entry
6
6
 
7
7
 
8
- class EntrySearchLlmBot(BaseLlmBot):
8
+ class EntrySearchLlmBot(Bot):
9
9
  @classmethod
10
- def process(
11
- cls,
12
- user_input: str,
13
- entry: Entry
14
- ) -> str:
10
+ def process(cls, user_input: str, entry: Entry) -> str:
15
11
  entry_prompt = Prompt()
16
-
17
12
  entry_prompt.text('You are a helpful assistant that helps users find information about entries.')
18
-
19
13
  entry_prompt.text(f'User Input: {user_input}')
20
-
21
14
  entry_prompt.text(f'Entry: {entry.name}')
22
15
 
23
16
  for version_block in entry.current_version.blocks.all():
24
17
  entry_prompt.text(version_block.render_to_text())
25
18
 
26
- result_intel: DefaultLlmIntel = cls.process_prompt_to_intel(
27
- prompt=entry_prompt
28
- )
29
-
30
- return result_intel.text
19
+ return cls().llm.prompt_to_text(prompt=entry_prompt)
@@ -1,4 +1,6 @@
1
- from dandy.intel import BaseIntel
1
+ from __future__ import annotations
2
+
3
+ from dandy import BaseIntel
2
4
 
3
5
 
4
6
  class CollectionIntel(BaseIntel):
@@ -1,6 +1,6 @@
1
- from typing import List
1
+ from __future__ import annotations
2
2
 
3
- from dandy.intel import BaseIntel, BaseListIntel
3
+ from dandy import BaseIntel, BaseListIntel
4
4
 
5
5
  from django_spire.knowledge.intelligence.intel.collection_intel import CollectionIntel
6
6
 
@@ -11,4 +11,4 @@ class EntryIntel(BaseIntel):
11
11
 
12
12
 
13
13
  class EntriesIntel(BaseListIntel[EntryIntel]):
14
- entry_intel_list: List[EntryIntel]
14
+ entry_intel_list: list[EntryIntel]
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.ai.chat.message_intel import BaseMessageIntel
2
4
 
3
5
 
@@ -1,20 +1,19 @@
1
- from typing import Type
1
+ from __future__ import annotations
2
2
 
3
- from dandy.llm import BaseLlmMap
4
- from dandy.map import Map
3
+ from dandy import Decoder
5
4
 
6
5
  from django_spire.knowledge.collection.models import Collection
7
6
 
8
7
 
9
- def get_collection_map_class() -> Type[BaseLlmMap]:
10
- class CollectionMap(BaseLlmMap):
11
- map_keys_description = 'Knowledge Collection Titles'
12
- map = Map({
8
+ def get_collection_map_class() -> type[Decoder]:
9
+ class CollectionMap(Decoder):
10
+ mapping_keys_description = 'Knowledge Collection Titles'
11
+ mapping = {
13
12
  **{
14
13
  collection.name: collection
15
14
  for collection in Collection.objects.all().annotate_entry_count()
16
15
  },
17
- **{'No Matching Knowledge Collection Titles': None}
18
- })
16
+ 'No Matching Knowledge Collection Titles': None
17
+ }
19
18
 
20
- return CollectionMap
19
+ return CollectionMap
@@ -1,20 +1,19 @@
1
- from typing import Type
1
+ from __future__ import annotations
2
2
 
3
- from dandy.llm import BaseLlmMap
4
- from dandy.map import Map
3
+ from dandy import Decoder
5
4
 
6
5
  from django_spire.knowledge.collection.models import Collection
7
6
 
8
7
 
9
- def get_entry_map_class(collection: Collection) -> Type[BaseLlmMap]:
10
- class EntryMap(BaseLlmMap):
11
- map_keys_description = 'Knowledge Entries'
12
- map = Map({
8
+ def get_entry_map_class(collection: Collection) -> type[Decoder]:
9
+ class EntryMap(Decoder):
10
+ mapping_keys_description = 'Knowledge Entries'
11
+ mapping = {
13
12
  **{
14
13
  entry.name: entry
15
14
  for entry in collection.entries.all()
16
15
  },
17
- **{'No Matching Knowledge Entries': None}
18
- })
16
+ 'No Matching Knowledge Entries': None
17
+ }
19
18
 
20
19
  return EntryMap
@@ -1,18 +1,18 @@
1
- from dandy.workflow import BaseWorkflow
1
+ from __future__ import annotations
2
2
 
3
3
  from django_spire.knowledge.intelligence.bots.entry_search_llm_bot import EntrySearchLlmBot
4
4
  from django_spire.knowledge.intelligence.intel.collection_intel import CollectionIntel
5
5
  from django_spire.knowledge.intelligence.intel.entry_intel import EntriesIntel, EntryIntel
6
- from django_spire.knowledge.intelligence.maps.collection_map import get_collection_map_class
7
6
  from django_spire.knowledge.intelligence.intel.message_intel import KnowledgeMessageIntel
7
+ from django_spire.knowledge.intelligence.maps.collection_map import get_collection_map_class
8
8
  from django_spire.knowledge.intelligence.maps.entry_map import get_entry_map_class
9
9
 
10
10
 
11
- class KnowledgeWorkflow(BaseWorkflow):
11
+ class KnowledgeWorkflow:
12
12
  @classmethod
13
13
  def process(cls, user_input: str) -> KnowledgeMessageIntel:
14
14
  CollectionMap = get_collection_map_class()
15
- collections = CollectionMap.process(user_input)
15
+ collections = CollectionMap().process(user_input).values
16
16
 
17
17
  if collections[0] is None:
18
18
  return KnowledgeMessageIntel(
@@ -26,7 +26,7 @@ class KnowledgeWorkflow(BaseWorkflow):
26
26
  for collection in collections:
27
27
  if collection.entry_count > 0:
28
28
  EntryMap = get_entry_map_class(collection=collection)
29
- entries.extend(EntryMap.process(user_input))
29
+ entries.extend(EntryMap().process(user_input).values)
30
30
 
31
31
  entries = [entry for entry in entries if entry is not None]
32
32
 
@@ -38,10 +38,12 @@ class KnowledgeWorkflow(BaseWorkflow):
38
38
  )
39
39
  )
40
40
 
41
+ entry_search_bot = EntrySearchLlmBot()
42
+
41
43
  entries_intel = EntriesIntel(
42
44
  entry_intel_list=[
43
45
  EntryIntel(
44
- body=EntrySearchLlmBot.process(
46
+ body=entry_search_bot.process(
45
47
  user_input=user_input,
46
48
  entry=entry
47
49
  ),
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  # These imports are for the migrations to work as a single app "django_spire_knowledge"
2
4
 
3
5
  from django_spire.knowledge.entry import models
@@ -1 +1,3 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.knowledge.collection.seeding.seed import *
@@ -1,7 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
 
3
5
  from django import template
4
6
 
7
+
5
8
  register = template.Library()
6
9
 
7
10
 
@@ -1,5 +1,6 @@
1
1
  from django.urls import include, path
2
2
 
3
+
3
4
  app_name = 'knowledge'
4
5
 
5
6
  urlpatterns = [
@@ -0,0 +1,13 @@
1
+ import threading
2
+
3
+ lock = threading.Lock()
4
+
5
+ from django_spire.profiling.middleware.profiling import ProfilingMiddleware
6
+ from django_spire.profiling.panel import ProfilingPanel
7
+
8
+
9
+ __all__ = [
10
+ 'ProfilingMiddleware',
11
+ 'ProfilingPanel',
12
+ 'lock'
13
+ ]
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from django_spire.profiling.middleware.profiling import ProfilingMiddleware
4
+
5
+
6
+ __all__ = ['ProfilingMiddleware']
@@ -5,20 +5,42 @@ import threading
5
5
  import time
6
6
 
7
7
  from pathlib import Path
8
- from typing_extensions import Any, Callable, TYPE_CHECKING
8
+ from typing_extensions import TYPE_CHECKING
9
9
 
10
10
  from django.conf import settings
11
11
  from django.utils.deprecation import MiddlewareMixin
12
12
 
13
+ from django_spire.profiling import lock
14
+
13
15
  try:
14
16
  from pyinstrument import Profiler
15
17
  except ImportError:
16
18
  Profiler = None
17
19
 
18
20
  if TYPE_CHECKING:
21
+ from typing_extensions import Any, Callable
22
+
19
23
  from django.http import HttpRequest, HttpResponse
20
24
 
21
25
 
26
+ IGNORE_EXTENSION = [
27
+ '.eot',
28
+ '.gif',
29
+ '.ico',
30
+ '.jpeg',
31
+ '.jpg',
32
+ '.js',
33
+ '.map',
34
+ '.pdf',
35
+ '.png',
36
+ '.svg',
37
+ '.ttf',
38
+ '.txt',
39
+ '.woff',
40
+ '.woff2',
41
+ '.zip',
42
+ ]
43
+
22
44
  IGNORE_PATH = [
23
45
  '/__',
24
46
  '/__debug__/',
@@ -36,12 +58,13 @@ IGNORE_PATH = [
36
58
  '/debug/',
37
59
  '/debug-toolbar/',
38
60
  '/django_glue/',
61
+ '/django_spire/theme/json/get_config/',
39
62
  '/docs/',
40
63
  '/favicon.ico',
41
64
  '/media/',
42
65
  '/openapi/',
43
66
  '/redoc/',
44
- '/robots.txt',
67
+ '/robots.txt/',
45
68
  '/schema/',
46
69
  '/sitemap.xml',
47
70
  '/static/',
@@ -50,37 +73,15 @@ IGNORE_PATH = [
50
73
  ]
51
74
 
52
75
 
53
- IGNORE_EXTENSION = [
54
- '.css',
55
- '.eot',
56
- '.gif',
57
- '.ico',
58
- '.jpeg',
59
- '.jpg',
60
- '.js',
61
- '.map',
62
- '.pdf',
63
- '.png',
64
- '.svg',
65
- '.ttf',
66
- '.txt',
67
- '.woff',
68
- '.woff2',
69
- '.zip',
70
- ]
71
-
72
-
73
76
  class ProfilingMiddleware(MiddlewareMixin):
74
77
  def __init__(self, get_response: Callable[[HttpRequest], HttpResponse]) -> None:
75
78
  super().__init__(get_response)
76
79
 
77
- if Profiler is None:
78
- message = 'pyinstrument is required for profiling.'
79
- raise ImportError(message)
80
-
81
80
  configuration = {
82
81
  'PROFILING_DIR': os.getenv('PROFILING_DIR', '.profile'),
83
82
  'PROFILING_ENABLED': os.getenv('PROFILING_ENABLED', 'False') == 'True',
83
+ 'PROFILING_MAX_FILES': int(os.getenv('PROFILING_MAX_FILES', '10')),
84
+ 'PROFILE_THRESHOLD': float(os.getenv('PROFILE_THRESHOLD', '0')),
84
85
  }
85
86
 
86
87
  directory = configuration.get('PROFILING_DIR', '.profile')
@@ -97,32 +98,28 @@ class ProfilingMiddleware(MiddlewareMixin):
97
98
  self.directory.mkdir(exist_ok=True)
98
99
 
99
100
  self.enabled = configuration.get('PROFILING_ENABLED', False)
100
- self.profile_threshold = configuration.get('PROFILE_THRESHOLD', 0)
101
+ self.threshold = configuration.get('PROFILE_THRESHOLD', 0)
102
+ self.maximum = configuration.get('PROFILING_MAX_FILES', 10)
101
103
 
102
104
  self.count = 0
103
105
  self.lock = threading.Lock()
104
106
 
105
- def _remove_profile(self) -> None:
106
- files = self.directory.glob('*.html')
107
- profiles = list(files)
107
+ def _remove_profiles(self) -> None:
108
+ files = list(self.directory.glob('*.html'))
109
+
110
+ if len(files) <= self.maximum:
111
+ return
108
112
 
109
- profiles.sort(
110
- key=lambda p: p.stat().st_mtime,
113
+ files.sort(
114
+ key=lambda p: p.stat().st_mtime if p.exists() else 0,
111
115
  reverse=True
112
116
  )
113
117
 
114
- maximum = 10
115
-
116
- if len(profiles) > maximum:
117
- for profile in profiles[maximum:]:
118
+ for profile in files[self.maximum:]:
119
+ if profile.exists():
118
120
  profile.unlink()
119
121
 
120
- def _save_profile(
121
- self,
122
- profiler: Profiler,
123
- request: HttpRequest,
124
- duration_ms: float
125
- ) -> None:
122
+ def _save_profile(self, profiler: Profiler, request: HttpRequest, duration: float) -> None:
126
123
  with self.lock:
127
124
  timestamp = int(time.time() * 1000)
128
125
  method = request.method
@@ -131,21 +128,29 @@ class ProfilingMiddleware(MiddlewareMixin):
131
128
  if not path or path == '_':
132
129
  path = 'root'
133
130
 
134
- filename = f'{timestamp}_{method}_{path}_{duration_ms:.1f}ms_{request._profiling_id}.html'
135
-
136
- path = str(self.directory / filename)
137
- profiler.write_html(path)
131
+ profileid = request._profiling_id
132
+ filename = f'{timestamp}_{method}_{path}_{duration:.1f}ms_{profileid}.html'
133
+ filepath = self.directory / filename
138
134
 
139
- self._remove_profile()
135
+ with lock:
136
+ profiler.write_html(str(filepath))
137
+ self._remove_profiles()
140
138
 
141
- def _should_skip_profiling(self, request: HttpRequest) -> bool:
139
+ def _should_skip(self, request: HttpRequest) -> bool:
142
140
  path = request.path
143
141
 
144
- return (
145
- any(path.startswith(pattern) or pattern in path for pattern in IGNORE_PATH) or
146
- any(path.endswith(extension) or extension in path for extension in IGNORE_EXTENSION)
142
+ path_match = any(
143
+ path.startswith(pattern) or pattern in path
144
+ for pattern in IGNORE_PATH
147
145
  )
148
146
 
147
+ extension_match = any(
148
+ path.endswith(extension) or extension in path
149
+ for extension in IGNORE_EXTENSION
150
+ )
151
+
152
+ return path_match or extension_match
153
+
149
154
  def process_view(
150
155
  self,
151
156
  request: HttpRequest,
@@ -159,7 +164,7 @@ class ProfilingMiddleware(MiddlewareMixin):
159
164
  if not self.enabled:
160
165
  return None
161
166
 
162
- if self._should_skip_profiling(request):
167
+ if self._should_skip(request):
163
168
  return None
164
169
 
165
170
  with self.lock:
@@ -167,7 +172,7 @@ class ProfilingMiddleware(MiddlewareMixin):
167
172
  request._profiling_id = self.count
168
173
 
169
174
  profiler = Profiler(interval=0.001)
170
- start_time = time.time()
175
+ start = time.time()
171
176
  profiler.start()
172
177
 
173
178
  try:
@@ -177,15 +182,15 @@ class ProfilingMiddleware(MiddlewareMixin):
177
182
  response.render()
178
183
  except Exception:
179
184
  profiler.stop()
180
- duration_ms = (time.time() - start_time) * 1000
181
- self._save_profile(profiler, request, duration_ms)
185
+ duration = (time.time() - start) * 1000
186
+ self._save_profile(profiler, request, duration)
182
187
 
183
188
  raise
184
189
  else:
185
190
  profiler.stop()
186
- duration_ms = (time.time() - start_time) * 1000
191
+ duration = (time.time() - start) * 1000
187
192
 
188
- if duration_ms >= self.profile_threshold:
189
- self._save_profile(profiler, request, duration_ms)
193
+ if duration >= self.threshold:
194
+ self._save_profile(profiler, request, duration)
190
195
 
191
196
  return response