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
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Error Handling
|
|
3
|
+
|
|
4
|
+
Error handling and error page generation.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class HTTPException(Exception):
|
|
10
|
+
"""HTTP exception with status code and detail message"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, status_code: int, detail: str = None):
|
|
13
|
+
self.status_code = status_code
|
|
14
|
+
self.detail = detail
|
|
15
|
+
super().__init__(detail)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ErrorPageGenerator:
|
|
19
|
+
"""Generate error pages for HTTP errors"""
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def generate_error_page(
|
|
23
|
+
status_code: int,
|
|
24
|
+
error_message: str,
|
|
25
|
+
path: str = "",
|
|
26
|
+
method: str = "GET",
|
|
27
|
+
details: str = ""
|
|
28
|
+
) -> str:
|
|
29
|
+
"""Generate error page HTML"""
|
|
30
|
+
status_text = {
|
|
31
|
+
400: "Bad Request",
|
|
32
|
+
401: "Unauthorized",
|
|
33
|
+
403: "Forbidden",
|
|
34
|
+
404: "Not Found",
|
|
35
|
+
405: "Method Not Allowed",
|
|
36
|
+
500: "Internal Server Error",
|
|
37
|
+
502: "Bad Gateway",
|
|
38
|
+
503: "Service Unavailable"
|
|
39
|
+
}.get(status_code, "Error")
|
|
40
|
+
|
|
41
|
+
html = f"""
|
|
42
|
+
<!DOCTYPE html>
|
|
43
|
+
<html lang="en">
|
|
44
|
+
<head>
|
|
45
|
+
<meta charset="UTF-8">
|
|
46
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
47
|
+
<title>{status_code} {status_text} - CREATESONLINE</title>
|
|
48
|
+
<style>
|
|
49
|
+
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
50
|
+
|
|
51
|
+
body {{
|
|
52
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
53
|
+
background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%);
|
|
54
|
+
color: #fff;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
min-height: 100vh;
|
|
59
|
+
padding: 2rem;
|
|
60
|
+
}}
|
|
61
|
+
|
|
62
|
+
.error-container {{
|
|
63
|
+
text-align: center;
|
|
64
|
+
max-width: 600px;
|
|
65
|
+
}}
|
|
66
|
+
|
|
67
|
+
.error-code {{
|
|
68
|
+
font-size: 8rem;
|
|
69
|
+
font-weight: 900;
|
|
70
|
+
margin-bottom: 1rem;
|
|
71
|
+
background: linear-gradient(135deg, #ef4444, #f59e0b);
|
|
72
|
+
-webkit-background-clip: text;
|
|
73
|
+
-webkit-text-fill-color: transparent;
|
|
74
|
+
background-clip: text;
|
|
75
|
+
}}
|
|
76
|
+
|
|
77
|
+
.error-title {{
|
|
78
|
+
font-size: 2rem;
|
|
79
|
+
margin-bottom: 1rem;
|
|
80
|
+
font-weight: 700;
|
|
81
|
+
}}
|
|
82
|
+
|
|
83
|
+
.error-message {{
|
|
84
|
+
font-size: 1.125rem;
|
|
85
|
+
color: #d1d5db;
|
|
86
|
+
margin-bottom: 2rem;
|
|
87
|
+
}}
|
|
88
|
+
|
|
89
|
+
.error-details {{
|
|
90
|
+
background: rgba(255, 255, 255, 0.05);
|
|
91
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
92
|
+
border-radius: 8px;
|
|
93
|
+
padding: 1.5rem;
|
|
94
|
+
margin-bottom: 2rem;
|
|
95
|
+
text-align: left;
|
|
96
|
+
font-family: 'Monaco', monospace;
|
|
97
|
+
font-size: 0.875rem;
|
|
98
|
+
}}
|
|
99
|
+
|
|
100
|
+
.detail-row {{
|
|
101
|
+
display: grid;
|
|
102
|
+
grid-template-columns: 150px 1fr;
|
|
103
|
+
gap: 1rem;
|
|
104
|
+
margin-bottom: 0.5rem;
|
|
105
|
+
}}
|
|
106
|
+
|
|
107
|
+
.detail-label {{
|
|
108
|
+
color: #6366f1;
|
|
109
|
+
font-weight: 600;
|
|
110
|
+
}}
|
|
111
|
+
|
|
112
|
+
.detail-value {{
|
|
113
|
+
color: #d1d5db;
|
|
114
|
+
word-break: break-all;
|
|
115
|
+
}}
|
|
116
|
+
|
|
117
|
+
.back-link {{
|
|
118
|
+
display: inline-block;
|
|
119
|
+
padding: 0.75rem 1.5rem;
|
|
120
|
+
background: #6366f1;
|
|
121
|
+
color: white;
|
|
122
|
+
text-decoration: none;
|
|
123
|
+
border-radius: 6px;
|
|
124
|
+
transition: all 0.3s ease;
|
|
125
|
+
}}
|
|
126
|
+
|
|
127
|
+
.back-link:hover {{
|
|
128
|
+
background: #4f46e5;
|
|
129
|
+
transform: translateY(-2px);
|
|
130
|
+
}}
|
|
131
|
+
</style>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<div class="error-container">
|
|
135
|
+
<div class="error-code">{status_code}</div>
|
|
136
|
+
<h1 class="error-title">{status_text}</h1>
|
|
137
|
+
<p class="error-message">{error_message}</p>
|
|
138
|
+
|
|
139
|
+
<div class="error-details">
|
|
140
|
+
<div class="detail-row">
|
|
141
|
+
<span class="detail-label">Status:</span>
|
|
142
|
+
<span class="detail-value">{status_code} {status_text}</span>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="detail-row">
|
|
145
|
+
<span class="detail-label">Method:</span>
|
|
146
|
+
<span class="detail-value">{method}</span>
|
|
147
|
+
</div>
|
|
148
|
+
<div class="detail-row">
|
|
149
|
+
<span class="detail-label">Path:</span>
|
|
150
|
+
<span class="detail-value">{path}</span>
|
|
151
|
+
</div>
|
|
152
|
+
{f'<div class="detail-row"><span class="detail-label">Details:</span><span class="detail-value">{details}</span></div>' if details else ''}
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<a href="/" class="back-link">← Back to Home</a>
|
|
156
|
+
</div>
|
|
157
|
+
</body>
|
|
158
|
+
</html>
|
|
159
|
+
"""
|
|
160
|
+
return html
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Compatibility layer for the AI-enhanced ORM.
|
|
2
|
+
|
|
3
|
+
The original test-suite and packaging metadata expect to import the
|
|
4
|
+
``AIEnhancedORM`` class from ``createsonline.config.orm``. During the
|
|
5
|
+
repository's refactor the implementation was moved to
|
|
6
|
+
``createsonline.ai.orm`` without leaving a public shim behind, which now
|
|
7
|
+
breaks both the automated tests and downstream users installing the PyPI
|
|
8
|
+
package. Re-introduce the module as a light-weight re-export so that the
|
|
9
|
+
documented import path keeps working while the actual implementation
|
|
10
|
+
remains in :mod:`createsonline.ai.orm`.
|
|
11
|
+
|
|
12
|
+
Only the public classes that are required by the tests are re-exported
|
|
13
|
+
today. Should additional helpers be needed in the future they can be
|
|
14
|
+
added here without changing the underlying ORM module again.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from createsonline.ai.orm import AIBaseModel, AIEnhancedORM, Base
|
|
20
|
+
|
|
21
|
+
__all__ = ["AIEnhancedORM", "AIBaseModel", "Base"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def __getattr__(name: str):
|
|
25
|
+
"""Provide a helpful error for unexpected attribute access.
|
|
26
|
+
|
|
27
|
+
The compatibility module purposefully keeps a small surface area. If
|
|
28
|
+
another symbol is requested we forward the attribute access to the
|
|
29
|
+
original module and raise an informative :class:`AttributeError` when
|
|
30
|
+
the symbol does not exist. This mirrors the behaviour developers were
|
|
31
|
+
accustomed to before the refactor while still pointing them to the
|
|
32
|
+
new canonical location.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from createsonline import ai
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
return getattr(ai.orm, name)
|
|
39
|
+
except AttributeError as exc: # pragma: no cover - defensive branch
|
|
40
|
+
raise AttributeError(
|
|
41
|
+
f"module 'createsonline.config.orm' has no attribute '{name}'. "
|
|
42
|
+
"The ORM implementation now lives in 'createsonline.ai.orm'."
|
|
43
|
+
) from exc
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Internal Request Handling
|
|
3
|
+
|
|
4
|
+
Request object and utilities for handling HTTP requests in internal ASGI mode.
|
|
5
|
+
"""
|
|
6
|
+
from typing import Dict, Any, Optional
|
|
7
|
+
from urllib.parse import parse_qs
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CreatesonlineInternalRequest:
|
|
11
|
+
"""Internal request object for zero-dependency mode"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, scope, receive):
|
|
14
|
+
self.scope = scope
|
|
15
|
+
self.receive = receive
|
|
16
|
+
self.method = scope.get('method', 'GET')
|
|
17
|
+
self.path = scope.get('path', '/')
|
|
18
|
+
self.url = self._build_url(scope)
|
|
19
|
+
self.query_params = self._parse_query_string(scope.get('query_string', b'').decode())
|
|
20
|
+
self.headers = self._parse_headers(scope.get('headers', []))
|
|
21
|
+
self._body = None
|
|
22
|
+
self._json_data = None
|
|
23
|
+
|
|
24
|
+
def _build_url(self, scope) -> str:
|
|
25
|
+
"""Build full URL from ASGI scope"""
|
|
26
|
+
scheme = scope.get('scheme', 'http')
|
|
27
|
+
server = scope.get('server', ('localhost', 8000))
|
|
28
|
+
host, port = server
|
|
29
|
+
path = scope.get('path', '/')
|
|
30
|
+
query_string = scope.get('query_string', b'').decode()
|
|
31
|
+
|
|
32
|
+
if (scheme == 'https' and port == 443) or (scheme == 'http' and port == 80):
|
|
33
|
+
url = f"{scheme}://{host}{path}"
|
|
34
|
+
else:
|
|
35
|
+
url = f"{scheme}://{host}:{port}{path}"
|
|
36
|
+
|
|
37
|
+
if query_string:
|
|
38
|
+
url += f"?{query_string}"
|
|
39
|
+
|
|
40
|
+
return url
|
|
41
|
+
|
|
42
|
+
def _parse_query_string(self, query_string: str) -> Dict[str, Any]:
|
|
43
|
+
"""Parse query string into dictionary"""
|
|
44
|
+
if not query_string:
|
|
45
|
+
return {}
|
|
46
|
+
return {k: v[0] if len(v) == 1 else v for k, v in parse_qs(query_string).items()}
|
|
47
|
+
|
|
48
|
+
def _parse_headers(self, headers: list) -> Dict[str, str]:
|
|
49
|
+
"""Parse ASGI headers format to dictionary"""
|
|
50
|
+
header_dict = {}
|
|
51
|
+
for name, value in headers:
|
|
52
|
+
key = name.decode('latin-1').lower()
|
|
53
|
+
header_dict[key] = value.decode('latin-1')
|
|
54
|
+
return header_dict
|
|
55
|
+
|
|
56
|
+
async def json(self) -> Dict[str, Any]:
|
|
57
|
+
"""Parse request body as JSON"""
|
|
58
|
+
if self._json_data is None:
|
|
59
|
+
import json
|
|
60
|
+
body = await self._get_body()
|
|
61
|
+
try:
|
|
62
|
+
self._json_data = json.loads(body.decode('utf-8'))
|
|
63
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
64
|
+
self._json_data = {}
|
|
65
|
+
return self._json_data
|
|
66
|
+
|
|
67
|
+
async def body(self) -> bytes:
|
|
68
|
+
"""Get raw request body"""
|
|
69
|
+
return await self._get_body()
|
|
70
|
+
|
|
71
|
+
async def _get_body(self) -> bytes:
|
|
72
|
+
"""Read body from request stream"""
|
|
73
|
+
if self._body is None:
|
|
74
|
+
body_parts = []
|
|
75
|
+
while True:
|
|
76
|
+
message = await self.receive()
|
|
77
|
+
if message['type'] == 'http.request':
|
|
78
|
+
body_parts.append(message.get('body', b''))
|
|
79
|
+
if not message.get('more_body', False):
|
|
80
|
+
break
|
|
81
|
+
self._body = b''.join(body_parts)
|
|
82
|
+
return self._body
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def client_ip(self) -> Optional[str]:
|
|
86
|
+
"""Get client IP address"""
|
|
87
|
+
client = self.scope.get('client')
|
|
88
|
+
return client[0] if client else None
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def is_secure(self) -> bool:
|
|
92
|
+
"""Check if request is HTTPS"""
|
|
93
|
+
return self.scope.get('scheme') == 'https'
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# createsonline/config/settings.py
|
|
2
|
+
"""
|
|
3
|
+
CREATESONLINE Settings Management
|
|
4
|
+
|
|
5
|
+
Simple, unified settings for CREATESONLINE applications.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
from typing import Dict, Any, Optional
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
class CreatesonlineSettings:
|
|
12
|
+
"""Simple settings manager for CREATESONLINE framework"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, env_file: Optional[str] = None):
|
|
15
|
+
"""Initialize settings from environment variables and .env file"""
|
|
16
|
+
self.env_file = env_file or '.env'
|
|
17
|
+
self._load_env_file()
|
|
18
|
+
|
|
19
|
+
def _load_env_file(self):
|
|
20
|
+
"""Load environment variables from .env file"""
|
|
21
|
+
env_path = Path(self.env_file)
|
|
22
|
+
if env_path.exists():
|
|
23
|
+
try:
|
|
24
|
+
from dotenv import load_dotenv
|
|
25
|
+
load_dotenv(env_path)
|
|
26
|
+
except ImportError:
|
|
27
|
+
# dotenv not installed, skip loading
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
# Core application settings
|
|
31
|
+
@property
|
|
32
|
+
def DEBUG(self) -> bool:
|
|
33
|
+
return os.getenv('DEBUG', 'false').lower() in ('true', '1', 'yes')
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def SECRET_KEY(self) -> str:
|
|
37
|
+
return os.getenv('SECRET_KEY', 'createsonline-dev-key-change-in-production')
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def HOST(self) -> str:
|
|
41
|
+
return os.getenv('HOST', '127.0.0.1')
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def PORT(self) -> int:
|
|
45
|
+
return int(os.getenv('PORT', '8000'))
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def ENVIRONMENT(self) -> str:
|
|
49
|
+
return os.getenv('ENVIRONMENT', 'development')
|
|
50
|
+
|
|
51
|
+
# Database settings
|
|
52
|
+
@property
|
|
53
|
+
def DATABASE_URL(self) -> str:
|
|
54
|
+
return os.getenv('DATABASE_URL', 'sqlite:///./createsonline.db')
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def DATABASE_ECHO(self) -> bool:
|
|
58
|
+
return os.getenv('DATABASE_ECHO', 'false').lower() in ('true', '1', 'yes')
|
|
59
|
+
|
|
60
|
+
# AI settings
|
|
61
|
+
@property
|
|
62
|
+
def OPENAI_API_KEY(self) -> Optional[str]:
|
|
63
|
+
return os.getenv('OPENAI_API_KEY')
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def ANTHROPIC_API_KEY(self) -> Optional[str]:
|
|
67
|
+
return os.getenv('ANTHROPIC_API_KEY')
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def AI_CACHE_TTL(self) -> int:
|
|
71
|
+
return int(os.getenv('AI_CACHE_TTL', '3600')) # 1 hour
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def AI_ENABLED(self) -> bool:
|
|
75
|
+
return os.getenv('AI_ENABLED', 'true').lower() in ('true', '1', 'yes')
|
|
76
|
+
|
|
77
|
+
# Admin settings
|
|
78
|
+
@property
|
|
79
|
+
def ADMIN_ENABLED(self) -> bool:
|
|
80
|
+
return os.getenv('ADMIN_ENABLED', 'true').lower() in ('true', '1', 'yes')
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def ADMIN_PATH(self) -> str:
|
|
84
|
+
return os.getenv('ADMIN_PATH', '/admin')
|
|
85
|
+
|
|
86
|
+
# Authentication settings
|
|
87
|
+
@property
|
|
88
|
+
def AUTH_ENABLED(self) -> bool:
|
|
89
|
+
return os.getenv('AUTH_ENABLED', 'true').lower() in ('true', '1', 'yes')
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def SESSION_TIMEOUT(self) -> int:
|
|
93
|
+
return int(os.getenv('SESSION_TIMEOUT', '3600')) # 1 hour
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def PASSWORD_MIN_LENGTH(self) -> int:
|
|
97
|
+
return int(os.getenv('PASSWORD_MIN_LENGTH', '8'))
|
|
98
|
+
|
|
99
|
+
# CORS settings
|
|
100
|
+
@property
|
|
101
|
+
def CORS_ORIGINS(self) -> list:
|
|
102
|
+
origins = os.getenv('CORS_ORIGINS', '')
|
|
103
|
+
if origins:
|
|
104
|
+
return [origin.strip() for origin in origins.split(',')]
|
|
105
|
+
return ['*'] if self.DEBUG else []
|
|
106
|
+
|
|
107
|
+
# Static files settings (Django-style)
|
|
108
|
+
@property
|
|
109
|
+
def STATIC_URL(self) -> str:
|
|
110
|
+
"""URL prefix for static files"""
|
|
111
|
+
return os.getenv('STATIC_URL', '/static/')
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def STATIC_ROOT(self) -> Optional[Path]:
|
|
115
|
+
"""Directory for collected static files (for production)"""
|
|
116
|
+
static_root = os.getenv('STATIC_ROOT')
|
|
117
|
+
return Path(static_root) if static_root else None
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def STATICFILES_DIRS(self) -> list:
|
|
121
|
+
"""Additional directories to search for static files"""
|
|
122
|
+
# Get from environment (comma-separated paths)
|
|
123
|
+
dirs_str = os.getenv('STATICFILES_DIRS', '')
|
|
124
|
+
if dirs_str:
|
|
125
|
+
return [Path(d.strip()) for d in dirs_str.split(',') if d.strip()]
|
|
126
|
+
|
|
127
|
+
# Auto-discover: Look for 'static' directory in project root
|
|
128
|
+
base_dir = Path.cwd()
|
|
129
|
+
static_dir = base_dir / "static"
|
|
130
|
+
|
|
131
|
+
if static_dir.exists():
|
|
132
|
+
return [static_dir]
|
|
133
|
+
return []
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def TEMPLATE_DIRS(self) -> list:
|
|
137
|
+
"""Directories to search for templates"""
|
|
138
|
+
# Get from environment
|
|
139
|
+
dirs_str = os.getenv('TEMPLATE_DIRS', '')
|
|
140
|
+
if dirs_str:
|
|
141
|
+
return [Path(d.strip()) for d in dirs_str.split(',') if d.strip()]
|
|
142
|
+
|
|
143
|
+
# Auto-discover: Look for 'templates' directory in project root
|
|
144
|
+
base_dir = Path.cwd()
|
|
145
|
+
template_dir = base_dir / "templates"
|
|
146
|
+
|
|
147
|
+
if template_dir.exists():
|
|
148
|
+
return [template_dir]
|
|
149
|
+
return []
|
|
150
|
+
|
|
151
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
152
|
+
"""Get setting value by key"""
|
|
153
|
+
return getattr(self, key, default)
|
|
154
|
+
|
|
155
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
156
|
+
"""Convert settings to dictionary"""
|
|
157
|
+
return {
|
|
158
|
+
'DEBUG': self.DEBUG,
|
|
159
|
+
'SECRET_KEY': self.SECRET_KEY,
|
|
160
|
+
'HOST': self.HOST,
|
|
161
|
+
'PORT': self.PORT,
|
|
162
|
+
'ENVIRONMENT': self.ENVIRONMENT,
|
|
163
|
+
'DATABASE_URL': self.DATABASE_URL,
|
|
164
|
+
'DATABASE_ECHO': self.DATABASE_ECHO,
|
|
165
|
+
'AI_ENABLED': self.AI_ENABLED,
|
|
166
|
+
'AI_CACHE_TTL': self.AI_CACHE_TTL,
|
|
167
|
+
'ADMIN_ENABLED': self.ADMIN_ENABLED,
|
|
168
|
+
'ADMIN_PATH': self.ADMIN_PATH,
|
|
169
|
+
'AUTH_ENABLED': self.AUTH_ENABLED,
|
|
170
|
+
'SESSION_TIMEOUT': self.SESSION_TIMEOUT,
|
|
171
|
+
'PASSWORD_MIN_LENGTH': self.PASSWORD_MIN_LENGTH,
|
|
172
|
+
'CORS_ORIGINS': self.CORS_ORIGINS,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Global settings instance
|
|
176
|
+
settings = CreatesonlineSettings()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Internal Data Structures Module
|
|
3
|
+
|
|
4
|
+
Pure Python data manipulation library with zero external dependencies.
|
|
5
|
+
Lightweight replacement for Pandas with AI-native features.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .dataframe import CreatesonlineDataFrame
|
|
9
|
+
from .series import CreatesonlineSeries
|
|
10
|
+
from .io import read_csv, read_json, to_csv, to_json
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'CreatesonlineDataFrame',
|
|
14
|
+
'CreatesonlineSeries',
|
|
15
|
+
'read_csv',
|
|
16
|
+
'read_json',
|
|
17
|
+
'to_csv',
|
|
18
|
+
'to_json'
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
# Convenience aliases
|
|
22
|
+
DataFrame = CreatesonlineDataFrame
|
|
23
|
+
Series = CreatesonlineSeries
|