django-spire 0.23.7__py3-none-any.whl → 0.23.8__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 (538) hide show
  1. django_spire/ai/admin.py +11 -11
  2. django_spire/ai/chat/apps.py +1 -0
  3. django_spire/ai/chat/templates/django_spire/ai/chat/widget/dialog_widget.html +1 -1
  4. django_spire/ai/chat/tests/factories.py +15 -0
  5. django_spire/ai/chat/tests/test_controller.py +45 -0
  6. django_spire/ai/chat/tests/test_models.py +301 -0
  7. django_spire/ai/chat/tests/test_prompts.py +48 -0
  8. django_spire/ai/chat/tests/test_responses.py +208 -0
  9. django_spire/ai/chat/tests/test_router/test_base_chat_router.py +66 -6
  10. django_spire/ai/chat/tests/test_router/test_chat_workflow.py +73 -3
  11. django_spire/ai/chat/tests/test_router/test_integration.py +86 -6
  12. django_spire/ai/chat/tests/test_router/test_intent_decoder.py +93 -1
  13. django_spire/ai/chat/tests/test_router/test_message_intel.py +60 -1
  14. django_spire/ai/chat/tests/test_router/test_spire_chat_router.py +110 -0
  15. django_spire/ai/chat/tests/test_urls/test_json_urls.py +202 -1
  16. django_spire/ai/context/tests/__init__.py +0 -0
  17. django_spire/ai/context/tests/test_context.py +188 -0
  18. django_spire/ai/decorators.py +7 -6
  19. django_spire/ai/prompt/tests/test_bots.py +100 -10
  20. django_spire/ai/prompt/tests/test_prompt_intel.py +83 -0
  21. django_spire/ai/prompt/tests/test_prompt_tuning.py +126 -0
  22. django_spire/ai/sms/decorators.py +8 -2
  23. django_spire/ai/sms/tests/test_sms.py +240 -16
  24. django_spire/ai/sms/tests/test_sms_intel.py +42 -0
  25. django_spire/ai/sms/tests/test_webhook.py +155 -7
  26. django_spire/ai/sms/views.py +23 -24
  27. django_spire/ai/tests/test_ai.py +131 -7
  28. django_spire/auth/apps.py +4 -2
  29. django_spire/auth/controller/controller.py +36 -23
  30. django_spire/auth/controller/exceptions.py +9 -0
  31. django_spire/auth/group/admin.py +1 -0
  32. django_spire/auth/group/apps.py +2 -0
  33. django_spire/auth/group/factories.py +17 -8
  34. django_spire/auth/group/forms.py +7 -0
  35. django_spire/auth/group/tests/test_factories.py +146 -0
  36. django_spire/auth/group/tests/test_forms.py +282 -0
  37. django_spire/auth/group/tests/test_models.py +192 -0
  38. django_spire/auth/group/tests/test_querysets.py +98 -0
  39. django_spire/auth/group/tests/test_utils.py +341 -0
  40. django_spire/auth/group/tests/test_views.py +377 -0
  41. django_spire/auth/group/urls/__init__.py +3 -1
  42. django_spire/auth/group/urls/form_urls.py +2 -0
  43. django_spire/auth/group/urls/json_urls.py +3 -0
  44. django_spire/auth/group/urls/page_urls.py +2 -0
  45. django_spire/auth/group/utils.py +6 -2
  46. django_spire/auth/group/views/form_views.py +6 -3
  47. django_spire/auth/group/views/json_views.py +6 -2
  48. django_spire/auth/mfa/admin.py +2 -0
  49. django_spire/auth/mfa/apps.py +2 -0
  50. django_spire/auth/mfa/forms.py +1 -0
  51. django_spire/auth/mfa/querysets.py +9 -2
  52. django_spire/auth/mfa/tests/test_models.py +233 -0
  53. django_spire/auth/mfa/tests/test_utils.py +106 -0
  54. django_spire/auth/mfa/urls/__init__.py +2 -0
  55. django_spire/auth/mfa/urls/page_urls.py +2 -0
  56. django_spire/auth/mfa/urls/redirect_urls.py +2 -0
  57. django_spire/auth/mfa/views/page_views.py +2 -1
  58. django_spire/auth/permissions/consts.py +2 -2
  59. django_spire/auth/permissions/decorators.py +8 -8
  60. django_spire/auth/permissions/permissions.py +28 -35
  61. django_spire/auth/permissions/tests/test_decorators.py +333 -0
  62. django_spire/auth/permissions/tests/test_permissions.py +337 -0
  63. django_spire/auth/permissions/tests/test_tools.py +305 -0
  64. django_spire/auth/permissions/tools.py +21 -15
  65. django_spire/auth/seeding/seed.py +3 -0
  66. django_spire/auth/seeding/seeder.py +2 -0
  67. django_spire/auth/tests/test_controller.py +323 -0
  68. django_spire/auth/tests/test_url_endpoints.py +9 -9
  69. django_spire/auth/tests/test_views.py +406 -0
  70. django_spire/auth/urls/admin_urls.py +2 -0
  71. django_spire/auth/urls/redirect_urls.py +2 -0
  72. django_spire/auth/user/apps.py +2 -0
  73. django_spire/auth/user/forms.py +9 -0
  74. django_spire/auth/user/models.py +1 -1
  75. django_spire/auth/user/services/services.py +1 -0
  76. django_spire/auth/user/tests/factories.py +14 -13
  77. django_spire/auth/user/tests/test_factories.py +166 -2
  78. django_spire/auth/user/tests/test_forms.py +573 -0
  79. django_spire/auth/user/tests/test_models.py +257 -0
  80. django_spire/auth/user/tests/test_services.py +200 -0
  81. django_spire/auth/user/tests/test_tools.py +153 -0
  82. django_spire/auth/user/tests/test_user_factories.py +139 -0
  83. django_spire/auth/user/tests/test_views.py +363 -0
  84. django_spire/auth/user/tools.py +7 -1
  85. django_spire/auth/user/urls/form_urls.py +3 -0
  86. django_spire/auth/user/urls/page_urls.py +3 -0
  87. django_spire/auth/user/views/form_views.py +19 -10
  88. django_spire/auth/user/views/page_views.py +8 -2
  89. django_spire/auth/views/redirect_views.py +14 -9
  90. django_spire/comment/admin.py +2 -0
  91. django_spire/comment/apps.py +2 -0
  92. django_spire/comment/templatetags/comment_tags.py +1 -0
  93. django_spire/comment/tests/test_forms.py +27 -0
  94. django_spire/comment/tests/test_models.py +215 -0
  95. django_spire/comment/tests/test_querysets.py +101 -0
  96. django_spire/comment/tests/test_utils.py +90 -0
  97. django_spire/comment/urls.py +2 -0
  98. django_spire/comment/utils.py +22 -13
  99. django_spire/comment/views.py +1 -1
  100. django_spire/conf.py +8 -6
  101. django_spire/consts.py +1 -1
  102. django_spire/contrib/breadcrumb/apps.py +2 -0
  103. django_spire/contrib/breadcrumb/breadcrumbs.py +18 -18
  104. django_spire/contrib/breadcrumb/tests/test_breadcrumbs.py +198 -0
  105. django_spire/contrib/constructor/__init__.py +3 -3
  106. django_spire/contrib/constructor/constructor.py +15 -15
  107. django_spire/contrib/constructor/django_model_constructor.py +5 -4
  108. django_spire/contrib/constructor/exceptions.py +5 -3
  109. django_spire/contrib/constructor/tests/__init__.py +0 -0
  110. django_spire/contrib/constructor/tests/test_constructor.py +193 -0
  111. django_spire/contrib/form/tests/__init__.py +0 -0
  112. django_spire/contrib/form/tests/test_forms.py +203 -0
  113. django_spire/contrib/generic_views/modal_views.py +2 -1
  114. django_spire/contrib/generic_views/portal_views.py +20 -19
  115. django_spire/contrib/generic_views/tests/__init__.py +0 -0
  116. django_spire/contrib/generic_views/tests/test_views.py +459 -0
  117. django_spire/contrib/help/apps.py +2 -0
  118. django_spire/contrib/help/templatetags/help.py +1 -0
  119. django_spire/contrib/help/tests/__init__.py +0 -0
  120. django_spire/contrib/help/tests/test_templatetags.py +100 -0
  121. django_spire/contrib/options/mixins.py +6 -5
  122. django_spire/contrib/options/tests/factories.py +5 -1
  123. django_spire/contrib/options/tests/test_options.py +234 -0
  124. django_spire/contrib/ordering/exceptions.py +7 -3
  125. django_spire/contrib/ordering/mixins.py +2 -0
  126. django_spire/contrib/ordering/querysets.py +3 -1
  127. django_spire/contrib/ordering/services/processor_service.py +8 -4
  128. django_spire/contrib/ordering/services/service.py +1 -2
  129. django_spire/contrib/ordering/tests/__init__.py +0 -0
  130. django_spire/contrib/ordering/tests/test_ordering.py +165 -0
  131. django_spire/contrib/ordering/validators.py +6 -6
  132. django_spire/contrib/pagination/templatetags/pagination_tags.py +12 -5
  133. django_spire/contrib/pagination/tests/__init__.py +0 -0
  134. django_spire/contrib/pagination/tests/test_pagination.py +179 -0
  135. django_spire/contrib/performance/decorators.py +16 -6
  136. django_spire/contrib/performance/tests/__init__.py +0 -0
  137. django_spire/contrib/performance/tests/test_performance.py +107 -0
  138. django_spire/contrib/queryset/enums.py +3 -1
  139. django_spire/contrib/queryset/filter_tools.py +10 -5
  140. django_spire/contrib/queryset/mixins.py +16 -16
  141. django_spire/contrib/queryset/tests/__init__.py +0 -0
  142. django_spire/contrib/queryset/tests/test_queryset.py +137 -0
  143. django_spire/contrib/seeding/field/base.py +13 -7
  144. django_spire/contrib/seeding/field/callable.py +8 -1
  145. django_spire/contrib/seeding/field/cleaners.py +5 -5
  146. django_spire/contrib/seeding/field/custom.py +20 -10
  147. django_spire/contrib/seeding/field/django/seeder.py +8 -6
  148. django_spire/contrib/seeding/field/enums.py +7 -5
  149. django_spire/contrib/seeding/field/override.py +16 -6
  150. django_spire/contrib/seeding/field/static.py +9 -2
  151. django_spire/contrib/seeding/field/tests/test_base.py +18 -14
  152. django_spire/contrib/seeding/field/tests/test_callable.py +13 -9
  153. django_spire/contrib/seeding/field/tests/test_cleaners.py +51 -38
  154. django_spire/contrib/seeding/field/tests/test_static.py +13 -9
  155. django_spire/contrib/seeding/intelligence/bots/seeder_generator_bot.py +2 -0
  156. django_spire/contrib/seeding/intelligence/intel.py +5 -1
  157. django_spire/contrib/seeding/intelligence/prompts/factory.py +6 -1
  158. django_spire/contrib/seeding/intelligence/prompts/foreign_key_selection_prompt.py +6 -1
  159. django_spire/contrib/seeding/intelligence/prompts/generate_django_model_seeder_prompts.py +2 -0
  160. django_spire/contrib/seeding/intelligence/prompts/generic_relationship_selection_prompt.py +7 -1
  161. django_spire/contrib/seeding/intelligence/prompts/hierarchical_selection_prompt.py +6 -2
  162. django_spire/contrib/seeding/intelligence/prompts/model_field_choices_prompt.py +8 -2
  163. django_spire/contrib/seeding/intelligence/prompts/negation_prompt.py +2 -0
  164. django_spire/contrib/seeding/intelligence/prompts/objective_prompt.py +6 -1
  165. django_spire/contrib/seeding/management/commands/seeding.py +9 -3
  166. django_spire/contrib/seeding/management/example.py +2 -0
  167. django_spire/contrib/seeding/model/base.py +16 -7
  168. django_spire/contrib/seeding/model/config.py +31 -15
  169. django_spire/contrib/seeding/model/django/config.py +13 -13
  170. django_spire/contrib/seeding/model/django/seeder.py +4 -4
  171. django_spire/contrib/seeding/model/django/tests/test_seeder.py +34 -23
  172. django_spire/contrib/seeding/model/enums.py +2 -0
  173. django_spire/contrib/seeding/tests/test_config.py +71 -0
  174. django_spire/contrib/seeding/tests/test_custom.py +35 -0
  175. django_spire/contrib/seeding/tests/test_enums.py +40 -0
  176. django_spire/contrib/seeding/tests/test_intel.py +32 -0
  177. django_spire/contrib/seeding/tests/test_override.py +63 -0
  178. django_spire/contrib/service/__init__.py +2 -2
  179. django_spire/contrib/service/django_model_service.py +16 -15
  180. django_spire/contrib/service/exceptions.py +5 -3
  181. django_spire/contrib/service/tests/__init__.py +0 -0
  182. django_spire/contrib/service/tests/test_service.py +153 -0
  183. django_spire/contrib/session/apps.py +2 -0
  184. django_spire/contrib/session/controller.py +48 -42
  185. django_spire/contrib/session/templatetags/session_tags.py +11 -2
  186. django_spire/contrib/session/tests/test_session_controller.py +117 -53
  187. django_spire/contrib/tests/__init__.py +0 -0
  188. django_spire/contrib/tests/test_utils.py +37 -0
  189. django_spire/contrib/utils.py +4 -1
  190. django_spire/core/apps.py +2 -0
  191. django_spire/core/converters/tests/test_to_data.py +353 -0
  192. django_spire/core/converters/tests/test_to_enums.py +61 -41
  193. django_spire/core/converters/tests/test_to_pydantic.py +138 -109
  194. django_spire/core/converters/to_data.py +29 -10
  195. django_spire/core/converters/to_enums.py +4 -2
  196. django_spire/core/converters/to_pydantic.py +22 -22
  197. django_spire/core/decorators.py +19 -6
  198. django_spire/core/forms/widgets.py +4 -0
  199. django_spire/core/maps.py +3 -1
  200. django_spire/core/middleware/maintenance.py +3 -3
  201. django_spire/core/middleware.py +8 -6
  202. django_spire/core/redirect/__init__.py +5 -0
  203. django_spire/core/redirect/generic_redirect.py +1 -2
  204. django_spire/core/redirect/tests/__init__.py +0 -0
  205. django_spire/core/redirect/tests/test_generic_redirect.py +34 -0
  206. django_spire/core/{tests/tests_redirect.py → redirect/tests/test_safe_redirect.py} +55 -81
  207. django_spire/core/shortcuts.py +3 -3
  208. django_spire/core/static/django_spire/css/app-layout.css +1 -1
  209. django_spire/core/static/django_spire/css/app-navigation.css +3 -3
  210. django_spire/core/static/django_spire/css/bootstrap-override.css +4 -0
  211. django_spire/core/tag/admin.py +12 -0
  212. django_spire/core/tag/intelligence/tag_set_bot.py +2 -0
  213. django_spire/core/tag/mixins.py +2 -0
  214. django_spire/core/tag/models.py +2 -0
  215. django_spire/core/tag/querysets.py +2 -0
  216. django_spire/core/tag/service/tag_service.py +6 -3
  217. django_spire/core/tag/tests/test_intelligence.py +9 -9
  218. django_spire/core/tag/tests/test_tags.py +44 -54
  219. django_spire/core/tag/tests/test_tools.py +191 -0
  220. django_spire/core/tag/tools.py +3 -0
  221. django_spire/core/templates/django_spire/card/card.html +5 -2
  222. django_spire/core/templates/django_spire/card/title_card.html +9 -4
  223. django_spire/core/templates/django_spire/infinite_scroll/base.html +1 -0
  224. django_spire/core/templates/django_spire/navigation/side_navigation.html +19 -24
  225. django_spire/core/templates/django_spire/page/full_page.html +46 -16
  226. django_spire/core/templates/django_spire/table/base.html +4 -2
  227. django_spire/core/templatetags/json.py +6 -2
  228. django_spire/core/templatetags/message.py +13 -8
  229. django_spire/core/templatetags/string_formating.py +8 -5
  230. django_spire/core/templatetags/tests/__init__.py +0 -0
  231. django_spire/core/templatetags/tests/test_templatetags.py +427 -0
  232. django_spire/core/templatetags/variable_types.py +17 -9
  233. django_spire/core/tests/test_cases.py +1 -1
  234. django_spire/core/tests/test_conf.py +43 -0
  235. django_spire/core/tests/test_consts.py +28 -0
  236. django_spire/core/tests/test_context_processors.py +93 -0
  237. django_spire/core/tests/test_decorators.py +95 -0
  238. django_spire/core/tests/test_django_spire_utils.py +56 -0
  239. django_spire/core/tests/test_exceptions.py +37 -0
  240. django_spire/core/tests/test_models.py +54 -0
  241. django_spire/core/tests/test_settings.py +45 -0
  242. django_spire/core/tests/test_shortcuts.py +74 -0
  243. django_spire/core/tests/test_urls.py +16 -0
  244. django_spire/core/tests/test_utils.py +58 -0
  245. django_spire/core/urls.py +4 -1
  246. django_spire/core/utils.py +12 -8
  247. django_spire/exceptions.py +16 -1
  248. django_spire/file/admin.py +4 -2
  249. django_spire/file/apps.py +8 -10
  250. django_spire/file/fields.py +7 -7
  251. django_spire/file/forms.py +1 -1
  252. django_spire/file/interfaces.py +15 -15
  253. django_spire/file/mixins.py +1 -4
  254. django_spire/file/models.py +3 -5
  255. django_spire/file/tests/factories.py +59 -0
  256. django_spire/file/tests/test_admin.py +69 -0
  257. django_spire/file/tests/test_apps.py +24 -0
  258. django_spire/file/tests/test_fields.py +114 -0
  259. django_spire/file/tests/test_forms.py +20 -0
  260. django_spire/file/tests/test_interfaces.py +183 -0
  261. django_spire/file/tests/test_models.py +82 -0
  262. django_spire/file/tests/test_querysets.py +102 -0
  263. django_spire/file/tests/test_utils.py +32 -0
  264. django_spire/file/tests/test_views.py +145 -0
  265. django_spire/file/tests/test_widgets.py +82 -0
  266. django_spire/file/tools.py +8 -2
  267. django_spire/file/views.py +7 -3
  268. django_spire/file/widgets.py +12 -12
  269. django_spire/help_desk/admin.py +15 -0
  270. django_spire/help_desk/apps.py +2 -0
  271. django_spire/help_desk/auth/controller.py +2 -0
  272. django_spire/help_desk/choices.py +2 -0
  273. django_spire/help_desk/enums.py +2 -0
  274. django_spire/help_desk/exceptions.py +31 -3
  275. django_spire/help_desk/forms.py +2 -0
  276. django_spire/help_desk/models.py +2 -0
  277. django_spire/help_desk/querysets.py +4 -1
  278. django_spire/help_desk/services/notification_service.py +26 -27
  279. django_spire/help_desk/services/service.py +2 -3
  280. django_spire/help_desk/tests/factories.py +8 -3
  281. django_spire/help_desk/tests/test_admin.py +41 -0
  282. django_spire/help_desk/tests/test_apps.py +41 -0
  283. django_spire/help_desk/tests/test_choices.py +50 -0
  284. django_spire/help_desk/tests/test_controller.py +87 -0
  285. django_spire/help_desk/tests/test_enums.py +18 -0
  286. django_spire/help_desk/tests/test_exceptions.py +37 -0
  287. django_spire/help_desk/tests/test_forms.py +89 -0
  288. django_spire/help_desk/tests/test_models.py +59 -0
  289. django_spire/help_desk/tests/test_querysets.py +38 -0
  290. django_spire/help_desk/tests/test_services/test_notification_service.py +15 -8
  291. django_spire/help_desk/tests/test_services/test_service.py +92 -0
  292. django_spire/help_desk/tests/test_urls/test_form_urls.py +6 -6
  293. django_spire/help_desk/tests/test_urls/test_page_urls.py +8 -9
  294. django_spire/help_desk/tests/test_views/test_form_views.py +46 -19
  295. django_spire/help_desk/tests/test_views/test_page_views.py +32 -9
  296. django_spire/help_desk/urls/__init__.py +4 -1
  297. django_spire/help_desk/urls/form_urls.py +3 -0
  298. django_spire/help_desk/urls/page_urls.py +3 -0
  299. django_spire/help_desk/views/form_views.py +13 -5
  300. django_spire/help_desk/views/page_views.py +11 -3
  301. django_spire/history/activity/admin.py +2 -0
  302. django_spire/history/activity/apps.py +3 -1
  303. django_spire/history/activity/mixins.py +13 -7
  304. django_spire/history/activity/models.py +6 -5
  305. django_spire/history/activity/querysets.py +2 -0
  306. django_spire/history/activity/tests/__init__.py +0 -0
  307. django_spire/history/activity/tests/test_activity.py +176 -0
  308. django_spire/history/admin.py +9 -2
  309. django_spire/history/choices.py +3 -0
  310. django_spire/history/models.py +5 -5
  311. django_spire/history/tests/test_admin.py +93 -0
  312. django_spire/history/tests/test_history.py +101 -0
  313. django_spire/history/tests/test_mixins.py +84 -0
  314. django_spire/history/viewed/admin.py +3 -1
  315. django_spire/history/viewed/apps.py +3 -1
  316. django_spire/history/viewed/models.py +2 -0
  317. django_spire/history/viewed/tests/__init__.py +0 -0
  318. django_spire/history/viewed/tests/test_viewed.py +46 -0
  319. django_spire/knowledge/auth/tests/__init__.py +0 -0
  320. django_spire/knowledge/auth/tests/test_controller.py +116 -0
  321. django_spire/knowledge/collection/admin.py +5 -1
  322. django_spire/knowledge/collection/models.py +3 -1
  323. django_spire/knowledge/collection/seeding/seed.py +1 -0
  324. django_spire/knowledge/collection/services/factory_service.py +10 -11
  325. django_spire/knowledge/collection/services/ordering_service.py +1 -2
  326. django_spire/knowledge/collection/services/service.py +5 -10
  327. django_spire/knowledge/collection/services/tag_service.py +5 -2
  328. django_spire/knowledge/collection/tests/factories.py +28 -1
  329. django_spire/knowledge/collection/tests/test_models.py +48 -0
  330. django_spire/knowledge/collection/tests/test_querysets.py +93 -0
  331. django_spire/knowledge/collection/tests/test_services/test_factory_service.py +100 -0
  332. django_spire/knowledge/collection/tests/test_services/test_services.py +160 -0
  333. django_spire/knowledge/collection/tests/test_urls/test_form_urls.py +21 -3
  334. django_spire/knowledge/collection/tests/test_urls/test_json_urls.py +39 -1
  335. django_spire/knowledge/collection/tests/test_urls/test_page_urls.py +12 -4
  336. django_spire/knowledge/collection/urls/__init__.py +3 -0
  337. django_spire/knowledge/collection/urls/form_urls.py +2 -0
  338. django_spire/knowledge/collection/urls/json_urls.py +2 -0
  339. django_spire/knowledge/collection/urls/page_urls.py +2 -0
  340. django_spire/knowledge/collection/views/form_views.py +4 -4
  341. django_spire/knowledge/collection/views/json_views.py +5 -1
  342. django_spire/knowledge/collection/views/page_views.py +5 -2
  343. django_spire/knowledge/entry/admin.py +7 -1
  344. django_spire/knowledge/entry/forms.py +2 -0
  345. django_spire/knowledge/entry/models.py +2 -0
  346. django_spire/knowledge/entry/seeding/seed.py +3 -0
  347. django_spire/knowledge/entry/services/automation_service.py +5 -4
  348. django_spire/knowledge/entry/services/factory_service.py +7 -5
  349. django_spire/knowledge/entry/services/service.py +4 -7
  350. django_spire/knowledge/entry/services/tag_service.py +0 -1
  351. django_spire/knowledge/entry/services/tool_service.py +1 -0
  352. django_spire/knowledge/entry/services/transformation_services.py +1 -5
  353. django_spire/knowledge/entry/tests/factories.py +1 -2
  354. django_spire/knowledge/entry/tests/test_factory_service.py +20 -0
  355. django_spire/knowledge/entry/tests/test_models.py +41 -0
  356. django_spire/knowledge/entry/tests/test_querysets.py +71 -0
  357. django_spire/knowledge/entry/tests/test_services.py +94 -0
  358. django_spire/knowledge/entry/tests/test_urls/test_form_urls.py +9 -14
  359. django_spire/knowledge/entry/tests/test_urls/test_json_urls.py +48 -5
  360. django_spire/knowledge/entry/tests/test_urls/test_page_urls.py +6 -8
  361. django_spire/knowledge/entry/tests/test_urls/test_template_urls.py +40 -0
  362. django_spire/knowledge/entry/urls/form_urls.py +2 -0
  363. django_spire/knowledge/entry/urls/json_urls.py +2 -0
  364. django_spire/knowledge/entry/urls/page_urls.py +2 -0
  365. django_spire/knowledge/entry/urls/template_urls.py +2 -0
  366. django_spire/knowledge/entry/version/block/choices.py +2 -0
  367. django_spire/knowledge/entry/version/block/data/data.py +1 -0
  368. django_spire/knowledge/entry/version/block/data/list/data.py +8 -13
  369. django_spire/knowledge/entry/version/block/data/list/maps.py +3 -0
  370. django_spire/knowledge/entry/version/block/data/list/meta.py +1 -2
  371. django_spire/knowledge/entry/version/block/data/list/tests/__init__.py +0 -0
  372. django_spire/knowledge/entry/version/block/data/list/tests/test_maps.py +32 -0
  373. django_spire/knowledge/entry/version/block/data/list/tests/test_meta.py +58 -0
  374. django_spire/knowledge/entry/version/block/data/maps.py +3 -6
  375. django_spire/knowledge/entry/version/block/models.py +7 -5
  376. django_spire/knowledge/entry/version/block/seeding/constants.py +5 -4
  377. django_spire/knowledge/entry/version/block/services/service.py +2 -3
  378. django_spire/knowledge/entry/version/block/tests/factories.py +4 -10
  379. django_spire/knowledge/entry/version/block/tests/test_choices.py +56 -0
  380. django_spire/knowledge/entry/version/block/tests/test_data.py +90 -0
  381. django_spire/knowledge/entry/version/block/tests/test_maps.py +37 -0
  382. django_spire/knowledge/entry/version/block/tests/test_models.py +55 -0
  383. django_spire/knowledge/entry/version/block/tests/test_querysets.py +35 -0
  384. django_spire/knowledge/entry/version/block/tests/test_services.py +65 -0
  385. django_spire/knowledge/entry/version/choices.py +2 -0
  386. django_spire/knowledge/entry/version/converters/converter.py +1 -1
  387. django_spire/knowledge/entry/version/converters/docx_converter.py +4 -7
  388. django_spire/knowledge/entry/version/converters/markdown_converter.py +20 -20
  389. django_spire/knowledge/entry/version/maps.py +4 -5
  390. django_spire/knowledge/entry/version/querysets.py +1 -1
  391. django_spire/knowledge/entry/version/seeding/seeder.py +1 -2
  392. django_spire/knowledge/entry/version/services/processor_service.py +5 -4
  393. django_spire/knowledge/entry/version/services/service.py +1 -2
  394. django_spire/knowledge/entry/version/tests/factories.py +2 -2
  395. django_spire/knowledge/entry/version/tests/test_choices.py +18 -0
  396. django_spire/knowledge/entry/version/tests/test_converters/test_docx_converter.py +56 -8
  397. django_spire/knowledge/entry/version/tests/test_converters/test_markdown_converter.py +78 -0
  398. django_spire/knowledge/entry/version/tests/test_maps.py +58 -0
  399. django_spire/knowledge/entry/version/tests/test_models.py +23 -0
  400. django_spire/knowledge/entry/version/tests/test_querysets.py +26 -0
  401. django_spire/knowledge/entry/version/tests/test_services.py +62 -0
  402. django_spire/knowledge/entry/version/tests/test_urls/test_json_urls.py +27 -8
  403. django_spire/knowledge/entry/version/tests/test_urls/test_page_urls.py +15 -8
  404. django_spire/knowledge/entry/version/tests/test_urls/test_redirect_urls.py +38 -0
  405. django_spire/knowledge/entry/version/urls/__init__.py +3 -0
  406. django_spire/knowledge/entry/version/urls/json_urls.py +2 -1
  407. django_spire/knowledge/entry/version/urls/page_urls.py +2 -0
  408. django_spire/knowledge/entry/version/urls/redirect_urls.py +2 -0
  409. django_spire/knowledge/entry/version/views/json_views.py +5 -1
  410. django_spire/knowledge/entry/version/views/page_views.py +10 -3
  411. django_spire/knowledge/entry/version/views/redirect_views.py +5 -1
  412. django_spire/knowledge/entry/views/form_views.py +16 -8
  413. django_spire/knowledge/entry/views/json_views.py +3 -1
  414. django_spire/knowledge/entry/views/page_views.py +8 -2
  415. django_spire/knowledge/entry/views/template_views.py +7 -1
  416. django_spire/knowledge/exceptions.py +2 -1
  417. django_spire/knowledge/intelligence/intel/answer_intel.py +2 -1
  418. django_spire/knowledge/intelligence/intel/entry_intel.py +0 -1
  419. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +4 -5
  420. django_spire/knowledge/models.py +1 -2
  421. django_spire/knowledge/tests/__init__.py +0 -0
  422. django_spire/knowledge/tests/test_templatetags.py +40 -0
  423. django_spire/knowledge/tests/test_urls/__init__.py +0 -0
  424. django_spire/knowledge/tests/test_urls/test_page_urls.py +24 -0
  425. django_spire/knowledge/urls/__init__.py +2 -0
  426. django_spire/knowledge/urls/page_urls.py +2 -0
  427. django_spire/knowledge/views/page_views.py +8 -3
  428. django_spire/notification/admin.py +3 -1
  429. django_spire/notification/app/admin.py +2 -0
  430. django_spire/notification/app/apps.py +3 -1
  431. django_spire/notification/app/exceptions.py +9 -2
  432. django_spire/notification/app/models.py +8 -4
  433. django_spire/notification/app/processor.py +22 -26
  434. django_spire/notification/app/querysets.py +2 -0
  435. django_spire/notification/app/tests/__init__.py +0 -0
  436. django_spire/notification/app/tests/factories.py +34 -0
  437. django_spire/notification/app/tests/test_apps.py +24 -0
  438. django_spire/notification/app/tests/test_models.py +72 -0
  439. django_spire/notification/app/tests/test_processor.py +111 -0
  440. django_spire/notification/app/tests/test_querysets.py +90 -0
  441. django_spire/notification/app/tests/test_views/__init__.py +0 -0
  442. django_spire/notification/app/tests/test_views/test_json_views.py +48 -0
  443. django_spire/notification/app/tests/test_views/test_page_views.py +19 -0
  444. django_spire/notification/app/urls/__init__.py +3 -1
  445. django_spire/notification/app/urls/json_urls.py +6 -4
  446. django_spire/notification/app/urls/page_urls.py +4 -3
  447. django_spire/notification/app/urls/template_urls.py +4 -2
  448. django_spire/notification/apps.py +4 -1
  449. django_spire/notification/email/admin.py +5 -1
  450. django_spire/notification/email/apps.py +3 -1
  451. django_spire/notification/email/exceptions.py +4 -2
  452. django_spire/notification/email/helper.py +5 -3
  453. django_spire/notification/email/models.py +4 -0
  454. django_spire/notification/email/processor.py +19 -15
  455. django_spire/notification/email/querysets.py +3 -0
  456. django_spire/notification/email/tests/__init__.py +0 -0
  457. django_spire/notification/email/tests/factories.py +35 -0
  458. django_spire/notification/email/tests/test_apps.py +24 -0
  459. django_spire/notification/email/tests/test_models.py +52 -0
  460. django_spire/notification/email/tests/test_processor.py +92 -0
  461. django_spire/notification/email/tests/test_querysets.py +43 -0
  462. django_spire/notification/exceptions.py +17 -2
  463. django_spire/notification/managers.py +7 -1
  464. django_spire/notification/maps.py +4 -1
  465. django_spire/notification/mixins.py +2 -0
  466. django_spire/notification/models.py +3 -1
  467. django_spire/notification/processors/notification.py +12 -5
  468. django_spire/notification/processors/processor.py +2 -0
  469. django_spire/notification/processors/tests/__init__.py +0 -0
  470. django_spire/notification/processors/tests/test_notification.py +106 -0
  471. django_spire/notification/push/admin.py +10 -1
  472. django_spire/notification/push/apps.py +3 -1
  473. django_spire/notification/push/models.py +2 -3
  474. django_spire/notification/push/tests/__init__.py +0 -0
  475. django_spire/notification/push/tests/test_apps.py +24 -0
  476. django_spire/notification/push/tests/test_models.py +28 -0
  477. django_spire/notification/querysets.py +7 -1
  478. django_spire/notification/sms/admin.py +2 -0
  479. django_spire/notification/sms/apps.py +4 -1
  480. django_spire/notification/sms/automations.py +2 -0
  481. django_spire/notification/sms/choices.py +2 -0
  482. django_spire/notification/sms/exceptions.py +19 -5
  483. django_spire/notification/sms/helper.py +33 -23
  484. django_spire/notification/sms/models.py +5 -1
  485. django_spire/notification/sms/processor.py +20 -20
  486. django_spire/notification/sms/querysets.py +2 -0
  487. django_spire/notification/sms/tests/factories.py +33 -0
  488. django_spire/notification/sms/tests/test_apps.py +24 -0
  489. django_spire/notification/sms/tests/test_automation.py +38 -0
  490. django_spire/notification/sms/tests/test_choices.py +15 -0
  491. django_spire/notification/sms/tests/test_consts.py +17 -0
  492. django_spire/notification/sms/tests/test_exceptions.py +27 -0
  493. django_spire/notification/sms/tests/test_helper.py +50 -0
  494. django_spire/notification/sms/tests/test_models.py +81 -0
  495. django_spire/notification/sms/tests/test_processor.py +107 -0
  496. django_spire/notification/sms/tests/test_tools.py +25 -11
  497. django_spire/notification/sms/tools.py +16 -5
  498. django_spire/notification/sms/urls/__init__.py +3 -1
  499. django_spire/notification/sms/urls/media_urls.py +2 -0
  500. django_spire/notification/sms/views/media_views.py +14 -4
  501. django_spire/notification/tests/__init__.py +0 -0
  502. django_spire/notification/tests/factories.py +26 -0
  503. django_spire/notification/tests/test_admin.py +55 -0
  504. django_spire/notification/tests/test_apps.py +30 -0
  505. django_spire/notification/tests/test_automation.py +18 -0
  506. django_spire/notification/tests/test_choices.py +59 -0
  507. django_spire/notification/tests/test_exceptions.py +58 -0
  508. django_spire/notification/tests/test_managers.py +100 -0
  509. django_spire/notification/tests/test_maps.py +31 -0
  510. django_spire/notification/tests/test_models.py +76 -0
  511. django_spire/notification/tests/test_querysets.py +184 -0
  512. django_spire/notification/tests/test_utils.py +23 -0
  513. django_spire/notification/urls.py +3 -1
  514. django_spire/notification/utils.py +3 -1
  515. django_spire/settings.py +3 -0
  516. django_spire/theme/tests/test_context_processor.py +15 -13
  517. django_spire/theme/tests/test_enums.py +2 -2
  518. django_spire/theme/tests/test_filesystem.py +2 -5
  519. django_spire/theme/tests/test_integration.py +12 -12
  520. django_spire/theme/tests/test_model.py +40 -38
  521. django_spire/theme/tests/test_views/test_json_views.py +33 -33
  522. django_spire/theme/urls/json_urls.py +3 -0
  523. django_spire/theme/urls/page_urls.py +3 -0
  524. django_spire/urls.py +19 -15
  525. django_spire/utils.py +13 -4
  526. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/METADATA +1 -1
  527. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/RECORD +530 -358
  528. django_spire/contrib/options/tests/test_unit.py +0 -148
  529. django_spire/contrib/seeding/tests/test_seeding.py +0 -25
  530. django_spire/core/tests/test_templatetags.py +0 -117
  531. django_spire/core/tests/tests_shortcuts.py +0 -73
  532. django_spire/history/activity/tests.py +0 -3
  533. django_spire/history/activity/views.py +0 -3
  534. django_spire/knowledge/collection/tests/test_services/test_transformation_service.py +0 -71
  535. django_spire/notification/app/tests.py +0 -3
  536. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/WHEEL +0 -0
  537. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/licenses/LICENSE.md +0 -0
  538. {django_spire-0.23.7.dist-info → django_spire-0.23.8.dist-info}/top_level.txt +0 -0
