django-cfg 1.4.108__py3-none-any.whl → 1.4.109__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.109.dist-info}/METADATA +1 -1
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.109.dist-info}/RECORD +8 -18
- django_cfg/modules/django_client/system/__init__.py +0 -24
- django_cfg/modules/django_client/system/base_generator.py +0 -123
- django_cfg/modules/django_client/system/generate_mjs_clients.py +0 -176
- django_cfg/modules/django_client/system/mjs_generator.py +0 -219
- django_cfg/modules/django_client/system/schema_parser.py +0 -199
- django_cfg/modules/django_client/system/templates/api_client.js.j2 +0 -87
- django_cfg/modules/django_client/system/templates/app_index.js.j2 +0 -13
- django_cfg/modules/django_client/system/templates/base_client.js.j2 +0 -166
- django_cfg/modules/django_client/system/templates/main_index.js.j2 +0 -80
- django_cfg/modules/django_client/system/templates/types.js.j2 +0 -24
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.109.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.109.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.109.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
django_cfg/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.109"
|
|
8
8
|
description = "Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "nextjs-admin", "react-admin", "websocket", "centrifugo", "real-time", "typescript-generation", "ai-agents", "enterprise-django", "django-settings", "type-safe-config", "modern-django",]
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-cfg
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.109
|
|
4
4
|
Summary: Modern Django framework with type-safe Pydantic v2 configuration, Next.js admin integration, real-time WebSockets, and 8 enterprise apps. Replace settings.py with validated models, 90% less code. Production-ready with AI agents, auto-generated TypeScript clients, and zero-config features.
|
|
5
5
|
Project-URL: Homepage, https://djangocfg.com
|
|
6
6
|
Project-URL: Documentation, https://djangocfg.com
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
django_cfg/README.md,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
django_cfg/__init__.py,sha256=
|
|
2
|
+
django_cfg/__init__.py,sha256=fOg2eKyhb6AAON0Y5yU5AoTiDjb3u-C0JommQ8cTqgg,1621
|
|
3
3
|
django_cfg/apps.py,sha256=72m3uuvyqGiLx6gOfE-BD3P61jddCCERuBOYpxTX518,1605
|
|
4
4
|
django_cfg/config.py,sha256=y4Z3rnYsHBE0TehpwAIPaxr---mkvyKrZGGsNwYso74,1398
|
|
5
5
|
django_cfg/apps/__init__.py,sha256=JtDmEYt1OcleWM2ZaeX0LKDnRQzPOavfaXBWG4ECB5Q,26
|
|
@@ -845,16 +845,6 @@ django_cfg/modules/django_client/spectacular/__init__.py,sha256=M8fG-odu2ltkG36a
|
|
|
845
845
|
django_cfg/modules/django_client/spectacular/async_detection.py,sha256=S_pwGR7_2SIWHjZJyiu7SCfySF3Nr3P8eqjDyBSkkLs,5731
|
|
846
846
|
django_cfg/modules/django_client/spectacular/enum_naming.py,sha256=FMJyJiS7nBs-Q9yDs5M0qsNLVX8UJhFgJK9Svo-T2tk,6486
|
|
847
847
|
django_cfg/modules/django_client/spectacular/schema.py,sha256=6oJxoNsSsZLlM2qmKuJGU1sPPyEGr1Xit_xBfLvprHU,1639
|
|
848
|
-
django_cfg/modules/django_client/system/__init__.py,sha256=uDARdYmmJ2yR0pCNaIQpsAPcErvhO7FNYX0CTbow8Y8,622
|
|
849
|
-
django_cfg/modules/django_client/system/base_generator.py,sha256=skEf8_vXz3AeHZqOg_BjqsL1AToPn-RQY0211-ZXncM,4396
|
|
850
|
-
django_cfg/modules/django_client/system/generate_mjs_clients.py,sha256=N8GsRUgrSJ4MaiJ99q5tvYg3K7OStF6VjjZO5J-7woQ,5749
|
|
851
|
-
django_cfg/modules/django_client/system/mjs_generator.py,sha256=c6ktFR8Ipuju3CGPe6NUnlBwiqPrT_ik1ysRsBcd9Nw,7351
|
|
852
|
-
django_cfg/modules/django_client/system/schema_parser.py,sha256=0IzTKcSEWDDe4FkAqNOSWG4MQTbQzg-bAXHOV0v3sZs,7216
|
|
853
|
-
django_cfg/modules/django_client/system/templates/api_client.js.j2,sha256=u3CLl4LVNgGrusuhiY8Q7btWpwkF2ijYxtZmFCsbBfk,2803
|
|
854
|
-
django_cfg/modules/django_client/system/templates/app_index.js.j2,sha256=PPxiIaYw7U5VeMv5bySu3srxFHRBwo5mt1yL-l1SZ_0,387
|
|
855
|
-
django_cfg/modules/django_client/system/templates/base_client.js.j2,sha256=Qjngf8783y47FnQ0ZOvtdFTSF6t-vYqvJaFPJmaTY40,4524
|
|
856
|
-
django_cfg/modules/django_client/system/templates/main_index.js.j2,sha256=rv5uYR4hjZRRCjWGqU_a7UsDbPx-CMu8itKdVBNsLHo,1827
|
|
857
|
-
django_cfg/modules/django_client/system/templates/types.js.j2,sha256=hO6y0WwMOpkd3sMfmu9b2X7I2dyosodI8oW9Y8evhZo,744
|
|
858
848
|
django_cfg/modules/django_currency/README.md,sha256=4R7KtF-rhR6yRg5jbsXwOVXMZNGEpOyW4N3WGN89W7A,4670
|
|
859
849
|
django_cfg/modules/django_currency/__init__.py,sha256=Vm9-KPM7HqK0SM35YHjh2ciJoTYkDz3PR2bod6B2Alg,2490
|
|
860
850
|
django_cfg/modules/django_currency/clients/__init__.py,sha256=bgrd3kJIgY-D80AQfs45pq9QanOkHdLfz2yAKsRnCyY,235
|
|
@@ -1025,7 +1015,7 @@ django_cfg/static/admin/js/alpine/commands-section.js,sha256=8z2MQNwZF9Tx_2EK1AY
|
|
|
1025
1015
|
django_cfg/static/admin/js/alpine/dashboard-tabs.js,sha256=ob8Q_I9lFLDv_hFERXgTyvqMDBspAGfzCxI_7slRur4,1354
|
|
1026
1016
|
django_cfg/static/admin/js/alpine/system-metrics.js,sha256=m-Fg55K_vpHXToD46PXL9twl4OBF_V9MONvbSWbQqDw,440
|
|
1027
1017
|
django_cfg/static/admin/js/alpine/toggle-section.js,sha256=T141NFmy0fRJyGGuuaCJRjJXwPam-xxtQNW1hi8BJbc,672
|
|
1028
|
-
django_cfg/static/frontend/admin.zip,sha256=
|
|
1018
|
+
django_cfg/static/frontend/admin.zip,sha256=MMRlhWT3NGk3_OkD06pRpNI9dskRQshbGdKAeUQdLFQ,7645836
|
|
1029
1019
|
django_cfg/static/js/api-loader.mjs,sha256=boGqqRGnFR-Mzo_RQOjhAzNvsb7QxZddSwMKROzkk9Q,5163
|
|
1030
1020
|
django_cfg/static/js/api/base.mjs,sha256=KUxZHHdELAV8mNnACpwJRvaQhdJxp-n5LFEQ4oUZxBo,4707
|
|
1031
1021
|
django_cfg/static/js/api/index.mjs,sha256=_-Q04jjHcgwi4CGfiaLyiOR6NW7Yu1HBhJWp2J1cjpc,2538
|
|
@@ -1060,9 +1050,9 @@ django_cfg/utils/version_check.py,sha256=WO51J2m2e-wVqWCRwbultEwu3q1lQasV67Mw2aa
|
|
|
1060
1050
|
django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
|
|
1061
1051
|
django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
|
|
1062
1052
|
django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1063
|
-
django_cfg/pyproject.toml,sha256=
|
|
1064
|
-
django_cfg-1.4.
|
|
1065
|
-
django_cfg-1.4.
|
|
1066
|
-
django_cfg-1.4.
|
|
1067
|
-
django_cfg-1.4.
|
|
1068
|
-
django_cfg-1.4.
|
|
1053
|
+
django_cfg/pyproject.toml,sha256=yWqSAdesaQe0xiDRPoQw25Opq0yhKyr1nZM-k058-E4,8614
|
|
1054
|
+
django_cfg-1.4.109.dist-info/METADATA,sha256=Xml6V2JxDfn4DHkJAC0BLkupFncQDqJsON9sROSJvms,23801
|
|
1055
|
+
django_cfg-1.4.109.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
1056
|
+
django_cfg-1.4.109.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
|
|
1057
|
+
django_cfg-1.4.109.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
|
|
1058
|
+
django_cfg-1.4.109.dist-info/RECORD,,
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MJS Client Generation System for django-cfg
|
|
3
|
-
|
|
4
|
-
This module provides a modular system for generating JavaScript ES modules
|
|
5
|
-
with JSDoc type annotations from OpenAPI schemas.
|
|
6
|
-
|
|
7
|
-
Components:
|
|
8
|
-
- base_generator: Base class with common utilities
|
|
9
|
-
- schema_parser: OpenAPI schema parsing and type extraction
|
|
10
|
-
- mjs_generator: Main generator for MJS clients
|
|
11
|
-
- templates/: Jinja2 templates for code generation
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from .base_generator import BaseGenerator
|
|
15
|
-
from .mjs_generator import MJSGenerator
|
|
16
|
-
from .schema_parser import SchemaParser
|
|
17
|
-
|
|
18
|
-
__all__ = [
|
|
19
|
-
'BaseGenerator',
|
|
20
|
-
'SchemaParser',
|
|
21
|
-
'MJSGenerator'
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
__version__ = '1.0.0'
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Base generator class for MJS clients.
|
|
3
|
-
Handles schema loading and common utilities.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import json
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Any, Dict
|
|
9
|
-
|
|
10
|
-
import yaml
|
|
11
|
-
from jinja2 import Environment, FileSystemLoader
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class BaseGenerator:
|
|
15
|
-
"""Base class for API client generation."""
|
|
16
|
-
|
|
17
|
-
def __init__(self, schema_path: Path, output_dir: Path):
|
|
18
|
-
"""Initialize the generator with schema and output directory."""
|
|
19
|
-
self.schema_path = schema_path
|
|
20
|
-
self.output_dir = output_dir
|
|
21
|
-
self.schema = self._load_schema()
|
|
22
|
-
|
|
23
|
-
# Set up Jinja2 environment
|
|
24
|
-
template_dir = Path(__file__).parent / 'templates'
|
|
25
|
-
self.jinja_env = Environment(
|
|
26
|
-
loader=FileSystemLoader(template_dir),
|
|
27
|
-
trim_blocks=True,
|
|
28
|
-
lstrip_blocks=True,
|
|
29
|
-
keep_trailing_newline=True
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
def _load_schema(self) -> Dict[str, Any]:
|
|
33
|
-
"""Load OpenAPI schema from JSON/YAML file."""
|
|
34
|
-
with open(self.schema_path) as f:
|
|
35
|
-
if self.schema_path.suffix == '.yaml':
|
|
36
|
-
return yaml.safe_load(f)
|
|
37
|
-
else:
|
|
38
|
-
return json.load(f)
|
|
39
|
-
|
|
40
|
-
def to_camel_case(self, snake_str: str) -> str:
|
|
41
|
-
"""Convert snake_case to camelCase."""
|
|
42
|
-
snake_str = snake_str.replace(' ', '_').replace('-', '_')
|
|
43
|
-
components = snake_str.split('_')
|
|
44
|
-
return components[0] + ''.join(x.title() for x in components[1:])
|
|
45
|
-
|
|
46
|
-
def to_pascal_case(self, snake_str: str) -> str:
|
|
47
|
-
"""Convert snake_case to PascalCase."""
|
|
48
|
-
snake_str = snake_str.replace(' ', '_').replace('-', '_')
|
|
49
|
-
return ''.join(word.capitalize() for word in snake_str.split('_'))
|
|
50
|
-
|
|
51
|
-
def sanitize_identifier(self, name: str) -> str:
|
|
52
|
-
"""Sanitize a name to be a valid JavaScript identifier."""
|
|
53
|
-
# Replace spaces and special chars with underscores
|
|
54
|
-
sanitized = name.replace(' ', '_').replace('-', '_').replace('.', '_')
|
|
55
|
-
# Remove any remaining invalid characters
|
|
56
|
-
import re
|
|
57
|
-
sanitized = re.sub(r'[^a-zA-Z0-9_]', '', sanitized)
|
|
58
|
-
# Ensure it doesn't start with a number
|
|
59
|
-
if sanitized and sanitized[0].isdigit():
|
|
60
|
-
sanitized = '_' + sanitized
|
|
61
|
-
return sanitized
|
|
62
|
-
|
|
63
|
-
def get_type_from_schema(self, schema: Dict[str, Any]) -> str:
|
|
64
|
-
"""Extract JavaScript type from OpenAPI schema."""
|
|
65
|
-
if not schema:
|
|
66
|
-
return 'any'
|
|
67
|
-
|
|
68
|
-
# Handle references
|
|
69
|
-
if '$ref' in schema:
|
|
70
|
-
ref_name = schema['$ref'].split('/')[-1]
|
|
71
|
-
return ref_name
|
|
72
|
-
|
|
73
|
-
# Handle arrays
|
|
74
|
-
if schema.get('type') == 'array':
|
|
75
|
-
items_type = self.get_type_from_schema(schema.get('items', {}))
|
|
76
|
-
return f'{items_type}[]'
|
|
77
|
-
|
|
78
|
-
# Handle objects
|
|
79
|
-
if schema.get('type') == 'object':
|
|
80
|
-
# If it has properties, we could generate an interface
|
|
81
|
-
# For now, just return a generic object type
|
|
82
|
-
if 'properties' in schema:
|
|
83
|
-
return 'Object'
|
|
84
|
-
return 'any'
|
|
85
|
-
|
|
86
|
-
# Handle primitive types
|
|
87
|
-
type_mapping = {
|
|
88
|
-
'string': 'string',
|
|
89
|
-
'integer': 'number',
|
|
90
|
-
'number': 'number',
|
|
91
|
-
'boolean': 'boolean',
|
|
92
|
-
'null': 'null'
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
openapi_type = schema.get('type', 'any')
|
|
96
|
-
return type_mapping.get(openapi_type, 'any')
|
|
97
|
-
|
|
98
|
-
def extract_response_type(self, operation: Dict[str, Any]) -> str:
|
|
99
|
-
"""Extract the response type from an operation."""
|
|
100
|
-
responses = operation.get('responses', {})
|
|
101
|
-
|
|
102
|
-
# Look for successful response (200, 201, etc.)
|
|
103
|
-
for status in ['200', '201', '202', '204']:
|
|
104
|
-
if status in responses:
|
|
105
|
-
response = responses[status]
|
|
106
|
-
|
|
107
|
-
# Handle 204 No Content
|
|
108
|
-
if status == '204':
|
|
109
|
-
return 'void'
|
|
110
|
-
|
|
111
|
-
# Extract content type
|
|
112
|
-
content = response.get('content', {})
|
|
113
|
-
if 'application/json' in content:
|
|
114
|
-
schema = content['application/json'].get('schema', {})
|
|
115
|
-
return self.get_type_from_schema(schema)
|
|
116
|
-
|
|
117
|
-
# Default to any if we can't determine the type
|
|
118
|
-
return 'any'
|
|
119
|
-
|
|
120
|
-
def render_template(self, template_name: str, **context) -> str:
|
|
121
|
-
"""Render a Jinja2 template with the given context."""
|
|
122
|
-
template = self.jinja_env.get_template(template_name)
|
|
123
|
-
return template.render(**context)
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Script to generate lightweight MJS (ES Module) clients for django-cfg project.
|
|
4
|
-
|
|
5
|
-
This script generates JavaScript ES modules with JSDoc type annotations
|
|
6
|
-
organized by Django apps for use in Django HTML templates.
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
poetry run python src/django_cfg/modules/django_client/system/generate_mjs_clients.py
|
|
10
|
-
poetry run python src/django_cfg/modules/django_client/system/generate_mjs_clients.py --clean
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import argparse
|
|
14
|
-
import os
|
|
15
|
-
import shutil
|
|
16
|
-
import subprocess
|
|
17
|
-
import sys
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
# Add current directory to path for imports
|
|
21
|
-
sys.path.insert(0, str(Path(__file__).parent))
|
|
22
|
-
|
|
23
|
-
from mjs_generator import MJSGenerator
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def main():
|
|
27
|
-
"""Main function to generate MJS clients."""
|
|
28
|
-
|
|
29
|
-
parser = argparse.ArgumentParser(
|
|
30
|
-
description='Generate MJS API clients with JSDoc types for django-cfg project'
|
|
31
|
-
)
|
|
32
|
-
parser.add_argument(
|
|
33
|
-
'--clean',
|
|
34
|
-
action='store_true',
|
|
35
|
-
help='Clean existing files before generation'
|
|
36
|
-
)
|
|
37
|
-
parser.add_argument(
|
|
38
|
-
'--schema',
|
|
39
|
-
type=str,
|
|
40
|
-
help='Path to OpenAPI schema file (YAML or JSON)'
|
|
41
|
-
)
|
|
42
|
-
parser.add_argument(
|
|
43
|
-
'--output',
|
|
44
|
-
type=str,
|
|
45
|
-
help='Output directory for generated clients'
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
args = parser.parse_args()
|
|
49
|
-
|
|
50
|
-
# Determine paths
|
|
51
|
-
script_dir = Path(__file__).parent
|
|
52
|
-
django_client_dir = script_dir.parent # django_client module
|
|
53
|
-
modules_dir = django_client_dir.parent # modules
|
|
54
|
-
django_cfg_dir = modules_dir.parent # django_cfg
|
|
55
|
-
src_dir = django_cfg_dir.parent # src
|
|
56
|
-
dev_dir = src_dir.parent # django-cfg-dev
|
|
57
|
-
projects_dir = dev_dir.parent # projects
|
|
58
|
-
root_dir = projects_dir.parent # root (django-cfg)
|
|
59
|
-
example_django_dir = root_dir / "solution" / "projects" / "django"
|
|
60
|
-
|
|
61
|
-
# Output directory for MJS clients
|
|
62
|
-
if args.output:
|
|
63
|
-
mjs_output_dir = Path(args.output)
|
|
64
|
-
else:
|
|
65
|
-
mjs_output_dir = django_cfg_dir / "static" / "js" / "api"
|
|
66
|
-
|
|
67
|
-
# Schema location
|
|
68
|
-
if args.schema:
|
|
69
|
-
schema_path = Path(args.schema)
|
|
70
|
-
else:
|
|
71
|
-
schema_path = example_django_dir / "openapi" / "schemas" / "cfg.yaml"
|
|
72
|
-
|
|
73
|
-
print("🚀 Generating MJS API clients with JSDoc type annotations...")
|
|
74
|
-
print(f"📁 Schema: {schema_path}")
|
|
75
|
-
print(f"📁 Output: {mjs_output_dir}")
|
|
76
|
-
|
|
77
|
-
# Generate schema if it doesn't exist
|
|
78
|
-
if not schema_path.exists():
|
|
79
|
-
print("⚙️ Generating OpenAPI schema first...")
|
|
80
|
-
os.chdir(example_django_dir)
|
|
81
|
-
|
|
82
|
-
result = subprocess.run(
|
|
83
|
-
["poetry", "run", "python", "manage.py", "generate_client", "--typescript", "--no-python"],
|
|
84
|
-
capture_output=True,
|
|
85
|
-
text=True
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
if result.returncode != 0 or not schema_path.exists():
|
|
89
|
-
print("❌ Error: Could not generate schema")
|
|
90
|
-
print(result.stderr)
|
|
91
|
-
sys.exit(1)
|
|
92
|
-
|
|
93
|
-
# Check for dependencies
|
|
94
|
-
try:
|
|
95
|
-
import yaml
|
|
96
|
-
except ImportError:
|
|
97
|
-
print("❌ PyYAML is required. Install with: pip install pyyaml")
|
|
98
|
-
sys.exit(1)
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
import jinja2
|
|
102
|
-
except ImportError:
|
|
103
|
-
print("❌ Jinja2 is required. Install with: pip install jinja2")
|
|
104
|
-
sys.exit(1)
|
|
105
|
-
|
|
106
|
-
# Generate MJS clients
|
|
107
|
-
try:
|
|
108
|
-
generator = MJSGenerator(schema_path, mjs_output_dir)
|
|
109
|
-
file_count = generator.generate()
|
|
110
|
-
|
|
111
|
-
print("\n✅ MJS clients generated successfully!")
|
|
112
|
-
print(f"📊 Generated {file_count} files in {mjs_output_dir}")
|
|
113
|
-
print("📂 Structure: Organized by Django apps with JSDoc types")
|
|
114
|
-
|
|
115
|
-
# Clean up openapi directory if it was created
|
|
116
|
-
openapi_dir = example_django_dir / "openapi"
|
|
117
|
-
if openapi_dir.exists():
|
|
118
|
-
print(f"🧹 Cleaning up example project openapi directory: {openapi_dir}")
|
|
119
|
-
shutil.rmtree(openapi_dir)
|
|
120
|
-
|
|
121
|
-
print("\n📝 Usage examples in Django template:")
|
|
122
|
-
print("""
|
|
123
|
-
<!-- Import from app folder with type support -->
|
|
124
|
-
<script type="module">
|
|
125
|
-
// Your IDE will provide autocomplete and type hints!
|
|
126
|
-
import { tasksAPI } from '{% static "api/tasks/index.mjs" %}';
|
|
127
|
-
|
|
128
|
-
async function loadTaskStats() {
|
|
129
|
-
try {
|
|
130
|
-
// IDE knows the return type from JSDoc annotations
|
|
131
|
-
const stats = await tasksAPI.cfgTasksApiTasksStatsRetrieve();
|
|
132
|
-
console.log('Task Stats:', stats);
|
|
133
|
-
} catch (error) {
|
|
134
|
-
// APIError type is documented
|
|
135
|
-
console.error('Error:', error);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
loadTaskStats();
|
|
140
|
-
</script>
|
|
141
|
-
|
|
142
|
-
<!-- Import multiple APIs from main index -->
|
|
143
|
-
<script type="module">
|
|
144
|
-
import { tasksAPI, paymentsAPI } from '{% static "api/index.mjs" %}';
|
|
145
|
-
|
|
146
|
-
// Both APIs have full JSDoc documentation
|
|
147
|
-
const tasks = await tasksAPI.cfgTasksApiTasksStatsRetrieve();
|
|
148
|
-
const webhooks = await paymentsAPI.cfgPaymentsWebhooksHealthRetrieve();
|
|
149
|
-
</script>
|
|
150
|
-
|
|
151
|
-
<!-- Use with custom base URL -->
|
|
152
|
-
<script type="module">
|
|
153
|
-
import { TasksAPI } from '{% static "api/tasks/index.mjs" %}';
|
|
154
|
-
|
|
155
|
-
// Constructor is documented with JSDoc
|
|
156
|
-
const api = new TasksAPI('https://api.example.com');
|
|
157
|
-
const stats = await api.cfgTasksApiTasksStatsRetrieve();
|
|
158
|
-
</script>
|
|
159
|
-
""")
|
|
160
|
-
|
|
161
|
-
print("\n🎯 Benefits of JSDoc types:")
|
|
162
|
-
print(" • IDE autocomplete and IntelliSense")
|
|
163
|
-
print(" • Type checking in VS Code and other editors")
|
|
164
|
-
print(" • Inline documentation in your editor")
|
|
165
|
-
print(" • Works with TypeScript if needed")
|
|
166
|
-
print(" • No build step required!")
|
|
167
|
-
|
|
168
|
-
except Exception as e:
|
|
169
|
-
print(f"❌ Error: {e}")
|
|
170
|
-
import traceback
|
|
171
|
-
traceback.print_exc()
|
|
172
|
-
sys.exit(1)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if __name__ == '__main__':
|
|
176
|
-
main()
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
MJS (ES Module) client generator for Django CFG.
|
|
3
|
-
Generates JavaScript modules with JSDoc type annotations.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import shutil
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import Any, Dict, List
|
|
9
|
-
|
|
10
|
-
from base_generator import BaseGenerator
|
|
11
|
-
from schema_parser import SchemaParser
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class MJSGenerator(BaseGenerator):
|
|
15
|
-
"""Generate MJS API clients with JSDoc type annotations."""
|
|
16
|
-
|
|
17
|
-
def __init__(self, schema_path: Path, output_dir: Path):
|
|
18
|
-
"""Initialize the MJS generator."""
|
|
19
|
-
super().__init__(schema_path, output_dir)
|
|
20
|
-
self.parser = SchemaParser(self.schema)
|
|
21
|
-
|
|
22
|
-
def generate(self) -> int:
|
|
23
|
-
"""Generate all MJS client files organized by apps."""
|
|
24
|
-
# Clean and create output directory
|
|
25
|
-
if self.output_dir.exists():
|
|
26
|
-
shutil.rmtree(self.output_dir)
|
|
27
|
-
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
28
|
-
|
|
29
|
-
# Generate base client
|
|
30
|
-
self._generate_base_client()
|
|
31
|
-
|
|
32
|
-
# Generate type definitions
|
|
33
|
-
self._generate_types()
|
|
34
|
-
|
|
35
|
-
# Group operations by app
|
|
36
|
-
operations_by_app = self.parser.group_operations_by_app()
|
|
37
|
-
|
|
38
|
-
# Generate client for each app
|
|
39
|
-
generated_apps = []
|
|
40
|
-
for app_name, operations in sorted(operations_by_app.items()):
|
|
41
|
-
# Skip certain apps
|
|
42
|
-
if app_name in ['default', 'endpoints', 'health']:
|
|
43
|
-
continue
|
|
44
|
-
|
|
45
|
-
self._generate_app_client(app_name, operations)
|
|
46
|
-
generated_apps.append(app_name)
|
|
47
|
-
|
|
48
|
-
# Generate main index file
|
|
49
|
-
self._generate_main_index(generated_apps)
|
|
50
|
-
|
|
51
|
-
# Count generated files
|
|
52
|
-
file_count = len(generated_apps) * 2 + 3 # apps * (client + index) + base + types + main index
|
|
53
|
-
return file_count
|
|
54
|
-
|
|
55
|
-
def _generate_base_client(self):
|
|
56
|
-
"""Generate the base API client class."""
|
|
57
|
-
content = self.render_template('base_client.js.j2')
|
|
58
|
-
|
|
59
|
-
base_file = self.output_dir / 'base.mjs'
|
|
60
|
-
base_file.write_text(content)
|
|
61
|
-
print(f" ✅ Generated: {base_file}")
|
|
62
|
-
|
|
63
|
-
def _generate_types(self):
|
|
64
|
-
"""Generate type definitions from schema components."""
|
|
65
|
-
# Extract all schema definitions
|
|
66
|
-
schemas = self.schema.get('components', {}).get('schemas', {})
|
|
67
|
-
|
|
68
|
-
# Convert schemas to JSDoc typedef format
|
|
69
|
-
typedefs = []
|
|
70
|
-
for schema_name, schema_def in schemas.items():
|
|
71
|
-
typedef = self._schema_to_typedef(schema_name, schema_def)
|
|
72
|
-
if typedef:
|
|
73
|
-
typedefs.append(typedef)
|
|
74
|
-
|
|
75
|
-
content = self.render_template('types.js.j2', typedefs=typedefs)
|
|
76
|
-
|
|
77
|
-
types_file = self.output_dir / 'types.mjs'
|
|
78
|
-
types_file.write_text(content)
|
|
79
|
-
print(f" ✅ Generated: {types_file}")
|
|
80
|
-
|
|
81
|
-
def _schema_to_typedef(self, name: str, schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
82
|
-
"""Convert an OpenAPI schema to a JSDoc typedef."""
|
|
83
|
-
if schema.get('type') != 'object':
|
|
84
|
-
return None
|
|
85
|
-
|
|
86
|
-
properties = []
|
|
87
|
-
required_props = schema.get('required', [])
|
|
88
|
-
|
|
89
|
-
for prop_name, prop_schema in schema.get('properties', {}).items():
|
|
90
|
-
prop_type = self.parser.get_js_type(prop_schema)
|
|
91
|
-
is_required = prop_name in required_props
|
|
92
|
-
|
|
93
|
-
properties.append({
|
|
94
|
-
'name': prop_name,
|
|
95
|
-
'type': prop_type,
|
|
96
|
-
'required': is_required,
|
|
97
|
-
'description': prop_schema.get('description', '')
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
'name': name,
|
|
102
|
-
'description': schema.get('description', ''),
|
|
103
|
-
'properties': properties
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
def _generate_app_client(self, app_name: str, operations: List[Dict]):
|
|
107
|
-
"""Generate client files for a specific app."""
|
|
108
|
-
# Create app directory
|
|
109
|
-
app_dir = self.output_dir / app_name
|
|
110
|
-
app_dir.mkdir(parents=True, exist_ok=True)
|
|
111
|
-
|
|
112
|
-
# Prepare operations data
|
|
113
|
-
methods = []
|
|
114
|
-
for op in operations:
|
|
115
|
-
method_data = self._prepare_method_data(op)
|
|
116
|
-
if method_data:
|
|
117
|
-
methods.append(method_data)
|
|
118
|
-
|
|
119
|
-
# Generate client file
|
|
120
|
-
class_name = self.to_pascal_case(app_name) + 'API'
|
|
121
|
-
instance_name = self.to_camel_case(app_name) + 'API'
|
|
122
|
-
|
|
123
|
-
content = self.render_template(
|
|
124
|
-
'api_client.js.j2',
|
|
125
|
-
app_name=app_name,
|
|
126
|
-
class_name=class_name,
|
|
127
|
-
instance_name=instance_name,
|
|
128
|
-
methods=methods
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
client_file = app_dir / 'client.mjs'
|
|
132
|
-
client_file.write_text(content)
|
|
133
|
-
print(f" ✅ Generated: {client_file}")
|
|
134
|
-
|
|
135
|
-
# Generate app index
|
|
136
|
-
index_content = self.render_template(
|
|
137
|
-
'app_index.js.j2',
|
|
138
|
-
app_name=app_name,
|
|
139
|
-
class_name=class_name,
|
|
140
|
-
instance_name=instance_name
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
index_file = app_dir / 'index.mjs'
|
|
144
|
-
index_file.write_text(index_content)
|
|
145
|
-
print(f" ✅ Generated: {index_file}")
|
|
146
|
-
|
|
147
|
-
def _prepare_method_data(self, operation: Dict[str, Any]) -> Dict[str, Any]:
|
|
148
|
-
"""Prepare method data for template rendering."""
|
|
149
|
-
operation_id = operation.get('operationId', '')
|
|
150
|
-
if not operation_id:
|
|
151
|
-
return None
|
|
152
|
-
|
|
153
|
-
method_name = self.parser.extract_method_name(operation_id)
|
|
154
|
-
method_name = self.to_camel_case(method_name)
|
|
155
|
-
|
|
156
|
-
# Extract parameters
|
|
157
|
-
path_params = []
|
|
158
|
-
query_params = []
|
|
159
|
-
|
|
160
|
-
for param in operation.get('parameters', []):
|
|
161
|
-
param_info = self.parser.extract_parameter_info(param)
|
|
162
|
-
|
|
163
|
-
if param_info['in'] == 'path':
|
|
164
|
-
path_params.append(param_info)
|
|
165
|
-
elif param_info['in'] == 'query':
|
|
166
|
-
query_params.append(param_info)
|
|
167
|
-
|
|
168
|
-
# Extract request body
|
|
169
|
-
request_body = self.parser.extract_request_body_info(
|
|
170
|
-
operation.get('requestBody')
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
# Extract response type
|
|
174
|
-
response_type = self.parser.get_response_type(
|
|
175
|
-
operation.get('responses', {})
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
# Build path with template literals
|
|
179
|
-
api_path = operation['path']
|
|
180
|
-
for param in path_params:
|
|
181
|
-
api_path = api_path.replace(
|
|
182
|
-
f"{{{param['name']}}}",
|
|
183
|
-
f"${{{param['name']}}}"
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
'name': method_name,
|
|
188
|
-
'http_method': operation['method'].upper(),
|
|
189
|
-
'path': api_path,
|
|
190
|
-
'summary': operation.get('summary', ''),
|
|
191
|
-
'description': operation.get('description', ''),
|
|
192
|
-
'path_params': path_params,
|
|
193
|
-
'query_params': query_params,
|
|
194
|
-
'request_body': request_body,
|
|
195
|
-
'response_type': response_type
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
def _generate_main_index(self, apps: List[str]):
|
|
199
|
-
"""Generate the main index file."""
|
|
200
|
-
# Prepare app data for template
|
|
201
|
-
app_imports = []
|
|
202
|
-
for app_name in sorted(apps):
|
|
203
|
-
class_name = self.to_pascal_case(app_name) + 'API'
|
|
204
|
-
instance_name = self.to_camel_case(app_name) + 'API'
|
|
205
|
-
|
|
206
|
-
app_imports.append({
|
|
207
|
-
'app_name': app_name,
|
|
208
|
-
'class_name': class_name,
|
|
209
|
-
'instance_name': instance_name
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
content = self.render_template(
|
|
213
|
-
'main_index.js.j2',
|
|
214
|
-
apps=app_imports
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
index_file = self.output_dir / 'index.mjs'
|
|
218
|
-
index_file.write_text(content)
|
|
219
|
-
print(f" ✅ Generated: {index_file}")
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
OpenAPI schema parser for extracting operation and type information.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import re
|
|
6
|
-
from typing import Any, Dict, List, Optional
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class SchemaParser:
|
|
10
|
-
"""Parse OpenAPI schema to extract operation and type information."""
|
|
11
|
-
|
|
12
|
-
def __init__(self, schema: Dict[str, Any]):
|
|
13
|
-
"""Initialize with OpenAPI schema."""
|
|
14
|
-
self.schema = schema
|
|
15
|
-
self.components = schema.get('components', {})
|
|
16
|
-
self.schemas = self.components.get('schemas', {})
|
|
17
|
-
|
|
18
|
-
def extract_app_from_path(self, path: str) -> str:
|
|
19
|
-
"""Extract app name from API path."""
|
|
20
|
-
# Pattern: /cfg/{app_name}/...
|
|
21
|
-
match = re.match(r'/cfg/([^/]+)/', path)
|
|
22
|
-
if match:
|
|
23
|
-
return match.group(1)
|
|
24
|
-
return 'core' # Default for paths without clear app
|
|
25
|
-
|
|
26
|
-
def extract_method_name(self, operation_id: str) -> str:
|
|
27
|
-
"""Extract clean method name from operation ID."""
|
|
28
|
-
# Remove common prefixes
|
|
29
|
-
for prefix in ['cfg__', 'api_', 'cfg_']:
|
|
30
|
-
if operation_id.startswith(prefix):
|
|
31
|
-
operation_id = operation_id[len(prefix):]
|
|
32
|
-
|
|
33
|
-
# Handle double underscores
|
|
34
|
-
operation_id = operation_id.replace('__', '_')
|
|
35
|
-
|
|
36
|
-
return operation_id
|
|
37
|
-
|
|
38
|
-
def group_operations_by_app(self) -> Dict[str, List[Dict]]:
|
|
39
|
-
"""Group API operations by Django app based on path patterns."""
|
|
40
|
-
grouped = {}
|
|
41
|
-
|
|
42
|
-
for path, path_item in self.schema.get('paths', {}).items():
|
|
43
|
-
for method, operation in path_item.items():
|
|
44
|
-
if method in ['get', 'post', 'put', 'patch', 'delete']:
|
|
45
|
-
# Extract app name from path
|
|
46
|
-
app_name = self.extract_app_from_path(path)
|
|
47
|
-
|
|
48
|
-
# Add operation info
|
|
49
|
-
op_info = {
|
|
50
|
-
'path': path,
|
|
51
|
-
'method': method,
|
|
52
|
-
'operationId': operation.get('operationId', ''),
|
|
53
|
-
'summary': operation.get('summary', ''),
|
|
54
|
-
'description': operation.get('description', ''),
|
|
55
|
-
'parameters': operation.get('parameters', []),
|
|
56
|
-
'requestBody': operation.get('requestBody'),
|
|
57
|
-
'responses': operation.get('responses', {}),
|
|
58
|
-
'tags': operation.get('tags', [])
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if app_name not in grouped:
|
|
62
|
-
grouped[app_name] = []
|
|
63
|
-
grouped[app_name].append(op_info)
|
|
64
|
-
|
|
65
|
-
return grouped
|
|
66
|
-
|
|
67
|
-
def extract_parameter_info(self, parameter: Dict[str, Any]) -> Dict[str, Any]:
|
|
68
|
-
"""Extract detailed parameter information."""
|
|
69
|
-
param_info = {
|
|
70
|
-
'name': parameter.get('name'),
|
|
71
|
-
'in': parameter.get('in'),
|
|
72
|
-
'required': parameter.get('required', False),
|
|
73
|
-
'description': parameter.get('description', ''),
|
|
74
|
-
'schema': parameter.get('schema', {})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# Extract type information
|
|
78
|
-
schema = parameter.get('schema', {})
|
|
79
|
-
param_info['type'] = self.get_js_type(schema)
|
|
80
|
-
param_info['format'] = schema.get('format')
|
|
81
|
-
|
|
82
|
-
return param_info
|
|
83
|
-
|
|
84
|
-
def extract_request_body_info(self, request_body: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
85
|
-
"""Extract request body information."""
|
|
86
|
-
if not request_body:
|
|
87
|
-
return None
|
|
88
|
-
|
|
89
|
-
content = request_body.get('content', {})
|
|
90
|
-
json_content = content.get('application/json', {})
|
|
91
|
-
schema = json_content.get('schema', {})
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
'required': request_body.get('required', False),
|
|
95
|
-
'description': request_body.get('description', ''),
|
|
96
|
-
'schema': schema,
|
|
97
|
-
'type': self.get_js_type(schema)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
def get_js_type(self, schema: Dict[str, Any]) -> str:
|
|
101
|
-
"""Convert OpenAPI schema to JavaScript/TypeScript type."""
|
|
102
|
-
if not schema:
|
|
103
|
-
return 'any'
|
|
104
|
-
|
|
105
|
-
# Handle references
|
|
106
|
-
if '$ref' in schema:
|
|
107
|
-
ref_name = schema['$ref'].split('/')[-1]
|
|
108
|
-
return ref_name
|
|
109
|
-
|
|
110
|
-
# Handle arrays
|
|
111
|
-
if schema.get('type') == 'array':
|
|
112
|
-
items_type = self.get_js_type(schema.get('items', {}))
|
|
113
|
-
return f'{items_type}[]'
|
|
114
|
-
|
|
115
|
-
# Handle objects
|
|
116
|
-
if schema.get('type') == 'object':
|
|
117
|
-
# Check if it has specific properties
|
|
118
|
-
if 'properties' in schema:
|
|
119
|
-
# Could generate an interface here
|
|
120
|
-
return 'Object'
|
|
121
|
-
# Check for additionalProperties (dictionary-like)
|
|
122
|
-
if 'additionalProperties' in schema:
|
|
123
|
-
additional = schema['additionalProperties']
|
|
124
|
-
# additionalProperties can be bool or schema object
|
|
125
|
-
if isinstance(additional, bool):
|
|
126
|
-
return 'Record<string, any>' if additional else 'Object'
|
|
127
|
-
value_type = self.get_js_type(additional)
|
|
128
|
-
return f'Record<string, {value_type}>'
|
|
129
|
-
return 'Object'
|
|
130
|
-
|
|
131
|
-
# Handle enums
|
|
132
|
-
if 'enum' in schema:
|
|
133
|
-
# Create a union type from enum values
|
|
134
|
-
values = schema['enum']
|
|
135
|
-
if all(isinstance(v, str) for v in values):
|
|
136
|
-
return ' | '.join(f'"{v}"' for v in values)
|
|
137
|
-
return 'string'
|
|
138
|
-
|
|
139
|
-
# Handle primitive types
|
|
140
|
-
type_mapping = {
|
|
141
|
-
'string': 'string',
|
|
142
|
-
'integer': 'number',
|
|
143
|
-
'number': 'number',
|
|
144
|
-
'boolean': 'boolean',
|
|
145
|
-
'null': 'null'
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
openapi_type = schema.get('type', 'any')
|
|
149
|
-
|
|
150
|
-
# Handle special formats
|
|
151
|
-
format_type = schema.get('format')
|
|
152
|
-
if openapi_type == 'string' and format_type:
|
|
153
|
-
format_mapping = {
|
|
154
|
-
'date': 'string', # Could be Date
|
|
155
|
-
'date-time': 'string', # Could be Date
|
|
156
|
-
'uuid': 'string',
|
|
157
|
-
'email': 'string',
|
|
158
|
-
'uri': 'string',
|
|
159
|
-
'binary': 'Blob',
|
|
160
|
-
'byte': 'string'
|
|
161
|
-
}
|
|
162
|
-
return format_mapping.get(format_type, 'string')
|
|
163
|
-
|
|
164
|
-
return type_mapping.get(openapi_type, 'any')
|
|
165
|
-
|
|
166
|
-
def get_response_type(self, responses: Dict[str, Any]) -> str:
|
|
167
|
-
"""Extract the response type from operation responses."""
|
|
168
|
-
# Look for successful response (200, 201, etc.)
|
|
169
|
-
for status in ['200', '201', '202']:
|
|
170
|
-
if status in responses:
|
|
171
|
-
response = responses[status]
|
|
172
|
-
content = response.get('content', {})
|
|
173
|
-
|
|
174
|
-
if 'application/json' in content:
|
|
175
|
-
schema = content['application/json'].get('schema', {})
|
|
176
|
-
return self.get_js_type(schema)
|
|
177
|
-
|
|
178
|
-
# Check for 204 No Content
|
|
179
|
-
if '204' in responses:
|
|
180
|
-
return 'void'
|
|
181
|
-
|
|
182
|
-
# Default to any
|
|
183
|
-
return 'any'
|
|
184
|
-
|
|
185
|
-
def resolve_ref(self, ref: str) -> Optional[Dict[str, Any]]:
|
|
186
|
-
"""Resolve a $ref to its schema definition."""
|
|
187
|
-
if not ref.startswith('#/'):
|
|
188
|
-
return None
|
|
189
|
-
|
|
190
|
-
parts = ref[2:].split('/')
|
|
191
|
-
current = self.schema
|
|
192
|
-
|
|
193
|
-
for part in parts:
|
|
194
|
-
if isinstance(current, dict) and part in current:
|
|
195
|
-
current = current[part]
|
|
196
|
-
else:
|
|
197
|
-
return None
|
|
198
|
-
|
|
199
|
-
return current
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { BaseAPIClient } from '../base.mjs';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* {{ app_name.replace('_', ' ').title() }} API Client
|
|
5
|
-
* Auto-generated from OpenAPI schema
|
|
6
|
-
* @module {{ app_name }}
|
|
7
|
-
* @extends BaseAPIClient
|
|
8
|
-
*/
|
|
9
|
-
export class {{ class_name }} extends BaseAPIClient {
|
|
10
|
-
/**
|
|
11
|
-
* Initialize {{ app_name }} API client
|
|
12
|
-
* @param {string} [baseURL] - Optional base URL
|
|
13
|
-
*/
|
|
14
|
-
constructor(baseURL) {
|
|
15
|
-
super(baseURL);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
{% for method in methods %}
|
|
19
|
-
/**
|
|
20
|
-
* {{ method.summary or method.name }}
|
|
21
|
-
{%- if method.description %}
|
|
22
|
-
* {{ method.description }}
|
|
23
|
-
{%- endif %}
|
|
24
|
-
{%- for param in method.path_params %}
|
|
25
|
-
* @param {{ '{' }}{{ param.type }}{{ '}' }} {{ param.name }}{% if param.description %} - {{ param.description }}{% endif %}
|
|
26
|
-
{%- endfor %}
|
|
27
|
-
{%- if method.request_body %}
|
|
28
|
-
* @param {{ '{' }}{{ method.request_body.type }}{{ '}' }} data - Request body{% if method.request_body.description %} - {{ method.request_body.description }}{% endif %}
|
|
29
|
-
{%- endif %}
|
|
30
|
-
{%- if method.query_params %}
|
|
31
|
-
* @param {{ '{Object}' }} [params={}] - Query parameters
|
|
32
|
-
{%- for param in method.query_params %}
|
|
33
|
-
* @param {{ '{' }}{{ param.type }}{{ '}' }} [params.{{ param.name }}]{% if param.description %} - {{ param.description }}{% endif %}
|
|
34
|
-
{%- endfor %}
|
|
35
|
-
{%- endif %}
|
|
36
|
-
* @returns {{ '{Promise<' }}{{ method.response_type }}{{ '>}' }} {{ 'Response data' if method.response_type != 'void' else 'No content' }}
|
|
37
|
-
*/
|
|
38
|
-
async {{ method.name }}(
|
|
39
|
-
{%- set params = [] %}
|
|
40
|
-
{%- for param in method.path_params %}
|
|
41
|
-
{%- set _ = params.append(param.name) %}
|
|
42
|
-
{%- endfor %}
|
|
43
|
-
{%- if method.request_body %}
|
|
44
|
-
{%- set _ = params.append('data') %}
|
|
45
|
-
{%- endif %}
|
|
46
|
-
{%- if method.query_params %}
|
|
47
|
-
{%- set _ = params.append('params = {}') %}
|
|
48
|
-
{%- endif %}
|
|
49
|
-
{{- params | join(', ') -}}
|
|
50
|
-
) {
|
|
51
|
-
const path = `{{ method.path }}`;
|
|
52
|
-
{%- if method.http_method == 'GET' %}
|
|
53
|
-
{%- if method.query_params %}
|
|
54
|
-
return this.get(path, params);
|
|
55
|
-
{%- else %}
|
|
56
|
-
return this.get(path);
|
|
57
|
-
{%- endif %}
|
|
58
|
-
{%- elif method.http_method == 'POST' %}
|
|
59
|
-
{%- if method.request_body %}
|
|
60
|
-
return this.post(path, data);
|
|
61
|
-
{%- else %}
|
|
62
|
-
return this.post(path, {});
|
|
63
|
-
{%- endif %}
|
|
64
|
-
{%- elif method.http_method == 'PUT' %}
|
|
65
|
-
{%- if method.request_body %}
|
|
66
|
-
return this.put(path, data);
|
|
67
|
-
{%- else %}
|
|
68
|
-
return this.put(path, {});
|
|
69
|
-
{%- endif %}
|
|
70
|
-
{%- elif method.http_method == 'PATCH' %}
|
|
71
|
-
{%- if method.request_body %}
|
|
72
|
-
return this.patch(path, data);
|
|
73
|
-
{%- else %}
|
|
74
|
-
return this.patch(path, {});
|
|
75
|
-
{%- endif %}
|
|
76
|
-
{%- elif method.http_method == 'DELETE' %}
|
|
77
|
-
return this.delete(path);
|
|
78
|
-
{%- endif %}
|
|
79
|
-
}
|
|
80
|
-
{% endfor %}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Default instance for convenience
|
|
84
|
-
export const {{ instance_name }} = new {{ class_name }}();
|
|
85
|
-
|
|
86
|
-
// Default export
|
|
87
|
-
export default {{ class_name }};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* {{ app_name.replace('_', ' ').title() }} API Module
|
|
3
|
-
* Re-exports the API client for convenient importing
|
|
4
|
-
* @module {{ app_name }}
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { {{ class_name }}, {{ instance_name }} } from './client.mjs';
|
|
8
|
-
|
|
9
|
-
// Re-export the class and instance
|
|
10
|
-
export { {{ class_name }}, {{ instance_name }} };
|
|
11
|
-
|
|
12
|
-
// Default export is the instance for convenience
|
|
13
|
-
export default {{ instance_name }};
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base API Client for django-cfg
|
|
3
|
-
* Lightweight ES Module with JSDoc type annotations
|
|
4
|
-
* @module base
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Custom error class for API errors
|
|
9
|
-
* @class APIError
|
|
10
|
-
* @extends Error
|
|
11
|
-
*/
|
|
12
|
-
class APIError extends Error {
|
|
13
|
-
/**
|
|
14
|
-
* @param {string} message - Error message
|
|
15
|
-
* @param {number} status - HTTP status code
|
|
16
|
-
* @param {any} data - Additional error data
|
|
17
|
-
*/
|
|
18
|
-
constructor(message, status, data) {
|
|
19
|
-
super(message);
|
|
20
|
-
this.name = 'APIError';
|
|
21
|
-
this.status = status;
|
|
22
|
-
this.data = data;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Get Django CSRF token from cookies
|
|
28
|
-
* @returns {string} CSRF token value
|
|
29
|
-
*/
|
|
30
|
-
function getCsrfToken() {
|
|
31
|
-
const cookie = document.cookie.split('; ')
|
|
32
|
-
.find(row => row.startsWith('csrftoken='));
|
|
33
|
-
return cookie ? cookie.split('=')[1] : '';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* @typedef {Object} RequestOptions
|
|
38
|
-
* @property {string} [method='GET'] - HTTP method
|
|
39
|
-
* @property {Object} [headers={}] - Request headers
|
|
40
|
-
* @property {any} [body] - Request body
|
|
41
|
-
* @property {string} [credentials='same-origin'] - Credentials mode
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Base API client class with built-in Django CSRF support
|
|
46
|
-
* @class BaseAPIClient
|
|
47
|
-
*/
|
|
48
|
-
export class BaseAPIClient {
|
|
49
|
-
/**
|
|
50
|
-
* Initialize the API client
|
|
51
|
-
* @param {string} [baseURL=''] - Base URL for API requests (defaults to current origin)
|
|
52
|
-
*/
|
|
53
|
-
constructor(baseURL = '') {
|
|
54
|
-
this.baseURL = baseURL || window.location.origin;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Make an API request
|
|
59
|
-
* @param {string} path - API endpoint path
|
|
60
|
-
* @param {RequestOptions} [options={}] - Request options
|
|
61
|
-
* @returns {Promise<any>} Response data
|
|
62
|
-
* @throws {APIError} When request fails
|
|
63
|
-
*/
|
|
64
|
-
async request(path, options = {}) {
|
|
65
|
-
const url = `${this.baseURL}${path}`;
|
|
66
|
-
|
|
67
|
-
// Default headers with CSRF token
|
|
68
|
-
const headers = {
|
|
69
|
-
'Content-Type': 'application/json',
|
|
70
|
-
'X-CSRFToken': getCsrfToken(),
|
|
71
|
-
...options.headers
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
const response = await fetch(url, {
|
|
76
|
-
...options,
|
|
77
|
-
headers,
|
|
78
|
-
credentials: 'same-origin'
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
if (!response.ok) {
|
|
82
|
-
const error = await response.json().catch(() => ({}));
|
|
83
|
-
throw new APIError(
|
|
84
|
-
error.detail || `HTTP ${response.status}`,
|
|
85
|
-
response.status,
|
|
86
|
-
error
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Handle empty responses
|
|
91
|
-
if (response.status === 204) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return await response.json();
|
|
96
|
-
} catch (error) {
|
|
97
|
-
if (error instanceof APIError) {
|
|
98
|
-
throw error;
|
|
99
|
-
}
|
|
100
|
-
throw new APIError(error.message, 0, null);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Make a GET request
|
|
106
|
-
* @param {string} path - API endpoint path
|
|
107
|
-
* @param {Object} [params={}] - Query parameters
|
|
108
|
-
* @returns {Promise<any>} Response data
|
|
109
|
-
*/
|
|
110
|
-
async get(path, params = {}) {
|
|
111
|
-
const queryString = new URLSearchParams(params).toString();
|
|
112
|
-
const fullPath = queryString ? `${path}?${queryString}` : path;
|
|
113
|
-
return this.request(fullPath, { method: 'GET' });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Make a POST request
|
|
118
|
-
* @param {string} path - API endpoint path
|
|
119
|
-
* @param {any} [data={}] - Request body data
|
|
120
|
-
* @returns {Promise<any>} Response data
|
|
121
|
-
*/
|
|
122
|
-
async post(path, data = {}) {
|
|
123
|
-
return this.request(path, {
|
|
124
|
-
method: 'POST',
|
|
125
|
-
body: JSON.stringify(data)
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Make a PUT request
|
|
131
|
-
* @param {string} path - API endpoint path
|
|
132
|
-
* @param {any} [data={}] - Request body data
|
|
133
|
-
* @returns {Promise<any>} Response data
|
|
134
|
-
*/
|
|
135
|
-
async put(path, data = {}) {
|
|
136
|
-
return this.request(path, {
|
|
137
|
-
method: 'PUT',
|
|
138
|
-
body: JSON.stringify(data)
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Make a PATCH request
|
|
144
|
-
* @param {string} path - API endpoint path
|
|
145
|
-
* @param {any} [data={}] - Request body data
|
|
146
|
-
* @returns {Promise<any>} Response data
|
|
147
|
-
*/
|
|
148
|
-
async patch(path, data = {}) {
|
|
149
|
-
return this.request(path, {
|
|
150
|
-
method: 'PATCH',
|
|
151
|
-
body: JSON.stringify(data)
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Make a DELETE request
|
|
157
|
-
* @param {string} path - API endpoint path
|
|
158
|
-
* @returns {Promise<any>} Response data
|
|
159
|
-
*/
|
|
160
|
-
async delete(path) {
|
|
161
|
-
return this.request(path, { method: 'DELETE' });
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Export the error class as well
|
|
166
|
-
export { APIError };
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Django CFG API Clients
|
|
3
|
-
* Lightweight ES Modules with JSDoc type annotations
|
|
4
|
-
* Organized by Django apps
|
|
5
|
-
*
|
|
6
|
-
* @module django-cfg-api
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* // Import specific app API
|
|
10
|
-
* import { tasksAPI } from '/static/api/tasks/index.mjs';
|
|
11
|
-
* const stats = await tasksAPI.cfgTasksApiTasksStatsRetrieve();
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* // Import multiple APIs from main index
|
|
15
|
-
* import { tasksAPI, paymentsAPI } from '/static/api/index.mjs';
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* // Import with custom base URL
|
|
19
|
-
* import { TasksAPI } from '/static/api/tasks/index.mjs';
|
|
20
|
-
* const api = new TasksAPI('https://api.example.com');
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { BaseAPIClient } from './base.mjs';
|
|
24
|
-
{% for app in apps %}
|
|
25
|
-
import { {{ app.class_name }}, {{ app.instance_name }} } from './{{ app.app_name }}/index.mjs';
|
|
26
|
-
{% endfor %}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Export all API classes for custom instantiation
|
|
30
|
-
* @exports
|
|
31
|
-
*/
|
|
32
|
-
export {
|
|
33
|
-
BaseAPIClient,
|
|
34
|
-
{% for app in apps %}
|
|
35
|
-
{{ app.class_name }},
|
|
36
|
-
{% endfor %}
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Export all default instances for convenience
|
|
41
|
-
* These instances use the current origin as base URL
|
|
42
|
-
* @exports
|
|
43
|
-
*/
|
|
44
|
-
export {
|
|
45
|
-
{% for app in apps %}
|
|
46
|
-
{{ app.instance_name }},
|
|
47
|
-
{% endfor %}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Grouped exports by functionality
|
|
52
|
-
* Access APIs by app name: apis.tasks, apis.payments, etc.
|
|
53
|
-
* @type {Object.<string, BaseAPIClient>}
|
|
54
|
-
*/
|
|
55
|
-
export const apis = {
|
|
56
|
-
{% for app in apps %}
|
|
57
|
-
{{ app.app_name }}: {{ app.instance_name }},
|
|
58
|
-
{% endfor %}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Helper function to get API by app name
|
|
63
|
-
* @param {string} appName - Name of the Django app
|
|
64
|
-
* @returns {BaseAPIClient|undefined} API instance for the app
|
|
65
|
-
* @example
|
|
66
|
-
* const tasksAPI = getAPI('tasks');
|
|
67
|
-
*/
|
|
68
|
-
export function getAPI(appName) {
|
|
69
|
-
return apis[appName];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* List of all available apps
|
|
74
|
-
* @type {string[]}
|
|
75
|
-
*/
|
|
76
|
-
export const availableApps = [
|
|
77
|
-
{% for app in apps %}
|
|
78
|
-
'{{ app.app_name }}',
|
|
79
|
-
{% endfor %}
|
|
80
|
-
];
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type definitions for django-cfg API
|
|
3
|
-
* Auto-generated from OpenAPI schema
|
|
4
|
-
* @module types
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// This file contains JSDoc type definitions generated from the OpenAPI schema
|
|
8
|
-
// These types can be used for better IDE support and documentation
|
|
9
|
-
|
|
10
|
-
{% for typedef in typedefs %}
|
|
11
|
-
/**
|
|
12
|
-
* @typedef {{ '{Object}' }} {{ typedef.name }}
|
|
13
|
-
{%- if typedef.description %}
|
|
14
|
-
* @description {{ typedef.description }}
|
|
15
|
-
{%- endif %}
|
|
16
|
-
{%- for prop in typedef.properties %}
|
|
17
|
-
* @property {{ '{' }}{{ prop.type }}{{ '}' }} {% if not prop.required %}[{% endif %}{{ prop.name }}{% if not prop.required %}]{% endif %}{% if prop.description %} - {{ prop.description }}{% endif %}
|
|
18
|
-
{%- endfor %}
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
{% endfor %}
|
|
22
|
-
|
|
23
|
-
// Export empty object to make this a module
|
|
24
|
-
export {};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|