django-cfg 1.4.17__py3-none-any.whl → 1.4.20__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.
@@ -20,8 +20,8 @@ class ArchiveType(models.TextChoices):
20
20
  """Supported archive formats."""
21
21
  ZIP = "zip", "ZIP"
22
22
  TAR = "tar", "TAR"
23
- TAR_GZ = "tar.gz", "TAR.GZ"
24
- TAR_BZ2 = "tar.bz2", "TAR.BZ2"
23
+ TAR_GZ = "tar.gz", "TAR GZ"
24
+ TAR_BZ2 = "tar.bz2", "TAR BZ2"
25
25
 
26
26
 
27
27
  class ContentType(models.TextChoices):
@@ -4,24 +4,8 @@ Knowledge Base URL Configuration
4
4
 
5
5
  from django.urls import path, include
6
6
  from rest_framework.routers import DefaultRouter
7
- from .views import (
8
- DocumentViewSet, ChatViewSet, ChatSessionViewSet,
9
- DocumentArchiveViewSet, ArchiveItemViewSet, ArchiveItemChunkViewSet
10
- )
11
7
  from .views.public_views import PublicDocumentViewSet, PublicCategoryViewSet
12
8
 
13
- # Create router and register viewsets
14
- router = DefaultRouter()
15
- router.register(r'documents', DocumentViewSet, basename='document')
16
- router.register(r'chat', ChatViewSet, basename='chat')
17
- router.register(r'sessions', ChatSessionViewSet, basename='session')
18
-
19
- # Archive router for authenticated users
20
- archive_router = DefaultRouter()
21
- archive_router.register(r'archives', DocumentArchiveViewSet, basename='archive')
22
- archive_router.register(r'items', ArchiveItemViewSet, basename='archive-item')
23
- archive_router.register(r'chunks', ArchiveItemChunkViewSet, basename='archive-chunk')
24
-
25
9
  # Public router for client access
26
10
  public_router = DefaultRouter()
27
11
  public_router.register(r'documents', PublicDocumentViewSet, basename='public-document')
