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 CHANGED
@@ -32,7 +32,7 @@ Example:
32
32
  default_app_config = "django_cfg.apps.DjangoCfgConfig"
33
33
 
34
34
  # Version information
35
- __version__ = "1.4.108"
35
+ __version__ = "1.4.109"
36
36
  __license__ = "MIT"
37
37
 
38
38
  # Import registry for organized lazy loading
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.108"
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.108
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=IOva6NUiepfrbs-A0jIjuXF_Ukn76RAB97unffae6Tw,1621
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=pLsFyDXNjsrLStXYZBYw820gPayTk8fYGZAy8_6kVtM,7645824
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=Ut3Yvuw83bf3FmZzYAJL24fp1zbblv_c2p61XF7318A,8614
1064
- django_cfg-1.4.108.dist-info/METADATA,sha256=IOJxCR_vzoWD4SuFisznEMmiEkvpx-mg_ktcf-TB81U,23801
1065
- django_cfg-1.4.108.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1066
- django_cfg-1.4.108.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1067
- django_cfg-1.4.108.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1068
- django_cfg-1.4.108.dist-info/RECORD,,
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 {};