django-cfg 1.4.119__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 (84) 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/__init__.py +2 -0
  54. django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
  55. django_cfg/modules/django_admin/config/__init__.py +2 -0
  56. django_cfg/modules/django_admin/config/field_config.py +24 -0
  57. django_cfg/modules/django_admin/utils/__init__.py +41 -3
  58. django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
  59. django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
  60. django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
  61. django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
  62. django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
  63. django_cfg/modules/django_admin/utils/html/badges.py +47 -0
  64. django_cfg/modules/django_admin/utils/html/base.py +167 -0
  65. django_cfg/modules/django_admin/utils/html/code.py +87 -0
  66. django_cfg/modules/django_admin/utils/html/composition.py +198 -0
  67. django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
  68. django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
  69. django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
  70. django_cfg/modules/django_admin/utils/html/progress.py +127 -0
  71. django_cfg/modules/django_admin/utils/html_builder.py +55 -408
  72. django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
  73. django_cfg/modules/django_admin/widgets/registry.py +42 -0
  74. django_cfg/modules/django_unfold/navigation.py +28 -0
  75. django_cfg/pyproject.toml +3 -5
  76. django_cfg/registry/modules.py +6 -0
  77. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/METADATA +10 -1
  78. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/RECORD +83 -34
  79. django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
  80. /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
  81. /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
  82. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/WHEEL +0 -0
  83. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/entry_points.txt +0 -0
  84. {django_cfg-1.4.119.dist-info → django_cfg-1.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,415 @@
1
+ """
2
+ Service Discovery for gRPC.
3
+
4
+ Automatically discovers and registers gRPC services from Django apps.
5
+ """
6
+
7
+ import importlib
8
+ import logging
9
+ from typing import Any, Dict, List, Optional, Tuple
10
+
11
+ from django.apps import apps
12
+ from django.conf import settings
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ServiceDiscovery:
18
+ """
19
+ Discovers gRPC services from Django applications.
20
+
21
+ Features:
22
+ - Auto-discovers services from enabled apps
23
+ - Supports custom service registration
24
+ - Configurable discovery paths
25
+ - Lazy loading support
26
+ - Integration with django-grpc-framework
27
+
28
+ Example:
29
+ ```python
30
+ from django_cfg.apps.grpc.services.discovery import ServiceDiscovery
31
+
32
+ discovery = ServiceDiscovery()
33
+ services = discovery.discover_services()
34
+
35
+ for service_class, add_to_server_func in services:
36
+ add_to_server_func(service_class.as_servicer(), server)
37
+ ```
38
+ """
39
+
40
+ def __init__(self):
41
+ """Initialize service discovery."""
42
+ self.grpc_config = getattr(settings, "GRPC_FRAMEWORK", {})
43
+ self.auto_register = self.grpc_config.get("AUTO_REGISTER_APPS", True)
44
+ self.enabled_apps = self.grpc_config.get("ENABLED_APPS", [])
45
+ self.custom_services = self.grpc_config.get("CUSTOM_SERVICES", {})
46
+
47
+ # Common module names where services might be defined
48
+ self.service_modules = [
49
+ "grpc_services",
50
+ "grpc_handlers",
51
+ "services.grpc",
52
+ "handlers.grpc",
53
+ "api.grpc",
54
+ ]
55
+
56
+ def discover_services(self) -> List[Tuple[Any, Any]]:
57
+ """
58
+ Discover all gRPC services.
59
+
60
+ Returns:
61
+ List of (service_class, add_to_server_func) tuples
62
+
63
+ Example:
64
+ >>> discovery = ServiceDiscovery()
65
+ >>> services = discovery.discover_services()
66
+ >>> len(services)
67
+ 5
68
+ """
69
+ discovered_services = []
70
+
71
+ # Discover from enabled apps
72
+ if self.auto_register:
73
+ for app_label in self.enabled_apps:
74
+ services = self._discover_app_services(app_label)
75
+ discovered_services.extend(services)
76
+
77
+ # Add custom services
78
+ for service_path in self.custom_services.values():
79
+ service = self._load_custom_service(service_path)
80
+ if service:
81
+ discovered_services.append(service)
82
+
83
+ logger.info(f"Discovered {len(discovered_services)} gRPC service(s)")
84
+ return discovered_services
85
+
86
+ def _discover_app_services(self, app_label: str) -> List[Tuple[Any, Any]]:
87
+ """
88
+ Discover services from a Django app.
89
+
90
+ Args:
91
+ app_label: Django app label (e.g., 'accounts', 'support')
92
+
93
+ Returns:
94
+ List of discovered services
95
+ """
96
+ services = []
97
+
98
+ # Get app config
99
+ try:
100
+ app_config = apps.get_app_config(app_label)
101
+ except LookupError:
102
+ logger.warning(f"App '{app_label}' not found in INSTALLED_APPS")
103
+ return services
104
+
105
+ # Try to import service modules
106
+ for module_name in self.service_modules:
107
+ full_module_path = f"{app_config.name}.{module_name}"
108
+
109
+ try:
110
+ module = importlib.import_module(full_module_path)
111
+ logger.debug(f"Found gRPC module: {full_module_path}")
112
+
113
+ # Look for services in module
114
+ app_services = self._extract_services_from_module(module, full_module_path)
115
+ services.extend(app_services)
116
+
117
+ except ImportError:
118
+ # Module doesn't exist, that's okay
119
+ logger.debug(f"No gRPC module at {full_module_path}")
120
+ continue
121
+ except Exception as e:
122
+ logger.error(f"Error importing {full_module_path}: {e}", exc_info=True)
123
+ continue
124
+
125
+ if services:
126
+ logger.info(f"Discovered {len(services)} service(s) from app '{app_label}'")
127
+
128
+ return services
129
+
130
+ def _extract_services_from_module(
131
+ self, module: Any, module_path: str
132
+ ) -> List[Tuple[Any, Any]]:
133
+ """
134
+ Extract gRPC services from a module.
135
+
136
+ Args:
137
+ module: Python module object
138
+ module_path: Full module path string
139
+
140
+ Returns:
141
+ List of (service_class, add_to_server_func) tuples
142
+ """
143
+ services = []
144
+
145
+ # Look for grpc_handlers() function (django-grpc-framework convention)
146
+ if hasattr(module, "grpc_handlers"):
147
+ handlers_func = getattr(module, "grpc_handlers")
148
+ if callable(handlers_func):
149
+ try:
150
+ # Call the handlers function to get list of services
151
+ handlers = handlers_func(None) # server argument not needed for discovery
152
+ logger.info(f"Found grpc_handlers() in {module_path}")
153
+
154
+ # handlers should be a list of tuples
155
+ if isinstance(handlers, list):
156
+ services.extend(handlers)
157
+ else:
158
+ logger.warning(
159
+ f"grpc_handlers() in {module_path} did not return a list"
160
+ )
161
+
162
+ except Exception as e:
163
+ logger.error(
164
+ f"Error calling grpc_handlers() in {module_path}: {e}",
165
+ exc_info=True,
166
+ )
167
+
168
+ # Look for individual service classes
169
+ for attr_name in dir(module):
170
+ # Skip private attributes
171
+ if attr_name.startswith("_"):
172
+ continue
173
+
174
+ attr = getattr(module, attr_name)
175
+
176
+ # Check if it's a gRPC service class
177
+ if self._is_grpc_service(attr):
178
+ logger.debug(f"Found gRPC service class: {module_path}.{attr_name}")
179
+
180
+ # Try to get add_to_server function
181
+ add_to_server_func = self._get_add_to_server_func(attr, module_path)
182
+
183
+ if add_to_server_func:
184
+ services.append((attr, add_to_server_func))
185
+
186
+ return services
187
+
188
+ def _is_grpc_service(self, obj: Any) -> bool:
189
+ """
190
+ Check if object is a gRPC service class.
191
+
192
+ Args:
193
+ obj: Object to check
194
+
195
+ Returns:
196
+ True if object is a gRPC service class
197
+ """
198
+ # Check if it's a class
199
+ if not isinstance(obj, type):
200
+ return False
201
+
202
+ # Check for django-grpc-framework service
203
+ try:
204
+ from django_grpc_framework import generics
205
+
206
+ # Check if it inherits from any generics service
207
+ if issubclass(obj, (
208
+ generics.Service,
209
+ generics.ModelService,
210
+ generics.ReadOnlyModelService,
211
+ )):
212
+ return True
213
+
214
+ except ImportError:
215
+ logger.debug("django-grpc-framework not installed, skipping service check")
216
+
217
+ # Check for grpc servicer (has add_to_server method)
218
+ if hasattr(obj, "add_to_server") and callable(getattr(obj, "add_to_server")):
219
+ return True
220
+
221
+ return False
222
+
223
+ def _get_add_to_server_func(self, service_class: Any, module_path: str) -> Optional[Any]:
224
+ """
225
+ Get add_to_server function for a service class.
226
+
227
+ Args:
228
+ service_class: Service class
229
+ module_path: Module path for logging
230
+
231
+ Returns:
232
+ add_to_server function or None
233
+ """
234
+ # For django-grpc-framework services
235
+ if hasattr(service_class, "add_to_server"):
236
+ return getattr(service_class, "add_to_server")
237
+
238
+ # Try to find matching _pb2_grpc module
239
+ # Convention: myservice.grpc_services.MyService -> myservice_pb2_grpc.add_MyServiceServicer_to_server
240
+ try:
241
+ # Get the module name without the service module part
242
+ base_module = module_path.rsplit(".", 1)[0]
243
+ pb2_grpc_module_name = f"{base_module}_pb2_grpc"
244
+
245
+ pb2_grpc_module = importlib.import_module(pb2_grpc_module_name)
246
+
247
+ # Look for add_<ServiceName>_to_server function
248
+ func_name = f"add_{service_class.__name__}_to_server"
249
+
250
+ if hasattr(pb2_grpc_module, func_name):
251
+ return getattr(pb2_grpc_module, func_name)
252
+
253
+ except ImportError:
254
+ logger.debug(f"No _pb2_grpc module found for {module_path}")
255
+ except Exception as e:
256
+ logger.debug(f"Error finding add_to_server for {service_class.__name__}: {e}")
257
+
258
+ return None
259
+
260
+ def _load_custom_service(self, service_path: str) -> Optional[Tuple[Any, Any]]:
261
+ """
262
+ Load a custom service from dotted path.
263
+
264
+ Args:
265
+ service_path: Dotted path to service class (e.g., 'myapp.services.MyService')
266
+
267
+ Returns:
268
+ (service_class, add_to_server_func) tuple or None
269
+ """
270
+ try:
271
+ # Split module path and class name
272
+ module_path, class_name = service_path.rsplit(".", 1)
273
+
274
+ # Import module
275
+ module = importlib.import_module(module_path)
276
+
277
+ # Get service class
278
+ service_class = getattr(module, class_name)
279
+
280
+ # Get add_to_server function
281
+ add_to_server_func = self._get_add_to_server_func(service_class, module_path)
282
+
283
+ if not add_to_server_func:
284
+ logger.warning(
285
+ f"Custom service {service_path} has no add_to_server function"
286
+ )
287
+ return None
288
+
289
+ logger.info(f"Loaded custom service: {service_path}")
290
+ return (service_class, add_to_server_func)
291
+
292
+ except ImportError as e:
293
+ logger.error(f"Failed to import custom service {service_path}: {e}")
294
+ return None
295
+ except AttributeError as e:
296
+ logger.error(f"Service class not found in {service_path}: {e}")
297
+ return None
298
+ except Exception as e:
299
+ logger.error(f"Error loading custom service {service_path}: {e}", exc_info=True)
300
+ return None
301
+
302
+ def get_handlers_hook(self) -> Optional[Any]:
303
+ """
304
+ Get the handlers hook function from ROOT_HANDLERS_HOOK setting.
305
+
306
+ Returns:
307
+ Handlers hook function or None
308
+
309
+ Example:
310
+ >>> discovery = ServiceDiscovery()
311
+ >>> handlers_hook = discovery.get_handlers_hook()
312
+ >>> if handlers_hook:
313
+ ... services = handlers_hook(server)
314
+ """
315
+ handlers_hook_path = self.grpc_config.get("ROOT_HANDLERS_HOOK")
316
+
317
+ if not handlers_hook_path:
318
+ logger.debug("No ROOT_HANDLERS_HOOK configured")
319
+ return None
320
+
321
+ try:
322
+ # Import the module containing the hook
323
+ module_path, func_name = handlers_hook_path.rsplit(".", 1)
324
+ module = importlib.import_module(module_path)
325
+
326
+ # Get the hook function
327
+ handlers_hook = getattr(module, func_name)
328
+
329
+ if not callable(handlers_hook):
330
+ logger.warning(f"ROOT_HANDLERS_HOOK {handlers_hook_path} is not callable")
331
+ return None
332
+
333
+ logger.info(f"Loaded handlers hook: {handlers_hook_path}")
334
+ return handlers_hook
335
+
336
+ except ImportError as e:
337
+ logger.error(f"Failed to import handlers hook {handlers_hook_path}: {e}")
338
+ return None
339
+ except AttributeError as e:
340
+ logger.error(f"Handlers hook function not found in {handlers_hook_path}: {e}")
341
+ return None
342
+ except Exception as e:
343
+ logger.error(
344
+ f"Error loading handlers hook {handlers_hook_path}: {e}",
345
+ exc_info=True,
346
+ )
347
+ return None
348
+
349
+
350
+ def discover_and_register_services(server: Any) -> int:
351
+ """
352
+ Discover and register all gRPC services to a server.
353
+
354
+ Args:
355
+ server: gRPC server instance
356
+
357
+ Returns:
358
+ Number of services registered
359
+
360
+ Example:
361
+ ```python
362
+ import grpc
363
+ from concurrent import futures
364
+ from django_cfg.apps.grpc.services.discovery import discover_and_register_services
365
+
366
+ # Create server
367
+ server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
368
+
369
+ # Auto-discover and register services
370
+ count = discover_and_register_services(server)
371
+ print(f"Registered {count} services")
372
+
373
+ # Start server
374
+ server.add_insecure_port('[::]:50051')
375
+ server.start()
376
+ ```
377
+ """
378
+ discovery = ServiceDiscovery()
379
+ count = 0
380
+
381
+ # Try handlers hook first
382
+ handlers_hook = discovery.get_handlers_hook()
383
+ if handlers_hook:
384
+ try:
385
+ handlers_hook(server)
386
+ logger.info("Successfully called handlers hook")
387
+ count += 1 # We don't know exact count, but at least 1
388
+ except Exception as e:
389
+ logger.error(f"Error calling handlers hook: {e}", exc_info=True)
390
+
391
+ # Discover and register services
392
+ services = discovery.discover_services()
393
+
394
+ for service_class, add_to_server_func in services:
395
+ try:
396
+ # Instantiate service
397
+ servicer = service_class()
398
+
399
+ # Register with server
400
+ add_to_server_func(servicer, server)
401
+
402
+ logger.debug(f"Registered service: {service_class.__name__}")
403
+ count += 1
404
+
405
+ except Exception as e:
406
+ logger.error(
407
+ f"Failed to register service {service_class.__name__}: {e}",
408
+ exc_info=True,
409
+ )
410
+
411
+ logger.info(f"Registered {count} gRPC service(s)")
412
+ return count
413
+
414
+
415
+ __all__ = ["ServiceDiscovery", "discover_and_register_services"]
@@ -0,0 +1,23 @@
1
+ """
2
+ URL patterns for gRPC module.
3
+
4
+ Public API endpoints for gRPC monitoring.
5
+ """
6
+
7
+ from django.urls import include, path
8
+ from rest_framework import routers
9
+
10
+ from .views.monitoring import GRPCMonitorViewSet
11
+
12
+ app_name = 'django_cfg_grpc'
13
+
14
+ # Create router
15
+ router = routers.DefaultRouter()
16
+
17
+ # Monitoring endpoints (Django logs based)
18
+ router.register(r'monitor', GRPCMonitorViewSet, basename='monitor')
19
+
20
+ urlpatterns = [
21
+ # Include router URLs
22
+ path('', include(router.urls)),
23
+ ]
@@ -0,0 +1,13 @@
1
+ """
2
+ gRPC utilities.
3
+
4
+ Provides proto generation and other helper utilities.
5
+ """
6
+
7
+ from .proto_gen import ProtoFieldMapper, ProtoGenerator, generate_proto_for_app
8
+
9
+ __all__ = [
10
+ "ProtoFieldMapper",
11
+ "ProtoGenerator",
12
+ "generate_proto_for_app",
13
+ ]