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
@@ -190,62 +190,62 @@ class NoPagination(PageNumberPagination):
190
190
  return Response(data)
191
191
 
192
192
 
193
- class CursorPaginationEnhanced(PageNumberPagination):
194
- """
195
- Enhanced cursor-based pagination for large datasets.
193
+ # class CursorPaginationEnhanced(PageNumberPagination):
194
+ # """
195
+ # Enhanced cursor-based pagination for large datasets.
196
196
 
197
- Better performance for large datasets but doesn't support jumping to arbitrary pages.
198
- """
199
- page_size = 100
200
- page_size_query_param = 'page_size'
201
- max_page_size = 1000
202
- cursor_query_param = 'cursor'
203
- ordering = '-created_at' # Default ordering, should be overridden
197
+ # Better performance for large datasets but doesn't support jumping to arbitrary pages.
198
+ # """
199
+ # page_size = 100
200
+ # page_size_query_param = 'page_size'
201
+ # max_page_size = 1000
202
+ # cursor_query_param = 'cursor'
203
+ # ordering = '-created_at' # Default ordering, should be overridden
204
204
 
205
- def get_paginated_response(self, data):
206
- """
207
- Return cursor-paginated response with enhanced format.
208
- """
209
- return Response({
210
- 'next': self.get_next_link(),
211
- 'previous': self.get_previous_link(),
212
- 'page_size': self.page_size,
213
- 'results': data,
214
- })
205
+ # def get_paginated_response(self, data):
206
+ # """
207
+ # Return cursor-paginated response with enhanced format.
208
+ # """
209
+ # return Response({
210
+ # 'next': self.get_next_link(),
211
+ # 'previous': self.get_previous_link(),
212
+ # 'page_size': self.page_size,
213
+ # 'results': data,
214
+ # })
215
215
 
216
- def get_paginated_response_schema(self, schema):
217
- """
218
- Return the OpenAPI schema for cursor-paginated responses.
219
- """
220
- return {
221
- 'type': 'object',
222
- 'required': ['results'],
223
- 'properties': {
224
- 'next': {
225
- 'type': 'string',
226
- 'nullable': True,
227
- 'format': 'uri',
228
- 'description': 'URL to next page of results',
229
- 'example': 'http://api.example.org/accounts/?cursor=cD0yMDIzLTEyLTE1KzAyJTNBMDA%3D'
230
- },
231
- 'previous': {
232
- 'type': 'string',
233
- 'nullable': True,
234
- 'format': 'uri',
235
- 'description': 'URL to previous page of results',
236
- 'example': 'http://api.example.org/accounts/?cursor=bD0yMDIzLTEyLTEzKzAyJTNBMDA%3D'
237
- },
238
- 'page_size': {
239
- 'type': 'integer',
240
- 'description': 'Number of items per page',
241
- 'example': 100
242
- },
243
- 'results': {
244
- **schema,
245
- 'description': 'Array of items for current page'
246
- },
247
- },
248
- }
216
+ # def get_paginated_response_schema(self, schema):
217
+ # """
218
+ # Return the OpenAPI schema for cursor-paginated responses.
219
+ # """
220
+ # return {
221
+ # 'type': 'object',
222
+ # 'required': ['results'],
223
+ # 'properties': {
224
+ # 'next': {
225
+ # 'type': 'string',
226
+ # 'nullable': True,
227
+ # 'format': 'uri',
228
+ # 'description': 'URL to next page of results',
229
+ # 'example': 'http://api.example.org/accounts/?cursor=cD0yMDIzLTEyLTE1KzAyJTNBMDA%3D'
230
+ # },
231
+ # 'previous': {
232
+ # 'type': 'string',
233
+ # 'nullable': True,
234
+ # 'format': 'uri',
235
+ # 'description': 'URL to previous page of results',
236
+ # 'example': 'http://api.example.org/accounts/?cursor=bD0yMDIzLTEyLTEzKzAyJTNBMDA%3D'
237
+ # },
238
+ # 'page_size': {
239
+ # 'type': 'integer',
240
+ # 'description': 'Number of items per page',
241
+ # 'example': 100
242
+ # },
243
+ # 'results': {
244
+ # **schema,
245
+ # 'description': 'Array of items for current page'
246
+ # },
247
+ # },
248
+ # }
249
249
 
