django-cfg 1.4.62__py3-none-any.whl → 1.4.64__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.

Files changed (181) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/services/otp_service.py +3 -14
  3. django_cfg/apps/centrifugo/__init__.py +57 -0
  4. django_cfg/apps/centrifugo/admin/__init__.py +13 -0
  5. django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
  6. django_cfg/apps/centrifugo/admin/config.py +82 -0
  7. django_cfg/apps/centrifugo/apps.py +31 -0
  8. django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
  9. django_cfg/apps/centrifugo/codegen/README.md +242 -0
  10. django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
  11. django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
  12. django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
  13. django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
  14. django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
  15. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
  16. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
  17. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
  18. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
  19. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
  20. django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
  21. django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
  22. django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
  23. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
  24. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
  25. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
  26. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
  27. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
  28. django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
  29. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
  30. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
  31. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
  32. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
  33. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
  34. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
  35. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
  36. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
  37. django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
  38. django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
  39. django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
  40. django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
  41. django_cfg/apps/centrifugo/decorators.py +137 -0
  42. django_cfg/apps/centrifugo/management/__init__.py +1 -0
  43. django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
  44. django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
  45. django_cfg/apps/centrifugo/managers/__init__.py +12 -0
  46. django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
  47. django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
  48. django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
  49. django_cfg/apps/centrifugo/models/__init__.py +11 -0
  50. django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
  51. django_cfg/apps/centrifugo/registry.py +106 -0
  52. django_cfg/apps/centrifugo/router.py +125 -0
  53. django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
  54. django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
  55. django_cfg/apps/centrifugo/serializers/channels.py +26 -0
  56. django_cfg/apps/centrifugo/serializers/health.py +17 -0
  57. django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
  58. django_cfg/apps/centrifugo/serializers/stats.py +21 -0
  59. django_cfg/apps/centrifugo/services/__init__.py +12 -0
  60. django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
  61. django_cfg/apps/centrifugo/services/client/client.py +582 -0
  62. django_cfg/apps/centrifugo/services/client/config.py +236 -0
  63. django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
  64. django_cfg/apps/centrifugo/services/config_helper.py +63 -0
  65. django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
  66. django_cfg/apps/centrifugo/services/logging.py +677 -0
  67. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
  68. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
  69. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
  70. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
  71. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
  72. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
  73. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
  77. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
  78. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
  79. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
  80. django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
  81. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
  82. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
  83. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
  84. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
  85. django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
  86. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
  87. django_cfg/apps/centrifugo/urls.py +31 -0
  88. django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
  89. django_cfg/apps/centrifugo/views/__init__.py +15 -0
  90. django_cfg/apps/centrifugo/views/admin_api.py +380 -0
  91. django_cfg/apps/centrifugo/views/dashboard.py +15 -0
  92. django_cfg/apps/centrifugo/views/monitoring.py +286 -0
  93. django_cfg/apps/centrifugo/views/testing_api.py +422 -0
  94. django_cfg/apps/support/utils/support_email_service.py +5 -18
  95. django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
  96. django_cfg/apps/urls.py +5 -5
  97. django_cfg/core/base/config_model.py +4 -44
  98. django_cfg/core/builders/apps_builder.py +2 -2
  99. django_cfg/core/generation/integration_generators/third_party.py +8 -8
  100. django_cfg/core/utils/__init__.py +5 -0
  101. django_cfg/core/utils/url_helpers.py +73 -0
  102. django_cfg/modules/base.py +7 -7
  103. django_cfg/modules/django_client/core/__init__.py +2 -1
  104. django_cfg/modules/django_client/core/config/config.py +8 -0
  105. django_cfg/modules/django_client/core/generator/__init__.py +42 -2
  106. django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
  107. django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
  108. django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
  109. django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
  110. django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
  111. django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
  112. django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
  113. django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
  114. django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
  115. django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
  116. django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
  117. django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
  118. django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
  119. django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
  120. django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
  121. django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
  122. django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
  123. django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
  124. django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
  125. django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
  126. django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
  127. django_cfg/modules/django_client/system/schema_parser.py +5 -1
  128. django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
  129. django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
  130. django_cfg/modules/django_unfold/dashboard.py +25 -19
  131. django_cfg/pyproject.toml +1 -1
  132. django_cfg/registry/core.py +2 -0
  133. django_cfg/registry/modules.py +2 -2
  134. django_cfg/static/js/api/centrifugo/client.mjs +164 -0
  135. django_cfg/static/js/api/centrifugo/index.mjs +13 -0
  136. django_cfg/static/js/api/index.mjs +5 -5
  137. django_cfg/static/js/api/types.mjs +89 -26
  138. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/METADATA +1 -1
  139. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/RECORD +142 -70
  140. django_cfg/apps/ipc/README.md +0 -346
  141. django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
  142. django_cfg/apps/ipc/TESTING.md +0 -539
  143. django_cfg/apps/ipc/__init__.py +0 -60
  144. django_cfg/apps/ipc/admin.py +0 -232
  145. django_cfg/apps/ipc/apps.py +0 -98
  146. django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
  147. django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
  148. django_cfg/apps/ipc/migrations/__init__.py +0 -0
  149. django_cfg/apps/ipc/models.py +0 -229
  150. django_cfg/apps/ipc/serializers/__init__.py +0 -29
  151. django_cfg/apps/ipc/serializers/serializers.py +0 -343
  152. django_cfg/apps/ipc/services/__init__.py +0 -7
  153. django_cfg/apps/ipc/services/client/__init__.py +0 -23
  154. django_cfg/apps/ipc/services/client/client.py +0 -621
  155. django_cfg/apps/ipc/services/client/config.py +0 -214
  156. django_cfg/apps/ipc/services/client/exceptions.py +0 -201
  157. django_cfg/apps/ipc/services/logging.py +0 -239
  158. django_cfg/apps/ipc/services/monitor.py +0 -466
  159. django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
  160. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
  161. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
  162. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
  163. django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
  164. django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
  165. django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
  166. django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
  167. django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
  168. django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
  169. django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
  170. django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
  171. django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
  172. django_cfg/apps/ipc/urls.py +0 -23
  173. django_cfg/apps/ipc/views/__init__.py +0 -13
  174. django_cfg/apps/ipc/views/dashboard.py +0 -15
  175. django_cfg/apps/ipc/views/monitoring.py +0 -251
  176. django_cfg/apps/ipc/views/testing.py +0 -285
  177. django_cfg/static/js/api/ipc/client.mjs +0 -114
  178. django_cfg/static/js/api/ipc/index.mjs +0 -13
  179. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.62.dist-info → django_cfg-1.4.64.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']