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.
Files changed (152) hide show
  1. createsonline/__init__.py +46 -0
  2. createsonline/admin/__init__.py +7 -0
  3. createsonline/admin/content.py +526 -0
  4. createsonline/admin/crud.py +805 -0
  5. createsonline/admin/field_builder.py +559 -0
  6. createsonline/admin/integration.py +482 -0
  7. createsonline/admin/interface.py +2562 -0
  8. createsonline/admin/model_creator.py +513 -0
  9. createsonline/admin/model_manager.py +388 -0
  10. createsonline/admin/modern_dashboard.py +498 -0
  11. createsonline/admin/permissions.py +264 -0
  12. createsonline/admin/user_forms.py +594 -0
  13. createsonline/ai/__init__.py +202 -0
  14. createsonline/ai/fields.py +1226 -0
  15. createsonline/ai/orm.py +325 -0
  16. createsonline/ai/services.py +1244 -0
  17. createsonline/app.py +506 -0
  18. createsonline/auth/__init__.py +8 -0
  19. createsonline/auth/management.py +228 -0
  20. createsonline/auth/models.py +552 -0
  21. createsonline/cli/__init__.py +5 -0
  22. createsonline/cli/commands/__init__.py +122 -0
  23. createsonline/cli/commands/database.py +416 -0
  24. createsonline/cli/commands/info.py +173 -0
  25. createsonline/cli/commands/initdb.py +218 -0
  26. createsonline/cli/commands/project.py +545 -0
  27. createsonline/cli/commands/serve.py +173 -0
  28. createsonline/cli/commands/shell.py +93 -0
  29. createsonline/cli/commands/users.py +148 -0
  30. createsonline/cli/main.py +2041 -0
  31. createsonline/cli/manage.py +274 -0
  32. createsonline/config/__init__.py +9 -0
  33. createsonline/config/app.py +2577 -0
  34. createsonline/config/database.py +179 -0
  35. createsonline/config/docs.py +384 -0
  36. createsonline/config/errors.py +160 -0
  37. createsonline/config/orm.py +43 -0
  38. createsonline/config/request.py +93 -0
  39. createsonline/config/settings.py +176 -0
  40. createsonline/data/__init__.py +23 -0
  41. createsonline/data/dataframe.py +925 -0
  42. createsonline/data/io.py +453 -0
  43. createsonline/data/series.py +557 -0
  44. createsonline/database/__init__.py +60 -0
  45. createsonline/database/abstraction.py +440 -0
  46. createsonline/database/assistant.py +585 -0
  47. createsonline/database/fields.py +442 -0
  48. createsonline/database/migrations.py +132 -0
  49. createsonline/database/models.py +604 -0
  50. createsonline/database.py +438 -0
  51. createsonline/http/__init__.py +28 -0
  52. createsonline/http/client.py +535 -0
  53. createsonline/ml/__init__.py +55 -0
  54. createsonline/ml/classification.py +552 -0
  55. createsonline/ml/clustering.py +680 -0
  56. createsonline/ml/metrics.py +542 -0
  57. createsonline/ml/neural.py +560 -0
  58. createsonline/ml/preprocessing.py +784 -0
  59. createsonline/ml/regression.py +501 -0
  60. createsonline/performance/__init__.py +19 -0
  61. createsonline/performance/cache.py +444 -0
  62. createsonline/performance/compression.py +335 -0
  63. createsonline/performance/core.py +419 -0
  64. createsonline/project_init.py +789 -0
  65. createsonline/routing.py +528 -0
  66. createsonline/security/__init__.py +34 -0
  67. createsonline/security/core.py +811 -0
  68. createsonline/security/encryption.py +349 -0
  69. createsonline/server.py +295 -0
  70. createsonline/static/css/admin.css +263 -0
  71. createsonline/static/css/common.css +358 -0
  72. createsonline/static/css/dashboard.css +89 -0
  73. createsonline/static/favicon.ico +0 -0
  74. createsonline/static/icons/icon-128x128.png +0 -0
  75. createsonline/static/icons/icon-128x128.webp +0 -0
  76. createsonline/static/icons/icon-16x16.png +0 -0
  77. createsonline/static/icons/icon-16x16.webp +0 -0
  78. createsonline/static/icons/icon-180x180.png +0 -0
  79. createsonline/static/icons/icon-180x180.webp +0 -0
  80. createsonline/static/icons/icon-192x192.png +0 -0
  81. createsonline/static/icons/icon-192x192.webp +0 -0
  82. createsonline/static/icons/icon-256x256.png +0 -0
  83. createsonline/static/icons/icon-256x256.webp +0 -0
  84. createsonline/static/icons/icon-32x32.png +0 -0
  85. createsonline/static/icons/icon-32x32.webp +0 -0
  86. createsonline/static/icons/icon-384x384.png +0 -0
  87. createsonline/static/icons/icon-384x384.webp +0 -0
  88. createsonline/static/icons/icon-48x48.png +0 -0
  89. createsonline/static/icons/icon-48x48.webp +0 -0
  90. createsonline/static/icons/icon-512x512.png +0 -0
  91. createsonline/static/icons/icon-512x512.webp +0 -0
  92. createsonline/static/icons/icon-64x64.png +0 -0
  93. createsonline/static/icons/icon-64x64.webp +0 -0
  94. createsonline/static/image/android-chrome-192x192.png +0 -0
  95. createsonline/static/image/android-chrome-512x512.png +0 -0
  96. createsonline/static/image/apple-touch-icon.png +0 -0
  97. createsonline/static/image/favicon-16x16.png +0 -0
  98. createsonline/static/image/favicon-32x32.png +0 -0
  99. createsonline/static/image/favicon.ico +0 -0
  100. createsonline/static/image/favicon.svg +17 -0
  101. createsonline/static/image/icon-128x128.png +0 -0
  102. createsonline/static/image/icon-128x128.webp +0 -0
  103. createsonline/static/image/icon-16x16.png +0 -0
  104. createsonline/static/image/icon-16x16.webp +0 -0
  105. createsonline/static/image/icon-180x180.png +0 -0
  106. createsonline/static/image/icon-180x180.webp +0 -0
  107. createsonline/static/image/icon-192x192.png +0 -0
  108. createsonline/static/image/icon-192x192.webp +0 -0
  109. createsonline/static/image/icon-256x256.png +0 -0
  110. createsonline/static/image/icon-256x256.webp +0 -0
  111. createsonline/static/image/icon-32x32.png +0 -0
  112. createsonline/static/image/icon-32x32.webp +0 -0
  113. createsonline/static/image/icon-384x384.png +0 -0
  114. createsonline/static/image/icon-384x384.webp +0 -0
  115. createsonline/static/image/icon-48x48.png +0 -0
  116. createsonline/static/image/icon-48x48.webp +0 -0
  117. createsonline/static/image/icon-512x512.png +0 -0
  118. createsonline/static/image/icon-512x512.webp +0 -0
  119. createsonline/static/image/icon-64x64.png +0 -0
  120. createsonline/static/image/icon-64x64.webp +0 -0
  121. createsonline/static/image/logo-header-h100.png +0 -0
  122. createsonline/static/image/logo-header-h100.webp +0 -0
  123. createsonline/static/image/logo-header-h200@2x.png +0 -0
  124. createsonline/static/image/logo-header-h200@2x.webp +0 -0
  125. createsonline/static/image/logo.png +0 -0
  126. createsonline/static/js/admin.js +274 -0
  127. createsonline/static/site.webmanifest +35 -0
  128. createsonline/static/templates/admin/base.html +87 -0
  129. createsonline/static/templates/admin/dashboard.html +217 -0
  130. createsonline/static/templates/admin/model_form.html +270 -0
  131. createsonline/static/templates/admin/model_list.html +202 -0
  132. createsonline/static/test_script.js +15 -0
  133. createsonline/static/test_styles.css +59 -0
  134. createsonline/static_files.py +365 -0
  135. createsonline/templates/404.html +100 -0
  136. createsonline/templates/admin_login.html +169 -0
  137. createsonline/templates/base.html +102 -0
  138. createsonline/templates/index.html +151 -0
  139. createsonline/templates.py +205 -0
  140. createsonline/testing.py +322 -0
  141. createsonline/utils.py +448 -0
  142. createsonline/validation/__init__.py +49 -0
  143. createsonline/validation/fields.py +598 -0
  144. createsonline/validation/models.py +504 -0
  145. createsonline/validation/validators.py +561 -0
  146. createsonline/views.py +184 -0
  147. createsonline-0.1.26.dist-info/METADATA +46 -0
  148. createsonline-0.1.26.dist-info/RECORD +152 -0
  149. createsonline-0.1.26.dist-info/WHEEL +5 -0
  150. createsonline-0.1.26.dist-info/entry_points.txt +2 -0
  151. createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
  152. createsonline-0.1.26.dist-info/top_level.txt +1 -0
