django-cfg 1.3.11__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/inlines.py +11 -5
  3. django_cfg/apps/accounts/admin/user_admin.py +39 -16
  4. django_cfg/apps/accounts/serializers/profile.py +1 -1
  5. django_cfg/apps/accounts/services/otp_service.py +18 -11
  6. django_cfg/apps/accounts/signals.py +15 -24
  7. django_cfg/apps/accounts/utils/notifications.py +217 -358
  8. django_cfg/apps/accounts/views/otp.py +2 -2
  9. django_cfg/apps/accounts/views/webhook.py +1 -1
  10. django_cfg/apps/agents/core/django_agent.py +1 -1
  11. django_cfg/apps/api/commands/views.py +66 -83
  12. django_cfg/apps/api/health/drf_views.py +269 -0
  13. django_cfg/apps/api/health/serializers.py +45 -0
  14. django_cfg/apps/api/health/urls.py +6 -1
  15. django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
  16. django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
  17. django_cfg/apps/knowbase/admin/document_admin.py +136 -270
  18. django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
  19. django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
  20. django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
  21. django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
  22. django_cfg/apps/knowbase/config/constance_fields.py +1 -1
  23. django_cfg/apps/knowbase/config/settings.py +2 -2
  24. django_cfg/apps/knowbase/mixins/__init__.py +19 -2
  25. django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
  26. django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
  27. django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
  28. django_cfg/apps/knowbase/mixins/creator.py +10 -10
  29. django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
  30. django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
  31. django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
  32. django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
  33. django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
  34. django_cfg/apps/knowbase/mixins/service.py +2 -2
  35. django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
  36. django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
  37. django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
  38. django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
  39. django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
  40. django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
  41. django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
  42. django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
  43. django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
  44. django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
  45. django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
  46. django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
  47. django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
  48. django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
  49. django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
  50. django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
  51. django_cfg/apps/knowbase/services/embedding/models.py +18 -14
  52. django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
  53. django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
  54. django_cfg/apps/leads/tests.py +1 -1
  55. django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
  56. django_cfg/apps/payments/admin/balance_admin.py +1 -1
  57. django_cfg/apps/payments/admin/currencies_admin.py +1 -1
  58. django_cfg/apps/payments/admin/payments_admin.py +1 -1
  59. django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
  60. django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
  61. django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
  62. django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
  63. django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
  64. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
  65. django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
  66. django_cfg/apps/payments/admin_interface/views/base.py +29 -2
  67. django_cfg/apps/payments/apps.py +1 -1
  68. django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
  69. django_cfg/apps/payments/config/helpers.py +3 -2
  70. django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
  71. django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
  72. django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
  73. django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
  74. django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
  75. django_cfg/apps/payments/management/commands/test_providers.py +1 -1
  76. django_cfg/apps/payments/middleware/api_access.py +1 -1
  77. django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
  78. django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
  79. django_cfg/apps/payments/models/balance.py +2 -2
  80. django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
  81. django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
  82. django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
  83. django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
  84. django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
  85. django_cfg/apps/payments/models/payments.py +2 -2
  86. django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
  87. django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
  88. django_cfg/apps/payments/services/core/base.py +1 -1
  89. django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
  90. django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
  91. django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
  92. django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
  93. django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
  94. django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
  95. django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
  96. django_cfg/apps/payments/services/core/payment_service.py +124 -612
  97. django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
  98. django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
  99. django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
  100. django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
  101. django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
  102. django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
  103. django_cfg/apps/payments/services/providers/base.py +1 -1
  104. django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
  105. django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
  106. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
  107. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
  108. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
  109. django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
  110. django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
  111. django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
  112. django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
  113. django_cfg/apps/payments/services/providers/registry.py +1 -1
  114. django_cfg/apps/payments/services/providers/sync_service.py +1 -1
  115. django_cfg/apps/payments/signals/__init__.py +1 -1
  116. django_cfg/apps/payments/signals/api_key_signals.py +1 -1
  117. django_cfg/apps/payments/signals/balance_signals.py +1 -1
  118. django_cfg/apps/payments/signals/payment_signals.py +1 -1
  119. django_cfg/apps/payments/signals/subscription_signals.py +1 -1
  120. django_cfg/apps/payments/views/api/api_keys.py +1 -1
  121. django_cfg/apps/payments/views/api/balances.py +1 -1
  122. django_cfg/apps/payments/views/api/base.py +1 -1
  123. django_cfg/apps/payments/views/api/currencies.py +1 -1
  124. django_cfg/apps/payments/views/api/payments.py +1 -1
  125. django_cfg/apps/payments/views/api/subscriptions.py +1 -1
  126. django_cfg/apps/payments/views/api/webhooks.py +1 -1
  127. django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
  128. django_cfg/apps/payments/views/serializers/balances.py +1 -1
  129. django_cfg/apps/payments/views/serializers/currencies.py +1 -1
  130. django_cfg/apps/payments/views/serializers/payments.py +1 -1
  131. django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
  132. django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
  133. django_cfg/apps/support/admin/support_admin.py +21 -13
  134. django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
  135. django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
  136. django_cfg/apps/support/utils/support_email_service.py +1 -1
  137. django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
  138. django_cfg/apps/tasks/utils/simulator.py +1 -1
  139. django_cfg/apps/tasks/views/dashboard.py +33 -3
  140. django_cfg/apps/urls.py +5 -1
  141. django_cfg/cli/README.md +57 -471
  142. django_cfg/cli/commands/create_project.py +140 -529
  143. django_cfg/cli/main.py +13 -10
  144. django_cfg/core/__init__.py +63 -6
  145. django_cfg/core/base/__init__.py +5 -0
  146. django_cfg/core/base/config_model.py +652 -0
  147. django_cfg/core/builders/__init__.py +11 -0
  148. django_cfg/core/builders/apps_builder.py +258 -0
  149. django_cfg/core/builders/middleware_builder.py +115 -0
  150. django_cfg/core/builders/security_builder.py +96 -0
  151. django_cfg/core/config.py +20 -892
  152. django_cfg/core/constants.py +69 -0
  153. django_cfg/core/environment/__init__.py +9 -0
  154. django_cfg/core/exceptions.py +45 -298
  155. django_cfg/core/generation/__init__.py +51 -0
  156. django_cfg/core/generation/core_generators/__init__.py +0 -0
  157. django_cfg/core/generation/core_generators/settings.py +90 -0
  158. django_cfg/core/generation/core_generators/static.py +82 -0
  159. django_cfg/core/generation/core_generators/templates.py +141 -0
  160. django_cfg/core/generation/data_generators/__init__.py +15 -0
  161. django_cfg/core/generation/data_generators/cache.py +132 -0
  162. django_cfg/core/generation/data_generators/database.py +117 -0
  163. django_cfg/core/generation/generation.py +92 -0
  164. django_cfg/core/generation/integration_generators/__init__.py +21 -0
  165. django_cfg/core/generation/integration_generators/api.py +237 -0
  166. django_cfg/core/generation/integration_generators/sessions.py +65 -0
  167. django_cfg/core/generation/integration_generators/tailwind.py +54 -0
  168. django_cfg/core/generation/integration_generators/tasks.py +92 -0
  169. django_cfg/core/generation/integration_generators/third_party.py +144 -0
  170. django_cfg/core/generation/orchestrator.py +285 -0
  171. django_cfg/core/generation/protocols.py +30 -0
  172. django_cfg/core/generation/security_generators/__init__.py +0 -0
  173. django_cfg/core/generation/utility_generators/__init__.py +24 -0
  174. django_cfg/core/generation/utility_generators/email.py +58 -0
  175. django_cfg/core/generation/utility_generators/i18n.py +66 -0
  176. django_cfg/core/generation/utility_generators/limits.py +58 -0
  177. django_cfg/core/generation/utility_generators/logging.py +66 -0
  178. django_cfg/core/generation/utility_generators/security.py +101 -0
  179. django_cfg/core/generation/utils/__init__.py +0 -0
  180. django_cfg/core/generation/utils/helpers.py +32 -0
  181. django_cfg/core/integration/__init__.py +18 -25
  182. django_cfg/core/integration/display/startup.py +146 -133
  183. django_cfg/core/integration/url_integration.py +13 -2
  184. django_cfg/core/services/__init__.py +5 -0
  185. django_cfg/core/services/config_service.py +121 -0
  186. django_cfg/core/state/__init__.py +9 -0
  187. django_cfg/core/state/registry.py +84 -0
  188. django_cfg/core/types/__init__.py +15 -0
  189. django_cfg/core/types/aliases.py +15 -0
  190. django_cfg/core/types/enums.py +49 -0
  191. django_cfg/dashboard/DEBUG_README.md +105 -0
  192. django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
  193. django_cfg/dashboard/__init__.py +24 -0
  194. django_cfg/dashboard/components.py +308 -0
  195. django_cfg/dashboard/debug.py +176 -0
  196. django_cfg/dashboard/management/__init__.py +0 -0
  197. django_cfg/dashboard/management/commands/__init__.py +0 -0
  198. django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
  199. django_cfg/dashboard/sections/__init__.py +1 -0
  200. django_cfg/dashboard/sections/base.py +128 -0
  201. django_cfg/dashboard/sections/commands.py +32 -0
  202. django_cfg/dashboard/sections/overview.py +394 -0
  203. django_cfg/dashboard/sections/stats.py +48 -0
  204. django_cfg/dashboard/sections/system.py +73 -0
  205. django_cfg/management/commands/check_settings.py +6 -2
  206. django_cfg/management/commands/clear_constance.py +6 -1
  207. django_cfg/management/commands/create_token.py +5 -4
  208. django_cfg/management/commands/generate.py +5 -0
  209. django_cfg/management/commands/list_urls.py +7 -2
  210. django_cfg/management/commands/migrate_all.py +6 -2
  211. django_cfg/management/commands/migrator.py +6 -1
  212. django_cfg/management/commands/rundramatiq.py +6 -1
  213. django_cfg/management/commands/rundramatiq_simulator.py +11 -4
  214. django_cfg/management/commands/runserver_ngrok.py +9 -7
  215. django_cfg/management/commands/script.py +25 -21
  216. django_cfg/management/commands/show_config.py +6 -1
  217. django_cfg/management/commands/show_urls.py +8 -3
  218. django_cfg/management/commands/superuser.py +5 -4
  219. django_cfg/management/commands/task_clear.py +8 -3
  220. django_cfg/management/commands/task_status.py +8 -3
  221. django_cfg/management/commands/test_email.py +6 -1
  222. django_cfg/management/commands/test_telegram.py +6 -1
  223. django_cfg/management/commands/test_twilio.py +6 -1
  224. django_cfg/management/commands/tree.py +7 -4
  225. django_cfg/models/__init__.py +88 -3
  226. django_cfg/models/api/__init__.py +27 -0
  227. django_cfg/models/{api.py → api/config.py} +1 -1
  228. django_cfg/models/api/drf/__init__.py +21 -0
  229. django_cfg/models/api/drf/config.py +101 -0
  230. django_cfg/models/api/drf/redoc.py +31 -0
  231. django_cfg/models/api/drf/spectacular.py +129 -0
  232. django_cfg/models/api/drf/swagger.py +59 -0
  233. django_cfg/models/{api_keys.py → api/keys.py} +16 -6
  234. django_cfg/models/{limits.py → api/limits.py} +0 -1
  235. django_cfg/models/base/__init__.py +14 -0
  236. django_cfg/models/django/__init__.py +16 -0
  237. django_cfg/models/{constance.py → django/constance.py} +1 -1
  238. django_cfg/models/{environment.py → django/environment.py} +1 -1
  239. django_cfg/models/infrastructure/__init__.py +17 -0
  240. django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
  241. django_cfg/models/infrastructure/database/__init__.py +22 -0
  242. django_cfg/models/infrastructure/database/config.py +265 -0
  243. django_cfg/models/infrastructure/database/converters.py +91 -0
  244. django_cfg/models/infrastructure/database/parsers.py +96 -0
  245. django_cfg/models/infrastructure/database/routing.py +85 -0
  246. django_cfg/models/infrastructure/database/validators.py +170 -0
  247. django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
  248. django_cfg/models/{security.py → infrastructure/security.py} +2 -2
  249. django_cfg/models/ngrok/__init__.py +11 -0
  250. django_cfg/models/ngrok/auth.py +37 -0
  251. django_cfg/models/ngrok/config.py +77 -0
  252. django_cfg/models/ngrok/tunnel.py +35 -0
  253. django_cfg/models/payments/__init__.py +20 -0
  254. django_cfg/models/payments/api_keys.py +57 -0
  255. django_cfg/models/{payments.py → payments/config.py} +56 -154
  256. django_cfg/models/payments/providers/__init__.py +15 -0
  257. django_cfg/models/payments/providers/base.py +25 -0
  258. django_cfg/models/payments/providers/nowpayments.py +48 -0
  259. django_cfg/models/services/__init__.py +18 -0
  260. django_cfg/models/services/base.py +65 -0
  261. django_cfg/models/{email.py → services/email.py} +1 -1
  262. django_cfg/models/services/telegram.py +172 -0
  263. django_cfg/models/tasks/__init__.py +51 -0
  264. django_cfg/models/tasks/backends.py +250 -0
  265. django_cfg/models/tasks/config.py +314 -0
  266. django_cfg/models/tasks/utils.py +174 -0
  267. django_cfg/modules/base.py +18 -3
  268. django_cfg/modules/django_admin/decorators/actions.py +1 -1
  269. django_cfg/modules/django_admin/decorators/display.py +1 -1
  270. django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
  271. django_cfg/modules/django_cfg_rpc_client/README.md +346 -0
  272. django_cfg/modules/django_cfg_rpc_client/__init__.py +51 -0
  273. django_cfg/modules/django_cfg_rpc_client/client.py +540 -0
  274. django_cfg/modules/django_cfg_rpc_client/config.py +207 -0
  275. django_cfg/modules/django_cfg_rpc_client/dashboard/README.md +517 -0
  276. django_cfg/modules/django_cfg_rpc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
  277. django_cfg/modules/django_cfg_rpc_client/dashboard/__init__.py +11 -0
  278. django_cfg/modules/django_cfg_rpc_client/dashboard/apps.py +22 -0
  279. django_cfg/modules/django_cfg_rpc_client/dashboard/monitor.py +435 -0
  280. django_cfg/modules/django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard/js/dashboard.js +373 -0
  281. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/base.html +76 -0
  282. django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/dashboard.html +200 -0
  283. django_cfg/modules/django_cfg_rpc_client/dashboard/urls.py +22 -0
  284. django_cfg/modules/django_cfg_rpc_client/dashboard/urls_admin.py +9 -0
  285. django_cfg/modules/django_cfg_rpc_client/dashboard/views.py +251 -0
  286. django_cfg/modules/django_cfg_rpc_client/exceptions.py +201 -0
  287. django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
  288. django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
  289. django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
  290. django_cfg/modules/django_drf_theme/README.md +207 -0
  291. django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
  292. django_cfg/modules/django_drf_theme/__init__.py +23 -0
  293. django_cfg/modules/django_drf_theme/apps.py +15 -0
  294. django_cfg/modules/django_drf_theme/renderers.py +58 -0
  295. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
  296. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
  297. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
  298. django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
  299. django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
  300. django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
  301. django_cfg/modules/django_email/__init__.py +14 -0
  302. django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
  303. django_cfg/modules/django_email/utils.py +40 -0
  304. django_cfg/modules/django_health/__init__.py +9 -0
  305. django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
  306. django_cfg/modules/django_llm/llm/client.py +155 -550
  307. django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
  308. django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
  309. django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
  310. django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
  311. django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
  312. django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
  313. django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
  314. django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
  315. django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
  316. django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
  317. django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
  318. django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
  319. django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
  320. django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
  321. django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
  322. django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
  323. django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
  324. django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
  325. django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
  326. django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
  327. django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
  328. django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
  329. django_cfg/modules/django_llm/translator/translator.py +150 -603
  330. django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
  331. django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
  332. django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
  333. django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
  334. django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
  335. django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
  336. django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
  337. django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
  338. django_cfg/modules/django_logging/__init__.py +14 -0
  339. django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
  340. django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
  341. django_cfg/modules/django_ngrok/__init__.py +39 -0
  342. django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
  343. django_cfg/modules/django_rpc_old/POETRY.md +344 -0
  344. django_cfg/modules/django_rpc_old/README.md +397 -0
  345. django_cfg/modules/django_rpc_old/TESTING.md +358 -0
  346. django_cfg/modules/django_rpc_old/__init__.py +39 -0
  347. django_cfg/modules/django_rpc_old/client.py +531 -0
  348. django_cfg/modules/django_rpc_old/config.py +279 -0
  349. django_cfg/modules/django_rpc_old/exceptions.py +172 -0
  350. django_cfg/modules/django_tailwind/README.md +478 -0
  351. django_cfg/modules/django_tailwind/__init__.py +7 -0
  352. django_cfg/modules/django_tailwind/apps.py +10 -0
  353. django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
  354. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
  355. django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
  356. django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
  357. django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
  358. django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
  359. django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
  360. django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
  361. django_cfg/modules/django_tasks/__init__.py +29 -0
  362. django_cfg/modules/django_tasks/factory.py +127 -0
  363. django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
  364. django_cfg/modules/django_tasks/settings.py +107 -0
  365. django_cfg/modules/django_telegram/__init__.py +29 -0
  366. django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
  367. django_cfg/modules/django_telegram/utils.py +62 -0
  368. django_cfg/modules/django_twilio/__init__.py +54 -107
  369. django_cfg/modules/django_twilio/_imports.py +30 -0
  370. django_cfg/modules/django_twilio/base.py +192 -0
  371. django_cfg/modules/django_twilio/email_otp.py +227 -0
  372. django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
  373. django_cfg/modules/django_twilio/simple_service.py +1 -2
  374. django_cfg/modules/django_twilio/sms.py +94 -0
  375. django_cfg/modules/django_twilio/twilio_service.py +2 -3
  376. django_cfg/modules/django_twilio/unified.py +310 -0
  377. django_cfg/modules/django_twilio/utils.py +190 -0
  378. django_cfg/modules/django_twilio/whatsapp.py +137 -0
  379. django_cfg/modules/django_unfold/callbacks/base.py +198 -7
  380. django_cfg/modules/django_unfold/callbacks/main.py +102 -10
  381. django_cfg/modules/django_unfold/dashboard.py +65 -43
  382. django_cfg/modules/django_unfold/models/config.py +13 -12
  383. django_cfg/modules/django_unfold/models/navigation.py +8 -3
  384. django_cfg/modules/django_unfold/models/tabs.py +2 -2
  385. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
  386. django_cfg/registry/core.py +24 -26
  387. django_cfg/registry/modules.py +5 -2
  388. django_cfg/registry/services.py +20 -3
  389. django_cfg/registry/third_party.py +8 -8
  390. django_cfg/static/admin/css/dashboard.css +260 -0
  391. django_cfg/static/admin/js/commands.js +171 -0
  392. django_cfg/static/admin/js/dashboard.js +126 -0
  393. django_cfg/templates/admin/components/management_commands.js +375 -0
  394. django_cfg/templates/admin/components/progress_bar.html +18 -23
  395. django_cfg/templates/admin/index.html +48 -20
  396. django_cfg/templates/admin/index_new.html +106 -0
  397. django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
  398. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
  399. django_cfg/templates/admin/sections/commands_section.html +626 -0
  400. django_cfg/templates/admin/sections/overview_section.html +112 -0
  401. django_cfg/templates/admin/sections/stats_section.html +35 -0
  402. django_cfg/templates/admin/sections/system_section.html +99 -0
  403. django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
  404. django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
  405. django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
  406. django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
  407. django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
  408. django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
  409. django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
  410. django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
  411. django_cfg/templatetags/django_cfg.py +7 -1
  412. django_cfg/utils/smart_defaults.py +4 -4
  413. django_cfg-1.4.0.dist-info/METADATA +920 -0
  414. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/RECORD +425 -196
  415. django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
  416. django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
  417. django_cfg/core/generation.py +0 -621
  418. django_cfg/management/commands/validate_config.py +0 -189
  419. django_cfg/models/database.py +0 -480
  420. django_cfg/models/drf.py +0 -272
  421. django_cfg/models/ngrok.py +0 -122
  422. django_cfg/models/services.py +0 -440
  423. django_cfg/models/tasks.py +0 -550
  424. django_cfg/modules/django_twilio/service.py +0 -942
  425. django_cfg/template_archive/django_sample.zip +0 -0
  426. django_cfg/templates/rest_framework/api.html +0 -12
  427. django_cfg/utils/toolkit.py +0 -703
  428. django_cfg-1.3.11.dist-info/METADATA +0 -1029
  429. /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
  430. /django_cfg/core/{environment.py → environment/detector.py} +0 -0
  431. /django_cfg/models/{cors.py → api/cors.py} +0 -0
  432. /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
  433. /django_cfg/models/{base.py → base/config.py} +0 -0
  434. /django_cfg/models/{cfg.py → base/module.py} +0 -0
  435. /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
  436. /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
  437. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/WHEEL +0 -0
  438. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/entry_points.txt +0 -0
  439. {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,310 @@
1
+ """
2
+ Unified OTP service with multi-channel support and DjangoTwilioService.
3
+
4
+ Provides intelligent channel selection and automatic fallback based on
5
+ configuration and delivery success rates.
6
+ """
7
+
8
+ import logging
9
+ from typing import Tuple, Optional, List, Dict, Any
10
+ from datetime import datetime
11
+ from asgiref.sync import sync_to_async
12
+
13
+ from .base import BaseTwilioService
14
+ from .whatsapp import WhatsAppOTPService
15
+ from .email_otp import EmailOTPService
16
+ from .sms import SMSOTPService
17
+ from .models import TwilioConfig, TwilioChannelType
18
+ from .exceptions import (
19
+ TwilioConfigurationError,
20
+ TwilioSendError,
21
+ TwilioVerificationError,
22
+ )
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class UnifiedOTPService(BaseTwilioService):
28
+ """
29
+ Unified OTP service that handles all channels with smart fallbacks.
30
+
31
+ Provides intelligent channel selection and automatic fallback
32
+ based on configuration and delivery success rates.
33
+ """
34
+
35
+ def __init__(self):
36
+ """Initialize with specialized service instances."""
37
+ super().__init__()
38
+ self._whatsapp_service = WhatsAppOTPService()
39
+ self._email_service = EmailOTPService()
40
+ self._sms_service = SMSOTPService()
41
+
42
+ def send_otp(
43
+ self,
44
+ identifier: str,
45
+ preferred_channel: Optional[TwilioChannelType] = None,
46
+ enable_fallback: bool = True
47
+ ) -> Tuple[bool, str, TwilioChannelType]:
48
+ """
49
+ Send OTP using the best available channel.
50
+
51
+ Args:
52
+ identifier: Phone number (E.164) or email address
53
+ preferred_channel: Preferred delivery channel
54
+ enable_fallback: Whether to try fallback channels
55
+
56
+ Returns:
57
+ Tuple[bool, str, TwilioChannelType]: (success, message, used_channel)
58
+ """
59
+ config = self.get_twilio_config()
60
+
61
+ # Determine identifier type
62
+ is_email = "@" in identifier
63
+
64
+ # Get available channels
65
+ available_channels = self._get_available_channels(is_email, config)
66
+
67
+ if not available_channels:
68
+ raise TwilioConfigurationError(
69
+ "No channels configured for OTP delivery",
70
+ suggestions=["Configure at least one channel (WhatsApp, SMS, or Email)"]
71
+ )
72
+
73
+ # Determine channel order
74
+ channel_order = self._get_channel_order(
75
+ available_channels, preferred_channel, is_email, config
76
+ )
77
+
78
+ last_error = None
79
+
80
+ for channel in channel_order:
81
+ try:
82
+ success, message = self._send_via_channel(identifier, channel)
83
+ if success:
84
+ return True, message, channel
85
+
86
+ except Exception as e:
87
+ last_error = e
88
+ logger.warning(f"Channel {channel.value} failed for {self._mask_identifier(identifier)}: {e}")
89
+
90
+ if not enable_fallback:
91
+ raise
92
+
93
+ # All channels failed
94
+ raise TwilioSendError(
95
+ f"All configured channels failed for {self._mask_identifier(identifier)}",
96
+ context={"tried_channels": [ch.value for ch in channel_order]},
97
+ suggestions=["Check service configurations", "Verify recipient details"]
98
+ ) from last_error
99
+
100
+ async def asend_otp(
101
+ self,
102
+ identifier: str,
103
+ preferred_channel: Optional[TwilioChannelType] = None,
104
+ enable_fallback: bool = True
105
+ ) -> Tuple[bool, str, TwilioChannelType]:
106
+ """Async version of send_otp."""
107
+ return await sync_to_async(self.send_otp)(identifier, preferred_channel, enable_fallback)
108
+
109
+ def verify_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
110
+ """
111
+ Verify OTP code for any channel.
112
+
113
+ Args:
114
+ identifier: Phone number or email used for OTP
115
+ code: OTP code to verify
116
+
117
+ Returns:
118
+ Tuple[bool, str]: (is_valid, message)
119
+ """
120
+ config = self.get_twilio_config()
121
+
122
+ # For Twilio Verify channels (WhatsApp, SMS), use Twilio verification
123
+ if not "@" in identifier and config.verify:
124
+ return self._verify_twilio_otp(identifier, code, config)
125
+
126
+ # For email or custom verification, use stored OTP
127
+ return self._verify_stored_otp(identifier, code)
128
+
129
+ async def averify_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
130
+ """Async version of verify_otp."""
131
+ return await sync_to_async(self.verify_otp)(identifier, code)
132
+
133
+ def _get_available_channels(self, is_email: bool, config: TwilioConfig) -> List[TwilioChannelType]:
134
+ """Get list of available channels based on configuration."""
135
+ channels = []
136
+
137
+ if config.verify:
138
+ if not is_email: # Phone number - can use WhatsApp/SMS
139
+ channels.extend([TwilioChannelType.WHATSAPP, TwilioChannelType.SMS])
140
+
141
+ if config.sendgrid: # Email available
142
+ channels.append(TwilioChannelType.EMAIL)
143
+
144
+ return channels
145
+
146
+ def _get_channel_order(
147
+ self,
148
+ available_channels: List[TwilioChannelType],
149
+ preferred_channel: Optional[TwilioChannelType],
150
+ is_email: bool,
151
+ config: TwilioConfig
152
+ ) -> List[TwilioChannelType]:
153
+ """Determine optimal channel order for delivery attempts."""
154
+
155
+ # If preferred channel is specified and available, try it first
156
+ if preferred_channel and preferred_channel in available_channels:
157
+ ordered_channels = [preferred_channel]
158
+ remaining = [ch for ch in available_channels if ch != preferred_channel]
159
+ ordered_channels.extend(remaining)
160
+ return ordered_channels
161
+
162
+ # Default ordering based on identifier type and configuration
163
+ if is_email:
164
+ return [TwilioChannelType.EMAIL]
165
+
166
+ # For phone numbers, prefer WhatsApp -> SMS
167
+ phone_channels = []
168
+ if TwilioChannelType.WHATSAPP in available_channels:
169
+ phone_channels.append(TwilioChannelType.WHATSAPP)
170
+ if TwilioChannelType.SMS in available_channels:
171
+ phone_channels.append(TwilioChannelType.SMS)
172
+
173
+ return phone_channels
174
+
175
+ def _send_via_channel(self, identifier: str, channel: TwilioChannelType) -> Tuple[bool, str]:
176
+ """Send OTP via specific channel."""
177
+ if channel == TwilioChannelType.WHATSAPP:
178
+ return self._whatsapp_service.send_otp(identifier, fallback_to_sms=False)
179
+ elif channel == TwilioChannelType.SMS:
180
+ return self._sms_service.send_otp(identifier)
181
+ elif channel == TwilioChannelType.EMAIL:
182
+ success, message, _ = self._email_service.send_otp(identifier)
183
+ return success, message
184
+ else:
185
+ raise TwilioSendError(f"Unsupported channel: {channel.value}")
186
+
187
+ def _verify_twilio_otp(self, phone_number: str, code: str, config: TwilioConfig) -> Tuple[bool, str]:
188
+ """Verify OTP using Twilio Verify API."""
189
+ try:
190
+ client = self.get_twilio_client()
191
+
192
+ verification_check = client.verify.v2.services(
193
+ config.verify.service_sid
194
+ ).verification_checks.create(
195
+ to=phone_number,
196
+ code=code
197
+ )
198
+
199
+ if verification_check.status == 'approved':
200
+ logger.info(f"OTP verified successfully for {self._mask_identifier(phone_number)}")
201
+ return True, "OTP verified successfully"
202
+ else:
203
+ return False, f"Invalid OTP code: {verification_check.status}"
204
+
205
+ except TwilioException as e:
206
+ raise TwilioVerificationError(
207
+ f"OTP verification failed: {e}",
208
+ phone_number=phone_number,
209
+ twilio_error_code=getattr(e, 'code', None),
210
+ twilio_error_message=str(e)
211
+ ) from e
212
+
213
+ def _verify_stored_otp(self, identifier: str, code: str) -> Tuple[bool, str]:
214
+ """Verify OTP using stored codes (for email)."""
215
+ stored_data = self._get_stored_otp(identifier)
216
+
217
+ if not stored_data:
218
+ return False, "OTP not found. Please request a new code."
219
+
220
+ if datetime.now() > stored_data['expires_at']:
221
+ self._remove_otp(identifier)
222
+ return False, "OTP expired. Please request a new code."
223
+
224
+ # Increment attempt counter
225
+ stored_data['attempts'] += 1
226
+
227
+ if stored_data['attempts'] > 5: # Max attempts
228
+ self._remove_otp(identifier)
229
+ return False, "Too many attempts. Please request a new code."
230
+
231
+ if stored_data['code'] == code:
232
+ self._remove_otp(identifier)
233
+ logger.info(f"Stored OTP verified successfully for {self._mask_identifier(identifier)}")
234
+ return True, "OTP verified successfully"
235
+ else:
236
+ return False, f"Invalid OTP code. {5 - stored_data['attempts']} attempts remaining."
237
+
238
+
239
+ class DjangoTwilioService(UnifiedOTPService):
240
+ """
241
+ Main Twilio service for django_cfg integration.
242
+
243
+ Provides unified access to all Twilio services with auto-configuration
244
+ and comprehensive error handling. This is the primary service class
245
+ that should be used in most applications.
246
+ """
247
+
248
+ def __init__(self):
249
+ """Initialize with all service capabilities."""
250
+ super().__init__()
251
+ logger.info("DjangoTwilioService initialized with auto-configuration")
252
+
253
+ def get_service_status(self) -> Dict[str, Any]:
254
+ """
255
+ Get comprehensive status of all Twilio services.
256
+
257
+ Returns:
258
+ Dictionary with service status information
259
+ """
260
+ try:
261
+ config = self.get_twilio_config()
262
+
263
+ status = {
264
+ "twilio_configured": True,
265
+ "account_sid": config.account_sid,
266
+ "region": config.region.value,
267
+ "services": {},
268
+ "enabled_channels": [ch.value for ch in config.get_enabled_channels()],
269
+ "test_mode": config.test_mode,
270
+ }
271
+
272
+ # Check Verify service
273
+ if config.verify:
274
+ status["services"]["verify"] = {
275
+ "enabled": True,
276
+ "service_sid": config.verify.service_sid,
277
+ "default_channel": config.verify.default_channel.value,
278
+ "fallback_channels": [ch.value for ch in config.verify.fallback_channels],
279
+ "code_length": config.verify.code_length,
280
+ "ttl_seconds": config.verify.ttl_seconds,
281
+ }
282
+ else:
283
+ status["services"]["verify"] = {"enabled": False}
284
+
285
+ # Check SendGrid service
286
+ if config.sendgrid:
287
+ status["services"]["sendgrid"] = {
288
+ "enabled": True,
289
+ "from_email": config.sendgrid.from_email,
290
+ "from_name": config.sendgrid.from_name,
291
+ "template_configured": config.sendgrid.otp_template_id is not None,
292
+ "tracking_enabled": config.sendgrid.tracking_enabled,
293
+ }
294
+ else:
295
+ status["services"]["sendgrid"] = {"enabled": False}
296
+
297
+ return status
298
+
299
+ except Exception as e:
300
+ return {
301
+ "twilio_configured": False,
302
+ "error": str(e),
303
+ "services": {},
304
+ }
305
+
306
+
307
+ __all__ = [
308
+ "UnifiedOTPService",
309
+ "DjangoTwilioService",
310
+ ]
@@ -0,0 +1,190 @@
1
+ """
2
+ Convenience functions for direct Twilio service usage.
3
+
4
+ Provides simple function-based API for sending and verifying OTPs
5
+ without manually instantiating service classes.
6
+ """
7
+
8
+ from typing import Tuple, Optional
9
+
10
+ from .whatsapp import WhatsAppOTPService
11
+ from .email_otp import EmailOTPService
12
+ from .sms import SMSOTPService
13
+ from .unified import UnifiedOTPService
14
+
15
+
16
+ # Sync convenience functions
17
+
18
+ def send_whatsapp_otp(phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
19
+ """
20
+ Send WhatsApp OTP with optional SMS fallback.
21
+
22
+ Args:
23
+ phone_number: Phone number in E.164 format
24
+ fallback_to_sms: Whether to fallback to SMS if WhatsApp fails
25
+
26
+ Returns:
27
+ Tuple[bool, str]: (success, message)
28
+
29
+ Example:
30
+ >>> success, message = send_whatsapp_otp("+1234567890")
31
+ >>> if success:
32
+ ... print(f"OTP sent: {message}")
33
+ """
34
+ service = WhatsAppOTPService()
35
+ return service.send_otp(phone_number, fallback_to_sms)
36
+
37
+
38
+ def send_email_otp(email: str, subject: Optional[str] = None) -> Tuple[bool, str, str]:
39
+ """
40
+ Send email OTP.
41
+
42
+ Args:
43
+ email: Recipient email address
44
+ subject: Optional custom email subject
45
+
46
+ Returns:
47
+ Tuple[bool, str, str]: (success, message, otp_code)
48
+
49
+ Example:
50
+ >>> success, message, otp_code = send_email_otp("user@example.com")
51
+ >>> if success:
52
+ ... print(f"OTP sent: {otp_code}")
53
+ """
54
+ service = EmailOTPService()
55
+ return service.send_otp(email, subject)
56
+
57
+
58
+ def send_sms_otp(phone_number: str) -> Tuple[bool, str]:
59
+ """
60
+ Send SMS OTP.
61
+
62
+ Args:
63
+ phone_number: Phone number in E.164 format
64
+
65
+ Returns:
66
+ Tuple[bool, str]: (success, message)
67
+
68
+ Example:
69
+ >>> success, message = send_sms_otp("+1234567890")
70
+ >>> if success:
71
+ ... print(f"OTP sent: {message}")
72
+ """
73
+ service = SMSOTPService()
74
+ return service.send_otp(phone_number)
75
+
76
+
77
+ def verify_otp(identifier: str, code: str) -> Tuple[bool, str]:
78
+ """
79
+ Verify OTP code for any channel.
80
+
81
+ Args:
82
+ identifier: Phone number or email used for OTP
83
+ code: OTP code to verify
84
+
85
+ Returns:
86
+ Tuple[bool, str]: (is_valid, message)
87
+
88
+ Example:
89
+ >>> is_valid, message = verify_otp("+1234567890", "123456")
90
+ >>> if is_valid:
91
+ ... print("OTP verified successfully!")
92
+ """
93
+ service = UnifiedOTPService()
94
+ return service.verify_otp(identifier, code)
95
+
96
+
97
+ # Async convenience functions
98
+
99
+ async def asend_whatsapp_otp(phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
100
+ """
101
+ Async send WhatsApp OTP.
102
+
103
+ Args:
104
+ phone_number: Phone number in E.164 format
105
+ fallback_to_sms: Whether to fallback to SMS if WhatsApp fails
106
+
107
+ Returns:
108
+ Tuple[bool, str]: (success, message)
109
+
110
+ Example:
111
+ >>> success, message = await asend_whatsapp_otp("+1234567890")
112
+ >>> if success:
113
+ ... print(f"OTP sent: {message}")
114
+ """
115
+ service = WhatsAppOTPService()
116
+ return await service.asend_otp(phone_number, fallback_to_sms)
117
+
118
+
119
+ async def asend_email_otp(email: str, subject: Optional[str] = None) -> Tuple[bool, str, str]:
120
+ """
121
+ Async send email OTP.
122
+
123
+ Args:
124
+ email: Recipient email address
125
+ subject: Optional custom email subject
126
+
127
+ Returns:
128
+ Tuple[bool, str, str]: (success, message, otp_code)
129
+
130
+ Example:
131
+ >>> success, message, otp_code = await asend_email_otp("user@example.com")
132
+ >>> if success:
133
+ ... print(f"OTP sent: {otp_code}")
134
+ """
135
+ service = EmailOTPService()
136
+ return await service.asend_otp(email, subject)
137
+
138
+
139
+ async def asend_sms_otp(phone_number: str) -> Tuple[bool, str]:
140
+ """
141
+ Async send SMS OTP.
142
+
143
+ Args:
144
+ phone_number: Phone number in E.164 format
145
+
146
+ Returns:
147
+ Tuple[bool, str]: (success, message)
148
+
149
+ Example:
150
+ >>> success, message = await asend_sms_otp("+1234567890")
151
+ >>> if success:
152
+ ... print(f"OTP sent: {message}")
153
+ """
154
+ service = SMSOTPService()
155
+ return await service.asend_otp(phone_number)
156
+
157
+
158
+ async def averify_otp(identifier: str, code: str) -> Tuple[bool, str]:
159
+ """
160
+ Async verify OTP code.
161
+
162
+ Args:
163
+ identifier: Phone number or email used for OTP
164
+ code: OTP code to verify
165
+
166
+ Returns:
167
+ Tuple[bool, str]: (is_valid, message)
168
+
169
+ Example:
170
+ >>> is_valid, message = await averify_otp("+1234567890", "123456")
171
+ >>> if is_valid:
172
+ ... print("OTP verified successfully!")
173
+ """
174
+ service = UnifiedOTPService()
175
+ return await service.averify_otp(identifier, code)
176
+
177
+
178
+ __all__ = [
179
+ # Sync convenience functions
180
+ "send_whatsapp_otp",
181
+ "send_email_otp",
182
+ "send_sms_otp",
183
+ "verify_otp",
184
+
185
+ # Async convenience functions
186
+ "asend_whatsapp_otp",
187
+ "asend_email_otp",
188
+ "asend_sms_otp",
189
+ "averify_otp",
190
+ ]
@@ -0,0 +1,137 @@
1
+ """
2
+ WhatsApp OTP service using Twilio Verify API.
3
+
4
+ Provides OTP delivery via WhatsApp with automatic SMS fallback.
5
+ Supports both sync and async operations.
6
+ """
7
+
8
+ import logging
9
+ from typing import Tuple
10
+ from ._imports import Client, TwilioException
11
+ from asgiref.sync import sync_to_async
12
+
13
+ from .base import BaseTwilioService
14
+ from .models import TwilioVerifyConfig
15
+ from .exceptions import (
16
+ TwilioConfigurationError,
17
+ TwilioSendError,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class WhatsAppOTPService(BaseTwilioService):
24
+ """
25
+ WhatsApp OTP service using Twilio Verify API.
26
+
27
+ Provides OTP delivery via WhatsApp with automatic SMS fallback.
28
+ Supports both sync and async operations.
29
+ """
30
+
31
+ def send_otp(self, phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
32
+ """
33
+ Send OTP via WhatsApp with optional SMS fallback.
34
+
35
+ Args:
36
+ phone_number: Phone number in E.164 format (e.g., +1234567890)
37
+ fallback_to_sms: Whether to fallback to SMS if WhatsApp fails
38
+
39
+ Returns:
40
+ Tuple[bool, str]: (success, message)
41
+
42
+ Raises:
43
+ TwilioConfigurationError: If service is not configured
44
+ TwilioSendError: If sending fails
45
+ """
46
+ config = self.get_twilio_config()
47
+
48
+ if not config.verify:
49
+ raise TwilioConfigurationError(
50
+ "Twilio Verify service not configured",
51
+ missing_fields=["verify"],
52
+ suggestions=["Configure TwilioVerifyConfig in your Twilio settings"]
53
+ )
54
+
55
+ client = self.get_twilio_client()
56
+
57
+ try:
58
+ # Try WhatsApp first
59
+ verification = client.verify.v2.services(
60
+ config.verify.service_sid
61
+ ).verifications.create(
62
+ to=phone_number,
63
+ channel='whatsapp'
64
+ )
65
+
66
+ if verification.status == 'pending':
67
+ logger.info(f"WhatsApp OTP sent successfully to {self._mask_identifier(phone_number)}")
68
+ return True, f"OTP sent via WhatsApp to {self._mask_identifier(phone_number)}"
69
+
70
+ # If WhatsApp failed and fallback is enabled, try SMS
71
+ if fallback_to_sms and verification.status != 'pending':
72
+ logger.warning(f"WhatsApp failed for {self._mask_identifier(phone_number)}, trying SMS fallback")
73
+ return self._send_sms_otp(phone_number, client, config.verify)
74
+
75
+ raise TwilioSendError(
76
+ f"WhatsApp OTP failed with status: {verification.status}",
77
+ channel="whatsapp",
78
+ recipient=phone_number,
79
+ suggestions=["Check if recipient has WhatsApp Business account", "Try SMS fallback"]
80
+ )
81
+
82
+ except TwilioException as e:
83
+ if fallback_to_sms:
84
+ logger.warning(f"WhatsApp error for {self._mask_identifier(phone_number)}: {e}, trying SMS")
85
+ return self._send_sms_otp(phone_number, client, config.verify)
86
+
87
+ raise TwilioSendError(
88
+ f"WhatsApp OTP failed: {e}",
89
+ channel="whatsapp",
90
+ recipient=phone_number,
91
+ twilio_error_code=getattr(e, 'code', None),
92
+ twilio_error_message=str(e)
93
+ ) from e
94
+ except Exception as e:
95
+ raise TwilioSendError(
96
+ f"Unexpected error sending WhatsApp OTP: {e}",
97
+ channel="whatsapp",
98
+ recipient=phone_number
99
+ ) from e
100
+
101
+ async def asend_otp(self, phone_number: str, fallback_to_sms: bool = True) -> Tuple[bool, str]:
102
+ """Async version of send_otp."""
103
+ return await sync_to_async(self.send_otp)(phone_number, fallback_to_sms)
104
+
105
+ def _send_sms_otp(self, phone_number: str, client: Client, verify_config: TwilioVerifyConfig) -> Tuple[bool, str]:
106
+ """Internal SMS fallback method."""
107
+ try:
108
+ verification = client.verify.v2.services(
109
+ verify_config.service_sid
110
+ ).verifications.create(
111
+ to=phone_number,
112
+ channel='sms'
113
+ )
114
+
115
+ if verification.status == 'pending':
116
+ logger.info(f"SMS fallback OTP sent to {self._mask_identifier(phone_number)}")
117
+ return True, f"OTP sent via SMS to {self._mask_identifier(phone_number)} (WhatsApp fallback)"
118
+
119
+ raise TwilioSendError(
120
+ f"SMS fallback failed with status: {verification.status}",
121
+ channel="sms",
122
+ recipient=phone_number
123
+ )
124
+
125
+ except TwilioException as e:
126
+ raise TwilioSendError(
127
+ f"SMS fallback failed: {e}",
128
+ channel="sms",
129
+ recipient=phone_number,
130
+ twilio_error_code=getattr(e, 'code', None),
131
+ twilio_error_message=str(e)
132
+ ) from e
133
+
134
+
135
+ __all__ = [
136
+ "WhatsAppOTPService",
137
+ ]