django-cfg 1.3.9__py3-none-any.whl → 1.3.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/accounts/admin/inlines.py +11 -5
  3. django_cfg/apps/payments/admin/networks_admin.py +12 -1
  4. django_cfg/apps/payments/admin/payments_admin.py +13 -0
  5. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +62 -14
  6. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  7. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  8. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  9. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  10. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  11. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  12. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +33 -3
  14. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  15. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +96 -45
  16. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  17. django_cfg/apps/payments/config/__init__.py +14 -15
  18. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  19. django_cfg/apps/payments/config/helpers.py +8 -13
  20. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  21. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  22. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  23. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  24. django_cfg/apps/payments/models/payments.py +94 -0
  25. django_cfg/apps/payments/services/core/base.py +4 -4
  26. django_cfg/apps/payments/services/core/payment_service.py +265 -38
  27. django_cfg/apps/payments/services/providers/base.py +209 -3
  28. django_cfg/apps/payments/services/providers/models/__init__.py +2 -0
  29. django_cfg/apps/payments/services/providers/models/base.py +25 -2
  30. django_cfg/apps/payments/services/providers/nowpayments/models.py +2 -2
  31. django_cfg/apps/payments/services/providers/nowpayments/provider.py +57 -9
  32. django_cfg/apps/payments/services/providers/registry.py +5 -5
  33. django_cfg/apps/payments/services/types/requests.py +19 -7
  34. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  35. django_cfg/apps/payments/static/payments/js/api-client.js +6 -1
  36. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  37. django_cfg/apps/payments/static/payments/js/payment-form.js +35 -26
  38. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  39. django_cfg/apps/payments/urls.py +3 -2
  40. django_cfg/apps/payments/views/api/currencies.py +3 -0
  41. django_cfg/apps/payments/views/serializers/currencies.py +18 -5
  42. django_cfg/apps/tasks/admin/tasks_admin.py +2 -2
  43. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  44. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  45. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  46. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  47. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  48. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  49. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  50. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  51. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  52. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  53. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  54. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  55. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  56. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  57. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  58. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  59. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  60. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  61. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  62. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  63. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  64. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  65. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  66. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  67. django_cfg/apps/tasks/urls.py +2 -2
  68. django_cfg/apps/tasks/urls_admin.py +2 -2
  69. django_cfg/apps/tasks/utils/__init__.py +1 -0
  70. django_cfg/apps/tasks/utils/simulator.py +356 -0
  71. django_cfg/apps/tasks/views/__init__.py +16 -0
  72. django_cfg/apps/tasks/views/api.py +569 -0
  73. django_cfg/apps/tasks/views/dashboard.py +58 -0
  74. django_cfg/core/integration/__init__.py +21 -0
  75. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  76. django_cfg/models/constance.py +0 -11
  77. django_cfg/models/payments.py +137 -3
  78. django_cfg/modules/django_tasks.py +54 -21
  79. django_cfg/registry/core.py +4 -9
  80. django_cfg/template_archive/django_sample.zip +0 -0
  81. {django_cfg-1.3.9.dist-info → django_cfg-1.3.13.dist-info}/METADATA +2 -2
  82. {django_cfg-1.3.9.dist-info → django_cfg-1.3.13.dist-info}/RECORD +85 -153
  83. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  84. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  85. django_cfg/apps/payments/config/constance/fields.py +0 -69
  86. django_cfg/apps/payments/config/constance/settings.py +0 -160
  87. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +0 -26
  88. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +0 -28
  89. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +0 -30
  90. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  91. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  92. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  93. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  94. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  95. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  96. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  97. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  98. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  99. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  100. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  101. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  102. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  103. django_cfg/apps/tasks/views.py +0 -461
  104. django_cfg/management/commands/app_agent_diagnose.py +0 -470
  105. django_cfg/management/commands/app_agent_generate.py +0 -342
  106. django_cfg/management/commands/app_agent_info.py +0 -308
  107. django_cfg/management/commands/auto_generate.py +0 -486
  108. django_cfg/modules/django_app_agent/__init__.py +0 -87
  109. django_cfg/modules/django_app_agent/agents/__init__.py +0 -40
  110. django_cfg/modules/django_app_agent/agents/base/__init__.py +0 -24
  111. django_cfg/modules/django_app_agent/agents/base/agent.py +0 -354
  112. django_cfg/modules/django_app_agent/agents/base/context.py +0 -236
  113. django_cfg/modules/django_app_agent/agents/base/executor.py +0 -430
  114. django_cfg/modules/django_app_agent/agents/generation/__init__.py +0 -12
  115. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +0 -15
  116. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +0 -147
  117. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +0 -99
  118. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +0 -32
  119. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +0 -290
  120. django_cfg/modules/django_app_agent/agents/interfaces.py +0 -376
  121. django_cfg/modules/django_app_agent/core/__init__.py +0 -33
  122. django_cfg/modules/django_app_agent/core/config.py +0 -300
  123. django_cfg/modules/django_app_agent/core/exceptions.py +0 -359
  124. django_cfg/modules/django_app_agent/models/__init__.py +0 -71
  125. django_cfg/modules/django_app_agent/models/base.py +0 -283
  126. django_cfg/modules/django_app_agent/models/context.py +0 -496
  127. django_cfg/modules/django_app_agent/models/enums.py +0 -481
  128. django_cfg/modules/django_app_agent/models/requests.py +0 -500
  129. django_cfg/modules/django_app_agent/models/responses.py +0 -585
  130. django_cfg/modules/django_app_agent/pytest.ini +0 -6
  131. django_cfg/modules/django_app_agent/services/__init__.py +0 -42
  132. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +0 -30
  133. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +0 -133
  134. django_cfg/modules/django_app_agent/services/app_generator/context.py +0 -40
  135. django_cfg/modules/django_app_agent/services/app_generator/main.py +0 -202
  136. django_cfg/modules/django_app_agent/services/app_generator/structure.py +0 -316
  137. django_cfg/modules/django_app_agent/services/app_generator/validation.py +0 -125
  138. django_cfg/modules/django_app_agent/services/base.py +0 -437
  139. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +0 -34
  140. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +0 -141
  141. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +0 -276
  142. django_cfg/modules/django_app_agent/services/context_builder/main.py +0 -272
  143. django_cfg/modules/django_app_agent/services/context_builder/models.py +0 -40
  144. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +0 -85
  145. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +0 -31
  146. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +0 -311
  147. django_cfg/modules/django_app_agent/services/project_scanner/main.py +0 -221
  148. django_cfg/modules/django_app_agent/services/project_scanner/models.py +0 -59
  149. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +0 -94
  150. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +0 -28
  151. django_cfg/modules/django_app_agent/services/questioning_service/main.py +0 -273
  152. django_cfg/modules/django_app_agent/services/questioning_service/models.py +0 -111
  153. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +0 -251
  154. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +0 -347
  155. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +0 -356
  156. django_cfg/modules/django_app_agent/services/report_service.py +0 -332
  157. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +0 -18
  158. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +0 -236
  159. django_cfg/modules/django_app_agent/services/template_manager/main.py +0 -159
  160. django_cfg/modules/django_app_agent/services/template_manager/models.py +0 -36
  161. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +0 -100
  162. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +0 -105
  163. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +0 -31
  164. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +0 -44
  165. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +0 -81
  166. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +0 -107
  167. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +0 -139
  168. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +0 -91
  169. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +0 -195
  170. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +0 -35
  171. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +0 -211
  172. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +0 -200
  173. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +0 -25
  174. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +0 -333
  175. django_cfg/modules/django_app_agent/services/validation_service/main.py +0 -242
  176. django_cfg/modules/django_app_agent/services/validation_service/models.py +0 -66
  177. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +0 -352
  178. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +0 -272
  179. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +0 -203
  180. django_cfg/modules/django_app_agent/ui/__init__.py +0 -25
  181. django_cfg/modules/django_app_agent/ui/cli.py +0 -419
  182. django_cfg/modules/django_app_agent/ui/rich_components.py +0 -622
  183. django_cfg/modules/django_app_agent/utils/__init__.py +0 -38
  184. django_cfg/modules/django_app_agent/utils/logging.py +0 -360
  185. django_cfg/modules/django_app_agent/utils/validation.py +0 -417
  186. {django_cfg-1.3.9.dist-info → django_cfg-1.3.13.dist-info}/WHEEL +0 -0
  187. {django_cfg-1.3.9.dist-info → django_cfg-1.3.13.dist-info}/entry_points.txt +0 -0
  188. {django_cfg-1.3.9.dist-info → django_cfg-1.3.13.dist-info}/licenses/LICENSE +0 -0
