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.
- django_cfg/apps/agents/management/commands/create_agent.py +1 -1
- django_cfg/apps/agents/management/commands/orchestrator_status.py +3 -3
- django_cfg/apps/newsletter/serializers.py +40 -3
- django_cfg/apps/newsletter/views/campaigns.py +12 -3
- django_cfg/apps/newsletter/views/emails.py +14 -3
- django_cfg/apps/newsletter/views/subscriptions.py +12 -2
- django_cfg/apps/payments/views/api/currencies.py +49 -6
- django_cfg/apps/payments/views/api/webhooks.py +72 -7
- django_cfg/apps/payments/views/overview/serializers.py +34 -1
- django_cfg/apps/payments/views/overview/views.py +2 -1
- django_cfg/apps/payments/views/serializers/payments.py +6 -6
- django_cfg/apps/urls.py +106 -45
- django_cfg/core/base/config_model.py +2 -2
- django_cfg/core/constants.py +1 -1
- django_cfg/core/generation/integration_generators/__init__.py +1 -1
- django_cfg/core/generation/integration_generators/api.py +73 -49
- django_cfg/core/integration/display/startup.py +30 -22
- django_cfg/core/integration/url_integration.py +15 -16
- django_cfg/management/commands/check_endpoints.py +11 -160
- django_cfg/management/commands/check_settings.py +13 -348
- django_cfg/management/commands/clear_constance.py +13 -201
- django_cfg/management/commands/create_token.py +13 -321
- django_cfg/management/commands/generate_clients.py +23 -0
- django_cfg/management/commands/list_urls.py +13 -306
- django_cfg/management/commands/migrate_all.py +13 -126
- django_cfg/management/commands/migrator.py +13 -396
- django_cfg/management/commands/rundramatiq.py +15 -247
- django_cfg/management/commands/rundramatiq_simulator.py +12 -429
- django_cfg/management/commands/runserver_ngrok.py +15 -160
- django_cfg/management/commands/script.py +12 -488
- django_cfg/management/commands/show_config.py +12 -215
- django_cfg/management/commands/show_urls.py +12 -342
- django_cfg/management/commands/superuser.py +15 -295
- django_cfg/management/commands/task_clear.py +14 -217
- django_cfg/management/commands/task_status.py +13 -248
- django_cfg/management/commands/test_email.py +15 -86
- django_cfg/management/commands/test_telegram.py +14 -61
- django_cfg/management/commands/test_twilio.py +15 -105
- django_cfg/management/commands/tree.py +13 -383
- django_cfg/management/commands/validate_openapi.py +10 -0
- django_cfg/middleware/README.md +1 -1
- django_cfg/middleware/user_activity.py +3 -3
- django_cfg/models/__init__.py +2 -2
- django_cfg/models/api/drf/spectacular.py +6 -6
- django_cfg/models/django/__init__.py +2 -2
- django_cfg/models/django/openapi.py +162 -0
- django_cfg/modules/django_admin/management/commands/check_endpoints.py +169 -0
- django_cfg/modules/django_admin/management/commands/check_settings.py +355 -0
- django_cfg/modules/django_admin/management/commands/clear_constance.py +208 -0
- django_cfg/modules/django_admin/management/commands/create_token.py +328 -0
- django_cfg/modules/django_admin/management/commands/list_urls.py +313 -0
- django_cfg/modules/django_admin/management/commands/migrate_all.py +133 -0
- django_cfg/modules/django_admin/management/commands/migrator.py +403 -0
- django_cfg/modules/django_admin/management/commands/script.py +496 -0
- django_cfg/modules/django_admin/management/commands/show_config.py +225 -0
- django_cfg/modules/django_admin/management/commands/show_urls.py +361 -0
- django_cfg/modules/django_admin/management/commands/superuser.py +302 -0
- django_cfg/modules/django_admin/management/commands/tree.py +390 -0
- django_cfg/modules/django_client/__init__.py +20 -0
- django_cfg/modules/django_client/apps.py +35 -0
- django_cfg/modules/django_client/core/__init__.py +56 -0
- django_cfg/modules/django_client/core/archive/__init__.py +11 -0
- django_cfg/modules/django_client/core/archive/manager.py +134 -0
- django_cfg/modules/django_client/core/cli/__init__.py +12 -0
- django_cfg/modules/django_client/core/cli/main.py +235 -0
- django_cfg/modules/django_client/core/config/__init__.py +18 -0
- django_cfg/modules/django_client/core/config/config.py +208 -0
- django_cfg/modules/django_client/core/config/group.py +101 -0
- django_cfg/modules/django_client/core/config/service.py +209 -0
- django_cfg/modules/django_client/core/generator/__init__.py +115 -0
- django_cfg/modules/django_client/core/generator/base.py +838 -0
- django_cfg/modules/django_client/core/generator/python/__init__.py +16 -0
- django_cfg/modules/django_client/core/generator/python/async_client_gen.py +174 -0
- django_cfg/modules/django_client/core/generator/python/files_generator.py +180 -0
- django_cfg/modules/django_client/core/generator/python/generator.py +182 -0
- django_cfg/modules/django_client/core/generator/python/models_generator.py +318 -0
- django_cfg/modules/django_client/core/generator/python/operations_generator.py +278 -0
- django_cfg/modules/django_client/core/generator/python/sync_client_gen.py +102 -0
- django_cfg/modules/django_client/core/generator/python/templates/__init__.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/api_wrapper.py.jinja +153 -0
- django_cfg/modules/django_client/core/generator/python/templates/app_init.py.jinja +6 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/app_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/flat_client.py.jinja +38 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client.py.jinja +68 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/main_client_file.py.jinja +14 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_main_client.py.jinja +50 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_operation_method.py.jinja +9 -0
- django_cfg/modules/django_client/core/generator/python/templates/client/sync_sub_client.py.jinja +18 -0
- django_cfg/modules/django_client/core/generator/python/templates/client_file.py.jinja +13 -0
- django_cfg/modules/django_client/core/generator/python/templates/main_init.py.jinja +52 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/app_models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/enum_class.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/enums.py.jinja +8 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/models.py.jinja +17 -0
- django_cfg/modules/django_client/core/generator/python/templates/models/schema_class.py.jinja +21 -0
- django_cfg/modules/django_client/core/generator/python/templates/pyproject.toml.jinja +55 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/logger.py.jinja +255 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.jinja +271 -0
- django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja +12 -0
- django_cfg/modules/django_client/core/generator/typescript/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/typescript/client_generator.py +165 -0
- django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py +428 -0
- django_cfg/modules/django_client/core/generator/typescript/files_generator.py +207 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +432 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +536 -0
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +245 -0
- django_cfg/modules/django_client/core/generator/typescript/operations_generator.py +298 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +329 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja +131 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja +2 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/app_client.ts.jinja +18 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/client.ts.jinja +403 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/flat_client.ts.jinja +109 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/main_client_file.ts.jinja +10 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/operation.ts.jinja +61 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_client.ts.jinja +15 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja +9 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +45 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja +30 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/index.ts.jinja +5 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/main_index.ts.jinja +268 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja +4 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja +8 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/package.json.jinja +52 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/index.ts.jinja +21 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/schemas/schema.ts.jinja +24 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/tsconfig.json.jinja +20 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/errors.ts.jinja +116 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/http.ts.jinja +98 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/logger.ts.jinja +259 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/retry.ts.jinja +175 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/schema.ts.jinja +7 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/utils/storage.ts.jinja +158 -0
- django_cfg/modules/django_client/core/groups/__init__.py +13 -0
- django_cfg/modules/django_client/core/groups/detector.py +178 -0
- django_cfg/modules/django_client/core/groups/manager.py +314 -0
- django_cfg/modules/django_client/core/ir/__init__.py +57 -0
- django_cfg/modules/django_client/core/ir/context.py +387 -0
- django_cfg/modules/django_client/core/ir/operation.py +518 -0
- django_cfg/modules/django_client/core/ir/schema.py +353 -0
- django_cfg/modules/django_client/core/parser/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/base.py +648 -0
- django_cfg/modules/django_client/core/parser/models/__init__.py +74 -0
- django_cfg/modules/django_client/core/parser/models/base.py +212 -0
- django_cfg/modules/django_client/core/parser/models/components.py +160 -0
- django_cfg/modules/django_client/core/parser/models/openapi.py +203 -0
- django_cfg/modules/django_client/core/parser/models/operation.py +207 -0
- django_cfg/modules/django_client/core/parser/models/schema.py +266 -0
- django_cfg/modules/django_client/core/parser/openapi30.py +56 -0
- django_cfg/modules/django_client/core/parser/openapi31.py +64 -0
- django_cfg/modules/django_client/core/validation/__init__.py +22 -0
- django_cfg/modules/django_client/core/validation/checker.py +134 -0
- django_cfg/modules/django_client/core/validation/fixer.py +216 -0
- django_cfg/modules/django_client/core/validation/reporter.py +480 -0
- django_cfg/modules/django_client/core/validation/rules/__init__.py +11 -0
- django_cfg/modules/django_client/core/validation/rules/base.py +96 -0
- django_cfg/modules/django_client/core/validation/rules/type_hints.py +288 -0
- django_cfg/modules/django_client/core/validation/safety.py +266 -0
- django_cfg/modules/django_client/management/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/__init__.py +3 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +427 -0
- django_cfg/modules/django_client/management/commands/validate_openapi.py +343 -0
- django_cfg/modules/django_client/pytest.ini +30 -0
- django_cfg/modules/django_client/spectacular/__init__.py +10 -0
- django_cfg/modules/django_client/spectacular/async_detection.py +187 -0
- django_cfg/modules/django_client/spectacular/enum_naming.py +192 -0
- django_cfg/modules/django_client/urls.py +72 -0
- django_cfg/{dashboard → modules/django_dashboard}/DEBUG_README.md +2 -2
- django_cfg/{dashboard → modules/django_dashboard}/REFACTORING_SUMMARY.md +1 -1
- django_cfg/modules/django_dashboard/management/__init__.py +0 -0
- django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
- django_cfg/{dashboard → modules/django_dashboard}/management/commands/debug_dashboard.py +5 -5
- django_cfg/modules/django_dashboard/sections/documentation.py +391 -0
- django_cfg/modules/django_email/management/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/__init__.py +0 -0
- django_cfg/modules/django_email/management/commands/test_email.py +93 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +1 -1
- django_cfg/modules/django_logging/django_logger.py +6 -6
- django_cfg/modules/django_ngrok/management/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/__init__.py +0 -0
- django_cfg/modules/django_ngrok/management/commands/runserver_ngrok.py +167 -0
- django_cfg/modules/django_tasks/management/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/__init__.py +0 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq.py +254 -0
- django_cfg/modules/django_tasks/management/commands/rundramatiq_simulator.py +437 -0
- django_cfg/modules/django_tasks/management/commands/task_clear.py +226 -0
- django_cfg/modules/django_tasks/management/commands/task_status.py +257 -0
- django_cfg/modules/django_telegram/management/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/__init__.py +0 -0
- django_cfg/modules/django_telegram/management/commands/test_telegram.py +68 -0
- django_cfg/modules/django_twilio/management/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/__init__.py +0 -0
- django_cfg/modules/django_twilio/management/commands/test_twilio.py +112 -0
- django_cfg/modules/django_unfold/callbacks/main.py +21 -10
- django_cfg/modules/django_unfold/callbacks/revolution.py +41 -36
- django_cfg/pyproject.toml +2 -6
- django_cfg/registry/third_party.py +5 -7
- django_cfg/routing/callbacks.py +1 -1
- django_cfg/static/admin/css/prose-unfold.css +666 -0
- django_cfg/templates/admin/index.html +8 -0
- django_cfg/templates/admin/index_new.html +13 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +15 -3
- django_cfg/templates/admin/sections/documentation_section.html +172 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +231 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/METADATA +2 -2
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/RECORD +224 -74
- django_cfg/management/commands/generate.py +0 -107
- /django_cfg/models/django/{revolution.py → revolution_legacy.py} +0 -0
- /django_cfg/{dashboard → modules/django_admin}/management/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_admin}/management/commands/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/components.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/debug.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/__init__.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/base.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/commands.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/overview.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/stats.py +0 -0
- /django_cfg/{dashboard → modules/django_dashboard}/sections/system.py +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.10.dist-info → django_cfg-1.4.13.dist-info}/entry_points.txt +0 -0
- {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()
|