250
250
 
251
251
  # Export all pagination classes
@@ -254,5 +254,4 @@ __all__ = [
254
254
  'LargePagination',
255
255
  'SmallPagination',
256
256
  'NoPagination',
257
- 'CursorPaginationEnhanced',
258
257
  ]
@@ -1,33 +1,35 @@
1
1
  """
2
2
  gRPC configuration models.
3
3
 
4
- Type-safe Pydantic v2 models for gRPC server, authentication, and proto generation.
4
+ Type-safe Pydantic v2 models for gRPC integration.
5
5
 
6
6
  Requires: pip install django-cfg[grpc]
7
7
 
8
8
  Example:
9
- >>> from django_cfg.models.api.grpc import GRPCConfig, GRPCServerConfig
9
+ Flat API (recommended - no nested config imports needed):
10
+ >>> from django_cfg import GRPCConfig
10
11
  >>> config = GRPCConfig(
11
12
  ... enabled=True,
12
- ... server=GRPCServerConfig(port=50051)
13
+ ... enabled_apps=["crypto"],
14
+ ... port=50051,
15
+ ... package_prefix="api",
16
+ ... )
17
+
18
+ Advanced with nested configs (optional):
19
+ >>> from django_cfg.models.api.grpc.config import GRPCServerConfig
20
+ >>> config = GRPCConfig(
21
+ ... enabled=True,
22
+ ... server=GRPCServerConfig(max_workers=50, compression="gzip")
13
23
  ... )
14
24
  """
15
25
 
16
26
  from typing import TYPE_CHECKING
17
27
 
18
28
  if TYPE_CHECKING:
19
- from .config import (
20
- GRPCAuthConfig,
21
- GRPCConfig,
22
- GRPCProtoConfig,
23
- GRPCServerConfig,
24
- )
29
+ from .config import GRPCConfig
25
30
 
26
31
  __all__ = [
27
32
  "GRPCConfig",
28
- "GRPCServerConfig",
29
- "GRPCAuthConfig",
30
- "GRPCProtoConfig",
31
33
  ]
32
34
 
33
35
 
@@ -35,18 +37,10 @@ def __getattr__(name: str):
35
37
  """Lazy import with helpful error message."""
36
38
  if name in __all__:
37
39
  try:
38
- from .config import (
39
- GRPCAuthConfig,
40
- GRPCConfig,
41
- GRPCProtoConfig,
42
- GRPCServerConfig,
43
- )
40
+ from .config import GRPCConfig
44
41
 
45
42
  return {
46
43
  "GRPCConfig": GRPCConfig,
47
- "GRPCServerConfig": GRPCServerConfig,
48
- "GRPCAuthConfig": GRPCAuthConfig,
49
- "GRPCProtoConfig": GRPCProtoConfig,
50
44
  }[name]
51
45
 
52
46
  except ImportError as e:
@@ -53,16 +53,21 @@ class GRPCServerConfig(BaseConfig):
53
53
  le=65535,
54
54
  )
55
55
 
