django-cfg 1.4.72__py3-none-any.whl → 1.4.74__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 (31) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/endpoints/README.md +144 -0
  3. django_cfg/apps/api/endpoints/endpoints_status/__init__.py +13 -0
  4. django_cfg/apps/api/endpoints/urls.py +13 -6
  5. django_cfg/apps/api/endpoints/urls_list/__init__.py +10 -0
  6. django_cfg/apps/api/endpoints/urls_list/serializers.py +74 -0
  7. django_cfg/apps/api/endpoints/urls_list/views.py +231 -0
  8. django_cfg/apps/api/health/drf_views.py +9 -0
  9. django_cfg/apps/api/health/serializers.py +4 -0
  10. django_cfg/models/django/crypto_fields.py +11 -0
  11. django_cfg/modules/django_client/core/__init__.py +2 -1
  12. django_cfg/modules/django_client/core/archive/manager.py +14 -0
  13. django_cfg/modules/django_client/core/generator/__init__.py +40 -2
  14. django_cfg/modules/django_client/core/generator/proto/__init__.py +17 -0
  15. django_cfg/modules/django_client/core/generator/proto/generator.py +461 -0
  16. django_cfg/modules/django_client/core/generator/proto/messages_generator.py +260 -0
  17. django_cfg/modules/django_client/core/generator/proto/services_generator.py +295 -0
  18. django_cfg/modules/django_client/core/generator/proto/test_proto_generator.py +262 -0
  19. django_cfg/modules/django_client/core/generator/proto/type_mapper.py +153 -0
  20. django_cfg/modules/django_client/management/commands/generate_client.py +49 -3
  21. django_cfg/pyproject.toml +1 -1
  22. {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/METADATA +1 -1
  23. {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/RECORD +31 -20
  24. /django_cfg/apps/api/endpoints/{checker.py → endpoints_status/checker.py} +0 -0
  25. /django_cfg/apps/api/endpoints/{drf_views.py → endpoints_status/drf_views.py} +0 -0
  26. /django_cfg/apps/api/endpoints/{serializers.py → endpoints_status/serializers.py} +0 -0
  27. /django_cfg/apps/api/endpoints/{tests.py → endpoints_status/tests.py} +0 -0
  28. /django_cfg/apps/api/endpoints/{views.py → endpoints_status/views.py} +0 -0
  29. {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/WHEEL +0 -0
  30. {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/entry_points.txt +0 -0
  31. {django_cfg-1.4.72.dist-info → django_cfg-1.4.74.dist-info}/licenses/LICENSE +0 -0
@@ -25,6 +25,7 @@ from typing import Literal
25
25
  from ..ir import IRContext
26
26
  from .base import GeneratedFile
27
27
  from .go import GoGenerator
28
+ from .proto import ProtoGenerator
28
29
  from .python import PythonGenerator
29
30
  from .typescript import TypeScriptGenerator
30
31
 
@@ -32,10 +33,12 @@ __all__ = [
32
33
  "PythonGenerator",
33
34
  "TypeScriptGenerator",
34
35
  "GoGenerator",
36
+ "ProtoGenerator",
35
37
  "GeneratedFile",
36
38
  "generate_python",
37
39
  "generate_typescript",
38
40
  "generate_go",
41
+ "generate_proto",
39
42
  "generate_client",
40
43
  ]
41
44
 
@@ -122,9 +125,41 @@ def generate_go(context: IRContext, output_dir: Path | None = None, **kwargs) ->
122
125
  return files
123
126
 
124
127
 
128
+ def generate_proto(context: IRContext, output_dir: Path | None = None, **kwargs) -> list[GeneratedFile]:
129
+ """
130
+ Generate Protocol Buffer definitions from IR.
131
+
132
+ Args:
133
+ context: IRContext from parser
134
+ output_dir: Optional output directory (saves files if provided)
135
+ **kwargs: Additional options (split_files, package_name, etc.)
136
+
137
+ Returns:
138
+ List of GeneratedFile objects
139
+
140
+ Examples:
141
+ >>> files = generate_proto(context)
142
+ >>> # Or save directly
143
+ >>> files = generate_proto(context, output_dir=Path("./generated/proto"))
144
+ >>> # With custom settings
145
+ >>> files = generate_proto(
146
+ ... context,
147
+ ... split_files=False, # Single api.proto file
148
+ ... package_name="myapi.v1"
149
+ ... )
150
+ """
151
+ generator = ProtoGenerator(context, **kwargs)
152
+ files = generator.generate()
153
+
154
+ if output_dir:
155
+ generator.save_files(files, output_dir)
156
+
157
+ return files
158
+
159
+
125
160
  def generate_client(
126
161
  context: IRContext,
127
- language: Literal["python", "typescript", "go"],
162
+ language: Literal["python", "typescript", "go", "proto"],
128
163
  output_dir: Path | None = None,
129
164
  **kwargs,
130
165
  ) -> list[GeneratedFile]:
@@ -133,7 +168,7 @@ def generate_client(
133
168
 
134
169
  Args:
135
170
  context: IRContext from parser
136
- language: Target language ('python', 'typescript', or 'go')
171
+ language: Target language ('python', 'typescript', 'go', or 'proto')
137
172
  output_dir: Optional output directory
138
173
  **kwargs: Additional language-specific options
139
174
 
@@ -144,6 +179,7 @@ def generate_client(
144
179
  >>> files = generate_client(context, "python")
145
180
  >>> files = generate_client(context, "typescript", Path("./generated"))
146
181
  >>> files = generate_client(context, "go", Path("./generated"), generate_package_files=True)
182
+ >>> files = generate_client(context, "proto", Path("./generated"), split_files=False)
147
183
  """
148
184
  if language == "python":
149
185
  return generate_python(context, output_dir)
@@ -151,5 +187,7 @@ def generate_client(
151
187
  return generate_typescript(context, output_dir)
152
188
  elif language == "go":
153
189
  return generate_go(context, output_dir, **kwargs)
190
+ elif language == "proto":
191
+ return generate_proto(context, output_dir, **kwargs)
154
192
  else:
155
193
  raise ValueError(f"Unsupported language: {language}")
@@ -0,0 +1,17 @@
1
+ """
2
+ Proto Generator Module - Protocol Buffer/gRPC code generation.
3
+
4
+ Generates .proto files from OpenAPI/IR for gRPC client generation.
5
+ """
6
+
7
+ from .generator import ProtoGenerator
8
+ from .messages_generator import ProtoMessagesGenerator
9
+ from .services_generator import ProtoServicesGenerator
10
+ from .type_mapper import ProtoTypeMapper
11
+
12
+ __all__ = [
13
+ "ProtoGenerator",
14
+ "ProtoTypeMapper",
15
+ "ProtoMessagesGenerator",
16
+ "ProtoServicesGenerator",
17
+ ]
@@ -0,0 +1,461 @@
1
+ """
2
+ Proto Generator - Main Protocol Buffer code generator.
3
+
4
+ Generates .proto files from IR (Intermediate Representation) for gRPC client generation.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ from ..base import BaseGenerator, GeneratedFile
12
+ from .messages_generator import ProtoMessagesGenerator
13
+ from .services_generator import ProtoServicesGenerator
14
+ from .type_mapper import ProtoTypeMapper
15
+
16
+ if TYPE_CHECKING:
17
+ from django_cfg.modules.django_client.core.ir import IRContext, IROperationObject, IRSchemaObject
18
+
19
+
20
+ class ProtoGenerator(BaseGenerator):
21
+ """
22
+ Protocol Buffer generator for gRPC clients.
23
+
24
+ Generates:
25
+ - messages.proto: Message definitions (models)
26
+ - services.proto: Service and RPC definitions (API endpoints)
27
+ - Or combined api.proto with both messages and services
28
+
29
+ The generated .proto files can be used with protoc to generate:
30
+ - Python gRPC client (via grpc_tools.protoc)
31
+ - Go gRPC client (via protoc-gen-go and protoc-gen-go-grpc)
32
+ - TypeScript gRPC client (via protoc-gen-ts)
33
+ - Any other language with protoc support
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ context: IRContext,
39
+ split_files: bool = True,
40
+ package_name: str | None = None,
41
+ **kwargs,
42
+ ):
43
+ """
44
+ Initialize Proto generator.
45
+
46
+ Args:
47
+ context: IRContext from parser
48
+ split_files: If True, generate separate messages.proto and services.proto
49
+ If False, generate single api.proto
50
+ package_name: Proto package name (e.g., "myapi.v1")
51
+ Defaults to "api.v1"
52
+ **kwargs: Additional arguments passed to BaseGenerator
53
+ """
54
+ super().__init__(context, **kwargs)
55
+
56
+ self.split_files = split_files
57
+ self.package_name = package_name or "api.v1"
58
+
59
+ # Initialize sub-generators
60
+ self.type_mapper = ProtoTypeMapper()
61
+ self.messages_generator = ProtoMessagesGenerator(self.type_mapper)
62
+ self.services_generator = ProtoServicesGenerator(self.type_mapper, context)
63
+
64
+ def generate(self) -> list[GeneratedFile]:
65
+ """
66
+ Generate all proto files.
67
+
68
+ Returns:
69
+ List of GeneratedFile objects organized by service/tag
70
+ """
71
+ files = []
72
+
73
+ # Group operations by tag (similar to other generators)
74
+ ops_by_tag = self.group_operations_by_tag()
75
+
76
+ # Generate proto files for each tag/service
77
+ for tag, operations in sorted(ops_by_tag.items()):
78
+ folder_name = self.tag_and_app_to_folder_name(tag, operations)
79
+
80
+ # Get schemas used by this service
81
+ service_schemas = self._get_schemas_for_operations(operations)
82
+
83
+ # Generate messages.proto for this service
84
+ messages_file = self._generate_service_messages_file(
85
+ folder_name, tag, service_schemas
86
+ )
87
+ files.append(messages_file)
88
+
89
+ # Generate service.proto for this service
90
+ service_file = self._generate_service_file(
91
+ folder_name, tag, operations
92
+ )
93
+ files.append(service_file)
94
+
95
+ # Generate root README.md with protoc compilation instructions
96
+ readme_file = self._generate_readme_file(ops_by_tag)
97
+ files.append(readme_file)
98
+
99
+ return files
100
+
101
+ def _get_schemas_for_operations(self, operations: list[IROperationObject]) -> dict[str, IRSchemaObject]:
102
+ """
103
+ Get all schemas used by given operations.
104
+
105
+ This resolves all schema dependencies to ensure nested schemas are included.
106
+ """
107
+ schemas = {}
108
+
109
+ def add_schema(schema_name: str):
110
+ """Recursively add schema and its dependencies."""
111
+ if schema_name in schemas or schema_name not in self.context.schemas:
112
+ return
113
+
114
+ schema = self.context.schemas[schema_name]
115
+ schemas[schema_name] = schema
116
+
117
+ # Recursively add referenced schemas
118
+ if schema.properties:
119
+ for prop_schema in schema.properties.values():
120
+ if prop_schema.ref and prop_schema.ref in self.context.schemas:
121
+ add_schema(prop_schema.ref)
122
+ elif prop_schema.type == "array" and prop_schema.items:
123
+ if prop_schema.items.ref:
124
+ add_schema(prop_schema.items.ref)
125
+
126
+ for operation in operations:
127
+ # Request body schemas
128
+ if operation.request_body and operation.request_body.schema_name:
129
+ schema_name = operation.request_body.schema_name
130
+ # Check in schemas, request_models, and response_models
131
+ if schema_name in self.context.schemas:
132
+ add_schema(schema_name)
133
+ elif schema_name in self.context.request_models:
134
+ schemas[schema_name] = self.context.request_models[schema_name]
135
+
136
+ # Patch request body schemas (important for PATCH operations!)
137
+ if hasattr(operation, 'patch_request_body') and operation.patch_request_body and operation.patch_request_body.schema_name:
138
+ schema_name = operation.patch_request_body.schema_name
139
+ # Check in schemas, request_models, patch_models
140
+ if schema_name in self.context.schemas:
141
+ add_schema(schema_name)
142
+ elif schema_name in self.context.patch_models:
143
+ schemas[schema_name] = self.context.patch_models[schema_name]
144
+ elif schema_name in self.context.request_models:
145
+ schemas[schema_name] = self.context.request_models[schema_name]
146
+
147
+ # Response schemas
148
+ for response in operation.responses.values():
149
+ if response.schema_name:
150
+ schema_name = response.schema_name
151
+ if schema_name in self.context.schemas:
152
+ add_schema(schema_name)
153
+ elif schema_name in self.context.response_models:
154
+ schemas[schema_name] = self.context.response_models[schema_name]
155
+
156
+ # Parameter schemas (if they reference components)
157
+ for param in operation.parameters:
158
+ if hasattr(param, 'schema_name') and param.schema_name:
159
+ if param.schema_name in self.context.schemas:
160
+ add_schema(param.schema_name)
161
+
162
+ return schemas
163
+
164
+ def _generate_service_messages_file(
165
+ self, folder_name: str, tag: str, schemas: dict[str, IRSchemaObject]
166
+ ) -> GeneratedFile:
167
+ """Generate messages.proto file for a specific service."""
168
+ # Generate message definitions for these schemas
169
+ self.messages_generator.generate_all_messages(schemas)
170
+ messages_content = self.messages_generator.get_all_definitions()
171
+
172
+ # Build proto file content
173
+ content = self._build_proto_header(f"{folder_name}/messages.proto", tag)
174
+
175
+ if messages_content:
176
+ content += "\n\n" + messages_content
177
+
178
+ return GeneratedFile(
179
+ path=f"{folder_name}/messages.proto",
180
+ content=content,
181
+ description=f"Protocol Buffer message definitions for {tag}",
182
+ )
183
+
184
+ def _generate_service_file(
185
+ self, folder_name: str, tag: str, operations: list[IROperationObject]
186
+ ) -> GeneratedFile:
187
+ """Generate service.proto file for a specific service."""
188
+ # Generate service definitions from operations
189
+ service_definitions = self.services_generator.generate_all_services(operations)
190
+
191
+ # Build proto file content
192
+ content = self._build_proto_header(f"{folder_name}/service.proto", tag)
193
+ # Import messages.proto from the same folder
194
+ content += f'\nimport "{folder_name}/messages.proto";\n'
195
+
196
+ # Add all service definitions
197
+ for service_name, service_def in service_definitions.items():
198
+ content += "\n\n" + service_def
199
+
200
+ return GeneratedFile(
201
+ path=f"{folder_name}/service.proto",
202
+ content=content,
203
+ description=f"gRPC service definitions for {tag}",
204
+ )
205
+
206
+ def _generate_readme_file(self, ops_by_tag: dict[str, list[IROperationObject]]) -> GeneratedFile:
207
+ """Generate README.md with protoc compilation instructions."""
208
+ lines = [
209
+ "# Protocol Buffer Definitions",
210
+ "",
211
+ f"Generated from OpenAPI specification for package `{self.package_name}`",
212
+ "",
213
+ "## Structure",
214
+ "",
215
+ "Each service has its own folder containing:",
216
+ "- `messages.proto` - Message definitions (models)",
217
+ "- `service.proto` - Service and RPC definitions",
218
+ "",
219
+ "## Services",
220
+ "",
221
+ ]
222
+
223
+ for tag in sorted(ops_by_tag.keys()):
224
+ operations = ops_by_tag[tag]
225
+ folder_name = self.tag_and_app_to_folder_name(tag, operations)
226
+ lines.append(f"- **{tag}**: `{folder_name}/` ({len(operations)} operations)")
227
+
228
+ lines.extend([
229
+ "",
230
+ "## Compilation",
231
+ "",
232
+ "### Python (grpc_tools)",
233
+ "```bash",
234
+ "# Install dependencies",
235
+ "pip install grpcio grpcio-tools",
236
+ "",
237
+ "# Compile each service",
238
+ ])
239
+
240
+ for tag in sorted(ops_by_tag.keys()):
241
+ operations = ops_by_tag[tag]
242
+ folder_name = self.tag_and_app_to_folder_name(tag, operations)
243
+ lines.append(f"python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. {folder_name}/*.proto")
244
+
245
+ lines.extend([
246
+ "```",
247
+ "",
248
+ "### Go",
249
+ "```bash",
250
+ "# Install dependencies",
251
+ "go install google.golang.org/protobuf/cmd/protoc-gen-go@latest",
252
+ "go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest",
253
+ "",
254
+ "# Compile each service",
255
+ ])
256
+
257
+ for tag in sorted(ops_by_tag.keys()):
258
+ operations = ops_by_tag[tag]
259
+ folder_name = self.tag_and_app_to_folder_name(tag, operations)
260
+ lines.append(f"protoc -I. --go_out=. --go-grpc_out=. {folder_name}/*.proto")
261
+
262
+ lines.extend([
263
+ "```",
264
+ "",
265
+ "### TypeScript (ts-proto)",
266
+ "```bash",
267
+ "# Install dependencies",
268
+ "npm install ts-proto",
269
+ "",
270
+ "# Compile each service",
271
+ ])
272
+
273
+ for tag in sorted(ops_by_tag.keys()):
274
+ operations = ops_by_tag[tag]
275
+ folder_name = self.tag_and_app_to_folder_name(tag, operations)
276
+ lines.append(f"protoc -I. --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=. {folder_name}/*.proto")
277
+
278
+ lines.extend([
279
+ "```",
280
+ "",
281
+ "## Usage Example",
282
+ "",
283
+ "After compilation, you can use the generated clients in your application.",
284
+ "",
285
+ "### Python",
286
+ "```python",
287
+ "import grpc",
288
+ f"from {self.package_name.replace('.', '_')} import service_pb2, service_pb2_grpc",
289
+ "",
290
+ "# Create channel",
291
+ "channel = grpc.insecure_channel('localhost:50051')",
292
+ "",
293
+ "# Create stub",
294
+ "stub = service_pb2_grpc.YourServiceStub(channel)",
295
+ "",
296
+ "# Make request",
297
+ "request = service_pb2.YourRequest(field='value')",
298
+ "response = stub.YourMethod(request)",
299
+ "```",
300
+ "",
301
+ "---",
302
+ "",
303
+ "*Generated by django-cfg django_client module*",
304
+ ])
305
+
306
+ return GeneratedFile(
307
+ path="README.md",
308
+ content="\n".join(lines),
309
+ description="Protocol Buffer compilation and usage instructions",
310
+ )
311
+
312
+ def _generate_messages_file(self) -> GeneratedFile:
313
+ """Generate messages.proto file with all message definitions."""
314
+ # Collect all schemas from context
315
+ all_schemas = {
316
+ **self.context.schemas,
317
+ **self.context.request_models,
318
+ **self.context.response_models,
319
+ **self.context.patch_models,
320
+ }
321
+
322
+ # Generate message definitions
323
+ self.messages_generator.generate_all_messages(all_schemas)
324
+ messages_content = self.messages_generator.get_all_definitions()
325
+
326
+ # Build proto file content
327
+ content = self._build_proto_header("messages.proto")
328
+
329
+ if messages_content:
330
+ content += "\n\n" + messages_content
331
+
332
+ return GeneratedFile(
333
+ path="messages.proto",
334
+ content=content,
335
+ description="Protocol Buffer message definitions",
336
+ )
337
+
338
+ def _generate_services_file(self) -> GeneratedFile:
339
+ """Generate services.proto file with all service definitions."""
340
+ # Generate service definitions from operations
341
+ operations = list(self.context.operations.values())
342
+ service_definitions = self.services_generator.generate_all_services(operations)
343
+
344
+ # Build proto file content
345
+ content = self._build_proto_header("services.proto")
346
+ content += '\nimport "messages.proto";\n'
347
+
348
+ # Add all service definitions
349
+ for service_name, service_def in service_definitions.items():
350
+ content += "\n\n" + service_def
351
+
352
+ return GeneratedFile(
353
+ path="services.proto",
354
+ content=content,
355
+ description="gRPC service definitions",
356
+ )
357
+
358
+ def _generate_combined_file(self) -> GeneratedFile:
359
+ """Generate single api.proto file with both messages and services."""
360
+ # Collect all schemas
361
+ all_schemas = {
362
+ **self.context.schemas,
363
+ **self.context.request_models,
364
+ **self.context.response_models,
365
+ **self.context.patch_models,
366
+ }
367
+
368
+ # Generate message definitions
369
+ self.messages_generator.generate_all_messages(all_schemas)
370
+ messages_content = self.messages_generator.get_all_definitions()
371
+
372
+ # Generate service definitions
373
+ operations = list(self.context.operations.values())
374
+ service_definitions = self.services_generator.generate_all_services(operations)
375
+
376
+ # Build combined proto file
377
+ content = self._build_proto_header("api.proto")
378
+
379
+ # Add messages first
380
+ if messages_content:
381
+ content += "\n\n// ===== Messages =====\n\n"
382
+ content += messages_content
383
+
384
+ # Add services
385
+ if service_definitions:
386
+ content += "\n\n// ===== Services =====\n\n"
387
+ for service_name, service_def in service_definitions.items():
388
+ content += service_def + "\n\n"
389
+
390
+ return GeneratedFile(
391
+ path="api.proto",
392
+ content=content,
393
+ description="Combined Protocol Buffer definitions",
394
+ )
395
+
396
+ def _build_proto_header(self, file_name: str, tag: str | None = None) -> str:
397
+ """
398
+ Build proto file header with syntax, package, and imports.
399
+
400
+ Args:
401
+ file_name: Name of the proto file
402
+ tag: Optional service tag for package naming
403
+
404
+ Returns:
405
+ Header string with syntax declaration, package, and imports
406
+ """
407
+ # Use tag-specific package if provided
408
+ package_name = f"{self.package_name}.{self.tag_to_property_name(tag)}" if tag else self.package_name
409
+
410
+ lines = [
411
+ f'// {file_name}',
412
+ '// Generated by django-cfg django_client module',
413
+ '// DO NOT EDIT - This file is auto-generated',
414
+ '',
415
+ 'syntax = "proto3";',
416
+ '',
417
+ f'package {package_name};',
418
+ '',
419
+ ]
420
+
421
+ # Add required imports
422
+ imports = self.type_mapper.get_required_imports()
423
+ if imports:
424
+ for import_path in imports:
425
+ lines.append(f'import "{import_path}";')
426
+ lines.append('')
427
+
428
+ return '\n'.join(lines)
429
+
430
+ # ===== Abstract Method Implementations (Not used for proto) =====
431
+
432
+ def generate_schema(self, schema: IRSchemaObject) -> str:
433
+ """
434
+ Generate proto message for a single schema.
435
+
436
+ Note: This is called by BaseGenerator abstract method requirement,
437
+ but proto generation works differently - we generate all messages at once.
438
+ """
439
+ return self.messages_generator.generate_message(schema)
440
+
441
+ def generate_enum(self, schema: IRSchemaObject) -> str:
442
+ """
443
+ Generate proto enum from schema.
444
+
445
+ Note: This is called by BaseGenerator abstract method requirement,
446
+ but enums are generated as part of message generation in proto.
447
+ """
448
+ if not schema.enum or not schema.name:
449
+ return ""
450
+
451
+ return self.messages_generator._generate_enum(schema, schema.name)
452
+
453
+ def generate_operation(self, operation: IROperationObject) -> str:
454
+ """
455
+ Generate RPC definition for a single operation.
456
+
457
+ Note: This is called by BaseGenerator abstract method requirement,
458
+ but proto generation works differently - we generate all services at once.
459
+ """
460
+ service_name, definitions = self.services_generator.generate_rpc(operation)
461
+ return '\n'.join(definitions)