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,442 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Database Fields
|
|
3
|
+
Field types that integrate with AI functionality.
|
|
4
|
+
"""
|
|
5
|
+
from typing import Any, Optional, Union, Dict, Callable
|
|
6
|
+
from datetime import datetime, date
|
|
7
|
+
from sqlalchemy import Column, Integer, String, Text, Float, Boolean, DateTime, Date, JSON
|
|
8
|
+
from sqlalchemy.types import TypeDecorator, VARCHAR
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
# Base field class for compatibility
|
|
12
|
+
class CreatesonlineField:
|
|
13
|
+
"""
|
|
14
|
+
Base CREATESONLINE field class for database abstraction.
|
|
15
|
+
Provides compatibility layer for the Pure Independence implementation.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self,
|
|
19
|
+
primary_key: bool = False,
|
|
20
|
+
nullable: bool = True,
|
|
21
|
+
default: Any = None,
|
|
22
|
+
unique: bool = False,
|
|
23
|
+
index: bool = False,
|
|
24
|
+
**kwargs):
|
|
25
|
+
self.primary_key = primary_key
|
|
26
|
+
self.nullable = nullable
|
|
27
|
+
self.default = default
|
|
28
|
+
self.unique = unique
|
|
29
|
+
self.index = index
|
|
30
|
+
self.extra_kwargs = kwargs
|
|
31
|
+
|
|
32
|
+
def to_column(self, name: str) -> Column:
|
|
33
|
+
"""Convert to SQLAlchemy column - must be implemented by subclasses."""
|
|
34
|
+
raise NotImplementedError("Subclasses must implement to_column method")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AIEnhancedField(CreatesonlineField):
|
|
38
|
+
"""
|
|
39
|
+
Base class for AI-enhanced fields that can be processed by AI models.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self,
|
|
43
|
+
ai_processable: bool = False,
|
|
44
|
+
ai_embedding: bool = False,
|
|
45
|
+
ai_searchable: bool = False,
|
|
46
|
+
ai_summarizable: bool = False,
|
|
47
|
+
**kwargs):
|
|
48
|
+
super().__init__(**kwargs)
|
|
49
|
+
self.ai_processable = ai_processable
|
|
50
|
+
self.ai_embedding = ai_embedding
|
|
51
|
+
self.ai_searchable = ai_searchable
|
|
52
|
+
self.ai_summarizable = ai_summarizable
|
|
53
|
+
self.ai_metadata = {k: v for k, v in kwargs.items() if k.startswith('ai_')}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class StringField(AIEnhancedField):
|
|
57
|
+
"""Enhanced string field with AI capabilities."""
|
|
58
|
+
|
|
59
|
+
def __init__(self,
|
|
60
|
+
max_length: int = 255,
|
|
61
|
+
nullable: bool = True,
|
|
62
|
+
default: Optional[str] = None,
|
|
63
|
+
unique: bool = False,
|
|
64
|
+
index: bool = False,
|
|
65
|
+
**ai_kwargs):
|
|
66
|
+
super().__init__(**ai_kwargs)
|
|
67
|
+
self.max_length = max_length
|
|
68
|
+
self.nullable = nullable
|
|
69
|
+
self.default = default
|
|
70
|
+
self.unique = unique
|
|
71
|
+
self.index = index
|
|
72
|
+
|
|
73
|
+
def to_column(self, name: str) -> Column:
|
|
74
|
+
"""Convert to SQLAlchemy column."""
|
|
75
|
+
return Column(
|
|
76
|
+
name,
|
|
77
|
+
String(self.max_length),
|
|
78
|
+
nullable=self.nullable,
|
|
79
|
+
default=self.default,
|
|
80
|
+
unique=self.unique,
|
|
81
|
+
index=self.index
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TextField(AIEnhancedField):
|
|
86
|
+
"""Enhanced text field for long content with AI capabilities."""
|
|
87
|
+
|
|
88
|
+
def __init__(self,
|
|
89
|
+
nullable: bool = True,
|
|
90
|
+
default: Optional[str] = None,
|
|
91
|
+
**ai_kwargs):
|
|
92
|
+
super().__init__(**ai_kwargs)
|
|
93
|
+
self.nullable = nullable
|
|
94
|
+
self.default = default
|
|
95
|
+
|
|
96
|
+
def to_column(self, name: str) -> Column:
|
|
97
|
+
"""Convert to SQLAlchemy column."""
|
|
98
|
+
return Column(
|
|
99
|
+
name,
|
|
100
|
+
Text,
|
|
101
|
+
nullable=self.nullable,
|
|
102
|
+
default=self.default
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class IntegerField(AIEnhancedField):
|
|
107
|
+
"""Enhanced integer field with AI capabilities."""
|
|
108
|
+
|
|
109
|
+
def __init__(self,
|
|
110
|
+
nullable: bool = True,
|
|
111
|
+
default: Optional[int] = None,
|
|
112
|
+
unique: bool = False,
|
|
113
|
+
index: bool = False,
|
|
114
|
+
primary_key: bool = False,
|
|
115
|
+
autoincrement: bool = False,
|
|
116
|
+
**ai_kwargs):
|
|
117
|
+
super().__init__(**ai_kwargs)
|
|
118
|
+
self.nullable = nullable
|
|
119
|
+
self.default = default
|
|
120
|
+
self.unique = unique
|
|
121
|
+
self.index = index
|
|
122
|
+
self.primary_key = primary_key
|
|
123
|
+
self.autoincrement = autoincrement
|
|
124
|
+
|
|
125
|
+
def to_column(self, name: str) -> Column:
|
|
126
|
+
"""Convert to SQLAlchemy column."""
|
|
127
|
+
return Column(
|
|
128
|
+
name,
|
|
129
|
+
Integer,
|
|
130
|
+
nullable=self.nullable,
|
|
131
|
+
default=self.default,
|
|
132
|
+
unique=self.unique,
|
|
133
|
+
index=self.index,
|
|
134
|
+
primary_key=self.primary_key,
|
|
135
|
+
autoincrement=self.autoincrement
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class FloatField(AIEnhancedField):
|
|
140
|
+
"""Enhanced float field with AI capabilities."""
|
|
141
|
+
|
|
142
|
+
def __init__(self,
|
|
143
|
+
nullable: bool = True,
|
|
144
|
+
default: Optional[float] = None,
|
|
145
|
+
unique: bool = False,
|
|
146
|
+
index: bool = False,
|
|
147
|
+
**ai_kwargs):
|
|
148
|
+
super().__init__(**ai_kwargs)
|
|
149
|
+
self.nullable = nullable
|
|
150
|
+
self.default = default
|
|
151
|
+
self.unique = unique
|
|
152
|
+
self.index = index
|
|
153
|
+
|
|
154
|
+
def to_column(self, name: str) -> Column:
|
|
155
|
+
"""Convert to SQLAlchemy column."""
|
|
156
|
+
return Column(
|
|
157
|
+
name,
|
|
158
|
+
Float,
|
|
159
|
+
nullable=self.nullable,
|
|
160
|
+
default=self.default,
|
|
161
|
+
unique=self.unique,
|
|
162
|
+
index=self.index
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class BooleanField(AIEnhancedField):
|
|
167
|
+
"""Enhanced boolean field with AI capabilities."""
|
|
168
|
+
|
|
169
|
+
def __init__(self,
|
|
170
|
+
default: Optional[bool] = None,
|
|
171
|
+
nullable: bool = True,
|
|
172
|
+
**ai_kwargs):
|
|
173
|
+
super().__init__(**ai_kwargs)
|
|
174
|
+
self.default = default
|
|
175
|
+
self.nullable = nullable
|
|
176
|
+
|
|
177
|
+
def to_column(self, name: str) -> Column:
|
|
178
|
+
"""Convert to SQLAlchemy column."""
|
|
179
|
+
return Column(
|
|
180
|
+
name,
|
|
181
|
+
Boolean,
|
|
182
|
+
default=self.default,
|
|
183
|
+
nullable=self.nullable
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class DateTimeField(AIEnhancedField):
|
|
188
|
+
"""Enhanced datetime field with AI capabilities."""
|
|
189
|
+
|
|
190
|
+
def __init__(self,
|
|
191
|
+
auto_now: bool = False,
|
|
192
|
+
auto_now_add: bool = False,
|
|
193
|
+
default: Optional[Union[datetime, Callable]] = None,
|
|
194
|
+
nullable: bool = True,
|
|
195
|
+
**ai_kwargs):
|
|
196
|
+
super().__init__(**ai_kwargs)
|
|
197
|
+
self.auto_now = auto_now
|
|
198
|
+
self.auto_now_add = auto_now_add
|
|
199
|
+
self.nullable = nullable
|
|
200
|
+
|
|
201
|
+
if auto_now_add and default is None:
|
|
202
|
+
self.default = datetime.utcnow
|
|
203
|
+
elif auto_now and default is None:
|
|
204
|
+
self.default = datetime.utcnow
|
|
205
|
+
else:
|
|
206
|
+
self.default = default
|
|
207
|
+
|
|
208
|
+
def to_column(self, name: str) -> Column:
|
|
209
|
+
"""Convert to SQLAlchemy column."""
|
|
210
|
+
onupdate = datetime.utcnow if self.auto_now else None
|
|
211
|
+
|
|
212
|
+
return Column(
|
|
213
|
+
name,
|
|
214
|
+
DateTime,
|
|
215
|
+
default=self.default,
|
|
216
|
+
onupdate=onupdate,
|
|
217
|
+
nullable=self.nullable
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class DateField(AIEnhancedField):
|
|
222
|
+
"""Enhanced date field with AI capabilities."""
|
|
223
|
+
|
|
224
|
+
def __init__(self,
|
|
225
|
+
auto_now: bool = False,
|
|
226
|
+
auto_now_add: bool = False,
|
|
227
|
+
default: Optional[Union[date, Callable]] = None,
|
|
228
|
+
nullable: bool = True,
|
|
229
|
+
**ai_kwargs):
|
|
230
|
+
super().__init__(**ai_kwargs)
|
|
231
|
+
self.auto_now = auto_now
|
|
232
|
+
self.auto_now_add = auto_now_add
|
|
233
|
+
self.nullable = nullable
|
|
234
|
+
|
|
235
|
+
if auto_now_add and default is None:
|
|
236
|
+
self.default = lambda: datetime.utcnow().date()
|
|
237
|
+
elif auto_now and default is None:
|
|
238
|
+
self.default = lambda: datetime.utcnow().date()
|
|
239
|
+
else:
|
|
240
|
+
self.default = default
|
|
241
|
+
|
|
242
|
+
def to_column(self, name: str) -> Column:
|
|
243
|
+
"""Convert to SQLAlchemy column."""
|
|
244
|
+
onupdate = lambda: datetime.utcnow().date() if self.auto_now else None
|
|
245
|
+
|
|
246
|
+
return Column(
|
|
247
|
+
name,
|
|
248
|
+
Date,
|
|
249
|
+
default=self.default,
|
|
250
|
+
onupdate=onupdate,
|
|
251
|
+
nullable=self.nullable
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class JSONField(TypeDecorator, AIEnhancedField):
|
|
256
|
+
"""Enhanced JSON field with AI capabilities."""
|
|
257
|
+
|
|
258
|
+
impl = VARCHAR
|
|
259
|
+
cache_ok = True
|
|
260
|
+
|
|
261
|
+
def __init__(self,
|
|
262
|
+
nullable: bool = True,
|
|
263
|
+
default: Optional[Union[dict, list]] = None,
|
|
264
|
+
**ai_kwargs):
|
|
265
|
+
# Call parent constructors properly
|
|
266
|
+
TypeDecorator.__init__(self)
|
|
267
|
+
AIEnhancedField.__init__(self, **ai_kwargs)
|
|
268
|
+
self.nullable = nullable
|
|
269
|
+
self.default = default or {}
|
|
270
|
+
|
|
271
|
+
def load_dialect_impl(self, dialect):
|
|
272
|
+
"""Load appropriate dialect implementation."""
|
|
273
|
+
if dialect.name == 'postgresql':
|
|
274
|
+
return dialect.type_descriptor(JSON())
|
|
275
|
+
else:
|
|
276
|
+
return dialect.type_descriptor(VARCHAR())
|
|
277
|
+
|
|
278
|
+
def process_bind_param(self, value, dialect):
|
|
279
|
+
"""Process value when binding to database."""
|
|
280
|
+
if value is not None:
|
|
281
|
+
return json.dumps(value)
|
|
282
|
+
return value
|
|
283
|
+
|
|
284
|
+
def process_result_value(self, value, dialect):
|
|
285
|
+
"""Process value when retrieving from database."""
|
|
286
|
+
if value is not None:
|
|
287
|
+
try:
|
|
288
|
+
return json.loads(value)
|
|
289
|
+
except (ValueError, TypeError):
|
|
290
|
+
return value
|
|
291
|
+
return value
|
|
292
|
+
|
|
293
|
+
def to_column(self, name: str) -> Column:
|
|
294
|
+
"""Convert to SQLAlchemy column."""
|
|
295
|
+
return Column(
|
|
296
|
+
name,
|
|
297
|
+
self,
|
|
298
|
+
nullable=self.nullable,
|
|
299
|
+
default=self.default
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class EmbeddingField(AIEnhancedField):
|
|
304
|
+
"""Special field for storing AI embeddings."""
|
|
305
|
+
|
|
306
|
+
def __init__(self,
|
|
307
|
+
dimensions: int = 1536, # OpenAI default
|
|
308
|
+
nullable: bool = True,
|
|
309
|
+
**ai_kwargs):
|
|
310
|
+
super().__init__(ai_embedding=True, **ai_kwargs)
|
|
311
|
+
self.dimensions = dimensions
|
|
312
|
+
self.nullable = nullable
|
|
313
|
+
|
|
314
|
+
def to_column(self, name: str) -> Column:
|
|
315
|
+
"""Convert to SQLAlchemy column (stores as JSON)."""
|
|
316
|
+
return Column(
|
|
317
|
+
name,
|
|
318
|
+
JSON,
|
|
319
|
+
nullable=self.nullable
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class SlugField(StringField):
|
|
324
|
+
"""URL-friendly slug field."""
|
|
325
|
+
|
|
326
|
+
def __init__(self,
|
|
327
|
+
max_length: int = 100,
|
|
328
|
+
unique: bool = True,
|
|
329
|
+
index: bool = True,
|
|
330
|
+
**kwargs):
|
|
331
|
+
super().__init__(
|
|
332
|
+
max_length=max_length,
|
|
333
|
+
unique=unique,
|
|
334
|
+
index=index,
|
|
335
|
+
**kwargs
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class EmailField(StringField):
|
|
340
|
+
"""Email field with validation."""
|
|
341
|
+
|
|
342
|
+
def __init__(self,
|
|
343
|
+
max_length: int = 254, # RFC 5321
|
|
344
|
+
unique: bool = False,
|
|
345
|
+
index: bool = True,
|
|
346
|
+
**kwargs):
|
|
347
|
+
super().__init__(
|
|
348
|
+
max_length=max_length,
|
|
349
|
+
unique=unique,
|
|
350
|
+
index=index,
|
|
351
|
+
**kwargs
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class URLField(StringField):
|
|
356
|
+
"""URL field."""
|
|
357
|
+
|
|
358
|
+
def __init__(self,
|
|
359
|
+
max_length: int = 2048,
|
|
360
|
+
**kwargs):
|
|
361
|
+
super().__init__(
|
|
362
|
+
max_length=max_length,
|
|
363
|
+
**kwargs
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# Convenience functions for creating columns
|
|
368
|
+
def create_string_column(name: str, field: StringField) -> Column:
|
|
369
|
+
"""Create string column from field."""
|
|
370
|
+
return field.to_column(name)
|
|
371
|
+
|
|
372
|
+
def create_text_column(name: str, field: TextField) -> Column:
|
|
373
|
+
"""Create text column from field."""
|
|
374
|
+
return field.to_column(name)
|
|
375
|
+
|
|
376
|
+
def create_integer_column(name: str, field: IntegerField) -> Column:
|
|
377
|
+
"""Create integer column from field."""
|
|
378
|
+
return field.to_column(name)
|
|
379
|
+
|
|
380
|
+
def create_float_column(name: str, field: FloatField) -> Column:
|
|
381
|
+
"""Create float column from field."""
|
|
382
|
+
return field.to_column(name)
|
|
383
|
+
|
|
384
|
+
def create_boolean_column(name: str, field: BooleanField) -> Column:
|
|
385
|
+
"""Create boolean column from field."""
|
|
386
|
+
return field.to_column(name)
|
|
387
|
+
|
|
388
|
+
def create_datetime_column(name: str, field: DateTimeField) -> Column:
|
|
389
|
+
"""Create datetime column from field."""
|
|
390
|
+
return field.to_column(name)
|
|
391
|
+
|
|
392
|
+
def create_date_column(name: str, field: DateField) -> Column:
|
|
393
|
+
"""Create date column from field."""
|
|
394
|
+
return field.to_column(name)
|
|
395
|
+
|
|
396
|
+
def create_json_column(name: str, field: JSONField) -> Column:
|
|
397
|
+
"""Create JSON column from field."""
|
|
398
|
+
return field.to_column(name)
|
|
399
|
+
|
|
400
|
+
def create_embedding_column(name: str, field: EmbeddingField) -> Column:
|
|
401
|
+
"""Create embedding column from field."""
|
|
402
|
+
return field.to_column(name)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# Field registry for AI processing
|
|
406
|
+
AI_FIELD_REGISTRY = {}
|
|
407
|
+
|
|
408
|
+
def register_ai_field(model_class, field_name: str, field: AIEnhancedField):
|
|
409
|
+
"""Register a field for AI processing."""
|
|
410
|
+
model_name = model_class.__name__
|
|
411
|
+
if model_name not in AI_FIELD_REGISTRY:
|
|
412
|
+
AI_FIELD_REGISTRY[model_name] = {}
|
|
413
|
+
|
|
414
|
+
AI_FIELD_REGISTRY[model_name][field_name] = {
|
|
415
|
+
'field': field,
|
|
416
|
+
'ai_processable': field.ai_processable,
|
|
417
|
+
'ai_embedding': field.ai_embedding,
|
|
418
|
+
'ai_searchable': field.ai_searchable,
|
|
419
|
+
'ai_summarizable': field.ai_summarizable,
|
|
420
|
+
'ai_metadata': field.ai_metadata
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
def get_ai_fields(model_class) -> Dict[str, Dict]:
|
|
424
|
+
"""Get all AI-enhanced fields for a model."""
|
|
425
|
+
model_name = model_class.__name__
|
|
426
|
+
return AI_FIELD_REGISTRY.get(model_name, {})
|
|
427
|
+
|
|
428
|
+
def get_embedding_fields(model_class) -> Dict[str, Dict]:
|
|
429
|
+
"""Get all embedding fields for a model."""
|
|
430
|
+
ai_fields = get_ai_fields(model_class)
|
|
431
|
+
return {
|
|
432
|
+
name: info for name, info in ai_fields.items()
|
|
433
|
+
if info.get('ai_embedding', False)
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
def get_searchable_fields(model_class) -> Dict[str, Dict]:
|
|
437
|
+
"""Get all searchable fields for a model."""
|
|
438
|
+
ai_fields = get_ai_fields(model_class)
|
|
439
|
+
return {
|
|
440
|
+
name: info for name, info in ai_fields.items()
|
|
441
|
+
if info.get('ai_searchable', False)
|
|
442
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Database Migrations
|
|
3
|
+
Simple database migration system for Pure Independence architecture.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import List, Dict, Any
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
# Setup logger
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class Migration:
|
|
16
|
+
"""
|
|
17
|
+
Represents a single database migration.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, name: str, description: str = "", version: str = ""):
|
|
21
|
+
self.name = name
|
|
22
|
+
self.description = description
|
|
23
|
+
self.version = version
|
|
24
|
+
self.timestamp = datetime.now()
|
|
25
|
+
self.applied = False
|
|
26
|
+
|
|
27
|
+
def up(self):
|
|
28
|
+
"""Apply this migration"""
|
|
29
|
+
raise NotImplementedError("Subclasses must implement up() method")
|
|
30
|
+
|
|
31
|
+
def down(self):
|
|
32
|
+
"""Reverse this migration"""
|
|
33
|
+
raise NotImplementedError("Subclasses must implement down() method")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MigrationManager:
|
|
37
|
+
"""
|
|
38
|
+
Manages database migrations for CREATESONLINE Pure Independence.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, migrations_dir: str = "migrations"):
|
|
42
|
+
self.migrations_dir = Path(migrations_dir)
|
|
43
|
+
self.migrations_dir.mkdir(exist_ok=True)
|
|
44
|
+
self.applied_migrations_file = self.migrations_dir / "applied.json"
|
|
45
|
+
self.migrations: List[Migration] = []
|
|
46
|
+
self.applied_migrations = self._load_applied_migrations()
|
|
47
|
+
|
|
48
|
+
def _load_applied_migrations(self) -> List[str]:
|
|
49
|
+
"""Load list of applied migrations"""
|
|
50
|
+
if self.applied_migrations_file.exists():
|
|
51
|
+
try:
|
|
52
|
+
with open(self.applied_migrations_file, 'r') as f:
|
|
53
|
+
return json.load(f)
|
|
54
|
+
except (json.JSONDecodeError, IOError):
|
|
55
|
+
return []
|
|
56
|
+
return []
|
|
57
|
+
|
|
58
|
+
def _save_applied_migrations(self):
|
|
59
|
+
"""Save list of applied migrations"""
|
|
60
|
+
try:
|
|
61
|
+
with open(self.applied_migrations_file, 'w') as f:
|
|
62
|
+
json.dump(self.applied_migrations, f, indent=2)
|
|
63
|
+
except IOError as e:
|
|
64
|
+
logger.warning(f"Could not save applied migrations: {e}")
|
|
65
|
+
|
|
66
|
+
def add_migration(self, migration: Migration):
|
|
67
|
+
"""Add a migration to the manager"""
|
|
68
|
+
self.migrations.append(migration)
|
|
69
|
+
|
|
70
|
+
def apply_migrations(self):
|
|
71
|
+
"""Apply all pending migrations"""
|
|
72
|
+
applied_count = 0
|
|
73
|
+
pending_migrations = [m for m in self.migrations if m.name not in self.applied_migrations]
|
|
74
|
+
|
|
75
|
+
if not pending_migrations:
|
|
76
|
+
logger.info("No pending migrations to apply")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
logger.info(f"Applying {len(pending_migrations)} pending migrations...")
|
|
80
|
+
|
|
81
|
+
for migration in pending_migrations:
|
|
82
|
+
logger.info(f"Applying migration: {migration.name}")
|
|
83
|
+
try:
|
|
84
|
+
migration.up()
|
|
85
|
+
self.applied_migrations.append(migration.name)
|
|
86
|
+
migration.applied = True
|
|
87
|
+
applied_count += 1
|
|
88
|
+
logger.info(f"✅ Applied: {migration.name}")
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.error(f"⌠Failed to apply {migration.name}: {e}")
|
|
91
|
+
break
|
|
92
|
+
|
|
93
|
+
if applied_count > 0:
|
|
94
|
+
self._save_applied_migrations()
|
|
95
|
+
logger.info(f"Applied {applied_count} migrations successfully")
|
|
96
|
+
|
|
97
|
+
def rollback_migration(self, migration_name: str):
|
|
98
|
+
"""Rollback a specific migration"""
|
|
99
|
+
for migration in reversed(self.migrations):
|
|
100
|
+
if migration.name == migration_name:
|
|
101
|
+
if migration_name in self.applied_migrations:
|
|
102
|
+
logger.info(f"Rolling back migration: {migration_name}")
|
|
103
|
+
try:
|
|
104
|
+
migration.down()
|
|
105
|
+
self.applied_migrations.remove(migration_name)
|
|
106
|
+
migration.applied = False
|
|
107
|
+
self._save_applied_migrations()
|
|
108
|
+
logger.info(f"✅ Rolled back: {migration_name}")
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"⌠Failed to rollback {migration_name}: {e}")
|
|
111
|
+
else:
|
|
112
|
+
logger.warning(f"Migration {migration_name} is not applied")
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
logger.error(f"Migration {migration_name} not found")
|
|
116
|
+
|
|
117
|
+
def get_migration_status(self) -> Dict[str, Any]:
|
|
118
|
+
"""Get status of all migrations"""
|
|
119
|
+
return {
|
|
120
|
+
"total_migrations": len(self.migrations),
|
|
121
|
+
"applied_migrations": len(self.applied_migrations),
|
|
122
|
+
"pending_migrations": len(self.migrations) - len(self.applied_migrations),
|
|
123
|
+
"migrations": [
|
|
124
|
+
{
|
|
125
|
+
"name": m.name,
|
|
126
|
+
"description": m.description,
|
|
127
|
+
"applied": m.name in self.applied_migrations,
|
|
128
|
+
"timestamp": m.timestamp.isoformat() if hasattr(m, 'timestamp') else None
|
|
129
|
+
}
|
|
130
|
+
for m in self.migrations
|
|
131
|
+
]
|
|
132
|
+
}
|