@@ -1,36 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django.utils import timezone
2
6
  from faker import Faker
3
7
 
4
8
  from django_spire.contrib.seeding.field.base import BaseFieldSeeder
5
9
  from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
6
10
 
11
+ if TYPE_CHECKING:
12
+ from datetime import datetime
13
+ from typing import Any
14
+
15
+ from django.db.models import Model
16
+
7
17
 
8
18
  class CustomFieldSeeder(BaseFieldSeeder):
9
19
  keyword = FieldSeederTypesEnum.CUSTOM
10
20
 
11
- def in_order(self, values: list, index: int) -> any:
21
+ def in_order(self, values: list, index: int) -> Any:
12
22
  if not values:
13
- raise ValueError(
14
- "Cannot select from empty values list. "
15
- "Make sure the related model has existing records before seeding foreign keys."
16
- )
17
- index = index % len(values) # Index loops back on itself
23
+ message = 'Cannot select from empty values list. Make sure the related model has existing records before seeding foreign keys.'
24
+ raise ValueError(message)
25
+
26
+ # Index loops back on itself
27
+ index = index % len(values)
18
28
  return values[index]
19
29
 
20
- def date_time_between(self, start_date: str, end_date: str):
30
+ def date_time_between(self, start_date: str, end_date: str) -> datetime:
21
31
  faker = Faker()
