django-cfg 1.4.61__py3-none-any.whl → 1.4.63__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +3 -14
- django_cfg/apps/centrifugo/__init__.py +57 -0
- django_cfg/apps/centrifugo/admin/__init__.py +13 -0
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
- django_cfg/apps/centrifugo/admin/config.py +82 -0
- django_cfg/apps/centrifugo/apps.py +31 -0
- django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
- django_cfg/apps/centrifugo/codegen/README.md +242 -0
- django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
- django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
- django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
- django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
- django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
- django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
- django_cfg/apps/centrifugo/decorators.py +137 -0
- django_cfg/apps/centrifugo/management/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
- django_cfg/apps/centrifugo/managers/__init__.py +12 -0
- django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
- django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
- django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
- django_cfg/apps/centrifugo/models/__init__.py +11 -0
- django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
- django_cfg/apps/centrifugo/registry.py +106 -0
- django_cfg/apps/centrifugo/router.py +125 -0
- django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
- django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
- django_cfg/apps/centrifugo/serializers/channels.py +26 -0
- django_cfg/apps/centrifugo/serializers/health.py +17 -0
- django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
- django_cfg/apps/centrifugo/serializers/stats.py +21 -0
- django_cfg/apps/centrifugo/services/__init__.py +12 -0
- django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
- django_cfg/apps/centrifugo/services/client/client.py +577 -0
- django_cfg/apps/centrifugo/services/client/config.py +228 -0
- django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
- django_cfg/apps/centrifugo/services/config_helper.py +63 -0
- django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
- django_cfg/apps/centrifugo/services/logging.py +677 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
- django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
- django_cfg/apps/centrifugo/urls.py +31 -0
- django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
- django_cfg/apps/centrifugo/views/__init__.py +15 -0
- django_cfg/apps/centrifugo/views/admin_api.py +374 -0
- django_cfg/apps/centrifugo/views/dashboard.py +15 -0
- django_cfg/apps/centrifugo/views/monitoring.py +286 -0
- django_cfg/apps/centrifugo/views/testing_api.py +422 -0
- django_cfg/apps/support/utils/support_email_service.py +5 -18
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
- django_cfg/apps/urls.py +5 -5
- django_cfg/core/base/config_model.py +4 -44
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +8 -8
- django_cfg/core/utils/__init__.py +5 -0
- django_cfg/core/utils/url_helpers.py +73 -0
- django_cfg/modules/base.py +7 -7
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/config/config.py +8 -0
- django_cfg/modules/django_client/core/generator/__init__.py +42 -2
- django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
- django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
- django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
- django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
- django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
- django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
- django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
- django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
- django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
- django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
- django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
- django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
- django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
- django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
- django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
- django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
- django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
- django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
- django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
- django_cfg/modules/django_client/system/schema_parser.py +5 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
- django_cfg/modules/django_unfold/dashboard.py +25 -19
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/core.py +2 -0
- django_cfg/registry/modules.py +2 -2
- django_cfg/static/js/api/centrifugo/client.mjs +164 -0
- django_cfg/static/js/api/centrifugo/index.mjs +13 -0
- django_cfg/static/js/api/index.mjs +5 -5
- django_cfg/static/js/api/types.mjs +89 -26
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -68
- django_cfg/apps/ipc/README.md +0 -346
- django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
- django_cfg/apps/ipc/TESTING.md +0 -539
- django_cfg/apps/ipc/__init__.py +0 -60
- django_cfg/apps/ipc/admin.py +0 -212
- django_cfg/apps/ipc/apps.py +0 -28
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -221
- django_cfg/apps/ipc/serializers/__init__.py +0 -29
- django_cfg/apps/ipc/serializers/serializers.py +0 -343
- django_cfg/apps/ipc/services/__init__.py +0 -7
- django_cfg/apps/ipc/services/client/__init__.py +0 -23
- django_cfg/apps/ipc/services/client/client.py +0 -621
- django_cfg/apps/ipc/services/client/config.py +0 -214
- django_cfg/apps/ipc/services/client/exceptions.py +0 -201
- django_cfg/apps/ipc/services/logging.py +0 -239
- django_cfg/apps/ipc/services/monitor.py +0 -466
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
- django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
- django_cfg/apps/ipc/urls.py +0 -23
- django_cfg/apps/ipc/views/__init__.py +0 -13
- django_cfg/apps/ipc/views/dashboard.py +0 -15
- django_cfg/apps/ipc/views/monitoring.py +0 -251
- django_cfg/apps/ipc/views/testing.py +0 -285
- django_cfg/static/js/api/ipc/client.mjs +0 -114
- django_cfg/static/js/api/ipc/index.mjs +0 -13
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django-CFG RPC Client Configuration.
|
|
3
|
-
|
|
4
|
-
Pydantic 2 configuration model for RPC client integration.
|
|
5
|
-
Follows django-cfg patterns for modular configuration.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from pydantic import BaseModel, Field, field_validator
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class DjangoCfgRPCConfig(BaseModel):
|
|
12
|
-
"""
|
|
13
|
-
Django-CFG RPC Client configuration module.
|
|
14
|
-
|
|
15
|
-
Configures Redis-based RPC communication between Django and
|
|
16
|
-
django-cfg-rpc WebSocket servers.
|
|
17
|
-
|
|
18
|
-
Example:
|
|
19
|
-
>>> from django_cfg import DjangoConfig
|
|
20
|
-
>>> from django_cfg.modules.django_ipc_client import DjangoCfgRPCConfig
|
|
21
|
-
>>>
|
|
22
|
-
>>> config = DjangoConfig(
|
|
23
|
-
... django_ipc=DjangoCfgRPCConfig(
|
|
24
|
-
... enabled=True,
|
|
25
|
-
... redis_url="redis://localhost:6379/2",
|
|
26
|
-
... rpc_timeout=30
|
|
27
|
-
... )
|
|
28
|
-
... )
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
# Module metadata
|
|
32
|
-
module_name: str = Field(
|
|
33
|
-
default="django_ipc_client",
|
|
34
|
-
frozen=True,
|
|
35
|
-
description="Module name for django-cfg integration",
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
enabled: bool = Field(
|
|
39
|
-
default=False,
|
|
40
|
-
description="Enable Django-CFG RPC client",
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
# Redis configuration
|
|
44
|
-
redis_url: str = Field(
|
|
45
|
-
default="redis://localhost:6379/2",
|
|
46
|
-
description="Redis URL for RPC communication (dedicated database recommended)",
|
|
47
|
-
examples=[
|
|
48
|
-
"redis://localhost:6379/2",
|
|
49
|
-
"redis://:password@localhost:6379/2",
|
|
50
|
-
"redis://redis-server:6379/2",
|
|
51
|
-
],
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
redis_max_connections: int = Field(
|
|
55
|
-
default=50,
|
|
56
|
-
ge=10,
|
|
57
|
-
le=500,
|
|
58
|
-
description="Maximum Redis connection pool size",
|
|
59
|
-
)
|
|
60
|
-
|
|
61
|
-
# RPC settings
|
|
62
|
-
rpc_timeout: int = Field(
|
|
63
|
-
default=30,
|
|
64
|
-
ge=5,
|
|
65
|
-
le=300,
|
|
66
|
-
description="Default RPC call timeout (seconds)",
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
request_stream: str = Field(
|
|
70
|
-
default="stream:requests",
|
|
71
|
-
min_length=1,
|
|
72
|
-
max_length=100,
|
|
73
|
-
description="Redis Stream name for RPC requests",
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
consumer_group: str = Field(
|
|
77
|
-
default="rpc_group",
|
|
78
|
-
min_length=1,
|
|
79
|
-
max_length=100,
|
|
80
|
-
description="Redis Streams consumer group name (on server side)",
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
stream_maxlen: int = Field(
|
|
84
|
-
default=10000,
|
|
85
|
-
ge=1000,
|
|
86
|
-
le=100000,
|
|
87
|
-
description="Maximum stream length (XADD MAXLEN)",
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
# Response settings
|
|
91
|
-
response_key_prefix: str = Field(
|
|
92
|
-
default="list:response:",
|
|
93
|
-
min_length=1,
|
|
94
|
-
max_length=50,
|
|
95
|
-
description="Prefix for response list keys",
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
response_key_ttl: int = Field(
|
|
99
|
-
default=60,
|
|
100
|
-
ge=10,
|
|
101
|
-
le=3600,
|
|
102
|
-
description="Response key TTL (seconds) for auto-cleanup",
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
# Performance settings
|
|
106
|
-
enable_connection_pooling: bool = Field(
|
|
107
|
-
default=True,
|
|
108
|
-
description="Enable Redis connection pooling",
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
socket_keepalive: bool = Field(
|
|
112
|
-
default=True,
|
|
113
|
-
description="Enable TCP keepalive for Redis connections",
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
# Logging settings
|
|
117
|
-
log_rpc_calls: bool = Field(
|
|
118
|
-
default=False,
|
|
119
|
-
description="Log all RPC calls (verbose, use for debugging)",
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
log_level: str = Field(
|
|
123
|
-
default="INFO",
|
|
124
|
-
pattern=r"^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$",
|
|
125
|
-
description="Log level for RPC module",
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
@field_validator("redis_url")
|
|
129
|
-
@classmethod
|
|
130
|
-
def validate_redis_url(cls, v: str) -> str:
|
|
131
|
-
"""
|
|
132
|
-
Validate Redis URL format.
|
|
133
|
-
|
|
134
|
-
Allows environment variable templates like ${VAR:-default}.
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
v: Redis URL to validate
|
|
138
|
-
|
|
139
|
-
Returns:
|
|
140
|
-
Validated Redis URL
|
|
141
|
-
|
|
142
|
-
Raises:
|
|
143
|
-
ValueError: If URL format is invalid
|
|
144
|
-
"""
|
|
145
|
-
# Skip validation for environment variable templates
|
|
146
|
-
if v.startswith("${") and "}" in v:
|
|
147
|
-
return v
|
|
148
|
-
|
|
149
|
-
# Validate actual URLs
|
|
150
|
-
if not v.startswith("redis://") and not v.startswith("rediss://"):
|
|
151
|
-
raise ValueError(
|
|
152
|
-
"redis_url must start with 'redis://' or 'rediss://' "
|
|
153
|
-
f"(got: {v})"
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
return v
|
|
157
|
-
|
|
158
|
-
def to_django_settings(self) -> dict:
|
|
159
|
-
"""
|
|
160
|
-
Generate Django settings dictionary.
|
|
161
|
-
|
|
162
|
-
Returns:
|
|
163
|
-
Dictionary with DJANGO_CFG_RPC settings
|
|
164
|
-
|
|
165
|
-
Example:
|
|
166
|
-
>>> config = DjangoCfgRPCConfig(enabled=True)
|
|
167
|
-
>>> settings_dict = config.to_django_settings()
|
|
168
|
-
>>> print(settings_dict["DJANGO_CFG_RPC"]["REDIS_URL"])
|
|
169
|
-
"""
|
|
170
|
-
if not self.enabled:
|
|
171
|
-
return {}
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
"DJANGO_CFG_RPC": {
|
|
175
|
-
"ENABLED": self.enabled,
|
|
176
|
-
"REDIS_URL": self.redis_url,
|
|
177
|
-
"REDIS_MAX_CONNECTIONS": self.redis_max_connections,
|
|
178
|
-
"RPC_TIMEOUT": self.rpc_timeout,
|
|
179
|
-
"REQUEST_STREAM": self.request_stream,
|
|
180
|
-
"CONSUMER_GROUP": self.consumer_group,
|
|
181
|
-
"STREAM_MAXLEN": self.stream_maxlen,
|
|
182
|
-
"RESPONSE_KEY_PREFIX": self.response_key_prefix,
|
|
183
|
-
"RESPONSE_KEY_TTL": self.response_key_ttl,
|
|
184
|
-
"LOG_RPC_CALLS": self.log_rpc_calls,
|
|
185
|
-
"LOG_LEVEL": self.log_level,
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
def get_redis_config(self) -> dict:
|
|
190
|
-
"""
|
|
191
|
-
Get Redis connection configuration.
|
|
192
|
-
|
|
193
|
-
Returns:
|
|
194
|
-
Dictionary with Redis connection options
|
|
195
|
-
|
|
196
|
-
Example:
|
|
197
|
-
>>> config = DjangoCfgRPCConfig()
|
|
198
|
-
>>> redis_config = config.get_redis_config()
|
|
199
|
-
>>> import redis
|
|
200
|
-
>>> redis_client = redis.Redis.from_url(**redis_config)
|
|
201
|
-
"""
|
|
202
|
-
config = {
|
|
203
|
-
"url": self.redis_url,
|
|
204
|
-
"max_connections": self.redis_max_connections,
|
|
205
|
-
"decode_responses": False, # We handle JSON ourselves
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if self.socket_keepalive:
|
|
209
|
-
config["socket_keepalive"] = True
|
|
210
|
-
|
|
211
|
-
return config
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
__all__ = ["DjangoCfgRPCConfig"]
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Custom Exceptions for Django-CFG RPC Client.
|
|
3
|
-
|
|
4
|
-
Provides specific exception types for better error handling and debugging.
|
|
5
|
-
Works independently or with django-cfg-rpc models.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from typing import Any, Optional
|
|
9
|
-
|
|
10
|
-
# Try to import RPCError from django-cfg-rpc if available
|
|
11
|
-
try:
|
|
12
|
-
from django_ipc.models.errors import RPCError
|
|
13
|
-
HAS_DJANGO_CFG_RPC = True
|
|
14
|
-
except ImportError:
|
|
15
|
-
# Fallback: simple RPCError for basic error handling
|
|
16
|
-
HAS_DJANGO_CFG_RPC = False
|
|
17
|
-
|
|
18
|
-
class RPCError: # type: ignore
|
|
19
|
-
"""Minimal RPCError fallback when django-cfg-rpc not installed."""
|
|
20
|
-
|
|
21
|
-
def __init__(self, code: str, message: str, retryable: bool = False, retry_after: Optional[int] = None):
|
|
22
|
-
self.code = code
|
|
23
|
-
self.message = message
|
|
24
|
-
self.retryable = retryable
|
|
25
|
-
self.retry_after = retry_after
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class RPCBaseException(Exception):
|
|
29
|
-
"""
|
|
30
|
-
Base exception for all RPC-related errors.
|
|
31
|
-
|
|
32
|
-
All custom RPC exceptions inherit from this class.
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def __init__(self, message: str):
|
|
36
|
-
"""
|
|
37
|
-
Initialize base RPC exception.
|
|
38
|
-
|
|
39
|
-
Args:
|
|
40
|
-
message: Error message
|
|
41
|
-
"""
|
|
42
|
-
self.message = message
|
|
43
|
-
super().__init__(message)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class RPCTimeoutError(RPCBaseException):
|
|
47
|
-
"""
|
|
48
|
-
RPC call timed out waiting for response.
|
|
49
|
-
|
|
50
|
-
Raised when BLPOP timeout is exceeded.
|
|
51
|
-
|
|
52
|
-
Example:
|
|
53
|
-
>>> try:
|
|
54
|
-
... result = rpc.call(method="slow", params=..., timeout=5)
|
|
55
|
-
... except RPCTimeoutError as e:
|
|
56
|
-
... print(f"RPC timeout: {e.message}")
|
|
57
|
-
... print(f"Timeout duration: {e.timeout_seconds}s")
|
|
58
|
-
"""
|
|
59
|
-
|
|
60
|
-
def __init__(self, message: str, method: str, timeout_seconds: int):
|
|
61
|
-
"""
|
|
62
|
-
Initialize timeout error.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
message: Error message
|
|
66
|
-
method: RPC method that timed out
|
|
67
|
-
timeout_seconds: Timeout duration that was exceeded
|
|
68
|
-
"""
|
|
69
|
-
super().__init__(message)
|
|
70
|
-
self.method = method
|
|
71
|
-
self.timeout_seconds = timeout_seconds
|
|
72
|
-
|
|
73
|
-
def __str__(self) -> str:
|
|
74
|
-
"""String representation."""
|
|
75
|
-
return f"RPC timeout on method '{self.method}' after {self.timeout_seconds}s: {self.message}"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
class RPCRemoteError(RPCBaseException):
|
|
79
|
-
"""
|
|
80
|
-
Remote RPC execution failed.
|
|
81
|
-
|
|
82
|
-
Raised when server returns error response.
|
|
83
|
-
|
|
84
|
-
Example:
|
|
85
|
-
>>> try:
|
|
86
|
-
... result = rpc.call(method="...", params=...)
|
|
87
|
-
... except RPCRemoteError as e:
|
|
88
|
-
... print(f"Remote error: {e.error.code}")
|
|
89
|
-
... print(f"Message: {e.error.message}")
|
|
90
|
-
... if e.is_retryable:
|
|
91
|
-
... print(f"Can retry after {e.retry_after}s")
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
def __init__(self, error: Any):
|
|
95
|
-
"""
|
|
96
|
-
Initialize remote error.
|
|
97
|
-
|
|
98
|
-
Args:
|
|
99
|
-
error: Structured RPC error from server (RPCError or dict)
|
|
100
|
-
"""
|
|
101
|
-
# Handle both RPCError objects and dicts
|
|
102
|
-
if isinstance(error, dict):
|
|
103
|
-
message = error.get("message", "Unknown error")
|
|
104
|
-
self.error = RPCError(
|
|
105
|
-
code=error.get("code", "internal_error"),
|
|
106
|
-
message=message,
|
|
107
|
-
retryable=error.get("retryable", False),
|
|
108
|
-
retry_after=error.get("retry_after"),
|
|
109
|
-
)
|
|
110
|
-
else:
|
|
111
|
-
message = error.message if hasattr(error, "message") else str(error)
|
|
112
|
-
self.error = error
|
|
113
|
-
|
|
114
|
-
super().__init__(message)
|
|
115
|
-
|
|
116
|
-
def __str__(self) -> str:
|
|
117
|
-
"""String representation."""
|
|
118
|
-
code = self.error.code if hasattr(self.error, "code") else "unknown"
|
|
119
|
-
return f"RPC remote error [{code}]: {self.message}"
|
|
120
|
-
|
|
121
|
-
@property
|
|
122
|
-
def is_retryable(self) -> bool:
|
|
123
|
-
"""Check if error is retryable."""
|
|
124
|
-
return getattr(self.error, "retryable", False)
|
|
125
|
-
|
|
126
|
-
@property
|
|
127
|
-
def retry_after(self) -> Optional[int]:
|
|
128
|
-
"""Get retry delay in seconds."""
|
|
129
|
-
return getattr(self.error, "retry_after", None)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
class RPCConnectionError(RPCBaseException):
|
|
133
|
-
"""
|
|
134
|
-
Failed to connect to Redis.
|
|
135
|
-
|
|
136
|
-
Raised when Redis connection fails.
|
|
137
|
-
|
|
138
|
-
Example:
|
|
139
|
-
>>> try:
|
|
140
|
-
... rpc = DjangoCfgRPCClient(redis_url="redis://invalid:6379")
|
|
141
|
-
... except RPCConnectionError as e:
|
|
142
|
-
... print(f"Connection failed: {e.message}")
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
def __init__(self, message: str, redis_url: Optional[str] = None):
|
|
146
|
-
"""
|
|
147
|
-
Initialize connection error.
|
|
148
|
-
|
|
149
|
-
Args:
|
|
150
|
-
message: Error message
|
|
151
|
-
redis_url: Redis URL that failed to connect
|
|
152
|
-
"""
|
|
153
|
-
super().__init__(message)
|
|
154
|
-
self.redis_url = redis_url
|
|
155
|
-
|
|
156
|
-
def __str__(self) -> str:
|
|
157
|
-
"""String representation."""
|
|
158
|
-
if self.redis_url:
|
|
159
|
-
return f"RPC connection error to {self.redis_url}: {self.message}"
|
|
160
|
-
return f"RPC connection error: {self.message}"
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
class RPCConfigurationError(RPCBaseException):
|
|
164
|
-
"""
|
|
165
|
-
RPC configuration error.
|
|
166
|
-
|
|
167
|
-
Raised when RPC client is misconfigured.
|
|
168
|
-
|
|
169
|
-
Example:
|
|
170
|
-
>>> try:
|
|
171
|
-
... rpc = get_rpc_client() # No config in settings
|
|
172
|
-
... except RPCConfigurationError as e:
|
|
173
|
-
... print(f"Configuration error: {e.message}")
|
|
174
|
-
"""
|
|
175
|
-
|
|
176
|
-
def __init__(self, message: str, config_key: Optional[str] = None):
|
|
177
|
-
"""
|
|
178
|
-
Initialize configuration error.
|
|
179
|
-
|
|
180
|
-
Args:
|
|
181
|
-
message: Error message
|
|
182
|
-
config_key: Configuration key that is missing/invalid
|
|
183
|
-
"""
|
|
184
|
-
super().__init__(message)
|
|
185
|
-
self.config_key = config_key
|
|
186
|
-
|
|
187
|
-
def __str__(self) -> str:
|
|
188
|
-
"""String representation."""
|
|
189
|
-
if self.config_key:
|
|
190
|
-
return f"RPC configuration error (key: {self.config_key}): {self.message}"
|
|
191
|
-
return f"RPC configuration error: {self.message}"
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
__all__ = [
|
|
195
|
-
"RPCBaseException",
|
|
196
|
-
"RPCTimeoutError",
|
|
197
|
-
"RPCRemoteError",
|
|
198
|
-
"RPCConnectionError",
|
|
199
|
-
"RPCConfigurationError",
|
|
200
|
-
"HAS_DJANGO_CFG_RPC",
|
|
201
|
-
]
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
RPC Logging helper for tracking RPC calls.
|
|
3
|
-
|
|
4
|
-
Provides async-safe logging of RPC calls to database.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import time
|
|
8
|
-
from typing import Any, Dict, Optional
|
|
9
|
-
from django.conf import settings
|
|
10
|
-
from django_cfg.modules.django_logging import get_logger
|
|
11
|
-
|
|
12
|
-
logger = get_logger("ipc.rpc")
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class RPCLogger:
|
|
16
|
-
"""
|
|
17
|
-
Helper class for logging RPC calls to database.
|
|
18
|
-
|
|
19
|
-
Usage:
|
|
20
|
-
>>> log_entry = RPCLogger.create_log(
|
|
21
|
-
... correlation_id="abc123",
|
|
22
|
-
... method="send_notification",
|
|
23
|
-
... params={"user_id": "123"},
|
|
24
|
-
... user=request.user if authenticated else None
|
|
25
|
-
... )
|
|
26
|
-
>>> # ... make RPC call ...
|
|
27
|
-
>>> RPCLogger.mark_success(log_entry, response_data, duration_ms=150)
|
|
28
|
-
"""
|
|
29
|
-
|
|
30
|
-
@staticmethod
|
|
31
|
-
def is_logging_enabled() -> bool:
|
|
32
|
-
"""
|
|
33
|
-
Check if RPC logging is enabled in settings.
|
|
34
|
-
|
|
35
|
-
Returns:
|
|
36
|
-
bool: True if logging is enabled
|
|
37
|
-
"""
|
|
38
|
-
# Check if IPC app is installed
|
|
39
|
-
if 'django_cfg.apps.ipc' not in settings.INSTALLED_APPS:
|
|
40
|
-
return False
|
|
41
|
-
|
|
42
|
-
# Check if logging is explicitly disabled
|
|
43
|
-
if hasattr(settings, 'DJANGO_CFG_RPC'):
|
|
44
|
-
return settings.DJANGO_CFG_RPC.get('ENABLE_LOGGING', True)
|
|
45
|
-
|
|
46
|
-
return True
|
|
47
|
-
|
|
48
|
-
@staticmethod
|
|
49
|
-
def create_log(
|
|
50
|
-
correlation_id: str,
|
|
51
|
-
method: str,
|
|
52
|
-
params: Dict[str, Any],
|
|
53
|
-
user: Optional[Any] = None,
|
|
54
|
-
caller_ip: Optional[str] = None,
|
|
55
|
-
user_agent: Optional[str] = None,
|
|
56
|
-
):
|
|
57
|
-
"""
|
|
58
|
-
Create RPC log entry in pending state.
|
|
59
|
-
|
|
60
|
-
Args:
|
|
61
|
-
correlation_id: UUID correlation ID from RPC request
|
|
62
|
-
method: RPC method name
|
|
63
|
-
params: Parameters sent to RPC method
|
|
64
|
-
user: Django User instance (optional)
|
|
65
|
-
caller_ip: IP address of caller (optional)
|
|
66
|
-
user_agent: User agent string (optional)
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
RPCLog instance or None if logging disabled
|
|
70
|
-
"""
|
|
71
|
-
if not RPCLogger.is_logging_enabled():
|
|
72
|
-
return None
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
from ..models import RPCLog
|
|
76
|
-
|
|
77
|
-
log_entry = RPCLog.objects.create(
|
|
78
|
-
correlation_id=correlation_id,
|
|
79
|
-
method=method,
|
|
80
|
-
params=params,
|
|
81
|
-
user=user,
|
|
82
|
-
caller_ip=caller_ip,
|
|
83
|
-
user_agent=user_agent,
|
|
84
|
-
status=RPCLog.StatusChoices.PENDING
|
|
85
|
-
)
|
|
86
|
-
return log_entry
|
|
87
|
-
|
|
88
|
-
except Exception as e:
|
|
89
|
-
logger.error(f"Failed to create RPC log: {e}", exc_info=True)
|
|
90
|
-
return None
|
|
91
|
-
|
|
92
|
-
@staticmethod
|
|
93
|
-
def mark_success(log_entry, response_data: Dict[str, Any], duration_ms: Optional[int] = None):
|
|
94
|
-
"""
|
|
95
|
-
Mark RPC log as successful.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
log_entry: RPCLog instance
|
|
99
|
-
response_data: Response data from RPC call
|
|
100
|
-
duration_ms: Duration in milliseconds (optional)
|
|
101
|
-
"""
|
|
102
|
-
if log_entry is None:
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
try:
|
|
106
|
-
log_entry.mark_success(response_data, duration_ms)
|
|
107
|
-
except Exception as e:
|
|
108
|
-
logger.error(f"Failed to mark RPC log as success: {e}", exc_info=True)
|
|
109
|
-
|
|
110
|
-
@staticmethod
|
|
111
|
-
def mark_failed(log_entry, error_code: str, error_message: str, duration_ms: Optional[int] = None):
|
|
112
|
-
"""
|
|
113
|
-
Mark RPC log as failed.
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
log_entry: RPCLog instance
|
|
117
|
-
error_code: Error code
|
|
118
|
-
error_message: Error message
|
|
119
|
-
duration_ms: Duration in milliseconds (optional)
|
|
120
|
-
"""
|
|
121
|
-
if log_entry is None:
|
|
122
|
-
return
|
|
123
|
-
|
|
124
|
-
try:
|
|
125
|
-
log_entry.mark_failed(error_code, error_message, duration_ms)
|
|
126
|
-
except Exception as e:
|
|
127
|
-
logger.error(f"Failed to mark RPC log as failed: {e}", exc_info=True)
|
|
128
|
-
|
|
129
|
-
@staticmethod
|
|
130
|
-
def mark_timeout(log_entry, timeout_seconds: int):
|
|
131
|
-
"""
|
|
132
|
-
Mark RPC log as timed out.
|
|
133
|
-
|
|
134
|
-
Args:
|
|
135
|
-
log_entry: RPCLog instance
|
|
136
|
-
timeout_seconds: Timeout duration in seconds
|
|
137
|
-
"""
|
|
138
|
-
if log_entry is None:
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
try:
|
|
142
|
-
log_entry.mark_timeout(timeout_seconds)
|
|
143
|
-
except Exception as e:
|
|
144
|
-
logger.error(f"Failed to mark RPC log as timeout: {e}", exc_info=True)
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
class RPCLogContext:
|
|
148
|
-
"""
|
|
149
|
-
Context manager for automatically logging RPC calls.
|
|
150
|
-
|
|
151
|
-
Usage:
|
|
152
|
-
>>> with RPCLogContext(
|
|
153
|
-
... correlation_id="abc123",
|
|
154
|
-
... method="send_notification",
|
|
155
|
-
... params={"user_id": "123"}
|
|
156
|
-
... ) as log_ctx:
|
|
157
|
-
... result = rpc.call(...)
|
|
158
|
-
... log_ctx.set_response(result)
|
|
159
|
-
"""
|
|
160
|
-
|
|
161
|
-
def __init__(
|
|
162
|
-
self,
|
|
163
|
-
correlation_id: str,
|
|
164
|
-
method: str,
|
|
165
|
-
params: Dict[str, Any],
|
|
166
|
-
user: Optional[Any] = None,
|
|
167
|
-
caller_ip: Optional[str] = None,
|
|
168
|
-
user_agent: Optional[str] = None,
|
|
169
|
-
):
|
|
170
|
-
"""
|
|
171
|
-
Initialize RPC log context.
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
correlation_id: UUID correlation ID
|
|
175
|
-
method: RPC method name
|
|
176
|
-
params: Parameters for RPC call
|
|
177
|
-
user: Django User instance (optional)
|
|
178
|
-
caller_ip: IP address (optional)
|
|
179
|
-
user_agent: User agent string (optional)
|
|
180
|
-
"""
|
|
181
|
-
self.correlation_id = correlation_id
|
|
182
|
-
self.method = method
|
|
183
|
-
self.params = params
|
|
184
|
-
self.user = user
|
|
185
|
-
self.caller_ip = caller_ip
|
|
186
|
-
self.user_agent = user_agent
|
|
187
|
-
self.log_entry = None
|
|
188
|
-
self.start_time = None
|
|
189
|
-
self.response = None
|
|
190
|
-
|
|
191
|
-
def __enter__(self):
|
|
192
|
-
"""Start timing and create log entry."""
|
|
193
|
-
self.start_time = time.time()
|
|
194
|
-
self.log_entry = RPCLogger.create_log(
|
|
195
|
-
correlation_id=self.correlation_id,
|
|
196
|
-
method=self.method,
|
|
197
|
-
params=self.params,
|
|
198
|
-
user=self.user,
|
|
199
|
-
caller_ip=self.caller_ip,
|
|
200
|
-
user_agent=self.user_agent,
|
|
201
|
-
)
|
|
202
|
-
return self
|
|
203
|
-
|
|
204
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
205
|
-
"""Mark log as success or failed based on exception."""
|
|
206
|
-
if self.log_entry is None:
|
|
207
|
-
return False
|
|
208
|
-
|
|
209
|
-
duration_ms = int((time.time() - self.start_time) * 1000) if self.start_time else None
|
|
210
|
-
|
|
211
|
-
if exc_type is None:
|
|
212
|
-
# Success - use response if set
|
|
213
|
-
RPCLogger.mark_success(self.log_entry, self.response or {}, duration_ms)
|
|
214
|
-
else:
|
|
215
|
-
# Failed - extract error details
|
|
216
|
-
error_code = exc_type.__name__ if exc_type else 'unknown'
|
|
217
|
-
error_message = str(exc_val) if exc_val else 'Unknown error'
|
|
218
|
-
|
|
219
|
-
# Check if it's a timeout
|
|
220
|
-
if 'timeout' in error_code.lower():
|
|
221
|
-
timeout_seconds = duration_ms // 1000 if duration_ms else 30
|
|
222
|
-
RPCLogger.mark_timeout(self.log_entry, timeout_seconds)
|
|
223
|
-
else:
|
|
224
|
-
RPCLogger.mark_failed(self.log_entry, error_code, error_message, duration_ms)
|
|
225
|
-
|
|
226
|
-
# Don't suppress exceptions
|
|
227
|
-
return False
|
|
228
|
-
|
|
229
|
-
def set_response(self, response: Dict[str, Any]):
|
|
230
|
-
"""
|
|
231
|
-
Set response data for successful call.
|
|
232
|
-
|
|
233
|
-
Args:
|
|
234
|
-
response: Response data from RPC call
|
|
235
|
-
"""
|
|
236
|
-
self.response = response
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
__all__ = ['RPCLogger', 'RPCLogContext']
|