django-cfg 1.3.9__py3-none-any.whl → 1.3.11__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 (187) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/payments/admin/networks_admin.py +12 -1
  3. django_cfg/apps/payments/admin/payments_admin.py +13 -0
  4. django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +62 -14
  5. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
  6. django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
  7. django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
  8. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
  9. django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
  10. django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
  11. django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
  12. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +33 -3
  13. django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
  14. django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +96 -45
  15. django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
  16. django_cfg/apps/payments/config/__init__.py +14 -15
  17. django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
  18. django_cfg/apps/payments/config/helpers.py +8 -13
  19. django_cfg/apps/payments/migrations/0001_initial.py +33 -46
  20. django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
  21. django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
  22. django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
  23. django_cfg/apps/payments/models/payments.py +94 -0
  24. django_cfg/apps/payments/services/core/base.py +4 -4
  25. django_cfg/apps/payments/services/core/payment_service.py +265 -38
  26. django_cfg/apps/payments/services/providers/base.py +209 -3
  27. django_cfg/apps/payments/services/providers/models/__init__.py +2 -0
  28. django_cfg/apps/payments/services/providers/models/base.py +25 -2
  29. django_cfg/apps/payments/services/providers/nowpayments/models.py +2 -2
  30. django_cfg/apps/payments/services/providers/nowpayments/provider.py +57 -9
  31. django_cfg/apps/payments/services/providers/registry.py +5 -5
  32. django_cfg/apps/payments/services/types/requests.py +19 -7
  33. django_cfg/apps/payments/signals/payment_signals.py +31 -2
  34. django_cfg/apps/payments/static/payments/js/api-client.js +6 -1
  35. django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
  36. django_cfg/apps/payments/static/payments/js/payment-form.js +35 -26
  37. django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
  38. django_cfg/apps/payments/urls.py +3 -2
  39. django_cfg/apps/payments/views/api/currencies.py +3 -0
  40. django_cfg/apps/payments/views/serializers/currencies.py +18 -5
  41. django_cfg/apps/tasks/admin/tasks_admin.py +2 -2
  42. django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
  43. django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
  44. django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
  45. django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
  46. django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
  47. django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
  48. django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
  49. django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
  50. django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
  51. django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
  52. django_cfg/apps/tasks/tasks/__init__.py +10 -0
  53. django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
  54. django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
  55. django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
  56. django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
  57. django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
  58. django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
  59. django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
  60. django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
  61. django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
  62. django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
  63. django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
  64. django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
  65. django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
  66. django_cfg/apps/tasks/urls.py +2 -2
  67. django_cfg/apps/tasks/urls_admin.py +2 -2
  68. django_cfg/apps/tasks/utils/__init__.py +1 -0
  69. django_cfg/apps/tasks/utils/simulator.py +356 -0
  70. django_cfg/apps/tasks/views/__init__.py +16 -0
  71. django_cfg/apps/tasks/views/api.py +569 -0
  72. django_cfg/apps/tasks/views/dashboard.py +58 -0
  73. django_cfg/core/integration/__init__.py +21 -0
  74. django_cfg/management/commands/rundramatiq_simulator.py +430 -0
  75. django_cfg/models/constance.py +0 -11
  76. django_cfg/models/payments.py +137 -3
  77. django_cfg/modules/django_tasks.py +54 -21
  78. django_cfg/registry/core.py +4 -9
  79. django_cfg/template_archive/django_sample.zip +0 -0
  80. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -2
  81. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/RECORD +84 -152
  82. django_cfg/apps/payments/config/constance/__init__.py +0 -22
  83. django_cfg/apps/payments/config/constance/config_service.py +0 -123
  84. django_cfg/apps/payments/config/constance/fields.py +0 -69
  85. django_cfg/apps/payments/config/constance/settings.py +0 -160
  86. django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +0 -26
  87. django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +0 -28
  88. django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +0 -30
  89. django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
  90. django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
  91. django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
  92. django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
  93. django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
  94. django_cfg/apps/tasks/templates/tasks/base.html +0 -96
  95. django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
  96. django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
  97. django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
  98. django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
  99. django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
  100. django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
  101. django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
  102. django_cfg/apps/tasks/views.py +0 -461
  103. django_cfg/management/commands/app_agent_diagnose.py +0 -470
  104. django_cfg/management/commands/app_agent_generate.py +0 -342
  105. django_cfg/management/commands/app_agent_info.py +0 -308
  106. django_cfg/management/commands/auto_generate.py +0 -486
  107. django_cfg/modules/django_app_agent/__init__.py +0 -87
  108. django_cfg/modules/django_app_agent/agents/__init__.py +0 -40
  109. django_cfg/modules/django_app_agent/agents/base/__init__.py +0 -24
  110. django_cfg/modules/django_app_agent/agents/base/agent.py +0 -354
  111. django_cfg/modules/django_app_agent/agents/base/context.py +0 -236
  112. django_cfg/modules/django_app_agent/agents/base/executor.py +0 -430
  113. django_cfg/modules/django_app_agent/agents/generation/__init__.py +0 -12
  114. django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +0 -15
  115. django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +0 -147
  116. django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +0 -99
  117. django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +0 -32
  118. django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +0 -290
  119. django_cfg/modules/django_app_agent/agents/interfaces.py +0 -376
  120. django_cfg/modules/django_app_agent/core/__init__.py +0 -33
  121. django_cfg/modules/django_app_agent/core/config.py +0 -300
  122. django_cfg/modules/django_app_agent/core/exceptions.py +0 -359
  123. django_cfg/modules/django_app_agent/models/__init__.py +0 -71
  124. django_cfg/modules/django_app_agent/models/base.py +0 -283
  125. django_cfg/modules/django_app_agent/models/context.py +0 -496
  126. django_cfg/modules/django_app_agent/models/enums.py +0 -481
  127. django_cfg/modules/django_app_agent/models/requests.py +0 -500
  128. django_cfg/modules/django_app_agent/models/responses.py +0 -585
  129. django_cfg/modules/django_app_agent/pytest.ini +0 -6
  130. django_cfg/modules/django_app_agent/services/__init__.py +0 -42
  131. django_cfg/modules/django_app_agent/services/app_generator/__init__.py +0 -30
  132. django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +0 -133
  133. django_cfg/modules/django_app_agent/services/app_generator/context.py +0 -40
  134. django_cfg/modules/django_app_agent/services/app_generator/main.py +0 -202
  135. django_cfg/modules/django_app_agent/services/app_generator/structure.py +0 -316
  136. django_cfg/modules/django_app_agent/services/app_generator/validation.py +0 -125
  137. django_cfg/modules/django_app_agent/services/base.py +0 -437
  138. django_cfg/modules/django_app_agent/services/context_builder/__init__.py +0 -34
  139. django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +0 -141
  140. django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +0 -276
  141. django_cfg/modules/django_app_agent/services/context_builder/main.py +0 -272
  142. django_cfg/modules/django_app_agent/services/context_builder/models.py +0 -40
  143. django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +0 -85
  144. django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +0 -31
  145. django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +0 -311
  146. django_cfg/modules/django_app_agent/services/project_scanner/main.py +0 -221
  147. django_cfg/modules/django_app_agent/services/project_scanner/models.py +0 -59
  148. django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +0 -94
  149. django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +0 -28
  150. django_cfg/modules/django_app_agent/services/questioning_service/main.py +0 -273
  151. django_cfg/modules/django_app_agent/services/questioning_service/models.py +0 -111
  152. django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +0 -251
  153. django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +0 -347
  154. django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +0 -356
  155. django_cfg/modules/django_app_agent/services/report_service.py +0 -332
  156. django_cfg/modules/django_app_agent/services/template_manager/__init__.py +0 -18
  157. django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +0 -236
  158. django_cfg/modules/django_app_agent/services/template_manager/main.py +0 -159
  159. django_cfg/modules/django_app_agent/services/template_manager/models.py +0 -36
  160. django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +0 -100
  161. django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +0 -105
  162. django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +0 -31
  163. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +0 -44
  164. django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +0 -81
  165. django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +0 -107
  166. django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +0 -139
  167. django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +0 -91
  168. django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +0 -195
  169. django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +0 -35
  170. django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +0 -211
  171. django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +0 -200
  172. django_cfg/modules/django_app_agent/services/validation_service/__init__.py +0 -25
  173. django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +0 -333
  174. django_cfg/modules/django_app_agent/services/validation_service/main.py +0 -242
  175. django_cfg/modules/django_app_agent/services/validation_service/models.py +0 -66
  176. django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +0 -352
  177. django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +0 -272
  178. django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +0 -203
  179. django_cfg/modules/django_app_agent/ui/__init__.py +0 -25
  180. django_cfg/modules/django_app_agent/ui/cli.py +0 -419
  181. django_cfg/modules/django_app_agent/ui/rich_components.py +0 -622
  182. django_cfg/modules/django_app_agent/utils/__init__.py +0 -38
  183. django_cfg/modules/django_app_agent/utils/logging.py +0 -360
  184. django_cfg/modules/django_app_agent/utils/validation.py +0 -417
  185. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
  186. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
  187. {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.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