django-cfg 1.4.120__py3-none-any.whl → 1.5.1__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 (80) hide show
  1. django_cfg/__init__.py +8 -4
  2. django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
  3. django_cfg/apps/grpc/__init__.py +9 -0
  4. django_cfg/apps/grpc/admin/__init__.py +11 -0
  5. django_cfg/apps/grpc/admin/config.py +89 -0
  6. django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
  7. django_cfg/apps/grpc/apps.py +28 -0
  8. django_cfg/apps/grpc/auth/__init__.py +9 -0
  9. django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
  10. django_cfg/apps/grpc/interceptors/__init__.py +19 -0
  11. django_cfg/apps/grpc/interceptors/errors.py +241 -0
  12. django_cfg/apps/grpc/interceptors/logging.py +270 -0
  13. django_cfg/apps/grpc/interceptors/metrics.py +306 -0
  14. django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
  15. django_cfg/apps/grpc/management/__init__.py +1 -0
  16. django_cfg/apps/grpc/management/commands/__init__.py +0 -0
  17. django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
  18. django_cfg/apps/grpc/managers/__init__.py +10 -0
  19. django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
  20. django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
  21. django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
  22. django_cfg/apps/grpc/migrations/__init__.py +0 -0
  23. django_cfg/apps/grpc/models/__init__.py +9 -0
  24. django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
  25. django_cfg/apps/grpc/serializers/__init__.py +23 -0
  26. django_cfg/apps/grpc/serializers/health.py +18 -0
  27. django_cfg/apps/grpc/serializers/requests.py +18 -0
  28. django_cfg/apps/grpc/serializers/services.py +50 -0
  29. django_cfg/apps/grpc/serializers/stats.py +22 -0
  30. django_cfg/apps/grpc/services/__init__.py +16 -0
  31. django_cfg/apps/grpc/services/base.py +375 -0
  32. django_cfg/apps/grpc/services/discovery.py +415 -0
  33. django_cfg/apps/grpc/urls.py +23 -0
  34. django_cfg/apps/grpc/utils/__init__.py +13 -0
  35. django_cfg/apps/grpc/utils/proto_gen.py +423 -0
  36. django_cfg/apps/grpc/views/__init__.py +9 -0
  37. django_cfg/apps/grpc/views/monitoring.py +497 -0
  38. django_cfg/apps/maintenance/admin/api_key_admin.py +7 -8
  39. django_cfg/apps/maintenance/admin/site_admin.py +5 -4
  40. django_cfg/apps/payments/admin/balance_admin.py +26 -36
  41. django_cfg/apps/payments/admin/payment_admin.py +65 -85
  42. django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
  43. django_cfg/apps/tasks/admin/task_log.py +20 -47
  44. django_cfg/apps/urls.py +7 -1
  45. django_cfg/config.py +106 -0
  46. django_cfg/core/base/config_model.py +6 -0
  47. django_cfg/core/builders/apps_builder.py +3 -0
  48. django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
  49. django_cfg/core/generation/orchestrator.py +10 -0
  50. django_cfg/models/api/grpc/__init__.py +59 -0
  51. django_cfg/models/api/grpc/config.py +364 -0
  52. django_cfg/modules/base.py +15 -0
  53. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  54. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  55. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  56. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  57. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  58. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  59. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  60. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  61. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  62. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  63. django_cfg/modules/django_admin/utils/html/composition.py +198 -0
  64. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  65. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  66. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  67. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  68. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  69. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  70. django_cfg/modules/django_unfold/navigation.py +28 -0
  71. django_cfg/pyproject.toml +3 -5
  72. django_cfg/registry/modules.py +6 -0
  73. {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/METADATA +10 -1
  74. {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/RECORD +79 -30
  75. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  76. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  77. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  78. {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/WHEEL +0 -0
  79. {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/entry_points.txt +0 -0
  80. {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,375 @@
1
+ """
2
+ Base service classes for gRPC.
3
+
4
+ Provides convenient base classes with Django integration.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ from typing import Any, Optional
11
+
12
+ import grpc
13
+ from django.contrib.auth import get_user_model
14
+ from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ User = get_user_model()
19
+
20
+
21
+ class BaseService:
22
+ """
23
+ Base class for gRPC services with Django integration.
24
+
25
+ Features:
26
+ - Easy access to authenticated user
27
+ - Permission checking helpers
28
+ - Error handling utilities
29
+ - Context helpers
30
+
31
+ Example:
32
+ ```python
33
+ from django_cfg.apps.grpc.services.base import BaseService
34
+ from myapp_pb2 import UserResponse
35
+ from myapp_pb2_grpc import UserServiceServicer
36
+
37
+ class UserService(BaseService, UserServiceServicer):
38
+ def GetUser(self, request, context):
39
+ # Get authenticated user
40
+ user = self.get_user(context)
41
+
42
+ # Check permission
43
+ if not user.has_perm('myapp.view_user'):
44
+ self.abort_permission_denied(context, "No permission to view users")
45
+
46
+ # Your logic
47
+ return UserResponse(id=user.id, username=user.username)
48
+ ```
49
+ """
50
+
51
+ def get_user(self, context: grpc.ServicerContext) -> Optional[User]:
52
+ """
53
+ Get authenticated user from context.
54
+
55
+ Args:
56
+ context: gRPC servicer context
57
+
58
+ Returns:
59
+ Authenticated User instance or None
60
+
61
+ Example:
62
+ >>> user = self.get_user(context)
63
+ >>> if user:
64
+ ... print(f"User: {user.username}")
65
+ """
66
+ return getattr(context, "user", None)
67
+
68
+ def require_user(self, context: grpc.ServicerContext) -> User:
69
+ """
70
+ Get authenticated user or abort if not authenticated.
71
+
72
+ Args:
73
+ context: gRPC servicer context
74
+
75
+ Returns:
76
+ Authenticated User instance
77
+
78
+ Raises:
79
+ grpc.RpcError: UNAUTHENTICATED if user not authenticated
80
+
81
+ Example:
82
+ >>> user = self.require_user(context)
83
+ >>> # user is guaranteed to be authenticated here
84
+ """
85
+ user = self.get_user(context)
86
+ if not user:
87
+ self.abort_unauthenticated(context, "Authentication required")
88
+ return user
89
+
90
+ def check_permission(
91
+ self,
92
+ context: grpc.ServicerContext,
93
+ permission: str,
94
+ obj: Any = None,
95
+ ) -> bool:
96
+ """
97
+ Check if user has permission.
98
+
99
+ Args:
100
+ context: gRPC servicer context
101
+ permission: Permission string (e.g., 'myapp.view_user')
102
+ obj: Object to check permission for (optional)
103
+
104
+ Returns:
105
+ True if user has permission
106
+
107
+ Example:
108
+ >>> if not self.check_permission(context, 'myapp.view_user'):
109
+ ... self.abort_permission_denied(context)
110
+ """
111
+ user = self.get_user(context)
112
+ if not user:
113
+ return False
114
+
115
+ return user.has_perm(permission, obj)
116
+
117
+ def require_permission(
118
+ self,
119
+ context: grpc.ServicerContext,
120
+ permission: str,
121
+ obj: Any = None,
122
+ message: str = None,
123
+ ):
124
+ """
125
+ Require user to have permission or abort.
126
+
127
+ Args:
128
+ context: gRPC servicer context
129
+ permission: Permission string (e.g., 'myapp.view_user')
130
+ obj: Object to check permission for (optional)
131
+ message: Custom error message (optional)
132
+
133
+ Raises:
134
+ grpc.RpcError: PERMISSION_DENIED if permission check fails
135
+
136
+ Example:
137
+ >>> self.require_permission(context, 'myapp.view_user')
138
+ >>> # user has permission, continue...
139
+ """
140
+ if not self.check_permission(context, permission, obj):
141
+ if message is None:
142
+ message = f"Permission '{permission}' required"
143
+ self.abort_permission_denied(context, message)
144
+
145
+ def check_staff(self, context: grpc.ServicerContext) -> bool:
146
+ """
147
+ Check if user is staff.
148
+
149
+ Args:
150
+ context: gRPC servicer context
151
+
152
+ Returns:
153
+ True if user is staff
154
+
155
+ Example:
156
+ >>> if self.check_staff(context):
157
+ ... # Staff-only logic
158
+ """
159
+ user = self.get_user(context)
160
+ return user.is_staff if user else False
161
+
162
+ def require_staff(self, context: grpc.ServicerContext, message: str = None):
163
+ """
164
+ Require user to be staff or abort.
165
+
166
+ Args:
167
+ context: gRPC servicer context
168
+ message: Custom error message (optional)
169
+
170
+ Raises:
171
+ grpc.RpcError: PERMISSION_DENIED if user is not staff
172
+
173
+ Example:
174
+ >>> self.require_staff(context)
175
+ >>> # user is staff, continue...
176
+ """
177
+ if not self.check_staff(context):
178
+ if message is None:
179
+ message = "Staff access required"
180
+ self.abort_permission_denied(context, message)
181
+
182
+ def check_superuser(self, context: grpc.ServicerContext) -> bool:
183
+ """
184
+ Check if user is superuser.
185
+
186
+ Args:
187
+ context: gRPC servicer context
188
+
189
+ Returns:
190
+ True if user is superuser
191
+
192
+ Example:
193
+ >>> if self.check_superuser(context):
194
+ ... # Superuser-only logic
195
+ """
196
+ user = self.get_user(context)
197
+ return user.is_superuser if user else False
198
+
199
+ def require_superuser(self, context: grpc.ServicerContext, message: str = None):
200
+ """
201
+ Require user to be superuser or abort.
202
+
203
+ Args:
204
+ context: gRPC servicer context
205
+ message: Custom error message (optional)
206
+
207
+ Raises:
208
+ grpc.RpcError: PERMISSION_DENIED if user is not superuser
209
+
210
+ Example:
211
+ >>> self.require_superuser(context)
212
+ >>> # user is superuser, continue...
213
+ """
214
+ if not self.check_superuser(context):
215
+ if message is None:
216
+ message = "Superuser access required"
217
+ self.abort_permission_denied(context, message)
218
+
219
+ # Error handling helpers
220
+
221
+ def abort(
222
+ self,
223
+ context: grpc.ServicerContext,
224
+ code: grpc.StatusCode,
225
+ message: str,
226
+ ):
227
+ """
228
+ Abort request with error.
229
+
230
+ Args:
231
+ context: gRPC servicer context
232
+ code: gRPC status code
233
+ message: Error message
234
+
235
+ Example:
236
+ >>> self.abort(context, grpc.StatusCode.INVALID_ARGUMENT, "Invalid user ID")
237
+ """
238
+ logger.warning(f"Aborting request: {code.name} - {message}")
239
+ context.abort(code, message)
240
+
241
+ def abort_invalid_argument(self, context: grpc.ServicerContext, message: str = "Invalid argument"):
242
+ """Abort with INVALID_ARGUMENT status."""
243
+ self.abort(context, grpc.StatusCode.INVALID_ARGUMENT, message)
244
+
245
+ def abort_not_found(self, context: grpc.ServicerContext, message: str = "Not found"):
246
+ """Abort with NOT_FOUND status."""
247
+ self.abort(context, grpc.StatusCode.NOT_FOUND, message)
248
+
249
+ def abort_permission_denied(self, context: grpc.ServicerContext, message: str = "Permission denied"):
250
+ """Abort with PERMISSION_DENIED status."""
251
+ self.abort(context, grpc.StatusCode.PERMISSION_DENIED, message)
252
+
253
+ def abort_unauthenticated(self, context: grpc.ServicerContext, message: str = "Unauthenticated"):
254
+ """Abort with UNAUTHENTICATED status."""
255
+ self.abort(context, grpc.StatusCode.UNAUTHENTICATED, message)
256
+
257
+ def abort_unimplemented(self, context: grpc.ServicerContext, message: str = "Not implemented"):
258
+ """Abort with UNIMPLEMENTED status."""
259
+ self.abort(context, grpc.StatusCode.UNIMPLEMENTED, message)
260
+
261
+ def abort_internal(self, context: grpc.ServicerContext, message: str = "Internal server error"):
262
+ """Abort with INTERNAL status."""
263
+ self.abort(context, grpc.StatusCode.INTERNAL, message)
264
+
265
+ # Context helpers
266
+
267
+ def get_metadata(self, context: grpc.ServicerContext, key: str) -> Optional[str]:
268
+ """
269
+ Get metadata value from context.
270
+
271
+ Args:
272
+ context: gRPC servicer context
273
+ key: Metadata key
274
+
275
+ Returns:
276
+ Metadata value or None
277
+
278
+ Example:
279
+ >>> user_agent = self.get_metadata(context, 'user-agent')
280
+ """
281
+ metadata = dict(context.invocation_metadata())
282
+ return metadata.get(key.lower())
283
+
284
+ def set_metadata(self, context: grpc.ServicerContext, key: str, value: str):
285
+ """
286
+ Set response metadata.
287
+
288
+ Args:
289
+ context: gRPC servicer context
290
+ key: Metadata key
291
+ value: Metadata value
292
+
293
+ Example:
294
+ >>> self.set_metadata(context, 'x-request-id', request_id)
295
+ """
296
+ context.set_trailing_metadata([(key, value)])
297
+
298
+ def get_peer(self, context: grpc.ServicerContext) -> str:
299
+ """
300
+ Get peer information.
301
+
302
+ Args:
303
+ context: gRPC servicer context
304
+
305
+ Returns:
306
+ Peer string
307
+
308
+ Example:
309
+ >>> peer = self.get_peer(context)
310
+ >>> print(f"Request from: {peer}")
311
+ """
312
+ return context.peer()
313
+
314
+
315
+ class ReadOnlyService(BaseService):
316
+ """
317
+ Base class for read-only gRPC services.
318
+
319
+ Automatically aborts on write operations.
320
+
321
+ Example:
322
+ ```python
323
+ class UserReadService(ReadOnlyService, UserServiceServicer):
324
+ def GetUser(self, request, context):
325
+ # Allowed
326
+ return UserResponse(...)
327
+
328
+ def CreateUser(self, request, context):
329
+ # Will abort with PERMISSION_DENIED
330
+ self.abort_readonly(context)
331
+ ```
332
+ """
333
+
334
+ def abort_readonly(self, context: grpc.ServicerContext, message: str = "Read-only service"):
335
+ """Abort with PERMISSION_DENIED for write operations."""
336
+ self.abort_permission_denied(context, message)
337
+
338
+
339
+ class AuthRequiredService(BaseService):
340
+ """
341
+ Base class for services that require authentication.
342
+
343
+ Automatically checks for authenticated user in all methods.
344
+
345
+ Example:
346
+ ```python
347
+ class UserService(AuthRequiredService, UserServiceServicer):
348
+ def GetUser(self, request, context):
349
+ # User is automatically required
350
+ user = self.user # Guaranteed to be authenticated
351
+ return UserResponse(...)
352
+ ```
353
+ """
354
+
355
+ def __init__(self):
356
+ super().__init__()
357
+ self._user = None
358
+
359
+ def _ensure_auth(self, context: grpc.ServicerContext):
360
+ """Ensure user is authenticated."""
361
+ self._user = self.require_user(context)
362
+
363
+ @property
364
+ def user(self) -> User:
365
+ """Get authenticated user (must call _ensure_auth first)."""
366
+ if self._user is None:
367
+ raise RuntimeError("User not set - did you call _ensure_auth?")
368
+ return self._user
369
+
370
+
371
+ __all__ = [
372
+ "BaseService",
373
+ "ReadOnlyService",
374
+ "AuthRequiredService",
375
+ ]