wbcore 1.46.0__py2.py3-none-any.whl → 1.58.2__py2.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 (321) hide show
  1. wbcore/cache/decorators.py +5 -3
  2. wbcore/cache/registry.py +14 -7
  3. wbcore/configs/__init__.py +1 -0
  4. wbcore/configs/configs.py +5 -0
  5. wbcore/configs/decorators.py +1 -1
  6. wbcore/configurations/configurations/apps.py +3 -2
  7. wbcore/configurations/configurations/authentication.py +1 -1
  8. wbcore/configurations/configurations/base.py +1 -1
  9. wbcore/configurations/configurations/cache.py +1 -1
  10. wbcore/configurations/configurations/i18nl10n.py +2 -1
  11. wbcore/configurations/configurations/maintenance.py +1 -1
  12. wbcore/configurations/configurations/media.py +1 -1
  13. wbcore/configurations/configurations/middleware.py +1 -1
  14. wbcore/configurations/configurations/rest_framework.py +1 -1
  15. wbcore/configurations/configurations/static.py +3 -3
  16. wbcore/configurations/configurations/wbcore.py +1 -1
  17. wbcore/content_type/serializers.py +13 -5
  18. wbcore/content_type/utils.py +3 -3
  19. wbcore/content_type/viewsets.py +2 -2
  20. wbcore/contrib/agenda/filters/calendar_item.py +5 -4
  21. wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po +145 -52
  22. wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po.translated +236 -0
  23. wbcore/contrib/agenda/locale/en/LC_MESSAGES/django.po +200 -0
  24. wbcore/contrib/agenda/locale/fr/LC_MESSAGES/django.po +201 -0
  25. wbcore/contrib/agenda/viewsets/calendar_items.py +7 -7
  26. wbcore/contrib/agenda/viewsets/menu/calendar_items.py +0 -6
  27. wbcore/contrib/ai/exceptions.py +5 -5
  28. wbcore/contrib/ai/llm/config.py +76 -27
  29. wbcore/contrib/ai/llm/mixins.py +5 -8
  30. wbcore/contrib/ai/llm/utils.py +50 -26
  31. wbcore/contrib/authentication/admin.py +2 -2
  32. wbcore/contrib/authentication/factories/__init__.py +8 -1
  33. wbcore/contrib/authentication/factories/users.py +19 -0
  34. wbcore/contrib/authentication/filters.py +1 -2
  35. wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po +209 -187
  36. wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po.translated +634 -0
  37. wbcore/contrib/authentication/locale/en/LC_MESSAGES/django.po +590 -0
  38. wbcore/contrib/authentication/locale/fr/LC_MESSAGES/django.po +592 -0
  39. wbcore/contrib/authentication/models/users.py +3 -3
  40. wbcore/contrib/authentication/models/users_activities.py +1 -1
  41. wbcore/contrib/authentication/serializers/users.py +2 -2
  42. wbcore/contrib/authentication/tests/test_tokens.py +3 -3
  43. wbcore/contrib/authentication/tests/test_users.py +0 -1
  44. wbcore/contrib/authentication/urls.py +0 -4
  45. wbcore/contrib/authentication/viewsets/endpoints/user_activities.py +2 -11
  46. wbcore/contrib/authentication/viewsets/endpoints/users.py +0 -3
  47. wbcore/contrib/authentication/viewsets/user_activities.py +2 -1
  48. wbcore/contrib/authentication/viewsets/users.py +6 -4
  49. wbcore/contrib/color/models.py +2 -1
  50. wbcore/contrib/currency/factories.py +1 -1
  51. wbcore/contrib/currency/import_export/backends/fixerio/currency_fx_rates.py +3 -1
  52. wbcore/contrib/currency/models.py +30 -8
  53. wbcore/contrib/currency/serializers.py +5 -1
  54. wbcore/contrib/currency/tests/test_serializers.py +7 -3
  55. wbcore/contrib/currency/tests/test_viewsets.py +1 -1
  56. wbcore/contrib/currency/viewsets/currency.py +2 -2
  57. wbcore/contrib/currency/viewsets/endpoints/currency_fx_rates.py +0 -9
  58. wbcore/contrib/dataloader/tests/test/dataloaders/protocols.py +1 -2
  59. wbcore/contrib/dataloader/utils.py +2 -2
  60. wbcore/contrib/directory/factories/__init__.py +1 -1
  61. wbcore/contrib/directory/factories/entries.py +2 -1
  62. wbcore/contrib/directory/filters/entries.py +9 -0
  63. wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po +728 -714
  64. wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po.translated +1779 -0
  65. wbcore/contrib/directory/locale/en/LC_MESSAGES/django.po +1652 -0
  66. wbcore/contrib/directory/locale/fr/LC_MESSAGES/django.po +1654 -0
  67. wbcore/contrib/directory/migrations/0011_person_description_person_i18n.py +24 -0
  68. wbcore/contrib/directory/migrations/0012_alter_person_managers.py +20 -0
  69. wbcore/contrib/directory/migrations/0013_alter_clientmanagerrelationship_options.py +17 -0
  70. wbcore/contrib/directory/models/contacts.py +2 -2
  71. wbcore/contrib/directory/models/entries.py +31 -5
  72. wbcore/contrib/directory/models/relationships.py +31 -35
  73. wbcore/contrib/directory/permissions.py +6 -0
  74. wbcore/contrib/directory/serializers/companies.py +16 -8
  75. wbcore/contrib/directory/serializers/contacts.py +8 -8
  76. wbcore/contrib/directory/serializers/entries.py +26 -15
  77. wbcore/contrib/directory/serializers/entry_representations.py +4 -2
  78. wbcore/contrib/directory/serializers/persons.py +12 -10
  79. wbcore/contrib/directory/serializers/relationships.py +2 -2
  80. wbcore/contrib/directory/tests/conftest.py +2 -0
  81. wbcore/contrib/directory/tests/disable_signals.py +11 -1
  82. wbcore/contrib/directory/tests/signals.py +2 -2
  83. wbcore/contrib/directory/tests/test_models.py +88 -66
  84. wbcore/contrib/directory/tests/test_serializers.py +1 -1
  85. wbcore/contrib/directory/tests/test_viewsets.py +8 -8
  86. wbcore/contrib/directory/viewsets/buttons/__init__.py +1 -1
  87. wbcore/contrib/directory/viewsets/buttons/relationships.py +32 -0
  88. wbcore/contrib/directory/viewsets/contacts.py +6 -6
  89. wbcore/contrib/directory/viewsets/display/__init__.py +1 -1
  90. wbcore/contrib/directory/viewsets/display/contacts.py +1 -14
  91. wbcore/contrib/directory/viewsets/display/entries.py +68 -38
  92. wbcore/contrib/directory/viewsets/display/relationships.py +26 -50
  93. wbcore/contrib/directory/viewsets/endpoints/relationships.py +1 -26
  94. wbcore/contrib/directory/viewsets/entries.py +8 -6
  95. wbcore/contrib/directory/viewsets/previews/entries.py +3 -3
  96. wbcore/contrib/directory/viewsets/relationships.py +16 -2
  97. wbcore/contrib/directory/viewsets/titles/relationships.py +2 -3
  98. wbcore/contrib/documents/filters.py +0 -2
  99. wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po +103 -94
  100. wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po.translated +285 -0
  101. wbcore/contrib/documents/locale/en/LC_MESSAGES/django.po +271 -0
  102. wbcore/contrib/documents/locale/fr/LC_MESSAGES/django.po +270 -0
  103. wbcore/contrib/documents/tests/test_models.py +32 -28
  104. wbcore/contrib/documents/viewsets/endpoints/shareable_links.py +2 -21
  105. wbcore/contrib/dynamic_preferences/types.py +108 -0
  106. wbcore/contrib/dynamic_preferences/viewsets.py +27 -0
  107. wbcore/contrib/example_app/filters/event.py +3 -1
  108. wbcore/contrib/example_app/filters/match.py +1 -1
  109. wbcore/contrib/example_app/models.py +91 -22
  110. wbcore/contrib/example_app/serializers/person_team.py +4 -4
  111. wbcore/contrib/example_app/templates/example_app/embedded_view.html +19 -0
  112. wbcore/contrib/example_app/tests/e2e/test_teams.py +1 -1
  113. wbcore/contrib/example_app/tests/test_models/test_match.py +17 -7
  114. wbcore/contrib/example_app/urls.py +2 -0
  115. wbcore/contrib/example_app/views.py +7 -0
  116. wbcore/contrib/example_app/viewsets/displays/team.py +23 -4
  117. wbcore/contrib/example_app/viewsets/menu/menus.py +1 -1
  118. wbcore/contrib/example_app/viewsets/menus.py +1 -1
  119. wbcore/contrib/geography/tests/conftest.py +14 -0
  120. wbcore/contrib/geography/tests/test_models.py +23 -8
  121. wbcore/contrib/geography/tests/test_viewsets.py +96 -2
  122. wbcore/contrib/guardian/tests/test_model_mixins.py +3 -4
  123. wbcore/contrib/guardian/tests/test_tasks.py +9 -9
  124. wbcore/contrib/guardian/tests/test_viewsets.py +2 -2
  125. wbcore/contrib/guardian/viewsets/configs/__init__.py +1 -1
  126. wbcore/contrib/guardian/viewsets/configs/buttons.py +9 -0
  127. wbcore/contrib/guardian/viewsets/configs/endpoints.py +7 -0
  128. wbcore/contrib/guardian/viewsets/viewsets.py +2 -0
  129. wbcore/contrib/i18n/__init__.py +2 -0
  130. wbcore/contrib/i18n/buttons.py +33 -0
  131. wbcore/contrib/i18n/serializers/__init__.py +0 -0
  132. wbcore/contrib/i18n/serializers/fields.py +20 -0
  133. wbcore/contrib/i18n/serializers/mixins.py +13 -0
  134. wbcore/contrib/i18n/tests/conftest.py +11 -0
  135. wbcore/contrib/i18n/tests/test_viewsets.py +67 -0
  136. wbcore/contrib/i18n/translation.py +140 -0
  137. wbcore/contrib/i18n/viewsets.py +36 -0
  138. wbcore/contrib/icons/backends/default.py +1 -0
  139. wbcore/contrib/icons/backends/material.py +1 -0
  140. wbcore/contrib/icons/icons.py +5 -8
  141. wbcore/contrib/io/admin.py +1 -0
  142. wbcore/contrib/io/backends/mail.py +3 -2
  143. wbcore/contrib/io/backends/utils.py +14 -17
  144. wbcore/contrib/io/exceptions.py +8 -0
  145. wbcore/contrib/io/factories.py +1 -1
  146. wbcore/contrib/io/import_export/backends/mail.py +1 -0
  147. wbcore/contrib/io/import_export/backends/sftp.py +29 -20
  148. wbcore/contrib/io/import_export/backends/stream.py +2 -2
  149. wbcore/contrib/io/import_export/parsers/__init__.py +0 -0
  150. wbcore/contrib/io/import_export/parsers/base_csv.py +36 -0
  151. wbcore/contrib/io/import_export/parsers/resources.py +50 -0
  152. wbcore/contrib/io/imports.py +33 -25
  153. wbcore/contrib/io/locale/de/LC_MESSAGES/django.po +114 -22
  154. wbcore/contrib/io/locale/de/LC_MESSAGES/django.po.translated +103 -0
  155. wbcore/contrib/io/locale/en/LC_MESSAGES/django.po +138 -0
  156. wbcore/contrib/io/locale/fr/LC_MESSAGES/django.po +138 -0
  157. wbcore/contrib/io/migrations/0008_importsource_resource_kwargs.py +18 -0
  158. wbcore/contrib/io/models.py +65 -45
  159. wbcore/contrib/io/resources.py +0 -6
  160. wbcore/contrib/io/serializers.py +2 -2
  161. wbcore/contrib/io/signals.py +4 -0
  162. wbcore/contrib/io/tests/test_backends.py +19 -13
  163. wbcore/contrib/io/tests/test_exports.py +1 -1
  164. wbcore/contrib/io/tests/test_imports.py +1 -1
  165. wbcore/contrib/io/tests/test_models.py +47 -14
  166. wbcore/contrib/io/tests/test_viewsets.py +271 -0
  167. wbcore/contrib/io/viewset_mixins.py +41 -54
  168. wbcore/contrib/notifications/admin.py +1 -0
  169. wbcore/contrib/notifications/apps.py +2 -1
  170. wbcore/contrib/notifications/backends/abstract_backend.py +2 -4
  171. wbcore/contrib/notifications/backends/firebase/backends.py +5 -2
  172. wbcore/contrib/notifications/dispatch.py +18 -7
  173. wbcore/contrib/notifications/factories/notification_types.py +1 -0
  174. wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po +25 -19
  175. wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po.translated +63 -0
  176. wbcore/contrib/notifications/locale/en/LC_MESSAGES/django.po +61 -0
  177. wbcore/contrib/notifications/locale/fr/LC_MESSAGES/django.po +62 -0
  178. wbcore/contrib/notifications/migrations/0008_notificationtype_is_lock.py +18 -0
  179. wbcore/contrib/notifications/migrations/0009_alter_notificationtypesetting_options_and_more.py +32 -0
  180. wbcore/contrib/notifications/models/notification_types.py +67 -24
  181. wbcore/contrib/notifications/serializers/notification_types.py +16 -1
  182. wbcore/contrib/notifications/tests/test_models/test_tokens.py +8 -0
  183. wbcore/contrib/notifications/tests/test_serializers/test_notification_types.py +5 -0
  184. wbcore/contrib/notifications/tests/test_viewsets/test_notification_types.py +3 -5
  185. wbcore/contrib/notifications/utils.py +3 -2
  186. wbcore/contrib/notifications/viewsets/configs/notification_types.py +28 -6
  187. wbcore/contrib/notifications/viewsets/menus.py +1 -1
  188. wbcore/contrib/notifications/viewsets/notification_types.py +12 -2
  189. wbcore/contrib/pandas/fields.py +38 -10
  190. wbcore/contrib/pandas/filters.py +4 -1
  191. wbcore/contrib/pandas/filterset.py +8 -7
  192. wbcore/contrib/pandas/tests/test_fields/test_number_fields.py +2 -7
  193. wbcore/contrib/pandas/utils.py +1 -1
  194. wbcore/contrib/pandas/views.py +14 -13
  195. wbcore/contrib/tags/models/tags.py +4 -1
  196. wbcore/contrib/workflow/factories/display.py +2 -2
  197. wbcore/contrib/workflow/factories/transition.py +16 -15
  198. wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po +457 -566
  199. wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po.translated +1326 -0
  200. wbcore/contrib/workflow/locale/en/LC_MESSAGES/django.po +1102 -0
  201. wbcore/contrib/workflow/locale/fr/LC_MESSAGES/django.po +1114 -0
  202. wbcore/contrib/workflow/models/data.py +7 -4
  203. wbcore/contrib/workflow/models/process.py +2 -2
  204. wbcore/contrib/workflow/models/step.py +57 -15
  205. wbcore/contrib/workflow/serializers/data.py +8 -8
  206. wbcore/contrib/workflow/serializers/process.py +3 -2
  207. wbcore/contrib/workflow/tests/conftest.py +224 -0
  208. wbcore/contrib/workflow/tests/test_dispatch.py +82 -77
  209. wbcore/contrib/workflow/tests/test_displays.py +10 -88
  210. wbcore/contrib/workflow/tests/test_filters.py +57 -40
  211. wbcore/contrib/workflow/tests/test_models/step/test_decision_step.py +71 -68
  212. wbcore/contrib/workflow/tests/test_models/step/test_email_step.py +78 -38
  213. wbcore/contrib/workflow/tests/test_models/step/test_finish_step.py +152 -90
  214. wbcore/contrib/workflow/tests/test_models/step/test_join_step.py +100 -110
  215. wbcore/contrib/workflow/tests/test_models/step/test_step.py +168 -33
  216. wbcore/contrib/workflow/tests/test_models/test_condition.py +1 -1
  217. wbcore/contrib/workflow/tests/test_models/test_workflow.py +3 -3
  218. wbcore/contrib/workflow/tests/test_serializers.py +172 -150
  219. wbcore/contrib/workflow/tests/test_viewsets.py +264 -323
  220. wbcore/contrib/workflow/tests/test_workflow_assignees.py +215 -205
  221. wbcore/contrib/workflow/viewsets/process.py +4 -1
  222. wbcore/contrib/workflow/workflows/assignees.py +12 -7
  223. wbcore/dynamic_preferences_registry.py +102 -0
  224. wbcore/enums.py +2 -51
  225. wbcore/filters/fields/choices.py +4 -6
  226. wbcore/filters/fields/content_type.py +15 -4
  227. wbcore/filters/fields/datetime.py +50 -25
  228. wbcore/filters/fields/models.py +18 -9
  229. wbcore/filters/fields/numbers.py +9 -8
  230. wbcore/filters/filterset.py +27 -6
  231. wbcore/filters/mixins.py +41 -42
  232. wbcore/forms.py +6 -6
  233. wbcore/fsm/markdown_extensions.py +1 -1
  234. wbcore/fsm/mixins.py +20 -6
  235. wbcore/locale/de/LC_MESSAGES/django.po +982 -397
  236. wbcore/locale/de/LC_MESSAGES/django.po.translated +1580 -0
  237. wbcore/locale/en/LC_MESSAGES/django.po +1234 -0
  238. wbcore/locale/fr/LC_MESSAGES/django.po +1235 -0
  239. wbcore/markdown/models.py +8 -5
  240. wbcore/markdown/views.py +1 -1
  241. wbcore/menus/menus.py +2 -2
  242. wbcore/metadata/configs/buttons/bases.py +10 -7
  243. wbcore/metadata/configs/buttons/buttons.py +2 -1
  244. wbcore/metadata/configs/buttons/enums.py +50 -0
  245. wbcore/metadata/configs/buttons/view_config.py +13 -46
  246. wbcore/metadata/configs/display/display.py +2 -2
  247. wbcore/metadata/configs/display/formatting.py +6 -9
  248. wbcore/metadata/configs/display/instance_display/display.py +5 -2
  249. wbcore/metadata/configs/display/instance_display/pages.py +1 -1
  250. wbcore/metadata/configs/display/instance_display/shortcuts.py +1 -1
  251. wbcore/metadata/configs/display/list_display.py +54 -40
  252. wbcore/metadata/configs/display/models.py +6 -0
  253. wbcore/metadata/configs/display/view_config.py +11 -9
  254. wbcore/metadata/configs/endpoints.py +11 -4
  255. wbcore/metadata/configs/fields.py +6 -1
  256. wbcore/metadata/configs/filter_fields.py +12 -13
  257. wbcore/metadata/configs/identifiers.py +3 -1
  258. wbcore/metadata/tests/test_buttons.py +13 -16
  259. wbcore/models/fields.py +2 -2
  260. wbcore/pagination.py +1 -2
  261. wbcore/permissions/permissions.py +2 -2
  262. wbcore/permissions/utils.py +2 -2
  263. wbcore/release_notes/display.py +2 -8
  264. wbcore/release_notes/serializers.py +2 -9
  265. wbcore/release_notes/viewsets.py +8 -2
  266. wbcore/reversion/viewsets/titles.py +4 -3
  267. wbcore/serializers/__init__.py +2 -0
  268. wbcore/serializers/fields/__init__.py +2 -1
  269. wbcore/serializers/fields/boolean.py +1 -1
  270. wbcore/serializers/fields/choice.py +28 -4
  271. wbcore/serializers/fields/datetime.py +45 -36
  272. wbcore/serializers/fields/fields.py +1 -1
  273. wbcore/serializers/fields/fsm.py +1 -1
  274. wbcore/serializers/fields/list.py +2 -5
  275. wbcore/serializers/fields/mixins.py +24 -11
  276. wbcore/serializers/fields/number.py +6 -23
  277. wbcore/serializers/fields/other.py +2 -10
  278. wbcore/serializers/fields/related.py +4 -6
  279. wbcore/serializers/fields/text.py +1 -1
  280. wbcore/serializers/fields/types.py +2 -0
  281. wbcore/serializers/serializers.py +12 -3
  282. wbcore/signals/__init__.py +1 -0
  283. wbcore/signals/clone.py +4 -0
  284. wbcore/signals/models.py +2 -6
  285. wbcore/tasks.py +2 -2
  286. wbcore/templates/wbcore/email_base_template.html +3 -3
  287. wbcore/test/e2e_helpers_methods/e2e_checks.py +10 -4
  288. wbcore/test/e2e_helpers_methods/e2e_helper_methods.py +4 -2
  289. wbcore/test/mixins.py +52 -102
  290. wbcore/test/tests.py +6 -9
  291. wbcore/test/utils.py +3 -4
  292. wbcore/tests/e2e/test_e2e.py +2 -2
  293. wbcore/tests/test_cache/test_decorators.py +4 -7
  294. wbcore/tests/test_configs.py +2 -5
  295. wbcore/tests/test_enums.py +2 -1
  296. wbcore/tests/test_fields/test_choice_fields.py +9 -1
  297. wbcore/tests/test_fields/test_number_fields.py +7 -15
  298. wbcore/tests/test_fields/test_other_fields.py +1 -2
  299. wbcore/tests/test_filters/test_mixins.py +35 -35
  300. wbcore/tests/test_list_display.py +0 -2
  301. wbcore/tests/test_models/test_mixins.py +1 -1
  302. wbcore/tests/test_utils/test_date.py +1 -1
  303. wbcore/tests/test_utils/test_date_builder.py +25 -1
  304. wbcore/tests/test_utils/test_primary.py +1 -1
  305. wbcore/urls.py +4 -0
  306. wbcore/utils/date.py +18 -2
  307. wbcore/utils/figures.py +2 -2
  308. wbcore/utils/models.py +21 -4
  309. wbcore/utils/reportlab.py +7 -0
  310. wbcore/utils/rrules.py +3 -1
  311. wbcore/utils/string_loader.py +1 -1
  312. wbcore/utils/strings.py +3 -3
  313. wbcore/utils/views.py +8 -3
  314. wbcore/viewsets/mixins.py +9 -4
  315. {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/METADATA +9 -5
  316. {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/RECORD +317 -271
  317. wbcore/contrib/geography/tests/test_serializers.py +0 -7
  318. wbcore/contrib/geography/tests/tests.py +0 -13
  319. wbcore/contrib/io/tests/tests.py +0 -19
  320. wbcore/contrib/workflow/tests/tests.py +0 -25
  321. {wbcore-1.46.0.dist-info → wbcore-1.58.2.dist-info}/WHEEL +0 -0
@@ -1,10 +1,12 @@
1
1
  from langchain_core.exceptions import LangChainException, OutputParserException
2
2
 
3
3
  APIStatusErrors = [LangChainException, OutputParserException]
4
+ BadRequestErrors = []
4
5
 
5
6
  try:
6
7
  from openai._exceptions import (
7
8
  AuthenticationError,
9
+ BadRequestError,
8
10
  ConflictError,
9
11
  InternalServerError,
10
12
  NotFoundError,
@@ -24,17 +26,15 @@ try:
24
26
  InternalServerError,
25
27
  ]
26
28
  )
29
+ BadRequestErrors.append(BadRequestError)
27
30
  except ImportError:
28
31
  pass
29
32
 
30
33
 
31
34
  try:
32
- from anthropic._exceptions import (
33
- APIConnectionError,
34
- APIResponseValidationError,
35
- APIStatusError,
36
- )
35
+ from anthropic._exceptions import APIConnectionError, APIResponseValidationError, APIStatusError, BadRequestError
37
36
 
38
37
  APIStatusErrors.extend([APIResponseValidationError, APIStatusError, APIConnectionError])
38
+ BadRequestErrors.append(BadRequestError)
39
39
  except ImportError:
40
40
  pass
@@ -1,22 +1,22 @@
1
1
  import logging
2
- from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar
2
+ from typing import Any, Callable, Generic, TypeVar
3
3
 
4
4
  from celery import shared_task
5
5
  from django.db import models
6
6
  from django.db.models.signals import ModelSignal
7
7
  from langchain_core.language_models import BaseChatModel
8
+ from langchain_core.messages import BaseMessage
8
9
  from langchain_openai import ChatOpenAI
10
+ from pydantic import BaseModel
9
11
 
10
- from ..exceptions import APIStatusErrors
11
- from .utils import construct_prompt, run_llm
12
+ from ..exceptions import APIStatusErrors, BadRequestErrors
13
+ from .utils import run_llm
12
14
 
13
15
  logger = logging.getLogger("llm")
14
16
 
15
- if TYPE_CHECKING:
16
- from pydantic import BaseModel
17
-
18
17
 
19
18
  @shared_task(
19
+ queue="llm",
20
20
  autoretry_for=tuple(APIStatusErrors),
21
21
  retry_backoff=10,
22
22
  max_retries=5, # retry 5 times maximum
@@ -31,16 +31,31 @@ def invoke_as_task(
31
31
  max_tokens: int,
32
32
  model_field: str | None,
33
33
  output_model: "type[BaseModel] | None",
34
- llm_kwargs: dict[str, Any],
34
+ tools: list[dict[str, Any]],
35
+ query: Callable | dict[str, Any] | None,
36
+ extra_query: dict[str, Any] | None,
37
+ llm_kwargs: dict[str, Any] | None,
38
+ result_parser: Callable | None,
35
39
  ):
36
40
  try:
37
- result = run_llm(prompt, instance, output_model, chat_model, chat_model_name, max_tokens, **llm_kwargs)
38
- if model_field is not None:
39
- setattr(instance, model_field, result)
40
-
41
- if output_model is not None:
42
- for field, value in result.items():
41
+ if callable(query):
42
+ query = query(instance)
43
+ if not query:
44
+ query = dict()
45
+ if extra_query:
46
+ query.update(extra_query)
47
+
48
+ output_result, ai_msg = run_llm(
49
+ prompt, output_model, chat_model, chat_model_name, max_tokens, query=query, extra_tools=tools, **llm_kwargs
50
+ )
51
+ # if a result parser is provided, we use this to parse the results into the instance
52
+ if result_parser:
53
+ instance = result_parser(instance, output_result, ai_msg)
54
+ elif output_result and isinstance(output_result, BaseModel):
55
+ for field, value in output_result.model_dump().items():
43
56
  setattr(instance, field, value)
57
+ except tuple(BadRequestErrors) as e: # we silent bad request error because there is nothing we can do about it
58
+ logger.warning(str(e))
44
59
  except tuple(APIStatusErrors) as e: # for APIStatusError, we let celery retry it
45
60
  raise e
46
61
  except Exception as e: # otherwise we log the error and silently fail
@@ -58,15 +73,18 @@ class LLMConfig(Generic[T]):
58
73
  def __init__(
59
74
  self,
60
75
  key: str,
61
- prompt: Callable | str,
76
+ prompt: Callable | str | list[BaseMessage],
62
77
  field: str | None = None,
63
78
  output_model: "type[BaseModel] | None" = None,
64
79
  on_save: bool = True,
65
80
  on_condition: Callable | bool | None = None,
66
- chat_model: type[BaseChatModel] = ChatOpenAI,
67
- chat_model_name: str = "gpt-4o-mini",
81
+ chat_model: Callable | tuple[type[BaseChatModel], str] = (ChatOpenAI, "gpt-4o-mini"),
68
82
  max_tokens: int = 16000,
69
- **kwargs,
83
+ tools: Callable | list[dict[str, str]] | None = None,
84
+ query: Callable | dict[str, Any] | None = None,
85
+ llm_kwargs_callback: Callable | None = None,
86
+ result_parser: Callable | None = None,
87
+ **llm_kwargs,
70
88
  ):
71
89
  self.key = key
72
90
  self.on_save = on_save
@@ -75,9 +93,12 @@ class LLMConfig(Generic[T]):
75
93
  self.prompt = prompt
76
94
  self.output_model = output_model
77
95
  self.chat_model = chat_model
78
- self.chat_model_name = chat_model_name
79
96
  self.max_tokens = max_tokens
80
- self.kwargs = kwargs
97
+ self.tools = tools
98
+ self.query = query
99
+ self.llm_kwargs_callback = llm_kwargs_callback
100
+ self.llm_kwargs = llm_kwargs
101
+ self.result_parser = result_parser
81
102
 
82
103
  def check_condition(self, instance: T) -> bool:
83
104
  if self.on_condition is None:
@@ -86,27 +107,55 @@ class LLMConfig(Generic[T]):
86
107
  return self.on_condition(instance)
87
108
  return bool(self.on_condition)
88
109
 
89
- def get_prompt(self, instance: T):
90
- prompt = construct_prompt(instance, self.prompt)
110
+ def _get_prompt(self, instance: T):
111
+ prompt = self.prompt
112
+ extra_query = dict()
113
+ if callable(prompt):
114
+ prompt = prompt(instance)
91
115
  for _, response in add_llm_prompt.send(sender=instance.__class__, instance=instance, key=self.key):
92
- prompt.extend(response)
116
+ remote_prompts, remote_query = response
117
+ prompt.extend(remote_prompts)
118
+ extra_query.update(remote_query)
119
+
120
+ return prompt, extra_query
121
+
122
+ def _get_chat_model(self, instance: T) -> tuple[type[BaseChatModel], str]:
123
+ if callable(self.chat_model):
124
+ return self.chat_model(instance)
125
+ return self.chat_model
126
+
127
+ def _get_tools(self, instance: T) -> list[dict[str, Any]]:
128
+ if callable(self.tools):
129
+ return self.tools(instance)
130
+ return self.tools
93
131
 
94
- return prompt
132
+ def _get_llm_kwargs(self, instance: T) -> dict[str, Any]:
133
+ llm_kwargs = self.llm_kwargs
134
+ if callable(self.llm_kwargs_callback):
135
+ llm_kwargs = self.llm_kwargs_callback(instance)
136
+ return llm_kwargs
95
137
 
96
138
  def schedule(self, instance: T, initial: bool = True):
97
- prompt = self.get_prompt(instance)
139
+ prompt, extra_query = self._get_prompt(instance)
98
140
  args = []
99
141
  if initial:
100
142
  args.append(instance)
143
+ chat_model, chat_model_name = self._get_chat_model(instance)
144
+ tools = self._get_tools(instance)
145
+ llm_kwargs = self._get_llm_kwargs(instance)
101
146
  args.extend(
102
147
  [
103
148
  prompt,
104
- self.chat_model,
105
- self.chat_model_name,
149
+ chat_model,
150
+ chat_model_name,
106
151
  self.max_tokens,
107
152
  self.field,
108
153
  self.output_model,
109
- self.kwargs,
154
+ tools,
155
+ self.query,
156
+ extra_query,
157
+ llm_kwargs,
158
+ self.result_parser,
110
159
  ]
111
160
  )
112
161
  return invoke_as_task.s(*args)
@@ -1,10 +1,8 @@
1
- from contextlib import suppress
2
-
3
1
  from celery import chain, shared_task
4
2
  from django.db.models import Model
5
3
 
6
4
 
7
- @shared_task
5
+ @shared_task(queue="llm")
8
6
  def save_instance_as_task(instance):
9
7
  instance.save(_with_llm=False)
10
8
 
@@ -25,11 +23,10 @@ class LLMMixin(Model):
25
23
  tasks.append(config.schedule(self, initial=index == 0))
26
24
  if tasks:
27
25
  res = chain(*tasks, save_instance_as_task.s())
28
- with suppress(Exception): # we suppress any possible celery exception or pytest issue
29
- if _llm_synchronous:
30
- res.apply()
31
- else:
32
- res.apply_async()
26
+ if _llm_synchronous:
27
+ res.apply()
28
+ else:
29
+ res.apply_async()
33
30
 
34
31
  class Meta:
35
32
  abstract = True
@@ -1,45 +1,69 @@
1
- from typing import Any, Callable
1
+ from contextlib import suppress
2
+ from typing import Any
2
3
 
3
4
  from langchain_core.language_models import BaseChatModel
5
+ from langchain_core.messages import SystemMessage
4
6
  from langchain_core.messages.base import BaseMessage
5
- from langchain_core.output_parsers import StrOutputParser
7
+ from langchain_core.output_parsers import PydanticOutputParser, StrOutputParser
8
+ from langchain_core.prompts import ChatPromptTemplate
6
9
  from langchain_openai import ChatOpenAI
7
- from pydantic import BaseModel
10
+ from pydantic import BaseModel, ValidationError
8
11
 
9
12
  type Prompt = str | list[BaseMessage] | BaseMessage
10
13
 
11
14
 
12
- def construct_prompt(instance: Any, prompt: Prompt | Callable[[Any], Prompt]) -> Prompt:
15
+ def _convert_prompt_to_chat_prompt_template(prompt: Prompt, format_instructions=None) -> ChatPromptTemplate:
16
+ if isinstance(prompt, BaseMessage):
17
+ prompt = [prompt]
13
18
  if isinstance(prompt, str):
14
- return prompt.format(instance)
15
-
16
- elif callable(prompt):
17
- return prompt(instance)
18
-
19
- return prompt
19
+ prompt = SystemMessage(content=prompt)
20
+ messages = []
21
+ for index, msg in enumerate(prompt):
22
+ content = msg.content
23
+ if index == 0 and format_instructions:
24
+ if "{format_instructions}" not in content:
25
+ content += "\n{format_instructions}"
26
+ messages.append((msg.type, content))
27
+ return ChatPromptTemplate.from_messages(messages).partial(format_instructions=format_instructions)
20
28
 
21
29
 
22
30
  def run_llm(
23
- prompt: Prompt | Callable[[Any], Prompt],
24
- instance: Any | None = None,
31
+ prompt: Prompt,
25
32
  output_model: "type[BaseModel] | None" = None,
26
33
  chat_model: type[BaseChatModel] = ChatOpenAI,
27
34
  chat_model_name: str = "gpt-4o-mini",
28
35
  max_tokens: int = 16000,
29
- **kwargs,
30
- ) -> str | dict[str, Any]:
31
- model = chat_model(model=chat_model_name, max_tokens=max_tokens, **kwargs) # type: ignore
32
- prompt = construct_prompt(instance, prompt)
33
- if output_model is None:
34
- result = model.invoke(prompt, **kwargs) # type: ignore
35
- parser = StrOutputParser()
36
- return parser.invoke(result, **kwargs)
36
+ extra_tools: list[dict[str, str]] | None = None,
37
+ query: dict[str, Any] | None = None,
38
+ **llm_kwargs,
39
+ ) -> tuple[BaseModel | None, BaseMessage]:
40
+ llm = chat_model(model=chat_model_name, max_tokens=max_tokens, **llm_kwargs) # type: ignore
41
+ tools = []
42
+ if not query:
43
+ query = {}
37
44
 
38
- structured_model = model.with_structured_output(output_model)
39
- result = structured_model.invoke(prompt) # type: ignore
45
+ if extra_tools:
46
+ tools.extend(extra_tools)
47
+ if output_model:
48
+ tools.append(output_model)
49
+ if tools:
50
+ llm = llm.bind_tools(tools)
51
+
52
+ # Set up a parser
53
+ if output_model:
54
+ parser = PydanticOutputParser(pydantic_object=output_model)
55
+ else:
56
+ parser = StrOutputParser()
57
+ try:
58
+ format_instructions = parser.get_format_instructions()
59
+ except NotImplementedError:
60
+ format_instructions = None
61
+ chat_prompt_template = _convert_prompt_to_chat_prompt_template(prompt, format_instructions=format_instructions)
40
62
 
41
- output = dict()
42
- for field, value in result:
43
- output[field] = value
63
+ chain = chat_prompt_template | llm
44
64
 
45
- return output
65
+ response = chain.invoke(query) # type: ignore
66
+ if output_model:
67
+ with suppress(ValidationError, IndexError):
68
+ return output_model.model_validate(response.tool_calls[0]["args"]), response
69
+ return None, response
@@ -202,11 +202,11 @@ class UserAdmin(admin.ModelAdmin):
202
202
  form = self.change_password_form(user)
203
203
 
204
204
  fieldsets = [(None, {"fields": list(form.base_fields)})]
205
- adminForm = admin.helpers.AdminForm(form, fieldsets, {})
205
+ admin_form = admin.helpers.AdminForm(form, fieldsets, {})
206
206
 
207
207
  context = {
208
208
  "title": _("Change password: %s") % escape(user.get_username()),
209
- "adminForm": adminForm,
209
+ "adminForm": admin_form,
210
210
  "form_url": form_url,
211
211
  "form": form,
212
212
  "is_popup": (IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET),
@@ -1,3 +1,10 @@
1
1
  from .tokens import TokenFactory
2
- from .users import AuthenticatedPersonFactory, GroupFactory, SuperUserFactory, UserFactory, InternalUserFactory
2
+ from .users import (
3
+ AuthenticatedPersonFactory,
4
+ GroupFactory,
5
+ PermissionFactory,
6
+ SuperUserFactory,
7
+ UserFactory,
8
+ InternalUserFactory,
9
+ )
3
10
  from .users_activities import UserActivityFactory
@@ -1,5 +1,6 @@
1
1
  import factory
2
2
  from django.contrib.auth.models import Permission
3
+ from django.contrib.contenttypes.models import ContentType
3
4
  from dynamic_preferences.registries import global_preferences_registry
4
5
 
5
6
  from wbcore.contrib.directory.factories import CompanyFactory, PersonFactory
@@ -90,3 +91,21 @@ class GroupFactory(factory.django.DjangoModelFactory):
90
91
 
91
92
  class Meta:
92
93
  model = Group
94
+
95
+
96
+ class ContentTypeFactory(factory.django.DjangoModelFactory):
97
+ class Meta:
98
+ model = ContentType
99
+
100
+ app_label = "app"
101
+ model = "model"
102
+
103
+
104
+ class PermissionFactory(factory.django.DjangoModelFactory):
105
+ class Meta:
106
+ model = Permission
107
+ django_get_or_create = ("content_type", "codename")
108
+
109
+ name = factory.Sequence(lambda n: f"Permission {n}")
110
+ codename = factory.Sequence(lambda n: f"codename_{n}")
111
+ content_type = factory.SubFactory(ContentTypeFactory)
@@ -6,11 +6,10 @@ from .models import UserActivity
6
6
 
7
7
  class UserActivityChartFilter(wb_filters.FilterSet):
8
8
  date = wb_filters.DateTimeRangeFilter(
9
- method=wb_filters.DateRangeFilter.base_date_range_filter_method,
10
9
  label="Date Range",
11
10
  required=True,
12
11
  clearable=False,
13
- default=current_month_date_range,
12
+ initial=current_month_date_range,
14
13
  )
15
14
 
16
15
  class Meta: