api-mocker 0.4.0__py3-none-any.whl → 0.5.0__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.
- api_mocker/auth_system.py +610 -0
- api_mocker/cli.py +252 -0
- api_mocker/database_integration.py +566 -0
- api_mocker/graphql_mock.py +593 -0
- api_mocker/ml_integration.py +709 -0
- api_mocker/websocket_mock.py +476 -0
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/METADATA +15 -2
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/RECORD +12 -7
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/WHEEL +0 -0
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/entry_points.txt +0 -0
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {api_mocker-0.4.0.dist-info → api_mocker-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database Integration System
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive database integration capabilities including:
|
|
5
|
+
- SQLite, PostgreSQL, and MongoDB support
|
|
6
|
+
- Connection pooling and management
|
|
7
|
+
- Query builders and ORM-like functionality
|
|
8
|
+
- Database migrations and schema management
|
|
9
|
+
- Transaction support
|
|
10
|
+
- Caching and performance optimization
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import sqlite3
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
from typing import Any, Dict, List, Optional, Union, Callable, Tuple
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from enum import Enum
|
|
19
|
+
from datetime import datetime
|
|
20
|
+
import threading
|
|
21
|
+
from contextlib import asynccontextmanager
|
|
22
|
+
import aiosqlite
|
|
23
|
+
import asyncpg
|
|
24
|
+
import motor.motor_asyncio
|
|
25
|
+
from pymongo import MongoClient
|
|
26
|
+
import redis
|
|
27
|
+
import pickle
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DatabaseType(Enum):
|
|
31
|
+
"""Database types"""
|
|
32
|
+
SQLITE = "sqlite"
|
|
33
|
+
POSTGRESQL = "postgresql"
|
|
34
|
+
MONGODB = "mongodb"
|
|
35
|
+
REDIS = "redis"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class QueryOperator(Enum):
|
|
39
|
+
"""Query operators"""
|
|
40
|
+
EQ = "="
|
|
41
|
+
NE = "!="
|
|
42
|
+
GT = ">"
|
|
43
|
+
GTE = ">="
|
|
44
|
+
LT = "<"
|
|
45
|
+
LTE = "<="
|
|
46
|
+
LIKE = "LIKE"
|
|
47
|
+
IN = "IN"
|
|
48
|
+
NOT_IN = "NOT IN"
|
|
49
|
+
IS_NULL = "IS NULL"
|
|
50
|
+
IS_NOT_NULL = "IS NOT NULL"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class DatabaseConfig:
|
|
55
|
+
"""Database configuration"""
|
|
56
|
+
db_type: DatabaseType
|
|
57
|
+
host: str = "localhost"
|
|
58
|
+
port: int = 5432
|
|
59
|
+
database: str = "api_mocker"
|
|
60
|
+
username: str = ""
|
|
61
|
+
password: str = ""
|
|
62
|
+
connection_pool_size: int = 10
|
|
63
|
+
max_connections: int = 100
|
|
64
|
+
timeout: int = 30
|
|
65
|
+
ssl_mode: str = "prefer"
|
|
66
|
+
additional_params: Dict[str, Any] = field(default_factory=dict)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class QueryCondition:
|
|
71
|
+
"""Query condition"""
|
|
72
|
+
field: str
|
|
73
|
+
operator: QueryOperator
|
|
74
|
+
value: Any
|
|
75
|
+
logical_operator: str = "AND" # AND, OR
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class QueryBuilder:
|
|
80
|
+
"""Query builder for database operations"""
|
|
81
|
+
table: str
|
|
82
|
+
conditions: List[QueryCondition] = field(default_factory=list)
|
|
83
|
+
fields: List[str] = field(default_factory=list)
|
|
84
|
+
order_by: List[str] = field(default_factory=list)
|
|
85
|
+
limit: Optional[int] = None
|
|
86
|
+
offset: Optional[int] = None
|
|
87
|
+
joins: List[Dict[str, str]] = field(default_factory=list)
|
|
88
|
+
group_by: List[str] = field(default_factory=list)
|
|
89
|
+
having: List[QueryCondition] = field(default_factory=list)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class SQLiteManager:
|
|
93
|
+
"""SQLite database manager"""
|
|
94
|
+
|
|
95
|
+
def __init__(self, db_path: str = "api_mocker.db"):
|
|
96
|
+
self.db_path = db_path
|
|
97
|
+
self.connection_pool = []
|
|
98
|
+
self.pool_lock = threading.Lock()
|
|
99
|
+
self.max_connections = 10
|
|
100
|
+
|
|
101
|
+
async def get_connection(self) -> aiosqlite.Connection:
|
|
102
|
+
"""Get a database connection from the pool"""
|
|
103
|
+
with self.pool_lock:
|
|
104
|
+
if self.connection_pool:
|
|
105
|
+
return self.connection_pool.pop()
|
|
106
|
+
else:
|
|
107
|
+
return await aiosqlite.connect(self.db_path)
|
|
108
|
+
|
|
109
|
+
async def return_connection(self, conn: aiosqlite.Connection) -> None:
|
|
110
|
+
"""Return a connection to the pool"""
|
|
111
|
+
with self.pool_lock:
|
|
112
|
+
if len(self.connection_pool) < self.max_connections:
|
|
113
|
+
self.connection_pool.append(conn)
|
|
114
|
+
else:
|
|
115
|
+
await conn.close()
|
|
116
|
+
|
|
117
|
+
async def execute_query(self, query: str, params: Tuple = ()) -> List[Dict[str, Any]]:
|
|
118
|
+
"""Execute a query and return results"""
|
|
119
|
+
conn = await self.get_connection()
|
|
120
|
+
try:
|
|
121
|
+
cursor = await conn.execute(query, params)
|
|
122
|
+
rows = await cursor.fetchall()
|
|
123
|
+
columns = [description[0] for description in cursor.description] if cursor.description else []
|
|
124
|
+
return [dict(zip(columns, row)) for row in rows]
|
|
125
|
+
finally:
|
|
126
|
+
await self.return_connection(conn)
|
|
127
|
+
|
|
128
|
+
async def execute_update(self, query: str, params: Tuple = ()) -> int:
|
|
129
|
+
"""Execute an update query and return affected rows"""
|
|
130
|
+
conn = await self.get_connection()
|
|
131
|
+
try:
|
|
132
|
+
cursor = await conn.execute(query, params)
|
|
133
|
+
await conn.commit()
|
|
134
|
+
return cursor.rowcount
|
|
135
|
+
finally:
|
|
136
|
+
await self.return_connection(conn)
|
|
137
|
+
|
|
138
|
+
async def create_table(self, table_name: str, schema: Dict[str, str]) -> None:
|
|
139
|
+
"""Create a table with the given schema"""
|
|
140
|
+
columns = [f"{name} {type_def}" for name, type_def in schema.items()]
|
|
141
|
+
query = f"CREATE TABLE IF NOT EXISTS {table_name} ({', '.join(columns)})"
|
|
142
|
+
await self.execute_update(query)
|
|
143
|
+
|
|
144
|
+
async def insert_record(self, table_name: str, data: Dict[str, Any]) -> int:
|
|
145
|
+
"""Insert a record and return the ID"""
|
|
146
|
+
fields = list(data.keys())
|
|
147
|
+
placeholders = ["?" for _ in fields]
|
|
148
|
+
query = f"INSERT INTO {table_name} ({', '.join(fields)}) VALUES ({', '.join(placeholders)})"
|
|
149
|
+
params = tuple(data.values())
|
|
150
|
+
|
|
151
|
+
conn = await self.get_connection()
|
|
152
|
+
try:
|
|
153
|
+
cursor = await conn.execute(query, params)
|
|
154
|
+
await conn.commit()
|
|
155
|
+
return cursor.lastrowid
|
|
156
|
+
finally:
|
|
157
|
+
await self.return_connection(conn)
|
|
158
|
+
|
|
159
|
+
async def update_record(self, table_name: str, data: Dict[str, Any],
|
|
160
|
+
conditions: List[QueryCondition]) -> int:
|
|
161
|
+
"""Update records based on conditions"""
|
|
162
|
+
set_clause = ", ".join([f"{field} = ?" for field in data.keys()])
|
|
163
|
+
where_clause, params = self._build_where_clause(conditions)
|
|
164
|
+
|
|
165
|
+
query = f"UPDATE {table_name} SET {set_clause} WHERE {where_clause}"
|
|
166
|
+
all_params = tuple(data.values()) + params
|
|
167
|
+
|
|
168
|
+
return await self.execute_update(query, all_params)
|
|
169
|
+
|
|
170
|
+
async def delete_record(self, table_name: str, conditions: List[QueryCondition]) -> int:
|
|
171
|
+
"""Delete records based on conditions"""
|
|
172
|
+
where_clause, params = self._build_where_clause(conditions)
|
|
173
|
+
query = f"DELETE FROM {table_name} WHERE {where_clause}"
|
|
174
|
+
return await self.execute_update(query, params)
|
|
175
|
+
|
|
176
|
+
def _build_where_clause(self, conditions: List[QueryCondition]) -> Tuple[str, Tuple]:
|
|
177
|
+
"""Build WHERE clause from conditions"""
|
|
178
|
+
if not conditions:
|
|
179
|
+
return "1=1", ()
|
|
180
|
+
|
|
181
|
+
clauses = []
|
|
182
|
+
params = []
|
|
183
|
+
|
|
184
|
+
for i, condition in enumerate(conditions):
|
|
185
|
+
if i > 0:
|
|
186
|
+
clauses.append(condition.logical_operator)
|
|
187
|
+
|
|
188
|
+
if condition.operator == QueryOperator.EQ:
|
|
189
|
+
clauses.append(f"{condition.field} = ?")
|
|
190
|
+
params.append(condition.value)
|
|
191
|
+
elif condition.operator == QueryOperator.NE:
|
|
192
|
+
clauses.append(f"{condition.field} != ?")
|
|
193
|
+
params.append(condition.value)
|
|
194
|
+
elif condition.operator == QueryOperator.LIKE:
|
|
195
|
+
clauses.append(f"{condition.field} LIKE ?")
|
|
196
|
+
params.append(condition.value)
|
|
197
|
+
elif condition.operator == QueryOperator.IN:
|
|
198
|
+
placeholders = ",".join(["?" for _ in condition.value])
|
|
199
|
+
clauses.append(f"{condition.field} IN ({placeholders})")
|
|
200
|
+
params.extend(condition.value)
|
|
201
|
+
elif condition.operator == QueryOperator.IS_NULL:
|
|
202
|
+
clauses.append(f"{condition.field} IS NULL")
|
|
203
|
+
elif condition.operator == QueryOperator.IS_NOT_NULL:
|
|
204
|
+
clauses.append(f"{condition.field} IS NOT NULL")
|
|
205
|
+
|
|
206
|
+
return " ".join(clauses), tuple(params)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class PostgreSQLManager:
|
|
210
|
+
"""PostgreSQL database manager"""
|
|
211
|
+
|
|
212
|
+
def __init__(self, config: DatabaseConfig):
|
|
213
|
+
self.config = config
|
|
214
|
+
self.pool = None
|
|
215
|
+
|
|
216
|
+
async def initialize(self) -> None:
|
|
217
|
+
"""Initialize the connection pool"""
|
|
218
|
+
self.pool = await asyncpg.create_pool(
|
|
219
|
+
host=self.config.host,
|
|
220
|
+
port=self.config.port,
|
|
221
|
+
database=self.config.database,
|
|
222
|
+
user=self.config.username,
|
|
223
|
+
password=self.config.password,
|
|
224
|
+
min_size=1,
|
|
225
|
+
max_size=self.config.connection_pool_size,
|
|
226
|
+
command_timeout=self.config.timeout
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
async def execute_query(self, query: str, *params) -> List[Dict[str, Any]]:
|
|
230
|
+
"""Execute a query and return results"""
|
|
231
|
+
async with self.pool.acquire() as conn:
|
|
232
|
+
rows = await conn.fetch(query, *params)
|
|
233
|
+
return [dict(row) for row in rows]
|
|
234
|
+
|
|
235
|
+
async def execute_update(self, query: str, *params) -> int:
|
|
236
|
+
"""Execute an update query and return affected rows"""
|
|
237
|
+
async with self.pool.acquire() as conn:
|
|
238
|
+
result = await conn.execute(query, *params)
|
|
239
|
+
return int(result.split()[-1])
|
|
240
|
+
|
|
241
|
+
async def close(self) -> None:
|
|
242
|
+
"""Close the connection pool"""
|
|
243
|
+
if self.pool:
|
|
244
|
+
await self.pool.close()
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class MongoDBManager:
|
|
248
|
+
"""MongoDB database manager"""
|
|
249
|
+
|
|
250
|
+
def __init__(self, config: DatabaseConfig):
|
|
251
|
+
self.config = config
|
|
252
|
+
self.client = None
|
|
253
|
+
self.database = None
|
|
254
|
+
|
|
255
|
+
async def initialize(self) -> None:
|
|
256
|
+
"""Initialize MongoDB connection"""
|
|
257
|
+
connection_string = f"mongodb://{self.config.username}:{self.config.password}@{self.config.host}:{self.config.port}/{self.config.database}"
|
|
258
|
+
self.client = motor.motor_asyncio.AsyncIOMotorClient(connection_string)
|
|
259
|
+
self.database = self.client[self.config.database]
|
|
260
|
+
|
|
261
|
+
async def insert_document(self, collection: str, document: Dict[str, Any]) -> str:
|
|
262
|
+
"""Insert a document and return the ID"""
|
|
263
|
+
result = await self.database[collection].insert_one(document)
|
|
264
|
+
return str(result.inserted_id)
|
|
265
|
+
|
|
266
|
+
async def find_documents(self, collection: str, filter_dict: Dict[str, Any] = None,
|
|
267
|
+
limit: int = None, skip: int = None) -> List[Dict[str, Any]]:
|
|
268
|
+
"""Find documents in a collection"""
|
|
269
|
+
cursor = self.database[collection].find(filter_dict or {})
|
|
270
|
+
|
|
271
|
+
if skip:
|
|
272
|
+
cursor = cursor.skip(skip)
|
|
273
|
+
if limit:
|
|
274
|
+
cursor = cursor.limit(limit)
|
|
275
|
+
|
|
276
|
+
documents = await cursor.to_list(length=limit or 1000)
|
|
277
|
+
return documents
|
|
278
|
+
|
|
279
|
+
async def update_document(self, collection: str, filter_dict: Dict[str, Any],
|
|
280
|
+
update_dict: Dict[str, Any]) -> int:
|
|
281
|
+
"""Update documents in a collection"""
|
|
282
|
+
result = await self.database[collection].update_many(filter_dict, {"$set": update_dict})
|
|
283
|
+
return result.modified_count
|
|
284
|
+
|
|
285
|
+
async def delete_document(self, collection: str, filter_dict: Dict[str, Any]) -> int:
|
|
286
|
+
"""Delete documents from a collection"""
|
|
287
|
+
result = await self.database[collection].delete_many(filter_dict)
|
|
288
|
+
return result.deleted_count
|
|
289
|
+
|
|
290
|
+
async def create_index(self, collection: str, index_spec: Dict[str, Any]) -> str:
|
|
291
|
+
"""Create an index on a collection"""
|
|
292
|
+
result = await self.database[collection].create_index(list(index_spec.items()))
|
|
293
|
+
return result
|
|
294
|
+
|
|
295
|
+
async def close(self) -> None:
|
|
296
|
+
"""Close MongoDB connection"""
|
|
297
|
+
if self.client:
|
|
298
|
+
self.client.close()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class RedisManager:
|
|
302
|
+
"""Redis database manager"""
|
|
303
|
+
|
|
304
|
+
def __init__(self, config: DatabaseConfig):
|
|
305
|
+
self.config = config
|
|
306
|
+
self.redis_client = None
|
|
307
|
+
|
|
308
|
+
async def initialize(self) -> None:
|
|
309
|
+
"""Initialize Redis connection"""
|
|
310
|
+
self.redis_client = redis.Redis(
|
|
311
|
+
host=self.config.host,
|
|
312
|
+
port=self.config.port,
|
|
313
|
+
db=0,
|
|
314
|
+
decode_responses=True
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
async def set(self, key: str, value: Any, expire: int = None) -> bool:
|
|
318
|
+
"""Set a key-value pair"""
|
|
319
|
+
if isinstance(value, (dict, list)):
|
|
320
|
+
value = json.dumps(value)
|
|
321
|
+
return self.redis_client.set(key, value, ex=expire)
|
|
322
|
+
|
|
323
|
+
async def get(self, key: str) -> Any:
|
|
324
|
+
"""Get a value by key"""
|
|
325
|
+
value = self.redis_client.get(key)
|
|
326
|
+
if value is None:
|
|
327
|
+
return None
|
|
328
|
+
|
|
329
|
+
try:
|
|
330
|
+
return json.loads(value)
|
|
331
|
+
except json.JSONDecodeError:
|
|
332
|
+
return value
|
|
333
|
+
|
|
334
|
+
async def delete(self, key: str) -> bool:
|
|
335
|
+
"""Delete a key"""
|
|
336
|
+
return bool(self.redis_client.delete(key))
|
|
337
|
+
|
|
338
|
+
async def exists(self, key: str) -> bool:
|
|
339
|
+
"""Check if a key exists"""
|
|
340
|
+
return bool(self.redis_client.exists(key))
|
|
341
|
+
|
|
342
|
+
async def expire(self, key: str, seconds: int) -> bool:
|
|
343
|
+
"""Set expiration for a key"""
|
|
344
|
+
return bool(self.redis_client.expire(key, seconds))
|
|
345
|
+
|
|
346
|
+
async def close(self) -> None:
|
|
347
|
+
"""Close Redis connection"""
|
|
348
|
+
if self.redis_client:
|
|
349
|
+
self.redis_client.close()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
class DatabaseManager:
|
|
353
|
+
"""Main database manager that handles multiple database types"""
|
|
354
|
+
|
|
355
|
+
def __init__(self):
|
|
356
|
+
self.managers: Dict[DatabaseType, Any] = {}
|
|
357
|
+
self.configs: Dict[DatabaseType, DatabaseConfig] = {}
|
|
358
|
+
|
|
359
|
+
def add_database(self, db_type: DatabaseType, config: DatabaseConfig) -> None:
|
|
360
|
+
"""Add a database configuration"""
|
|
361
|
+
self.configs[db_type] = config
|
|
362
|
+
|
|
363
|
+
if db_type == DatabaseType.SQLITE:
|
|
364
|
+
self.managers[db_type] = SQLiteManager(config.database)
|
|
365
|
+
elif db_type == DatabaseType.POSTGRESQL:
|
|
366
|
+
self.managers[db_type] = PostgreSQLManager(config)
|
|
367
|
+
elif db_type == DatabaseType.MONGODB:
|
|
368
|
+
self.managers[db_type] = MongoDBManager(config)
|
|
369
|
+
elif db_type == DatabaseType.REDIS:
|
|
370
|
+
self.managers[db_type] = RedisManager(config)
|
|
371
|
+
|
|
372
|
+
async def initialize_all(self) -> None:
|
|
373
|
+
"""Initialize all configured databases"""
|
|
374
|
+
for db_type, manager in self.managers.items():
|
|
375
|
+
if hasattr(manager, 'initialize'):
|
|
376
|
+
await manager.initialize()
|
|
377
|
+
|
|
378
|
+
async def close_all(self) -> None:
|
|
379
|
+
"""Close all database connections"""
|
|
380
|
+
for manager in self.managers.values():
|
|
381
|
+
if hasattr(manager, 'close'):
|
|
382
|
+
await manager.close()
|
|
383
|
+
|
|
384
|
+
def get_manager(self, db_type: DatabaseType):
|
|
385
|
+
"""Get a database manager by type"""
|
|
386
|
+
return self.managers.get(db_type)
|
|
387
|
+
|
|
388
|
+
async def execute_sqlite_query(self, query: str, params: Tuple = ()) -> List[Dict[str, Any]]:
|
|
389
|
+
"""Execute a SQLite query"""
|
|
390
|
+
manager = self.get_manager(DatabaseType.SQLITE)
|
|
391
|
+
if manager:
|
|
392
|
+
return await manager.execute_query(query, params)
|
|
393
|
+
return []
|
|
394
|
+
|
|
395
|
+
async def execute_postgresql_query(self, query: str, *params) -> List[Dict[str, Any]]:
|
|
396
|
+
"""Execute a PostgreSQL query"""
|
|
397
|
+
manager = self.get_manager(DatabaseType.POSTGRESQL)
|
|
398
|
+
if manager:
|
|
399
|
+
return await manager.execute_query(query, *params)
|
|
400
|
+
return []
|
|
401
|
+
|
|
402
|
+
async def insert_mongodb_document(self, collection: str, document: Dict[str, Any]) -> str:
|
|
403
|
+
"""Insert a MongoDB document"""
|
|
404
|
+
manager = self.get_manager(DatabaseType.MONGODB)
|
|
405
|
+
if manager:
|
|
406
|
+
return await manager.insert_document(collection, document)
|
|
407
|
+
return ""
|
|
408
|
+
|
|
409
|
+
async def set_redis_value(self, key: str, value: Any, expire: int = None) -> bool:
|
|
410
|
+
"""Set a Redis value"""
|
|
411
|
+
manager = self.get_manager(DatabaseType.REDIS)
|
|
412
|
+
if manager:
|
|
413
|
+
return await manager.set(key, value, expire)
|
|
414
|
+
return False
|
|
415
|
+
|
|
416
|
+
async def get_redis_value(self, key: str) -> Any:
|
|
417
|
+
"""Get a Redis value"""
|
|
418
|
+
manager = self.get_manager(DatabaseType.REDIS)
|
|
419
|
+
if manager:
|
|
420
|
+
return await manager.get(key)
|
|
421
|
+
return None
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class DatabaseMigration:
|
|
425
|
+
"""Database migration system"""
|
|
426
|
+
|
|
427
|
+
def __init__(self, db_manager: DatabaseManager):
|
|
428
|
+
self.db_manager = db_manager
|
|
429
|
+
self.migrations: List[Dict[str, Any]] = []
|
|
430
|
+
|
|
431
|
+
def add_migration(self, version: str, description: str,
|
|
432
|
+
up_sql: str, down_sql: str = None) -> None:
|
|
433
|
+
"""Add a migration"""
|
|
434
|
+
migration = {
|
|
435
|
+
"version": version,
|
|
436
|
+
"description": description,
|
|
437
|
+
"up_sql": up_sql,
|
|
438
|
+
"down_sql": down_sql,
|
|
439
|
+
"applied": False
|
|
440
|
+
}
|
|
441
|
+
self.migrations.append(migration)
|
|
442
|
+
|
|
443
|
+
async def run_migrations(self) -> None:
|
|
444
|
+
"""Run all pending migrations"""
|
|
445
|
+
# Create migrations table if it doesn't exist
|
|
446
|
+
await self._create_migrations_table()
|
|
447
|
+
|
|
448
|
+
# Get applied migrations
|
|
449
|
+
applied_migrations = await self._get_applied_migrations()
|
|
450
|
+
|
|
451
|
+
# Run pending migrations
|
|
452
|
+
for migration in self.migrations:
|
|
453
|
+
if migration["version"] not in applied_migrations:
|
|
454
|
+
await self._apply_migration(migration)
|
|
455
|
+
|
|
456
|
+
async def _create_migrations_table(self) -> None:
|
|
457
|
+
"""Create migrations tracking table"""
|
|
458
|
+
sql = """
|
|
459
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
460
|
+
version TEXT PRIMARY KEY,
|
|
461
|
+
description TEXT,
|
|
462
|
+
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
463
|
+
)
|
|
464
|
+
"""
|
|
465
|
+
await self.db_manager.execute_sqlite_query(sql)
|
|
466
|
+
|
|
467
|
+
async def _get_applied_migrations(self) -> List[str]:
|
|
468
|
+
"""Get list of applied migrations"""
|
|
469
|
+
result = await self.db_manager.execute_sqlite_query(
|
|
470
|
+
"SELECT version FROM migrations ORDER BY applied_at"
|
|
471
|
+
)
|
|
472
|
+
return [row["version"] for row in result]
|
|
473
|
+
|
|
474
|
+
async def _apply_migration(self, migration: Dict[str, Any]) -> None:
|
|
475
|
+
"""Apply a migration"""
|
|
476
|
+
try:
|
|
477
|
+
# Execute up SQL
|
|
478
|
+
await self.db_manager.execute_sqlite_query(migration["up_sql"])
|
|
479
|
+
|
|
480
|
+
# Record migration
|
|
481
|
+
await self.db_manager.execute_sqlite_query(
|
|
482
|
+
"INSERT INTO migrations (version, description) VALUES (?, ?)",
|
|
483
|
+
(migration["version"], migration["description"])
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
print(f"Applied migration: {migration['version']} - {migration['description']}")
|
|
487
|
+
except Exception as e:
|
|
488
|
+
print(f"Error applying migration {migration['version']}: {e}")
|
|
489
|
+
raise
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
# Global database manager instance
|
|
493
|
+
db_manager = DatabaseManager()
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
# Convenience functions
|
|
497
|
+
async def setup_sqlite_database(db_path: str = "api_mocker.db") -> None:
|
|
498
|
+
"""Setup SQLite database with default tables"""
|
|
499
|
+
config = DatabaseConfig(
|
|
500
|
+
db_type=DatabaseType.SQLITE,
|
|
501
|
+
database=db_path
|
|
502
|
+
)
|
|
503
|
+
db_manager.add_database(DatabaseType.SQLITE, config)
|
|
504
|
+
await db_manager.initialize_all()
|
|
505
|
+
|
|
506
|
+
# Create default tables
|
|
507
|
+
sqlite_manager = db_manager.get_manager(DatabaseType.SQLITE)
|
|
508
|
+
if sqlite_manager:
|
|
509
|
+
await sqlite_manager.create_table("users", {
|
|
510
|
+
"id": "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
511
|
+
"username": "TEXT UNIQUE NOT NULL",
|
|
512
|
+
"email": "TEXT UNIQUE NOT NULL",
|
|
513
|
+
"created_at": "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
await sqlite_manager.create_table("api_requests", {
|
|
517
|
+
"id": "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
518
|
+
"method": "TEXT NOT NULL",
|
|
519
|
+
"path": "TEXT NOT NULL",
|
|
520
|
+
"headers": "TEXT",
|
|
521
|
+
"body": "TEXT",
|
|
522
|
+
"response_status": "INTEGER",
|
|
523
|
+
"response_body": "TEXT",
|
|
524
|
+
"created_at": "TIMESTAMP DEFAULT CURRENT_TIMESTAMP"
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
async def setup_postgresql_database(host: str, port: int, database: str,
|
|
529
|
+
username: str, password: str) -> None:
|
|
530
|
+
"""Setup PostgreSQL database"""
|
|
531
|
+
config = DatabaseConfig(
|
|
532
|
+
db_type=DatabaseType.POSTGRESQL,
|
|
533
|
+
host=host,
|
|
534
|
+
port=port,
|
|
535
|
+
database=database,
|
|
536
|
+
username=username,
|
|
537
|
+
password=password
|
|
538
|
+
)
|
|
539
|
+
db_manager.add_database(DatabaseType.POSTGRESQL, config)
|
|
540
|
+
await db_manager.initialize_all()
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
async def setup_mongodb_database(host: str, port: int, database: str,
|
|
544
|
+
username: str = "", password: str = "") -> None:
|
|
545
|
+
"""Setup MongoDB database"""
|
|
546
|
+
config = DatabaseConfig(
|
|
547
|
+
db_type=DatabaseType.MONGODB,
|
|
548
|
+
host=host,
|
|
549
|
+
port=port,
|
|
550
|
+
database=database,
|
|
551
|
+
username=username,
|
|
552
|
+
password=password
|
|
553
|
+
)
|
|
554
|
+
db_manager.add_database(DatabaseType.MONGODB, config)
|
|
555
|
+
await db_manager.initialize_all()
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
async def setup_redis_database(host: str = "localhost", port: int = 6379) -> None:
|
|
559
|
+
"""Setup Redis database"""
|
|
560
|
+
config = DatabaseConfig(
|
|
561
|
+
db_type=DatabaseType.REDIS,
|
|
562
|
+
host=host,
|
|
563
|
+
port=port
|
|
564
|
+
)
|
|
565
|
+
db_manager.add_database(DatabaseType.REDIS, config)
|
|
566
|
+
await db_manager.initialize_all()
|