createsonline 0.1.26__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.
- createsonline/__init__.py +46 -0
- createsonline/admin/__init__.py +7 -0
- createsonline/admin/content.py +526 -0
- createsonline/admin/crud.py +805 -0
- createsonline/admin/field_builder.py +559 -0
- createsonline/admin/integration.py +482 -0
- createsonline/admin/interface.py +2562 -0
- createsonline/admin/model_creator.py +513 -0
- createsonline/admin/model_manager.py +388 -0
- createsonline/admin/modern_dashboard.py +498 -0
- createsonline/admin/permissions.py +264 -0
- createsonline/admin/user_forms.py +594 -0
- createsonline/ai/__init__.py +202 -0
- createsonline/ai/fields.py +1226 -0
- createsonline/ai/orm.py +325 -0
- createsonline/ai/services.py +1244 -0
- createsonline/app.py +506 -0
- createsonline/auth/__init__.py +8 -0
- createsonline/auth/management.py +228 -0
- createsonline/auth/models.py +552 -0
- createsonline/cli/__init__.py +5 -0
- createsonline/cli/commands/__init__.py +122 -0
- createsonline/cli/commands/database.py +416 -0
- createsonline/cli/commands/info.py +173 -0
- createsonline/cli/commands/initdb.py +218 -0
- createsonline/cli/commands/project.py +545 -0
- createsonline/cli/commands/serve.py +173 -0
- createsonline/cli/commands/shell.py +93 -0
- createsonline/cli/commands/users.py +148 -0
- createsonline/cli/main.py +2041 -0
- createsonline/cli/manage.py +274 -0
- createsonline/config/__init__.py +9 -0
- createsonline/config/app.py +2577 -0
- createsonline/config/database.py +179 -0
- createsonline/config/docs.py +384 -0
- createsonline/config/errors.py +160 -0
- createsonline/config/orm.py +43 -0
- createsonline/config/request.py +93 -0
- createsonline/config/settings.py +176 -0
- createsonline/data/__init__.py +23 -0
- createsonline/data/dataframe.py +925 -0
- createsonline/data/io.py +453 -0
- createsonline/data/series.py +557 -0
- createsonline/database/__init__.py +60 -0
- createsonline/database/abstraction.py +440 -0
- createsonline/database/assistant.py +585 -0
- createsonline/database/fields.py +442 -0
- createsonline/database/migrations.py +132 -0
- createsonline/database/models.py +604 -0
- createsonline/database.py +438 -0
- createsonline/http/__init__.py +28 -0
- createsonline/http/client.py +535 -0
- createsonline/ml/__init__.py +55 -0
- createsonline/ml/classification.py +552 -0
- createsonline/ml/clustering.py +680 -0
- createsonline/ml/metrics.py +542 -0
- createsonline/ml/neural.py +560 -0
- createsonline/ml/preprocessing.py +784 -0
- createsonline/ml/regression.py +501 -0
- createsonline/performance/__init__.py +19 -0
- createsonline/performance/cache.py +444 -0
- createsonline/performance/compression.py +335 -0
- createsonline/performance/core.py +419 -0
- createsonline/project_init.py +789 -0
- createsonline/routing.py +528 -0
- createsonline/security/__init__.py +34 -0
- createsonline/security/core.py +811 -0
- createsonline/security/encryption.py +349 -0
- createsonline/server.py +295 -0
- createsonline/static/css/admin.css +263 -0
- createsonline/static/css/common.css +358 -0
- createsonline/static/css/dashboard.css +89 -0
- createsonline/static/favicon.ico +0 -0
- createsonline/static/icons/icon-128x128.png +0 -0
- createsonline/static/icons/icon-128x128.webp +0 -0
- createsonline/static/icons/icon-16x16.png +0 -0
- createsonline/static/icons/icon-16x16.webp +0 -0
- createsonline/static/icons/icon-180x180.png +0 -0
- createsonline/static/icons/icon-180x180.webp +0 -0
- createsonline/static/icons/icon-192x192.png +0 -0
- createsonline/static/icons/icon-192x192.webp +0 -0
- createsonline/static/icons/icon-256x256.png +0 -0
- createsonline/static/icons/icon-256x256.webp +0 -0
- createsonline/static/icons/icon-32x32.png +0 -0
- createsonline/static/icons/icon-32x32.webp +0 -0
- createsonline/static/icons/icon-384x384.png +0 -0
- createsonline/static/icons/icon-384x384.webp +0 -0
- createsonline/static/icons/icon-48x48.png +0 -0
- createsonline/static/icons/icon-48x48.webp +0 -0
- createsonline/static/icons/icon-512x512.png +0 -0
- createsonline/static/icons/icon-512x512.webp +0 -0
- createsonline/static/icons/icon-64x64.png +0 -0
- createsonline/static/icons/icon-64x64.webp +0 -0
- createsonline/static/image/android-chrome-192x192.png +0 -0
- createsonline/static/image/android-chrome-512x512.png +0 -0
- createsonline/static/image/apple-touch-icon.png +0 -0
- createsonline/static/image/favicon-16x16.png +0 -0
- createsonline/static/image/favicon-32x32.png +0 -0
- createsonline/static/image/favicon.ico +0 -0
- createsonline/static/image/favicon.svg +17 -0
- createsonline/static/image/icon-128x128.png +0 -0
- createsonline/static/image/icon-128x128.webp +0 -0
- createsonline/static/image/icon-16x16.png +0 -0
- createsonline/static/image/icon-16x16.webp +0 -0
- createsonline/static/image/icon-180x180.png +0 -0
- createsonline/static/image/icon-180x180.webp +0 -0
- createsonline/static/image/icon-192x192.png +0 -0
- createsonline/static/image/icon-192x192.webp +0 -0
- createsonline/static/image/icon-256x256.png +0 -0
- createsonline/static/image/icon-256x256.webp +0 -0
- createsonline/static/image/icon-32x32.png +0 -0
- createsonline/static/image/icon-32x32.webp +0 -0
- createsonline/static/image/icon-384x384.png +0 -0
- createsonline/static/image/icon-384x384.webp +0 -0
- createsonline/static/image/icon-48x48.png +0 -0
- createsonline/static/image/icon-48x48.webp +0 -0
- createsonline/static/image/icon-512x512.png +0 -0
- createsonline/static/image/icon-512x512.webp +0 -0
- createsonline/static/image/icon-64x64.png +0 -0
- createsonline/static/image/icon-64x64.webp +0 -0
- createsonline/static/image/logo-header-h100.png +0 -0
- createsonline/static/image/logo-header-h100.webp +0 -0
- createsonline/static/image/logo-header-h200@2x.png +0 -0
- createsonline/static/image/logo-header-h200@2x.webp +0 -0
- createsonline/static/image/logo.png +0 -0
- createsonline/static/js/admin.js +274 -0
- createsonline/static/site.webmanifest +35 -0
- createsonline/static/templates/admin/base.html +87 -0
- createsonline/static/templates/admin/dashboard.html +217 -0
- createsonline/static/templates/admin/model_form.html +270 -0
- createsonline/static/templates/admin/model_list.html +202 -0
- createsonline/static/test_script.js +15 -0
- createsonline/static/test_styles.css +59 -0
- createsonline/static_files.py +365 -0
- createsonline/templates/404.html +100 -0
- createsonline/templates/admin_login.html +169 -0
- createsonline/templates/base.html +102 -0
- createsonline/templates/index.html +151 -0
- createsonline/templates.py +205 -0
- createsonline/testing.py +322 -0
- createsonline/utils.py +448 -0
- createsonline/validation/__init__.py +49 -0
- createsonline/validation/fields.py +598 -0
- createsonline/validation/models.py +504 -0
- createsonline/validation/validators.py +561 -0
- createsonline/views.py +184 -0
- createsonline-0.1.26.dist-info/METADATA +46 -0
- createsonline-0.1.26.dist-info/RECORD +152 -0
- createsonline-0.1.26.dist-info/WHEEL +5 -0
- createsonline-0.1.26.dist-info/entry_points.txt +2 -0
- createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
- createsonline-0.1.26.dist-info/top_level.txt +1 -0
createsonline/utils.py
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# createsonline/utils.py
|
|
2
|
+
"""
|
|
3
|
+
CREATESONLINE Shared Utilities Module
|
|
4
|
+
|
|
5
|
+
Common functions and utilities used across the framework.
|
|
6
|
+
Consolidates duplicate code patterns identified in the codebase.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import hashlib
|
|
11
|
+
import secrets
|
|
12
|
+
import logging
|
|
13
|
+
from datetime import datetime, timedelta
|
|
14
|
+
from typing import Dict, Any, List, Union, Callable
|
|
15
|
+
|
|
16
|
+
# Setup logging
|
|
17
|
+
logger = logging.getLogger("createsonline.utils")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Common Import Error Handler
|
|
21
|
+
def get_import_error(module_name: str, feature_name: str = None) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Standardized import error message for optional dependencies.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
module_name: Name of the missing module
|
|
27
|
+
feature_name: Optional feature name that requires the module
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Formatted error message
|
|
31
|
+
"""
|
|
32
|
+
if feature_name:
|
|
33
|
+
return f"❌ {feature_name} requires {module_name}. Install with: pip install {module_name}"
|
|
34
|
+
return f"❌ Module '{module_name}' not found. Install with: pip install {module_name}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Base Info Pattern
|
|
38
|
+
class BaseInfoProvider:
|
|
39
|
+
"""Base class for standardized info dictionary patterns"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, name: str, version: str = "1.0.0"):
|
|
42
|
+
self.name = name
|
|
43
|
+
self.version = version
|
|
44
|
+
self.available = True
|
|
45
|
+
self.features = []
|
|
46
|
+
self.dependencies = []
|
|
47
|
+
|
|
48
|
+
def get_info(self) -> Dict[str, Any]:
|
|
49
|
+
"""Get standardized info dictionary"""
|
|
50
|
+
return {
|
|
51
|
+
'name': self.name,
|
|
52
|
+
'version': self.version,
|
|
53
|
+
'available': self.available,
|
|
54
|
+
'features': self.features,
|
|
55
|
+
'dependencies': self.dependencies,
|
|
56
|
+
'timestamp': datetime.utcnow().isoformat(),
|
|
57
|
+
'framework': 'CREATESONLINE'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def add_feature(self, feature: str, description: str = None):
|
|
61
|
+
"""Add a feature to the info"""
|
|
62
|
+
self.features.append({
|
|
63
|
+
'name': feature,
|
|
64
|
+
'description': description or f"Feature: {feature}",
|
|
65
|
+
'enabled': True
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
def add_dependency(self, name: str, version: str = None, optional: bool = False):
|
|
69
|
+
"""Add a dependency to the info"""
|
|
70
|
+
self.dependencies.append({
|
|
71
|
+
'name': name,
|
|
72
|
+
'version': version,
|
|
73
|
+
'optional': optional,
|
|
74
|
+
'available': self._check_dependency(name)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
def _check_dependency(self, name: str) -> bool:
|
|
78
|
+
"""Check if dependency is available"""
|
|
79
|
+
try:
|
|
80
|
+
__import__(name)
|
|
81
|
+
return True
|
|
82
|
+
except ImportError:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Metrics Base Class
|
|
87
|
+
class BaseMetrics:
|
|
88
|
+
"""Base metrics collection mixin"""
|
|
89
|
+
|
|
90
|
+
def __init__(self):
|
|
91
|
+
self.requests = 0
|
|
92
|
+
self.errors = 0
|
|
93
|
+
self.start_time = datetime.utcnow()
|
|
94
|
+
self.last_activity = datetime.utcnow()
|
|
95
|
+
|
|
96
|
+
def increment_requests(self):
|
|
97
|
+
"""Increment request counter"""
|
|
98
|
+
self.requests += 1
|
|
99
|
+
self.last_activity = datetime.utcnow()
|
|
100
|
+
|
|
101
|
+
def increment_errors(self):
|
|
102
|
+
"""Increment error counter"""
|
|
103
|
+
self.errors += 1
|
|
104
|
+
self.last_activity = datetime.utcnow()
|
|
105
|
+
|
|
106
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
107
|
+
"""Get standardized stats dictionary"""
|
|
108
|
+
uptime = datetime.utcnow() - self.start_time
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
'requests': self.requests,
|
|
112
|
+
'errors': self.errors,
|
|
113
|
+
'uptime_seconds': int(uptime.total_seconds()),
|
|
114
|
+
'uptime_human': self._format_uptime(uptime),
|
|
115
|
+
'last_activity': self.last_activity.isoformat(),
|
|
116
|
+
'error_rate': (self.errors / max(self.requests, 1)) * 100,
|
|
117
|
+
'status': 'healthy' if self.errors == 0 else 'degraded'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def _format_uptime(self, uptime: timedelta) -> str:
|
|
121
|
+
"""Format uptime in human readable format"""
|
|
122
|
+
days = uptime.days
|
|
123
|
+
hours = uptime.seconds // 3600
|
|
124
|
+
minutes = (uptime.seconds % 3600) // 60
|
|
125
|
+
|
|
126
|
+
if days > 0:
|
|
127
|
+
return f"{days}d {hours}h {minutes}m"
|
|
128
|
+
elif hours > 0:
|
|
129
|
+
return f"{hours}h {minutes}m"
|
|
130
|
+
else:
|
|
131
|
+
return f"{minutes}m"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Response Formatter
|
|
135
|
+
class ResponseFormatter:
|
|
136
|
+
"""Standardized response formatting"""
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def success(data: Any = None, message: str = "Success") -> Dict[str, Any]:
|
|
140
|
+
"""Format success response"""
|
|
141
|
+
return {
|
|
142
|
+
'status': 'success',
|
|
143
|
+
'message': message,
|
|
144
|
+
'data': data,
|
|
145
|
+
'timestamp': datetime.utcnow().isoformat(),
|
|
146
|
+
'framework': 'CREATESONLINE'
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
def error(message: str, code: int = 500, details: Any = None) -> Dict[str, Any]:
|
|
151
|
+
"""Format error response"""
|
|
152
|
+
return {
|
|
153
|
+
'status': 'error',
|
|
154
|
+
'message': message,
|
|
155
|
+
'code': code,
|
|
156
|
+
'details': details,
|
|
157
|
+
'timestamp': datetime.utcnow().isoformat(),
|
|
158
|
+
'framework': 'CREATESONLINE'
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def paginated(data: List[Any], page: int, per_page: int, total: int) -> Dict[str, Any]:
|
|
163
|
+
"""Format paginated response"""
|
|
164
|
+
return {
|
|
165
|
+
'status': 'success',
|
|
166
|
+
'data': data,
|
|
167
|
+
'pagination': {
|
|
168
|
+
'page': page,
|
|
169
|
+
'per_page': per_page,
|
|
170
|
+
'total': total,
|
|
171
|
+
'pages': (total + per_page - 1) // per_page,
|
|
172
|
+
'has_next': page * per_page < total,
|
|
173
|
+
'has_prev': page > 1
|
|
174
|
+
},
|
|
175
|
+
'timestamp': datetime.utcnow().isoformat(),
|
|
176
|
+
'framework': 'CREATESONLINE'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# Security Utilities
|
|
181
|
+
class SecurityUtils:
|
|
182
|
+
"""Common security utilities"""
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def generate_token(length: int = 32) -> str:
|
|
186
|
+
"""Generate secure random token"""
|
|
187
|
+
return secrets.token_urlsafe(length)
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def hash_password(password: str, salt: str = None) -> Dict[str, str]:
|
|
191
|
+
"""
|
|
192
|
+
Hash password with salt - recommends bcrypt for production.
|
|
193
|
+
|
|
194
|
+
IMPORTANT: SHA-256 fallback is for development only!
|
|
195
|
+
For production use, install bcrypt: pip install bcrypt
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
password: Plain text password to hash
|
|
199
|
+
salt: Optional salt (used only for SHA-256 fallback, ignored for bcrypt)
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
Dict with 'hash', 'salt', and 'method' keys
|
|
203
|
+
"""
|
|
204
|
+
if salt is None:
|
|
205
|
+
salt = secrets.token_hex(16)
|
|
206
|
+
|
|
207
|
+
# Try to use bcrypt if available, fallback to SHA-256
|
|
208
|
+
try:
|
|
209
|
+
import bcrypt
|
|
210
|
+
# bcrypt handles salting internally
|
|
211
|
+
password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
|
|
212
|
+
return {
|
|
213
|
+
'hash': password_hash,
|
|
214
|
+
'salt': '', # bcrypt includes salt in hash
|
|
215
|
+
'method': 'bcrypt'
|
|
216
|
+
}
|
|
217
|
+
except ImportError:
|
|
218
|
+
# WARNING: SHA-256 fallback is for development only!
|
|
219
|
+
# For production use, install bcrypt: pip install bcrypt
|
|
220
|
+
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
|
221
|
+
return {
|
|
222
|
+
'hash': password_hash,
|
|
223
|
+
'salt': '', # Empty salt for consistency with verify_password fallback
|
|
224
|
+
'method': 'sha256'
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def verify_password(password: str, stored_hash: str, salt: str = None) -> bool:
|
|
229
|
+
"""Verify password against stored hash (consolidated implementation)"""
|
|
230
|
+
# Try bcrypt first if hash looks like bcrypt format
|
|
231
|
+
if stored_hash.startswith(('$2a$', '$2b$', '$2y$')):
|
|
232
|
+
try:
|
|
233
|
+
import bcrypt
|
|
234
|
+
return bcrypt.checkpw(password.encode('utf-8'), stored_hash.encode('utf-8'))
|
|
235
|
+
except ImportError:
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
# Fallback to SHA-256 verification
|
|
239
|
+
if salt:
|
|
240
|
+
# Salted hash verification
|
|
241
|
+
computed_hash = hashlib.sha256((password + salt).encode()).hexdigest()
|
|
242
|
+
return secrets.compare_digest(computed_hash, stored_hash)
|
|
243
|
+
else:
|
|
244
|
+
# Simple hash verification (for backward compatibility)
|
|
245
|
+
computed_hash = hashlib.sha256(password.encode()).hexdigest()
|
|
246
|
+
return secrets.compare_digest(computed_hash, stored_hash)
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def sanitize_input(value: str, max_length: int = 1000, escape_html: bool = False) -> str:
|
|
250
|
+
"""Enhanced input sanitization with optional HTML escaping"""
|
|
251
|
+
if not isinstance(value, str):
|
|
252
|
+
value = str(value)
|
|
253
|
+
|
|
254
|
+
# Remove null bytes and limit length
|
|
255
|
+
value = value.replace('\x00', '').strip()[:max_length]
|
|
256
|
+
|
|
257
|
+
# Optional HTML escaping for template safety
|
|
258
|
+
if escape_html:
|
|
259
|
+
# Basic HTML entity escaping
|
|
260
|
+
value = (value
|
|
261
|
+
.replace('&', '&')
|
|
262
|
+
.replace('<', '<')
|
|
263
|
+
.replace('>', '>')
|
|
264
|
+
.replace('"', '"')
|
|
265
|
+
.replace("'", '''))
|
|
266
|
+
|
|
267
|
+
return value
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# File Utilities
|
|
271
|
+
class FileUtils:
|
|
272
|
+
"""Common file operations"""
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def ensure_directory(path: str) -> bool:
|
|
276
|
+
"""Ensure directory exists, create if needed"""
|
|
277
|
+
try:
|
|
278
|
+
os.makedirs(path, exist_ok=True)
|
|
279
|
+
return True
|
|
280
|
+
except Exception as e:
|
|
281
|
+
logger.error(f"Failed to create directory {path}: {e}")
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
@staticmethod
|
|
285
|
+
def safe_filename(filename: str) -> str:
|
|
286
|
+
"""Generate safe filename"""
|
|
287
|
+
# Remove dangerous characters
|
|
288
|
+
safe_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_."
|
|
289
|
+
safe_filename = ''.join(c for c in filename if c in safe_chars)
|
|
290
|
+
|
|
291
|
+
# Limit length and ensure it's not empty
|
|
292
|
+
safe_filename = safe_filename[:100]
|
|
293
|
+
if not safe_filename:
|
|
294
|
+
safe_filename = f"file_{secrets.token_hex(8)}"
|
|
295
|
+
|
|
296
|
+
return safe_filename
|
|
297
|
+
|
|
298
|
+
@staticmethod
|
|
299
|
+
def get_file_info(path: str, include_error_details: bool = False) -> Dict[str, Any]:
|
|
300
|
+
"""Get file information with optional error details for debugging"""
|
|
301
|
+
try:
|
|
302
|
+
stat = os.stat(path)
|
|
303
|
+
return {
|
|
304
|
+
'path': path,
|
|
305
|
+
'size': stat.st_size,
|
|
306
|
+
'modified': datetime.fromtimestamp(stat.st_mtime).isoformat(),
|
|
307
|
+
'created': datetime.fromtimestamp(stat.st_ctime).isoformat(),
|
|
308
|
+
'is_file': os.path.isfile(path),
|
|
309
|
+
'is_dir': os.path.isdir(path),
|
|
310
|
+
'exists': True
|
|
311
|
+
}
|
|
312
|
+
except Exception as e:
|
|
313
|
+
result = {
|
|
314
|
+
'path': path,
|
|
315
|
+
'exists': False
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# Include error details in development/debug mode
|
|
319
|
+
if include_error_details:
|
|
320
|
+
result['error'] = str(e)
|
|
321
|
+
result['error_type'] = type(e).__name__
|
|
322
|
+
|
|
323
|
+
return result
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# Validation Utilities
|
|
327
|
+
class ValidationUtils:
|
|
328
|
+
"""Common validation functions"""
|
|
329
|
+
|
|
330
|
+
@staticmethod
|
|
331
|
+
def is_valid_email(email: str) -> bool:
|
|
332
|
+
"""Basic email validation"""
|
|
333
|
+
import re
|
|
334
|
+
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
335
|
+
return bool(re.match(pattern, email))
|
|
336
|
+
|
|
337
|
+
@staticmethod
|
|
338
|
+
def is_strong_password(password: str) -> Dict[str, Union[bool, List[str]]]:
|
|
339
|
+
"""Check password strength"""
|
|
340
|
+
issues = []
|
|
341
|
+
|
|
342
|
+
if len(password) < 8:
|
|
343
|
+
issues.append("Password must be at least 8 characters long")
|
|
344
|
+
|
|
345
|
+
if not any(c.isupper() for c in password):
|
|
346
|
+
issues.append("Password must contain at least one uppercase letter")
|
|
347
|
+
|
|
348
|
+
if not any(c.islower() for c in password):
|
|
349
|
+
issues.append("Password must contain at least one lowercase letter")
|
|
350
|
+
|
|
351
|
+
if not any(c.isdigit() for c in password):
|
|
352
|
+
issues.append("Password must contain at least one number")
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
'is_strong': len(issues) == 0,
|
|
356
|
+
'issues': issues
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def validate_required_fields(data: Dict[str, Any], required_fields: List[str]) -> Dict[str, Union[bool, List[str]]]:
|
|
361
|
+
"""Validate required fields in data"""
|
|
362
|
+
missing = []
|
|
363
|
+
|
|
364
|
+
for field in required_fields:
|
|
365
|
+
if field not in data or data[field] is None or data[field] == '':
|
|
366
|
+
missing.append(field)
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
'is_valid': len(missing) == 0,
|
|
370
|
+
'missing_fields': missing
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
# Environment Utilities
|
|
375
|
+
class EnvUtils:
|
|
376
|
+
"""Environment variable utilities"""
|
|
377
|
+
|
|
378
|
+
@staticmethod
|
|
379
|
+
def get_env_bool(key: str, default: bool = False) -> bool:
|
|
380
|
+
"""Get boolean from environment variable"""
|
|
381
|
+
value = os.getenv(key, str(default)).lower()
|
|
382
|
+
return value in ('true', '1', 'yes', 'on')
|
|
383
|
+
|
|
384
|
+
@staticmethod
|
|
385
|
+
def get_env_int(key: str, default: int = 0) -> int:
|
|
386
|
+
"""Get integer from environment variable"""
|
|
387
|
+
try:
|
|
388
|
+
return int(os.getenv(key, default))
|
|
389
|
+
except (ValueError, TypeError):
|
|
390
|
+
return default
|
|
391
|
+
|
|
392
|
+
@staticmethod
|
|
393
|
+
def get_env_list(key: str, default: List[str] = None, separator: str = ',') -> List[str]:
|
|
394
|
+
"""Get list from environment variable"""
|
|
395
|
+
value = os.getenv(key)
|
|
396
|
+
if not value:
|
|
397
|
+
return default or []
|
|
398
|
+
|
|
399
|
+
return [item.strip() for item in value.split(separator) if item.strip()]
|
|
400
|
+
|
|
401
|
+
@staticmethod
|
|
402
|
+
def get_database_config() -> Dict[str, Any]:
|
|
403
|
+
"""Get database configuration from environment"""
|
|
404
|
+
return {
|
|
405
|
+
'url': os.getenv('DATABASE_URL', 'sqlite:///createsonline.db'),
|
|
406
|
+
'pool_size': EnvUtils.get_env_int('DATABASE_POOL_SIZE', 5),
|
|
407
|
+
'max_overflow': EnvUtils.get_env_int('DATABASE_MAX_OVERFLOW', 10),
|
|
408
|
+
'echo': EnvUtils.get_env_bool('DATABASE_ECHO', False),
|
|
409
|
+
'auto_create_tables': EnvUtils.get_env_bool('DATABASE_AUTO_CREATE', True)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
# Decorator Utilities
|
|
414
|
+
def retry(max_attempts: int = 3, delay: float = 1.0, backoff: float = 2.0):
|
|
415
|
+
"""Retry decorator with exponential backoff"""
|
|
416
|
+
def decorator(func: Callable) -> Callable:
|
|
417
|
+
def wrapper(*args, **kwargs):
|
|
418
|
+
for attempt in range(max_attempts):
|
|
419
|
+
try:
|
|
420
|
+
return func(*args, **kwargs)
|
|
421
|
+
except Exception as e:
|
|
422
|
+
if attempt == max_attempts - 1:
|
|
423
|
+
raise e
|
|
424
|
+
|
|
425
|
+
import time
|
|
426
|
+
time.sleep(delay * (backoff ** attempt))
|
|
427
|
+
|
|
428
|
+
return wrapper
|
|
429
|
+
return decorator
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def timing(func: Callable) -> Callable:
|
|
433
|
+
"""Timing decorator to measure function execution time"""
|
|
434
|
+
def wrapper(*args, **kwargs):
|
|
435
|
+
start_time = datetime.utcnow()
|
|
436
|
+
try:
|
|
437
|
+
result = func(*args, **kwargs)
|
|
438
|
+
end_time = datetime.utcnow()
|
|
439
|
+
duration = (end_time - start_time).total_seconds()
|
|
440
|
+
logger.debug(f"{func.__name__} executed in {duration:.3f}s")
|
|
441
|
+
return result
|
|
442
|
+
except Exception as e:
|
|
443
|
+
end_time = datetime.utcnow()
|
|
444
|
+
duration = (end_time - start_time).total_seconds()
|
|
445
|
+
logger.error(f"{func.__name__} failed after {duration:.3f}s: {e}")
|
|
446
|
+
raise
|
|
447
|
+
|
|
448
|
+
return wrapper
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Internal Validation System
|
|
3
|
+
|
|
4
|
+
Pure Python validation system with zero external dependencies.
|
|
5
|
+
Lightweight replacement for Pydantic with AI-native features.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .models import BaseModel, ValidationError
|
|
9
|
+
from .fields import (
|
|
10
|
+
Field, StringField, IntField, FloatField, BoolField,
|
|
11
|
+
EmailField, URLField, DateField, ListField, DictField,
|
|
12
|
+
OptionalField, ChoiceField
|
|
13
|
+
)
|
|
14
|
+
from .validators import (
|
|
15
|
+
validator, required, min_length, max_length,
|
|
16
|
+
min_value, max_value, regex_validator, email_validator,
|
|
17
|
+
url_validator
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
# Core classes
|
|
22
|
+
'BaseModel',
|
|
23
|
+
'ValidationError',
|
|
24
|
+
|
|
25
|
+
# Fields
|
|
26
|
+
'Field',
|
|
27
|
+
'StringField',
|
|
28
|
+
'IntField',
|
|
29
|
+
'FloatField',
|
|
30
|
+
'BoolField',
|
|
31
|
+
'EmailField',
|
|
32
|
+
'URLField',
|
|
33
|
+
'DateField',
|
|
34
|
+
'ListField',
|
|
35
|
+
'DictField',
|
|
36
|
+
'OptionalField',
|
|
37
|
+
'ChoiceField',
|
|
38
|
+
|
|
39
|
+
# Validators
|
|
40
|
+
'validator',
|
|
41
|
+
'required',
|
|
42
|
+
'min_length',
|
|
43
|
+
'max_length',
|
|
44
|
+
'min_value',
|
|
45
|
+
'max_value',
|
|
46
|
+
'regex_validator',
|
|
47
|
+
'email_validator',
|
|
48
|
+
'url_validator'
|
|
49
|
+
]
|