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,540 @@
|
|
1
|
+
"""
|
2
|
+
Django-CFG RPC Client.
|
3
|
+
|
4
|
+
Synchronous RPC client enabling Django applications to communicate
|
5
|
+
with django-cfg-rpc WebSocket servers via Redis.
|
6
|
+
|
7
|
+
Works with or without django-cfg-rpc installed:
|
8
|
+
- With django-cfg-rpc: Full type safety with Pydantic models
|
9
|
+
- Without django-cfg-rpc: Basic dict-based communication
|
10
|
+
"""
|
11
|
+
|
12
|
+
import redis
|
13
|
+
import json
|
14
|
+
import logging
|
15
|
+
from uuid import uuid4
|
16
|
+
from typing import Optional, TypeVar, Type, Any, Dict
|
17
|
+
|
18
|
+
from .exceptions import (
|
19
|
+
RPCTimeoutError,
|
20
|
+
RPCRemoteError,
|
21
|
+
RPCConnectionError,
|
22
|
+
RPCConfigurationError,
|
23
|
+
HAS_DJANGO_CFG_RPC,
|
24
|
+
)
|
25
|
+
|
26
|
+
logger = logging.getLogger(__name__)
|
27
|
+
|
28
|
+
# Try to import Pydantic from django-cfg-rpc
|
29
|
+
if HAS_DJANGO_CFG_RPC:
|
30
|
+
try:
|
31
|
+
from pydantic import BaseModel
|
32
|
+
TParams = TypeVar("TParams", bound=BaseModel)
|
33
|
+
TResult = TypeVar("TResult", bound=BaseModel)
|
34
|
+
except ImportError:
|
35
|
+
# Fallback if pydantic not available
|
36
|
+
BaseModel = dict # type: ignore
|
37
|
+
TParams = TypeVar("TParams")
|
38
|
+
TResult = TypeVar("TResult")
|
39
|
+
else:
|
40
|
+
BaseModel = dict # type: ignore
|
41
|
+
TParams = TypeVar("TParams")
|
42
|
+
TResult = TypeVar("TResult")
|
43
|
+
|
44
|
+
|
45
|
+
class DjangoCfgRPCClient:
|
46
|
+
"""
|
47
|
+
Synchronous RPC client for Django to communicate with django-cfg-rpc servers.
|
48
|
+
|
49
|
+
Features:
|
50
|
+
- Uses Redis Streams for reliable request delivery
|
51
|
+
- Uses Redis Lists for fast response retrieval
|
52
|
+
- Blocks synchronously using BLPOP (no async/await)
|
53
|
+
- Handles correlation IDs automatically
|
54
|
+
- Type-safe API with Pydantic models (if django-cfg-rpc installed)
|
55
|
+
- Connection pooling for performance
|
56
|
+
- Automatic cleanup of ephemeral keys
|
57
|
+
|
58
|
+
Example:
|
59
|
+
>>> from django_cfg.modules.django_cfg_rpc_client import get_rpc_client
|
60
|
+
>>>
|
61
|
+
>>> # With django-cfg-rpc models
|
62
|
+
>>> from django_cfg_rpc.models import NotificationRequest, NotificationResponse
|
63
|
+
>>> rpc = get_rpc_client()
|
64
|
+
>>> result: NotificationResponse = rpc.call(
|
65
|
+
... method="send_notification",
|
66
|
+
... params=NotificationRequest(user_id="123", type="info",
|
67
|
+
... title="Hello", message="World"),
|
68
|
+
... result_model=NotificationResponse
|
69
|
+
... )
|
70
|
+
>>>
|
71
|
+
>>> # Without django-cfg-rpc (dict-based)
|
72
|
+
>>> result = rpc.call_dict(
|
73
|
+
... method="send_notification",
|
74
|
+
... params={"user_id": "123", "type": "info",
|
75
|
+
... "title": "Hello", "message": "World"}
|
76
|
+
... )
|
77
|
+
"""
|
78
|
+
|
79
|
+
def __init__(
|
80
|
+
self,
|
81
|
+
redis_url: Optional[str] = None,
|
82
|
+
timeout: int = 30,
|
83
|
+
request_stream: str = "stream:requests",
|
84
|
+
consumer_group: str = "rpc_group",
|
85
|
+
stream_maxlen: int = 10000,
|
86
|
+
response_key_prefix: str = "list:response:",
|
87
|
+
response_key_ttl: int = 60,
|
88
|
+
max_connections: int = 50,
|
89
|
+
log_calls: bool = False,
|
90
|
+
):
|
91
|
+
"""
|
92
|
+
Initialize RPC client.
|
93
|
+
|
94
|
+
Args:
|
95
|
+
redis_url: Redis connection URL
|
96
|
+
timeout: Default timeout for RPC calls (seconds)
|
97
|
+
request_stream: Redis Stream name for requests
|
98
|
+
consumer_group: Consumer group name
|
99
|
+
stream_maxlen: Maximum stream length
|
100
|
+
response_key_prefix: Prefix for response list keys
|
101
|
+
response_key_ttl: Response key TTL (seconds)
|
102
|
+
max_connections: Maximum Redis connections in pool
|
103
|
+
log_calls: Log all RPC calls (verbose)
|
104
|
+
"""
|
105
|
+
self.redis_url = redis_url or self._get_redis_url_from_settings()
|
106
|
+
self.default_timeout = timeout
|
107
|
+
self.request_stream = request_stream
|
108
|
+
self.consumer_group = consumer_group
|
109
|
+
self.stream_maxlen = stream_maxlen
|
110
|
+
self.response_key_prefix = response_key_prefix
|
111
|
+
self.response_key_ttl = response_key_ttl
|
112
|
+
self.log_calls = log_calls
|
113
|
+
|
114
|
+
# Create Redis connection pool
|
115
|
+
try:
|
116
|
+
self._pool = redis.ConnectionPool.from_url(
|
117
|
+
self.redis_url,
|
118
|
+
max_connections=max_connections,
|
119
|
+
decode_responses=False, # We handle JSON ourselves
|
120
|
+
socket_keepalive=True,
|
121
|
+
)
|
122
|
+
self._redis = redis.Redis(connection_pool=self._pool)
|
123
|
+
|
124
|
+
# Test connection
|
125
|
+
self._redis.ping()
|
126
|
+
|
127
|
+
logger.info(f"Django-CFG RPC Client initialized: {self.redis_url}")
|
128
|
+
|
129
|
+
except redis.ConnectionError as e:
|
130
|
+
raise RPCConnectionError(
|
131
|
+
f"Failed to connect to Redis: {e}",
|
132
|
+
redis_url=self.redis_url,
|
133
|
+
)
|
134
|
+
except Exception as e:
|
135
|
+
raise RPCConnectionError(
|
136
|
+
f"Failed to initialize RPC client: {e}",
|
137
|
+
redis_url=self.redis_url,
|
138
|
+
)
|
139
|
+
|
140
|
+
def _get_redis_url_from_settings(self) -> str:
|
141
|
+
"""
|
142
|
+
Get Redis URL from Django settings.
|
143
|
+
|
144
|
+
Returns:
|
145
|
+
Redis URL string
|
146
|
+
|
147
|
+
Raises:
|
148
|
+
RPCConfigurationError: If settings not configured
|
149
|
+
"""
|
150
|
+
try:
|
151
|
+
from django.conf import settings
|
152
|
+
|
153
|
+
if not hasattr(settings, "DJANGO_CFG_RPC"):
|
154
|
+
raise RPCConfigurationError(
|
155
|
+
"DJANGO_CFG_RPC not found in Django settings. "
|
156
|
+
"Configure DjangoCfgRPCConfig in django-cfg.",
|
157
|
+
config_key="DJANGO_CFG_RPC",
|
158
|
+
)
|
159
|
+
|
160
|
+
redis_url = settings.DJANGO_CFG_RPC.get("REDIS_URL")
|
161
|
+
if not redis_url:
|
162
|
+
raise RPCConfigurationError(
|
163
|
+
"REDIS_URL not found in DJANGO_CFG_RPC settings",
|
164
|
+
config_key="DJANGO_CFG_RPC.REDIS_URL",
|
165
|
+
)
|
166
|
+
|
167
|
+
return redis_url
|
168
|
+
|
169
|
+
except ImportError:
|
170
|
+
raise RPCConfigurationError(
|
171
|
+
"Django not installed. Provide redis_url explicitly or configure Django."
|
172
|
+
)
|
173
|
+
|
174
|
+
def call(
|
175
|
+
self,
|
176
|
+
method: str,
|
177
|
+
params: Any,
|
178
|
+
result_model: Optional[Type[TResult]] = None,
|
179
|
+
timeout: Optional[int] = None,
|
180
|
+
) -> Any:
|
181
|
+
"""
|
182
|
+
Make synchronous RPC call to django-cfg-rpc server.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
method: RPC method name
|
186
|
+
params: Pydantic model or dict with parameters
|
187
|
+
result_model: Expected result model class (optional)
|
188
|
+
timeout: Optional timeout override (seconds)
|
189
|
+
|
190
|
+
Returns:
|
191
|
+
Pydantic result model instance (if result_model provided) or dict
|
192
|
+
|
193
|
+
Raises:
|
194
|
+
RPCTimeoutError: If timeout exceeded
|
195
|
+
RPCRemoteError: If remote execution failed
|
196
|
+
ValidationError: If response doesn't match result_model
|
197
|
+
|
198
|
+
Example:
|
199
|
+
>>> from django_cfg_rpc.models import NotificationRequest, NotificationResponse
|
200
|
+
>>> result = rpc.call(
|
201
|
+
... method="send_notification",
|
202
|
+
... params=NotificationRequest(user_id="123", type="info",
|
203
|
+
... title="Hello", message="World"),
|
204
|
+
... result_model=NotificationResponse,
|
205
|
+
... timeout=10
|
206
|
+
... )
|
207
|
+
>>> print(result.delivered) # True/False
|
208
|
+
"""
|
209
|
+
timeout = timeout or self.default_timeout
|
210
|
+
|
211
|
+
# Generate correlation ID
|
212
|
+
cid = str(uuid4())
|
213
|
+
reply_key = f"{self.response_key_prefix}{cid}"
|
214
|
+
|
215
|
+
# Serialize params
|
216
|
+
if HAS_DJANGO_CFG_RPC and hasattr(params, "model_dump_json"):
|
217
|
+
params_json = params.model_dump_json()
|
218
|
+
elif HAS_DJANGO_CFG_RPC and hasattr(params, "model_dump"):
|
219
|
+
params_json = json.dumps(params.model_dump())
|
220
|
+
elif isinstance(params, dict):
|
221
|
+
params_json = json.dumps(params)
|
222
|
+
else:
|
223
|
+
params_json = json.dumps({"data": params})
|
224
|
+
|
225
|
+
# Build RPC request payload
|
226
|
+
request_payload = {
|
227
|
+
"type": "rpc",
|
228
|
+
"method": method,
|
229
|
+
"params": json.loads(params_json), # Embedded as dict
|
230
|
+
"correlation_id": cid,
|
231
|
+
"timeout": timeout,
|
232
|
+
}
|
233
|
+
|
234
|
+
if self.log_calls:
|
235
|
+
logger.debug(f"RPC call: {method} (cid={cid})")
|
236
|
+
|
237
|
+
try:
|
238
|
+
# Send request to Redis Stream
|
239
|
+
message_id = self._redis.xadd(
|
240
|
+
self.request_stream,
|
241
|
+
{"payload": json.dumps(request_payload)},
|
242
|
+
maxlen=self.stream_maxlen,
|
243
|
+
approximate=True,
|
244
|
+
)
|
245
|
+
|
246
|
+
if self.log_calls:
|
247
|
+
logger.debug(f"Request sent to stream: {message_id}")
|
248
|
+
|
249
|
+
# Block waiting for response (BLPOP)
|
250
|
+
response_data = self._redis.blpop(reply_key, timeout)
|
251
|
+
|
252
|
+
if response_data is None:
|
253
|
+
# Timeout occurred
|
254
|
+
logger.warning(f"RPC timeout: {method} (cid={cid}, timeout={timeout}s)")
|
255
|
+
raise RPCTimeoutError(
|
256
|
+
f"RPC call '{method}' timed out after {timeout}s",
|
257
|
+
method=method,
|
258
|
+
timeout_seconds=timeout,
|
259
|
+
)
|
260
|
+
|
261
|
+
# Unpack BLPOP result: (key, value)
|
262
|
+
_, response_json = response_data
|
263
|
+
|
264
|
+
# Parse response
|
265
|
+
response_dict = json.loads(response_json)
|
266
|
+
|
267
|
+
if self.log_calls:
|
268
|
+
logger.debug(f"RPC response received: {method}")
|
269
|
+
|
270
|
+
# Check response type
|
271
|
+
if response_dict.get("type") == "error":
|
272
|
+
# Error response
|
273
|
+
error_data = response_dict.get("error", {})
|
274
|
+
raise RPCRemoteError(error_data)
|
275
|
+
|
276
|
+
# Extract result
|
277
|
+
result_data = response_dict.get("result")
|
278
|
+
|
279
|
+
if result_data is None:
|
280
|
+
raise RPCRemoteError({
|
281
|
+
"code": "internal_error",
|
282
|
+
"message": "Response has no result field",
|
283
|
+
})
|
284
|
+
|
285
|
+
# Deserialize result if model provided
|
286
|
+
if result_model and HAS_DJANGO_CFG_RPC:
|
287
|
+
try:
|
288
|
+
return result_model(**result_data)
|
289
|
+
except Exception as e:
|
290
|
+
logger.error(f"Failed to deserialize result: {e}")
|
291
|
+
# Return raw dict as fallback
|
292
|
+
return result_data
|
293
|
+
else:
|
294
|
+
return result_data
|
295
|
+
|
296
|
+
finally:
|
297
|
+
# Always cleanup response key
|
298
|
+
try:
|
299
|
+
self._redis.delete(reply_key)
|
300
|
+
except Exception as e:
|
301
|
+
logger.error(f"Failed to cleanup response key {reply_key}: {e}")
|
302
|
+
|
303
|
+
def call_dict(
|
304
|
+
self,
|
305
|
+
method: str,
|
306
|
+
params: Dict[str, Any],
|
307
|
+
timeout: Optional[int] = None,
|
308
|
+
) -> Dict[str, Any]:
|
309
|
+
"""
|
310
|
+
Make RPC call with dict params (no Pydantic).
|
311
|
+
|
312
|
+
Args:
|
313
|
+
method: RPC method name
|
314
|
+
params: Dictionary with parameters
|
315
|
+
timeout: Optional timeout override (seconds)
|
316
|
+
|
317
|
+
Returns:
|
318
|
+
Dictionary with result
|
319
|
+
|
320
|
+
Example:
|
321
|
+
>>> result = rpc.call_dict(
|
322
|
+
... method="send_notification",
|
323
|
+
... params={"user_id": "123", "type": "info",
|
324
|
+
... "title": "Hello", "message": "World"}
|
325
|
+
... )
|
326
|
+
>>> print(result["delivered"])
|
327
|
+
"""
|
328
|
+
return self.call(method=method, params=params, result_model=None, timeout=timeout)
|
329
|
+
|
330
|
+
def fire_and_forget(self, method: str, params: Any) -> str:
|
331
|
+
"""
|
332
|
+
Send RPC request without waiting for response.
|
333
|
+
|
334
|
+
Useful for notifications where result doesn't matter.
|
335
|
+
Returns immediately after sending to Redis Stream.
|
336
|
+
|
337
|
+
Args:
|
338
|
+
method: RPC method name
|
339
|
+
params: Pydantic model or dict with parameters
|
340
|
+
|
341
|
+
Returns:
|
342
|
+
Message ID from Redis Stream
|
343
|
+
|
344
|
+
Example:
|
345
|
+
>>> rpc.fire_and_forget(
|
346
|
+
... method="log_event",
|
347
|
+
... params={"event": "user_login", "user_id": "123"}
|
348
|
+
... )
|
349
|
+
"""
|
350
|
+
cid = str(uuid4())
|
351
|
+
|
352
|
+
# Serialize params
|
353
|
+
if HAS_DJANGO_CFG_RPC and hasattr(params, "model_dump_json"):
|
354
|
+
params_json = params.model_dump_json()
|
355
|
+
elif HAS_DJANGO_CFG_RPC and hasattr(params, "model_dump"):
|
356
|
+
params_json = json.dumps(params.model_dump())
|
357
|
+
elif isinstance(params, dict):
|
358
|
+
params_json = json.dumps(params)
|
359
|
+
else:
|
360
|
+
params_json = json.dumps({"data": params})
|
361
|
+
|
362
|
+
request_payload = {
|
363
|
+
"type": "rpc",
|
364
|
+
"method": method,
|
365
|
+
"params": json.loads(params_json),
|
366
|
+
"correlation_id": cid,
|
367
|
+
"timeout": 0, # Indicates fire-and-forget
|
368
|
+
}
|
369
|
+
|
370
|
+
message_id = self._redis.xadd(
|
371
|
+
self.request_stream,
|
372
|
+
{"payload": json.dumps(request_payload)},
|
373
|
+
maxlen=self.stream_maxlen,
|
374
|
+
approximate=True,
|
375
|
+
)
|
376
|
+
|
377
|
+
if self.log_calls:
|
378
|
+
logger.debug(f"Fire-and-forget: {method} (mid={message_id})")
|
379
|
+
|
380
|
+
return message_id.decode() if isinstance(message_id, bytes) else str(message_id)
|
381
|
+
|
382
|
+
def health_check(self, timeout: int = 5) -> bool:
|
383
|
+
"""
|
384
|
+
Check if RPC system is healthy.
|
385
|
+
|
386
|
+
Attempts to ping Redis.
|
387
|
+
|
388
|
+
Args:
|
389
|
+
timeout: Health check timeout (seconds)
|
390
|
+
|
391
|
+
Returns:
|
392
|
+
True if healthy, False otherwise
|
393
|
+
|
394
|
+
Example:
|
395
|
+
>>> if rpc.health_check():
|
396
|
+
... print("RPC system healthy")
|
397
|
+
... else:
|
398
|
+
... print("RPC system unhealthy")
|
399
|
+
"""
|
400
|
+
try:
|
401
|
+
# Try to ping Redis
|
402
|
+
ping_result = self._redis.ping()
|
403
|
+
if not ping_result:
|
404
|
+
logger.error("Health check failed: Redis ping returned False")
|
405
|
+
return False
|
406
|
+
|
407
|
+
return True
|
408
|
+
|
409
|
+
except Exception as e:
|
410
|
+
logger.error(f"Health check failed: {e}")
|
411
|
+
return False
|
412
|
+
|
413
|
+
def get_connection_info(self) -> dict:
|
414
|
+
"""
|
415
|
+
Get connection information.
|
416
|
+
|
417
|
+
Returns:
|
418
|
+
Dictionary with connection details
|
419
|
+
|
420
|
+
Example:
|
421
|
+
>>> info = rpc.get_connection_info()
|
422
|
+
>>> print(info["redis_url"])
|
423
|
+
>>> print(info["request_stream"])
|
424
|
+
"""
|
425
|
+
return {
|
426
|
+
"redis_url": self.redis_url,
|
427
|
+
"pool_size": self._pool.max_connections if self._pool else 0,
|
428
|
+
"request_stream": self.request_stream,
|
429
|
+
"consumer_group": self.consumer_group,
|
430
|
+
"default_timeout": self.default_timeout,
|
431
|
+
"has_django_cfg_rpc": HAS_DJANGO_CFG_RPC,
|
432
|
+
}
|
433
|
+
|
434
|
+
def close(self):
|
435
|
+
"""
|
436
|
+
Close Redis connection pool.
|
437
|
+
|
438
|
+
Call this when shutting down application to clean up resources.
|
439
|
+
|
440
|
+
Example:
|
441
|
+
>>> rpc.close()
|
442
|
+
"""
|
443
|
+
if self._pool:
|
444
|
+
self._pool.disconnect()
|
445
|
+
logger.info("RPC client closed")
|
446
|
+
|
447
|
+
def __enter__(self):
|
448
|
+
"""Context manager entry."""
|
449
|
+
return self
|
450
|
+
|
451
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
452
|
+
"""Context manager exit."""
|
453
|
+
self.close()
|
454
|
+
|
455
|
+
|
456
|
+
# ==================== Singleton Pattern ====================
|
457
|
+
|
458
|
+
_rpc_client: Optional[DjangoCfgRPCClient] = None
|
459
|
+
_rpc_client_lock = None
|
460
|
+
|
461
|
+
|
462
|
+
def get_rpc_client(force_new: bool = False) -> DjangoCfgRPCClient:
|
463
|
+
"""
|
464
|
+
Get global RPC client instance (singleton).
|
465
|
+
|
466
|
+
Creates client from Django settings on first call.
|
467
|
+
Subsequent calls return the same instance (thread-safe).
|
468
|
+
|
469
|
+
Args:
|
470
|
+
force_new: Force create new instance (for testing)
|
471
|
+
|
472
|
+
Returns:
|
473
|
+
DjangoCfgRPCClient instance
|
474
|
+
|
475
|
+
Example:
|
476
|
+
>>> from django_cfg.modules.django_cfg_rpc_client import get_rpc_client
|
477
|
+
>>> rpc = get_rpc_client()
|
478
|
+
>>> result = rpc.call(...)
|
479
|
+
"""
|
480
|
+
global _rpc_client, _rpc_client_lock
|
481
|
+
|
482
|
+
if force_new:
|
483
|
+
return _create_client_from_settings()
|
484
|
+
|
485
|
+
if _rpc_client is None:
|
486
|
+
# Thread-safe singleton creation
|
487
|
+
import threading
|
488
|
+
|
489
|
+
if _rpc_client_lock is None:
|
490
|
+
_rpc_client_lock = threading.Lock()
|
491
|
+
|
492
|
+
with _rpc_client_lock:
|
493
|
+
if _rpc_client is None:
|
494
|
+
_rpc_client = _create_client_from_settings()
|
495
|
+
|
496
|
+
return _rpc_client
|
497
|
+
|
498
|
+
|
499
|
+
def _create_client_from_settings() -> DjangoCfgRPCClient:
|
500
|
+
"""
|
501
|
+
Create RPC client from Django settings.
|
502
|
+
|
503
|
+
Returns:
|
504
|
+
DjangoCfgRPCClient instance
|
505
|
+
|
506
|
+
Raises:
|
507
|
+
RPCConfigurationError: If settings not configured
|
508
|
+
"""
|
509
|
+
try:
|
510
|
+
from django.conf import settings
|
511
|
+
|
512
|
+
if not hasattr(settings, "DJANGO_CFG_RPC"):
|
513
|
+
raise RPCConfigurationError(
|
514
|
+
"DJANGO_CFG_RPC not found in Django settings"
|
515
|
+
)
|
516
|
+
|
517
|
+
rpc_settings = settings.DJANGO_CFG_RPC
|
518
|
+
|
519
|
+
return DjangoCfgRPCClient(
|
520
|
+
redis_url=rpc_settings.get("REDIS_URL"),
|
521
|
+
timeout=rpc_settings.get("RPC_TIMEOUT", 30),
|
522
|
+
request_stream=rpc_settings.get("REQUEST_STREAM", "stream:requests"),
|
523
|
+
consumer_group=rpc_settings.get("CONSUMER_GROUP", "rpc_group"),
|
524
|
+
stream_maxlen=rpc_settings.get("STREAM_MAXLEN", 10000),
|
525
|
+
response_key_prefix=rpc_settings.get("RESPONSE_KEY_PREFIX", "list:response:"),
|
526
|
+
response_key_ttl=rpc_settings.get("RESPONSE_KEY_TTL", 60),
|
527
|
+
max_connections=rpc_settings.get("REDIS_MAX_CONNECTIONS", 50),
|
528
|
+
log_calls=rpc_settings.get("LOG_RPC_CALLS", False),
|
529
|
+
)
|
530
|
+
|
531
|
+
except ImportError:
|
532
|
+
raise RPCConfigurationError(
|
533
|
+
"Django not installed. Cannot create client from settings."
|
534
|
+
)
|
535
|
+
|
536
|
+
|
537
|
+
__all__ = [
|
538
|
+
"DjangoCfgRPCClient",
|
539
|
+
"get_rpc_client",
|
540
|
+
]
|