pyflashdb 1.0.1__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.
- flash/__init__.py +10 -0
- flash/adapters/__init__.py +0 -0
- flash/adapters/mongo_adapter.py +247 -0
- flash/adapters/mysql_adapter.py +217 -0
- flash/adapters/postgres_adapter.py +226 -0
- flash/core.py +306 -0
- flash/exceptions.py +33 -0
- flash/filters.py +140 -0
- flash/triggers.py +148 -0
- pyflashdb-1.0.1.dist-info/METADATA +34 -0
- pyflashdb-1.0.1.dist-info/RECORD +13 -0
- pyflashdb-1.0.1.dist-info/WHEEL +5 -0
- pyflashdb-1.0.1.dist-info/top_level.txt +1 -0
flash/filters.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Query filter translation utilities.
|
|
3
|
+
Converts Flash's unified filter syntax to SQL WHERE clauses or MongoDB query dicts.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Mapping from Flash operators to SQL and MongoDB equivalents
|
|
7
|
+
OPERATOR_MAP = {
|
|
8
|
+
">": {"sql": ">", "mongo": "$gt"},
|
|
9
|
+
"<": {"sql": "<", "mongo": "$lt"},
|
|
10
|
+
">=": {"sql": ">=", "mongo": "$gte"},
|
|
11
|
+
"<=": {"sql": "<=", "mongo": "$lte"},
|
|
12
|
+
"!=": {"sql": "!=", "mongo": "$ne"},
|
|
13
|
+
"=": {"sql": "=", "mongo": "$eq"},
|
|
14
|
+
"in": {"sql": "IN", "mongo": "$in"},
|
|
15
|
+
"not in": {"sql": "NOT IN", "mongo": "$nin"},
|
|
16
|
+
"like": {"sql": "LIKE", "mongo": "$regex"},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def filters_to_sql(filters: dict) -> tuple:
|
|
21
|
+
"""
|
|
22
|
+
Convert Flash filter dict to SQL WHERE clause + params list.
|
|
23
|
+
|
|
24
|
+
Example:
|
|
25
|
+
filters = {"age": {">": 18}, "name": "John"}
|
|
26
|
+
-> ("age > %s AND name = %s", [18, "John"])
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
(clause_str, params_list)
|
|
30
|
+
"""
|
|
31
|
+
if not filters:
|
|
32
|
+
return "", []
|
|
33
|
+
|
|
34
|
+
clauses = []
|
|
35
|
+
params = []
|
|
36
|
+
|
|
37
|
+
for field, value in filters.items():
|
|
38
|
+
if isinstance(value, dict):
|
|
39
|
+
for op, val in value.items():
|
|
40
|
+
sql_op = OPERATOR_MAP.get(op, {}).get("sql", op)
|
|
41
|
+
if op in ("in", "not in"):
|
|
42
|
+
placeholders = ", ".join(["%s"] * len(val))
|
|
43
|
+
clauses.append(f"{field} {sql_op} ({placeholders})")
|
|
44
|
+
params.extend(val)
|
|
45
|
+
else:
|
|
46
|
+
clauses.append(f"{field} {sql_op} %s")
|
|
47
|
+
params.append(val)
|
|
48
|
+
else:
|
|
49
|
+
clauses.append(f"{field} = %s")
|
|
50
|
+
params.append(value)
|
|
51
|
+
|
|
52
|
+
return " AND ".join(clauses), params
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def filters_to_mongo(filters: dict) -> dict:
|
|
56
|
+
"""
|
|
57
|
+
Convert Flash filter dict to MongoDB query dict.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
filters = {"age": {">": 18}, "name": "John"}
|
|
61
|
+
-> {"age": {"$gt": 18}, "name": "John"}
|
|
62
|
+
"""
|
|
63
|
+
if not filters:
|
|
64
|
+
return {}
|
|
65
|
+
|
|
66
|
+
query = {}
|
|
67
|
+
|
|
68
|
+
for field, value in filters.items():
|
|
69
|
+
if isinstance(value, dict):
|
|
70
|
+
mongo_expr = {}
|
|
71
|
+
for op, val in value.items():
|
|
72
|
+
mongo_op = OPERATOR_MAP.get(op, {}).get("mongo")
|
|
73
|
+
if mongo_op:
|
|
74
|
+
mongo_expr[mongo_op] = val
|
|
75
|
+
else:
|
|
76
|
+
mongo_expr[op] = val
|
|
77
|
+
query[field] = mongo_expr
|
|
78
|
+
else:
|
|
79
|
+
query[field] = value
|
|
80
|
+
|
|
81
|
+
return query
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def build_update_sql(data: dict) -> tuple:
|
|
85
|
+
"""
|
|
86
|
+
Build SQL SET clause from update data dict.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
(set_clause_str, params_list)
|
|
90
|
+
"""
|
|
91
|
+
if not data:
|
|
92
|
+
return "", []
|
|
93
|
+
|
|
94
|
+
clauses = [f"{field} = %s" for field in data.keys()]
|
|
95
|
+
params = list(data.values())
|
|
96
|
+
|
|
97
|
+
return ", ".join(clauses), params
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def python_type_to_sql(type_str: str, db_type: str = "mysql") -> str:
|
|
101
|
+
"""
|
|
102
|
+
Convert Flash type strings to DB-specific SQL column types.
|
|
103
|
+
"""
|
|
104
|
+
type_map = {
|
|
105
|
+
"mysql": {
|
|
106
|
+
"int": "INT",
|
|
107
|
+
"integer": "INT",
|
|
108
|
+
"str": "VARCHAR(255)",
|
|
109
|
+
"string": "VARCHAR(255)",
|
|
110
|
+
"text": "TEXT",
|
|
111
|
+
"float": "FLOAT",
|
|
112
|
+
"double": "DOUBLE",
|
|
113
|
+
"bool": "TINYINT(1)",
|
|
114
|
+
"boolean": "TINYINT(1)",
|
|
115
|
+
"date": "DATE",
|
|
116
|
+
"datetime": "DATETIME",
|
|
117
|
+
"timestamp": "TIMESTAMP",
|
|
118
|
+
"json": "JSON",
|
|
119
|
+
"blob": "BLOB",
|
|
120
|
+
},
|
|
121
|
+
"postgres": {
|
|
122
|
+
"int": "INTEGER",
|
|
123
|
+
"integer": "INTEGER",
|
|
124
|
+
"str": "VARCHAR(255)",
|
|
125
|
+
"string": "VARCHAR(255)",
|
|
126
|
+
"text": "TEXT",
|
|
127
|
+
"float": "REAL",
|
|
128
|
+
"double": "DOUBLE PRECISION",
|
|
129
|
+
"bool": "BOOLEAN",
|
|
130
|
+
"boolean": "BOOLEAN",
|
|
131
|
+
"date": "DATE",
|
|
132
|
+
"datetime": "TIMESTAMP",
|
|
133
|
+
"timestamp": "TIMESTAMP",
|
|
134
|
+
"json": "JSONB",
|
|
135
|
+
"blob": "BYTEA",
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
db_types = type_map.get(db_type, type_map["mysql"])
|
|
140
|
+
return db_types.get(type_str.lower(), type_str.upper())
|
flash/triggers.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trigger and hook system for Flash.
|
|
3
|
+
Supports before/after hooks for insert, update, delete operations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from typing import Callable, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TriggerRegistry:
|
|
11
|
+
"""
|
|
12
|
+
Registry for before/after operation hooks on collections/tables.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
VALID_EVENTS = {"insert", "update", "delete", "select"}
|
|
16
|
+
VALID_TIMINGS = {"before", "after"}
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
# Structure: {timing: {event: {table: [callable, ...]}}}
|
|
20
|
+
self._hooks: dict = {
|
|
21
|
+
"before": defaultdict(lambda: defaultdict(list)),
|
|
22
|
+
"after": defaultdict(lambda: defaultdict(list)),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
def register(self, timing: str, event: str, table: str, fn: Callable):
|
|
26
|
+
"""Register a hook function for a given timing, event, and table."""
|
|
27
|
+
if timing not in self.VALID_TIMINGS:
|
|
28
|
+
raise ValueError(f"Invalid timing '{timing}'. Choose from: {self.VALID_TIMINGS}")
|
|
29
|
+
if event not in self.VALID_EVENTS:
|
|
30
|
+
raise ValueError(f"Invalid event '{event}'. Choose from: {self.VALID_EVENTS}")
|
|
31
|
+
|
|
32
|
+
self._hooks[timing][event][table].append(fn)
|
|
33
|
+
|
|
34
|
+
def fire(self, timing: str, event: str, table: str, data, result=None):
|
|
35
|
+
"""
|
|
36
|
+
Fire all hooks for a given timing/event/table.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
timing: 'before' or 'after'
|
|
40
|
+
event: 'insert', 'update', 'delete', 'select'
|
|
41
|
+
table: collection/table name
|
|
42
|
+
data: the payload (filter or record dict)
|
|
43
|
+
result: the query result (only for 'after' hooks)
|
|
44
|
+
"""
|
|
45
|
+
hooks = self._hooks[timing][event].get(table, [])
|
|
46
|
+
for fn in hooks:
|
|
47
|
+
try:
|
|
48
|
+
if timing == "after":
|
|
49
|
+
fn(data, result)
|
|
50
|
+
else:
|
|
51
|
+
fn(data)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"[Flash Warning] Hook '{fn.__name__}' raised an error: {e}")
|
|
54
|
+
|
|
55
|
+
def clear(self, table: Optional[str] = None):
|
|
56
|
+
"""Clear all hooks, or hooks for a specific table."""
|
|
57
|
+
if table:
|
|
58
|
+
for timing in self.VALID_TIMINGS:
|
|
59
|
+
for event in self.VALID_EVENTS:
|
|
60
|
+
self._hooks[timing][event].pop(table, None)
|
|
61
|
+
else:
|
|
62
|
+
self.__init__()
|
|
63
|
+
|
|
64
|
+
def list_hooks(self) -> dict:
|
|
65
|
+
"""Return a summary of all registered hooks."""
|
|
66
|
+
summary = {}
|
|
67
|
+
for timing in self.VALID_TIMINGS:
|
|
68
|
+
for event in self.VALID_EVENTS:
|
|
69
|
+
for table, fns in self._hooks[timing][event].items():
|
|
70
|
+
key = f"{timing}_{event}:{table}"
|
|
71
|
+
summary[key] = [fn.__name__ for fn in fns]
|
|
72
|
+
return {k: v for k, v in summary.items() if v}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# Singleton registry shared across a FlashDB instance
|
|
76
|
+
def make_trigger_mixin():
|
|
77
|
+
"""
|
|
78
|
+
Returns a mixin class providing decorator-style trigger registration.
|
|
79
|
+
Each FlashDB instance gets its own mixin with its own registry.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
class TriggerMixin:
|
|
83
|
+
def _init_triggers(self):
|
|
84
|
+
self._triggers = TriggerRegistry()
|
|
85
|
+
|
|
86
|
+
# ── Decorator API ─────────────────────────────────────────────────
|
|
87
|
+
def before_insert(self, table: str):
|
|
88
|
+
def decorator(fn: Callable):
|
|
89
|
+
self._triggers.register("before", "insert", table, fn)
|
|
90
|
+
return fn
|
|
91
|
+
return decorator
|
|
92
|
+
|
|
93
|
+
def after_insert(self, table: str):
|
|
94
|
+
def decorator(fn: Callable):
|
|
95
|
+
self._triggers.register("after", "insert", table, fn)
|
|
96
|
+
return fn
|
|
97
|
+
return decorator
|
|
98
|
+
|
|
99
|
+
def before_update(self, table: str):
|
|
100
|
+
def decorator(fn: Callable):
|
|
101
|
+
self._triggers.register("before", "update", table, fn)
|
|
102
|
+
return fn
|
|
103
|
+
return decorator
|
|
104
|
+
|
|
105
|
+
def after_update(self, table: str):
|
|
106
|
+
def decorator(fn: Callable):
|
|
107
|
+
self._triggers.register("after", "update", table, fn)
|
|
108
|
+
return fn
|
|
109
|
+
return decorator
|
|
110
|
+
|
|
111
|
+
def before_delete(self, table: str):
|
|
112
|
+
def decorator(fn: Callable):
|
|
113
|
+
self._triggers.register("before", "delete", table, fn)
|
|
114
|
+
return fn
|
|
115
|
+
return decorator
|
|
116
|
+
|
|
117
|
+
def after_delete(self, table: str):
|
|
118
|
+
def decorator(fn: Callable):
|
|
119
|
+
self._triggers.register("after", "delete", table, fn)
|
|
120
|
+
return fn
|
|
121
|
+
return decorator
|
|
122
|
+
|
|
123
|
+
def before_select(self, table: str):
|
|
124
|
+
def decorator(fn: Callable):
|
|
125
|
+
self._triggers.register("before", "select", table, fn)
|
|
126
|
+
return fn
|
|
127
|
+
return decorator
|
|
128
|
+
|
|
129
|
+
def after_select(self, table: str):
|
|
130
|
+
def decorator(fn: Callable):
|
|
131
|
+
self._triggers.register("after", "select", table, fn)
|
|
132
|
+
return fn
|
|
133
|
+
return decorator
|
|
134
|
+
|
|
135
|
+
# ── Programmatic API ──────────────────────────────────────────────
|
|
136
|
+
def add_hook(self, timing: str, event: str, table: str, fn: Callable):
|
|
137
|
+
"""Programmatically register a hook without decorators."""
|
|
138
|
+
self._triggers.register(timing, event, table, fn)
|
|
139
|
+
|
|
140
|
+
def list_hooks(self) -> dict:
|
|
141
|
+
"""List all registered hooks."""
|
|
142
|
+
return self._triggers.list_hooks()
|
|
143
|
+
|
|
144
|
+
def clear_hooks(self, table: Optional[str] = None):
|
|
145
|
+
"""Remove hooks (all, or for a specific table)."""
|
|
146
|
+
self._triggers.clear(table)
|
|
147
|
+
|
|
148
|
+
return TriggerMixin
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyflashdb
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: A unified, trigger-aware database interface for MySQL, PostgreSQL, and MongoDB.
|
|
5
|
+
Author: Flash Team
|
|
6
|
+
Keywords: database mysql postgresql mongodb orm crud flash unified
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Topic :: Database
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Provides-Extra: mysql
|
|
15
|
+
Requires-Dist: mysql-connector-python>=8.0; extra == "mysql"
|
|
16
|
+
Provides-Extra: postgres
|
|
17
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "postgres"
|
|
18
|
+
Provides-Extra: mongodb
|
|
19
|
+
Requires-Dist: pymongo>=4.0; extra == "mongodb"
|
|
20
|
+
Provides-Extra: all
|
|
21
|
+
Requires-Dist: mysql-connector-python>=8.0; extra == "all"
|
|
22
|
+
Requires-Dist: psycopg2-binary>=2.9; extra == "all"
|
|
23
|
+
Requires-Dist: pymongo>=4.0; extra == "all"
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: classifier
|
|
26
|
+
Dynamic: description
|
|
27
|
+
Dynamic: description-content-type
|
|
28
|
+
Dynamic: keywords
|
|
29
|
+
Dynamic: provides-extra
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
32
|
+
|
|
33
|
+
# flash
|
|
34
|
+
A lightweight, multi-database Python library that provides simple CRUD operations, unified query syntax, and trigger hooks with minimal setup
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
flash/__init__.py,sha256=Qp4vnw9Y0ty_khkEQo7zLMo8qU0Nzg4JOvIIY0W62RU,298
|
|
2
|
+
flash/core.py,sha256=p8MVgA5B99ckz7xmBfiqGYNzpu2dfYn6zG5AppddocQ,11512
|
|
3
|
+
flash/exceptions.py,sha256=WI8H95LM9CV9wbqirNVcmbbzhIKIFlAdokt5FTQsqRk,658
|
|
4
|
+
flash/filters.py,sha256=GIyY1SQkQLYjJyx9Rrgna11oD60l7wnZnOtiBTq94Fc,4091
|
|
5
|
+
flash/triggers.py,sha256=8RoSLd9HashQ85wAr0Xgn45BVaTaDnDaPRkbxmBYFW0,5744
|
|
6
|
+
flash/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
flash/adapters/mongo_adapter.py,sha256=zCZ4nzLPkcaHVTSZrr27Bt4zjOuQd_Oedi5tWUuKma4,9495
|
|
8
|
+
flash/adapters/mysql_adapter.py,sha256=Brd9LAZu7JYoUyaYs-MUCWkpVjFRGhB3bVFOttTpqe8,8429
|
|
9
|
+
flash/adapters/postgres_adapter.py,sha256=u6fNx2VSE18GtbNvEQMZ4PMbz8YvZbPt0GAnuJhNw7E,9061
|
|
10
|
+
pyflashdb-1.0.1.dist-info/METADATA,sha256=RlH_ioWYTnA3XmyNLNTDXt0lCpzzWjx-n3o6moBN7dM,1283
|
|
11
|
+
pyflashdb-1.0.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
pyflashdb-1.0.1.dist-info/top_level.txt,sha256=gPTfQwGao1M89TdHszAxa2fgqvl88iO9S3O7zmm7GoE,6
|
|
13
|
+
pyflashdb-1.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
flash
|