django-cfg 1.4.62__py3-none-any.whl → 1.4.63__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.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +3 -14
- django_cfg/apps/centrifugo/__init__.py +57 -0
- django_cfg/apps/centrifugo/admin/__init__.py +13 -0
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
- django_cfg/apps/centrifugo/admin/config.py +82 -0
- django_cfg/apps/centrifugo/apps.py +31 -0
- django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
- django_cfg/apps/centrifugo/codegen/README.md +242 -0
- django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
- django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
- django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
- django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
- django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
- django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
- django_cfg/apps/centrifugo/decorators.py +137 -0
- django_cfg/apps/centrifugo/management/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
- django_cfg/apps/centrifugo/managers/__init__.py +12 -0
- django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
- django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
- django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
- django_cfg/apps/centrifugo/models/__init__.py +11 -0
- django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
- django_cfg/apps/centrifugo/registry.py +106 -0
- django_cfg/apps/centrifugo/router.py +125 -0
- django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
- django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
- django_cfg/apps/centrifugo/serializers/channels.py +26 -0
- django_cfg/apps/centrifugo/serializers/health.py +17 -0
- django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
- django_cfg/apps/centrifugo/serializers/stats.py +21 -0
- django_cfg/apps/centrifugo/services/__init__.py +12 -0
- django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
- django_cfg/apps/centrifugo/services/client/client.py +577 -0
- django_cfg/apps/centrifugo/services/client/config.py +228 -0
- django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
- django_cfg/apps/centrifugo/services/config_helper.py +63 -0
- django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
- django_cfg/apps/centrifugo/services/logging.py +677 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
- django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
- django_cfg/apps/centrifugo/urls.py +31 -0
- django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
- django_cfg/apps/centrifugo/views/__init__.py +15 -0
- django_cfg/apps/centrifugo/views/admin_api.py +374 -0
- django_cfg/apps/centrifugo/views/dashboard.py +15 -0
- django_cfg/apps/centrifugo/views/monitoring.py +286 -0
- django_cfg/apps/centrifugo/views/testing_api.py +422 -0
- django_cfg/apps/support/utils/support_email_service.py +5 -18
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
- django_cfg/apps/urls.py +5 -5
- django_cfg/core/base/config_model.py +4 -44
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +8 -8
- django_cfg/core/utils/__init__.py +5 -0
- django_cfg/core/utils/url_helpers.py +73 -0
- django_cfg/modules/base.py +7 -7
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/config/config.py +8 -0
- django_cfg/modules/django_client/core/generator/__init__.py +42 -2
- django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
- django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
- django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
- django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
- django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
- django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
- django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
- django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
- django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
- django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
- django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
- django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
- django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
- django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
- django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
- django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
- django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
- django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
- django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
- django_cfg/modules/django_client/system/schema_parser.py +5 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
- django_cfg/modules/django_unfold/dashboard.py +25 -19
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/core.py +2 -0
- django_cfg/registry/modules.py +2 -2
- django_cfg/static/js/api/centrifugo/client.mjs +164 -0
- django_cfg/static/js/api/centrifugo/index.mjs +13 -0
- django_cfg/static/js/api/index.mjs +5 -5
- django_cfg/static/js/api/types.mjs +89 -26
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -70
- django_cfg/apps/ipc/README.md +0 -346
- django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
- django_cfg/apps/ipc/TESTING.md +0 -539
- django_cfg/apps/ipc/__init__.py +0 -60
- django_cfg/apps/ipc/admin.py +0 -232
- django_cfg/apps/ipc/apps.py +0 -98
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/0002_rpclog_is_event.py +0 -23
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -229
- django_cfg/apps/ipc/serializers/__init__.py +0 -29
- django_cfg/apps/ipc/serializers/serializers.py +0 -343
- django_cfg/apps/ipc/services/__init__.py +0 -7
- django_cfg/apps/ipc/services/client/__init__.py +0 -23
- django_cfg/apps/ipc/services/client/client.py +0 -621
- django_cfg/apps/ipc/services/client/config.py +0 -214
- django_cfg/apps/ipc/services/client/exceptions.py +0 -201
- django_cfg/apps/ipc/services/logging.py +0 -239
- django_cfg/apps/ipc/services/monitor.py +0 -466
- django_cfg/apps/ipc/services/rpc_log_consumer.py +0 -330
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
- django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
- django_cfg/apps/ipc/urls.py +0 -23
- django_cfg/apps/ipc/views/__init__.py +0 -13
- django_cfg/apps/ipc/views/dashboard.py +0 -15
- django_cfg/apps/ipc/views/monitoring.py +0 -251
- django_cfg/apps/ipc/views/testing.py +0 -285
- django_cfg/static/js/api/ipc/client.mjs +0 -114
- django_cfg/static/js/api/ipc/index.mjs +0 -13
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.62.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Files Generator - Generates auxiliary files (go.mod, README, Makefile, etc.).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from jinja2 import Environment
|
|
12
|
+
|
|
13
|
+
from ...ir import IRContext
|
|
14
|
+
from ..base import GeneratedFile
|
|
15
|
+
from .generator import GoGenerator
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FilesGenerator:
|
|
19
|
+
"""Generates auxiliary files for Go client."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
jinja_env: Environment,
|
|
24
|
+
context: IRContext,
|
|
25
|
+
generator: GoGenerator,
|
|
26
|
+
):
|
|
27
|
+
"""Initialize files generator."""
|
|
28
|
+
self.jinja_env = jinja_env
|
|
29
|
+
self.context = context
|
|
30
|
+
self.generator = generator
|
|
31
|
+
|
|
32
|
+
def generate_go_mod(self) -> GeneratedFile:
|
|
33
|
+
"""Generate go.mod file."""
|
|
34
|
+
template = self.jinja_env.get_template("go.mod.j2")
|
|
35
|
+
|
|
36
|
+
module_name = self.generator.package_config.get("module_name", "apiclient")
|
|
37
|
+
go_version = self.generator.package_config.get("go_version", "1.21")
|
|
38
|
+
|
|
39
|
+
content = template.render(
|
|
40
|
+
module_name=module_name,
|
|
41
|
+
go_version=go_version,
|
|
42
|
+
generated_at=datetime.now().isoformat(),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return self.generator._create_generated_file(
|
|
46
|
+
path="go.mod",
|
|
47
|
+
content=content,
|
|
48
|
+
description="Go module definition"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def generate_readme(self) -> GeneratedFile:
|
|
52
|
+
"""Generate README.md file."""
|
|
53
|
+
template = self.jinja_env.get_template("README.md.j2")
|
|
54
|
+
|
|
55
|
+
content = template.render(
|
|
56
|
+
module_name=self.generator.package_config.get("module_name", "apiclient"),
|
|
57
|
+
api_title=self.context.openapi_info.title if self.context.openapi_info else "API Client",
|
|
58
|
+
api_version=self.context.openapi_info.api_version if self.context.openapi_info else "1.0.0",
|
|
59
|
+
generated_at=datetime.now().isoformat(),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return self.generator._create_generated_file(
|
|
63
|
+
path="README.md",
|
|
64
|
+
content=content,
|
|
65
|
+
description="Usage documentation"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def generate_makefile(self) -> GeneratedFile:
|
|
69
|
+
"""Generate Makefile."""
|
|
70
|
+
template = self.jinja_env.get_template("Makefile.j2")
|
|
71
|
+
|
|
72
|
+
content = template.render(
|
|
73
|
+
generated_at=datetime.now().isoformat(),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return self.generator._create_generated_file(
|
|
77
|
+
path="Makefile",
|
|
78
|
+
content=content,
|
|
79
|
+
description="Build automation"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def generate_errors_file(self, shared: bool = False) -> GeneratedFile:
|
|
83
|
+
"""
|
|
84
|
+
Generate errors.go with API error handling.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
shared: If True, generate in shared/ package for namespaced structure
|
|
88
|
+
"""
|
|
89
|
+
template = self.jinja_env.get_template("errors.go.j2")
|
|
90
|
+
|
|
91
|
+
package_name = "shared" if shared else self.generator.package_name
|
|
92
|
+
path = "shared/errors.go" if shared else "errors.go"
|
|
93
|
+
|
|
94
|
+
content = template.render(
|
|
95
|
+
package_name=package_name,
|
|
96
|
+
generated_at=datetime.now().isoformat(),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return self.generator._create_generated_file(
|
|
100
|
+
path=path,
|
|
101
|
+
content=content,
|
|
102
|
+
description="API error types"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def generate_middleware_file(self) -> GeneratedFile:
|
|
106
|
+
"""Generate middleware.go with retry, logging, rate limiting."""
|
|
107
|
+
template = self.jinja_env.get_template("middleware.go.j2")
|
|
108
|
+
|
|
109
|
+
content = template.render(
|
|
110
|
+
package_name=self.generator.package_name,
|
|
111
|
+
generated_at=datetime.now().isoformat(),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return self.generator._create_generated_file(
|
|
115
|
+
path="middleware.go",
|
|
116
|
+
content=content,
|
|
117
|
+
description="HTTP middleware (retry, logging, rate limiting)"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def generate_validation_file(self) -> GeneratedFile:
|
|
121
|
+
"""Generate validation.go with client-side validation helpers."""
|
|
122
|
+
template = self.jinja_env.get_template("validation.go.j2")
|
|
123
|
+
|
|
124
|
+
content = template.render(
|
|
125
|
+
package_name=self.generator.package_name,
|
|
126
|
+
generated_at=datetime.now().isoformat(),
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
return self.generator._create_generated_file(
|
|
130
|
+
path="validation.go",
|
|
131
|
+
content=content,
|
|
132
|
+
description="Client-side validation helpers"
|
|
133
|
+
)
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Go Generator - Main orchestrator for Go client generation.
|
|
3
|
+
|
|
4
|
+
Coordinates all sub-generators:
|
|
5
|
+
- ModelsGenerator: Go structs and enums
|
|
6
|
+
- OperationsGenerator: Operation methods
|
|
7
|
+
- ClientGenerator: HTTP client
|
|
8
|
+
- FilesGenerator: Auxiliary files (go.mod, README, Makefile)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import pathlib
|
|
14
|
+
|
|
15
|
+
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
|
16
|
+
|
|
17
|
+
from ...ir import IROperationObject, IRSchemaObject
|
|
18
|
+
from ..base import BaseGenerator, GeneratedFile
|
|
19
|
+
from .client_generator import ClientGenerator
|
|
20
|
+
from .files_generator import FilesGenerator
|
|
21
|
+
from .models_generator import ModelsGenerator
|
|
22
|
+
from .naming import get_go_package_name
|
|
23
|
+
from .operations_generator import OperationsGenerator
|
|
24
|
+
from .type_mapper import GoTypeMapper
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GoGenerator(BaseGenerator):
|
|
28
|
+
"""
|
|
29
|
+
Go client generator.
|
|
30
|
+
|
|
31
|
+
Generates:
|
|
32
|
+
- models.go: Go structs (User, UserRequest, PatchedUser)
|
|
33
|
+
- enums.go: Enum types with constants
|
|
34
|
+
- client.go: HTTP client with all operations
|
|
35
|
+
- errors.go: API error handling
|
|
36
|
+
- go.mod: Module definition
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, *args, **kwargs):
|
|
40
|
+
super().__init__(*args, **kwargs)
|
|
41
|
+
|
|
42
|
+
# Setup Jinja2 environment
|
|
43
|
+
templates_dir = pathlib.Path(__file__).parent / "templates"
|
|
44
|
+
self.jinja_env = Environment(
|
|
45
|
+
loader=FileSystemLoader(str(templates_dir)),
|
|
46
|
+
autoescape=select_autoescape(['html', 'xml']),
|
|
47
|
+
trim_blocks=True,
|
|
48
|
+
lstrip_blocks=True,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Initialize type mapper
|
|
52
|
+
# Use types package for namespaced structure to avoid import cycles
|
|
53
|
+
use_types_pkg = self.client_structure == "namespaced"
|
|
54
|
+
self.type_mapper = GoTypeMapper(use_types_package=use_types_pkg)
|
|
55
|
+
|
|
56
|
+
# Get package name
|
|
57
|
+
self.package_name = get_go_package_name(
|
|
58
|
+
self.package_config.get("name", "apiclient")
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Initialize sub-generators
|
|
62
|
+
self.models_gen = ModelsGenerator(self.jinja_env, self.context, self)
|
|
63
|
+
self.operations_gen = OperationsGenerator(self.jinja_env, self.context, self)
|
|
64
|
+
self.client_gen = ClientGenerator(self.jinja_env, self.context, self, self.operations_gen)
|
|
65
|
+
self.files_gen = FilesGenerator(self.jinja_env, self.context, self)
|
|
66
|
+
|
|
67
|
+
def generate(self) -> list[GeneratedFile]:
|
|
68
|
+
"""Generate all Go client files."""
|
|
69
|
+
files = []
|
|
70
|
+
|
|
71
|
+
if self.client_structure == "namespaced":
|
|
72
|
+
# Generate per-app folders
|
|
73
|
+
ops_by_tag = self.group_operations_by_tag()
|
|
74
|
+
|
|
75
|
+
for tag, operations in sorted(ops_by_tag.items()):
|
|
76
|
+
# Generate app folder (models.go, operations.go)
|
|
77
|
+
app_files = self._generate_app_folder(tag, operations)
|
|
78
|
+
if app_files:
|
|
79
|
+
files.extend(app_files)
|
|
80
|
+
|
|
81
|
+
# Generate shared enums.go
|
|
82
|
+
all_schemas = self.context.schemas
|
|
83
|
+
all_enums = self._collect_enums_from_schemas(all_schemas)
|
|
84
|
+
if all_enums:
|
|
85
|
+
enum_file = self.models_gen.generate_shared_enums_file(all_enums)
|
|
86
|
+
if enum_file:
|
|
87
|
+
files.append(enum_file)
|
|
88
|
+
|
|
89
|
+
# Generate main client.go
|
|
90
|
+
files.append(self.client_gen.generate_main_client_file(ops_by_tag))
|
|
91
|
+
|
|
92
|
+
# Generate supporting files in shared/ package to avoid circular imports
|
|
93
|
+
files.append(self.files_gen.generate_errors_file(shared=True))
|
|
94
|
+
files.append(self.files_gen.generate_middleware_file())
|
|
95
|
+
files.append(self.files_gen.generate_validation_file())
|
|
96
|
+
|
|
97
|
+
if self.generate_package_files:
|
|
98
|
+
files.append(self.files_gen.generate_go_mod())
|
|
99
|
+
files.append(self.files_gen.generate_readme())
|
|
100
|
+
files.append(self.files_gen.generate_makefile())
|
|
101
|
+
else:
|
|
102
|
+
# Flat structure
|
|
103
|
+
files.append(self.models_gen.generate_models_file())
|
|
104
|
+
|
|
105
|
+
enum_schemas = self.get_enum_schemas()
|
|
106
|
+
if enum_schemas:
|
|
107
|
+
enum_file = self.models_gen.generate_enums_file()
|
|
108
|
+
if enum_file:
|
|
109
|
+
files.append(enum_file)
|
|
110
|
+
|
|
111
|
+
files.append(self.client_gen.generate_client_file())
|
|
112
|
+
files.append(self.files_gen.generate_errors_file())
|
|
113
|
+
files.append(self.files_gen.generate_middleware_file())
|
|
114
|
+
files.append(self.files_gen.generate_validation_file())
|
|
115
|
+
|
|
116
|
+
if self.generate_package_files:
|
|
117
|
+
files.append(self.files_gen.generate_go_mod())
|
|
118
|
+
files.append(self.files_gen.generate_readme())
|
|
119
|
+
files.append(self.files_gen.generate_makefile())
|
|
120
|
+
|
|
121
|
+
return files
|
|
122
|
+
|
|
123
|
+
def _generate_app_folder(self, tag: str, operations: list[IROperationObject]) -> list[GeneratedFile]:
|
|
124
|
+
"""Generate folder for a specific app (tag)."""
|
|
125
|
+
files = []
|
|
126
|
+
|
|
127
|
+
# Get schemas used by this app
|
|
128
|
+
app_schemas = self._get_schemas_for_operations(operations)
|
|
129
|
+
|
|
130
|
+
# Generate models.go for this app
|
|
131
|
+
models_file = self.models_gen.generate_app_models_file(tag, app_schemas, operations)
|
|
132
|
+
if models_file:
|
|
133
|
+
files.append(models_file)
|
|
134
|
+
|
|
135
|
+
# Generate client.go with HTTP operations for this app
|
|
136
|
+
client_file = self.client_gen.generate_subpackage_client_file(tag, operations)
|
|
137
|
+
if client_file:
|
|
138
|
+
files.append(client_file)
|
|
139
|
+
|
|
140
|
+
return files
|
|
141
|
+
|
|
142
|
+
def _get_schemas_for_operations(self, operations: list[IROperationObject]) -> dict[str, IRSchemaObject]:
|
|
143
|
+
"""Get all schemas used by given operations, including nested dependencies."""
|
|
144
|
+
schemas = {}
|
|
145
|
+
schemas_to_process = set()
|
|
146
|
+
|
|
147
|
+
# Collect top-level schemas from operations
|
|
148
|
+
for operation in operations:
|
|
149
|
+
# Request body schemas
|
|
150
|
+
if operation.request_body and operation.request_body.schema_name:
|
|
151
|
+
schemas_to_process.add(operation.request_body.schema_name)
|
|
152
|
+
|
|
153
|
+
# Patch request body schemas
|
|
154
|
+
if operation.patch_request_body and operation.patch_request_body.schema_name:
|
|
155
|
+
schemas_to_process.add(operation.patch_request_body.schema_name)
|
|
156
|
+
|
|
157
|
+
# Response schemas
|
|
158
|
+
for status_code, response in operation.responses.items():
|
|
159
|
+
if response.schema_name:
|
|
160
|
+
schemas_to_process.add(response.schema_name)
|
|
161
|
+
|
|
162
|
+
# Recursively collect all dependencies
|
|
163
|
+
processed = set()
|
|
164
|
+
while schemas_to_process:
|
|
165
|
+
schema_name = schemas_to_process.pop()
|
|
166
|
+
if schema_name in processed or schema_name not in self.context.schemas:
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
processed.add(schema_name)
|
|
170
|
+
schema = self.context.schemas[schema_name]
|
|
171
|
+
schemas[schema_name] = schema
|
|
172
|
+
|
|
173
|
+
# Find nested schema references
|
|
174
|
+
if schema.properties:
|
|
175
|
+
for prop_name, prop_schema in schema.properties.items():
|
|
176
|
+
# Direct reference to another schema
|
|
177
|
+
if prop_schema.ref and prop_schema.ref in self.context.schemas:
|
|
178
|
+
schemas_to_process.add(prop_schema.ref)
|
|
179
|
+
|
|
180
|
+
# Array items reference
|
|
181
|
+
if prop_schema.items and prop_schema.items.ref:
|
|
182
|
+
if prop_schema.items.ref in self.context.schemas:
|
|
183
|
+
schemas_to_process.add(prop_schema.items.ref)
|
|
184
|
+
|
|
185
|
+
return schemas
|
|
186
|
+
|
|
187
|
+
def _create_generated_file(self, path: str, content: str, description: str) -> GeneratedFile:
|
|
188
|
+
"""Create GeneratedFile instance."""
|
|
189
|
+
return GeneratedFile(path=path, content=content, description=description)
|
|
190
|
+
|
|
191
|
+
# Backward compatibility - delegate to sub-generators
|
|
192
|
+
def generate_schema(self, schema: IRSchemaObject) -> str:
|
|
193
|
+
"""Generate Go struct for schema (delegates to ModelsGenerator)."""
|
|
194
|
+
return self.models_gen.generate_schema(schema)
|
|
195
|
+
|
|
196
|
+
def generate_enum(self, schema: IRSchemaObject) -> str:
|
|
197
|
+
"""Generate enum type (not directly supported, returns empty)."""
|
|
198
|
+
return "" # Go enums are handled differently
|
|
199
|
+
|
|
200
|
+
def generate_operation(self, operation: IROperationObject, remove_tag_prefix: bool = False) -> str:
|
|
201
|
+
"""Generate method for operation (for compatibility)."""
|
|
202
|
+
op_method = self.operations_gen.generate_operation_method(operation, remove_tag_prefix)
|
|
203
|
+
return f"func (c *Client) {op_method['name']}(...) (...) {{ ... }}"
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Models Generator - Generates Go structs from IR schemas.
|
|
3
|
+
|
|
4
|
+
Handles generation of:
|
|
5
|
+
- Response models (User, Post, etc.)
|
|
6
|
+
- Request models (UserRequest, PostRequest)
|
|
7
|
+
- Patch models (PatchedUser, PatchedPost)
|
|
8
|
+
- Enum types with constants
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from jinja2 import Environment
|
|
18
|
+
|
|
19
|
+
from ...ir import IRContext, IRSchemaObject
|
|
20
|
+
from ..base import GeneratedFile
|
|
21
|
+
from .generator import GoGenerator
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ModelsGenerator:
|
|
25
|
+
"""Generates Go struct definitions from IR schemas."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
jinja_env: Environment,
|
|
30
|
+
context: IRContext,
|
|
31
|
+
generator: GoGenerator,
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Initialize models generator.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
jinja_env: Jinja2 environment
|
|
38
|
+
context: IRContext from parser
|
|
39
|
+
generator: Parent GoGenerator instance
|
|
40
|
+
"""
|
|
41
|
+
self.jinja_env = jinja_env
|
|
42
|
+
self.context = context
|
|
43
|
+
self.generator = generator
|
|
44
|
+
|
|
45
|
+
def generate_models_file(self) -> GeneratedFile:
|
|
46
|
+
"""
|
|
47
|
+
Generate models.go with all struct definitions.
|
|
48
|
+
|
|
49
|
+
Contains:
|
|
50
|
+
- All response models (User, Post, etc.)
|
|
51
|
+
- All request models (UserRequest, PostRequest)
|
|
52
|
+
- All patch models (PatchedUser, PatchedPost)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
GeneratedFile with models.go content
|
|
56
|
+
"""
|
|
57
|
+
template = self.jinja_env.get_template("models.go.j2")
|
|
58
|
+
|
|
59
|
+
structs = []
|
|
60
|
+
|
|
61
|
+
# Generate response models
|
|
62
|
+
for schema_name, schema in sorted(self.context.response_models.items()):
|
|
63
|
+
struct_def = self.generator.type_mapper.ir_schema_to_struct(schema)
|
|
64
|
+
structs.append(struct_def)
|
|
65
|
+
|
|
66
|
+
# Generate request models
|
|
67
|
+
for schema_name, schema in sorted(self.context.request_models.items()):
|
|
68
|
+
struct_def = self.generator.type_mapper.ir_schema_to_struct(schema)
|
|
69
|
+
structs.append(struct_def)
|
|
70
|
+
|
|
71
|
+
# Generate patch models
|
|
72
|
+
for schema_name, schema in sorted(self.context.patch_models.items()):
|
|
73
|
+
struct_def = self.generator.type_mapper.ir_schema_to_struct(schema)
|
|
74
|
+
structs.append(struct_def)
|
|
75
|
+
|
|
76
|
+
# Collect imports
|
|
77
|
+
imports = self._collect_imports(structs)
|
|
78
|
+
|
|
79
|
+
content = template.render(
|
|
80
|
+
package_name=self.generator.package_name,
|
|
81
|
+
structs=structs,
|
|
82
|
+
imports=imports,
|
|
83
|
+
generated_at=datetime.now().isoformat(),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return self.generator._create_generated_file(
|
|
87
|
+
path="models.go",
|
|
88
|
+
content=content,
|
|
89
|
+
description="API data models"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def generate_enums_file(self) -> GeneratedFile:
|
|
93
|
+
"""
|
|
94
|
+
Generate enums.go with enum type definitions.
|
|
95
|
+
|
|
96
|
+
Go approach to enums:
|
|
97
|
+
```go
|
|
98
|
+
type StatusEnum int64
|
|
99
|
+
|
|
100
|
+
const (
|
|
101
|
+
StatusNew StatusEnum = 1
|
|
102
|
+
StatusInProgress StatusEnum = 2
|
|
103
|
+
StatusComplete StatusEnum = 3
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
func (s StatusEnum) String() string {
|
|
107
|
+
switch s {
|
|
108
|
+
case StatusNew:
|
|
109
|
+
return "STATUS_NEW"
|
|
110
|
+
case StatusInProgress:
|
|
111
|
+
return "STATUS_IN_PROGRESS"
|
|
112
|
+
case StatusComplete:
|
|
113
|
+
return "STATUS_COMPLETE"
|
|
114
|
+
default:
|
|
115
|
+
return fmt.Sprintf("StatusEnum(%d)", s)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func (s StatusEnum) MarshalJSON() ([]byte, error) {
|
|
120
|
+
return json.Marshal(int64(s))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
func (s *StatusEnum) UnmarshalJSON(data []byte) error {
|
|
124
|
+
var v int64
|
|
125
|
+
if err := json.Unmarshal(data, &v); err != nil {
|
|
126
|
+
return err
|
|
127
|
+
}
|
|
128
|
+
*s = StatusEnum(v)
|
|
129
|
+
return nil
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
GeneratedFile with enums.go content
|
|
135
|
+
"""
|
|
136
|
+
template = self.jinja_env.get_template("enums.go.j2")
|
|
137
|
+
|
|
138
|
+
enums = []
|
|
139
|
+
for schema_name, schema in sorted(self.context.enum_schemas.items()):
|
|
140
|
+
enum_def = self.generator.type_mapper.generate_enum_definition(schema)
|
|
141
|
+
enums.append(enum_def)
|
|
142
|
+
|
|
143
|
+
if not enums:
|
|
144
|
+
# No enums to generate
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
content = template.render(
|
|
148
|
+
package_name=self.generator.package_name,
|
|
149
|
+
enums=enums,
|
|
150
|
+
generated_at=datetime.now().isoformat(),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return self.generator._create_generated_file(
|
|
154
|
+
path="enums.go",
|
|
155
|
+
content=content,
|
|
156
|
+
description="API enum types"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def generate_shared_enums_file(self, enums: dict[str, IRSchemaObject]) -> GeneratedFile:
|
|
160
|
+
"""
|
|
161
|
+
Generate shared enums.go (for namespaced structure).
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
enums: Dictionary of enum schemas
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
GeneratedFile with shared enums.go content
|
|
168
|
+
"""
|
|
169
|
+
template = self.jinja_env.get_template("enums.go.j2")
|
|
170
|
+
|
|
171
|
+
enum_defs = []
|
|
172
|
+
for schema_name, schema in sorted(enums.items()):
|
|
173
|
+
enum_def = self.generator.type_mapper.generate_enum_definition(schema)
|
|
174
|
+
enum_defs.append(enum_def)
|
|
175
|
+
|
|
176
|
+
if not enum_defs:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
content = template.render(
|
|
180
|
+
package_name="types",
|
|
181
|
+
enums=enum_defs,
|
|
182
|
+
generated_at=datetime.now().isoformat(),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return self.generator._create_generated_file(
|
|
186
|
+
path="types/enums.go",
|
|
187
|
+
content=content,
|
|
188
|
+
description="Shared API enum types"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def generate_app_models_file(
|
|
192
|
+
self,
|
|
193
|
+
tag: str,
|
|
194
|
+
schemas: dict[str, IRSchemaObject],
|
|
195
|
+
operations: list,
|
|
196
|
+
) -> GeneratedFile:
|
|
197
|
+
"""
|
|
198
|
+
Generate models.go for specific app/tag (namespaced structure).
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
tag: Tag name
|
|
202
|
+
schemas: Schemas used by this app
|
|
203
|
+
operations: Operations for this app
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
GeneratedFile with app-specific models.go
|
|
207
|
+
"""
|
|
208
|
+
template = self.jinja_env.get_template("models.go.j2")
|
|
209
|
+
|
|
210
|
+
structs = []
|
|
211
|
+
for schema_name, schema in sorted(schemas.items()):
|
|
212
|
+
# Skip enum schemas (they go in shared enums.go)
|
|
213
|
+
if schema.enum:
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
struct_def = self.generator.type_mapper.ir_schema_to_struct(schema)
|
|
217
|
+
structs.append(struct_def)
|
|
218
|
+
|
|
219
|
+
if not structs:
|
|
220
|
+
# No models for this app
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
# Collect imports
|
|
224
|
+
imports = self._collect_imports(structs)
|
|
225
|
+
|
|
226
|
+
# Get folder name for this app
|
|
227
|
+
folder_name = self.generator.tag_and_app_to_folder_name(tag, operations)
|
|
228
|
+
|
|
229
|
+
content = template.render(
|
|
230
|
+
package_name=folder_name,
|
|
231
|
+
structs=structs,
|
|
232
|
+
imports=imports,
|
|
233
|
+
generated_at=datetime.now().isoformat(),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return self.generator._create_generated_file(
|
|
237
|
+
path=f"{folder_name}/models.go",
|
|
238
|
+
content=content,
|
|
239
|
+
description=f"{tag} API models"
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def _collect_imports(self, structs: list[dict]) -> list[str]:
|
|
243
|
+
"""
|
|
244
|
+
Collect required imports for structs.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
structs: List of struct definitions
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
List of import paths
|
|
251
|
+
|
|
252
|
+
Examples:
|
|
253
|
+
>>> structs = [{"needs_time_import": True}]
|
|
254
|
+
>>> imports = generator._collect_imports(structs)
|
|
255
|
+
>>> "time" in imports
|
|
256
|
+
True
|
|
257
|
+
"""
|
|
258
|
+
imports = set()
|
|
259
|
+
|
|
260
|
+
# Check if time import is needed
|
|
261
|
+
for struct in structs:
|
|
262
|
+
if struct.get("needs_time_import"):
|
|
263
|
+
imports.add("time")
|
|
264
|
+
|
|
265
|
+
# Check if types package is used (for enums)
|
|
266
|
+
for field in struct.get("fields", []):
|
|
267
|
+
if "types." in field.get("type", ""):
|
|
268
|
+
module_name = self.generator.package_config.get("module_name", "apiclient")
|
|
269
|
+
imports.add(f"{module_name}/types")
|
|
270
|
+
break
|
|
271
|
+
|
|
272
|
+
return sorted(imports)
|
|
273
|
+
|
|
274
|
+
def generate_schema(self, schema: IRSchemaObject) -> str:
|
|
275
|
+
"""
|
|
276
|
+
Generate Go struct code for a single schema (for backward compatibility).
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
schema: IRSchemaObject to generate
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Generated struct code as string
|
|
283
|
+
"""
|
|
284
|
+
struct_def = self.generator.type_mapper.ir_schema_to_struct(schema)
|
|
285
|
+
|
|
286
|
+
lines = []
|
|
287
|
+
|
|
288
|
+
# Add doc comment
|
|
289
|
+
if struct_def["doc"]:
|
|
290
|
+
lines.append(f"// {struct_def['doc']}")
|
|
291
|
+
|
|
292
|
+
# Add struct definition
|
|
293
|
+
lines.append(f"type {struct_def['name']} struct {{")
|
|
294
|
+
|
|
295
|
+
# Add fields
|
|
296
|
+
for field in struct_def["fields"]:
|
|
297
|
+
if field["description"]:
|
|
298
|
+
lines.append(f"\t// {field['description']}")
|
|
299
|
+
|
|
300
|
+
lines.append(f"\t{field['name']} {field['type']} {field['json_tag']}")
|
|
301
|
+
|
|
302
|
+
lines.append("}")
|
|
303
|
+
|
|
304
|
+
return "\n".join(lines)
|