22
32
  naive_dt = faker.date_time_between(start_date=start_date, end_date=end_date)
23
33
  return timezone.make_aware(naive_dt)
24
34
 
25
- def fk_random(self, model_class, ids: list[int]):
35
+ def fk_random(self, model_class: type[Model], ids: list[int]) -> int:
26
36
  faker = Faker()
27
37
  return faker.random_element(elements=ids)
28
38
 
29
- def fk_in_order(self, model_class, index: int, ids: list[int]):
39
+ def fk_in_order(self, model_class: type[Model], index: int, ids: list[int]) -> int:
30
40
  """Takes a queryset, calls it then returns a random id"""
31
41
  return self.in_order(values=ids, index=index)
32
42
 
33
- def seed(self, manager, count) -> list[dict]:
43
+ def seed(self, manager: Any, count: int) -> list[dict]:
34
44
  data = []
35
45
 
36
46
  foreign_keys = {}
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING
4
+
3
5
  from dandy import BaseIntel, Prompt
4
6
 
5
7
  from django_spire.core.converters import django_to_pydantic_model, fake_model_field_value
@@ -7,11 +9,14 @@ from django_spire.contrib.seeding.field.base import BaseFieldSeeder
7
9
  from django_spire.contrib.seeding.field.enums import FieldSeederTypesEnum
