django-cfg 1.4.108__py3-none-any.whl → 1.4.110__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/__init__.py +1 -1
- django_cfg/modules/django_admin/__init__.py +6 -0
- django_cfg/modules/django_admin/base/pydantic_admin.py +91 -0
- django_cfg/modules/django_admin/config/__init__.py +5 -0
- django_cfg/modules/django_admin/config/admin_config.py +7 -0
- django_cfg/modules/django_admin/config/documentation_config.py +406 -0
- django_cfg/modules/django_admin/config/field_config.py +87 -0
- django_cfg/modules/django_admin/templates/django_admin/change_form_docs.html +23 -0
- django_cfg/modules/django_admin/templates/django_admin/change_list_docs.html +23 -0
- django_cfg/modules/django_admin/templates/django_admin/documentation_block.html +297 -0
- django_cfg/modules/django_admin/templates/django_admin/markdown_docs_block.html +37 -0
- django_cfg/modules/django_admin/utils/__init__.py +3 -0
- django_cfg/modules/django_admin/utils/html_builder.py +94 -1
- django_cfg/modules/django_admin/utils/markdown_renderer.py +344 -0
- django_cfg/pyproject.toml +2 -2
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/METADATA +2 -1
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/RECORD +21 -28
- django_cfg/modules/django_admin/CHANGELOG_CODE_METHODS.md +0 -153
- django_cfg/modules/django_admin/IMPORT_EXPORT_FIX.md +0 -72
- django_cfg/modules/django_admin/RESOURCE_CONFIG_ENHANCEMENT.md +0 -350
- 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.110.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.108.dist-info → django_cfg-1.4.110.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Import/Export Button Fix
|
|
2
|
-
|
|
3
|
-
## Problem
|
|
4
|
-
|
|
5
|
-
Import/Export buttons in `PydanticAdmin` were displaying incorrectly because:
|
|
6
|
-
|
|
7
|
-
1. `PydanticAdmin` was using the original `ImportExportModelAdmin` from django-import-export
|
|
8
|
-
2. Original django-import-export templates don't integrate properly with Unfold UI
|
|
9
|
-
3. Buttons were styled as tabs instead of toolbar buttons
|
|
10
|
-
|
|
11
|
-
## Solution
|
|
12
|
-
|
|
13
|
-
Updated `PydanticAdmin` base class to use django_cfg's custom `ImportExportMixin`:
|
|
14
|
-
|
|
15
|
-
### Before
|
|
16
|
-
```python
|
|
17
|
-
class UnfoldImportExportModelAdmin(ImportExportModelAdmin, UnfoldModelAdmin):
|
|
18
|
-
pass
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### After
|
|
22
|
-
```python
|
|
23
|
-
class UnfoldImportExportModelAdmin(UnfoldModelAdmin, ImportExportMixin):
|
|
24
|
-
pass
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Changes Made
|
|
28
|
-
|
|
29
|
-
1. **MRO (Method Resolution Order)**:
|
|
30
|
-
- `UnfoldModelAdmin` comes first (for template priority)
|
|
31
|
-
- `ImportExportMixin` comes second (adds import/export functionality)
|
|
32
|
-
|
|
33
|
-
2. **Custom Templates** (`django_cfg.modules.django_import_export`):
|
|
34
|
-
- Custom `change_list_*.html` templates that extend Unfold's base
|
|
35
|
-
- Properly styled import/export buttons as round icons
|
|
36
|
-
- Import button: green with upload icon
|
|
37
|
-
- Export button: blue with download icon
|
|
38
|
-
|
|
39
|
-
3. **Integration**:
|
|
40
|
-
- Uses Unfold's forms (`ImportForm`, `ExportForm`)
|
|
41
|
-
- Buttons appear in `object-tools-items` block (next to "Add" button)
|
|
42
|
-
- Full dark mode support
|
|
43
|
-
|
|
44
|
-
## Result
|
|
45
|
-
|
|
46
|
-
Now `PydanticAdmin` admins with `import_export_enabled=True` will have:
|
|
47
|
-
- ✅ Properly styled import/export buttons
|
|
48
|
-
- ✅ Consistent Unfold UI
|
|
49
|
-
- ✅ Round icon buttons matching the design system
|
|
50
|
-
- ✅ Correct positioning in the toolbar
|
|
51
|
-
|
|
52
|
-
## Usage
|
|
53
|
-
|
|
54
|
-
No changes required in user code - this fix is automatic:
|
|
55
|
-
|
|
56
|
-
```python
|
|
57
|
-
from django_cfg.modules.django_admin import AdminConfig
|
|
58
|
-
from django_cfg.modules.django_admin.base import PydanticAdmin
|
|
59
|
-
|
|
60
|
-
config = AdminConfig(
|
|
61
|
-
model=MyModel,
|
|
62
|
-
import_export_enabled=True,
|
|
63
|
-
resource_class=MyModelResource,
|
|
64
|
-
list_display=["name", "status"],
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
@admin.register(MyModel)
|
|
68
|
-
class MyModelAdmin(PydanticAdmin):
|
|
69
|
-
config = config
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
The import/export buttons will now display correctly!
|
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
# ResourceConfig & BackgroundTaskConfig Enhancement
|
|
2
|
-
|
|
3
|
-
## Summary
|
|
4
|
-
|
|
5
|
-
Enhanced django-admin module with declarative import/export configuration and background task support.
|
|
6
|
-
|
|
7
|
-
## New Features
|
|
8
|
-
|
|
9
|
-
### 1. ResourceConfig
|
|
10
|
-
|
|
11
|
-
Declarative configuration for django-import-export Resource classes.
|
|
12
|
-
|
|
13
|
-
**Location:** `django_cfg/modules/django_admin/config/resource_config.py`
|
|
14
|
-
|
|
15
|
-
**Features:**
|
|
16
|
-
- Field selection and exclusion
|
|
17
|
-
- Import ID fields configuration
|
|
18
|
-
- Import/export behavior control
|
|
19
|
-
- Hook support (before/after import, per-row hooks)
|
|
20
|
-
- Export field ordering
|
|
21
|
-
- Batch processing options
|
|
22
|
-
|
|
23
|
-
**Example:**
|
|
24
|
-
```python
|
|
25
|
-
from django_cfg.modules.django_admin import ResourceConfig
|
|
26
|
-
|
|
27
|
-
resource_config = ResourceConfig(
|
|
28
|
-
fields=['host', 'port', 'username', 'password'],
|
|
29
|
-
exclude=['metadata', 'config'],
|
|
30
|
-
import_id_fields=['host', 'port'],
|
|
31
|
-
skip_unchanged=True,
|
|
32
|
-
after_import_row='apps.myapp.tasks.test_after_import',
|
|
33
|
-
batch_size=100,
|
|
34
|
-
)
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### 2. BackgroundTaskConfig
|
|
38
|
-
|
|
39
|
-
Configuration for background task processing.
|
|
40
|
-
|
|
41
|
-
**Location:** `django_cfg/modules/django_admin/config/background_task_config.py`
|
|
42
|
-
|
|
43
|
-
**Features:**
|
|
44
|
-
- Task runner selection (rearq, celery, django_q, sync)
|
|
45
|
-
- Batch size configuration
|
|
46
|
-
- Timeout settings
|
|
47
|
-
- Retry policy
|
|
48
|
-
- Priority levels
|
|
49
|
-
|
|
50
|
-
**Example:**
|
|
51
|
-
```python
|
|
52
|
-
from django_cfg.modules.django_admin import BackgroundTaskConfig
|
|
53
|
-
|
|
54
|
-
background_config = BackgroundTaskConfig(
|
|
55
|
-
enabled=True,
|
|
56
|
-
task_runner='rearq',
|
|
57
|
-
batch_size=50,
|
|
58
|
-
timeout=300,
|
|
59
|
-
retry_on_failure=True,
|
|
60
|
-
max_retries=3,
|
|
61
|
-
)
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### 3. Enhanced AdminConfig
|
|
65
|
-
|
|
66
|
-
**Updated:** `django_cfg/modules/django_admin/config/admin_config.py`
|
|
67
|
-
|
|
68
|
-
**New Fields:**
|
|
69
|
-
```python
|
|
70
|
-
class AdminConfig(BaseModel):
|
|
71
|
-
# ... existing fields ...
|
|
72
|
-
|
|
73
|
-
# Import/Export enhancement
|
|
74
|
-
resource_config: Optional[ResourceConfig] = None
|
|
75
|
-
|
|
76
|
-
# Background task processing
|
|
77
|
-
background_task_config: Optional[BackgroundTaskConfig] = None
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### 4. Enhanced PydanticAdmin
|
|
81
|
-
|
|
82
|
-
**Updated:** `django_cfg/modules/django_admin/base/pydantic_admin.py`
|
|
83
|
-
|
|
84
|
-
**Key Changes:**
|
|
85
|
-
- `_generate_resource_class()` now uses ResourceConfig if provided
|
|
86
|
-
- Auto-generates Resource with hooks support
|
|
87
|
-
- Dynamically attaches hook methods to Resource class
|
|
88
|
-
|
|
89
|
-
**Hook Support:**
|
|
90
|
-
- `before_import` - Called before import starts
|
|
91
|
-
- `after_import` - Called after import completes
|
|
92
|
-
- `before_import_row` - Called before each row import
|
|
93
|
-
- `after_import_row` - Called after each row import (★ most useful)
|
|
94
|
-
|
|
95
|
-
## Usage Example
|
|
96
|
-
|
|
97
|
-
### Complete Proxy Admin with Import/Export
|
|
98
|
-
|
|
99
|
-
```python
|
|
100
|
-
from django_cfg.modules.django_admin import (
|
|
101
|
-
ActionConfig,
|
|
102
|
-
AdminConfig,
|
|
103
|
-
BackgroundTaskConfig,
|
|
104
|
-
ResourceConfig,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
proxy_config = AdminConfig(
|
|
108
|
-
model=Proxy,
|
|
109
|
-
|
|
110
|
-
# Enable import/export with ResourceConfig
|
|
111
|
-
import_export_enabled=True,
|
|
112
|
-
resource_config=ResourceConfig(
|
|
113
|
-
fields=[
|
|
114
|
-
'host', 'port', 'proxy_type', 'proxy_mode',
|
|
115
|
-
'username', 'password',
|
|
116
|
-
'provider', 'country',
|
|
117
|
-
],
|
|
118
|
-
exclude=['metadata', 'config', 'last_error'],
|
|
119
|
-
import_id_fields=['host', 'port', 'provider'],
|
|
120
|
-
skip_unchanged=True,
|
|
121
|
-
# Auto-test after import
|
|
122
|
-
after_import_row='apps.proxies.tasks.after_import_row_test_proxy',
|
|
123
|
-
),
|
|
124
|
-
|
|
125
|
-
# Background task configuration
|
|
126
|
-
background_task_config=BackgroundTaskConfig(
|
|
127
|
-
enabled=True,
|
|
128
|
-
task_runner='rearq',
|
|
129
|
-
batch_size=50,
|
|
130
|
-
timeout=300,
|
|
131
|
-
),
|
|
132
|
-
|
|
133
|
-
# Admin actions
|
|
134
|
-
actions=[
|
|
135
|
-
ActionConfig(
|
|
136
|
-
name='test_selected_proxies',
|
|
137
|
-
description='Test selected proxies',
|
|
138
|
-
variant='warning',
|
|
139
|
-
icon='speed',
|
|
140
|
-
handler='apps.proxies.admin.actions.test_selected_proxies',
|
|
141
|
-
),
|
|
142
|
-
],
|
|
143
|
-
|
|
144
|
-
list_display=['host', 'port', 'status', 'success_rate'],
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
@admin.register(Proxy)
|
|
148
|
-
class ProxyAdmin(PydanticAdmin):
|
|
149
|
-
config = proxy_config
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Hook Implementation
|
|
153
|
-
|
|
154
|
-
```python
|
|
155
|
-
# apps/proxies/tasks.py
|
|
156
|
-
|
|
157
|
-
def after_import_row_test_proxy(row, row_result, **kwargs):
|
|
158
|
-
"""Hook called after each proxy import."""
|
|
159
|
-
|
|
160
|
-
# Skip if dry run
|
|
161
|
-
if kwargs.get('dry_run'):
|
|
162
|
-
return
|
|
163
|
-
|
|
164
|
-
# Only test new proxies
|
|
165
|
-
if row_result.import_type == 'new':
|
|
166
|
-
proxy = row_result.instance
|
|
167
|
-
|
|
168
|
-
# Queue async test
|
|
169
|
-
from api.workers import get_worker
|
|
170
|
-
worker = get_worker()
|
|
171
|
-
worker.enqueue_task(
|
|
172
|
-
'apps.proxies.tasks.test_proxy_async',
|
|
173
|
-
proxy_id=str(proxy.id)
|
|
174
|
-
)
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## Benefits
|
|
178
|
-
|
|
179
|
-
### Before (Manual Resource Class)
|
|
180
|
-
```python
|
|
181
|
-
from import_export import resources
|
|
182
|
-
|
|
183
|
-
class ProxyResource(resources.ModelResource):
|
|
184
|
-
class Meta:
|
|
185
|
-
model = Proxy
|
|
186
|
-
fields = ('host', 'port', 'username', 'password')
|
|
187
|
-
import_id_fields = ['host', 'port']
|
|
188
|
-
skip_unchanged = True
|
|
189
|
-
|
|
190
|
-
def after_import_row(self, row, row_result, **kwargs):
|
|
191
|
-
# Custom logic here
|
|
192
|
-
pass
|
|
193
|
-
|
|
194
|
-
proxy_config = AdminConfig(
|
|
195
|
-
model=Proxy,
|
|
196
|
-
import_export_enabled=True,
|
|
197
|
-
resource_class=ProxyResource, # Manual class
|
|
198
|
-
)
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### After (Declarative ResourceConfig)
|
|
202
|
-
```python
|
|
203
|
-
proxy_config = AdminConfig(
|
|
204
|
-
model=Proxy,
|
|
205
|
-
import_export_enabled=True,
|
|
206
|
-
resource_config=ResourceConfig(
|
|
207
|
-
fields=['host', 'port', 'username', 'password'],
|
|
208
|
-
import_id_fields=['host', 'port'],
|
|
209
|
-
skip_unchanged=True,
|
|
210
|
-
after_import_row='apps.proxies.tasks.after_import_row_test_proxy',
|
|
211
|
-
),
|
|
212
|
-
)
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
**Advantages:**
|
|
216
|
-
- ✅ No separate Resource class needed
|
|
217
|
-
- ✅ All configuration in one place
|
|
218
|
-
- ✅ Type-safe with Pydantic validation
|
|
219
|
-
- ✅ Reusable across multiple admins
|
|
220
|
-
- ✅ Hook as string path (lazy import)
|
|
221
|
-
- ✅ Auto-generated Resource with full control
|
|
222
|
-
|
|
223
|
-
## Dependencies Added
|
|
224
|
-
|
|
225
|
-
**pyproject.toml updates:**
|
|
226
|
-
```toml
|
|
227
|
-
dependencies = [
|
|
228
|
-
# ... existing ...
|
|
229
|
-
"pytz>=2025.1",
|
|
230
|
-
"httpx>=0.28.1,<1.0",
|
|
231
|
-
]
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
- `pytz` - Timezone support for datetime operations
|
|
235
|
-
- `httpx` - Modern HTTP client for proxy testing
|
|
236
|
-
|
|
237
|
-
## Files Changed
|
|
238
|
-
|
|
239
|
-
### django-cfg Core
|
|
240
|
-
1. `config/resource_config.py` - NEW
|
|
241
|
-
2. `config/background_task_config.py` - NEW
|
|
242
|
-
3. `config/admin_config.py` - MODIFIED (added new configs)
|
|
243
|
-
4. `config/__init__.py` - MODIFIED (exports)
|
|
244
|
-
5. `base/pydantic_admin.py` - MODIFIED (Resource generation)
|
|
245
|
-
6. `__init__.py` - MODIFIED (exports)
|
|
246
|
-
7. `pyproject.toml` - MODIFIED (dependencies)
|
|
247
|
-
|
|
248
|
-
### stockapis Implementation
|
|
249
|
-
1. `apps/proxies/admin/proxy_admin.py` - Uses ResourceConfig
|
|
250
|
-
2. `apps/proxies/admin/actions.py` - NEW (admin actions)
|
|
251
|
-
3. `apps/proxies/services/proxy_tester.py` - NEW (testing logic)
|
|
252
|
-
4. `apps/proxies/tasks.py` - NEW (background tasks)
|
|
253
|
-
5. `apps/proxies/@docs/IMPORT_EXPORT_SETUP.md` - NEW (docs)
|
|
254
|
-
|
|
255
|
-
## Backward Compatibility
|
|
256
|
-
|
|
257
|
-
✅ **100% Backward Compatible**
|
|
258
|
-
|
|
259
|
-
Old code still works:
|
|
260
|
-
```python
|
|
261
|
-
# Old way - still works
|
|
262
|
-
proxy_config = AdminConfig(
|
|
263
|
-
model=Proxy,
|
|
264
|
-
import_export_enabled=True,
|
|
265
|
-
resource_class=MyCustomResource, # Manual class
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
# New way - alternative
|
|
269
|
-
proxy_config = AdminConfig(
|
|
270
|
-
model=Proxy,
|
|
271
|
-
import_export_enabled=True,
|
|
272
|
-
resource_config=ResourceConfig(...), # Declarative
|
|
273
|
-
)
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
Priority:
|
|
277
|
-
1. If `resource_class` provided → use it (legacy)
|
|
278
|
-
2. If `resource_config` provided → auto-generate Resource
|
|
279
|
-
3. If neither → auto-generate basic Resource
|
|
280
|
-
|
|
281
|
-
## Testing
|
|
282
|
-
|
|
283
|
-
```bash
|
|
284
|
-
# Test django-cfg imports
|
|
285
|
-
cd django-cfg-dev
|
|
286
|
-
poetry run python -c "from django_cfg.modules.django_admin import ResourceConfig, BackgroundTaskConfig; print('✅ OK')"
|
|
287
|
-
|
|
288
|
-
# Test stockapis admin
|
|
289
|
-
cd stockapis
|
|
290
|
-
poetry run python manage.py check --tag admin
|
|
291
|
-
|
|
292
|
-
# Test in browser
|
|
293
|
-
poetry run python manage.py runserver
|
|
294
|
-
# Visit: http://localhost:8000/admin/proxies/proxy/
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
## Future Enhancements
|
|
298
|
-
|
|
299
|
-
- [ ] `ResourceConfig.field_widgets` - Custom widgets for fields
|
|
300
|
-
- [ ] `ResourceConfig.validators` - Custom validation rules
|
|
301
|
-
- [ ] `ResourceConfig.transformers` - Data transformation before import
|
|
302
|
-
- [ ] Progress tracking for large imports
|
|
303
|
-
- [ ] Import history and rollback
|
|
304
|
-
- [ ] Scheduled imports via cron
|
|
305
|
-
- [ ] API endpoint for programmatic imports
|
|
306
|
-
|
|
307
|
-
## Migration Guide
|
|
308
|
-
|
|
309
|
-
### For Existing Projects
|
|
310
|
-
|
|
311
|
-
1. Update django-cfg to latest version
|
|
312
|
-
2. Optional: Replace manual Resource classes with ResourceConfig
|
|
313
|
-
3. Optional: Add BackgroundTaskConfig for async operations
|
|
314
|
-
4. Optional: Add after_import_row hooks for post-import processing
|
|
315
|
-
|
|
316
|
-
### Example Migration
|
|
317
|
-
|
|
318
|
-
**Before:**
|
|
319
|
-
```python
|
|
320
|
-
# resources.py
|
|
321
|
-
class ProxyResource(resources.ModelResource):
|
|
322
|
-
class Meta:
|
|
323
|
-
model = Proxy
|
|
324
|
-
fields = ('host', 'port')
|
|
325
|
-
|
|
326
|
-
# admin.py
|
|
327
|
-
config = AdminConfig(
|
|
328
|
-
model=Proxy,
|
|
329
|
-
import_export_enabled=True,
|
|
330
|
-
resource_class=ProxyResource,
|
|
331
|
-
)
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
**After:**
|
|
335
|
-
```python
|
|
336
|
-
# admin.py only - no resources.py needed
|
|
337
|
-
config = AdminConfig(
|
|
338
|
-
model=Proxy,
|
|
339
|
-
import_export_enabled=True,
|
|
340
|
-
resource_config=ResourceConfig(
|
|
341
|
-
fields=['host', 'port'],
|
|
342
|
-
),
|
|
343
|
-
)
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
## Version
|
|
347
|
-
|
|
348
|
-
- **django-cfg**: 1.4.106+
|
|
349
|
-
- **Python**: 3.12+
|
|
350
|
-
- **Django**: 5.2+
|
|
@@ -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)
|