@@ -0,0 +1,585 @@
1
+ # createsonline/database/assistant.py
2
+ """
3
+ CREATESONLINE Database Assistant - AI-Powered SQL Generation
4
+
5
+ Converts natural language prompts into safe SQLAlchemy queries.
6
+ Focuses on SELECT operations with strict safety guards against destructive operations.
7
+
8
+ Features:
9
+ - Natural language → SQLAlchemy object conversion
10
+ - Safety filters for destructive operations
11
+ - Audit logging for all operations
12
+ - Rollback capabilities
13
+
14
+ Usage:
15
+ assistant = DatabaseAssistant(db_connection)
16
+ query = assistant.generate_query("show last 10 error logs")
17
+ result = assistant.execute_safe(query)
18
+ """
19
+
20
+ import re
21
+ import logging
22
+ from typing import Dict, Any, List, Optional, Tuple, Union
23
+ from datetime import datetime, timedelta
24
+ from enum import Enum
25
+ import json
26
+
27
+ # Core imports - using absolute imports to avoid relative import issues
28
+ try:
29
+ from createsonline.database import DatabaseConnection
30
+ except ImportError:
31
+ # Fallback for direct execution
32
+ import sys
33
+ import os
34
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
35
+
36
+ import importlib.util
37
+ db_spec = importlib.util.spec_from_file_location(
38
+ "database_connection",
39
+ os.path.join(os.path.dirname(__file__), '..', 'database.py')
40
+ )
41
+ db_module = importlib.util.module_from_spec(db_spec)
42
+ db_spec.loader.exec_module(db_module)
43
+ DatabaseConnection = db_module.DatabaseConnection
44
+
45
+ # Setup logging
46
+ logger = logging.getLogger("createsonline.database.assistant")
47
+
48
+ class QueryType(Enum):
49
+ """Supported query types with safety levels"""
50
+ SELECT = "select" # Always safe
51
+ COUNT = "count" # Always safe
52
+ UPDATE = "update" # Requires confirmation
53
+ INSERT = "insert" # Requires confirmation
54
+ DELETE = "delete" # Requires explicit approval
55
+ DROP = "drop" # Blocked by default
56
+ CREATE = "create" # Requires confirmation
57
+
58
+
59
+ class SafetyLevel(Enum):
60
+ """Safety levels for different operations"""
61
+ SAFE = "safe" # No confirmation needed
62
+ CONFIRMATION = "confirm" # Requires user confirmation
63
+ EXPLICIT = "explicit" # Requires explicit --allow-destructive flag
64
+ BLOCKED = "blocked" # Always blocked
65
+
66
+
67
+ class DatabaseAssistant:
68
+ """AI-powered database query assistant with safety controls"""
69
+
70
+ def __init__(self, db, safety_mode: str = "strict"):
71
+ self.db = db
72
+ self.safety_mode = safety_mode
73
+
74
+ # Safety mappings
75
+ self.safety_map = {
76
+ QueryType.SELECT: SafetyLevel.SAFE,
77
+ QueryType.COUNT: SafetyLevel.SAFE,
78
+ QueryType.UPDATE: SafetyLevel.CONFIRMATION,
79
+ QueryType.INSERT: SafetyLevel.CONFIRMATION,
80
+ QueryType.DELETE: SafetyLevel.EXPLICIT,
81
+ QueryType.DROP: SafetyLevel.BLOCKED,
82
+ QueryType.CREATE: SafetyLevel.CONFIRMATION
83
+ }
84
+
85
+ # Natural language patterns for different query types
86
+ self.nl_patterns = {
87
+ QueryType.DROP: [
88
+ r'\b(drop)\b.*\b(table|database|index)\b',
89
+ r'\b(drop table|drop database|drop index)\b'
90
+ ],
91
+ QueryType.DELETE: [
92
+ r'\b(delete)\b.*\b(from|users?|records?|entries?)\b',
93
+ r'\b(remove|drop)\b.*\b(user|record|entry)\b'
94
+ ],
95
+ QueryType.UPDATE: [
96
+ r'\b(update)\b.*\b(set|users?|records?)\b',
97
+ r'\b(modify|change|set)\b.*\b(password|email|status)\b',
98
+ r'\b(make|turn)\b.*\b(active|inactive)\b'
99
+ ],
100
+ QueryType.INSERT: [
101
+ r'\b(insert)\b.*\b(into|values?|users?)\b',
102
+ r'\b(add|create|new)\b.*\b(user|record|entry)\b'
103
+ ],
104
+ QueryType.COUNT: [
105
+ r'\bhow many\b',
106
+ r'\bcount\b.*\b(users|records|entries|rows)\b',
107
+ r'\btotal\b.*\b(number|count)\b'
108
+ ],
109
+ QueryType.SELECT: [
110
+ r'\b(show|display|list|get|find|fetch|retrieve)\b',
111
+ r'\b(what|which)\b.*\b(users|records|entries|rows)\b',
112
+ r'\b(last|recent|latest)\b.*\b(\d+)\b',
113
+ r'\bselect\b'
114
+ ]
115
+ }
116
+
117
+ # Common table mapping for natural language
118
+ self.table_mapping = {
119
+ 'users': 'createsonline_users',
120
+ 'user': 'createsonline_users',
121
+ 'sessions': 'admin_sessions',
122
+ 'session': 'admin_sessions',
123
+ 'settings': 'app_settings',
124
+ 'setting': 'app_settings',
125
+ 'conversations': 'ai_conversations',
126
+ 'conversation': 'ai_conversations',
127
+ 'logs': 'audit_logs',
128
+ 'log': 'audit_logs',
129
+ 'errors': 'audit_logs',
130
+ 'error': 'audit_logs'
131
+ }
132
+
133
+ # Common column mapping
134
+ self.column_mapping = {
135
+ 'name': 'username',
136
+ 'email': 'email',
137
+ 'active': 'is_active',
138
+ 'staff': 'is_staff',
139
+ 'admin': 'is_superuser',
140
+ 'created': 'date_joined',
141
+ 'joined': 'date_joined',
142
+ 'login': 'last_login'
143
+ }
144
+
145
+ def parse_natural_language(self, prompt: str) -> Dict[str, Any]:
146
+ """Parse natural language prompt into structured query components"""
147
+ prompt_lower = prompt.lower().strip()
148
+
149
+ # Detect query type
150
+ query_type = self._detect_query_type(prompt_lower)
151
+
152
+ # Extract table name
153
+ table_name = self._extract_table_name(prompt_lower)
154
+
155
+ # Extract conditions
156
+ conditions = self._extract_conditions(prompt_lower)
157
+
158
+ # Extract limit/order
159
+ limit = self._extract_limit(prompt_lower)
160
+ order_by = self._extract_order(prompt_lower)
161
+
162
+ # Extract columns (for SELECT)
163
+ columns = self._extract_columns(prompt_lower, query_type)
164
+
165
+ return {
166
+ 'query_type': query_type,
167
+ 'table': table_name,
168
+ 'columns': columns,
169
+ 'conditions': conditions,
170
+ 'limit': limit,
171
+ 'order_by': order_by,
172
+ 'original_prompt': prompt,
173
+ 'safety_level': self.safety_map.get(query_type, SafetyLevel.BLOCKED)
174
+ }
175
+
176
+ def _detect_query_type(self, prompt: str) -> QueryType:
177
+ """Detect the type of query from natural language"""
178
+ for query_type, patterns in self.nl_patterns.items():
179
+ for pattern in patterns:
180
+ if re.search(pattern, prompt, re.IGNORECASE):
181
+ return query_type
182
+
183
+ # Default to SELECT for safety
184
+ return QueryType.SELECT
185
+
186
+ def _extract_table_name(self, prompt: str) -> str:
187
+ """Extract table name from natural language"""
188
+ # Look for known table keywords
189
+ for nl_name, db_name in self.table_mapping.items():
190
+ if re.search(rf'\b{re.escape(nl_name)}\b', prompt):
191
+ return db_name
192
+
193
+ # Default to users table (most common)
194
+ return 'createsonline_users'
195
+
196
+ def _extract_conditions(self, prompt: str) -> List[Dict[str, Any]]:
197
+ """Extract WHERE conditions from natural language"""
198
+ conditions = []
199
+
200
+ # Pattern: "where X is Y" or "with X = Y"
201
+ where_patterns = [
202
+ r'\b(?:where|with)\s+(\w+)\s+(?:is|=|equals?)\s+(["\']?\w+["\']?)',
203
+ r'\b(\w+)\s+(?:is|=|equals?)\s+(["\']?\w+["\']?)',
204
+ r'\bactive\b', # Special case for is_active = true
205
+ r'\binactive\b', # Special case for is_active = false
206
+ r'\badmin\b', # Special case for is_superuser = true
207
+ ]
208
+
209
+ for pattern in where_patterns:
210
+ matches = re.finditer(pattern, prompt, re.IGNORECASE)
211
+ for match in matches:
212
+ if 'active' in match.group().lower():
213
+ conditions.append({
214
+ 'column': 'is_active',
215
+ 'operator': '=',
216
+ 'value': not ('inactive' in match.group().lower())
217
+ })
218
+ elif 'admin' in match.group().lower():
219
+ conditions.append({
220
+ 'column': 'is_superuser',
221
+ 'operator': '=',
222
+ 'value': True
223
+ })
224
+ elif match.groups() and len(match.groups()) >= 2:
225
+ column = self.column_mapping.get(match.group(1), match.group(1))
226
+ value = match.group(2).strip('"\'')
227
+ conditions.append({
228
+ 'column': column,
229
+ 'operator': '=',
230
+ 'value': value
231
+ })
232
+
233
+ return conditions
234
+
235
+ def _extract_limit(self, prompt: str) -> Optional[int]:
236
+ """Extract LIMIT from natural language"""
237
+ # Pattern: "last 10", "first 5", "limit 20"
238
+ limit_patterns = [
239
+ r'\b(?:last|first|top|limit)\s+(\d+)\b',
240
+ r'\b(\d+)\s+(?:users|records|entries|rows)\b'
241
+ ]
242
+
243
+ for pattern in limit_patterns:
244
+ match = re.search(pattern, prompt)
245
+ if match:
246
+ return int(match.group(1))
247
+
248
+ return None
249
+
250
+ def _extract_order(self, prompt: str) -> Optional[Dict[str, str]]:
251
+ """Extract ORDER BY from natural language"""
252
+ order_patterns = [
253
+ r'\b(?:order by|sort by)\s+(\w+)\s*(asc|desc)?\b',
254
+ r'\b(newest|latest|recent)\b', # ORDER BY created DESC
255
+ r'\b(oldest|first)\b', # ORDER BY created ASC
256
+ ]
257
+
258
+ for pattern in order_patterns:
259
+ match = re.search(pattern, prompt, re.IGNORECASE)
260
+ if match:
261
+ if 'newest' in match.group().lower() or 'latest' in match.group().lower() or 'recent' in match.group().lower():
262
+ return {'column': 'date_joined', 'direction': 'DESC'}
263
+ elif 'oldest' in match.group().lower() or 'first' in match.group().lower():
264
+ return {'column': 'date_joined', 'direction': 'ASC'}
265
+ else:
266
+ column = self.column_mapping.get(match.group(1), match.group(1))
267
+ direction = match.group(2).upper() if len(match.groups()) > 1 and match.group(2) else 'ASC'
268
+ return {'column': column, 'direction': direction}
269
+
270
+ return None
271
+
272
+ def _extract_columns(self, prompt: str, query_type: QueryType) -> List[str]:
273
+ """Extract columns to select"""
274
+ if query_type == QueryType.COUNT:
275
+ return ['COUNT(*)']
276
+
277
+ # Look for specific column mentions
278
+ columns = []
279
+ column_patterns = [
280
+ r'\b(username|email|name)\b',
281
+ r'\b(first_name|last_name|full name)\b',
282
+ r'\b(active|status)\b',
283
+ r'\b(admin|superuser)\b',
284
+ r'\b(joined|created|date)\b'
285
+ ]
286
+
287
+ for pattern in column_patterns:
288
+ if re.search(pattern, prompt, re.IGNORECASE):
289
+ match = re.search(pattern, prompt, re.IGNORECASE).group()
290
+ mapped_column = self.column_mapping.get(match.lower(), match.lower())
291
+ if mapped_column not in columns:
292
+ columns.append(mapped_column)
293
+
294
+ # Default to all columns if none specified
295
+ if not columns:
296
+ columns = ['*']
297
+
298
+ return columns
299
+
300
+ def generate_sql(self, natural_query_or_parsed) -> str:
301
+ """Generate safe SQL from natural language query or parsed query
302
+
303
+ Args:
304
+ natural_query_or_parsed: Either a string (natural language) or dict (parsed query)
305
+
306
+ Returns:
307
+ SQL string
308
+ """
309
+ # Handle both string input and parsed dict input
310
+ if isinstance(natural_query_or_parsed, str):
311
+ parsed_query = self.parse_natural_language(natural_query_or_parsed)
312
+ else:
313
+ parsed_query = natural_query_or_parsed
314
+
315
+ query_type = parsed_query['query_type']
316
+ table = parsed_query['table']
317
+ columns = parsed_query['columns']
318
+ conditions = parsed_query['conditions']
319
+ limit = parsed_query['limit']
320
+ order_by = parsed_query['order_by']
321
+ safety_level = parsed_query['safety_level']
322
+
323
+ # Safety check - block dangerous operations
324
+ if safety_level == SafetyLevel.BLOCKED:
325
+ raise ValueError(f"Query type {query_type.value} is blocked for safety reasons")
326
+
327
+ if safety_level in [SafetyLevel.EXPLICIT, SafetyLevel.CONFIRMATION]:
328
+ raise ValueError(f"Query type {query_type.value} requires {safety_level.value} approval")
329
+
330
+ # Validate table name for safety
331
+ safe_table = self.db._validate_identifier(table)
332
+
333
+ # Build SQL based on query type
334
+ if query_type in [QueryType.SELECT, QueryType.COUNT]:
335
+ # Build SELECT query
336
+ column_str = ', '.join(columns) if columns != ['*'] else '*'
337
+ sql = f"SELECT {column_str} FROM {safe_table}"
338
+
339
+ # Add WHERE conditions
340
+ if conditions:
341
+ where_parts = []
342
+ for condition in conditions:
343
+ safe_column = self.db._validate_identifier(condition['column'])
344
+ if isinstance(condition['value'], bool):
345
+ value_str = 'TRUE' if condition['value'] else 'FALSE'
346
+ elif isinstance(condition['value'], str):
347
+ value_str = f"'{condition['value']}'"
348
+ else:
349
+ value_str = str(condition['value'])
350
+
351
+ where_parts.append(f"{safe_column} {condition['operator']} {value_str}")
352
+
353
+ sql += " WHERE " + " AND ".join(where_parts)
354
+
355
+ # Add ORDER BY
356
+ if order_by:
357
+ safe_order_column = self.db._validate_identifier(order_by['column'])
358
+ sql += f" ORDER BY {safe_order_column} {order_by['direction']}"
359
+
360
+ # Add LIMIT
361
+ if limit:
362
+ sql += f" LIMIT {limit}"
363
+
364
+ else:
365
+ # For now, only support SELECT queries in safe mode
366
+ raise ValueError(f"Query type {query_type.value} requires explicit approval")
367
+
368
+ return sql
369
+
370
+ def execute_safe(self, sql: str, parsed_query: Dict[str, Any] = None) -> Dict[str, Any]:
371
+ """Execute SQL with safety checks and audit logging"""
372
+
373
+ # Safety check - only allow SELECT statements in safe mode
374
+ sql_upper = sql.strip().upper()
375
+ if not sql_upper.startswith('SELECT'):
376
+ raise ValueError("Only SELECT queries are allowed in safe mode")
377
+
378
+ # Log the query attempt
379
+ audit_entry = {
380
+ 'timestamp': datetime.now().isoformat(),
381
+ 'query_type': 'SELECT',
382
+ 'sql': sql,
383
+ 'original_prompt': parsed_query.get('original_prompt', '') if parsed_query else '',
384
+ 'status': 'pending'
385
+ }
386
+
387
+ try:
388
+ # Execute the query
389
+ result = self.db.execute(sql)
390
+
391
+ # Update audit log
392
+ audit_entry['status'] = 'success'
393
+ audit_entry['rows_returned'] = len(result)
394
+
395
+ # Log successful execution
396
+ self._log_audit_entry(audit_entry)
397
+
398
+ return {
399
+ 'success': True,
400
+ 'data': result,
401
+ 'sql': sql,
402
+ 'rows_returned': len(result),
403
+ 'execution_time': audit_entry['timestamp']
404
+ }
405
+
406
+ except Exception as e:
407
+ # Update audit log with error
408
+ audit_entry['status'] = 'error'
409
+ audit_entry['error'] = str(e)
410
+
411
+ # Log failed execution
412
+ self._log_audit_entry(audit_entry)
413
+
414
+ return {
415
+ 'success': False,
416
+ 'error': str(e),
417
+ 'sql': sql,
418
+ 'execution_time': audit_entry['timestamp']
419
+ }
420
+
421
+ def _log_audit_entry(self, entry: Dict[str, Any]):
422
+ """Log audit entry to database"""
423
+ try:
424
+ # Create audit_logs table if it doesn't exist
425
+ self._ensure_audit_table()
426
+
427
+ # Insert audit entry
428
+ self.db.insert('audit_logs', {
429
+ 'timestamp': entry['timestamp'],
430
+ 'query_type': entry['query_type'],
431
+ 'sql_query': entry['sql'],
432
+ 'original_prompt': entry['original_prompt'],
433
+ 'status': entry['status'],
434
+ 'rows_affected': entry.get('rows_returned', entry.get('rows_affected', 0)),
435
+ 'error_message': entry.get('error', ''),
436
+ 'user_id': 1 # TODO: Get from current session
437
+ })
438
+
439
+ except Exception as e:
440
+ logger.error(f"Failed to log audit entry: {e}")
441
+
442
+ def _ensure_audit_table(self):
443
+ """Ensure audit_logs table exists"""
444
+ create_sql = f'''
445
+ CREATE TABLE IF NOT EXISTS audit_logs (
446
+ id {'SERIAL' if self.db.db_type == 'postgresql' else 'INTEGER'} PRIMARY KEY{' AUTOINCREMENT' if self.db.db_type == 'sqlite' else ''},
447
+ timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
448
+ query_type VARCHAR(20) NOT NULL,
449
+ sql_query TEXT NOT NULL,
450
+ original_prompt TEXT,
451
+ status VARCHAR(20) NOT NULL,
452
+ rows_affected INTEGER DEFAULT 0,
453
+ error_message TEXT,
454
+ user_id INTEGER REFERENCES createsonline_users(id),
455
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
456
+ )
457
+ '''
458
+
459
+ try:
460
+ cursor = self.db.connection.cursor()
461
+ cursor.execute(create_sql)
462
+ self.db.connection.commit()
463
+ except Exception as e:
464
+ logger.error(f"Failed to create audit_logs table: {e}")
465
+
466
+ def query_from_natural_language(self, prompt: str) -> Dict[str, Any]:
467
+ """Complete workflow: natural language → SQL → execution → results"""
468
+
469
+ # Parse the natural language prompt
470
+ parsed_query = self.parse_natural_language(prompt)
471
+
472
+ # Check safety level
473
+ safety_level = parsed_query['safety_level']
474
+ if safety_level == SafetyLevel.BLOCKED:
475
+ return {
476
+ 'success': False,
477
+ 'error': f"Query type {parsed_query['query_type'].value} is blocked for safety",
478
+ 'prompt': prompt
479
+ }
480
+
481
+ try:
482
+ # Generate SQL
483
+ sql = self.generate_sql(parsed_query)
484
+
485
+ # Execute safely
486
+ result = self.execute_safe(sql, parsed_query)
487
+
488
+ # Add parsed query info to result
489
+ result['parsed_query'] = parsed_query
490
+ result['prompt'] = prompt
491
+
492
+ return result
493
+
494
+ except Exception as e:
495
+ return {
496
+ 'success': False,
497
+ 'error': str(e),
498
+ 'prompt': prompt,
499
+ 'parsed_query': parsed_query
500
+ }
501
+
502
+ def explain_query(self, prompt: str) -> Dict[str, Any]:
503
+ """Explain what SQL would be generated without executing it"""
504
+ try:
505
+ parsed_query = self.parse_natural_language(prompt)
506
+ sql = self.generate_sql(parsed_query)
507
+
508
+ return {
509
+ 'success': True,
510
+ 'prompt': prompt,
511
+ 'parsed_query': parsed_query,
512
+ 'generated_sql': sql,
513
+ 'explanation': self._generate_explanation(parsed_query, sql),
514
+ 'safety_level': parsed_query['safety_level'].value
515
+ }
516
+
517
+ except Exception as e:
518
+ return {
519
+ 'success': False,
520
+ 'error': str(e),
521
+ 'prompt': prompt
522
+ }
523
+
524
+ def _generate_explanation(self, parsed_query: Dict[str, Any], sql: str) -> str:
525
+ """Generate human-readable explanation of the query"""
526
+ query_type = parsed_query['query_type']
527
+ table = parsed_query['table']
528
+ conditions = parsed_query['conditions']
529
+ limit = parsed_query['limit']
530
+
531
+ explanation = f"This will {query_type.value.upper()} data from the '{table}' table"
532
+
533
+ if conditions:
534
+ condition_strs = []
535
+ for condition in conditions:
536
+ condition_strs.append(f"{condition['column']} {condition['operator']} {condition['value']}")
537
+ explanation += f" where {' and '.join(condition_strs)}"
538
+
539
+ if limit:
540
+ explanation += f", limited to {limit} rows"
541
+
542
+ explanation += f". Generated SQL: {sql}"
543
+
544
+ return explanation
545
+
546
+ def get_recent_queries(self, limit: int = 10) -> List[Dict[str, Any]]:
547
+ """Get recent query history from audit logs"""
548
+ try:
549
+ self._ensure_audit_table()
550
+
551
+ sql = f"""
552
+ SELECT timestamp, original_prompt, sql_query, status, rows_affected, error_message
553
+ FROM audit_logs
554
+ ORDER BY timestamp DESC
555
+ LIMIT {limit}
556
+ """
557
+
558
+ result = self.db.execute(sql)
559
+ return result
560
+
561
+ except Exception as e:
562
+ logger.error(f"Failed to get recent queries: {e}")
563
+ return []
564
+
565
+ # Convenience function for quick access
566
+ def create_assistant(db=None):
567
+ """Create a database assistant instance"""
568
+ if db is None:
569
+ # Import the main database module to get DatabaseConnection
570
+ import sys
571
+ import importlib.util
572
+ import os
573
+
574
+ # Get the path to the main database.py file
575
+ db_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'database.py')
576
+
577
+ # Load the database module
578
+ spec = importlib.util.spec_from_file_location("database_main", db_path)
579
+ db_module = importlib.util.module_from_spec(spec)
580
+ spec.loader.exec_module(db_module)
581
+
582
+ # Get the database connection
583
+ db = db_module.get_database()
584
+
585
+ return DatabaseAssistant(db)