8
10
  from django_spire.contrib.seeding.intelligence.bots.field_seeding_bots import LlmFieldSeedingBot
9
11
 
12
+ if TYPE_CHECKING:
13
+ from typing import Any
14
+
10
15
 
11
16
  class DjangoFieldLlmSeeder(BaseFieldSeeder):
12
17
  keyword = FieldSeederTypesEnum.LLM
13
18
 
14
- def seed(self, model_seeder, count: int = 1,) -> list[dict]:
19
+ def seed(self, model_seeder: Any, count: int = 1) -> list[dict]:
15
20
 
16
21
  include_fields = list(self.seeder_fields.keys())
17
22
 
@@ -31,10 +36,7 @@ class DjangoFieldLlmSeeder(BaseFieldSeeder):
31
36
  base_prompt = (
32
37
  Prompt()
33
38
  .heading('General Seeding Rules')
34
- .list(
35
- [
36
- 'Create data for each field provided.'
37
- ])
39
+ .list(['Create data for each field provided.'])
38
40
  .heading('Field Rules & Context')
39
41
  .prompt(self.field_prompt)
40
42
  )
@@ -115,7 +117,7 @@ class DjangoFieldLlmSeeder(BaseFieldSeeder):
115
117
  class DjangoFieldFakerSeeder(BaseFieldSeeder):
