django-cfg 1.4.10__py3-none-any.whl → 1.4.11__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.
Files changed (181) hide show
  1. django_cfg/apps/agents/management/commands/create_agent.py +1 -1
  2. django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
  3. django_cfg/apps/newsletter/serializers.py +40 -3
  4. django_cfg/apps/newsletter/views/campaigns.py +12 -3
  5. django_cfg/apps/newsletter/views/emails.py +14 -3
  6. django_cfg/apps/newsletter/views/subscriptions.py +12 -2
  7. django_cfg/apps/payments/views/api/currencies.py +49 -6
  8. django_cfg/apps/payments/views/api/webhooks.py +72 -7
  9. django_cfg/apps/payments/views/overview/serializers.py +34 -1
  10. django_cfg/apps/payments/views/overview/views.py +2 -1
  11. django_cfg/apps/payments/views/serializers/payments.py +6 -6
  12. django_cfg/apps/urls.py +106 -45
  13. django_cfg/core/base/config_model.py +2 -2
  14. django_cfg/core/constants.py +1 -1
  15. django_cfg/core/generation/integration_generators/__init__.py +1 -1
  16. django_cfg/core/generation/integration_generators/api.py +72 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/dashboard/sections/documentation.py +391 -0
  20. django_cfg/management/commands/check_endpoints.py +11 -160
  21. django_cfg/management/commands/check_settings.py +13 -348
  22. django_cfg/management/commands/clear_constance.py +13 -201
  23. django_cfg/management/commands/create_token.py +13 -321
  24. django_cfg/management/commands/generate_clients.py +23 -0
  25. django_cfg/management/commands/list_urls.py +13 -306
  26. django_cfg/management/commands/migrate_all.py +13 -126
  27. django_cfg/management/commands/migrator.py +13 -396
  28. django_cfg/management/commands/rundramatiq.py +15 -247
  29. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  30. django_cfg/management/commands/runserver_ngrok.py +15 -160
  31. django_cfg/management/commands/script.py +12 -488
  32. django_cfg/management/commands/show_config.py +12 -215
  33. django_cfg/management/commands/show_urls.py +12 -342
  34. django_cfg/management/commands/superuser.py +15 -295
  35. django_cfg/management/commands/task_clear.py +14 -217
  36. django_cfg/management/commands/task_status.py +13 -248
  37. django_cfg/management/commands/test_email.py +15 -86
  38. django_cfg/management/commands/test_telegram.py +14 -61
  39. django_cfg/management/commands/test_twilio.py +15 -105
  40. django_cfg/management/commands/tree.py +13 -383
  41. django_cfg/management/commands/validate_openapi.py +10 -0
  42. django_cfg/middleware/README.md +1 -1
  43. django_cfg/middleware/user_activity.py +3 -3
  44. django_cfg/models/__init__.py +2 -2
  45. django_cfg/models/api/drf/spectacular.py +6 -6
  46. django_cfg/models/django/__init__.py +2 -2
  47. django_cfg/models/django/openapi.py +238 -0
  48. django_cfg/modules/django_admin/management/__init__.py +0 -0
  49. django_cfg/modules/django_admin/management/commands/__init__.py +0 -0
  50. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  51. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  52. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  53. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  54. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  55. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  56. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  57. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  58. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  59. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  60. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  61. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  62. django_cfg/modules/django_client/__init__.py +20 -0
  63. django_cfg/modules/django_client/apps.py +35 -0
  64. django_cfg/modules/django_client/core/__init__.py +56 -0
  65. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  66. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  67. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  68. django_cfg/modules/django_client/core/cli/main.py +235 -0
  69. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  70. django_cfg/modules/django_client/core/config/config.py +188 -0
  71. django_cfg/modules/django_client/core/config/group.py +101 -0
  72. django_cfg/modules/django_client/core/config/service.py +209 -0
  73. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  74. django_cfg/modules/django_client/core/generator/base.py +767 -0
  75. django_cfg/modules/django_client/core/generator/python.py +751 -0
  76. django_cfg/modules/django_client/core/generator/templates/python/__init__.py.jinja +9 -0
  77. django_cfg/modules/django_client/core/generator/templates/python/api_wrapper.py.jinja +130 -0
  78. django_cfg/modules/django_client/core/generator/templates/python/app_init.py.jinja +6 -0
  79. django_cfg/modules/django_client/core/generator/templates/python/client/app_client.py.jinja +18 -0
  80. django_cfg/modules/django_client/core/generator/templates/python/client/flat_client.py.jinja +38 -0
  81. django_cfg/modules/django_client/core/generator/templates/python/client/main_client.py.jinja +50 -0
  82. django_cfg/modules/django_client/core/generator/templates/python/client/main_client_file.py.jinja +13 -0
  83. django_cfg/modules/django_client/core/generator/templates/python/client/operation_method.py.jinja +7 -0
  84. django_cfg/modules/django_client/core/generator/templates/python/client/sub_client.py.jinja +11 -0
  85. django_cfg/modules/django_client/core/generator/templates/python/client_file.py.jinja +13 -0
  86. django_cfg/modules/django_client/core/generator/templates/python/main_init.py.jinja +50 -0
  87. django_cfg/modules/django_client/core/generator/templates/python/models/app_models.py.jinja +17 -0
  88. django_cfg/modules/django_client/core/generator/templates/python/models/enum_class.py.jinja +15 -0
  89. django_cfg/modules/django_client/core/generator/templates/python/models/enums.py.jinja +8 -0
  90. django_cfg/modules/django_client/core/generator/templates/python/models/models.py.jinja +17 -0
  91. django_cfg/modules/django_client/core/generator/templates/python/models/schema_class.py.jinja +19 -0
  92. django_cfg/modules/django_client/core/generator/templates/python/utils/logger.py.jinja +255 -0
  93. django_cfg/modules/django_client/core/generator/templates/python/utils/schema.py.jinja +12 -0
  94. django_cfg/modules/django_client/core/generator/templates/typescript/app_index.ts.jinja +2 -0
  95. django_cfg/modules/django_client/core/generator/templates/typescript/client/app_client.ts.jinja +18 -0
  96. django_cfg/modules/django_client/core/generator/templates/typescript/client/client.ts.jinja +327 -0
  97. django_cfg/modules/django_client/core/generator/templates/typescript/client/flat_client.ts.jinja +109 -0
  98. django_cfg/modules/django_client/core/generator/templates/typescript/client/main_client_file.ts.jinja +9 -0
  99. django_cfg/modules/django_client/core/generator/templates/typescript/client/operation.ts.jinja +61 -0
  100. django_cfg/modules/django_client/core/generator/templates/typescript/client/sub_client.ts.jinja +15 -0
  101. django_cfg/modules/django_client/core/generator/templates/typescript/client_file.ts.jinja +9 -0
  102. django_cfg/modules/django_client/core/generator/templates/typescript/index.ts.jinja +5 -0
  103. django_cfg/modules/django_client/core/generator/templates/typescript/main_index.ts.jinja +206 -0
  104. django_cfg/modules/django_client/core/generator/templates/typescript/models/app_models.ts.jinja +8 -0
  105. django_cfg/modules/django_client/core/generator/templates/typescript/models/enums.ts.jinja +4 -0
  106. django_cfg/modules/django_client/core/generator/templates/typescript/models/models.ts.jinja +8 -0
  107. django_cfg/modules/django_client/core/generator/templates/typescript/utils/errors.ts.jinja +114 -0
  108. django_cfg/modules/django_client/core/generator/templates/typescript/utils/http.ts.jinja +98 -0
  109. django_cfg/modules/django_client/core/generator/templates/typescript/utils/logger.ts.jinja +251 -0
  110. django_cfg/modules/django_client/core/generator/templates/typescript/utils/schema.ts.jinja +7 -0
  111. django_cfg/modules/django_client/core/generator/templates/typescript/utils/storage.ts.jinja +114 -0
  112. django_cfg/modules/django_client/core/generator/typescript.py +872 -0
  113. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  114. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  115. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  116. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  117. django_cfg/modules/django_client/core/ir/context.py +387 -0
  118. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  119. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  120. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  121. django_cfg/modules/django_client/core/parser/base.py +648 -0
  122. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  123. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  124. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  125. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  126. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  127. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  128. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  129. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  130. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  131. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  132. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  133. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  134. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  135. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  136. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  137. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  138. django_cfg/modules/django_client/management/__init__.py +3 -0
  139. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  140. django_cfg/modules/django_client/management/commands/generate_client.py +422 -0
  141. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  142. django_cfg/modules/django_client/spectacular/__init__.py +9 -0
  143. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  144. django_cfg/modules/django_client/urls.py +72 -0
  145. django_cfg/modules/django_email/management/__init__.py +0 -0
  146. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  147. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  148. django_cfg/modules/django_logging/django_logger.py +6 -6
  149. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  150. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  151. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  152. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  153. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  154. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  155. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  156. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  157. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  158. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  159. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  160. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  161. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  162. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  163. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  164. django_cfg/modules/django_unfold/callbacks/main.py +16 -5
  165. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  166. django_cfg/pyproject.toml +2 -6
  167. django_cfg/registry/third_party.py +5 -7
  168. django_cfg/routing/callbacks.py +1 -1
  169. django_cfg/static/admin/css/prose-unfold.css +666 -0
  170. django_cfg/templates/admin/index.html +8 -0
  171. django_cfg/templates/admin/index_new.html +13 -0
  172. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  173. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  174. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  175. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/METADATA +2 -2
  176. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/RECORD +180 -59
  177. django_cfg/management/commands/generate.py +0 -107
  178. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  179. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/WHEEL +0 -0
  180. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/entry_points.txt +0 -0
  181. {django_cfg-1.4.10.dist-info → django_cfg-1.4.11.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,74 @@
1
+ """
2
+ OpenAPI Input Models - Pydantic 2 models for raw OpenAPI specs.
3
+
4
+ These models represent the OpenAPI specification structure as it appears
5
+ in JSON/YAML files, before normalization to IR.
6
+
7
+ Supports both OpenAPI 3.0.3 and 3.1.0.
8
+
9
+ Usage:
10
+ >>> from django_cfg.modules.django_client.core.parser.models import OpenAPISpec
11
+ >>> spec = OpenAPISpec.model_validate(openapi_dict)
12
+ >>> spec.openapi_version
13
+ '3.1.0'
14
+ >>> spec.all_schema_names
15
+ ['User', 'UserRequest', 'PatchedUser', ...]
16
+ """
17
+
18
+ from .base import (
19
+ ContactObject,
20
+ ExampleObject,
21
+ ExternalDocumentationObject,
22
+ InfoObject,
23
+ LicenseObject,
24
+ LinkObject,
25
+ ReferenceObject,
26
+ ServerObject,
27
+ ServerVariableObject,
28
+ TagObject,
29
+ )
30
+ from .components import ComponentsObject, SecuritySchemeObject
31
+ from .openapi import OpenAPISpec
32
+ from .operation import (
33
+ CallbackObject,
34
+ EncodingObject,
35
+ MediaTypeObject,
36
+ OperationObject,
37
+ ParameterObject,
38
+ PathItemObject,
39
+ RequestBodyObject,
40
+ ResponseObject,
41
+ )
42
+ from .schema import Discriminator, SchemaObject, XMLObject
43
+
44
+ __all__ = [
45
+ # Root
46
+ "OpenAPISpec",
47
+ # Base
48
+ "InfoObject",
49
+ "ContactObject",
50
+ "LicenseObject",
51
+ "ServerObject",
52
+ "ServerVariableObject",
53
+ "TagObject",
54
+ "ExternalDocumentationObject",
55
+ "ReferenceObject",
56
+ "ExampleObject",
57
+ "LinkObject",
58
+ # Schema
59
+ "SchemaObject",
60
+ "Discriminator",
61
+ "XMLObject",
62
+ # Operation
63
+ "ParameterObject",
64
+ "MediaTypeObject",
65
+ "EncodingObject",
66
+ "RequestBodyObject",
67
+ "ResponseObject",
68
+ "OperationObject",
69
+ "PathItemObject",
70
+ "CallbackObject",
71
+ # Components
72
+ "ComponentsObject",
73
+ "SecuritySchemeObject",
74
+ ]
@@ -0,0 +1,212 @@
1
+ """
2
+ OpenAPI Input Models - Base types.
3
+
4
+ These models represent the raw OpenAPI specification structure as it appears
5
+ in the JSON/YAML file. They are version-agnostic where possible.
6
+
7
+ Reference: https://spec.openapis.org/oas/v3.1.0
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any
13
+
14
+ from pydantic import BaseModel, ConfigDict, Field
15
+
16
+
17
+ class ContactObject(BaseModel):
18
+ """
19
+ OpenAPI Contact Object.
20
+
21
+ Reference: https://spec.openapis.org/oas/v3.1.0#contact-object
22
+ """
23
+
24
+ model_config = ConfigDict(extra="allow")
25
+
26
+ name: str | None = None
27
+ url: str | None = None
28
+ email: str | None = None
29
+
30
+
31
+ class LicenseObject(BaseModel):
32
+ """
33
+ OpenAPI License Object.
34
+
35
+ Reference: https://spec.openapis.org/oas/v3.1.0#license-object
36
+ """
37
+
38
+ model_config = ConfigDict(extra="allow")
39
+
40
+ name: str = Field(..., description="License name (e.g., 'MIT', 'Apache 2.0')")
41
+ identifier: str | None = Field(
42
+ None, description="SPDX license identifier (OAS 3.1.0)"
43
+ )
44
+ url: str | None = None
45
+
46
+
47
+ class InfoObject(BaseModel):
48
+ """
49
+ OpenAPI Info Object.
50
+
51
+ Reference: https://spec.openapis.org/oas/v3.1.0#info-object
52
+ """
53
+
54
+ model_config = ConfigDict(extra="allow")
55
+
56
+ title: str = Field(..., description="API title")
57
+ version: str = Field(..., description="API version (e.g., '1.0.0')")
58
+ summary: str | None = Field(None, description="Short summary (OAS 3.1.0)")
59
+ description: str | None = None
60
+ termsOfService: str | None = None
61
+ contact: ContactObject | None = None
62
+ license: LicenseObject | None = None
63
+
64
+
65
+ class ServerVariableObject(BaseModel):
66
+ """
67
+ OpenAPI Server Variable Object.
68
+
69
+ Reference: https://spec.openapis.org/oas/v3.1.0#server-variable-object
70
+ """
71
+
72
+ model_config = ConfigDict(extra="allow")
73
+
74
+ enum: list[str] | None = None
75
+ default: str = Field(..., description="Default value for substitution")
76
+ description: str | None = None
77
+
78
+
79
+ class ServerObject(BaseModel):
80
+ """
81
+ OpenAPI Server Object.
82
+
83
+ Reference: https://spec.openapis.org/oas/v3.1.0#server-object
84
+ """
85
+
86
+ model_config = ConfigDict(extra="allow")
87
+
88
+ url: str = Field(..., description="Server URL (e.g., 'https://api.example.com')")
89
+ description: str | None = None
90
+ variables: dict[str, ServerVariableObject] | None = None
91
+
92
+
93
+ class ExternalDocumentationObject(BaseModel):
94
+ """
95
+ OpenAPI External Documentation Object.
96
+
97
+ Reference: https://spec.openapis.org/oas/v3.1.0#external-documentation-object
98
+ """
99
+
100
+ model_config = ConfigDict(extra="allow")
101
+
102
+ url: str = Field(..., description="Documentation URL")
103
+ description: str | None = None
104
+
105
+
106
+ class TagObject(BaseModel):
107
+ """
108
+ OpenAPI Tag Object.
109
+
110
+ Reference: https://spec.openapis.org/oas/v3.1.0#tag-object
111
+ """
112
+
113
+ model_config = ConfigDict(extra="allow")
114
+
115
+ name: str = Field(..., description="Tag name")
116
+ description: str | None = None
117
+ externalDocs: ExternalDocumentationObject | None = None
118
+
119
+
120
+ class ReferenceObject(BaseModel):
121
+ """
122
+ OpenAPI Reference Object ($ref).
123
+
124
+ Reference: https://spec.openapis.org/oas/v3.1.0#reference-object
125
+
126
+ Examples:
127
+ >>> ref = ReferenceObject(ref="#/components/schemas/User")
128
+ >>> ref.ref
129
+ '#/components/schemas/User'
130
+ """
131
+
132
+ model_config = ConfigDict(extra="allow")
133
+
134
+ ref: str = Field(..., alias="$ref", description="Reference URI")
135
+ summary: str | None = Field(None, description="Summary (OAS 3.1.0)")
136
+ description: str | None = Field(None, description="Description (OAS 3.1.0)")
137
+
138
+ @property
139
+ def ref_name(self) -> str:
140
+ """
141
+ Extract referenced name from $ref.
142
+
143
+ Examples:
144
+ >>> ReferenceObject(ref="#/components/schemas/User").ref_name
145
+ 'User'
146
+ >>> ReferenceObject(ref="#/components/responses/NotFound").ref_name
147
+ 'NotFound'
148
+ """
149
+ return self.ref.split("/")[-1]
150
+
151
+ @property
152
+ def ref_type(self) -> str:
153
+ """
154
+ Extract reference type from $ref.
155
+
156
+ Examples:
157
+ >>> ReferenceObject(ref="#/components/schemas/User").ref_type
158
+ 'schemas'
159
+ >>> ReferenceObject(ref="#/components/responses/NotFound").ref_type
160
+ 'responses'
161
+ """
162
+ parts = self.ref.split("/")
163
+ if len(parts) >= 3 and parts[1] == "components":
164
+ return parts[2]
165
+ return "unknown"
166
+
167
+
168
+ class ExampleObject(BaseModel):
169
+ """
170
+ OpenAPI Example Object.
171
+
172
+ Reference: https://spec.openapis.org/oas/v3.1.0#example-object
173
+ """
174
+
175
+ model_config = ConfigDict(extra="allow")
176
+
177
+ summary: str | None = None
178
+ description: str | None = None
179
+ value: Any | None = None
180
+ externalValue: str | None = None
181
+
182
+
183
+ class HeaderObject(BaseModel):
184
+ """
185
+ OpenAPI Header Object.
186
+
187
+ Reference: https://spec.openapis.org/oas/v3.1.0#header-object
188
+ """
189
+
190
+ model_config = ConfigDict(extra="allow")
191
+
192
+ description: str | None = None
193
+ required: bool = False
194
+ deprecated: bool = False
195
+ # Schema will be added as SchemaObject | ReferenceObject in schema.py
196
+
197
+
198
+ class LinkObject(BaseModel):
199
+ """
200
+ OpenAPI Link Object.
201
+
202
+ Reference: https://spec.openapis.org/oas/v3.1.0#link-object
203
+ """
204
+
205
+ model_config = ConfigDict(extra="allow")
206
+
207
+ operationRef: str | None = None
208
+ operationId: str | None = None
209
+ parameters: dict[str, Any] | None = None
210
+ requestBody: Any | None = None
211
+ description: str | None = None
212
+ server: ServerObject | None = None
@@ -0,0 +1,160 @@
1
+ """
2
+ OpenAPI Input Models - Components Object.
3
+
4
+ ComponentsObject holds reusable schemas, responses, parameters, etc.
5
+
6
+ Reference: https://spec.openapis.org/oas/v3.1.0#components-object
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from pydantic import BaseModel, ConfigDict, Field
14
+
15
+ from .base import ExampleObject, LinkObject, ReferenceObject
16
+ from .operation import (
17
+ CallbackObject,
18
+ ParameterObject,
19
+ RequestBodyObject,
20
+ ResponseObject,
21
+ )
22
+ from .schema import SchemaObject
23
+
24
+
25
+ class SecuritySchemeObject(BaseModel):
26
+ """
27
+ OpenAPI Security Scheme Object.
28
+
29
+ Reference: https://spec.openapis.org/oas/v3.1.0#security-scheme-object
30
+ """
31
+
32
+ model_config = ConfigDict(extra="allow")
33
+
34
+ type: str = Field(
35
+ ...,
36
+ description="Security type: apiKey, http, oauth2, openIdConnect",
37
+ )
38
+ description: str | None = None
39
+
40
+ # apiKey
41
+ name: str | None = Field(None, description="Header/query/cookie name (for apiKey)")
42
+ in_: str | None = Field(None, alias="in", description="Location: query, header, cookie")
43
+
44
+ # http
45
+ scheme: str | None = Field(None, description="HTTP scheme: basic, bearer, etc.")
46
+ bearerFormat: str | None = None
47
+
48
+ # oauth2
49
+ flows: dict[str, Any] | None = None # OAuthFlowsObject
50
+
51
+ # openIdConnect
52
+ openIdConnectUrl: str | None = None
53
+
54
+
55
+ class ComponentsObject(BaseModel):
56
+ """
57
+ OpenAPI Components Object.
58
+
59
+ Contains reusable schemas, responses, parameters, etc.
60
+
61
+ Reference: https://spec.openapis.org/oas/v3.1.0#components-object
62
+ """
63
+
64
+ model_config = ConfigDict(extra="allow")
65
+
66
+ # ===== Schemas (most important for us) =====
67
+ schemas: dict[str, SchemaObject | ReferenceObject] | None = Field(
68
+ None,
69
+ description="Reusable schemas (User, UserRequest, PatchedUser, etc.)",
70
+ )
71
+
72
+ # ===== Responses =====
73
+ responses: dict[str, ResponseObject | ReferenceObject] | None = Field(
74
+ None,
75
+ description="Reusable responses (NotFound, ValidationError, etc.)",
76
+ )
77
+
78
+ # ===== Parameters =====
79
+ parameters: dict[str, ParameterObject | ReferenceObject] | None = Field(
80
+ None,
81
+ description="Reusable parameters",
82
+ )
83
+
84
+ # ===== Examples =====
85
+ examples: dict[str, ExampleObject | ReferenceObject] | None = Field(
86
+ None,
87
+ description="Reusable examples",
88
+ )
89
+
90
+ # ===== Request Bodies =====
91
+ requestBodies: dict[str, RequestBodyObject | ReferenceObject] | None = Field(
92
+ None,
93
+ description="Reusable request bodies",
94
+ )
95
+
96
+ # ===== Headers =====
97
+ headers: dict[str, Any] | None = Field(
98
+ None,
99
+ description="Reusable headers (HeaderObject | ReferenceObject)",
100
+ )
101
+
102
+ # ===== Security Schemes =====
103
+ securitySchemes: dict[str, SecuritySchemeObject | ReferenceObject] | None = Field(
104
+ None,
105
+ description="Security schemes (basicAuth, bearerAuth, etc.)",
106
+ )
107
+
108
+ # ===== Links =====
109
+ links: dict[str, LinkObject | ReferenceObject] | None = Field(
110
+ None,
111
+ description="Reusable links",
112
+ )
113
+
114
+ # ===== Callbacks =====
115
+ callbacks: dict[str, CallbackObject | ReferenceObject] | None = Field(
116
+ None,
117
+ description="Reusable callbacks",
118
+ )
119
+
120
+ # ===== Path Items (OAS 3.1.0) =====
121
+ pathItems: dict[str, Any] | None = Field(
122
+ None,
123
+ description="Reusable path items (OAS 3.1.0)",
124
+ )
125
+
126
+ @property
127
+ def has_schemas(self) -> bool:
128
+ """Check if components contain schemas."""
129
+ return self.schemas is not None and len(self.schemas) > 0
130
+
131
+ @property
132
+ def schema_names(self) -> list[str]:
133
+ """Get all schema names."""
134
+ if not self.schemas:
135
+ return []
136
+ return list(self.schemas.keys())
137
+
138
+ def get_schema(self, name: str) -> SchemaObject | ReferenceObject | None:
139
+ """Get schema by name."""
140
+ if not self.schemas:
141
+ return None
142
+ return self.schemas.get(name)
143
+
144
+ def __repr__(self) -> str:
145
+ """String representation for debugging."""
146
+ parts = ["ComponentsObject("]
147
+
148
+ if self.schemas:
149
+ parts.append(f"schemas={len(self.schemas)}")
150
+
151
+ if self.responses:
152
+ parts.append(f"responses={len(self.responses)}")
153
+
154
+ if self.parameters:
155
+ parts.append(f"parameters={len(self.parameters)}")
156
+
157
+ if self.securitySchemes:
158
+ parts.append(f"securitySchemes={len(self.securitySchemes)}")
159
+
160
+ return ", ".join(parts) + ")"
@@ -0,0 +1,203 @@
1
+ """
2
+ OpenAPI Input Models - Root OpenAPI Spec.
3
+
4
+ This module defines the root OpenAPISpec model that represents a complete
5
+ OpenAPI specification (version 3.0.3 or 3.1.0).
6
+
7
+ Reference: https://spec.openapis.org/oas/v3.1.0
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Any, Literal
13
+
14
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
15
+
16
+ from .base import ExternalDocumentationObject, InfoObject, ServerObject, TagObject
17
+ from .components import ComponentsObject
18
+ from .operation import PathItemObject
19
+
20
+
21
+ class OpenAPISpec(BaseModel):
22
+ """
23
+ OpenAPI Specification root object.
24
+
25
+ This is the top-level object representing a complete OpenAPI specification.
26
+ Supports both OpenAPI 3.0.3 and 3.1.0.
27
+
28
+ Reference: https://spec.openapis.org/oas/v3.1.0
29
+
30
+ Examples:
31
+ >>> spec = OpenAPISpec(
32
+ ... openapi="3.1.0",
33
+ ... info=InfoObject(title="My API", version="1.0.0"),
34
+ ... paths={"/users/": PathItemObject(...)},
35
+ ... )
36
+ >>> spec.openapi_version
37
+ '3.1.0'
38
+ >>> spec.is_openapi_31
39
+ True
40
+ """
41
+
42
+ model_config = ConfigDict(
43
+ extra="allow", # Allow x-* extensions
44
+ validate_assignment=True,
45
+ )
46
+
47
+ # ===== Required Fields =====
48
+ openapi: str = Field(
49
+ ...,
50
+ description="OpenAPI version (3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.1.0)",
51
+ )
52
+ info: InfoObject = Field(..., description="API metadata")
53
+ paths: dict[str, PathItemObject] | None = Field(
54
+ None,
55
+ description="API paths (/users/, /tasks/{id}/, etc.)",
56
+ )
57
+
58
+ # ===== Optional Fields =====
59
+ jsonSchemaDialect: str | None = Field(
60
+ None,
61
+ description="JSON Schema dialect (OAS 3.1.0 only)",
62
+ )
63
+ servers: list[ServerObject] | None = Field(
64
+ None,
65
+ description="Server URLs",
66
+ )
67
+ components: ComponentsObject | None = Field(
68
+ None,
69
+ description="Reusable components (schemas, responses, etc.)",
70
+ )
71
+ security: list[dict[str, list[str]]] | None = Field(
72
+ None,
73
+ description="Global security requirements",
74
+ )
75
+ tags: list[TagObject] | None = Field(
76
+ None,
77
+ description="API tags for grouping operations",
78
+ )
79
+ externalDocs: ExternalDocumentationObject | None = None
80
+
81
+ # ===== drf-spectacular Extensions =====
82
+ x_tagGroups: list[dict[str, Any]] | None = Field(
83
+ None,
84
+ alias="x-tagGroups",
85
+ description="Tag groups from drf-spectacular",
86
+ )
87
+
88
+ # ===== Validation =====
89
+
90
+ @field_validator("openapi")
91
+ @classmethod
92
+ def validate_openapi_version(cls, v: str) -> str:
93
+ """Validate OpenAPI version is 3.0.x or 3.1.0."""
94
+ valid_versions = ["3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.1.0"]
95
+ if v not in valid_versions:
96
+ raise ValueError(
97
+ f"Unsupported OpenAPI version: {v}. "
98
+ f"Supported versions: {', '.join(valid_versions)}"
99
+ )
100
+ return v
101
+
102
+ # ===== Computed Properties =====
103
+
104
+ @property
105
+ def openapi_version(self) -> str:
106
+ """Get OpenAPI version string."""
107
+ return self.openapi
108
+
109
+ @property
110
+ def is_openapi_30(self) -> bool:
111
+ """Check if OpenAPI 3.0.x."""
112
+ return self.openapi.startswith("3.0.")
113
+
114
+ @property
115
+ def is_openapi_31(self) -> bool:
116
+ """Check if OpenAPI 3.1.0."""
117
+ return self.openapi == "3.1.0"
118
+
119
+ @property
120
+ def normalized_version(self) -> Literal["3.0.3", "3.1.0"]:
121
+ """
122
+ Get normalized OpenAPI version (3.0.3 or 3.1.0).
123
+
124
+ All 3.0.x versions are normalized to 3.0.3.
125
+ """
126
+ if self.is_openapi_30:
127
+ return "3.0.3"
128
+ return "3.1.0"
129
+
130
+ @property
131
+ def has_components(self) -> bool:
132
+ """Check if spec has components."""
133
+ return self.components is not None and self.components.has_schemas
134
+
135
+ @property
136
+ def has_paths(self) -> bool:
137
+ """Check if spec has paths."""
138
+ return self.paths is not None and len(self.paths) > 0
139
+
140
+ @property
141
+ def all_paths(self) -> dict[str, PathItemObject]:
142
+ """Get all paths (empty dict if none)."""
143
+ return self.paths or {}
144
+
145
+ @property
146
+ def all_operations(self) -> dict[str, tuple[str, str, Any]]:
147
+ """
148
+ Get all operations from all paths.
149
+
150
+ Returns:
151
+ Dict of {operation_id: (http_method, path, OperationObject)}
152
+
153
+ Examples:
154
+ >>> spec = OpenAPISpec(...)
155
+ >>> ops = spec.all_operations
156
+ >>> ops['users_list']
157
+ ('GET', '/api/users/', OperationObject(...))
158
+ """
159
+ result = {}
160
+ if not self.paths:
161
+ return result
162
+
163
+ for path, path_item in self.paths.items():
164
+ for method, operation in path_item.operations.items():
165
+ if operation.operationId:
166
+ result[operation.operationId] = (method, path, operation)
167
+
168
+ return result
169
+
170
+ @property
171
+ def all_schema_names(self) -> list[str]:
172
+ """Get all schema names from components."""
173
+ if not self.components or not self.components.schemas:
174
+ return []
175
+ return list(self.components.schemas.keys())
176
+
177
+ @property
178
+ def server_urls(self) -> list[str]:
179
+ """Get all server URLs."""
180
+ if not self.servers:
181
+ return []
182
+ return [server.url for server in self.servers]
183
+
184
+ def get_schema(self, name: str) -> Any:
185
+ """Get schema from components by name."""
186
+ if not self.components:
187
+ return None
188
+ return self.components.get_schema(name)
189
+
190
+ def __repr__(self) -> str:
191
+ """String representation for debugging."""
192
+ parts = [
193
+ f"OpenAPISpec(openapi={self.openapi!r}",
194
+ f"title={self.info.title!r}",
195
+ ]
196
+
197
+ if self.paths:
198
+ parts.append(f"paths={len(self.paths)}")
199
+
200
+ if self.components and self.components.schemas:
201
+ parts.append(f"schemas={len(self.components.schemas)}")
202
+
203
+ return ", ".join(parts) + ")"