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,440 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CREATESONLINE Database Abstraction Layer
|
|
3
|
+
|
|
4
|
+
Pure Python database abstraction that wraps SQLAlchemy with a clean API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional, Union, Type
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
|
|
12
|
+
# Setup logger
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DatabaseError(Exception):
|
|
17
|
+
"""Custom database error for better error handling"""
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
# Try to import SQLAlchemy, fallback to internal implementation if not available
|
|
21
|
+
try:
|
|
22
|
+
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, Text, Boolean, DateTime, Float, JSON, ForeignKey
|
|
23
|
+
from sqlalchemy.orm import sessionmaker, declarative_base, Session
|
|
24
|
+
from sqlalchemy.exc import SQLAlchemyError
|
|
25
|
+
from sqlalchemy.pool import StaticPool
|
|
26
|
+
SQLALCHEMY_AVAILABLE = True
|
|
27
|
+
except ImportError:
|
|
28
|
+
SQLALCHEMY_AVAILABLE = False
|
|
29
|
+
# Create placeholder classes
|
|
30
|
+
class Session: pass
|
|
31
|
+
class SQLAlchemyError(Exception): pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Database:
|
|
35
|
+
"""
|
|
36
|
+
CREATESONLINE Database Abstraction
|
|
37
|
+
|
|
38
|
+
Provides a clean, simple API that wraps SQLAlchemy complexity.
|
|
39
|
+
Falls back to internal database implementation if SQLAlchemy is not available.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
_instance = None # Singleton instance
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self,
|
|
46
|
+
database_url: Optional[str] = None,
|
|
47
|
+
echo: bool = False,
|
|
48
|
+
pool_size: int = 5,
|
|
49
|
+
max_overflow: int = 10
|
|
50
|
+
):
|
|
51
|
+
"""
|
|
52
|
+
Initialize database
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
database_url: Database connection URL
|
|
56
|
+
echo: Enable SQL query logging
|
|
57
|
+
pool_size: Connection pool size
|
|
58
|
+
max_overflow: Maximum overflow connections
|
|
59
|
+
"""
|
|
60
|
+
self.database_url = database_url or os.getenv('DATABASE_URL', 'sqlite:///createsonline.db')
|
|
61
|
+
self.echo = echo or os.getenv('DATABASE_ECHO', 'false').lower() == 'true'
|
|
62
|
+
|
|
63
|
+
if SQLALCHEMY_AVAILABLE:
|
|
64
|
+
self._setup_sqlalchemy(pool_size, max_overflow)
|
|
65
|
+
else:
|
|
66
|
+
self._setup_fallback()
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def get_instance(cls) -> 'Database':
|
|
70
|
+
"""Get singleton database instance"""
|
|
71
|
+
if cls._instance is None:
|
|
72
|
+
cls._instance = cls()
|
|
73
|
+
return cls._instance
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def set_instance(cls, instance: 'Database'):
|
|
77
|
+
"""Set singleton database instance"""
|
|
78
|
+
cls._instance = instance
|
|
79
|
+
|
|
80
|
+
def _setup_sqlalchemy(self, pool_size: int, max_overflow: int):
|
|
81
|
+
"""Setup SQLAlchemy engine and session"""
|
|
82
|
+
try:
|
|
83
|
+
# Configure engine based on database type
|
|
84
|
+
if self.database_url.startswith('sqlite'):
|
|
85
|
+
# SQLite configuration
|
|
86
|
+
engine_kwargs = {
|
|
87
|
+
'echo': self.echo,
|
|
88
|
+
'poolclass': StaticPool,
|
|
89
|
+
'connect_args': {'check_same_thread': False}
|
|
90
|
+
}
|
|
91
|
+
else:
|
|
92
|
+
# PostgreSQL/MySQL configuration
|
|
93
|
+
engine_kwargs = {
|
|
94
|
+
'echo': self.echo,
|
|
95
|
+
'pool_size': pool_size,
|
|
96
|
+
'max_overflow': max_overflow
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
self.engine = create_engine(self.database_url, **engine_kwargs)
|
|
100
|
+
self.metadata = MetaData()
|
|
101
|
+
|
|
102
|
+
# Create session factory
|
|
103
|
+
self.SessionLocal = sessionmaker(
|
|
104
|
+
autocommit=False,
|
|
105
|
+
autoflush=False,
|
|
106
|
+
bind=self.engine
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Create declarative base
|
|
110
|
+
self.Base = declarative_base(metadata=self.metadata)
|
|
111
|
+
|
|
112
|
+
self.mode = 'sqlalchemy'
|
|
113
|
+
logger.info("✅ CREATESONLINE: Database initialized with SQLAlchemy")
|
|
114
|
+
|
|
115
|
+
except SQLAlchemyError as e:
|
|
116
|
+
logger.warning(f"SQLAlchemy error: {e}")
|
|
117
|
+
self._setup_fallback()
|
|
118
|
+
|
|
119
|
+
def _setup_fallback(self):
|
|
120
|
+
"""Setup fallback database implementation"""
|
|
121
|
+
# Import the existing database implementation
|
|
122
|
+
from .. import database as legacy_db
|
|
123
|
+
|
|
124
|
+
self.legacy_db = legacy_db.DatabaseConnection(self.database_url)
|
|
125
|
+
self.mode = 'legacy'
|
|
126
|
+
logger.info("✅ CREATESONLINE: Database initialized with legacy implementation")
|
|
127
|
+
|
|
128
|
+
def get_session(self) -> Union[Session, 'LegacySession']:
|
|
129
|
+
"""Get database session"""
|
|
130
|
+
if self.mode == 'sqlalchemy':
|
|
131
|
+
return self.SessionLocal()
|
|
132
|
+
else:
|
|
133
|
+
return LegacySession(self.legacy_db)
|
|
134
|
+
|
|
135
|
+
@contextmanager
|
|
136
|
+
def session_scope(self):
|
|
137
|
+
"""Context manager for database sessions"""
|
|
138
|
+
session = self.get_session()
|
|
139
|
+
try:
|
|
140
|
+
yield session
|
|
141
|
+
if hasattr(session, 'commit'):
|
|
142
|
+
session.commit()
|
|
143
|
+
except Exception as e:
|
|
144
|
+
if hasattr(session, 'rollback'):
|
|
145
|
+
session.rollback()
|
|
146
|
+
raise
|
|
147
|
+
finally:
|
|
148
|
+
if hasattr(session, 'close'):
|
|
149
|
+
session.close()
|
|
150
|
+
|
|
151
|
+
def execute_raw(self, query: str, params: tuple = ()) -> List[Dict[str, Any]]:
|
|
152
|
+
"""Execute raw SQL query with proper error handling"""
|
|
153
|
+
try:
|
|
154
|
+
if self.mode == 'sqlalchemy':
|
|
155
|
+
with self.session_scope() as session:
|
|
156
|
+
result = session.execute(query, params)
|
|
157
|
+
if query.strip().upper().startswith('SELECT'):
|
|
158
|
+
return [dict(row) for row in result.fetchall()]
|
|
159
|
+
else:
|
|
160
|
+
return [{'affected_rows': result.rowcount}]
|
|
161
|
+
else:
|
|
162
|
+
return self.legacy_db.execute(query, params)
|
|
163
|
+
|
|
164
|
+
except SQLAlchemyError as e:
|
|
165
|
+
logger.error(f"Database query failed: {query[:100]}... Error: {e}")
|
|
166
|
+
raise DatabaseError(f"Query execution failed: {str(e)}") from e
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.error(f"Unexpected error executing query: {query[:100]}... Error: {e}")
|
|
169
|
+
raise DatabaseError(f"Unexpected database error: {str(e)}") from e
|
|
170
|
+
|
|
171
|
+
def create_tables(self, models: List[Type[Any]] = None):
|
|
172
|
+
"""Create database tables"""
|
|
173
|
+
if self.mode == 'sqlalchemy':
|
|
174
|
+
if models:
|
|
175
|
+
# Create tables for specific models
|
|
176
|
+
for model in models:
|
|
177
|
+
model.metadata.create_all(bind=self.engine)
|
|
178
|
+
else:
|
|
179
|
+
# Create all tables
|
|
180
|
+
self.Base.metadata.create_all(bind=self.engine)
|
|
181
|
+
else:
|
|
182
|
+
# Legacy implementation already creates tables
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
def drop_tables(self, models: List[Type[Any]] = None):
|
|
186
|
+
"""Drop database tables"""
|
|
187
|
+
if self.mode == 'sqlalchemy':
|
|
188
|
+
if models:
|
|
189
|
+
for model in models:
|
|
190
|
+
model.metadata.drop_all(bind=self.engine)
|
|
191
|
+
else:
|
|
192
|
+
self.Base.metadata.drop_all(bind=self.engine)
|
|
193
|
+
else:
|
|
194
|
+
# For legacy, would need to implement table dropping
|
|
195
|
+
logger.warning("Table dropping not implemented in legacy mode")
|
|
196
|
+
|
|
197
|
+
def get_table_names(self) -> List[str]:
|
|
198
|
+
"""Get list of table names"""
|
|
199
|
+
if self.mode == 'sqlalchemy':
|
|
200
|
+
self.metadata.reflect(bind=self.engine)
|
|
201
|
+
return list(self.metadata.tables.keys())
|
|
202
|
+
else:
|
|
203
|
+
# For SQLite legacy implementation
|
|
204
|
+
result = self.legacy_db.execute(
|
|
205
|
+
"SELECT name FROM sqlite_master WHERE type='table'"
|
|
206
|
+
)
|
|
207
|
+
return [row['name'] for row in result]
|
|
208
|
+
|
|
209
|
+
def table_exists(self, table_name: str) -> bool:
|
|
210
|
+
"""Check if table exists"""
|
|
211
|
+
return table_name in self.get_table_names()
|
|
212
|
+
|
|
213
|
+
def get_connection_info(self) -> Dict[str, Any]:
|
|
214
|
+
"""Get database connection information"""
|
|
215
|
+
return {
|
|
216
|
+
'database_url': self.database_url,
|
|
217
|
+
'mode': self.mode,
|
|
218
|
+
'sqlalchemy_available': SQLALCHEMY_AVAILABLE,
|
|
219
|
+
'tables': self.get_table_names()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class Connection:
|
|
224
|
+
"""Database connection wrapper"""
|
|
225
|
+
|
|
226
|
+
def __init__(self, database: Database):
|
|
227
|
+
self.database = database
|
|
228
|
+
self.session = database.get_session()
|
|
229
|
+
|
|
230
|
+
def execute(self, query: str, params: tuple = ()) -> List[Dict[str, Any]]:
|
|
231
|
+
"""Execute SQL query"""
|
|
232
|
+
return self.database.execute_raw(query, params)
|
|
233
|
+
|
|
234
|
+
def commit(self):
|
|
235
|
+
"""Commit transaction"""
|
|
236
|
+
if hasattr(self.session, 'commit'):
|
|
237
|
+
self.session.commit()
|
|
238
|
+
|
|
239
|
+
def rollback(self):
|
|
240
|
+
"""Rollback transaction"""
|
|
241
|
+
if hasattr(self.session, 'rollback'):
|
|
242
|
+
self.session.rollback()
|
|
243
|
+
|
|
244
|
+
def close(self):
|
|
245
|
+
"""Close connection"""
|
|
246
|
+
if hasattr(self.session, 'close'):
|
|
247
|
+
self.session.close()
|
|
248
|
+
|
|
249
|
+
def __enter__(self):
|
|
250
|
+
return self
|
|
251
|
+
|
|
252
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
253
|
+
if exc_type:
|
|
254
|
+
self.rollback()
|
|
255
|
+
else:
|
|
256
|
+
self.commit()
|
|
257
|
+
self.close()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class Transaction:
|
|
261
|
+
"""Database transaction wrapper"""
|
|
262
|
+
|
|
263
|
+
def __init__(self, database: Database):
|
|
264
|
+
self.database = database
|
|
265
|
+
self.session = None
|
|
266
|
+
|
|
267
|
+
def __enter__(self):
|
|
268
|
+
self.session = self.database.get_session()
|
|
269
|
+
if hasattr(self.session, 'begin'):
|
|
270
|
+
self.transaction = self.session.begin()
|
|
271
|
+
return self.session
|
|
272
|
+
|
|
273
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
274
|
+
if exc_type:
|
|
275
|
+
if hasattr(self.session, 'rollback'):
|
|
276
|
+
self.session.rollback()
|
|
277
|
+
else:
|
|
278
|
+
if hasattr(self.session, 'commit'):
|
|
279
|
+
self.session.commit()
|
|
280
|
+
|
|
281
|
+
if hasattr(self.session, 'close'):
|
|
282
|
+
self.session.close()
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class LegacySession:
|
|
286
|
+
"""Wrapper for legacy database implementation to provide session-like interface"""
|
|
287
|
+
|
|
288
|
+
def __init__(self, legacy_db):
|
|
289
|
+
self.legacy_db = legacy_db
|
|
290
|
+
self._changes = []
|
|
291
|
+
self._dirty = False
|
|
292
|
+
|
|
293
|
+
def execute(self, query: str, params: tuple = ()):
|
|
294
|
+
"""Execute query"""
|
|
295
|
+
return self.legacy_db.execute(query, params)
|
|
296
|
+
|
|
297
|
+
def add(self, instance):
|
|
298
|
+
"""Add instance with proper change tracking"""
|
|
299
|
+
self._changes.append(('add', instance))
|
|
300
|
+
self._dirty = True
|
|
301
|
+
|
|
302
|
+
def delete(self, instance):
|
|
303
|
+
"""Delete instance with proper change tracking"""
|
|
304
|
+
self._changes.append(('delete', instance))
|
|
305
|
+
self._dirty = True
|
|
306
|
+
|
|
307
|
+
def commit(self):
|
|
308
|
+
"""Commit changes - flush pending operations"""
|
|
309
|
+
if not self._dirty:
|
|
310
|
+
return
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
for operation, instance in self._changes:
|
|
315
|
+
if operation == 'add':
|
|
316
|
+
self._flush_add(instance)
|
|
317
|
+
elif operation == 'delete':
|
|
318
|
+
self._flush_delete(instance)
|
|
319
|
+
|
|
320
|
+
self._changes.clear()
|
|
321
|
+
self._dirty = False
|
|
322
|
+
|
|
323
|
+
except Exception as e:
|
|
324
|
+
logger.error(f"Failed to commit changes: {e}")
|
|
325
|
+
raise
|
|
326
|
+
|
|
327
|
+
def _flush_add(self, instance):
|
|
328
|
+
"""Flush add operation to database"""
|
|
329
|
+
# This is a simplified implementation
|
|
330
|
+
# In a real ORM, this would use the instance's metadata
|
|
331
|
+
table_name = getattr(instance.__class__, '__tablename__', 'unknown')
|
|
332
|
+
|
|
333
|
+
# Extract data from instance
|
|
334
|
+
data = {}
|
|
335
|
+
for attr_name in dir(instance):
|
|
336
|
+
if not attr_name.startswith('_') and not callable(getattr(instance, attr_name)):
|
|
337
|
+
value = getattr(instance, attr_name)
|
|
338
|
+
if value is not None:
|
|
339
|
+
data[attr_name] = value
|
|
340
|
+
|
|
341
|
+
if data:
|
|
342
|
+
result = self.legacy_db.insert(table_name, data)
|
|
343
|
+
|
|
344
|
+
def _flush_delete(self, instance):
|
|
345
|
+
"""Flush delete operation to database"""
|
|
346
|
+
table_name = getattr(instance.__class__, '__tablename__', 'unknown')
|
|
347
|
+
|
|
348
|
+
# Use ID if available
|
|
349
|
+
if hasattr(instance, 'id') and instance.id:
|
|
350
|
+
result = self.legacy_db.delete(table_name, {'id': instance.id})
|
|
351
|
+
|
|
352
|
+
def rollback(self):
|
|
353
|
+
"""Rollback changes"""
|
|
354
|
+
self._changes.clear()
|
|
355
|
+
self._dirty = False
|
|
356
|
+
|
|
357
|
+
def close(self):
|
|
358
|
+
"""Close session"""
|
|
359
|
+
if self._dirty:
|
|
360
|
+
logger.warning("Closing session with uncommitted changes")
|
|
361
|
+
self._changes.clear()
|
|
362
|
+
|
|
363
|
+
def query(self, model_class):
|
|
364
|
+
"""Create query (basic implementation)"""
|
|
365
|
+
return LegacyQuery(self.legacy_db, model_class)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class LegacyQuery:
|
|
369
|
+
"""Legacy query implementation for compatibility"""
|
|
370
|
+
|
|
371
|
+
def __init__(self, legacy_db, model_class):
|
|
372
|
+
self.legacy_db = legacy_db
|
|
373
|
+
self.model_class = model_class
|
|
374
|
+
self._filters = []
|
|
375
|
+
self._limit = None
|
|
376
|
+
self._offset = None
|
|
377
|
+
self._order_by = []
|
|
378
|
+
|
|
379
|
+
def filter(self, *criteria):
|
|
380
|
+
"""Add filter criteria"""
|
|
381
|
+
self._filters.extend(criteria)
|
|
382
|
+
return self
|
|
383
|
+
|
|
384
|
+
def filter_by(self, **kwargs):
|
|
385
|
+
"""Add filter by keyword arguments"""
|
|
386
|
+
for key, value in kwargs.items():
|
|
387
|
+
self._filters.append(f"{key} = ?")
|
|
388
|
+
return self
|
|
389
|
+
|
|
390
|
+
def limit(self, limit_value):
|
|
391
|
+
"""Add limit"""
|
|
392
|
+
self._limit = limit_value
|
|
393
|
+
return self
|
|
394
|
+
|
|
395
|
+
def offset(self, offset_value):
|
|
396
|
+
"""Add offset"""
|
|
397
|
+
self._offset = offset_value
|
|
398
|
+
return self
|
|
399
|
+
|
|
400
|
+
def order_by(self, *columns):
|
|
401
|
+
"""Add order by"""
|
|
402
|
+
self._order_by.extend(columns)
|
|
403
|
+
return self
|
|
404
|
+
|
|
405
|
+
def first(self):
|
|
406
|
+
"""Get first result"""
|
|
407
|
+
results = self.limit(1).all()
|
|
408
|
+
return results[0] if results else None
|
|
409
|
+
|
|
410
|
+
def all(self):
|
|
411
|
+
"""Get all results"""
|
|
412
|
+
# This is a simplified implementation
|
|
413
|
+
# In practice, would need to parse model class and build proper query
|
|
414
|
+
table_name = getattr(self.model_class, '__tablename__', 'unknown')
|
|
415
|
+
query = f"SELECT * FROM {table_name}"
|
|
416
|
+
|
|
417
|
+
if self._filters:
|
|
418
|
+
query += " WHERE " + " AND ".join(self._filters)
|
|
419
|
+
|
|
420
|
+
if self._order_by:
|
|
421
|
+
query += " ORDER BY " + ", ".join(str(col) for col in self._order_by)
|
|
422
|
+
|
|
423
|
+
if self._limit:
|
|
424
|
+
query += f" LIMIT {self._limit}"
|
|
425
|
+
|
|
426
|
+
if self._offset:
|
|
427
|
+
query += f" OFFSET {self._offset}"
|
|
428
|
+
|
|
429
|
+
return self.legacy_db.execute(query)
|
|
430
|
+
|
|
431
|
+
def count(self):
|
|
432
|
+
"""Count results"""
|
|
433
|
+
table_name = getattr(self.model_class, '__tablename__', 'unknown')
|
|
434
|
+
query = f"SELECT COUNT(*) as count FROM {table_name}"
|
|
435
|
+
|
|
436
|
+
if self._filters:
|
|
437
|
+
query += " WHERE " + " AND ".join(self._filters)
|
|
438
|
+
|
|
439
|
+
result = self.legacy_db.execute(query)
|
|
440
|
+
return result[0]['count'] if result else 0
|