116
118
  keyword = FieldSeederTypesEnum.FAKER
117
119
 
118
- def seed(self, model_seeder, count = 1) -> list[dict]:
120
+ def seed(self, model_seeder: Any, count: int = 1) -> list[dict]:
119
121
  data = []
120
122
 
121
123
  for _ in range(count):
@@ -1,9 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
4
 
3
5
 
4
6
  class FieldSeederTypesEnum(str, Enum):
5
- LLM = "llm"
6
- FAKER = "faker"
7
- STATIC = "static"
8
- CALLABLE = "callable"
9
- CUSTOM = "custom"
7
+ LLM = 'llm'
8
+ FAKER = 'faker'
9
+ STATIC = 'static'
10
+ CALLABLE = 'callable'
11
+ CUSTOM = 'custom'
@@ -1,18 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from typing import Any
7
+
8
+
1
9
  class FieldOverride:
2
- def __init__(self, seeder_class):
10
+ def __init__(self, seeder_class: Any) -> None:
3
11
  self.seeder_class = seeder_class
4
12
  self.overrides = {}
5
13
 
6
- def __getattr__(self, name):
14
+ def __getattr__(self, name: str) -> Any:
7
15
  """
8
16
  Delegate attribute lookup to self.seeder_class if the attribute isn't found
9
17
  in this FieldOverride instance.
10
18
  """