@@ -1,417 +0,0 @@
1
- """
2
- Validation utilities for Django App Agent Module.
3
-
4
- This module provides comprehensive validation functions for:
5
- - Django app names and identifiers
6
- - File paths and names
7
- - Python code validation
8
- - Input sanitization
9
- """
10
-
11
- import re
12
- import keyword
13
- from typing import List, Optional, Tuple
14
- from pathlib import Path
15
-
16
- from ..core.exceptions import ValidationError
17
-
18
-
19
- # Regular expressions for validation
20
- APP_NAME_PATTERN = re.compile(r'^[a-z][a-z0-9_]*$')
21
- PYTHON_IDENTIFIER_PATTERN = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
22
- SAFE_FILENAME_PATTERN = re.compile(r'^[a-zA-Z0-9._-]+$')
23
-
24
- # Reserved Django app names
25
- RESERVED_APP_NAMES = {
26
- 'admin', 'auth', 'contenttypes', 'sessions', 'messages', 'staticfiles',
27
- 'django', 'test', 'tests', 'migrations', 'management', 'locale',
28
- 'fixtures', 'templates', 'static', 'media'
29
- }
30
-
31
- # Reserved Python keywords and builtins
32
- RESERVED_PYTHON_NAMES = set(keyword.kwlist) | {
33
- 'True', 'False', 'None', '__builtins__', '__name__', '__file__',
34
- 'int', 'str', 'list', 'dict', 'tuple', 'set', 'bool', 'float'
35
- }
36
-
37
-
38
- def validate_app_name(name: str, *, check_reserved: bool = True) -> str:
39
- """Validate Django application name.
40
-
41
- Args:
42
- name: Application name to validate
43
- check_reserved: Whether to check against reserved names
44
-
45
- Returns:
46
- Validated and normalized app name
47
-
48
- Raises:
49
- ValidationError: If name is invalid
50
- """
51
- if not name:
52
- raise ValidationError(
53
- "App name cannot be empty",
54
- validation_type="app_name",
55
- field_name="name",
56
- field_value=name
57
- )
58
-
59
- # Normalize name
60
- normalized_name = name.strip().lower()
61
-
62
- # Check length
63
- if len(normalized_name) < 2:
64
- raise ValidationError(
65
- "App name must be at least 2 characters long",
66
- validation_type="app_name",
67
- field_name="name",
68
- field_value=name
69
- )
70
-
71
- if len(normalized_name) > 50:
72
- raise ValidationError(
73
- "App name must be no more than 50 characters long",
74
- validation_type="app_name",
75
- field_name="name",
76
- field_value=name
77
- )
78
-
79
- # Check pattern
80
- if not APP_NAME_PATTERN.match(normalized_name):
81
- raise ValidationError(
82
- "App name must start with a letter and contain only lowercase letters, numbers, and underscores",
83
- validation_type="app_name",
84
- field_name="name",
85
- field_value=name
86
- )
87
-
88
- # Check reserved names
89
- if check_reserved and normalized_name in RESERVED_APP_NAMES:
90
- raise ValidationError(
91
- f"'{normalized_name}' is a reserved Django app name",
92
- validation_type="app_name",
93
- field_name="name",
94
- field_value=name
95
- )
96
-
97
- # Check Python keywords
98
- if normalized_name in RESERVED_PYTHON_NAMES:
99
- raise ValidationError(
100
- f"'{normalized_name}' is a reserved Python keyword",
101
- validation_type="app_name",
102
- field_name="name",
103
- field_value=name
104
- )
105
-
106
- return normalized_name
107
-
108
-
109
- def validate_python_identifier(identifier: str, *, context: str = "identifier") -> str:
110
- """Validate Python identifier (variable, class, function name).
111
-
112
- Args:
113
- identifier: Identifier to validate
114
- context: Context for error messages
115
-
116
- Returns:
117
- Validated identifier
118
-
119
- Raises:
120
- ValidationError: If identifier is invalid
121
- """
122
- if not identifier:
123
- raise ValidationError(
124
- f"Python {context} cannot be empty",
125
- validation_type="python_identifier",
126
- field_name=context,
127
- field_value=identifier
128
- )
129
-
130
- # Check pattern
131
- if not PYTHON_IDENTIFIER_PATTERN.match(identifier):
132
- raise ValidationError(
133
- f"Python {context} must start with a letter or underscore and contain only letters, numbers, and underscores",
134
- validation_type="python_identifier",
135
- field_name=context,
136
- field_value=identifier
137
- )
138
-
139
- # Check keywords
140
- if keyword.iskeyword(identifier):
141
- raise ValidationError(
142
- f"'{identifier}' is a reserved Python keyword",
143
- validation_type="python_identifier",
144
- field_name=context,
145
- field_value=identifier
146
- )
147
-
148
- # Check builtins
149
- if identifier in RESERVED_PYTHON_NAMES:
150
- raise ValidationError(
151
- f"'{identifier}' is a reserved Python name",
152
- validation_type="python_identifier",
153
- field_name=context,
154
- field_value=identifier
155
- )
156
-
157
- return identifier
158
-
159
-
160
- def validate_file_path(
161
- path: Path,
162
- *,
163
- must_exist: bool = False,
164
- must_be_dir: bool = False,
165
- must_be_file: bool = False,
166
- create_parents: bool = False
167
- ) -> Path:
168
- """Validate file or directory path.
169
-
170
- Args:
171
- path: Path to validate
172
- must_exist: Whether path must exist
173
- must_be_dir: Whether path must be a directory
174
- must_be_file: Whether path must be a file
175
- create_parents: Whether to create parent directories
176
-
177
- Returns:
178
- Validated path
179
-
180
- Raises:
181
- ValidationError: If path is invalid
182
- """
183
- if not isinstance(path, Path):
184
- try:
185
- path = Path(path)
186
- except Exception as e:
187
- raise ValidationError(
188
- f"Invalid path format: {e}",
189
- validation_type="file_path",
190
- field_name="path",
191
- field_value=str(path),
192
- cause=e
193
- )
194
-
195
- # Resolve path
196
- try:
197
- resolved_path = path.resolve()
198
- except Exception as e:
199
- raise ValidationError(
200
- f"Cannot resolve path: {e}",
201
- validation_type="file_path",
202
- field_name="path",
203
- field_value=str(path),
204
- cause=e
205
- )
206
-
207
- # Check existence
208
- if must_exist and not resolved_path.exists():
209
- raise ValidationError(
210
- f"Path does not exist: {resolved_path}",
211
- validation_type="file_path",
212
- field_name="path",
213
- field_value=str(path)
214
- )
215
-
216
- # Check type constraints
217
- if resolved_path.exists():
218
- if must_be_dir and not resolved_path.is_dir():
219
- raise ValidationError(
220
- f"Path is not a directory: {resolved_path}",
221
- validation_type="file_path",
222
- field_name="path",
223
- field_value=str(path)
224
- )
225
-
226
- if must_be_file and not resolved_path.is_file():
227
- raise ValidationError(
228
- f"Path is not a file: {resolved_path}",
229
- validation_type="file_path",
230
- field_name="path",
231
- field_value=str(path)
232
- )
233
-
234
- # Create parent directories if requested
235
- if create_parents and not resolved_path.parent.exists():
236
- try:
237
- resolved_path.parent.mkdir(parents=True, exist_ok=True)
238
- except Exception as e:
239
- raise ValidationError(
240
- f"Cannot create parent directories: {e}",
241
- validation_type="file_path",
242
- field_name="path",
243
- field_value=str(path),
244
- cause=e
245
- )
246
-
247
- return resolved_path
248
-
249
-
250
- def sanitize_filename(filename: str, *, max_length: int = 255) -> str:
251
- """Sanitize filename for safe file system usage.
252
-
253
- Args:
254
- filename: Filename to sanitize
255
- max_length: Maximum filename length
256
-
257
- Returns:
258
- Sanitized filename
259
-
260
- Raises:
261
- ValidationError: If filename cannot be sanitized
262
- """
263
- if not filename:
264
- raise ValidationError(
265
- "Filename cannot be empty",
266
- validation_type="filename",
267
- field_name="filename",
268
- field_value=filename
269
- )
270
-
271
- # Remove or replace unsafe characters
272
- sanitized = re.sub(r'[<>:"/\\|?*]', '_', filename)
273
-
274
- # Remove control characters
275
- sanitized = re.sub(r'[\x00-\x1f\x7f]', '', sanitized)
276
-
277
- # Trim whitespace
278
- sanitized = sanitized.strip()
279
-
280
- # Handle reserved Windows names
281
- reserved_windows = {
282
- 'CON', 'PRN', 'AUX', 'NUL',
283
- 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
284
- 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
285
- }
286
-
287
- name_part = sanitized.split('.')[0].upper()
288
- if name_part in reserved_windows:
289
- sanitized = f"_{sanitized}"
290
-
291
- # Ensure not empty after sanitization
292
- if not sanitized:
293
- raise ValidationError(
294
- "Filename becomes empty after sanitization",
295
- validation_type="filename",
296
- field_name="filename",
297
- field_value=filename
298
- )
299
-
300
- # Check length
301
- if len(sanitized) > max_length:
302
- # Try to preserve extension
303
- if '.' in sanitized:
304
- name, ext = sanitized.rsplit('.', 1)
305
- max_name_length = max_length - len(ext) - 1
306
- if max_name_length > 0:
307
- sanitized = f"{name[:max_name_length]}.{ext}"
308
- else:
309
- sanitized = sanitized[:max_length]
310
- else:
311
- sanitized = sanitized[:max_length]
312
-
313
- return sanitized
314
-
315
-
316
- def validate_description(description: str, *, min_length: int = 10, max_length: int = 500) -> str:
317
- """Validate application description.
318
-
319
- Args:
320
- description: Description to validate
321
- min_length: Minimum description length
322
- max_length: Maximum description length
323
-
324
- Returns:
325
- Validated description
326
-
327
- Raises:
328
- ValidationError: If description is invalid
329
- """
330
- if not description:
331
- raise ValidationError(
332
- "Description cannot be empty",
333
- validation_type="description",
334
- field_name="description",
335
- field_value=description
336
- )
337
-
338
- # Normalize whitespace
339
- normalized = ' '.join(description.split())
340
-
341
- # Check length
342
- if len(normalized) < min_length:
343
- raise ValidationError(
344
- f"Description must be at least {min_length} characters long",
345
- validation_type="description",
346
- field_name="description",
347
- field_value=description
348
- )
349
-
350
- if len(normalized) > max_length:
351
- raise ValidationError(
352
- f"Description must be no more than {max_length} characters long",
353
- validation_type="description",
354
- field_name="description",
355
- field_value=description
356
- )
357
-
358
- return normalized
359
-
360
-
361
- def validate_existing_apps(
362
- app_name: str,
363
- existing_apps: List[str],
364
- *,
365
- case_sensitive: bool = False
366
- ) -> None:
367
- """Validate that app name doesn't conflict with existing apps.
368
-
369
- Args:
370
- app_name: New app name to check
371
- existing_apps: List of existing app names
372
- case_sensitive: Whether comparison should be case sensitive
373
-
374
- Raises:
375
- ValidationError: If app name conflicts with existing apps
376
- """
377
- if not case_sensitive:
378
- existing_lower = [app.lower() for app in existing_apps]
379
- if app_name.lower() in existing_lower:
380
- # Find the actual conflicting app name
381
- for existing_app in existing_apps:
382
- if existing_app.lower() == app_name.lower():
383
- raise ValidationError(
384
- f"App '{app_name}' conflicts with existing app '{existing_app}'",
385
- validation_type="app_name_conflict",
386
- field_name="app_name",
387
- field_value=app_name
388
- )
389
- else:
390
- if app_name in existing_apps:
391
- raise ValidationError(
392
- f"App '{app_name}' already exists",
393
- validation_type="app_name_conflict",
394
- field_name="app_name",
395
- field_value=app_name
396
- )
397
-
398
-
399
- def validate_python_code(code: str, *, filename: str = "<generated>") -> Tuple[bool, Optional[str]]:
400
- """Validate Python code syntax.
401
-
402
- Args:
403
- code: Python code to validate
404
- filename: Filename for error reporting
405
-
406
- Returns:
407
- Tuple of (is_valid, error_message)
408
- """
409
- try:
410
- compile(code, filename, 'exec')
411
- return True, None
412
- except SyntaxError as e:
413
- error_msg = f"Syntax error in {filename} at line {e.lineno}: {e.msg}"
414
- return False, error_msg
415
- except Exception as e:
416
- error_msg = f"Compilation error in {filename}: {e}"
417
- return False, error_msg