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/testing.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# createsonline/testing.py
|
|
2
|
+
"""
|
|
3
|
+
CREATESONLINE Testing Module
|
|
4
|
+
|
|
5
|
+
Provides test client and utilities for testing CREATESONLINE applications.
|
|
6
|
+
Pure Python implementation without external dependencies.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
from typing import Dict, Any, Union
|
|
13
|
+
from urllib.parse import urlencode, urlparse, parse_qs
|
|
14
|
+
|
|
15
|
+
# Setup logging
|
|
16
|
+
logger = logging.getLogger("createsonline.testing")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestResponse:
|
|
20
|
+
"""Test response object mimicking HTTP response"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, status_code: int, content: Union[str, bytes, dict], headers: Dict[str, str] = None):
|
|
23
|
+
self.status_code = status_code
|
|
24
|
+
self.headers = headers or {}
|
|
25
|
+
|
|
26
|
+
if isinstance(content, dict):
|
|
27
|
+
self._content = json.dumps(content).encode()
|
|
28
|
+
self.headers.setdefault('content-type', 'application/json')
|
|
29
|
+
elif isinstance(content, str):
|
|
30
|
+
self._content = content.encode()
|
|
31
|
+
self.headers.setdefault('content-type', 'text/plain')
|
|
32
|
+
else:
|
|
33
|
+
self._content = content
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def content(self) -> bytes:
|
|
37
|
+
return self._content
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def text(self) -> str:
|
|
41
|
+
return self._content.decode()
|
|
42
|
+
|
|
43
|
+
def json(self) -> Dict[str, Any]:
|
|
44
|
+
"""Parse response as JSON"""
|
|
45
|
+
try:
|
|
46
|
+
return json.loads(self.text)
|
|
47
|
+
except json.JSONDecodeError:
|
|
48
|
+
raise ValueError("Response is not valid JSON")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestClient:
|
|
52
|
+
"""
|
|
53
|
+
Test client for CREATESONLINE applications.
|
|
54
|
+
Provides methods to test HTTP endpoints without external dependencies.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, app):
|
|
58
|
+
self.app = app
|
|
59
|
+
self.base_url = "http://testserver"
|
|
60
|
+
self._loop = None
|
|
61
|
+
|
|
62
|
+
def _get_loop(self):
|
|
63
|
+
"""Get or create event loop for async operations"""
|
|
64
|
+
if self._loop is None or self._loop.is_closed():
|
|
65
|
+
try:
|
|
66
|
+
# Try to get existing loop
|
|
67
|
+
self._loop = asyncio.get_event_loop()
|
|
68
|
+
if self._loop.is_closed():
|
|
69
|
+
raise RuntimeError("Loop is closed")
|
|
70
|
+
except RuntimeError:
|
|
71
|
+
# Create new loop if none exists
|
|
72
|
+
self._loop = asyncio.new_event_loop()
|
|
73
|
+
asyncio.set_event_loop(self._loop)
|
|
74
|
+
return self._loop
|
|
75
|
+
|
|
76
|
+
def _build_request(self, method: str, url: str, **kwargs) -> Dict[str, Any]:
|
|
77
|
+
"""Build request object from parameters"""
|
|
78
|
+
# Parse URL
|
|
79
|
+
parsed = urlparse(url if url.startswith('http') else self.base_url + url)
|
|
80
|
+
|
|
81
|
+
request = {
|
|
82
|
+
'method': method.upper(),
|
|
83
|
+
'url': url,
|
|
84
|
+
'path': parsed.path,
|
|
85
|
+
'query_params': parse_qs(parsed.query),
|
|
86
|
+
'headers': kwargs.get('headers', {}),
|
|
87
|
+
'body': None
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Handle JSON data
|
|
91
|
+
if 'json' in kwargs:
|
|
92
|
+
request['body'] = json.dumps(kwargs['json'])
|
|
93
|
+
request['headers']['content-type'] = 'application/json'
|
|
94
|
+
|
|
95
|
+
# Handle form data
|
|
96
|
+
elif 'data' in kwargs:
|
|
97
|
+
if isinstance(kwargs['data'], dict):
|
|
98
|
+
request['body'] = urlencode(kwargs['data'])
|
|
99
|
+
request['headers']['content-type'] = 'application/x-www-form-urlencoded'
|
|
100
|
+
else:
|
|
101
|
+
request['body'] = kwargs['data']
|
|
102
|
+
|
|
103
|
+
# Handle files
|
|
104
|
+
elif 'files' in kwargs:
|
|
105
|
+
# Simple file handling (for testing purposes)
|
|
106
|
+
request['body'] = str(kwargs['files'])
|
|
107
|
+
request['headers']['content-type'] = 'multipart/form-data'
|
|
108
|
+
|
|
109
|
+
return request
|
|
110
|
+
|
|
111
|
+
async def _execute_request(self, request: Dict[str, Any]) -> TestResponse:
|
|
112
|
+
"""Execute request against the CREATESONLINE app"""
|
|
113
|
+
try:
|
|
114
|
+
# Find matching route in app
|
|
115
|
+
path = request['path']
|
|
116
|
+
method = request['method']
|
|
117
|
+
route_key = f"{method}:{path}"
|
|
118
|
+
|
|
119
|
+
# Check if app has routes (support both internal and full app)
|
|
120
|
+
routes = None
|
|
121
|
+
if hasattr(self.app, 'routes') and isinstance(self.app.routes, dict):
|
|
122
|
+
routes = self.app.routes
|
|
123
|
+
elif hasattr(self.app, '_internal_routes'):
|
|
124
|
+
routes = self.app._internal_routes
|
|
125
|
+
|
|
126
|
+
if routes and route_key in routes:
|
|
127
|
+
handler = routes[route_key]
|
|
128
|
+
|
|
129
|
+
# Create mock request object for handler
|
|
130
|
+
class MockRequest:
|
|
131
|
+
def __init__(self, method, path, query_params, headers, body):
|
|
132
|
+
self.method = method
|
|
133
|
+
self.path = path
|
|
134
|
+
self.query_params = query_params
|
|
135
|
+
self.headers = headers
|
|
136
|
+
self._body = body
|
|
137
|
+
|
|
138
|
+
async def json(self):
|
|
139
|
+
"""Async json method for handlers that await request.json()"""
|
|
140
|
+
if self._body and 'json' in self.headers.get('content-type', ''):
|
|
141
|
+
return json.loads(self._body)
|
|
142
|
+
return {}
|
|
143
|
+
|
|
144
|
+
mock_request = MockRequest(
|
|
145
|
+
method, path,
|
|
146
|
+
request.get('query_params', {}),
|
|
147
|
+
request.get('headers', {}),
|
|
148
|
+
request.get('body', '')
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Call the handler
|
|
152
|
+
if asyncio.iscoroutinefunction(handler):
|
|
153
|
+
result = await handler(mock_request)
|
|
154
|
+
else:
|
|
155
|
+
result = handler(mock_request)
|
|
156
|
+
|
|
157
|
+
# Handle different response types including (data, status) and (data, status, headers) tuples
|
|
158
|
+
if isinstance(result, tuple):
|
|
159
|
+
if len(result) == 3:
|
|
160
|
+
data, status, headers = result
|
|
161
|
+
return TestResponse(status, data, headers)
|
|
162
|
+
elif len(result) == 2:
|
|
163
|
+
data, status = result
|
|
164
|
+
return TestResponse(status, data)
|
|
165
|
+
elif isinstance(result, dict):
|
|
166
|
+
return TestResponse(200, result)
|
|
167
|
+
elif isinstance(result, (str, bytes)):
|
|
168
|
+
return TestResponse(200, result)
|
|
169
|
+
else:
|
|
170
|
+
return TestResponse(200, str(result))
|
|
171
|
+
else:
|
|
172
|
+
# Try fallback to app's HTTP handler if available
|
|
173
|
+
if hasattr(self.app, '_handle_http'):
|
|
174
|
+
# Parse URL to get query string
|
|
175
|
+
from urllib.parse import urlparse
|
|
176
|
+
parsed = urlparse(request.get('url', path))
|
|
177
|
+
query_string = parsed.query.encode() if parsed.query else b''
|
|
178
|
+
|
|
179
|
+
# Build ASGI scope for internal handler
|
|
180
|
+
scope = {
|
|
181
|
+
'type': 'http',
|
|
182
|
+
'method': method,
|
|
183
|
+
'path': path,
|
|
184
|
+
'query_string': query_string,
|
|
185
|
+
'headers': [(k.encode(), v.encode()) for k, v in request.get('headers', {}).items()]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Mock receive function
|
|
189
|
+
async def receive():
|
|
190
|
+
return {
|
|
191
|
+
'type': 'http.request',
|
|
192
|
+
'body': request['body'].encode() if request.get('body') else b'',
|
|
193
|
+
'more_body': False
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Mock send function to capture response
|
|
197
|
+
response_data = {'status': 200, 'body': b'', 'headers': []}
|
|
198
|
+
|
|
199
|
+
async def send(message):
|
|
200
|
+
if message['type'] == 'http.response.start':
|
|
201
|
+
response_data['status'] = message['status']
|
|
202
|
+
response_data['headers'] = message.get('headers', [])
|
|
203
|
+
elif message['type'] == 'http.response.body':
|
|
204
|
+
response_data['body'] += message.get('body', b'')
|
|
205
|
+
|
|
206
|
+
# Execute request through app (use __call__ for ASGI)
|
|
207
|
+
await self.app(scope, receive, send)
|
|
208
|
+
|
|
209
|
+
# Convert headers back to dict
|
|
210
|
+
headers = {k.decode(): v.decode() for k, v in response_data['headers']}
|
|
211
|
+
|
|
212
|
+
# Try to parse as JSON, fallback to text
|
|
213
|
+
body = response_data['body']
|
|
214
|
+
if headers.get('content-type', '').startswith('application/json'):
|
|
215
|
+
try:
|
|
216
|
+
content = json.loads(body.decode())
|
|
217
|
+
except:
|
|
218
|
+
content = body.decode()
|
|
219
|
+
else:
|
|
220
|
+
content = body.decode()
|
|
221
|
+
|
|
222
|
+
return TestResponse(response_data['status'], content, headers)
|
|
223
|
+
else:
|
|
224
|
+
return TestResponse(404, {"error": "Not found", "path": path, "method": method})
|
|
225
|
+
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.error(f"Test request error: {e}")
|
|
228
|
+
return TestResponse(500, {"error": f"Internal server error: {str(e)}"})
|
|
229
|
+
|
|
230
|
+
def request(self, method: str, url: str, **kwargs) -> TestResponse:
|
|
231
|
+
"""Generic request method with proper async handling"""
|
|
232
|
+
request = self._build_request(method, url, **kwargs)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
# Check if we're in an async context
|
|
236
|
+
import asyncio
|
|
237
|
+
try:
|
|
238
|
+
current_loop = asyncio.get_running_loop()
|
|
239
|
+
# We're already in a running loop - need to create new loop
|
|
240
|
+
import concurrent.futures
|
|
241
|
+
import threading
|
|
242
|
+
|
|
243
|
+
def run_in_new_loop():
|
|
244
|
+
new_loop = asyncio.new_event_loop()
|
|
245
|
+
asyncio.set_event_loop(new_loop)
|
|
246
|
+
try:
|
|
247
|
+
return new_loop.run_until_complete(self._execute_request(request))
|
|
248
|
+
finally:
|
|
249
|
+
new_loop.close()
|
|
250
|
+
|
|
251
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
252
|
+
future = executor.submit(run_in_new_loop)
|
|
253
|
+
return future.result()
|
|
254
|
+
|
|
255
|
+
except RuntimeError:
|
|
256
|
+
# No running loop - we can create our own
|
|
257
|
+
loop = self._get_loop()
|
|
258
|
+
return loop.run_until_complete(self._execute_request(request))
|
|
259
|
+
|
|
260
|
+
except Exception as e:
|
|
261
|
+
logger.error(f"Request execution failed: {e}")
|
|
262
|
+
return TestResponse(500, {"error": f"Test execution failed: {str(e)}"})
|
|
263
|
+
|
|
264
|
+
def get(self, url: str, **kwargs) -> TestResponse:
|
|
265
|
+
"""Send GET request"""
|
|
266
|
+
return self.request('GET', url, **kwargs)
|
|
267
|
+
|
|
268
|
+
def post(self, url: str, **kwargs) -> TestResponse:
|
|
269
|
+
"""Send POST request"""
|
|
270
|
+
return self.request('POST', url, **kwargs)
|
|
271
|
+
|
|
272
|
+
def put(self, url: str, **kwargs) -> TestResponse:
|
|
273
|
+
"""Send PUT request"""
|
|
274
|
+
return self.request('PUT', url, **kwargs)
|
|
275
|
+
|
|
276
|
+
def patch(self, url: str, **kwargs) -> TestResponse:
|
|
277
|
+
"""Send PATCH request"""
|
|
278
|
+
return self.request('PATCH', url, **kwargs)
|
|
279
|
+
|
|
280
|
+
def delete(self, url: str, **kwargs) -> TestResponse:
|
|
281
|
+
"""Send DELETE request"""
|
|
282
|
+
return self.request('DELETE', url, **kwargs)
|
|
283
|
+
|
|
284
|
+
def options(self, url: str, **kwargs) -> TestResponse:
|
|
285
|
+
"""Send OPTIONS request"""
|
|
286
|
+
return self.request('OPTIONS', url, **kwargs)
|
|
287
|
+
|
|
288
|
+
def head(self, url: str, **kwargs) -> TestResponse:
|
|
289
|
+
"""Send HEAD request"""
|
|
290
|
+
return self.request('HEAD', url, **kwargs)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# Test utilities
|
|
294
|
+
class TestDatabase:
|
|
295
|
+
"""In-memory test database for testing"""
|
|
296
|
+
|
|
297
|
+
def __init__(self):
|
|
298
|
+
from .database import DatabaseConnection
|
|
299
|
+
self.db = DatabaseConnection('sqlite:///:memory:')
|
|
300
|
+
|
|
301
|
+
def reset(self):
|
|
302
|
+
"""Reset test database"""
|
|
303
|
+
# Drop all tables and recreate
|
|
304
|
+
cursor = self.db.connection.cursor()
|
|
305
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
306
|
+
tables = cursor.fetchall()
|
|
307
|
+
|
|
308
|
+
for table in tables:
|
|
309
|
+
cursor.execute(f"DROP TABLE {table[0]}")
|
|
310
|
+
|
|
311
|
+
self.db.connection.commit()
|
|
312
|
+
self.db._create_default_tables()
|
|
313
|
+
|
|
314
|
+
def seed_admin_user(self, username: str = "admin", email: str = "admin@test.com", password: str = "admin123"):
|
|
315
|
+
"""Create test admin user"""
|
|
316
|
+
return self.db.create_admin_user(username, email, password)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def create_test_app():
|
|
320
|
+
"""Create a test CREATESONLINE app"""
|
|
321
|
+
from . import create_app
|
|
322
|
+
return create_app(title="Test App", debug=False)
|