django-cfg 1.4.10__py3-none-any.whl → 1.4.13__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 (225) 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 +73 -49
  17. django_cfg/core/integration/display/startup.py +30 -22
  18. django_cfg/core/integration/url_integration.py +15 -16
  19. django_cfg/management/commands/check_endpoints.py +11 -160
  20. django_cfg/management/commands/check_settings.py +13 -348
  21. django_cfg/management/commands/clear_constance.py +13 -201
  22. django_cfg/management/commands/create_token.py +13 -321
  23. django_cfg/management/commands/generate_clients.py +23 -0
  24. django_cfg/management/commands/list_urls.py +13 -306
  25. django_cfg/management/commands/migrate_all.py +13 -126
  26. django_cfg/management/commands/migrator.py +13 -396
  27. django_cfg/management/commands/rundramatiq.py +15 -247
  28. django_cfg/management/commands/rundramatiq_simulator.py +12 -429
  29. django_cfg/management/commands/runserver_ngrok.py +15 -160
  30. django_cfg/management/commands/script.py +12 -488
  31. django_cfg/management/commands/show_config.py +12 -215
  32. django_cfg/management/commands/show_urls.py +12 -342
  33. django_cfg/management/commands/superuser.py +15 -295
  34. django_cfg/management/commands/task_clear.py +14 -217
  35. django_cfg/management/commands/task_status.py +13 -248
  36. django_cfg/management/commands/test_email.py +15 -86
  37. django_cfg/management/commands/test_telegram.py +14 -61
  38. django_cfg/management/commands/test_twilio.py +15 -105
  39. django_cfg/management/commands/tree.py +13 -383
  40. django_cfg/management/commands/validate_openapi.py +10 -0
  41. django_cfg/middleware/README.md +1 -1
  42. django_cfg/middleware/user_activity.py +3 -3
  43. django_cfg/models/__init__.py +2 -2
  44. django_cfg/models/api/drf/spectacular.py +6 -6
  45. django_cfg/models/django/__init__.py +2 -2
  46. django_cfg/models/django/openapi.py +162 -0
  47. django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
  48. django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
  49. django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
  50. django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
  51. django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
  52. django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
  53. django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
  54. django_cfg/modules/django_admin/management/commands/script.py +496 -0
  55. django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
  56. django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
  57. django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
  58. django_cfg/modules/django_admin/management/commands/tree.py +390 -0
  59. django_cfg/modules/django_client/__init__.py +20 -0
  60. django_cfg/modules/django_client/apps.py +35 -0
  61. django_cfg/modules/django_client/core/__init__.py +56 -0
  62. django_cfg/modules/django_client/core/archive/__init__.py +11 -0
  63. django_cfg/modules/django_client/core/archive/manager.py +134 -0
  64. django_cfg/modules/django_client/core/cli/__init__.py +12 -0
  65. django_cfg/modules/django_client/core/cli/main.py +235 -0
  66. django_cfg/modules/django_client/core/config/__init__.py +18 -0
  67. django_cfg/modules/django_client/core/config/config.py +208 -0
  68. django_cfg/modules/django_client/core/config/group.py +101 -0
  69. django_cfg/modules/django_client/core/config/service.py +209 -0
  70. django_cfg/modules/django_client/core/generator/__init__.py +115 -0
  71. django_cfg/modules/django_client/core/generator/base.py +838 -0
  72. django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
  73. django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
  74. django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
  75. django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
  76. django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
  77. django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
  78. django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
  79. django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
  80. django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
  81. django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
  82. django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
  83. django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
  84. django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
  85. django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
  86. django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
  87. django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
  88. django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
  89. django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
  90. django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
  91. django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
  92. django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
  93. django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
  94. django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
  95. django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
  96. django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
  97. django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
  98. django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
  99. django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
  100. django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
  101. django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
  102. django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
  103. django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
  104. django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
  105. django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
  106. django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
  107. django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
  108. django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
  109. django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
  110. django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
  111. django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
  112. django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
  113. django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
  114. django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
  115. django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
  116. django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
  117. django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
  118. django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
  119. django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
  120. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
  121. django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
  122. django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
  123. django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
  124. django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
  125. django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
  126. django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
  127. django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
  128. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
  129. django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
  130. django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
  131. django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
  132. django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
  133. django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
  134. django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
  135. django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
  136. django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
  137. django_cfg/modules/django_client/core/groups/__init__.py +13 -0
  138. django_cfg/modules/django_client/core/groups/detector.py +178 -0
  139. django_cfg/modules/django_client/core/groups/manager.py +314 -0
  140. django_cfg/modules/django_client/core/ir/__init__.py +57 -0
  141. django_cfg/modules/django_client/core/ir/context.py +387 -0
  142. django_cfg/modules/django_client/core/ir/operation.py +518 -0
  143. django_cfg/modules/django_client/core/ir/schema.py +353 -0
  144. django_cfg/modules/django_client/core/parser/__init__.py +74 -0
  145. django_cfg/modules/django_client/core/parser/base.py +648 -0
  146. django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
  147. django_cfg/modules/django_client/core/parser/models/base.py +212 -0
  148. django_cfg/modules/django_client/core/parser/models/components.py +160 -0
  149. django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
  150. django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
  151. django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
  152. django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
  153. django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
  154. django_cfg/modules/django_client/core/validation/__init__.py +22 -0
  155. django_cfg/modules/django_client/core/validation/checker.py +134 -0
  156. django_cfg/modules/django_client/core/validation/fixer.py +216 -0
  157. django_cfg/modules/django_client/core/validation/reporter.py +480 -0
  158. django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
  159. django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
  160. django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
  161. django_cfg/modules/django_client/core/validation/safety.py +266 -0
  162. django_cfg/modules/django_client/management/__init__.py +3 -0
  163. django_cfg/modules/django_client/management/commands/__init__.py +3 -0
  164. django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
  165. django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
  166. django_cfg/modules/django_client/pytest.ini +30 -0
  167. django_cfg/modules/django_client/spectacular/__init__.py +10 -0
  168. django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
  169. django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
  170. django_cfg/modules/django_client/urls.py +72 -0
  171. django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
  172. django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
  173. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  174. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  175. django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
  176. django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
  177. django_cfg/modules/django_email/management/__init__.py +0 -0
  178. django_cfg/modules/django_email/management/commands/__init__.py +0 -0
  179. django_cfg/modules/django_email/management/commands/test_email.py +93 -0
  180. django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
  181. django_cfg/modules/django_logging/django_logger.py +6 -6
  182. django_cfg/modules/django_ngrok/management/__init__.py +0 -0
  183. django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
  184. django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
  185. django_cfg/modules/django_tasks/management/__init__.py +0 -0
  186. django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
  187. django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
  188. django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
  189. django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
  190. django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
  191. django_cfg/modules/django_telegram/management/__init__.py +0 -0
  192. django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
  193. django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
  194. django_cfg/modules/django_twilio/management/__init__.py +0 -0
  195. django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
  196. django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
  197. django_cfg/modules/django_unfold/callbacks/main.py +21 -10
  198. django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
  199. django_cfg/pyproject.toml +2 -6
  200. django_cfg/registry/third_party.py +5 -7
  201. django_cfg/routing/callbacks.py +1 -1
  202. django_cfg/static/admin/css/prose-unfold.css +666 -0
  203. django_cfg/templates/admin/index.html +8 -0
  204. django_cfg/templates/admin/index_new.html +13 -0
  205. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
  206. django_cfg/templates/admin/sections/documentation_section.html +172 -0
  207. django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
  208. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
  209. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
  210. django_cfg/management/commands/generate.py +0 -107
  211. /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
  212. /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
  213. /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
  214. /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
  215. /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
  216. /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
  217. /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
  218. /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
  219. /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
  220. /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
  221. /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
  222. /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
  223. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
  224. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
  225. {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,353 @@
1
+ """
2
+ IR Schema Models - Type-safe schema representation with Request/Response split.
3
+
4
+ This module defines the intermediate representation for OpenAPI schemas,
5
+ normalized from both OpenAPI 3.0.3 and 3.1.0.
6
+
7
+ Key Features:
8
+ - Request/Response split detection (UserRequest vs User)
9
+ - x-enum-varnames support for strongly typed enums
10
+ - Nullable normalization (3.0 nullable: true vs 3.1 type: [.., 'null'])
11
+ - 100% Pydantic 2 with strict validation
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any, Literal
17
+
18
+ from pydantic import BaseModel, ConfigDict, Field
19
+
20
+
21
+ class IRSchemaObject(BaseModel):
22
+ """
23
+ Unified schema representation (version-agnostic, language-agnostic).
24
+
25
+ This model represents a single schema component from the OpenAPI spec,
26
+ normalized for both OpenAPI 3.0.3 and 3.1.0.
27
+
28
+ Key Features:
29
+ - Request/Response awareness: Detects UserRequest vs User patterns
30
+ - x-enum-varnames: Strongly typed enums from drf-spectacular
31
+ - Nullable normalization: Both 3.0 and 3.1 → single `nullable` field
32
+
33
+ Examples:
34
+ >>> # Response model
35
+ >>> user_response = IRSchemaObject(
36
+ ... name="User",
37
+ ... type="object",
38
+ ... properties={
39
+ ... "id": IRSchemaObject(name="id", type="integer"),
40
+ ... "username": IRSchemaObject(name="username", type="string"),
41
+ ... },
42
+ ... required=["id", "username"],
43
+ ... is_request_model=False,
44
+ ... )
45
+
46
+ >>> # Request model (no readOnly fields)
47
+ >>> user_request = IRSchemaObject(
48
+ ... name="UserRequest",
49
+ ... type="object",
50
+ ... properties={
51
+ ... "username": IRSchemaObject(name="username", type="string"),
52
+ ... "email": IRSchemaObject(name="email", type="string"),
53
+ ... },
54
+ ... required=["username", "email"],
55
+ ... is_request_model=True,
56
+ ... related_response="User",
57
+ ... )
58
+
59
+ >>> # Enum with x-enum-varnames
60
+ >>> status_enum = IRSchemaObject(
61
+ ... name="status",
62
+ ... type="integer",
63
+ ... enum=[1, 2, 3],
64
+ ... enum_var_names=["STATUS_NEW", "STATUS_IN_PROGRESS", "STATUS_COMPLETE"],
65
+ ... )
66
+ >>> assert status_enum.has_enum is True
67
+ """
68
+
69
+ model_config = ConfigDict(
70
+ validate_assignment=True, # Validate on attribute assignment
71
+ extra="forbid", # No extra fields allowed
72
+ frozen=False, # Allow mutations (for plugin transforms)
73
+ validate_default=True, # Validate default values
74
+ str_strip_whitespace=True, # Strip whitespace from strings
75
+ )
76
+
77
+ # ===== Core Fields =====
78
+ name: str = Field(..., description="Schema name (e.g., 'User', 'UserRequest')")
79
+ type: str = Field(
80
+ ...,
81
+ description="JSON Schema type: object, string, integer, number, boolean, array, null",
82
+ )
83
+ format: str | None = Field(
84
+ None,
85
+ description="Format hint: date-time, email, uri, uuid, binary, etc.",
86
+ )
87
+ description: str | None = Field(
88
+ None, description="Human-readable description"
89
+ )
90
+
91
+ # ===== Nullable (Normalized from 3.0 and 3.1) =====
92
+ nullable: bool = Field(
93
+ False,
94
+ description="Can be null (normalized from OAS 3.0 nullable: true or 3.1 type: ['string', 'null'])",
95
+ )
96
+
97
+ # ===== Object Properties =====
98
+ properties: dict[str, IRSchemaObject] = Field(
99
+ default_factory=dict,
100
+ description="Object properties (for type: object)",
101
+ )
102
+ required: list[str] = Field(
103
+ default_factory=list,
104
+ description="Required property names",
105
+ )
106
+
107
+ # ===== Array Items =====
108
+ items: IRSchemaObject | None = Field(
109
+ None,
110
+ description="Array item schema (for type: array)",
111
+ )
112
+
113
+ # ===== Enum Support (with x-enum-varnames) =====
114
+ enum: list[str | int | float] | None = Field(
115
+ None,
116
+ description="Enum values (e.g., [1, 2, 3] or ['active', 'inactive'])",
117
+ )
118
+ enum_var_names: list[str] | None = Field(
119
+ None,
120
+ description="Enum variable names from x-enum-varnames (e.g., ['STATUS_NEW', 'STATUS_IN_PROGRESS'])",
121
+ )
122
+ choices: list[dict[str, Any]] | None = Field(
123
+ None,
124
+ description="Django choices from x-choices (e.g., [{'value': 1, 'display_name': 'Active'}])",
125
+ )
126
+ const: str | int | float | None = Field(
127
+ None,
128
+ description="Constant value (OAS 3.1 const keyword)",
129
+ )
130
+
131
+ # ===== Validation Constraints =====
132
+ min_length: int | None = Field(None, ge=0, description="Minimum string length")
133
+ max_length: int | None = Field(None, ge=0, description="Maximum string length")
134
+ pattern: str | None = Field(None, description="Regex pattern for string validation")
135
+ minimum: int | float | None = Field(None, description="Minimum numeric value")
136
+ maximum: int | float | None = Field(None, description="Maximum numeric value")
137
+ exclusive_minimum: int | float | None = Field(
138
+ None, description="Exclusive minimum"
139
+ )
140
+ exclusive_maximum: int | float | None = Field(
141
+ None, description="Exclusive maximum"
142
+ )
143
+ multiple_of: int | float | None = Field(None, gt=0, description="Multiple of")
144
+
145
+ # ===== References =====
146
+ ref: str | None = Field(
147
+ None,
148
+ description="$ref reference (e.g., '#/components/schemas/Profile')",
149
+ )
150
+
151
+ # ===== Request/Response Split (NEW) =====
152
+ is_request_model: bool = Field(
153
+ False,
154
+ description="True if this is a request model (UserRequest, PatchedUser)",
155
+ )
156
+ is_response_model: bool = Field(
157
+ True,
158
+ description="True if this is a response model (User - default)",
159
+ )
160
+ related_request: str | None = Field(
161
+ None,
162
+ description="Related request model name (User → UserRequest)",
163
+ )
164
+ related_response: str | None = Field(
165
+ None,
166
+ description="Related response model name (UserRequest → User)",
167
+ )
168
+ is_patch_model: bool = Field(
169
+ False,
170
+ description="True if this is a PATCH model (PatchedUser)",
171
+ )
172
+
173
+ # ===== Content Metadata (OAS 3.1) =====
174
+ content_media_type: str | None = Field(
175
+ None,
176
+ description="Content media type (OAS 3.1 contentMediaType)",
177
+ )
178
+ content_encoding: str | None = Field(
179
+ None,
180
+ description="Content encoding (OAS 3.1 contentEncoding, e.g., 'base64')",
181
+ )
182
+
183
+ # ===== Additional Metadata =====
184
+ read_only: bool = Field(
185
+ False,
186
+ description="Field is read-only (appears in responses only)",
187
+ )
188
+ write_only: bool = Field(
189
+ False,
190
+ description="Field is write-only (appears in requests only)",
191
+ )
192
+ deprecated: bool = Field(False, description="Field is deprecated")
193
+ example: Any | None = Field(None, description="Example value")
194
+
195
+ # ===== Computed Properties =====
196
+
197
+ @property
198
+ def has_enum(self) -> bool:
199
+ """
200
+ Check if this field is an enum with variable names.
201
+
202
+ Returns:
203
+ True if enum and enum_var_names are both present.
204
+
205
+ Examples:
206
+ >>> schema = IRSchemaObject(
207
+ ... name="status",
208
+ ... type="integer",
209
+ ... enum=[1, 2, 3],
210
+ ... enum_var_names=["STATUS_NEW", "STATUS_IN_PROGRESS", "STATUS_COMPLETE"],
211
+ ... )
212
+ >>> schema.has_enum
213
+ True
214
+
215
+ >>> schema_no_names = IRSchemaObject(
216
+ ... name="status",
217
+ ... type="integer",
218
+ ... enum=[1, 2, 3],
219
+ ... )
220
+ >>> schema_no_names.has_enum
221
+ False
222
+ """
223
+ return (
224
+ self.enum is not None
225
+ and self.enum_var_names is not None
226
+ and len(self.enum) == len(self.enum_var_names)
227
+ )
228
+
229
+ @property
230
+ def is_object(self) -> bool:
231
+ """Check if type is object."""
232
+ return self.type == "object"
233
+
234
+ @property
235
+ def is_array(self) -> bool:
236
+ """Check if type is array."""
237
+ return self.type == "array"
238
+
239
+ @property
240
+ def is_primitive(self) -> bool:
241
+ """Check if type is primitive (string, integer, number, boolean)."""
242
+ return self.type in ("string", "integer", "number", "boolean")
243
+
244
+ @property
245
+ def is_binary(self) -> bool:
246
+ """
247
+ Check if field represents binary data.
248
+
249
+ Handles both OAS 3.0 (format: binary) and OAS 3.1 (contentEncoding: base64).
250
+
251
+ Returns:
252
+ True if field is binary.
253
+ """
254
+ return self.format == "binary" or self.content_encoding == "base64"
255
+
256
+ @property
257
+ def python_type(self) -> str:
258
+ """
259
+ Get Python type hint for this schema.
260
+
261
+ Returns:
262
+ Python type as string (e.g., "str", "int", "list[str]").
263
+
264
+ Examples:
265
+ >>> IRSchemaObject(name="x", type="string").python_type
266
+ 'str'
267
+ >>> IRSchemaObject(name="x", type="integer").python_type
268
+ 'int'
269
+ >>> IRSchemaObject(name="x", type="string", nullable=True).python_type
270
+ 'str | None'
271
+ """
272
+ type_map = {
273
+ "string": "str",
274
+ "integer": "int",
275
+ "number": "float",
276
+ "boolean": "bool",
277
+ "array": f"list[{self.items.python_type if self.items else 'Any'}]",
278
+ "object": "dict[str, Any]",
279
+ }
280
+
281
+ base_type = type_map.get(self.type, "Any")
282
+
283
+ if self.nullable:
284
+ return f"{base_type} | None"
285
+
286
+ return base_type
287
+
288
+ @property
289
+ def typescript_type(self) -> str:
290
+ """
291
+ Get TypeScript type for this schema.
292
+
293
+ Returns:
294
+ TypeScript type as string (e.g., "string", "number", "Array<string>").
295
+
296
+ Examples:
297
+ >>> IRSchemaObject(name="x", type="string").typescript_type
298
+ 'string'
299
+ >>> IRSchemaObject(name="x", type="integer").typescript_type
300
+ 'number'
301
+ >>> IRSchemaObject(name="x", type="string", nullable=True).typescript_type
302
+ 'string | null'
303
+ >>> # Array with $ref items
304
+ >>> IRSchemaObject(
305
+ ... name="users",
306
+ ... type="array",
307
+ ... items=IRSchemaObject(name="User", type="object", ref="User")
308
+ ... ).typescript_type
309
+ 'Array<User>'
310
+ """
311
+ # Handle array type with proper item type resolution
312
+ if self.type == "array":
313
+ if self.items:
314
+ # If items is a $ref, use the ref name directly
315
+ if self.items.ref:
316
+ item_type = self.items.ref
317
+ else:
318
+ item_type = self.items.typescript_type
319
+ base_type = f"Array<{item_type}>"
320
+ else:
321
+ base_type = "Array<any>"
322
+ else:
323
+ type_map = {
324
+ "string": "string",
325
+ "integer": "number",
326
+ "number": "number",
327
+ "boolean": "boolean",
328
+ "object": "Record<string, any>",
329
+ }
330
+ base_type = type_map.get(self.type, "any")
331
+
332
+ if self.nullable:
333
+ return f"{base_type} | null"
334
+
335
+ return base_type
336
+
337
+ def __repr__(self) -> str:
338
+ """String representation for debugging."""
339
+ parts = [f"IRSchemaObject(name={self.name!r}", f"type={self.type!r}"]
340
+
341
+ if self.nullable:
342
+ parts.append("nullable=True")
343
+
344
+ if self.is_request_model:
345
+ parts.append("is_request_model=True")
346
+
347
+ if self.has_enum:
348
+ parts.append(f"enum={self.enum}")
349
+
350
+ if self.ref:
351
+ parts.append(f"ref={self.ref!r}")
352
+
353
+ return ", ".join(parts) + ")"
@@ -0,0 +1,74 @@
1
+ """
2
+ Universal Parser - OpenAPI → IR conversion.
3
+
4
+ This package provides parsers for converting OpenAPI specifications
5
+ (both 3.0.3 and 3.1.0) to the unified IR (Intermediate Representation).
6
+
7
+ Usage:
8
+ >>> from django_cfg.modules.django_client.core.parser import parse_openapi
9
+ >>> spec_dict = {...} # OpenAPI spec as dict
10
+ >>> context = parse_openapi(spec_dict)
11
+ >>> context.openapi_info.version
12
+ '3.1.0'
13
+ >>> context.schemas['User']
14
+ IRSchemaObject(name='User', ...)
15
+
16
+ Auto-detection:
17
+ The parse_openapi() function automatically detects the OpenAPI version
18
+ and uses the appropriate parser (OpenAPI30Parser or OpenAPI31Parser).
19
+ """
20
+
21
+ from typing import Any
22
+
23
+ from django_cfg.modules.django_client.core.ir import IRContext
24
+ from django_cfg.modules.django_client.core.parser.models import OpenAPISpec
25
+ from django_cfg.modules.django_client.core.parser.openapi30 import OpenAPI30Parser
26
+ from django_cfg.modules.django_client.core.parser.openapi31 import OpenAPI31Parser
27
+
28
+ __all__ = [
29
+ "parse_openapi",
30
+ "OpenAPI30Parser",
31
+ "OpenAPI31Parser",
32
+ ]
33
+
34
+
35
+ def parse_openapi(spec_dict: dict[str, Any]) -> IRContext:
36
+ """
37
+ Parse OpenAPI specification to IR (auto-detects version).
38
+
39
+ This is the main entry point for parsing OpenAPI specs. It automatically
40
+ detects the OpenAPI version and uses the appropriate parser.
41
+
42
+ Args:
43
+ spec_dict: OpenAPI spec as dictionary (from JSON/YAML)
44
+
45
+ Returns:
46
+ IRContext with all schemas and operations
47
+
48
+ Raises:
49
+ ValueError: If OpenAPI version is unsupported or COMPONENT_SPLIT_REQUEST not detected
50
+
51
+ Examples:
52
+ >>> spec = {
53
+ ... "openapi": "3.1.0",
54
+ ... "info": {"title": "My API", "version": "1.0.0"},
55
+ ... "paths": {...},
56
+ ... "components": {"schemas": {...}},
57
+ ... }
58
+ >>> context = parse_openapi(spec)
59
+ >>> context.has_request_response_split
60
+ True
61
+ """
62
+ # Validate and parse spec
63
+ spec = OpenAPISpec.model_validate(spec_dict)
64
+
65
+ # Select parser based on version
66
+ if spec.is_openapi_30:
67
+ parser = OpenAPI30Parser(spec)
68
+ elif spec.is_openapi_31:
69
+ parser = OpenAPI31Parser(spec)
70
+ else:
71
+ raise ValueError(f"Unsupported OpenAPI version: {spec.openapi}")
72
+
73
+ # Parse to IR
74
+ return parser.parse()