@@ -29,15 +13,10 @@ public_router.register(r'categories', PublicCategoryViewSet, basename='public-ca
29
13
 
30
14
  # URL patterns
31
15
  urlpatterns = [
32
- # Admin API endpoints (require authentication + admin rights)
33
- path('admin/', include(router.urls)),
34
-
35
- # Archive API endpoints (require authentication)
36
- path('', include(archive_router.urls)),
37
-
16
+
38
17
  # Public API endpoints (no authentication required)
39
- path('public/', include(public_router.urls)),
18
+ path('', include(public_router.urls)),
40
19
  ]
41
20
 
42
21
  # Add app name for namespacing
43
- app_name = 'knowbase'
22
+ app_name = 'cfg_knowbase'
@@ -0,0 +1,23 @@
1
+ """
2
+ Knowledge Base URL Configuration
3
+ """
4
+
5
+ from django.urls import path, include
6
+ from rest_framework.routers import DefaultRouter
7
+ from .views import (
8
+ DocumentViewSet, ChatViewSet, ChatSessionViewSet,
9
+ )
10
+
11
+ # Create router and register viewsets
12
+ router = DefaultRouter()
13
+ router.register(r'documents', DocumentViewSet, basename='document')
14
+ router.register(r'chat', ChatViewSet, basename='chat')
15
+ router.register(r'sessions', ChatSessionViewSet, basename='session')
16
+
17
+ # URL patterns
18
+ urlpatterns = [
19
+ # Admin API endpoints (require authentication + admin rights)
20
+ path('', include(router.urls)),
21
+ ]
22
+
23
+ app_name = 'cfg_knowbase_admin'
@@ -0,0 +1,26 @@
1
+ """
2
+ Knowledge Base URL Configuration
3
+ """
4
+
5
+ from django.urls import path, include
6
+ from rest_framework.routers import DefaultRouter
7
+ from .views import (
8
+ DocumentArchiveViewSet, ArchiveItemViewSet, ArchiveItemChunkViewSet
9
+ )
10
+
11
+ # Archive router for authenticated users
12
+ archive_router = DefaultRouter()
13
+ archive_router.register(r'archives', DocumentArchiveViewSet, basename='archive')
14
+ archive_router.register(r'items', ArchiveItemViewSet, basename='archive-item')
15
+ archive_router.register(r'chunks', ArchiveItemChunkViewSet, basename='archive-chunk')
16
+
17
+ # URL patterns
18
+ urlpatterns = [
19
+
20
+ # Archive API endpoints (require authentication)
21
+ path('', include(archive_router.urls)),
22
+
23
+ ]
24
+
25
+ # Add app name for namespacing
26
+ app_name = 'cfg_knowbase_system'
django_cfg/apps/urls.py CHANGED
@@ -22,6 +22,9 @@ def get_enabled_cfg_apps() -> List[str]:
22
22
 
23
23
  if base_module.is_accounts_enabled():
24
24
  enabled_apps.append("django_cfg.apps.accounts")
25
+
26
+ if base_module.is_knowbase_enabled():
27
+ enabled_apps.append("django_cfg.apps.knowbase")
25
28
 
26
29
  if base_module.is_support_enabled():
27
30
  enabled_apps.append("django_cfg.apps.support")
@@ -32,9 +35,6 @@ def get_enabled_cfg_apps() -> List[str]:
32
35
  if base_module.is_leads_enabled():
33
36
  enabled_apps.append("django_cfg.apps.leads")
34
37
 
35
- if base_module.is_knowbase_enabled():
36
- enabled_apps.append("django_cfg.apps.knowbase")
37
-
38
38
  if base_module.is_agents_enabled():
39
39
  enabled_apps.append("django_cfg.apps.agents")
40
40
 
@@ -114,6 +114,8 @@ APP_URL_MAP = {
114
114
  ],
115
115
  "django_cfg.apps.knowbase": [
116
116
  ("cfg/knowbase/", "django_cfg.apps.knowbase.urls"),
117
+ ("cfg/knowbase/admin/", "django_cfg.apps.knowbase.urls_admin"),
118
+ ("cfg/knowbase/system/", "django_cfg.apps.knowbase.urls_system"),
117
119
  ],
118
120
  "django_cfg.apps.agents": [
119
121
  ("cfg/agents/", "django_cfg.apps.agents.urls"),
@@ -159,7 +159,58 @@ class FetchersGenerator:
159
159
  func_params.append(f"{param.name}: {param_type}")
160
160
  api_call_params.append(param.name)
161
161
 
162
+ # Request body (passed as data or unpacked for multipart)
163
+ # NOTE: This must come BEFORE query params to match client method signature order!
164
+ if operation.request_body:
165
+ # Check if this is a file upload operation
166
+ is_multipart = operation.request_body.content_type == "multipart/form-data"
167
+
168
+ if is_multipart:
169
+ # For multipart, unpack data properties to match client signature
170
+ schema_name = operation.request_body.schema_name
171
+ if schema_name and schema_name in self.context.schemas:
172
+ schema = self.context.schemas[schema_name]
173
+ # Add data parameter in func signature (keeps API simple)
174
+ func_params.append(f"data: {schema_name}")
175
+ # But unpack when calling client (which expects individual params)
176
+ # IMPORTANT: Order must match client - required first, then optional
177
+ required_props = []
178
+ optional_props = []
179
+
180
+ for prop_name, prop in schema.properties.items():
181
+ if prop_name in schema.required:
182
+ required_props.append(prop_name)
183
+ else:
184
+ optional_props.append(prop_name)
185
+
186
+ # Add required first, then optional (matches client signature)
187
+ for prop_name in required_props + optional_props:
188
+ api_call_params.append(f"data.{prop_name}")
189
+ else:
190
+ # Inline schema - use data as-is
191
+ func_params.append(f"data: FormData")
192
+ api_call_params.append("data")
193
+ else:
194
+ # JSON request body - pass data object
195
+ schema_name = operation.request_body.schema_name
196
+ if schema_name and schema_name in self.context.schemas:
197
+ body_type = schema_name
198
+ else:
199
+ body_type = "any"
200
+ func_params.append(f"data: {body_type}")
201
+ api_call_params.append("data")
202
+ elif operation.patch_request_body:
203
+ # PATCH request body (optional)
204
+ schema_name = operation.patch_request_body.schema_name
205
+ if schema_name and schema_name in self.context.schemas:
206
+ func_params.append(f"data?: {schema_name}")
207
+ api_call_params.append("data")
208
+ else:
209
+ func_params.append(f"data?: any")
210
+ api_call_params.append("data")
211
+
162
212
  # Query parameters (passed as params object, but unpacked when calling API)
213
+ # NOTE: This must come AFTER request body to match client method signature order!
163
214
  if operation.query_parameters:
164
215
  query_fields = []
165
216
  # params is required only if all parameters are required
@@ -177,17 +228,6 @@ class FetchersGenerator:
177
228
  params_optional = "" if all_required else "?"
178
229
  func_params.append(f"params{params_optional}: {{ {'; '.join(query_fields)} }}")
179
230
 
180
- # Request body (passed as data)
181
- if operation.request_body:
182
- schema_name = operation.request_body.schema_name
183
- # Use schema only if it exists as a component (not inline)
184
- if schema_name and schema_name in self.context.schemas:
185
- body_type = schema_name
186
- else:
187
- body_type = "any"
188
- func_params.append(f"data: {body_type}")
189
- api_call_params.append("data")
190
-
191
231
  return {
192
232
  'func_params': ", ".join(func_params) if func_params else "",
193
233
  'api_call_params': ", ".join(api_call_params) if api_call_params else ""
@@ -338,6 +378,12 @@ class FetchersGenerator:
338
378
  # Only add if schema exists in components (not inline)
339
379
  if operation.request_body.schema_name in self.context.schemas:
340
380
  schema_names.add(operation.request_body.schema_name)
381
+
382
+ # Add patch request body schemas
383
+ if operation.patch_request_body and operation.patch_request_body.schema_name:
384
+ # Only add if schema exists in components (not inline)
385
+ if operation.patch_request_body.schema_name in self.context.schemas:
386
+ schema_names.add(operation.patch_request_body.schema_name)
341
387
 
342
388
  # Get display name and folder name (use same naming as APIClient)
343
389
  tag_display_name = self.base.tag_to_display_name(tag)
@@ -327,7 +327,7 @@ class TypeScriptGenerator(BaseGenerator):
327
327
 
328
328
  # Generate individual schema files
329
329
  for schema_name, schema in sorted(all_schemas.items()):
330
- # Skip enum schemas (they use z.nativeEnum from enums.ts)
330
+ # Skip enum schemas (they use z.enum() with literal values)
331
331
  if schema.enum:
332
332
  continue
333
333
 
@@ -207,7 +207,27 @@ class HooksGenerator:
207
207
  func_params.append(f"{param.name}: {param_type}")
208
208
  fetcher_params.append(param.name)
209
209
 
210
- # Query parameters
210
+ # Request body (must come BEFORE query params to match fetcher signature!)
211
+ if operation.request_body:
212
+ schema_name = operation.request_body.schema_name
213
+ # Use schema only if it exists as a component (not inline)
214
+ if schema_name and schema_name in self.context.schemas:
215
+ body_type = schema_name
216
+ else:
217
+ body_type = "any"
218
+ func_params.append(f"data: {body_type}")
219
+ fetcher_params.append("data")
220
+ elif operation.patch_request_body:
221
+ # PATCH request body (optional)
222
+ schema_name = operation.patch_request_body.schema_name
223
+ if schema_name and schema_name in self.context.schemas:
224
+ func_params.append(f"data?: {schema_name}")
225
+ fetcher_params.append("data")
226
+ else:
227
+ func_params.append(f"data?: any")
228
+ fetcher_params.append("data")
229
+
230
+ # Query parameters (must come AFTER request body to match fetcher signature!)
211
231
  if operation.query_parameters:
212
232
  query_fields = []
213
233
  all_required = all(param.required for param in operation.query_parameters)
@@ -222,17 +242,6 @@ class HooksGenerator:
222
242
  func_params.append(f"params{params_optional}: {{ {'; '.join(query_fields)} }}")
223
243
  fetcher_params.append("params")
224
244
 
225
- # Request body
226
- if operation.request_body:
227
- schema_name = operation.request_body.schema_name
228
- # Use schema only if it exists as a component (not inline)
229
- if schema_name and schema_name in self.context.schemas:
230
- body_type = schema_name
231
- else:
232
- body_type = "any"
233
- func_params.append(f"data: {body_type}")
234
- fetcher_params.append("data")
235
-
236
245
  return {
237
246
  'func_params': ", ".join(func_params) if func_params else "",
238
247
  'fetcher_params': ", ".join(fetcher_params) if fetcher_params else ""
@@ -162,17 +162,22 @@ class ModelsGenerator:
162
162
  if ts_type.endswith(" | null"):
163
163
  ts_type = ts_type[:-7] # Remove " | null"
164
164
  else:
165
- # Get TypeScript type and remove | null suffix if present
165
+ # Get TypeScript type
166
166
  ts_type = schema.typescript_type
167
+ # Remove | null suffix to rebuild it properly based on schema.nullable
167
168
  if ts_type.endswith(" | null"):
168
169
  ts_type = ts_type[:-7] # Remove " | null"
169
170
 
170
171
  # Check if required
171
172
  is_required = name in required_fields
172
173
 
173
- # Optional marker - use for both non-required AND nullable fields
174
- # This converts Django's nullable=True to TypeScript's optional (undefined)
175
- optional_marker = "" if is_required and not schema.nullable else "?"
174
+ # Handle nullable and optional separately
175
+ # - nullable: add | null to type
176
+ # - not required: add ? optional marker
177
+ if schema.nullable:
178
+ ts_type = f"{ts_type} | null"
179
+
180
+ optional_marker = "" if is_required else "?"
176
181
 
177
182
  # Comment
178
183
  if schema.description:
@@ -203,10 +208,14 @@ class ModelsGenerator:
203
208
  if not var_name or (isinstance(value, str) and value == ''):
204
209
  continue
205
210
 
211
+ # Sanitize var_name: replace dots and spaces with underscores, convert to UPPER_CASE
212
+ # "TAR.GZ" -> "TAR_GZ", "TAR GZ" -> "TAR_GZ"
213
+ sanitized_var_name = var_name.replace('.', '_').replace(' ', '_').upper()
214
+
206
215
  if isinstance(value, str):
207
- member_lines.append(f'{var_name} = "{value}",')
216
+ member_lines.append(f'{sanitized_var_name} = "{value}",')
208
217
  else:
209
- member_lines.append(f"{var_name} = {value},")
218
+ member_lines.append(f"{sanitized_var_name} = {value},")
210
219
 
211
220
  # Build enum
212
221
  lines = []
@@ -56,17 +56,31 @@ class OperationsGenerator:
56
56
  schema_name = operation.request_body.schema_name
57
57
  if schema_name and schema_name in self.context.schemas:
58
58
  schema = self.context.schemas[schema_name]
59
+ # Add required params first, then optional (TypeScript requirement)
60
+ required_params = []
61
+ optional_params = []
62
+
59
63
  for prop_name, prop in schema.properties.items():
60
64
  # Check if it's a file field (format: binary)
61
65
  if prop.format == "binary":
62
- params.append(f"{prop_name}: File | Blob")
66
+ param_str = f"{prop_name}: File | Blob"
63
67
  else:
64
68
  # Regular field in multipart
65
69
  prop_type = self._map_param_type(prop.type)
66
70
  if prop_name in schema.required:
67
- params.append(f"{prop_name}: {prop_type}")
71
+ param_str = f"{prop_name}: {prop_type}"
68
72
  else:
69
- params.append(f"{prop_name}?: {prop_type}")
73
+ param_str = f"{prop_name}?: {prop_type}"
74
+
75
+ # Separate required and optional
76
+ if prop_name in schema.required:
77
+ required_params.append(param_str)
78
+ else:
79
+ optional_params.append(param_str)
80
+
81
+ # Add required first, then optional
82
+ params.extend(required_params)
83
+ params.extend(optional_params)
70
84
  else:
71
85
  # Inline schema - use FormData
72
86
  params.append("data: FormData")
@@ -98,7 +112,9 @@ class OperationsGenerator:
98
112
  # Return type
99
113
  primary_response = operation.primary_success_response
100
114
  if primary_response and primary_response.schema_name:
101
- if operation.is_list_operation:
115
+ # Check if response is paginated (has 'results' field)
116
+ is_paginated = primary_response.schema_name.startswith('Paginated')
117
+ if operation.is_list_operation and not is_paginated:
102
118
  return_type = f"Models.{primary_response.schema_name}[]"
103
119
  else:
104
120
  return_type = f"Models.{primary_response.schema_name}"
@@ -151,15 +167,37 @@ class OperationsGenerator:
151
167
  if use_rest_params and query_params_list:
152
168
  # Extract parameters from args array
153
169
  path_params_count = len(operation.path_parameters)
154
- body_params_count = 1 if (operation.request_body or operation.patch_request_body) else 0
170
+
171
+ # For multipart, body params are unpacked as individual fields
172
+ if is_multipart and operation.request_body:
173
+ schema_name = operation.request_body.schema_name
174
+ if schema_name and schema_name in self.context.schemas:
175
+ schema = self.context.schemas[schema_name]
176
+ body_params_count = len(schema.properties)
177
+ else:
178
+ body_params_count = 1 # data: FormData
179
+ else:
180
+ body_params_count = 1 if (operation.request_body or operation.patch_request_body) else 0
181
+
155
182
  first_query_pos = path_params_count + body_params_count
156
183
 
157
184
  # Extract path parameters
158
185
  for i, param in enumerate(operation.path_parameters):
159
186
  body_lines.append(f"const {param.name} = args[{i}];")
160
187
 
161
- # Extract body/data parameter
162
- if operation.request_body or operation.patch_request_body:
188
+ # Extract body/data parameter(s)
189
+ if is_multipart and operation.request_body:
190
+ schema_name = operation.request_body.schema_name
191
+ if schema_name and schema_name in self.context.schemas:
192
+ schema = self.context.schemas[schema_name]
193
+ # Extract each property as separate variable
194
+ arg_idx = path_params_count
195
+ for prop_name in schema.properties.keys():
196
+ body_lines.append(f"const {prop_name} = args[{arg_idx}];")
197
+ arg_idx += 1
198
+ else:
199
+ body_lines.append(f"const data = args[{path_params_count}];")
200
+ elif operation.request_body or operation.patch_request_body:
163
201
  body_lines.append(f"const data = args[{path_params_count}];")
164
202
 
165
203
  # Check if first query arg is object (params style) or primitive (old style)
@@ -185,7 +223,18 @@ class OperationsGenerator:
185
223
  if use_rest_params:
186
224
  # Extract params from args array - handle both calling styles
187
225
  path_params_count = len(operation.path_parameters)
188
- body_params_count = 1 if (operation.request_body or operation.patch_request_body) else 0
226
+
227
+ # For multipart, body params are unpacked as individual fields
228
+ if is_multipart and operation.request_body:
229
+ schema_name = operation.request_body.schema_name
230
+ if schema_name and schema_name in self.context.schemas:
231
+ schema = self.context.schemas[schema_name]
232
+ body_params_count = len(schema.properties)
233
+ else:
234
+ body_params_count = 1 # data: FormData
235
+ else:
236
+ body_params_count = 1 if (operation.request_body or operation.patch_request_body) else 0
237
+
189
238
  first_query_pos = path_params_count + body_params_count
190
239
 
191
240
  body_lines.append("let params;")
@@ -242,8 +291,14 @@ class OperationsGenerator:
242
291
 
243
292
  # Handle response
244
293
  if operation.is_list_operation and primary_response:
245
- # Extract results from paginated response
246
- body_lines.append("return (response as any).results || [];")
294
+ # Check if response is paginated
295
+ is_paginated = primary_response.schema_name.startswith('Paginated')
296
+ if is_paginated:
297
+ # Return full paginated response object
298
+ body_lines.append("return response;")
299
+ else:
300
+ # Extract results from array response
301
+ body_lines.append("return (response as any).results || [];")
247
302
  elif return_type != "void":
248
303
  body_lines.append("return response;")
249
304
  else:
@@ -3,7 +3,7 @@ Zod Schemas Generator - Generates Zod validation schemas from IR.
3
3
 
4
4
  This generator creates Zod schemas for runtime validation:
5
5
  - Object schemas (z.object)
6
- - Enum schemas (z.nativeEnum)
6
+ - Enum schemas (z.enum)
7
7
  - Array schemas (z.array)
8
8
  - Type inference (z.infer<typeof Schema>)
9
9
  """
@@ -22,7 +22,7 @@ class SchemasGenerator:
22
22
  Features:
23
23
  - Runtime validation with Zod
24
24
  - Type inference from schemas
25
- - Enum validation with z.nativeEnum()
25
+ - Enum validation with z.enum()
26
26
  - Nested object validation
27
27
  - Array and nullable types
28
28
  """
@@ -46,7 +46,7 @@ class SchemasGenerator:
46
46
  >>> generate_schema(User)
47
47
  export const UserSchema = z.object({
48
48
  id: z.number(),
49
- email: z.string().email(),
49
+ email: z.email(),
50
50
  username: z.string().min(1).max(150),
51
51
  })
52
52
  """
@@ -74,7 +74,7 @@ class SchemasGenerator:
74
74
  # Generate fields
75
75
  if schema.properties:
76
76
  for prop_name, prop_schema in schema.properties.items():
77
- field_code = self._generate_field(prop_name, prop_schema, schema.required)
77
+ field_code = self._generate_field(prop_name, prop_schema, schema.required, parent_schema=schema)
78
78
  lines.append(f" {field_code},")
79
79
 
80
80
  lines.append("})")
@@ -86,60 +86,66 @@ class SchemasGenerator:
86
86
  name: str,
87
87
  schema: IRSchemaObject,
88
88
  required_fields: list[str],
89
+ parent_schema: IRSchemaObject | None = None,
89
90
  ) -> str:
90
91
  """
91
92
  Generate Zod field validation.
92
93
 
93
94
  Examples:
94
95
  id: z.number()
95
- email: z.string().email()
96
+ email: z.email()
96
97
  username: z.string().min(1).max(150)
97
- status: z.nativeEnum(Enums.StatusEnum)
98
- created_at: z.string().datetime()
98
+ status: z.nativeEnum(Enums.StatusEnum) # Reference to TypeScript enum
99
+ created_at: z.iso.datetime()
99
100
  """
100
101
  # Check if this field is an enum
101
102
  if schema.enum and schema.name:
102
- # Use enum validation
103
+ # Use nativeEnum to reference TypeScript enum from enums.ts
103
104
  zod_type = f"z.nativeEnum(Enums.{self.base.sanitize_enum_name(schema.name)})"
104
105
  # Check if this field is a reference to an enum
105
106
  elif schema.ref and schema.ref in self.context.schemas:
106
107
  ref_schema = self.context.schemas[schema.ref]
107
108
  if ref_schema.enum:
108
- # Reference to enum component
109
+ # Reference to enum component - use nativeEnum
109
110
  zod_type = f"z.nativeEnum(Enums.{self.base.sanitize_enum_name(schema.ref)})"
110
111
  else:
111
112
  # Reference to another schema
112
113
  zod_type = f"{schema.ref}Schema"
113
114
  else:
114
115
  # Map TypeScript type to Zod type
115
- zod_type = self._map_type_to_zod(schema)
116
+ zod_type = self._map_type_to_zod(schema, parent_schema=parent_schema)
116
117
 
117
118
  # Check if required
118
119
  is_required = name in required_fields
119
120
 
120
- # Handle optional fields - use .optional() for both non-required AND nullable
121
- # This converts Django's nullable=True to TypeScript's optional (undefined)
122
- if not is_required or schema.nullable:
121
+ # Handle nullable and optional separately
122
+ # - nullable: field can be null (use .nullable())
123
+ # - not required: field can be undefined (use .optional())
124
+ if schema.nullable:
125
+ zod_type = f"{zod_type}.nullable()"
126
+
127
+ if not is_required:
123
128
  zod_type = f"{zod_type}.optional()"
124
129
 
125
130
  return f"{name}: {zod_type}"
126
131
 
127
- def _map_type_to_zod(self, schema: IRSchemaObject) -> str:
132
+ def _map_type_to_zod(self, schema: IRSchemaObject, parent_schema: IRSchemaObject | None = None) -> str:
128
133
  """
129
134
  Map OpenAPI/TypeScript type to Zod validation.
130
135
 
131
136
  Args:
132
137
  schema: IRSchemaObject with type information
138
+ parent_schema: Parent schema (for context about patch models, etc.)
133
139
 
134
140
  Returns:
135
141
  Zod validation code
136
142
 
137
143
  Examples:
138
144
  string -> z.string()
139
- string (format: email) -> z.string().email()
140
- string (format: date-time) -> z.string().datetime()
141
- string (format: uri) -> z.string().url()
142
- integer -> z.number().int()
145
+ string (format: email) -> z.email()
146
+ string (format: date-time) -> z.iso.datetime()
147
+ string (format: uri) -> z.url()
148
+ integer -> z.int()
143
149
  number -> z.number()
144
150
  boolean -> z.boolean()
145
151
  array -> z.array(...)
@@ -147,39 +153,49 @@ class SchemasGenerator:
147
153
  schema_type = schema.type
148
154
  schema_format = schema.format
149
155
 
156
+ # Binary type (File/Blob for file uploads)
157
+ # NOTE: Skip binary validation for PATCH models (they use JSON, not multipart)
158
+ # PATCH models typically don't allow file uploads
159
+ parent_is_patch = parent_schema and parent_schema.is_patch_model if parent_schema else False
160
+ if schema.is_binary and not parent_is_patch:
161
+ # For multipart/form-data file uploads, use z.instanceof()
162
+ # This works for both File and Blob in browser/React Native
163
+ return "z.union([z.instanceof(File), z.instanceof(Blob)])"
164
+
150
165
  # String types with format validation
151
166
  if schema_type == "string":
152
167
  base_type = "z.string()"
153
168
 
154
- # Add format validation
169
+ # Add format validation - use new Zod v4 format APIs
155
170
  if schema_format == "email":
156
- base_type = "z.string().email()"
171
+ base_type = "z.email()"
157
172
  elif schema_format in ("date-time", "datetime"):
158
- base_type = "z.string().datetime()"
173
+ base_type = "z.iso.datetime()"
159
174
  elif schema_format == "date":
160
- base_type = "z.string().date()"
175
+ base_type = "z.iso.date()"
161
176
  elif schema_format in ("uri", "url"):
162
- base_type = "z.string().url()"
177
+ base_type = "z.url()"
163
178
  elif schema_format == "uuid":
164
- base_type = "z.string().uuid()"
179
+ base_type = "z.uuid()"
165
180
 
166
- # Add length constraints
167
- if schema.min_length is not None:
168
- base_type = f"{base_type}.min({schema.min_length})"
169
- if schema.max_length is not None:
170
- base_type = f"{base_type}.max({schema.max_length})"
181
+ # Add length constraints (only for plain strings, not formatted ones)
182
+ if base_type == "z.string()":
183
+ if schema.min_length is not None:
184
+ base_type = f"{base_type}.min({schema.min_length})"
185
+ if schema.max_length is not None:
186
+ base_type = f"{base_type}.max({schema.max_length})"
171
187
 
172
- # Add pattern validation
173
- if schema.pattern:
174
- # Escape regex pattern for JS
175
- escaped_pattern = schema.pattern.replace('\\', '\\\\')
176
- base_type = f"{base_type}.regex(/{escaped_pattern}/)"
188
+ # Add pattern validation
189
+ if schema.pattern:
190
+ # Escape forward slashes for JS regex literal
191
+ escaped_pattern = schema.pattern.replace('/', r'\/')
192
+ base_type = f"{base_type}.regex(/{escaped_pattern}/)"
177
193
 
178
194
  return base_type
179
195
 
180
196
  # Integer type
181
197
  elif schema_type == "integer":
182
- base_type = "z.number().int()"
198
+ base_type = "z.int()"
183
199
 
184
200
  # Add range constraints
185
201
  if schema.minimum is not None:
@@ -246,7 +262,7 @@ class SchemasGenerator:
246
262
  return "\n".join(lines)
247
263
 
248
264
  def _generate_enum_schema(self, schema: IRSchemaObject) -> str:
249
- """Generate z.nativeEnum() schema."""
265
+ """Generate z.nativeEnum() schema that references TypeScript enum."""
250
266
  enum_name = self.base.sanitize_enum_name(schema.name)
251
267
 
252
268
  lines = []
@@ -254,6 +270,8 @@ class SchemasGenerator:
254
270
  if schema.description:
255
271
  lines.append(f"/**\n * {schema.description}\n */")
256
272
 
273
+ # Use z.nativeEnum to reference TypeScript enum from enums.ts
274
+ # This ensures type compatibility with models.ts
257
275
  lines.append(f"export const {enum_name}Schema = z.nativeEnum(Enums.{enum_name})")
258
276
 
259
277
  return "\n".join(lines)
@@ -17,6 +17,7 @@
17
17
  import useSWR from 'swr'
18
18
  import { useSWRConfig } from 'swr'
19
19
  import * as Fetchers from '../fetchers/{{ tag_file }}'
20
+ import type { API } from '../../index'
20
21
  {% if has_schemas %}
21
22
  {% for schema_name in schema_names %}
22
23
  import type { {{ schema_name }} } from '../schemas/{{ schema_name }}.schema'
@@ -7,11 +7,11 @@
7
7
  export function {{ hook_name }}() {
8
8
  const { mutate } = useSWRConfig()
9
9
 
10
- return async ({% if func_params %}{{ func_params }}{% endif %}): Promise<{{ response_type }}> => {
10
+ return async ({% if func_params %}{{ func_params }}, client?: API{% else %}client?: API{% endif %}): Promise<{{ response_type }}> => {
11
11
  {% if fetcher_params %}
12
- const result = await Fetchers.{{ fetcher_name }}({{ fetcher_params }})
12
+ const result = await Fetchers.{{ fetcher_name }}({{ fetcher_params }}, client)
13
13
  {% else %}
14
- const result = await Fetchers.{{ fetcher_name }}()
14
+ const result = await Fetchers.{{ fetcher_name }}(client)
15
15
  {% endif %}
16
16
  {% if revalidation_keys %}
17
17
  // Revalidate related queries
@@ -6,16 +6,15 @@
6
6
  */
7
7
  export function {{ hook_name }}(
8
8
  {%- if func_params -%}
9
- {{ func_params }}
10
- {%- endif -%}
11
- ) {
12
- return useSWR<{{ response_type }}>(
13
- {{ swr_key }},
14
- {%- if fetcher_params %}
15
- () => Fetchers.{{ fetcher_name }}({{ fetcher_params }})
9
+ {{ func_params }}, client?: API
16
10
  {%- else %}
17
- () => Fetchers.{{ fetcher_name }}()
11
+ client?: API
18
12
  {%- endif %}
19
- )
13
+ ): ReturnType<typeof useSWR<{{ response_type }}>> {
14
+ return useSWR<{{ response_type }}>(
15
+ {{ swr_key }},
16
+ {% if fetcher_params %} () => Fetchers.{{ fetcher_name }}({{ fetcher_params }}, client)
17
+ {% else %} () => Fetchers.{{ fetcher_name }}(client)
18
+ {% endif %} )
20
19
  }
21
20
 
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.17"
7
+ version = "1.4.20"
8
8
  description = "Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django."
9
9
  readme = "README.md"
10
10
  keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-cfg
3
- Version: 1.4.17
3
+ Version: 1.4.20
4
4
  Summary: Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django.
5
5
  Project-URL: Homepage, https://djangocfg.com
6
6
  Project-URL: Documentation, https://djangocfg.com
@@ -3,7 +3,7 @@ django_cfg/__init__.py,sha256=4dZgnuTlq8YmLISTJAqnPrr080kxPfmPKUjhiWhkEDc,1630
3
3
  django_cfg/apps.py,sha256=k84brkeXJI7EgKZLEpTkM9YFZofKI4PzhFOn1cl9Msc,1656
4
4
  django_cfg/config.py,sha256=3hX5bOCbOWdUvtD9Z5qEHEOEyWzY1-4CsvFs_EO7VSw,1398
5
5
  django_cfg/apps/__init__.py,sha256=JtDmEYt1OcleWM2ZaeX0LKDnRQzPOavfaXBWG4ECB5Q,26
6
- django_cfg/apps/urls.py,sha256=DKlW8kSPv4MImjNlc9uaukOEkKrk-AvOQrOSjerVUf8,4633
6
+ django_cfg/apps/urls.py,sha256=nQgaDWlr33h-fXawz6PA_KF5k_jfbfDVuinTPxCqcsw,4775
7
7
  django_cfg/apps/accounts/README.md,sha256=YkUYJ3iKMYTmm9ALK2PDnX75SDqZxgnkzNLCD5efxRs,8227
8
8
  django_cfg/apps/accounts/__init__.py,sha256=osecEQhMJVP8ejhZzElNsAqA1fX-GPD3K5_yNwDk6IE,100
9
9
  django_cfg/apps/accounts/__models.py,sha256=65AomWYd78ptQ60drPbodxf0Ue310vmJQpQOPHL6V3E,10161
@@ -117,7 +117,9 @@ django_cfg/apps/api/health/views.py,sha256=D_HL7P4CrfV5tUqyto59MBCAKDoFG8x7PrQUb
117
117
  django_cfg/apps/knowbase/README.md,sha256=HXt_J6WCN-LsMhA7p9mdvih07_vp_r_hkPdmqHhNEeo,3965
118
118
  django_cfg/apps/knowbase/__init__.py,sha256=cfGnxDQwjajPhUoleKkgvdabJcB0LdXEglnsBojKkPo,1045
119
119
  django_cfg/apps/knowbase/apps.py,sha256=FNvRmaUkZ64TierGSabWNb9IzzbDFMndscSldxUn1dU,3426
120
- django_cfg/apps/knowbase/urls.py,sha256=L-epTFzhgqFLlf9P5XLFnRihdiE8thv4M3slaGElfik,1585
120
+ django_cfg/apps/knowbase/urls.py,sha256=lOMuEoNDIBE4Fhhr3aQ0RxWInNS1Ri4NYPgVLijsPuU,642
121
+ django_cfg/apps/knowbase/urls_admin.py,sha256=WN6Tm5lGlisrGGBK10PHCjPhztqQ2GotW6-WVUE5UHM,630
122
+ django_cfg/apps/knowbase/urls_system.py,sha256=c4zl7sMTJE0p-esoT2UC2V3qouVWJhi35z1Id1z4o4g,747
121
123
  django_cfg/apps/knowbase/admin/__init__.py,sha256=NGYMzpLec7e8jYeQg3Gz5x-N-nMUePN6DHqzMHC4dAQ,499
122
124
  django_cfg/apps/knowbase/admin/archive_admin.py,sha256=uQtK-6XRyiFR8H0polOwCKvufJE_pninjZL6sIlP-30,21239
123
125
  django_cfg/apps/knowbase/admin/chat_admin.py,sha256=5iG5NEmTzQRlKjpy6wBjY8LLJkJNj1ZN9gT41dArtHY,16655
@@ -163,7 +165,7 @@ django_cfg/apps/knowbase/mixins/generators/content_generator.py,sha256=gBog40cfL
163
165
  django_cfg/apps/knowbase/mixins/generators/field_analyzer.py,sha256=-r1ovN1LwAJLjuT8fVwf0BKtoHJDl7NsFBESzPyh35c,2759
164
166
  django_cfg/apps/knowbase/mixins/generators/metadata_generator.py,sha256=gfF9SoWtKuCHfoVPtWsH9vAyYntJ4git2tMmIJg77OE,4476
165
167
  django_cfg/apps/knowbase/models/__init__.py,sha256=MnMzbOZTB3dlo0IU1VsgQVY1uPOgU6FtQFiAUyaK6AU,742
166
- django_cfg/apps/knowbase/models/archive.py,sha256=qNSfOvfAf3oDzpiGjgZH7v2JjLMz2Yt7CdrEUxd7bUM,18600
168
+ django_cfg/apps/knowbase/models/archive.py,sha256=CiXfTRacHe9PuaF6_W78uKKWg2TBm468i80Y6tkBF_k,18600
167
169
  django_cfg/apps/knowbase/models/base.py,sha256=ojkeEige-Z4W4HV96FSL3phjipVUwbUzKjRzWl_28vM,1478
168
170
  django_cfg/apps/knowbase/models/chat.py,sha256=HBiYxwLpve5VTIJkhHuGWCK_OEZZKWzmMKSe1jHxSpU,4578
169
171
  django_cfg/apps/knowbase/models/document.py,sha256=NVVQIaZHJFPgyAVQe8sJK-faUVWrymKagV6AIJUZA-0,8368
@@ -760,14 +762,14 @@ django_cfg/modules/django_client/core/generator/python/templates/utils/retry.py.
760
762
  django_cfg/modules/django_client/core/generator/python/templates/utils/schema.py.jinja,sha256=xrnhAc-hNk6DigxHmTvhR3dyDFI9ocnkhL-_Hz9hCs8,270
761
763
  django_cfg/modules/django_client/core/generator/typescript/__init__.py,sha256=eHOZp7M65WZ9u3tA_xQlON5-oijZZiGXDhz22Bq73s0,371
762
764
  django_cfg/modules/django_client/core/generator/typescript/client_generator.py,sha256=7ql-m59YVt6zGKfVBCxy1OR3CNy6C9lkaMEUqexiRvo,5878
763
- django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py,sha256=ZiSXMyT7HV28IK0AE8p2Sma2xwFrWnzSurBZEaWw_Z8,13791
765
+ django_cfg/modules/django_client/core/generator/typescript/fetchers_generator.py,sha256=2Gd-AIKx5m_0q5bVPr-PbQQjqAeRwL-dJgfbnIHAFHs,16445
764
766
  django_cfg/modules/django_client/core/generator/typescript/files_generator.py,sha256=faRdhVVf9GQ-0esVz94dsaQMB56zK3csyNkhEHL4al4,7044
765
- django_cfg/modules/django_client/core/generator/typescript/generator.py,sha256=_xuQC10SJ1ZKwA2_h3te4ZkSmH2jlnjUr__EJuBwNVE,16982
766
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py,sha256=urnmej5isaPwnyo-kH_VVGP9kehcdeI2hR9mqHmVN4s,15133
767
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py,sha256=2cBA-YzeBkGekoKz0pzT-Rt56MCvRTT-lSb44AAujfk,8787
767
+ django_cfg/modules/django_client/core/generator/typescript/generator.py,sha256=oBM7CKlsD7arzIt0dSeow7Ujuo6RWl6h5to221Q19Gc,16984
768
+ django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py,sha256=woTohitYEyo6R7s-XqO6o9KC5u-nbo2lAd1jAchvT-U,15694
769
+ django_cfg/modules/django_client/core/generator/typescript/models_generator.py,sha256=dvfBsXBds_SvGBXrmcIcODMgQMUYAMsHuqBo9Z5F80c,9131
768
770
  django_cfg/modules/django_client/core/generator/typescript/naming.py,sha256=uUh2XVmorQ8gzfdzZJB8KNPng6UI4axe3lEaFtTqYks,2592
769
- django_cfg/modules/django_client/core/generator/typescript/operations_generator.py,sha256=lY56TkDafI-CL6wv7GXWNpvDr7087W3oWRTC4bJLJ9k,13493
770
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py,sha256=tmFZ8yFJ_Nk_iT7QGDDUVSFJhg8WTF79lYDOqQDzz8c,10877
771
+ django_cfg/modules/django_client/core/generator/typescript/operations_generator.py,sha256=Nwcx0m4Z0fnMEiwbsZJW9m4Ut6XBD-Hw4WPxXuPIEVg,16354
772
+ django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py,sha256=DnattUopGrSazcLIIFbRC-BG2aD1DrZ_D0osp9Z8D1E,12041
771
773
  django_cfg/modules/django_client/core/generator/typescript/templates/api_instance.ts.jinja,sha256=OPUjnz6Dk3kY97UFIRcgvxkEIKd6fUGqBzXJWOXKykE,2906
772
774
  django_cfg/modules/django_client/core/generator/typescript/templates/app_index.ts.jinja,sha256=gLsoYyEzKD6Gv64vsO9sQHMPiFMGdaB5XVufLHeRyvQ,62
773
775
  django_cfg/modules/django_client/core/generator/typescript/templates/client_file.ts.jinja,sha256=LHUt72fO2eRNAHYEscIYvqVR69GC6mxqjcgSlUzeCtc,251
@@ -784,10 +786,10 @@ django_cfg/modules/django_client/core/generator/typescript/templates/client/sub_
784
786
  django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja,sha256=HNeYh8LnTTmeKTQS7uup37Oq-_HwBPZH8qE1kneaJsg,1296
785
787
  django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja,sha256=cR4Y4eJ1ENcON9BcfI-ttnJfKxv61efMW2oVDrmJga8,580
786
788
  django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/index.ts.jinja,sha256=DoAVm8EoglJKtLrE917Fk7Na6rn5Bt9L1nI39o9AwzM,747
787
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja,sha256=VS9_Pl1IemR2mPA2df9h-5xXSE9suvttpSC1YPClagE,763
789
+ django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja,sha256=_iyvJBEQviLLtZ5z1qXNUM976zXPW-v9DXFyHJdrpCQ,802
788
790
  django_cfg/modules/django_client/core/generator/typescript/templates/hooks/index.ts.jinja,sha256=c4Jru6DAKD-y8nzig1my0iE-BO3OwjSCENALbHvAp7E,694
789
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/mutation_hook.ts.jinja,sha256=iVB8hXAt0VEw5Y4OsWsPJJmpxflr5LLid3l7xASxvRU,647
790
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/query_hook.ts.jinja,sha256=hdNU62F_xL4m9Lz5gEkkw2tny9dHguvpDRK9dz7CRy0,432
791
+ django_cfg/modules/django_client/core/generator/typescript/templates/hooks/mutation_hook.ts.jinja,sha256=pOeyEY9nkYKVARTN6P4dfEj_mg-CIuPd8rrxzfaCfF8,697
792
+ django_cfg/modules/django_client/core/generator/typescript/templates/hooks/query_hook.ts.jinja,sha256=_Qit1DTZQTYIM_l6gmBkS2Fy7358EsGrUWr0iwCwyhw,526
791
793
  django_cfg/modules/django_client/core/generator/typescript/templates/models/app_models.ts.jinja,sha256=hRJ06Z-L4M5IEB23dd_rVgwSzSEGmqaSsKVrg5NasY0,122
792
794
  django_cfg/modules/django_client/core/generator/typescript/templates/models/enums.ts.jinja,sha256=8BguyHxV4r5JdJD9wYYgZrClysIdlkjf_Bp5toU81u8,49
793
795
  django_cfg/modules/django_client/core/generator/typescript/templates/models/models.ts.jinja,sha256=mycvqd12K2LsnN4mnwMdQ_SepiIWx6e9gDlUMAGOoBA,121
@@ -1100,9 +1102,9 @@ django_cfg/utils/version_check.py,sha256=jI4v3YMdQriUEeb_TvRl511sDghy6I75iKRDUaN
1100
1102
  django_cfg/CHANGELOG.md,sha256=jtT3EprqEJkqSUh7IraP73vQ8PmKUMdRtznQsEnqDZk,2052
1101
1103
  django_cfg/CONTRIBUTING.md,sha256=DU2kyQ6PU0Z24ob7O_OqKWEYHcZmJDgzw-lQCmu6uBg,3041
1102
1104
  django_cfg/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1103
- django_cfg/pyproject.toml,sha256=cS3SRfVShKr8ndIdMpiDOJiH3gFLLZ_5MdWAlCU3vpU,8210
1104
- django_cfg-1.4.17.dist-info/METADATA,sha256=5kh9Jcmaml3LflWBK4tDei8CwFXViH-ZwapA6bumVBc,22533
1105
- django_cfg-1.4.17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1106
- django_cfg-1.4.17.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1107
- django_cfg-1.4.17.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1108
- django_cfg-1.4.17.dist-info/RECORD,,
1105
+ django_cfg/pyproject.toml,sha256=nbORroIo8szkSqBsezUyoTy51R_ggBBDmb5C-iSIO3w,8210
1106
+ django_cfg-1.4.20.dist-info/METADATA,sha256=qxG77CBP_ISz9wRdUM9AzZe4bm7xHILFwZkwSO3xdRI,22533
1107
+ django_cfg-1.4.20.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
1108
+ django_cfg-1.4.20.dist-info/entry_points.txt,sha256=Ucmde4Z2wEzgb4AggxxZ0zaYDb9HpyE5blM3uJ0_VNg,56
1109
+ django_cfg-1.4.20.dist-info/licenses/LICENSE,sha256=xHuytiUkSZCRG3N11nk1X6q1_EGQtv6aL5O9cqNRhKE,1071
1110
+ django_cfg-1.4.20.dist-info/RECORD,,