11
19
 
12
20
  attr = getattr(self.seeder_class, name)
21
+
13
22
  if callable(attr):
14
- def wrapper(*args, **kwargs):
23
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
15
24
  # TODO: Error here if fields is passed as an arg.
25
+
16
26
  # Pass the overrides field to the seed method
17
27
  if 'fields' in kwargs and isinstance(kwargs['fields'], dict):
18
28
  kwargs['fields'] = {**kwargs['fields'], **self.overrides}
@@ -22,12 +32,12 @@ class FieldOverride:
22
32
  return attr(*args, **kwargs)
23
33
 
24
34
  return wrapper
35
+
25
36
  return attr
26
37
 
27
- def filter(self, **kwargs):
38
+ def filter(self, **kwargs: Any) -> FieldOverride:
28
39
  self.overrides.update(kwargs)
29
40
  return self
30
41
 
31
- def seed(self, count=1):
42
+ def seed(self, count: int = 1) -> list:
32
43
  return self.seeder_class.seed(count=count, fields=self.overrides)
33
-
@@ -1,10 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django_spire.contrib.seeding.field.base import BaseFieldSeeder
2
6
 
7
+ if TYPE_CHECKING:
8
+ from typing import Any
9
+
3
10
 
4
11
  class StaticFieldSeeder(BaseFieldSeeder):
5
- keyword = "static"
12
+ keyword = 'static'
6
13
 
