django-spire 0.20.0__py3-none-any.whl → 0.20.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,8 @@ from typing import Callable, TYPE_CHECKING
5
5
  from dandy import Decoder
6
6
 
7
7
  from django_spire.auth.controller.controller import AppAuthController
8
+ from django_spire.conf import settings
9
+ from django_spire.core.utils import get_callable_from_module_string_and_validate_arguments
8
10
  from django_spire.knowledge.intelligence.workflows.knowledge_workflow import knowledge_search_workflow
9
11
 
10
12
  if TYPE_CHECKING:
@@ -20,8 +22,11 @@ def generate_intent_decoder(
20
22
  if AppAuthController(app_name='knowledge', request=request).can_view():
21
23
  intent_dict['The user is looking for information or knowledge on something.'] = knowledge_search_workflow
22
24
 
23
- if default_callable is not None:
24
- intent_dict['None of the above choices match the user\'s intent'] = default_callable
25
+ if settings.AI_CHAT_DEFAULT_CALLABLE is not None:
26
+ intent_dict['None of the above choices match the user\'s intent'] = get_callable_from_module_string_and_validate_arguments(
27
+ settings.AI_CHAT_DEFAULT_CALLABLE,
28
+ ['request', 'user_input', 'message_history']
29
+ )
25
30
 
26
31
  return Decoder(
27
32
  mapping_keys_description='Intent of the User\'s Request',
@@ -8,25 +8,15 @@ from dandy.recorder import recorder_to_html_file
8
8
  from django_spire.ai.chat.intelligence.decoders.tools import generate_intent_decoder
9
9
  from django_spire.ai.chat.message_intel import BaseMessageIntel, DefaultMessageIntel
10
10
  from django_spire.ai.decorators import log_ai_interaction_from_recorder
11
+ from django_spire.conf import settings
12
+ from django_spire.core.utils import get_callable_from_module_string_and_validate_arguments
11
13
 
12
14
  if TYPE_CHECKING:
13
15
  from dandy.llm.request.message import MessageHistory
14
16
  from django.core.handlers.wsgi import WSGIRequest
15
17
 
16
18
 
17
- def SpireChatWorkflow(
18
- request: WSGIRequest,
19
- user_input: str,
20
- message_history: MessageHistory | None = None
21
- ) -> BaseMessageIntel:
22
- return chat_workflow(
23
- request=request,
24
- user_input=user_input,
25
- message_history=message_history
26
- )
27
-
28
-
29
- def default_chat_response(
19
+ def default_chat_callable(
30
20
  request: WSGIRequest,
31
21
  user_input: str,
32
22
  message_history: MessageHistory | None = None
@@ -45,13 +35,6 @@ def chat_workflow(
45
35
  user_input: str,
46
36
  message_history: MessageHistory | None = None
47
37
  ) -> BaseMessageIntel:
48
- intent_decoder = generate_intent_decoder(
49
- request=request,
50
- default_callable=default_chat_response,
51
- )
52
-
53
- intent_process = intent_decoder.process(user_input, max_return_values=1)[0]
54
-
55
38
  @log_ai_interaction_from_recorder(request.user)
56
39
  def run_workflow_process(callable_: Callable) -> BaseMessageIntel | None:
57
40
  return callable_(
@@ -60,11 +43,28 @@ def chat_workflow(
60
43
  message_history=message_history,
61
44
  )
62
45
 
63
- message_intel = run_workflow_process(intent_process)
46
+ if settings.AI_CHAT_CALLABLE is not None:
47
+ chat_callable = get_callable_from_module_string_and_validate_arguments(
48
+ settings.AI_CHAT_CALLABLE,
49
+ ['request', 'user_input', 'message_history']
50
+ )
51
+
52
+ message_intel = run_workflow_process(chat_callable)
53
+
54
+ else:
55
+
56
+ intent_decoder = generate_intent_decoder(
57
+ request=request,
58
+ default_callable=default_chat_callable,
59
+ )
60
+
61
+ intent_process = intent_decoder.process(user_input, max_return_values=1)[0]
62
+
63
+ message_intel = run_workflow_process(intent_process)
64
64
 
65
65
  if not isinstance(message_intel, BaseMessageIntel):
66
66
  if message_intel is None:
67
- return default_chat_response(
67
+ return default_chat_callable(
68
68
  request=request,
69
69
  user_input=user_input,
70
70
  message_history=message_history
django_spire/consts.py CHANGED
@@ -1,4 +1,4 @@
1
- __VERSION__ = '0.20.0'
1
+ __VERSION__ = '0.20.2'
2
2
 
3
3
  MAINTENANCE_MODE_SETTINGS_NAME = 'MAINTENANCE_MODE'
4
4
 
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from django.conf import settings
4
4
  from django.core.mail import EmailMessage
5
5
 
6
+ from django_spire.file.models import File
6
7
  from django_spire.notification.models import Notification
7
8
 
8
9
 
@@ -27,6 +28,11 @@ class EmailHelper:
27
28
  self.from_email = settings.DEFAULT_FROM_EMAIL
28
29
  self.fail_silently = fail_silently
29
30
 
31
+ self.attachments = []
32
+ for attachment in list(email.attachments.all()):
33
+ with attachment.file.open('rb') as f:
34
+ self.attachments.append((attachment.name, f.read(), attachment.type))
35
+
30
36
 
31
37
  class SendGridEmailHelper(EmailHelper):
32
38
  def __init__(
@@ -58,7 +64,9 @@ class SendGridEmailHelper(EmailHelper):
58
64
  to=self.to,
59
65
  cc=self.cc,
60
66
  bcc=self.bcc,
67
+ attachments=self.attachments
61
68
  )
69
+
62
70
  msg.template_id = self.template_id
63
71
  msg.dynamic_template_data = self.template_data
64
72
  msg.send(fail_silently=self.fail_silently)
@@ -0,0 +1,19 @@
1
+ # Generated by Django 5.1.8 on 2025-11-13 20:22
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('django_spire_file', '0001_initial'),
10
+ ('django_spire_notification_email', '0002_emailnotification_bcc_emailnotification_cc_and_more'),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.AddField(
15
+ model_name='emailnotification',
16
+ name='attachments',
17
+ field=models.ManyToManyField(blank=True, related_name='attachments', related_query_name='attachment', to='django_spire_file.file'),
18
+ ),
19
+ ]
@@ -1,10 +1,15 @@
1
1
  from django.db import models
2
2
 
3
- from django_spire.notification.models import Notification
3
+ from django_spire.file.models import File
4
4
  from django_spire.notification.email.querysets import EmailNotificationQuerySet
5
+ from django_spire.notification.models import Notification
5
6
 
6
7
 
7
8
  class EmailNotification(models.Model):
9
+ """
10
+ It is important to note size limits for email content contained in this model. E.g., Sendgrid has a hard total email
11
+ limit of 30mb (and a recommended limit of 10mb for attachments): https://www.twilio.com/docs/sendgrid/ui/sending-email/attachments-with-digioh#-Limitations
12
+ """
8
13
  notification = models.OneToOneField(
9
14
  Notification,
10
15
  editable=False,
@@ -12,6 +17,14 @@ class EmailNotification(models.Model):
12
17
  related_name='email',
13
18
  related_query_name='email',
14
19
  )
20
+
21
+ attachments = models.ManyToManyField(
22
+ File,
23
+ blank=True,
24
+ related_name='attachments',
25
+ related_query_name='attachment',
26
+ )
27
+
15
28
  to_email_address = models.EmailField()
16
29
  template_id = models.CharField(max_length=64, default='')
17
30
  context_data = models.JSONField(default=dict)
@@ -73,7 +73,7 @@ class EmailNotificationProcessor(BaseNotificationProcessor):
73
73
  email_notifications()
74
74
  .ready_to_send()
75
75
  .active()
76
- .prefetch_related('email')
76
+ .prefetch_related('email', 'email__attachment')
77
77
  )
78
78
 
79
79
  def process_errored(self):
@@ -82,5 +82,5 @@ class EmailNotificationProcessor(BaseNotificationProcessor):
82
82
  .email_notifications()
83
83
  .errored()
84
84
  .active()
85
- .prefetch_related('email')
85
+ .prefetch_related('email', 'email__attachment')
86
86
  )
@@ -8,7 +8,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey
8
8
 
9
9
  from django_spire.history.mixins import HistoryModelMixin
10
10
  from django_spire.notification.choices import (
11
- NotificationTypeChoices, NotificationPriorityChoices, NotificationStatusChoices
11
+ NotificationTypeChoices, NotificationPriorityChoices, NotificationStatusChoices,
12
12
  )
13
13
  from django_spire.notification.querysets import NotificationQuerySet
14
14
 
django_spire/settings.py CHANGED
@@ -6,6 +6,7 @@ DJANGO_SPIRE_AUTH_CONTROLLERS = {
6
6
 
7
7
  # AI Settings
8
8
  AI_PERSONA_NAME = 'AI Assistant'
9
+ AI_CHAT_CALLABLE = None
9
10
  AI_CHAT_DEFAULT_CALLABLE = None
10
11
  AI_SMS_CONVERSATION_DEFAULT_CALLABLE = None
11
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-spire
3
- Version: 0.20.0
3
+ Version: 0.20.2
4
4
  Summary: A project for Django Spire
5
5
  Author-email: Brayden Carlson <braydenc@stratusadv.com>, Nathan Johnson <nathanj@stratusadv.com>
6
6
  License: Copyright (c) 2024 Stratus Advanced Technologies and Contributors.
@@ -49,7 +49,7 @@ Requires-Dist: beautifulsoup4>=4.14.2
49
49
  Requires-Dist: boto3>=1.34.0
50
50
  Requires-Dist: botocore>=1.34.0
51
51
  Requires-Dist: crispy-bootstrap5==2024.10
52
- Requires-Dist: dandy>=1.3.2
52
+ Requires-Dist: dandy>=1.3.3
53
53
  Requires-Dist: django>=5.1.8
54
54
  Requires-Dist: django-crispy-forms==2.3
55
55
  Requires-Dist: django-glue>=0.8.1
@@ -1,8 +1,8 @@
1
1
  django_spire/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  django_spire/conf.py,sha256=c5Hs-7lk9T15254tOasiQ2ZTFLQIVJof9_QJDfm1PAI,933
3
- django_spire/consts.py,sha256=jzZ3QZPmurM0sxnWsizcY-CSRDC5L-av-LHjFpgR_FA,171
3
+ django_spire/consts.py,sha256=4gTrojrEZVyMXC1XkjBgNQ0x3NXM6MSEIRWwdYAcLoI,171
4
4
  django_spire/exceptions.py,sha256=L5ndRO5ftMmh0pHkO2z_NG3LSGZviJ-dDHNT73SzTNw,48
5
- django_spire/settings.py,sha256=_bM5uUqJXq3sW-NZBNAzt4egZwmvLq_jA8DaQGHqVoE,661
5
+ django_spire/settings.py,sha256=5gzQCMKAHcBYFREPrYLkQcqFj5gSSgyduUU1H2ztWN8,685
6
6
  django_spire/urls.py,sha256=mKeZszb5U4iIGqddMb5Tt5fRC72U2wABEOi6mvOfEBU,656
7
7
  django_spire/utils.py,sha256=kW0HP1xWj8Oz0h1GWs4NflnD8Jq8_F4hABwKTiT-Iyk,1006
8
8
  django_spire/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -25,9 +25,9 @@ django_spire/ai/chat/auth/controller.py,sha256=l4xHQxSlPgS_QPvofsARUOMcXzVo3cTMs
25
25
  django_spire/ai/chat/intelligence/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
26
  django_spire/ai/chat/intelligence/prompts.py,sha256=jnFaN7EUUQM-wxloWJZ2UAGw1RDXIOKkX4JfvT6ukxw,686
27
27
  django_spire/ai/chat/intelligence/decoders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- django_spire/ai/chat/intelligence/decoders/tools.py,sha256=8fSnuWL1Apr32-hH52Fz9UUAA0Ph54EYUgohAXnwGIM,933
28
+ django_spire/ai/chat/intelligence/decoders/tools.py,sha256=pC3Rvg0a1bgi0ESAsDH2HDJQTsu9aCcTslXfp4R7HwQ,1233
29
29
  django_spire/ai/chat/intelligence/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- django_spire/ai/chat/intelligence/workflows/chat_workflow.py,sha256=a5rMWbSEBHFe_EAiWpiow0WanAdibLdywtJfrL0oBX8,2289
30
+ django_spire/ai/chat/intelligence/workflows/chat_workflow.py,sha256=VaaIqN2U5C1opyQXyJevDs9fDtfwn7gVm5-EZDju1EI,2475
31
31
  django_spire/ai/chat/migrations/0001_initial.py,sha256=1cbREhX3_fNsbfumJoKAZ8w91Kq5NeXUn_iI45B7oGE,2632
32
32
  django_spire/ai/chat/migrations/0002_remove_chatmessage_content_chatmessage__content_and_more.py,sha256=KeNT7VRFmwA74odh9wxIE1Cr4KAO4Tmtqu0FuI2AmK0,752
33
33
  django_spire/ai/chat/migrations/0003_rename__content_chatmessage__intel_data_and_more.py,sha256=wAcJ6Ia3fWrGbqnVrqD2C3-3ijAot0LK-B3KZavoY_A,754
@@ -1007,7 +1007,7 @@ django_spire/notification/exceptions.py,sha256=gD_rJkZ0t6Pn13FJqf9p5uT0QWEJ58igy
1007
1007
  django_spire/notification/managers.py,sha256=QzRIRRffua5yl71RDQzGQPk7FHN9UfVMwX2aWuf2MbQ,1595
1008
1008
  django_spire/notification/maps.py,sha256=iTZnuro23HX4tg069FS148sZY9CbVlKZ83Jvh0WiIBI,622
1009
1009
  django_spire/notification/mixins.py,sha256=deGNC_SD7P9NfM2exFvbZlpOF_daMTW9zzeWre0zXfA,294
1010
- django_spire/notification/models.py,sha256=IcgRo3USO-vNoUynrexpLzf6p7GcTcxO1EutpTPtpuY,2139
1010
+ django_spire/notification/models.py,sha256=51DxAW0XeBSKx7BhPKQ-YabG1VzoKgJ_EoxhFd3-UL8,2140
1011
1011
  django_spire/notification/querysets.py,sha256=Tq1UjVTyD3xOMBAsNDyx6BNPS-8rwIGs4JrlnCeg3jM,3860
1012
1012
  django_spire/notification/urls.py,sha256=cewnrbbMe5A7zExvYX19BmOJPpcpcKGgpXzGZs5CIJw,249
1013
1013
  django_spire/notification/utils.py,sha256=cc2qHoKHv9xUfqkUljN2hPSPUmgnXS30dIez3exlBRI,253
@@ -1040,12 +1040,13 @@ django_spire/notification/email/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm
1040
1040
  django_spire/notification/email/admin.py,sha256=qpz1nfYx9oy_Bvnkc5wtZktP_7KNzBOjPwIG1s2G7A8,264
1041
1041
  django_spire/notification/email/apps.py,sha256=xePF3Rnc2IEWxFF-jO_vNi-ynl-WGQ4yCvX6itfLfaE,419
1042
1042
  django_spire/notification/email/exceptions.py,sha256=pzDVfTzIEBlBKCIrSscVWZbIZ1F-xsHpWHA1R2_zF2s,124
1043
- django_spire/notification/email/helper.py,sha256=85EhlIkollA2qVv9Wf5427y_U4xWmosqX7F6pA38DdU,1859
1044
- django_spire/notification/email/models.py,sha256=4w6tZ59TABhlsowU59owaEjSm9sX5KR8krCBSOO9fXg,952
1045
- django_spire/notification/email/processor.py,sha256=a-ETd9dZG3XHam_7avmvdHs9aenjwRTZ2jlLUvk-Q0A,3250
1043
+ django_spire/notification/email/helper.py,sha256=qwBrphhmrN0RE7nvqUsQq_lGYm5tZ9Mnwfl-5-Gm7Yw,2167
1044
+ django_spire/notification/email/models.py,sha256=aB7y_ZGCZ90i_H4EBpZ-Pea7GCfBuRdlo3oUuHP8lL0,1452
1045
+ django_spire/notification/email/processor.py,sha256=BzrgqpBTazvsqB5KzN64WTfAMmuScjUtGgflvEU1ZpY,3292
1046
1046
  django_spire/notification/email/querysets.py,sha256=xXjax8Ce35Fs5nET9Abzw-4J1BIvgUDOn1Xxt8iiLzE,1217
1047
1047
  django_spire/notification/email/migrations/0001_initial.py,sha256=o8BObuOTRl3IqqLuEWpYXMs4qh4-7eY3TVD004pWYb0,1029
1048
1048
  django_spire/notification/email/migrations/0002_emailnotification_bcc_emailnotification_cc_and_more.py,sha256=M2cafrh7ESAdpUQhfIYziGRAcTLVqjTVwn2A6H3zqq4,907
1049
+ django_spire/notification/email/migrations/0003_emailnotification_attachments.py,sha256=MXQx_8WFD3IMe6bykcowavouoD2aG2VVAOpxjGWK7Ec,597
1049
1050
  django_spire/notification/email/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1050
1051
  django_spire/notification/migrations/0001_initial.py,sha256=0KQFckVRQQvMtMRwQaFwJfcZ6U38rRboJpqlr7DpvgE,2594
1051
1052
  django_spire/notification/migrations/0002_pushnotification.py,sha256=5Vlz3V1LycFpZK-HJVA6kJnFPPB27Il_LD6kDBwa1dg,999
@@ -1142,8 +1143,8 @@ django_spire/theme/urls/page_urls.py,sha256=S8nkKkgbhG3XHI3uMUL-piOjXIrRkuY2UlM_
1142
1143
  django_spire/theme/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
1143
1144
  django_spire/theme/views/json_views.py,sha256=W1khC2K_EMbEzAFmMxC_P76_MFnkRH4-eVdodrRfAhw,1904
1144
1145
  django_spire/theme/views/page_views.py,sha256=pHr8iekjtR99xs7w1taj35HEo133Svq1dvDD0y0VL1c,3933
1145
- django_spire-0.20.0.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1146
- django_spire-0.20.0.dist-info/METADATA,sha256=pwo8ltJcMZ3LThTMRrQsGn6gEnto8nH-dQPnW9RLk5k,4967
1147
- django_spire-0.20.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1148
- django_spire-0.20.0.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1149
- django_spire-0.20.0.dist-info/RECORD,,
1146
+ django_spire-0.20.2.dist-info/licenses/LICENSE.md,sha256=tlTbOtgKoy-xAQpUk9gPeh9O4oRXCOzoWdW3jJz0wnA,1091
1147
+ django_spire-0.20.2.dist-info/METADATA,sha256=ONxHK7ThNrmHEG1WHKwcvYBwX_AnPLigNlLbQSRBKjg,4967
1148
+ django_spire-0.20.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1149
+ django_spire-0.20.2.dist-info/top_level.txt,sha256=xf3QV1e--ONkVpgMDQE9iqjQ1Vg4--_6C8wmO-KxPHQ,13
1150
+ django_spire-0.20.2.dist-info/RECORD,,