django-cfg 1.5.8__py3-none-any.whl → 1.5.20__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 (159) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/commands/serializers.py +152 -0
  3. django_cfg/apps/api/commands/views.py +32 -0
  4. django_cfg/apps/business/accounts/management/commands/otp_test.py +5 -2
  5. django_cfg/apps/business/accounts/serializers/profile.py +42 -0
  6. django_cfg/apps/business/agents/management/commands/create_agent.py +5 -194
  7. django_cfg/apps/business/agents/management/commands/load_agent_templates.py +205 -0
  8. django_cfg/apps/business/agents/management/commands/orchestrator_status.py +4 -2
  9. django_cfg/apps/business/knowbase/management/commands/knowbase_stats.py +4 -2
  10. django_cfg/apps/business/knowbase/management/commands/setup_knowbase.py +4 -2
  11. django_cfg/apps/business/newsletter/management/commands/test_newsletter.py +5 -2
  12. django_cfg/apps/business/payments/management/commands/check_payment_status.py +4 -2
  13. django_cfg/apps/business/payments/management/commands/create_payment.py +4 -2
  14. django_cfg/apps/business/payments/management/commands/sync_currencies.py +4 -2
  15. django_cfg/apps/business/support/serializers.py +3 -2
  16. django_cfg/apps/integrations/centrifugo/apps.py +2 -1
  17. django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
  18. django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +6 -6
  19. django_cfg/apps/integrations/centrifugo/serializers/__init__.py +2 -1
  20. django_cfg/apps/integrations/centrifugo/serializers/publishes.py +22 -2
  21. django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
  22. django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
  23. django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
  24. django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
  25. django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
  26. django_cfg/apps/integrations/centrifugo/urls.py +8 -0
  27. django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
  28. django_cfg/apps/integrations/centrifugo/views/monitoring.py +25 -40
  29. django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
  30. django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
  31. django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
  32. django_cfg/apps/integrations/grpc/admin/__init__.py +7 -1
  33. django_cfg/apps/integrations/grpc/admin/config.py +113 -9
  34. django_cfg/apps/integrations/grpc/admin/grpc_api_key.py +129 -0
  35. django_cfg/apps/integrations/grpc/admin/grpc_request_log.py +72 -63
  36. django_cfg/apps/integrations/grpc/admin/grpc_server_status.py +236 -0
  37. django_cfg/apps/integrations/grpc/auth/__init__.py +11 -3
  38. django_cfg/apps/integrations/grpc/auth/api_key_auth.py +320 -0
  39. django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
  40. django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
  41. django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
  42. django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
  43. django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
  44. django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
  45. django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
  46. django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
  47. django_cfg/apps/integrations/grpc/interceptors/logging.py +17 -20
  48. django_cfg/apps/integrations/grpc/interceptors/metrics.py +15 -14
  49. django_cfg/apps/integrations/grpc/interceptors/request_logger.py +79 -59
  50. django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
  51. django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +185 -0
  52. django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +474 -95
  53. django_cfg/apps/integrations/grpc/management/commands/test_grpc_integration.py +75 -0
  54. django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
  55. django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
  56. django_cfg/apps/integrations/grpc/managers/__init__.py +2 -0
  57. django_cfg/apps/integrations/grpc/managers/grpc_api_key.py +192 -0
  58. django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +19 -11
  59. django_cfg/apps/integrations/grpc/migrations/0005_grpcapikey.py +143 -0
  60. django_cfg/apps/integrations/grpc/migrations/0006_grpcrequestlog_api_key_and_more.py +34 -0
  61. django_cfg/apps/integrations/grpc/models/__init__.py +2 -0
  62. django_cfg/apps/integrations/grpc/models/grpc_api_key.py +198 -0
  63. django_cfg/apps/integrations/grpc/models/grpc_request_log.py +11 -0
  64. django_cfg/apps/integrations/grpc/models/grpc_server_status.py +39 -4
  65. django_cfg/apps/integrations/grpc/serializers/__init__.py +22 -6
  66. django_cfg/apps/integrations/grpc/serializers/api_keys.py +63 -0
  67. django_cfg/apps/integrations/grpc/serializers/charts.py +118 -120
  68. django_cfg/apps/integrations/grpc/serializers/config.py +65 -51
  69. django_cfg/apps/integrations/grpc/serializers/health.py +7 -7
  70. django_cfg/apps/integrations/grpc/serializers/proto_files.py +74 -0
  71. django_cfg/apps/integrations/grpc/serializers/requests.py +13 -7
  72. django_cfg/apps/integrations/grpc/serializers/service_registry.py +181 -112
  73. django_cfg/apps/integrations/grpc/serializers/services.py +14 -32
  74. django_cfg/apps/integrations/grpc/serializers/stats.py +50 -12
  75. django_cfg/apps/integrations/grpc/serializers/testing.py +66 -58
  76. django_cfg/apps/integrations/grpc/services/__init__.py +2 -0
  77. django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
  78. django_cfg/apps/integrations/grpc/services/monitoring_service.py +149 -43
  79. django_cfg/apps/integrations/grpc/services/proto_files_manager.py +268 -0
  80. django_cfg/apps/integrations/grpc/services/service_registry.py +48 -46
  81. django_cfg/apps/integrations/grpc/services/testing_service.py +10 -15
  82. django_cfg/apps/integrations/grpc/urls.py +8 -0
  83. django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
  84. django_cfg/apps/integrations/grpc/utils/__init__.py +4 -13
  85. django_cfg/apps/integrations/grpc/utils/integration_test.py +334 -0
  86. django_cfg/apps/integrations/grpc/utils/proto_gen.py +48 -8
  87. django_cfg/apps/integrations/grpc/utils/streaming_logger.py +378 -0
  88. django_cfg/apps/integrations/grpc/views/__init__.py +4 -0
  89. django_cfg/apps/integrations/grpc/views/api_keys.py +255 -0
  90. django_cfg/apps/integrations/grpc/views/charts.py +21 -14
  91. django_cfg/apps/integrations/grpc/views/config.py +8 -6
  92. django_cfg/apps/integrations/grpc/views/monitoring.py +51 -79
  93. django_cfg/apps/integrations/grpc/views/proto_files.py +214 -0
  94. django_cfg/apps/integrations/grpc/views/services.py +30 -21
  95. django_cfg/apps/integrations/grpc/views/testing.py +45 -43
  96. django_cfg/apps/integrations/rq/views/jobs.py +19 -9
  97. django_cfg/apps/integrations/rq/views/schedule.py +7 -3
  98. django_cfg/apps/system/dashboard/serializers/commands.py +25 -1
  99. django_cfg/apps/system/dashboard/serializers/config.py +95 -9
  100. django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
  101. django_cfg/apps/system/dashboard/services/commands_service.py +12 -1
  102. django_cfg/apps/system/frontend/views.py +87 -6
  103. django_cfg/apps/system/maintenance/management/commands/maintenance.py +5 -2
  104. django_cfg/apps/system/maintenance/management/commands/process_scheduled_maintenance.py +4 -2
  105. django_cfg/apps/system/maintenance/management/commands/sync_cloudflare.py +5 -2
  106. django_cfg/config.py +33 -0
  107. django_cfg/core/builders/security_builder.py +1 -0
  108. django_cfg/core/generation/integration_generators/api.py +2 -0
  109. django_cfg/core/generation/integration_generators/grpc_generator.py +30 -32
  110. django_cfg/management/commands/check_endpoints.py +2 -2
  111. django_cfg/management/commands/check_settings.py +3 -10
  112. django_cfg/management/commands/clear_constance.py +3 -10
  113. django_cfg/management/commands/create_token.py +4 -11
  114. django_cfg/management/commands/list_urls.py +4 -10
  115. django_cfg/management/commands/migrate_all.py +18 -12
  116. django_cfg/management/commands/migrator.py +4 -11
  117. django_cfg/management/commands/script.py +4 -10
  118. django_cfg/management/commands/show_config.py +8 -16
  119. django_cfg/management/commands/show_urls.py +5 -11
  120. django_cfg/management/commands/superuser.py +4 -11
  121. django_cfg/management/commands/tree.py +5 -10
  122. django_cfg/management/utils/README.md +402 -0
  123. django_cfg/management/utils/__init__.py +29 -0
  124. django_cfg/management/utils/mixins.py +176 -0
  125. django_cfg/middleware/pagination.py +53 -54
  126. django_cfg/models/api/grpc/__init__.py +15 -21
  127. django_cfg/models/api/grpc/config.py +155 -73
  128. django_cfg/models/ngrok/config.py +7 -6
  129. django_cfg/modules/django_client/core/generator/python/files_generator.py +5 -13
  130. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +16 -4
  131. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +2 -3
  132. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +6 -5
  133. django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
  134. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
  135. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
  136. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
  137. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
  138. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
  139. django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
  140. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +12 -8
  141. django_cfg/modules/django_client/core/ir/schema.py +15 -1
  142. django_cfg/modules/django_client/core/parser/base.py +126 -30
  143. django_cfg/modules/django_client/management/commands/generate_client.py +5 -2
  144. django_cfg/modules/django_client/management/commands/validate_openapi.py +5 -2
  145. django_cfg/modules/django_email/management/commands/test_email.py +4 -10
  146. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +16 -13
  147. django_cfg/modules/django_telegram/management/commands/test_telegram.py +4 -11
  148. django_cfg/modules/django_twilio/management/commands/test_twilio.py +4 -11
  149. django_cfg/modules/django_unfold/navigation.py +6 -18
  150. django_cfg/pyproject.toml +1 -1
  151. django_cfg/registry/modules.py +1 -4
  152. django_cfg/requirements.txt +52 -0
  153. django_cfg/static/frontend/admin.zip +0 -0
  154. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
  155. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/RECORD +158 -121
  156. django_cfg/apps/integrations/grpc/auth/jwt_auth.py +0 -295
  157. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
  158. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
  159. {django_cfg-1.5.8.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,75 @@
1
+ """
2
+ Django management command for gRPC integration testing.
3
+
4
+ Usage:
5
+ python manage.py test_grpc_integration
6
+ python manage.py test_grpc_integration --app crypto
7
+ python manage.py test_grpc_integration --quiet
8
+ """
9
+
10
+ import sys
11
+
12
+ from django_cfg.management.utils import SafeCommand
13
+
14
+ from django_cfg.apps.integrations.grpc.utils import GRPCIntegrationTest
15
+
16
+
17
+ class Command(SafeCommand):
18
+ command_name = 'test_grpc_integration'
19
+ help = "Run comprehensive gRPC integration test with API keys"
20
+
21
+ def add_arguments(self, parser):
22
+ parser.add_argument(
23
+ "--app",
24
+ type=str,
25
+ default="crypto",
26
+ help="Django app label to test (default: crypto)",
27
+ )
28
+ parser.add_argument(
29
+ "--quiet",
30
+ "-q",
31
+ action="store_true",
32
+ help="Suppress verbose output",
33
+ )
34
+
35
+ def handle(self, *args, **options):
36
+ app_label = options["app"]
37
+ quiet = options["quiet"]
38
+
39
+ if not quiet:
40
+ self.stdout.write(
41
+ self.style.SUCCESS(f"Starting gRPC integration test for app: {app_label}")
42
+ )
43
+
44
+ # Создаем и запускаем тест
45
+ test = GRPCIntegrationTest(app_label=app_label, quiet=quiet)
46
+
47
+ try:
48
+ success = test.run()
49
+
50
+ if success:
51
+ self.stdout.write(
52
+ self.style.SUCCESS("\n✅ Integration test completed successfully!")
53
+ )
54
+ sys.exit(0)
55
+ else:
56
+ self.stdout.write(
57
+ self.style.ERROR("\n❌ Integration test failed!")
58
+ )
59
+ sys.exit(1)
60
+
61
+ except KeyboardInterrupt:
62
+ self.stdout.write(
63
+ self.style.WARNING("\n⚠️ Test interrupted by user")
64
+ )
65
+ test.step6_cleanup()
66
+ sys.exit(1)
67
+
68
+ except Exception as e:
69
+ self.stdout.write(
70
+ self.style.ERROR(f"\n❌ Critical error: {e}")
71
+ )
72
+ import traceback
73
+ traceback.print_exc()
74
+ test.step6_cleanup()
75
+ sys.exit(1)
@@ -0,0 +1,3 @@
1
+ """
2
+ Proto utilities for django-cfg gRPC integration.
3
+ """
@@ -0,0 +1,194 @@
1
+ """
2
+ Proto compiler utilities.
3
+ Shared functionality for compiling .proto files to Python.
4
+ """
5
+
6
+ import logging
7
+ import re
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+ from typing import List, Optional
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class ProtoCompiler:
17
+ """Compiles .proto files to Python using grpc_tools.protoc."""
18
+
19
+ def __init__(
20
+ self,
21
+ output_dir: Optional[Path] = None,
22
+ proto_import_path: Optional[Path] = None,
23
+ fix_imports: bool = True,
24
+ verbose: bool = True,
25
+ ):
26
+ """
27
+ Initialize proto compiler.
28
+
29
+ Args:
30
+ output_dir: Output directory for generated files (default: same as proto file)
31
+ proto_import_path: Additional proto import path (passed to protoc -I flag)
32
+ fix_imports: Fix imports in generated _grpc.py files (default: True)
33
+ verbose: Print compilation progress (default: True)
34
+ """
35
+ self.output_dir = output_dir
36
+ self.proto_import_path = proto_import_path
37
+ self.fix_imports = fix_imports
38
+ self.verbose = verbose
39
+
40
+ def compile_file(self, proto_file: Path) -> bool:
41
+ """
42
+ Compile a single .proto file.
43
+
44
+ Args:
45
+ proto_file: Path to .proto file
46
+
47
+ Returns:
48
+ True if compilation succeeded, False otherwise
49
+ """
50
+ if self.verbose:
51
+ logger.info(f"📦 Compiling: {proto_file}")
52
+
53
+ # Determine output directory
54
+ output_dir = self.output_dir or proto_file.parent
55
+
56
+ # Determine proto import path
57
+ proto_import_path = self.proto_import_path or proto_file.parent
58
+
59
+ # Ensure output directory exists
60
+ output_dir.mkdir(parents=True, exist_ok=True)
61
+
62
+ # Build protoc command
63
+ cmd = [
64
+ sys.executable,
65
+ "-m",
66
+ "grpc_tools.protoc",
67
+ f"-I{proto_import_path}",
68
+ f"--python_out={output_dir}",
69
+ f"--grpc_python_out={output_dir}",
70
+ str(proto_file),
71
+ ]
72
+
73
+ # Run protoc
74
+ try:
75
+ result = subprocess.run(
76
+ cmd,
77
+ capture_output=True,
78
+ text=True,
79
+ check=True,
80
+ )
81
+
82
+ if result.stdout and self.verbose:
83
+ logger.info(f" {result.stdout}")
84
+
85
+ if self.verbose:
86
+ logger.info(f" ✅ Compiled successfully")
87
+
88
+ # Fix imports if requested
89
+ if self.fix_imports:
90
+ self._fix_imports(proto_file, output_dir)
91
+
92
+ return True
93
+
94
+ except subprocess.CalledProcessError as e:
95
+ logger.error(f" ❌ Compilation failed")
96
+ logger.error(f" Error: {e.stderr}")
97
+ return False
98
+
99
+ def compile_directory(
100
+ self,
101
+ proto_path: Path,
102
+ recursive: bool = False,
103
+ ) -> tuple[int, int]:
104
+ """
105
+ Compile all .proto files in a directory.
106
+
107
+ Args:
108
+ proto_path: Directory containing .proto files
109
+ recursive: Recursively compile all .proto files
110
+
111
+ Returns:
112
+ Tuple of (success_count, failure_count)
113
+ """
114
+ # Collect proto files
115
+ proto_files = self._collect_proto_files(proto_path, recursive)
116
+
117
+ if not proto_files:
118
+ logger.warning(f"No .proto files found in: {proto_path}")
119
+ return 0, 0
120
+
121
+ if self.verbose:
122
+ logger.info(f"🔧 Compiling {len(proto_files)} proto file(s)...")
123
+
124
+ # Compile each proto file
125
+ success_count = 0
126
+ failure_count = 0
127
+
128
+ for proto_file in proto_files:
129
+ if self.compile_file(proto_file):
130
+ success_count += 1
131
+ else:
132
+ failure_count += 1
133
+
134
+ return success_count, failure_count
135
+
136
+ def _collect_proto_files(self, path: Path, recursive: bool) -> List[Path]:
137
+ """Collect all .proto files from path."""
138
+ if path.is_file():
139
+ if path.suffix == ".proto":
140
+ return [path]
141
+ else:
142
+ raise ValueError(f"File is not a .proto file: {path}")
143
+
144
+ # Directory
145
+ if recursive:
146
+ return list(path.rglob("*.proto"))
147
+ else:
148
+ return list(path.glob("*.proto"))
149
+
150
+ def _fix_imports(self, proto_file: Path, output_dir: Path):
151
+ """
152
+ Fix imports in generated _grpc.py files.
153
+
154
+ Changes: import foo_pb2 as foo__pb2
155
+ To: from . import foo_pb2 as foo__pb2
156
+ """
157
+ # Find generated _grpc.py file
158
+ grpc_file = output_dir / f"{proto_file.stem}_pb2_grpc.py"
159
+
160
+ if not grpc_file.exists():
161
+ if self.verbose:
162
+ logger.warning(f" ⚠️ Skipping import fix: {grpc_file.name} not found")
163
+ return
164
+
165
+ if self.verbose:
166
+ logger.info(f" 🔧 Fixing imports in {grpc_file.name}...")
167
+
168
+ # Read file
169
+ content = grpc_file.read_text()
170
+
171
+ # Pattern to match: import xxx_pb2 as yyy
172
+ # But NOT: from xxx import ...
173
+ pattern = r"^import (\w+_pb2) as (\w+)$"
174
+
175
+ # Replace with: from . import xxx_pb2 as yyy
176
+ def replace_func(match):
177
+ module = match.group(1)
178
+ alias = match.group(2)
179
+ return f"from . import {module} as {alias}"
180
+
181
+ # Apply replacement
182
+ new_content = re.sub(pattern, replace_func, content, flags=re.MULTILINE)
183
+
184
+ # Count changes
185
+ changes = content.count("\nimport ") - new_content.count("\nimport ")
186
+
187
+ if changes > 0:
188
+ # Write back
189
+ grpc_file.write_text(new_content)
190
+ if self.verbose:
191
+ logger.info(f" ✅ Fixed {changes} import(s) in {grpc_file.name}")
192
+ else:
193
+ if self.verbose:
194
+ logger.info(f" ℹ️ No imports to fix in {grpc_file.name}")
@@ -2,10 +2,12 @@
2
2
  Managers for gRPC app models.
3
3
  """
4
4
 
5
+ from .grpc_api_key import GrpcApiKeyManager
5
6
  from .grpc_request_log import GRPCRequestLogManager, GRPCRequestLogQuerySet
6
7
  from .grpc_server_status import GRPCServerStatusManager
7
8
 
8
9
  __all__ = [
10
+ "GrpcApiKeyManager",
9
11
  "GRPCRequestLogManager",
10
12
  "GRPCRequestLogQuerySet",
11
13
  "GRPCServerStatusManager",
@@ -0,0 +1,192 @@
1
+ """
2
+ Manager for GrpcApiKey model.
3
+
4
+ Provides convenient methods for API key management.
5
+ """
6
+
7
+ from datetime import timedelta
8
+ from typing import Optional
9
+
10
+ from django.contrib.auth import get_user_model
11
+ from django.db import models
12
+ from django.utils import timezone
13
+
14
+ User = get_user_model()
15
+
16
+
17
+ class GrpcApiKeyManager(models.Manager):
18
+ """
19
+ Manager for GrpcApiKey model.
20
+
21
+ Provides convenient methods for creating and validating API keys.
22
+ """
23
+
24
+ def create_for_user(
25
+ self,
26
+ user: User,
27
+ name: str,
28
+ description: str = "",
29
+ key_type: str = "service",
30
+ expires_in_days: Optional[int] = None,
31
+ created_by: Optional[User] = None,
32
+ ) -> "GrpcApiKey":
33
+ """
34
+ Create a new API key for a user.
35
+
36
+ Args:
37
+ user: User this key authenticates as
38
+ name: Descriptive name for this key
39
+ description: Additional details about this key
40
+ key_type: Type of key (service, cli, webhook, etc.)
41
+ expires_in_days: Number of days until expiration (None = never)
42
+ created_by: User who created this key
43
+
44
+ Returns:
45
+ Created GrpcApiKey instance
46
+
47
+ Example:
48
+ >>> key = GrpcApiKey.objects.create_for_user(
49
+ ... user=admin_user,
50
+ ... name="Analytics Service",
51
+ ... description="Internal analytics microservice",
52
+ ... expires_in_days=365,
53
+ ... )
54
+ """
55
+ expires_at = None
56
+ if expires_in_days:
57
+ expires_at = timezone.now() + timedelta(days=expires_in_days)
58
+
59
+ return self.create(
60
+ user=user,
61
+ name=name,
62
+ description=description,
63
+ key_type=key_type,
64
+ expires_at=expires_at,
65
+ created_by=created_by or user,
66
+ )
67
+
68
+ def get_by_key(self, key: str) -> Optional["GrpcApiKey"]:
69
+ """
70
+ Get API key by key string.
71
+
72
+ Args:
73
+ key: API key string
74
+
75
+ Returns:
76
+ GrpcApiKey instance or None if not found
77
+
78
+ Example:
79
+ >>> api_key = GrpcApiKey.objects.get_by_key("abc123...")
80
+ >>> if api_key and api_key.is_valid:
81
+ ... user = api_key.user
82
+ """
83
+ try:
84
+ return self.select_related("user").get(key=key)
85
+ except self.model.DoesNotExist:
86
+ return None
87
+
88
+ def validate_key(self, key: str) -> Optional[User]:
89
+ """
90
+ Validate API key and return associated user.
91
+
92
+ Args:
93
+ key: API key string
94
+
95
+ Returns:
96
+ User instance if key is valid, None otherwise
97
+
98
+ Example:
99
+ >>> user = GrpcApiKey.objects.validate_key("abc123...")
100
+ >>> if user:
101
+ ... print(f"Authenticated as {user.username}")
102
+ """
103
+ api_key = self.get_by_key(key)
104
+
105
+ if not api_key:
106
+ return None
107
+
108
+ if not api_key.is_valid:
109
+ return None
110
+
111
+ # Mark as used
112
+ api_key.mark_used()
113
+
114
+ return api_key.user
115
+
116
+ def active(self):
117
+ """
118
+ Get all active API keys.
119
+
120
+ Returns:
121
+ QuerySet of active keys
122
+
123
+ Example:
124
+ >>> active_keys = GrpcApiKey.objects.active()
125
+ """
126
+ return self.filter(is_active=True)
127
+
128
+ def valid(self):
129
+ """
130
+ Get all valid API keys (active and not expired).
131
+
132
+ Returns:
133
+ QuerySet of valid keys
134
+
135
+ Example:
136
+ >>> valid_keys = GrpcApiKey.objects.valid()
137
+ """
138
+ now = timezone.now()
139
+ return self.filter(
140
+ is_active=True
141
+ ).filter(
142
+ models.Q(expires_at__isnull=True) | models.Q(expires_at__gt=now)
143
+ )
144
+
145
+ def for_user(self, user: User):
146
+ """
147
+ Get all API keys for a user.
148
+
149
+ Args:
150
+ user: User instance
151
+
152
+ Returns:
153
+ QuerySet of keys for this user
154
+
155
+ Example:
156
+ >>> user_keys = GrpcApiKey.objects.for_user(request.user)
157
+ """
158
+ return self.filter(user=user)
159
+
160
+ def expired(self):
161
+ """
162
+ Get all expired API keys.
163
+
164
+ Returns:
165
+ QuerySet of expired keys
166
+
167
+ Example:
168
+ >>> expired_keys = GrpcApiKey.objects.expired()
169
+ """
170
+ return self.filter(
171
+ expires_at__isnull=False,
172
+ expires_at__lte=timezone.now()
173
+ )
174
+
175
+ def revoke_all_for_user(self, user: User) -> int:
176
+ """
177
+ Revoke all API keys for a user.
178
+
179
+ Args:
180
+ user: User instance
181
+
182
+ Returns:
183
+ Number of keys revoked
184
+
185
+ Example:
186
+ >>> count = GrpcApiKey.objects.revoke_all_for_user(user)
187
+ >>> print(f"Revoked {count} keys")
188
+ """
189
+ return self.filter(user=user, is_active=True).update(is_active=False)
190
+
191
+
192
+ __all__ = ["GrpcApiKeyManager"]
@@ -53,6 +53,10 @@ class GRPCServerStatusManager(models.Manager):
53
53
  ... )
54
54
  >>> status.is_running
55
55
  True
56
+
57
+ Note:
58
+ External/internal server detection is automatic based on env_mode.
59
+ Production mode assumes external server (Docker), dev/test assumes local.
56
60
  """
57
61
  if pid is None:
58
62
  pid = os.getpid()
@@ -64,18 +68,22 @@ class GRPCServerStatusManager(models.Manager):
64
68
  # Mark any existing server at this address as stopped
65
69
  self.stop_servers_at_address(address)
66
70
 
67
- # Create new server status
68
- status = self.create(
71
+ # Create or update server status (handles restart with same instance_id)
72
+ status, created = self.update_or_create(
69
73
  instance_id=instance_id,
70
- host=host,
71
- port=port,
72
- address=address,
73
- pid=pid,
74
- hostname=hostname,
75
- status=self.model.StatusChoices.STARTING,
76
- max_workers=max_workers,
77
- enable_reflection=enable_reflection,
78
- enable_health_check=enable_health_check,
74
+ defaults={
75
+ "host": host,
76
+ "port": port,
77
+ "address": address,
78
+ "pid": pid,
79
+ "hostname": hostname,
80
+ "status": self.model.StatusChoices.STARTING,
81
+ "max_workers": max_workers,
82
+ "enable_reflection": enable_reflection,
83
+ "enable_health_check": enable_health_check,
84
+ "started_at": timezone.now(),
85
+ "last_heartbeat": timezone.now(),
86
+ },
79
87
  )
80
88
 
81
89
  return status
@@ -0,0 +1,143 @@
1
+ # Generated by Django 5.2.7 on 2025-11-04 05:14
2
+
3
+ import django.db.models.deletion
4
+ import django_cfg.apps.integrations.grpc.models.grpc_api_key
5
+ from django.conf import settings
6
+ from django.db import migrations, models
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ("grpc", "0004_grpcserverstatus_registered_services"),
13
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.CreateModel(
18
+ name="GrpcApiKey",
19
+ fields=[
20
+ (
21
+ "id",
22
+ models.BigAutoField(
23
+ auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
24
+ ),
25
+ ),
26
+ (
27
+ "key",
28
+ models.CharField(
29
+ db_index=True,
30
+ default=django_cfg.apps.integrations.grpc.models.grpc_api_key.generate_api_key,
31
+ help_text="API key (auto-generated)",
32
+ max_length=64,
33
+ unique=True,
34
+ ),
35
+ ),
36
+ (
37
+ "name",
38
+ models.CharField(
39
+ help_text="Descriptive name for this key (e.g., 'Analytics Service')",
40
+ max_length=255,
41
+ ),
42
+ ),
43
+ (
44
+ "description",
45
+ models.TextField(
46
+ blank=True, help_text="Additional details about this key's purpose"
47
+ ),
48
+ ),
49
+ (
50
+ "key_type",
51
+ models.CharField(
52
+ choices=[
53
+ ("service", "Service-to-Service"),
54
+ ("cli", "CLI Tool"),
55
+ ("webhook", "Webhook"),
56
+ ("internal", "Internal System"),
57
+ ("development", "Development"),
58
+ ],
59
+ default="service",
60
+ help_text="Type of API key",
61
+ max_length=20,
62
+ ),
63
+ ),
64
+ (
65
+ "is_active",
66
+ models.BooleanField(
67
+ db_index=True,
68
+ default=True,
69
+ help_text="Whether this key is currently active (can be used)",
70
+ ),
71
+ ),
72
+ (
73
+ "expires_at",
74
+ models.DateTimeField(
75
+ blank=True,
76
+ db_index=True,
77
+ help_text="When this key expires (null = never expires)",
78
+ null=True,
79
+ ),
80
+ ),
81
+ (
82
+ "last_used_at",
83
+ models.DateTimeField(
84
+ blank=True, help_text="When this key was last used", null=True
85
+ ),
86
+ ),
87
+ (
88
+ "request_count",
89
+ models.IntegerField(
90
+ default=0, help_text="Total number of requests made with this key"
91
+ ),
92
+ ),
93
+ (
94
+ "created_at",
95
+ models.DateTimeField(
96
+ auto_now_add=True, db_index=True, help_text="When this key was created"
97
+ ),
98
+ ),
99
+ (
100
+ "updated_at",
101
+ models.DateTimeField(auto_now=True, help_text="When this key was last updated"),
102
+ ),
103
+ (
104
+ "created_by",
105
+ models.ForeignKey(
106
+ blank=True,
107
+ help_text="User who created this key",
108
+ null=True,
109
+ on_delete=django.db.models.deletion.SET_NULL,
110
+ related_name="created_grpc_api_keys",
111
+ to=settings.AUTH_USER_MODEL,
112
+ ),
113
+ ),
114
+ (
115
+ "user",
116
+ models.ForeignKey(
117
+ help_text="User this key authenticates as",
118
+ on_delete=django.db.models.deletion.CASCADE,
119
+ related_name="grpc_api_keys",
120
+ to=settings.AUTH_USER_MODEL,
121
+ ),
122
+ ),
123
+ ],
124
+ options={
125
+ "verbose_name": "gRPC API Key",
126
+ "verbose_name_plural": "gRPC API Keys",
127
+ "db_table": "django_cfg_grpc_api_key",
128
+ "ordering": ["-created_at"],
129
+ "indexes": [
130
+ models.Index(
131
+ fields=["user", "-created_at"], name="django_cfg__user_id_9c5276_idx"
132
+ ),
133
+ models.Index(
134
+ fields=["is_active", "-created_at"], name="django_cfg__is_acti_26421c_idx"
135
+ ),
136
+ models.Index(fields=["expires_at"], name="django_cfg__expires_8f83cf_idx"),
137
+ models.Index(
138
+ fields=["key_type", "-created_at"], name="django_cfg__key_typ_6ad1cb_idx"
139
+ ),
140
+ ],
141
+ },
142
+ ),
143
+ ]
@@ -0,0 +1,34 @@
1
+ # Generated by Django 5.2.7 on 2025-11-04 05:30
2
+
3
+ import django.db.models.deletion
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+
10
+ dependencies = [
11
+ ("grpc", "0005_grpcapikey"),
12
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+ ]
14
+
15
+ operations = [
16
+ migrations.AddField(
17
+ model_name="grpcrequestlog",
18
+ name="api_key",
19
+ field=models.ForeignKey(
20
+ blank=True,
21
+ help_text="API key used for authentication (if applicable)",
22
+ null=True,
23
+ on_delete=django.db.models.deletion.SET_NULL,
24
+ related_name="request_logs",
25
+ to="grpc.grpcapikey",
26
+ ),
27
+ ),
28
+ migrations.AddIndex(
29
+ model_name="grpcrequestlog",
30
+ index=models.Index(
31
+ fields=["api_key", "-created_at"], name="django_cfg__api_key_218ecc_idx"
32
+ ),
33
+ ),
34
+ ]