56
- max_workers: int = Field(
57
- default=10,
58
- description="ThreadPoolExecutor max workers",
56
+ max_concurrent_streams: Optional[int] = Field(
57
+ default=None,
58
+ description="Max concurrent streams per connection (None = unlimited, async server)",
59
59
  ge=1,
60
- le=1000,
60
+ le=10000,
61
61
  )
62
62
 
63
- enable_reflection: bool = Field(
63
+ asyncio_debug: bool = Field(
64
64
  default=False,
65
- description="Enable server reflection for dynamic clients (grpcurl, etc.)",
65
+ description="Enable asyncio debug mode (shows async warnings and coroutine leaks)",
66
+ )
67
+
68
+ enable_reflection: bool = Field(
69
+ default=True,
70
+ description="Enable server reflection for grpcurl and other tools (enabled by default)",
66
71
  )
67
72
 
68
73
  enable_health_check: bool = Field(
@@ -70,6 +75,11 @@ class GRPCServerConfig(BaseConfig):
70
75
  description="Enable gRPC health check service",
71
76
  )
72
77
 
78
+ public_url: Optional[str] = Field(
79
+ default=None,
80
+ description="Public URL for clients (auto-generated from api_url if None)",
81
+ )
82
+
73
83
  compression: Optional[str] = Field(
74
84
  default=None,
75
85
  description="Compression algorithm: 'gzip', 'deflate', or None",
@@ -124,18 +134,59 @@ class GRPCServerConfig(BaseConfig):
124
134
  raise ValueError("Host cannot be empty")
125
135
  return v.strip()
126
136
 
137
+ @model_validator(mode="after")
138
+ def auto_set_smart_defaults(self) -> "GRPCServerConfig":
139
+ """Auto-set smart defaults based on Django settings."""
140
+ try:
141
+ from django_cfg.core import get_current_config
142
+ config = get_current_config()
143
+
144
+ if config:
145
+ # Auto-set public_url from api_url
146
+ if self.public_url is None and hasattr(config, 'api_url') and config.api_url:
147
+ # https://api.djangocfg.com → grpc.djangocfg.com:50051
148
+ url = config.api_url
149
+ url = url.replace("https://", "").replace("http://", "")
150
+ url = url.replace("api.", "grpc.")
151
+ # Remove trailing slash
152
+ url = url.rstrip("/")
153
+ self.public_url = f"{url}:{self.port}"
154
+
155
+ # Auto-enable asyncio_debug in development mode
156
+ # Check if already explicitly set (if user set it, don't override)
157
+ # Only auto-enable if env_mode is development/local/dev
158
+ if hasattr(config, 'env_mode'):
159
+ is_dev = config.env_mode in ("local", "development", "dev")
160
+ # Only auto-enable if not explicitly set to False
161
+ # We check if it's still the default value (False) and enable it in dev
162
+ if is_dev and not self.asyncio_debug:
163
+ # Check Django DEBUG setting as fallback
164
+ try:
165
+ from django.conf import settings
166
+ if hasattr(settings, 'DEBUG') and settings.DEBUG:
167
+ self.asyncio_debug = True
168
+ except:
169
+ # If Django not configured yet, just use env_mode
170
+ self.asyncio_debug = True
171
+
172
+ except Exception:
173
+ # Config not available yet
174
+ pass
175
+
176
+ return self
177
+
127
178
 
128
179
  class GRPCAuthConfig(BaseConfig):
129
180
  """
130
181
  gRPC authentication configuration.
131
182
 
132
- Supports JWT authentication with configurable token handling.
183
+ Uses API key authentication with Django ORM for secure, manageable access control.
133
184
 
134
185
  Example:
135
186
  >>> config = GRPCAuthConfig(
136
187
  ... enabled=True,
137
- ... require_auth=True,
138
- ... jwt_algorithm="HS256"
188
+ ... require_auth=False,
189
+ ... accept_django_secret_key=True,
139
190
  ... )
140
191
  """
141
192
 
@@ -145,71 +196,31 @@ class GRPCAuthConfig(BaseConfig):
145
196
  )
146
197
 
147
198
  require_auth: bool = Field(
148
- default=True,
199
+ default=False, # Smart default: easy development
149
200
  description="Require authentication for all services (except public_methods)",
150
201
  )
151
202
 
152
- token_header: str = Field(
153
- default="authorization",
154
- description="Metadata key for auth token",
203
+ # === API Key Authentication ===
204
+ api_key_header: str = Field(
205
+ default="x-api-key",
206
+ description="Metadata header name for API key (default: x-api-key)",
155
207
  )
156
208
 
157
- token_prefix: str = Field(
158
- default="Bearer",
159
- description="Token prefix (e.g., 'Bearer' for JWT)",
160
- )
161
-
162
- jwt_secret_key: Optional[str] = Field(
163
- default=None,
164
- description="JWT secret key (defaults to Django SECRET_KEY if None)",
165
- )
166
-
167
- jwt_algorithm: str = Field(
168
- default="HS256",
169
- description="JWT signing algorithm",
170
- )
171
-
172
- jwt_verify_exp: bool = Field(
173
- default=True,
174
- description="Verify JWT expiration",
175
- )
176
-
177
- jwt_leeway: int = Field(
178
- default=0,
179
- description="JWT expiration leeway in seconds",
180
- ge=0,
209
+ accept_django_secret_key: bool = Field(
210
+ default=True, # Smart default: SECRET_KEY works for development
211
+ description="Accept Django SECRET_KEY as valid API key (for development/internal use)",
181
212
  )
182
213
 
214
+ # === Public Methods ===
183
215
  public_methods: List[str] = Field(
184
216
  default_factory=lambda: [
185
217
  "/grpc.health.v1.Health/Check",
186
218
  "/grpc.health.v1.Health/Watch",
219
+ "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo",
187
220
  ],
188
221
  description="RPC methods that don't require authentication",
189
222
  )
190
223
 
191
- @field_validator("jwt_algorithm")
192
- @classmethod
193
- def validate_jwt_algorithm(cls, v: str) -> str:
194
- """Validate JWT algorithm."""
195
- valid_algorithms = {
196
- "HS256",
197
- "HS384",
198
- "HS512",
199
- "RS256",
200
- "RS384",
201
- "RS512",
202
- "ES256",
203
- "ES384",
204
- "ES512",
205
- }
206
- if v not in valid_algorithms:
207
- raise ValueError(
208
- f"Invalid JWT algorithm: {v}. "
209
- f"Must be one of: {', '.join(sorted(valid_algorithms))}"
210
- )
211
- return v
212
-
213
224
 
214
225
  class GRPCProtoConfig(BaseConfig):
215
226
  """
@@ -230,9 +241,9 @@ class GRPCProtoConfig(BaseConfig):
230
241
  description="Auto-generate proto files from Django models",
231
242
  )
232
243
 
233
- output_dir: str = Field(
234
- default="protos",
235
- description="Proto files output directory (relative to BASE_DIR)",
244
+ output_dir: Optional[str] = Field(
245
+ default=None,
246
+ description="Proto files output directory (auto: media/protos if None)",
236
247
  )
237
248
 
238
249
  package_prefix: str = Field(
@@ -262,13 +273,23 @@ class GRPCProtoConfig(BaseConfig):
262
273
 
263
274
  @field_validator("output_dir")
264
275
  @classmethod
265
- def validate_output_dir(cls, v: str) -> str:
276
+ def validate_output_dir(cls, v: Optional[str]) -> Optional[str]:
266
277
  """Validate output directory."""
267
- if not v or not v.strip():
268
- raise ValueError("output_dir cannot be empty")
278
+ if v is None:
279
+ return None
280
+ if not v.strip():
281
+ raise ValueError("output_dir cannot be empty string")
269
282
  # Remove leading/trailing slashes
270
283
  return v.strip().strip("/")
271
284
 
285
+ @model_validator(mode="after")
286
+ def auto_set_output_dir(self) -> "GRPCProtoConfig":
287
+ """Auto-set output_dir to media/protos if not specified."""
288
+ if self.output_dir is None:
289
+ # Better default: generated files go to media
290
+ self.output_dir = "media/protos"
291
+ return self
292
+
272
293
 
273
294
  class GRPCConfig(BaseConfig):
274
295
  """
@@ -277,15 +298,19 @@ class GRPCConfig(BaseConfig):
277
298
  Combines server, authentication, and proto generation settings.
278
299
 
279
300
  Example:
280
- Basic setup:
281
- >>> config = GRPCConfig(enabled=True)
301
+ Simple flat API (recommended):
302
+ >>> config = GRPCConfig(
303
+ ... enabled=True,
304
+ ... enabled_apps=["crypto"],
305
+ ... package_prefix="api",
306
+ ... )
282
307
 
283
- Advanced setup:
308
+ Advanced with nested configs (optional):
284
309
  >>> config = GRPCConfig(
285
310
  ... enabled=True,
286
311
  ... server=GRPCServerConfig(port=8080, max_workers=50),
287
312
  ... auth=GRPCAuthConfig(require_auth=True),
288
- ... auto_register_apps=["accounts", "support"]
313
+ ... enabled_apps=["accounts", "support"]
289
314
  ... )
290
315
  """
291
316
 
@@ -294,19 +319,55 @@ class GRPCConfig(BaseConfig):
294
319
  description="Enable gRPC integration",
295
320
  )
296
321
 
322
+ # === Flatten Server Config (most common settings) ===
323
+ # These are shortcuts that configure the nested server config
324
+ host: Optional[str] = Field(
325
+ default=None,
326
+ description="Server bind address (e.g., '[::]' for IPv6, '0.0.0.0' for IPv4). If None, uses server.host default",
327
+ )
328
+
329
+ port: Optional[int] = Field(
330
+ default=None,
331
+ description="Server port (e.g., 50051). If None, uses server.port default",
332
+ ge=1024,
333
+ le=65535,
334
+ )
335
+
336
+ public_url: Optional[str] = Field(
337
+ default=None,
338
+ description="Public URL for clients (e.g., 'grpc.djangocfg.com:443'). If None, auto-generated from api_url",
339
+ )
340
+
341
+ enable_reflection: Optional[bool] = Field(
342
+ default=None,
343
+ description="Enable server reflection for grpcurl/tools. If None, uses server.enable_reflection (True by default)",
344
+ )
345
+
346
+ # === Flatten Proto Config (most common settings) ===
347
+ package_prefix: Optional[str] = Field(
348
+ default=None,
349
+ description="Package prefix for proto files (e.g., 'api'). If None, uses proto.package_prefix default",
350
+ )
351
+
352
+ output_dir: Optional[str] = Field(
353
+ default=None,
354
+ description="Proto files output directory. If None, uses proto.output_dir default (media/protos)",
355
+ )
356
+
357
+ # === Nested Configs (for advanced use) ===
297
358
  server: GRPCServerConfig = Field(
298
359
  default_factory=GRPCServerConfig,
299
- description="Server configuration",
360
+ description="Advanced server configuration (optional, use flatten fields above for common settings)",
300
361
  )
301
362
 
302
363
  auth: GRPCAuthConfig = Field(
303
364
  default_factory=GRPCAuthConfig,
304
- description="Authentication configuration",
365
+ description="Authentication configuration (optional)",
305
366
  )
306
367
 
307
368
  proto: GRPCProtoConfig = Field(
308
369
  default_factory=GRPCProtoConfig,
309
- description="Proto generation configuration",
370
+ description="Proto generation configuration (optional, use flatten fields above for common settings)",
310
371
  )
311
372
 
312
373
  handlers_hook: str = Field(
@@ -338,7 +399,28 @@ class GRPCConfig(BaseConfig):
338
399
 
339
400
  @model_validator(mode="after")
340
401
  def validate_grpc_config(self) -> "GRPCConfig":
341
- """Cross-field validation."""
402
+ """
403
+ Cross-field validation and apply flatten fields to nested configs.
404
+
405
+ This allows users to configure common settings at the top level without
406
+ importing nested config classes.
407
+ """
408
+ # Apply flatten server fields to nested server config
409
+ if self.host is not None:
410
+ self.server.host = self.host
411
+ if self.port is not None:
412
+ self.server.port = self.port
413
+ if self.public_url is not None:
414
+ self.server.public_url = self.public_url
415
+ if self.enable_reflection is not None:
416
+ self.server.enable_reflection = self.enable_reflection
417
+
418
+ # Apply flatten proto fields to nested proto config
419
+ if self.package_prefix is not None:
420
+ self.proto.package_prefix = self.package_prefix
421
+ if self.output_dir is not None:
422
+ self.proto.output_dir = self.output_dir
423
+
342
424
  # Check dependencies if enabled
343
425
  if self.enabled:
344
426
  from django_cfg.apps.integrations.grpc._cfg import require_grpc_feature
@@ -55,14 +55,15 @@ class NgrokConfig(BaseModel):
55
55
  def validate_enabled_in_debug_only(cls, v: bool) -> bool:
56
56
  """Ensure ngrok is only enabled in debug mode."""
57
57
  if v:
58
- # Only check if Django is available and fully configured
58
+ # Use get_current_config() approach (same as GRPCServerConfig)
59
59
  try:
60
- from django.conf import settings
61
- # Only validate if settings are configured and DEBUG attribute exists
62
- if settings.configured and hasattr(settings, 'DEBUG') and not settings.DEBUG:
60
+ from django_cfg.core import get_current_config
61
+
62
+ config = get_current_config()
63
+ if config and not config.debug:
63
64
  raise ValueError("Ngrok can only be enabled in DEBUG mode")
64
- except (ImportError, AttributeError, RuntimeError):
65
- # Django not available, not configured, or settings not ready - skip validation
65
+ except Exception:
66
+ # Config not available yet - skip validation
66
67
  pass
67
68
  return v
68
69
 
@@ -161,20 +161,12 @@ class FilesGenerator:
161
161
  )
162
162
 
163
163
  def generate_schema_file(self, openapi_schema: dict) -> GeneratedFile:
164
- """Generate schema.py with OpenAPI schema as dict."""
165
- # First, convert to pretty JSON
166
- schema_json = json.dumps(openapi_schema, indent=4, ensure_ascii=False)
167
-
168
- # Convert JSON literals to Python literals
169
- schema_json = re.sub(r'\btrue\b', 'True', schema_json)
170
- schema_json = re.sub(r'\bfalse\b', 'False', schema_json)
171
- schema_json = re.sub(r'\bnull\b', 'None', schema_json)
172
-
173
- template = self.jinja_env.get_template('utils/schema.py.jinja')
174
- content = template.render(schema_dict=schema_json)
164
+ """Generate schema.json with OpenAPI schema."""
165
+ # Generate JSON file with proper formatting
166
+ content = json.dumps(openapi_schema, indent=4, ensure_ascii=False)
175
167
 
176
168
  return GeneratedFile(
177
- path="schema.py",
169
+ path="schema.json",
178
170
  content=content,
179
- description="OpenAPI Schema",
171
+ description="OpenAPI Schema (JSON format)",
180
172
  )
@@ -127,14 +127,26 @@ class API:
127
127
  """Get current base URL."""
128
128
  return self.base_url
129
129
 
130
- def get_schema(self) -> dict[str, Any]:
130
+ def get_schema_path(self) -> str:
131
131
  """
132
- Get OpenAPI schema.
132
+ Get OpenAPI schema path.
133
133
 
134
134
  Returns:
135
- Complete OpenAPI specification for this API
135
+ Path to the OpenAPI schema JSON file
136
+
137
+ Note:
138
+ The OpenAPI schema is available in the schema.json file.
139
+ You can load it dynamically using:
140
+ ```python
141
+ import json
142
+ from pathlib import Path
143
+
144
+ schema_path = Path(__file__).parent / 'schema.json'
145
+ with open(schema_path) as f:
146
+ schema = json.load(f)
147
+ ```
136
148
  """
137
- return OPENAPI_SCHEMA
149
+ return './schema.json'
138
150
 
139
151
  async def __aenter__(self) -> 'API':
140
152
  """Async context manager entry."""
@@ -18,8 +18,8 @@ Usage:
18
18
  >>> if api.is_authenticated():
19
19
  ... # ...
20
20
  >>>
21
- >>> # Get OpenAPI schema
22
- >>> schema = api.get_schema()
21
+ >>> # Get OpenAPI schema path
22
+ >>> schema_path = api.get_schema_path()
23
23
  """
24
24
 
25
25
  from __future__ import annotations
@@ -30,7 +30,6 @@ from typing import Any
30
30
  import httpx
31
31
 
32
32
  from .client import APIClient
33
- from .schema import OPENAPI_SCHEMA
34
33
  from .logger import LoggerConfig
35
34
  from .retry import RetryConfig
36
35
  {% for tag in tags %}
@@ -196,13 +196,14 @@ class FilesGenerator:
196
196
  )
197
197
 
198
198
  def generate_schema_file(self):
199
- """Generate schema.ts with OpenAPI schema as const."""
199
+ """Generate schema.json with OpenAPI schema."""
200
+ import json
200
201
 
201
- template = self.jinja_env.get_template('utils/schema.ts.jinja')
202
- content = template.render(schema=self.openapi_schema)
202
+ # Generate JSON file with proper formatting
203
+ content = json.dumps(self.openapi_schema, indent=2, ensure_ascii=False)
203
204
 
204
205
  return GeneratedFile(
205
- path="schema.ts",
206
+ path="schema.json",
206
207
  content=content,
207
- description="OpenAPI Schema",
208
+ description="OpenAPI Schema (JSON format)",
208
209
  )