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/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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ flash