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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/inlines.py +11 -5
- django_cfg/apps/accounts/admin/user_admin.py +39 -16
- django_cfg/apps/accounts/serializers/profile.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +18 -11
- django_cfg/apps/accounts/signals.py +15 -24
- django_cfg/apps/accounts/utils/notifications.py +217 -358
- django_cfg/apps/accounts/views/otp.py +2 -2
- django_cfg/apps/accounts/views/webhook.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +1 -1
- django_cfg/apps/api/commands/views.py +66 -83
- django_cfg/apps/api/health/drf_views.py +269 -0
- django_cfg/apps/api/health/serializers.py +45 -0
- django_cfg/apps/api/health/urls.py +6 -1
- django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
- django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
- django_cfg/apps/knowbase/admin/document_admin.py +136 -270
- django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
- django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
- django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
- django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
- django_cfg/apps/knowbase/config/constance_fields.py +1 -1
- django_cfg/apps/knowbase/config/settings.py +2 -2
- django_cfg/apps/knowbase/mixins/__init__.py +19 -2
- django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
- django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
- django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
- django_cfg/apps/knowbase/mixins/creator.py +10 -10
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
- django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
- django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
- django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
- django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
- django_cfg/apps/knowbase/mixins/service.py +2 -2
- django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
- django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
- django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
- django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
- django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
- django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
- django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
- django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
- django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
- django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
- django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
- django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
- django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
- django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
- django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
- django_cfg/apps/knowbase/services/embedding/models.py +18 -14
- django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
- django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
- django_cfg/apps/leads/tests.py +1 -1
- django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
- django_cfg/apps/payments/admin/balance_admin.py +1 -1
- django_cfg/apps/payments/admin/currencies_admin.py +1 -1
- django_cfg/apps/payments/admin/payments_admin.py +1 -1
- django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
- django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
- django_cfg/apps/payments/admin_interface/views/base.py +29 -2
- django_cfg/apps/payments/apps.py +1 -1
- django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
- django_cfg/apps/payments/config/helpers.py +3 -2
- django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
- django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
- django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
- django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
- django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
- django_cfg/apps/payments/management/commands/test_providers.py +1 -1
- django_cfg/apps/payments/middleware/api_access.py +1 -1
- django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
- django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
- django_cfg/apps/payments/models/balance.py +2 -2
- django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
- django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
- django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
- django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
- django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
- django_cfg/apps/payments/models/payments.py +2 -2
- django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
- django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
- django_cfg/apps/payments/services/core/base.py +1 -1
- django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
- django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
- django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
- django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
- django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
- django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
- django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
- django_cfg/apps/payments/services/core/payment_service.py +124 -612
- django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
- django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
- django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
- django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
- django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
- django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
- django_cfg/apps/payments/services/providers/base.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
- django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
- django_cfg/apps/payments/services/providers/registry.py +1 -1
- django_cfg/apps/payments/services/providers/sync_service.py +1 -1
- django_cfg/apps/payments/signals/__init__.py +1 -1
- django_cfg/apps/payments/signals/api_key_signals.py +1 -1
- django_cfg/apps/payments/signals/balance_signals.py +1 -1
- django_cfg/apps/payments/signals/payment_signals.py +1 -1
- django_cfg/apps/payments/signals/subscription_signals.py +1 -1
- django_cfg/apps/payments/views/api/api_keys.py +1 -1
- django_cfg/apps/payments/views/api/balances.py +1 -1
- django_cfg/apps/payments/views/api/base.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +1 -1
- django_cfg/apps/payments/views/api/payments.py +1 -1
- django_cfg/apps/payments/views/api/subscriptions.py +1 -1
- django_cfg/apps/payments/views/api/webhooks.py +1 -1
- django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
- django_cfg/apps/payments/views/serializers/balances.py +1 -1
- django_cfg/apps/payments/views/serializers/currencies.py +1 -1
- django_cfg/apps/payments/views/serializers/payments.py +1 -1
- django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
- django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
- django_cfg/apps/support/admin/support_admin.py +21 -13
- django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
- django_cfg/apps/support/utils/support_email_service.py +1 -1
- django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
- django_cfg/apps/tasks/utils/simulator.py +1 -1
- django_cfg/apps/tasks/views/dashboard.py +33 -3
- django_cfg/apps/urls.py +5 -1
- django_cfg/cli/README.md +57 -471
- django_cfg/cli/commands/create_project.py +140 -529
- django_cfg/cli/main.py +13 -10
- django_cfg/core/__init__.py +63 -6
- django_cfg/core/base/__init__.py +5 -0
- django_cfg/core/base/config_model.py +652 -0
- django_cfg/core/builders/__init__.py +11 -0
- django_cfg/core/builders/apps_builder.py +258 -0
- django_cfg/core/builders/middleware_builder.py +115 -0
- django_cfg/core/builders/security_builder.py +96 -0
- django_cfg/core/config.py +20 -892
- django_cfg/core/constants.py +69 -0
- django_cfg/core/environment/__init__.py +9 -0
- django_cfg/core/exceptions.py +45 -298
- django_cfg/core/generation/__init__.py +51 -0
- django_cfg/core/generation/core_generators/__init__.py +0 -0
- django_cfg/core/generation/core_generators/settings.py +90 -0
- django_cfg/core/generation/core_generators/static.py +82 -0
- django_cfg/core/generation/core_generators/templates.py +141 -0
- django_cfg/core/generation/data_generators/__init__.py +15 -0
- django_cfg/core/generation/data_generators/cache.py +132 -0
- django_cfg/core/generation/data_generators/database.py +117 -0
- django_cfg/core/generation/generation.py +92 -0
- django_cfg/core/generation/integration_generators/__init__.py +21 -0
- django_cfg/core/generation/integration_generators/api.py +237 -0
- django_cfg/core/generation/integration_generators/sessions.py +65 -0
- django_cfg/core/generation/integration_generators/tailwind.py +54 -0
- django_cfg/core/generation/integration_generators/tasks.py +92 -0
- django_cfg/core/generation/integration_generators/third_party.py +144 -0
- django_cfg/core/generation/orchestrator.py +285 -0
- django_cfg/core/generation/protocols.py +30 -0
- django_cfg/core/generation/security_generators/__init__.py +0 -0
- django_cfg/core/generation/utility_generators/__init__.py +24 -0
- django_cfg/core/generation/utility_generators/email.py +58 -0
- django_cfg/core/generation/utility_generators/i18n.py +66 -0
- django_cfg/core/generation/utility_generators/limits.py +58 -0
- django_cfg/core/generation/utility_generators/logging.py +66 -0
- django_cfg/core/generation/utility_generators/security.py +101 -0
- django_cfg/core/generation/utils/__init__.py +0 -0
- django_cfg/core/generation/utils/helpers.py +32 -0
- django_cfg/core/integration/__init__.py +18 -25
- django_cfg/core/integration/display/startup.py +146 -133
- django_cfg/core/integration/url_integration.py +13 -2
- django_cfg/core/services/__init__.py +5 -0
- django_cfg/core/services/config_service.py +121 -0
- django_cfg/core/state/__init__.py +9 -0
- django_cfg/core/state/registry.py +84 -0
- django_cfg/core/types/__init__.py +15 -0
- django_cfg/core/types/aliases.py +15 -0
- django_cfg/core/types/enums.py +49 -0
- django_cfg/dashboard/DEBUG_README.md +105 -0
- django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
- django_cfg/dashboard/__init__.py +24 -0
- django_cfg/dashboard/components.py +308 -0
- django_cfg/dashboard/debug.py +176 -0
- django_cfg/dashboard/management/__init__.py +0 -0
- django_cfg/dashboard/management/commands/__init__.py +0 -0
- django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
- django_cfg/dashboard/sections/__init__.py +1 -0
- django_cfg/dashboard/sections/base.py +128 -0
- django_cfg/dashboard/sections/commands.py +32 -0
- django_cfg/dashboard/sections/overview.py +394 -0
- django_cfg/dashboard/sections/stats.py +48 -0
- django_cfg/dashboard/sections/system.py +73 -0
- django_cfg/management/commands/check_settings.py +6 -2
- django_cfg/management/commands/clear_constance.py +6 -1
- django_cfg/management/commands/create_token.py +5 -4
- django_cfg/management/commands/generate.py +5 -0
- django_cfg/management/commands/list_urls.py +7 -2
- django_cfg/management/commands/migrate_all.py +6 -2
- django_cfg/management/commands/migrator.py +6 -1
- django_cfg/management/commands/rundramatiq.py +6 -1
- django_cfg/management/commands/rundramatiq_simulator.py +11 -4
- django_cfg/management/commands/runserver_ngrok.py +9 -7
- django_cfg/management/commands/script.py +25 -21
- django_cfg/management/commands/show_config.py +6 -1
- django_cfg/management/commands/show_urls.py +8 -3
- django_cfg/management/commands/superuser.py +5 -4
- django_cfg/management/commands/task_clear.py +8 -3
- django_cfg/management/commands/task_status.py +8 -3
- django_cfg/management/commands/test_email.py +6 -1
- django_cfg/management/commands/test_telegram.py +6 -1
- django_cfg/management/commands/test_twilio.py +6 -1
- django_cfg/management/commands/tree.py +7 -4
- django_cfg/models/__init__.py +88 -3
- django_cfg/models/api/__init__.py +27 -0
- django_cfg/models/{api.py → api/config.py} +1 -1
- django_cfg/models/api/drf/__init__.py +21 -0
- django_cfg/models/api/drf/config.py +101 -0
- django_cfg/models/api/drf/redoc.py +31 -0
- django_cfg/models/api/drf/spectacular.py +129 -0
- django_cfg/models/api/drf/swagger.py +59 -0
- django_cfg/models/{api_keys.py → api/keys.py} +16 -6
- django_cfg/models/{limits.py → api/limits.py} +0 -1
- django_cfg/models/base/__init__.py +14 -0
- django_cfg/models/django/__init__.py +16 -0
- django_cfg/models/{constance.py → django/constance.py} +1 -1
- django_cfg/models/{environment.py → django/environment.py} +1 -1
- django_cfg/models/infrastructure/__init__.py +17 -0
- django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
- django_cfg/models/infrastructure/database/__init__.py +22 -0
- django_cfg/models/infrastructure/database/config.py +265 -0
- django_cfg/models/infrastructure/database/converters.py +91 -0
- django_cfg/models/infrastructure/database/parsers.py +96 -0
- django_cfg/models/infrastructure/database/routing.py +85 -0
- django_cfg/models/infrastructure/database/validators.py +170 -0
- django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
- django_cfg/models/{security.py → infrastructure/security.py} +2 -2
- django_cfg/models/ngrok/__init__.py +11 -0
- django_cfg/models/ngrok/auth.py +37 -0
- django_cfg/models/ngrok/config.py +77 -0
- django_cfg/models/ngrok/tunnel.py +35 -0
- django_cfg/models/payments/__init__.py +20 -0
- django_cfg/models/payments/api_keys.py +57 -0
- django_cfg/models/{payments.py → payments/config.py} +56 -154
- django_cfg/models/payments/providers/__init__.py +15 -0
- django_cfg/models/payments/providers/base.py +25 -0
- django_cfg/models/payments/providers/nowpayments.py +48 -0
- django_cfg/models/services/__init__.py +18 -0
- django_cfg/models/services/base.py +65 -0
- django_cfg/models/{email.py → services/email.py} +1 -1
- django_cfg/models/services/telegram.py +172 -0
- django_cfg/models/tasks/__init__.py +51 -0
- django_cfg/models/tasks/backends.py +250 -0
- django_cfg/models/tasks/config.py +314 -0
- django_cfg/models/tasks/utils.py +174 -0
- django_cfg/modules/base.py +18 -3
- django_cfg/modules/django_admin/decorators/actions.py +1 -1
- django_cfg/modules/django_admin/decorators/display.py +1 -1
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
- django_cfg/modules/django_cfg_rpc_client/README.md +346 -0
- django_cfg/modules/django_cfg_rpc_client/__init__.py +51 -0
- django_cfg/modules/django_cfg_rpc_client/client.py +540 -0
- django_cfg/modules/django_cfg_rpc_client/config.py +207 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/README.md +517 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/__init__.py +11 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/apps.py +22 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/monitor.py +435 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/static/django_cfg_rpc_dashboard/js/dashboard.js +373 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/base.html +76 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/templates/django_cfg_rpc_dashboard/dashboard.html +200 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/urls.py +22 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/urls_admin.py +9 -0
- django_cfg/modules/django_cfg_rpc_client/dashboard/views.py +251 -0
- django_cfg/modules/django_cfg_rpc_client/exceptions.py +201 -0
- django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
- django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
- django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
- django_cfg/modules/django_drf_theme/README.md +207 -0
- django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
- django_cfg/modules/django_drf_theme/__init__.py +23 -0
- django_cfg/modules/django_drf_theme/apps.py +15 -0
- django_cfg/modules/django_drf_theme/renderers.py +58 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
- django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
- django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
- django_cfg/modules/django_email/__init__.py +14 -0
- django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
- django_cfg/modules/django_email/utils.py +40 -0
- django_cfg/modules/django_health/__init__.py +9 -0
- django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
- django_cfg/modules/django_llm/llm/client.py +155 -550
- django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
- django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
- django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
- django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
- django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
- django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
- django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
- django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
- django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
- django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
- django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
- django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
- django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
- django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
- django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
- django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
- django_cfg/modules/django_llm/translator/translator.py +150 -603
- django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
- django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
- django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
- django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
- django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
- django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
- django_cfg/modules/django_logging/__init__.py +14 -0
- django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
- django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
- django_cfg/modules/django_ngrok/__init__.py +39 -0
- django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
- django_cfg/modules/django_rpc_old/POETRY.md +344 -0
- django_cfg/modules/django_rpc_old/README.md +397 -0
- django_cfg/modules/django_rpc_old/TESTING.md +358 -0
- django_cfg/modules/django_rpc_old/__init__.py +39 -0
- django_cfg/modules/django_rpc_old/client.py +531 -0
- django_cfg/modules/django_rpc_old/config.py +279 -0
- django_cfg/modules/django_rpc_old/exceptions.py +172 -0
- django_cfg/modules/django_tailwind/README.md +478 -0
- django_cfg/modules/django_tailwind/__init__.py +7 -0
- django_cfg/modules/django_tailwind/apps.py +10 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
- django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
- django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
- django_cfg/modules/django_tasks/__init__.py +29 -0
- django_cfg/modules/django_tasks/factory.py +127 -0
- django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
- django_cfg/modules/django_tasks/settings.py +107 -0
- django_cfg/modules/django_telegram/__init__.py +29 -0
- django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
- django_cfg/modules/django_telegram/utils.py +62 -0
- django_cfg/modules/django_twilio/__init__.py +54 -107
- django_cfg/modules/django_twilio/_imports.py +30 -0
- django_cfg/modules/django_twilio/base.py +192 -0
- django_cfg/modules/django_twilio/email_otp.py +227 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
- django_cfg/modules/django_twilio/simple_service.py +1 -2
- django_cfg/modules/django_twilio/sms.py +94 -0
- django_cfg/modules/django_twilio/twilio_service.py +2 -3
- django_cfg/modules/django_twilio/unified.py +310 -0
- django_cfg/modules/django_twilio/utils.py +190 -0
- django_cfg/modules/django_twilio/whatsapp.py +137 -0
- django_cfg/modules/django_unfold/callbacks/base.py +198 -7
- django_cfg/modules/django_unfold/callbacks/main.py +102 -10
- django_cfg/modules/django_unfold/dashboard.py +65 -43
- django_cfg/modules/django_unfold/models/config.py +13 -12
- django_cfg/modules/django_unfold/models/navigation.py +8 -3
- django_cfg/modules/django_unfold/models/tabs.py +2 -2
- django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
- django_cfg/registry/core.py +24 -26
- django_cfg/registry/modules.py +5 -2
- django_cfg/registry/services.py +20 -3
- django_cfg/registry/third_party.py +8 -8
- django_cfg/static/admin/css/dashboard.css +260 -0
- django_cfg/static/admin/js/commands.js +171 -0
- django_cfg/static/admin/js/dashboard.js +126 -0
- django_cfg/templates/admin/components/management_commands.js +375 -0
- django_cfg/templates/admin/components/progress_bar.html +18 -23
- django_cfg/templates/admin/index.html +48 -20
- django_cfg/templates/admin/index_new.html +106 -0
- django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
- django_cfg/templates/admin/sections/commands_section.html +626 -0
- django_cfg/templates/admin/sections/overview_section.html +112 -0
- django_cfg/templates/admin/sections/stats_section.html +35 -0
- django_cfg/templates/admin/sections/system_section.html +99 -0
- django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
- django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
- django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
- django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
- django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
- django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
- django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
- django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
- django_cfg/templatetags/django_cfg.py +7 -1
- django_cfg/utils/smart_defaults.py +4 -4
- django_cfg-1.4.0.dist-info/METADATA +920 -0
- {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/RECORD +425 -196
- django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
- django_cfg/core/generation.py +0 -621
- django_cfg/management/commands/validate_config.py +0 -189
- django_cfg/models/database.py +0 -480
- django_cfg/models/drf.py +0 -272
- django_cfg/models/ngrok.py +0 -122
- django_cfg/models/services.py +0 -440
- django_cfg/models/tasks.py +0 -550
- django_cfg/modules/django_twilio/service.py +0 -942
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/rest_framework/api.html +0 -12
- django_cfg/utils/toolkit.py +0 -703
- django_cfg-1.3.11.dist-info/METADATA +0 -1029
- /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
- /django_cfg/core/{environment.py → environment/detector.py} +0 -0
- /django_cfg/models/{cors.py → api/cors.py} +0 -0
- /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
- /django_cfg/models/{base.py → base/config.py} +0 -0
- /django_cfg/models/{cfg.py → base/module.py} +0 -0
- /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
- /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
- {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.11.dist-info → django_cfg-1.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,531 @@
|
|
1
|
+
"""
|
2
|
+
WebSocket RPC Client for Django.
|
3
|
+
|
4
|
+
Synchronous RPC client enabling Django applications to communicate
|
5
|
+
with external WebSocket servers via Redis.
|
6
|
+
|
7
|
+
File size: ~650 lines
|
8
|
+
"""
|
9
|
+
|
10
|
+
import redis
|
11
|
+
import json
|
12
|
+
import logging
|
13
|
+
from uuid import uuid4
|
14
|
+
from typing import Optional, TypeVar, Type
|
15
|
+
from pydantic import BaseModel
|
16
|
+
|
17
|
+
from django_cfg.models.websocket import (
|
18
|
+
RPCRequest,
|
19
|
+
RPCResponse,
|
20
|
+
RPCError,
|
21
|
+
RPCErrorCode,
|
22
|
+
)
|
23
|
+
from .exceptions import (
|
24
|
+
RPCTimeoutError,
|
25
|
+
RPCRemoteError,
|
26
|
+
RPCConnectionError,
|
27
|
+
RPCConfigurationError,
|
28
|
+
)
|
29
|
+
|
30
|
+
logger = logging.getLogger(__name__)
|
31
|
+
|
32
|
+
TParams = TypeVar("TParams", bound=BaseModel)
|
33
|
+
TResult = TypeVar("TResult", bound=BaseModel)
|
34
|
+
|
35
|
+
|
36
|
+
class WebSocketRPCClient:
|
37
|
+
"""
|
38
|
+
Synchronous RPC client for Django to communicate with WebSocket servers.
|
39
|
+
|
40
|
+
Features:
|
41
|
+
- Uses Redis Streams for reliable request delivery
|
42
|
+
- Uses Redis Lists for fast response retrieval
|
43
|
+
- Blocks synchronously using BLPOP (no async/await)
|
44
|
+
- Handles correlation IDs automatically
|
45
|
+
- Type-safe API with Pydantic models
|
46
|
+
- Connection pooling for performance
|
47
|
+
- Automatic cleanup of ephemeral keys
|
48
|
+
|
49
|
+
Example:
|
50
|
+
>>> from django_cfg.modules.django_rpc import get_rpc_client
|
51
|
+
>>> from django_cfg.models.websocket import NotificationRequest, NotificationResponse
|
52
|
+
>>>
|
53
|
+
>>> rpc = get_rpc_client()
|
54
|
+
>>> result: NotificationResponse = rpc.call(
|
55
|
+
... method="send_notification",
|
56
|
+
... params=NotificationRequest(user_id="123", message="Hello"),
|
57
|
+
... result_model=NotificationResponse
|
58
|
+
... )
|
59
|
+
"""
|
60
|
+
|
61
|
+
def __init__(
|
62
|
+
self,
|
63
|
+
redis_url: Optional[str] = None,
|
64
|
+
timeout: int = 30,
|
65
|
+
request_stream: str = "stream:requests",
|
66
|
+
consumer_group: str = "rpc_group",
|
67
|
+
stream_maxlen: int = 10000,
|
68
|
+
response_key_prefix: str = "list:response:",
|
69
|
+
response_key_ttl: int = 60,
|
70
|
+
max_connections: int = 50,
|
71
|
+
log_calls: bool = False,
|
72
|
+
):
|
73
|
+
"""
|
74
|
+
Initialize RPC client.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
redis_url: Redis connection URL
|
78
|
+
timeout: Default timeout for RPC calls (seconds)
|
79
|
+
request_stream: Redis Stream name for requests
|
80
|
+
consumer_group: Consumer group name
|
81
|
+
stream_maxlen: Maximum stream length
|
82
|
+
response_key_prefix: Prefix for response list keys
|
83
|
+
response_key_ttl: Response key TTL (seconds)
|
84
|
+
max_connections: Maximum Redis connections in pool
|
85
|
+
log_calls: Log all RPC calls (verbose)
|
86
|
+
"""
|
87
|
+
self.redis_url = redis_url or self._get_redis_url_from_settings()
|
88
|
+
self.default_timeout = timeout
|
89
|
+
self.request_stream = request_stream
|
90
|
+
self.consumer_group = consumer_group
|
91
|
+
self.stream_maxlen = stream_maxlen
|
92
|
+
self.response_key_prefix = response_key_prefix
|
93
|
+
self.response_key_ttl = response_key_ttl
|
94
|
+
self.log_calls = log_calls
|
95
|
+
|
96
|
+
# Create Redis connection pool
|
97
|
+
try:
|
98
|
+
self._pool = redis.ConnectionPool.from_url(
|
99
|
+
self.redis_url,
|
100
|
+
max_connections=max_connections,
|
101
|
+
decode_responses=False, # We handle JSON ourselves
|
102
|
+
socket_keepalive=True,
|
103
|
+
)
|
104
|
+
self._redis = redis.Redis(connection_pool=self._pool)
|
105
|
+
|
106
|
+
# Test connection
|
107
|
+
self._redis.ping()
|
108
|
+
|
109
|
+
logger.info(f"WebSocket RPC Client initialized: {self.redis_url}")
|
110
|
+
|
111
|
+
except redis.ConnectionError as e:
|
112
|
+
raise RPCConnectionError(
|
113
|
+
f"Failed to connect to Redis: {e}",
|
114
|
+
redis_url=self.redis_url,
|
115
|
+
)
|
116
|
+
except Exception as e:
|
117
|
+
raise RPCConnectionError(
|
118
|
+
f"Failed to initialize RPC client: {e}",
|
119
|
+
redis_url=self.redis_url,
|
120
|
+
)
|
121
|
+
|
122
|
+
def _get_redis_url_from_settings(self) -> str:
|
123
|
+
"""
|
124
|
+
Get Redis URL from Django settings.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
Redis URL string
|
128
|
+
|
129
|
+
Raises:
|
130
|
+
RPCConfigurationError: If settings not configured
|
131
|
+
"""
|
132
|
+
try:
|
133
|
+
from django.conf import settings
|
134
|
+
|
135
|
+
if not hasattr(settings, "WEBSOCKET_RPC"):
|
136
|
+
raise RPCConfigurationError(
|
137
|
+
"WEBSOCKET_RPC not found in Django settings. "
|
138
|
+
"Configure WebSocketRPCConfig in django-cfg.",
|
139
|
+
config_key="WEBSOCKET_RPC",
|
140
|
+
)
|
141
|
+
|
142
|
+
redis_url = settings.WEBSOCKET_RPC.get("REDIS_URL")
|
143
|
+
if not redis_url:
|
144
|
+
raise RPCConfigurationError(
|
145
|
+
"REDIS_URL not found in WEBSOCKET_RPC settings",
|
146
|
+
config_key="WEBSOCKET_RPC.REDIS_URL",
|
147
|
+
)
|
148
|
+
|
149
|
+
return redis_url
|
150
|
+
|
151
|
+
except ImportError:
|
152
|
+
raise RPCConfigurationError(
|
153
|
+
"Django not installed. Provide redis_url explicitly or configure Django."
|
154
|
+
)
|
155
|
+
|
156
|
+
def call(
|
157
|
+
self,
|
158
|
+
method: str,
|
159
|
+
params: TParams,
|
160
|
+
result_model: Type[TResult],
|
161
|
+
timeout: Optional[int] = None,
|
162
|
+
) -> TResult:
|
163
|
+
"""
|
164
|
+
Make synchronous RPC call to WebSocket server.
|
165
|
+
|
166
|
+
Args:
|
167
|
+
method: RPC method name
|
168
|
+
params: Pydantic model with parameters
|
169
|
+
result_model: Expected result model class
|
170
|
+
timeout: Optional timeout override (seconds)
|
171
|
+
|
172
|
+
Returns:
|
173
|
+
Pydantic result model instance
|
174
|
+
|
175
|
+
Raises:
|
176
|
+
RPCTimeoutError: If timeout exceeded
|
177
|
+
RPCRemoteError: If remote execution failed
|
178
|
+
ValidationError: If response doesn't match result_model
|
179
|
+
|
180
|
+
Example:
|
181
|
+
>>> from django_cfg.models.websocket import EchoParams, EchoResult
|
182
|
+
>>> result = rpc.call(
|
183
|
+
... method="echo",
|
184
|
+
... params=EchoParams(message="Hello"),
|
185
|
+
... result_model=EchoResult,
|
186
|
+
... timeout=10
|
187
|
+
... )
|
188
|
+
>>> print(result.echoed) # "Hello"
|
189
|
+
"""
|
190
|
+
timeout = timeout or self.default_timeout
|
191
|
+
|
192
|
+
# Generate correlation ID
|
193
|
+
cid = uuid4()
|
194
|
+
reply_key = f"{self.response_key_prefix}{cid}"
|
195
|
+
|
196
|
+
# Build RPC request
|
197
|
+
request = RPCRequest[type(params)](
|
198
|
+
correlation_id=cid,
|
199
|
+
method=method,
|
200
|
+
params=params,
|
201
|
+
reply_to=reply_key,
|
202
|
+
timeout=timeout,
|
203
|
+
)
|
204
|
+
|
205
|
+
if self.log_calls:
|
206
|
+
logger.debug(f"RPC call: {method} (cid={cid})")
|
207
|
+
|
208
|
+
try:
|
209
|
+
# Send request to Redis Stream
|
210
|
+
message_id = self._redis.xadd(
|
211
|
+
self.request_stream,
|
212
|
+
{"payload": request.model_dump_json()},
|
213
|
+
maxlen=self.stream_maxlen,
|
214
|
+
approximate=True,
|
215
|
+
)
|
216
|
+
|
217
|
+
if self.log_calls:
|
218
|
+
logger.debug(f"Request sent to stream: {message_id}")
|
219
|
+
|
220
|
+
# Block waiting for response (BLPOP)
|
221
|
+
response_data = self._redis.blpop(reply_key, timeout)
|
222
|
+
|
223
|
+
if response_data is None:
|
224
|
+
# Timeout occurred
|
225
|
+
logger.warning(f"RPC timeout: {method} (cid={cid}, timeout={timeout}s)")
|
226
|
+
raise RPCTimeoutError(
|
227
|
+
f"RPC call '{method}' timed out after {timeout}s",
|
228
|
+
method=method,
|
229
|
+
timeout_seconds=timeout,
|
230
|
+
)
|
231
|
+
|
232
|
+
# Unpack BLPOP result: (key, value)
|
233
|
+
_, response_json = response_data
|
234
|
+
|
235
|
+
# Parse response
|
236
|
+
response_dict = json.loads(response_json)
|
237
|
+
response = RPCResponse[result_model](**response_dict)
|
238
|
+
|
239
|
+
if self.log_calls:
|
240
|
+
logger.debug(f"RPC response: {method} (success={response.success})")
|
241
|
+
|
242
|
+
# Check for errors
|
243
|
+
if not response.success:
|
244
|
+
error = RPCError(
|
245
|
+
code=response.error_code or RPCErrorCode.INTERNAL_ERROR,
|
246
|
+
message=response.error or "Unknown error",
|
247
|
+
retryable=response.error_code in {
|
248
|
+
RPCErrorCode.TIMEOUT,
|
249
|
+
RPCErrorCode.SERVICE_UNAVAILABLE,
|
250
|
+
RPCErrorCode.RATE_LIMIT_EXCEEDED,
|
251
|
+
},
|
252
|
+
)
|
253
|
+
raise RPCRemoteError(error)
|
254
|
+
|
255
|
+
if response.result is None:
|
256
|
+
raise RPCRemoteError(
|
257
|
+
RPCError(
|
258
|
+
code=RPCErrorCode.INTERNAL_ERROR,
|
259
|
+
message="Response success=True but result is None",
|
260
|
+
)
|
261
|
+
)
|
262
|
+
|
263
|
+
return response.result
|
264
|
+
|
265
|
+
finally:
|
266
|
+
# Always cleanup response key
|
267
|
+
try:
|
268
|
+
self._redis.delete(reply_key)
|
269
|
+
except Exception as e:
|
270
|
+
logger.error(f"Failed to cleanup response key {reply_key}: {e}")
|
271
|
+
|
272
|
+
def fire_and_forget(self, method: str, params: TParams) -> str:
|
273
|
+
"""
|
274
|
+
Send RPC request without waiting for response.
|
275
|
+
|
276
|
+
Useful for notifications where result doesn't matter.
|
277
|
+
Returns immediately after sending to Redis Stream.
|
278
|
+
|
279
|
+
Args:
|
280
|
+
method: RPC method name
|
281
|
+
params: Pydantic model with parameters
|
282
|
+
|
283
|
+
Returns:
|
284
|
+
Message ID from Redis Stream
|
285
|
+
|
286
|
+
Example:
|
287
|
+
>>> from pydantic import BaseModel
|
288
|
+
>>> class EventLog(BaseModel):
|
289
|
+
... event: str
|
290
|
+
... user_id: str
|
291
|
+
>>> rpc.fire_and_forget(
|
292
|
+
... method="log_event",
|
293
|
+
... params=EventLog(event="user_login", user_id="123")
|
294
|
+
... )
|
295
|
+
"""
|
296
|
+
cid = uuid4()
|
297
|
+
|
298
|
+
request = RPCRequest[type(params)](
|
299
|
+
correlation_id=cid,
|
300
|
+
method=method,
|
301
|
+
params=params,
|
302
|
+
reply_to=f"{self.response_key_prefix}{cid}", # Won't be used
|
303
|
+
timeout=0, # Indicates fire-and-forget
|
304
|
+
)
|
305
|
+
|
306
|
+
message_id = self._redis.xadd(
|
307
|
+
self.request_stream,
|
308
|
+
{"payload": request.model_dump_json()},
|
309
|
+
maxlen=self.stream_maxlen,
|
310
|
+
approximate=True,
|
311
|
+
)
|
312
|
+
|
313
|
+
if self.log_calls:
|
314
|
+
logger.debug(f"Fire-and-forget: {method} (mid={message_id})")
|
315
|
+
|
316
|
+
return message_id.decode()
|
317
|
+
|
318
|
+
def broadcast(self, channel: str, message: BaseModel) -> int:
|
319
|
+
"""
|
320
|
+
Broadcast message via Redis Pub/Sub.
|
321
|
+
|
322
|
+
Sends message to all WebSocket servers subscribed to channel.
|
323
|
+
|
324
|
+
Args:
|
325
|
+
channel: Redis channel name
|
326
|
+
message: Pydantic model to broadcast
|
327
|
+
|
328
|
+
Returns:
|
329
|
+
Number of subscribers that received message
|
330
|
+
|
331
|
+
Example:
|
332
|
+
>>> from django_cfg.models.websocket import BroadcastRequest
|
333
|
+
>>> rpc.broadcast(
|
334
|
+
... channel="notifications:broadcast",
|
335
|
+
... message=BroadcastRequest(
|
336
|
+
... target="all",
|
337
|
+
... event_type="system_update",
|
338
|
+
... payload={"version": "2.0"}
|
339
|
+
... )
|
340
|
+
... )
|
341
|
+
"""
|
342
|
+
subscribers = self._redis.publish(channel, message.model_dump_json())
|
343
|
+
|
344
|
+
if self.log_calls:
|
345
|
+
logger.info(f"Broadcast to {channel}: {subscribers} subscribers")
|
346
|
+
|
347
|
+
return subscribers
|
348
|
+
|
349
|
+
def health_check(self, timeout: int = 5) -> bool:
|
350
|
+
"""
|
351
|
+
Check if RPC system is healthy.
|
352
|
+
|
353
|
+
Attempts to:
|
354
|
+
1. Ping Redis
|
355
|
+
2. Send echo RPC call (if echo method exists)
|
356
|
+
|
357
|
+
Args:
|
358
|
+
timeout: Health check timeout (seconds)
|
359
|
+
|
360
|
+
Returns:
|
361
|
+
True if healthy, False otherwise
|
362
|
+
|
363
|
+
Example:
|
364
|
+
>>> if rpc.health_check():
|
365
|
+
... print("RPC system healthy")
|
366
|
+
... else:
|
367
|
+
... print("RPC system unhealthy")
|
368
|
+
"""
|
369
|
+
try:
|
370
|
+
# Try to ping Redis
|
371
|
+
ping_result = self._redis.ping()
|
372
|
+
if not ping_result:
|
373
|
+
logger.error("Health check failed: Redis ping returned False")
|
374
|
+
return False
|
375
|
+
|
376
|
+
# Try simple echo RPC (optional, depends on server implementation)
|
377
|
+
try:
|
378
|
+
from django_cfg.models.websocket import EchoParams, EchoResult
|
379
|
+
|
380
|
+
result = self.call(
|
381
|
+
method="echo",
|
382
|
+
params=EchoParams(message="health_check"),
|
383
|
+
result_model=EchoResult,
|
384
|
+
timeout=timeout,
|
385
|
+
)
|
386
|
+
|
387
|
+
if result.echoed != "health_check":
|
388
|
+
logger.error(
|
389
|
+
f"Health check failed: Echo mismatch "
|
390
|
+
f"(expected 'health_check', got '{result.echoed}')"
|
391
|
+
)
|
392
|
+
return False
|
393
|
+
|
394
|
+
except (ImportError, RPCTimeoutError, RPCRemoteError):
|
395
|
+
# Echo method may not be implemented, that's okay
|
396
|
+
# Redis ping success is minimum health check
|
397
|
+
pass
|
398
|
+
|
399
|
+
return True
|
400
|
+
|
401
|
+
except Exception as e:
|
402
|
+
logger.error(f"Health check failed: {e}")
|
403
|
+
return False
|
404
|
+
|
405
|
+
def get_connection_info(self) -> dict:
|
406
|
+
"""
|
407
|
+
Get connection information.
|
408
|
+
|
409
|
+
Returns:
|
410
|
+
Dictionary with connection details
|
411
|
+
|
412
|
+
Example:
|
413
|
+
>>> info = rpc.get_connection_info()
|
414
|
+
>>> print(info["redis_url"])
|
415
|
+
>>> print(info["pool_size"])
|
416
|
+
"""
|
417
|
+
return {
|
418
|
+
"redis_url": self.redis_url,
|
419
|
+
"pool_size": self._pool.max_connections if self._pool else 0,
|
420
|
+
"request_stream": self.request_stream,
|
421
|
+
"consumer_group": self.consumer_group,
|
422
|
+
"default_timeout": self.default_timeout,
|
423
|
+
}
|
424
|
+
|
425
|
+
def close(self):
|
426
|
+
"""
|
427
|
+
Close Redis connection pool.
|
428
|
+
|
429
|
+
Call this when shutting down application to clean up resources.
|
430
|
+
|
431
|
+
Example:
|
432
|
+
>>> rpc.close()
|
433
|
+
"""
|
434
|
+
if self._pool:
|
435
|
+
self._pool.disconnect()
|
436
|
+
logger.info("RPC client closed")
|
437
|
+
|
438
|
+
def __enter__(self):
|
439
|
+
"""Context manager entry."""
|
440
|
+
return self
|
441
|
+
|
442
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
443
|
+
"""Context manager exit."""
|
444
|
+
self.close()
|
445
|
+
|
446
|
+
|
447
|
+
# ==================== Singleton Pattern ====================
|
448
|
+
|
449
|
+
_rpc_client: Optional[WebSocketRPCClient] = None
|
450
|
+
_rpc_client_lock = None
|
451
|
+
|
452
|
+
|
453
|
+
def get_rpc_client(force_new: bool = False) -> WebSocketRPCClient:
|
454
|
+
"""
|
455
|
+
Get global RPC client instance (singleton).
|
456
|
+
|
457
|
+
Creates client from Django settings on first call.
|
458
|
+
Subsequent calls return the same instance (thread-safe).
|
459
|
+
|
460
|
+
Args:
|
461
|
+
force_new: Force create new instance (for testing)
|
462
|
+
|
463
|
+
Returns:
|
464
|
+
WebSocketRPCClient instance
|
465
|
+
|
466
|
+
Example:
|
467
|
+
>>> from django_cfg.modules.django_rpc import get_rpc_client
|
468
|
+
>>> rpc = get_rpc_client()
|
469
|
+
>>> result = rpc.call(...)
|
470
|
+
"""
|
471
|
+
global _rpc_client, _rpc_client_lock
|
472
|
+
|
473
|
+
if force_new:
|
474
|
+
return _create_client_from_settings()
|
475
|
+
|
476
|
+
if _rpc_client is None:
|
477
|
+
# Thread-safe singleton creation
|
478
|
+
import threading
|
479
|
+
|
480
|
+
if _rpc_client_lock is None:
|
481
|
+
_rpc_client_lock = threading.Lock()
|
482
|
+
|
483
|
+
with _rpc_client_lock:
|
484
|
+
if _rpc_client is None:
|
485
|
+
_rpc_client = _create_client_from_settings()
|
486
|
+
|
487
|
+
return _rpc_client
|
488
|
+
|
489
|
+
|
490
|
+
def _create_client_from_settings() -> WebSocketRPCClient:
|
491
|
+
"""
|
492
|
+
Create RPC client from Django settings.
|
493
|
+
|
494
|
+
Returns:
|
495
|
+
WebSocketRPCClient instance
|
496
|
+
|
497
|
+
Raises:
|
498
|
+
RPCConfigurationError: If settings not configured
|
499
|
+
"""
|
500
|
+
try:
|
501
|
+
from django.conf import settings
|
502
|
+
|
503
|
+
if not hasattr(settings, "WEBSOCKET_RPC"):
|
504
|
+
raise RPCConfigurationError(
|
505
|
+
"WEBSOCKET_RPC not found in Django settings"
|
506
|
+
)
|
507
|
+
|
508
|
+
rpc_settings = settings.WEBSOCKET_RPC
|
509
|
+
|
510
|
+
return WebSocketRPCClient(
|
511
|
+
redis_url=rpc_settings.get("REDIS_URL"),
|
512
|
+
timeout=rpc_settings.get("RPC_TIMEOUT", 30),
|
513
|
+
request_stream=rpc_settings.get("REQUEST_STREAM", "stream:requests"),
|
514
|
+
consumer_group=rpc_settings.get("CONSUMER_GROUP", "rpc_group"),
|
515
|
+
stream_maxlen=rpc_settings.get("STREAM_MAXLEN", 10000),
|
516
|
+
response_key_prefix=rpc_settings.get("RESPONSE_KEY_PREFIX", "list:response:"),
|
517
|
+
response_key_ttl=rpc_settings.get("RESPONSE_KEY_TTL", 60),
|
518
|
+
max_connections=rpc_settings.get("REDIS_MAX_CONNECTIONS", 50),
|
519
|
+
log_calls=rpc_settings.get("LOG_RPC_CALLS", False),
|
520
|
+
)
|
521
|
+
|
522
|
+
except ImportError:
|
523
|
+
raise RPCConfigurationError(
|
524
|
+
"Django not installed. Cannot create client from settings."
|
525
|
+
)
|
526
|
+
|
527
|
+
|
528
|
+
__all__ = [
|
529
|
+
"WebSocketRPCClient",
|
530
|
+
"get_rpc_client",
|
531
|
+
]
|