7
- def seed(self, manager = None, count = 1) -> list:
14
+ def seed(self, manager: Any = None, count: int = 1) -> list[dict]:
8
15
  return [
9
16
  {field_name: value[1] for field_name, value in self.seeder_fields.items()}
10
17
  for _ in range(count)
@@ -1,24 +1,28 @@
1
- import unittest
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
4
+
2
5
  from django_spire.contrib.seeding.field.base import BaseFieldSeeder
3
6
 
4
7
 
5
8
  class DummyFieldSeeder(BaseFieldSeeder):
6
- keyword = "faker"
7
-
8
- def seed(self, model_seeder_cls=None, count: int = 1):
9
- return [{"fake": True} for _ in range(count)]
9
+ keyword = 'faker'
10
10
 
11
+ def seed(self, model_seeder_cls=None, count: int = 1) -> list[dict]:
12
+ return [{'fake': True} for _ in range(count)]
11
13
 
12
- class TestBaseFieldSeeder(unittest.TestCase):
13
14
 
14
- def test_filter_fields(self):
15
+ class TestBaseFieldSeeder(TestCase):
16
+ def test_filter_fields(self) -> None:
15
17
  fields = {
16
- "name": ("faker", "name"),
17
- "email": ("faker", "email"),
18
- "description": ("llm", "prompt")
18
+ 'name': ('faker', 'name'),
19
+ 'email': ('faker', 'email'),
20
+ 'description': ('llm', 'prompt')
19
21
  }
20
22
  seeder = DummyFieldSeeder(fields=fields)
21
- faker_fields = seeder.filter_fields("faker")
22
- self.assertIn("name", faker_fields)
23
- self.assertIn("email", faker_fields)
24
- self.assertNotIn("description", faker_fields)
23
+
24
+ faker_fields = seeder.filter_fields('faker')
25
+
26
+ assert 'name' in faker_fields
27
+ assert 'email' in faker_fields
28
+ assert 'description' not in faker_fields
@@ -1,18 +1,22 @@
1
- import unittest
2
- from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
3
4
 
5
+ from django_spire.contrib.seeding.field.callable import CallableFieldSeeder
4
6
 
5
- class TestCallableFieldSeeder(unittest.TestCase):
6
7
 
7
- def test_seeds_callable_fields(self):
8
+ class TestCallableFieldSeeder(TestCase):
9
+ def test_seeds_callable_fields(self) -> None:
8
10
  fields = {
9
- "timestamp": lambda: "2024-01-01 12:00:00",
10
- "status": lambda: "active"
11
+ 'timestamp': lambda: '2024-01-01 12:00:00',
12
+ 'status': lambda: 'active'
11
13
  }
12
14
  seeder = CallableFieldSeeder(fields=fields)
15
+
13
16
  result = seeder.seed(None, count=3)
14
17
 
15
- self.assertEqual(len(result), 3)
18
+ assert len(result) == 3
19
+
16
20
  for row in result:
17
- self.assertEqual(row["timestamp"], "2024-01-01 12:00:00")
18
- self.assertEqual(row["status"], "active")
21
+ assert row['timestamp'] == '2024-01-01 12:00:00'
22
+ assert row['status'] == 'active'
@@ -1,61 +1,74 @@
1
- import unittest
1
+ from __future__ import annotations
2
+
2
3
  from copy import copy
4
+
5
+ from django.test import TestCase
6
+
3
7
  from django_spire.contrib.seeding.field.cleaners import normalize_seeder_fields
4
8
 
5
9
 
6
10
  FIELDS = {
7
- "faker_tuple": ("faker", "name"),
8
- "llm_type": "llm",
9
- "faker_type": "faker",
10
- "static_type": "static",
11
- "callable_type": "callable",
12
- "custom_type": "custom",
13
- "static_bool": True,
14
- "static_str": "approved",
15
- "static_int": 10,
16
- "callable_func": lambda: "now",
17
- "exclude_str": "exclude",
18
- "exclude_tuple": ("exclude",)
11
+ 'faker_tuple': ('faker', 'name'),
12
+ 'llm_type': 'llm',
13
+ 'faker_type': 'faker',
14
+ 'static_type': 'static',
15
+ 'callable_type': 'callable',
16
+ 'custom_type': 'custom',
17
+ 'static_bool': True,
18
+ 'static_str': 'approved',
19
+ 'static_int': 10,
20
+ 'callable_func': lambda: 'now',
21
+ 'exclude_str': 'exclude',
22
+ 'exclude_tuple': ('exclude',)
19
23
  }
20
24
 
21
25
 
22
- class TestNormalizeSeederFields(unittest.TestCase):
26
+ class TestNormalizeSeederFields(TestCase):
27
+ def test_excludes_fields(self) -> None:
28
+ fields = copy(FIELDS)
23
29
 
24
- def test_normalizes_faker_tuple(self):
25
- fields = {"faker_tuple": FIELDS["faker_tuple"]}
26
30
  normalized = normalize_seeder_fields(fields)
27
- self.assertEqual(normalized["faker_tuple"], ("faker", "name"))
28
31
 
29
- def test_excludes_fields(self):
30
- fields = copy(FIELDS)
32
+ assert 'exclude_str' not in normalized
33
+ assert 'exclude_tuple' not in normalized
34
+ assert 'static_str' in normalized
35
+
36
+ def test_normalizes_callable(self) -> None:
37
+ fields = {'callable_func': FIELDS['callable_func']}
38
+
39
+ normalized = normalize_seeder_fields(fields)
40
+
41
+ assert normalized['callable_func'][0] == 'callable'
42
+ assert callable(normalized['callable_func'][1])
43
+
44
+ def test_normalizes_faker_tuple(self) -> None:
45
+ fields = {'faker_tuple': FIELDS['faker_tuple']}
46
+
31
47
  normalized = normalize_seeder_fields(fields)
32
- self.assertNotIn("exclude_str", normalized)
33
- self.assertNotIn("exclude_tuple", normalized)
34
- self.assertIn("static_str", normalized)
35
48
 
36
- def test_normalizes_single_value_strings_to_tuples(self):
49
+ assert normalized['faker_tuple'] == ('faker', 'name')
50
+
51
+ def test_normalizes_single_value_strings_to_tuples(self) -> None:
37
52
  fields = {
38
53
  k: FIELDS[k] for k in (
39
- "llm_type", "faker_type", "static_type", "callable_type", "custom_type"
54
+ 'llm_type', 'faker_type', 'static_type', 'callable_type', 'custom_type'
40
55
  )
41
56
  }
42
- normalized = normalize_seeder_fields(fields)
43
- for key, value in fields.items():
44
- self.assertEqual(normalized[key], (value,))
45
57
 
46
- def test_normalizes_callable(self):
47
- fields = {"callable_func": FIELDS["callable_func"]}
48
58
  normalized = normalize_seeder_fields(fields)
49
- self.assertEqual(normalized["callable_func"][0], "callable")
50
- self.assertTrue(callable(normalized["callable_func"][1]))
51
59
 
52
- def test_normalizes_static_values(self):
60
+ for key, value in fields.items():
61
+ assert normalized[key] == (value,)
62
+
63
+ def test_normalizes_static_values(self) -> None:
53
64
  fields = {
54
- "static_bool": FIELDS["static_bool"],
55
- "static_str": FIELDS["static_str"],
56
- "static_int": FIELDS["static_int"]
65
+ 'static_bool': FIELDS['static_bool'],
66
+ 'static_str': FIELDS['static_str'],
67
+ 'static_int': FIELDS['static_int']
57
68
  }
69
+
58
70
  normalized = normalize_seeder_fields(fields)
59
- self.assertEqual(normalized["static_bool"], ("static", True))
60
- self.assertEqual(normalized["static_str"], ("static", "approved"))
61
- self.assertEqual(normalized["static_int"], ("static", 10))
71
+
72
+ assert normalized['static_bool'] == ('static', True)
73
+ assert normalized['static_str'] == ('static', 'approved')
74
+ assert normalized['static_int'] == ('static', 10)
@@ -1,18 +1,22 @@
1
- import unittest
2
- from django_spire.contrib.seeding.field.static import StaticFieldSeeder
1
+ from __future__ import annotations
2
+
3
+ from django.test import TestCase
3
4
 
5
+ from django_spire.contrib.seeding.field.static import StaticFieldSeeder
4
6
 
5
- class TestStaticFieldSeeder(unittest.TestCase):
6
7
 
7
- def test_seeds_static_fields(self):
8
+ class TestStaticFieldSeeder(TestCase):
9
+ def test_seeds_static_fields(self) -> None:
8
10
  fields = {
9
- "category": ("static", "books"),
10
- "in_stock": ("static", True)
11
+ 'category': ('static', 'books'),
12
+ 'in_stock': ('static', True)
11
13
  }
12
14
  seeder = StaticFieldSeeder(fields=fields)
15
+
13
16
  result = seeder.seed(count=3)
14
17
 
15
- self.assertEqual(len(result), 3)
18
+ assert len(result) == 3
19
+
16
20
  for row in result:
17
- self.assertEqual(row["category"], "books")
18
- self.assertEqual(row["in_stock"], True)
21
+ assert row['category'] == 'books'
22
+ assert row['in_stock'] is True
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dandy import Bot, LlmConfigOptions
2
4
 
3
5
  from django_spire.contrib.seeding.intelligence.intel import SourceIntel
@@ -1,10 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterator
4
+
1
5
  from dandy import BaseIntel
2
6
 
3
7
 
4
8
  class SeedingIntel(BaseIntel):
5
9
  items: list[dict]
6
10
 
7
- def __iter__(self):
11
+ def __iter__(self) -> Iterator[dict]:
8
12
  return iter(self.items)
9
13
 
10
14
 
@@ -1,7 +1,12 @@
1
- from django.db.models.base import Model
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
2
4
 
3
5
  from dandy import Prompt
4
6
 
7
+ if TYPE_CHECKING:
8
+ from django.db.models.base import Model
9
+
5
10
 
6
11
  class SeedingModelClassPromptFactory:
7
12
 
@@ -1,7 +1,12 @@
1
- from django.db.models import Model
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
2
4
 
3
5
  from dandy import Prompt
4
6
 
7
+ if TYPE_CHECKING:
8
+ from django.db.models import Model
9
+
5
10
 
6
11
  def foreign_key_selection_prompt(
7
12
  model_class: type[Model],
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
2
4
 
3
5
  from dandy import Prompt
@@ -1,8 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  from django.contrib.contenttypes.models import ContentType
2
- from django.db.models import Model
3
6
 
4
7
  from dandy import Prompt
5
8
 
9
+ if TYPE_CHECKING:
10
+ from django.db.models import Model
11
+
6
12
 
7
13
  def generic_relationship_selection_prompt(
8
14
  model_class: type[Model],
@@ -1,9 +1,13 @@
1
- from random import shuffle
1
+ from __future__ import annotations
2
2
 
3
- from django.db.models import Model
3
+ from random import shuffle
4
+ from typing import TYPE_CHECKING
4
5
 
5
6
  from dandy import Prompt
6
7
 
8
+ if TYPE_CHECKING:
9
+ from django.db.models import Model
10
+
7
11
 
8
12
  def hierarchical_selection_prompt(
9
13
  model_class: type[Model],
@@ -1,6 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  from dandy import Prompt
2
- from django.db.models.enums import TextChoices
3
- from django.db.models.fields import Field
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from django.db.models.fields import Field
9
+ from django.db.models.enums import TextChoices
4
10
 
5
11
 
6
12
  def model_field_choices_prompt(
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dandy import Prompt
2
4
 
3
5
 
@@ -1,7 +1,12 @@
1
- from django.db.models import Model
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
2
4
 
3
5
  from dandy import Prompt
4
6
 
7
+ if TYPE_CHECKING:
8
+ from django.db.models import Model
9
+
5
10
 
6
11
  def objective_prompt(
7
12
  model_class: type[Model],
@@ -1,12 +1,18 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
4
+ from typing import TYPE_CHECKING
2
5
 
3
6
  from django.core.management.base import BaseCommand, CommandError
4
7
 
5
8
  from dandy.conf import settings
6
- from dandy.recorder import recorder_to_html_file
7
9
 
8
10
  from django_spire.contrib.seeding.intelligence.bots.seeder_generator_bot import SeederGeneratorBot
9
11
 
12
+ if TYPE_CHECKING:
13
+ from argparse import ArgumentParser
14
+ from typing import Any
15
+
10
16
 
11
17
  _SEEDING_OUTPUT_PATH = Path(settings.BASE_PATH, '.seeding_generator_output')
12
18
 
@@ -14,7 +20,7 @@ _SEEDING_OUTPUT_PATH = Path(settings.BASE_PATH, '.seeding_generator_output')
14
20
  class Command(BaseCommand):
15
21
  help = 'Generate a Seeder'
16
22
 
17
- def add_arguments(self, parser):
23
+ def add_arguments(self, parser: ArgumentParser) -> None:
18
24
  parser.add_argument(
19
25
  'model_import',
20
26
  type=str,
@@ -29,7 +35,7 @@ class Command(BaseCommand):
29
35
  )
30
36
 
31
37
  # @recorder_to_html_file('seeding_generator')
32
- def handle(self, *args, **kwargs):
38
+ def handle(self, *args: Any, **kwargs: Any) -> None:
33
39
  if not kwargs['model_import'] or not kwargs['model_description']:
34
40
  message = 'You must provide a model import path and a model description'
35
41
  raise CommandError(message)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from django_spire.contrib.seeding import DjangoModelSeeder
2
4
  from django_spire.contrib.seeding.model.django.tests.test_seeder import Product
3
5
 
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
2
5
 
3
6
  from dandy.recorder import recorder_to_html_file
4
7
  from dandy import SqliteCache, generate_cache_key
@@ -6,12 +9,15 @@ from dandy import SqliteCache, generate_cache_key
6
9
  from django_spire.contrib.seeding.field.override import FieldOverride
7
10
  from django_spire.contrib.seeding.model.config import FieldsConfig
8
11
 
12
+ if TYPE_CHECKING:
13
+ from typing import Any
14
+
9
15
 
10
16
  class classproperty:
11
- def __init__(self, fget):
17
+ def __init__(self, fget: Any) -> None:
12
18
  self.fget = fget
13
19
 
14
- def __get__(self, instance, owner):
20
+ def __get__(self, instance: Any, owner: Any) -> Any:
15
21
  return self.fget(owner)
16
22
 
17
23
 
@@ -33,30 +39,33 @@ class BaseModelSeeder(ABC):
33
39
  _field_config: FieldsConfig = None
34
40
 
35
41
  @classproperty
36
- def override(cls):
42
+ def override(cls) -> FieldOverride:
37
43
  return cls.override_class(seeder_class=cls)
38
44
 
39
45
  @classmethod
40
46
  def get_field_config(cls) -> FieldsConfig:
41
47
  if cls._field_config is None:
42
48
  if cls.model_class is None:
43
- raise ValueError('model_class must be defined before using seeder.')
49
+ message = 'model_class must be defined before using seeder.'
50
+ raise ValueError(message)
44
51
 
45
52
  raw_fields = cls.__dict__.get('fields', {})
53
+
46
54
  cls._field_config = cls.field_config_class(
47
55
  raw_fields=raw_fields,
48
56
  field_names=cls.field_names(),
49
57
  default_to=cls.default_to,
50
58
  model_class=cls.model_class,
51
59
  )
60
+
52
61
  return cls._field_config
53
62
 
54
63
  @classmethod
55
- def resolved_fields(cls):
64
+ def resolved_fields(cls) -> dict:
56
65
  return cls.get_field_config().fields
57
66
 
58
67
  @classmethod
59
- def clear_cache(cls):
68
+ def clear_cache(cls) -> None:
60
69
  SqliteCache.clear(cache_name=cls.cache_name)
61
70
 
62
71
  @classmethod
@@ -68,7 +77,7 @@ class BaseModelSeeder(ABC):
68
77
  @recorder_to_html_file('model_seeder')
69
78
  def seed_data(
70
79
  cls,
71
- count=1,
80
+ count: int = 1,
72
81
  fields: dict | None = None,
73
82
  ) -> list[dict]:
74
83
  field_config = (