devdb-sql 1.0.5__tar.gz → 1.0.6__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devdb-sql
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: Lightweight SQLite SDK with a MongoDB-like developer experience
5
5
  Author: Mahadev
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devdb-sql
3
- Version: 1.0.5
3
+ Version: 1.0.6
4
4
  Summary: Lightweight SQLite SDK with a MongoDB-like developer experience
5
5
  Author: Mahadev
6
6
  License: MIT
@@ -1,10 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
- devdb/__init__.py
5
- devdb/db.py
6
- devdb/schema.py
7
- devdb/table.py
8
4
  devdb_sql.egg-info/PKG-INFO
9
5
  devdb_sql.egg-info/SOURCES.txt
10
6
  devdb_sql.egg-info/dependency_links.txt
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devdb-sql"
7
- version = "1.0.5"
7
+ version = "1.0.6"
8
8
  description = "Lightweight SQLite SDK with a MongoDB-like developer experience"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,5 +0,0 @@
1
- from .db import DB
2
- from .table import Table
3
-
4
- __all__ = ["DB", "Table"]
5
- __version__ = "1.0.2"
@@ -1,61 +0,0 @@
1
- import sqlite3
2
- from pathlib import Path
3
-
4
- from .schema import compile_schema
5
- from .table import Table
6
-
7
-
8
- class DB:
9
- def __init__(self, path):
10
- if not path:
11
- raise ValueError("Database path is required")
12
-
13
- self.path = path
14
-
15
- parent = Path(path).parent
16
- if str(parent) not in ("", "."):
17
- parent.mkdir(parents=True, exist_ok=True)
18
-
19
- self.sqlite = sqlite3.connect(path)
20
- self.sqlite.row_factory = sqlite3.Row
21
- self._tables = {}
22
-
23
- def create(self, name, schema):
24
- if not name:
25
- raise ValueError("Table name is required")
26
-
27
- if not isinstance(schema, dict):
28
- raise ValueError(f'Schema is required for table "{name}"')
29
-
30
- if name in self._tables:
31
- raise ValueError(f'Table "{name}" is already registered')
32
-
33
- sql = compile_schema(name, schema)
34
- self.sqlite.executescript(sql)
35
- self.sqlite.commit()
36
-
37
- table = Table(self, name, schema)
38
- self._tables[name] = table
39
- return table
40
-
41
- def drop(self, name):
42
- self.sqlite.execute(f"DROP TABLE IF EXISTS {name}")
43
- self.sqlite.commit()
44
- self._tables.pop(name, None)
45
- return True
46
-
47
- def tables(self):
48
- rows = self.sqlite.execute("""
49
- SELECT name
50
- FROM sqlite_master
51
- WHERE type = 'table'
52
- AND name NOT LIKE 'sqlite_%'
53
- ORDER BY name
54
- """).fetchall()
55
- return [row["name"] for row in rows]
56
-
57
- def show_tables(self):
58
- return self.tables()
59
-
60
- def close(self):
61
- self.sqlite.close()
@@ -1,98 +0,0 @@
1
- import re
2
-
3
- TYPE_MAP = {
4
- "string": "TEXT",
5
- "integer": "INTEGER",
6
- "number": "REAL",
7
- "boolean": "INTEGER",
8
- "date": "TEXT",
9
- "json": "TEXT",
10
- }
11
-
12
- _IDENTIFIER = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
13
-
14
-
15
- def validate_identifier(name):
16
- if not isinstance(name, str) or not _IDENTIFIER.fullmatch(name):
17
- raise ValueError(f"Invalid SQL identifier: {name}")
18
-
19
-
20
- def compile_default(value, sql_type):
21
- if value is None:
22
- return "NULL"
23
-
24
- if sql_type in ("INTEGER", "REAL"):
25
- if isinstance(value, bool) or not isinstance(value, (int, float)):
26
- raise ValueError("Numeric default value expected")
27
- return str(value)
28
-
29
- if not isinstance(value, str):
30
- raise ValueError("String default value expected")
31
-
32
- return "'" + value.replace("'", "''") + "'"
33
-
34
-
35
- def compile_schema(table_name, schema):
36
- validate_identifier(table_name)
37
-
38
- if not isinstance(schema, dict):
39
- raise ValueError("Schema must be an object")
40
-
41
- columns = []
42
-
43
- for name, config in schema.items():
44
- validate_identifier(name)
45
-
46
- if not isinstance(config, dict):
47
- raise ValueError(f'Invalid configuration for field "{name}"')
48
-
49
- field_type = config.get("type")
50
- sql_type = TYPE_MAP.get(field_type)
51
-
52
- if not sql_type:
53
- raise ValueError(
54
- f'Unsupported type "{field_type}" for field "{name}"'
55
- )
56
-
57
- definition = f"{name} {sql_type}"
58
-
59
- if config.get("primary"):
60
- definition += " PRIMARY KEY"
61
-
62
- if config.get("autoIncrement"):
63
- if sql_type != "INTEGER" or not config.get("primary"):
64
- raise ValueError(
65
- f'autoIncrement requires INTEGER PRIMARY KEY for "{name}"'
66
- )
67
- definition += " AUTOINCREMENT"
68
-
69
- if config.get("required"):
70
- definition += " NOT NULL"
71
-
72
- if config.get("unique"):
73
- definition += " UNIQUE"
74
-
75
- if "default" in config:
76
- definition += f" DEFAULT {compile_default(config['default'], sql_type)}"
77
-
78
- columns.append(definition)
79
-
80
- if not columns:
81
- raise ValueError("Schema cannot be empty")
82
-
83
- return f"""
84
- CREATE TABLE IF NOT EXISTS {table_name} (
85
- {", ".join(columns)}
86
- )
87
- """
88
-
89
-
90
- def get_primary_key(schema):
91
- for name, config in schema.items():
92
- if config.get("primary"):
93
- return name
94
- raise ValueError("Schema must contain a primary key")
95
-
96
-
97
- def get_sql_type(field_type):
98
- return TYPE_MAP.get(field_type)
@@ -1,253 +0,0 @@
1
- import json
2
- import sqlite3
3
-
4
- from .schema import get_primary_key
5
-
6
-
7
- class Table:
8
- def __init__(self, db, name, schema):
9
- self.db = db
10
- self.name = name
11
- self.schema = schema
12
- self.primary_key = get_primary_key(schema)
13
-
14
- def _check_field(self, field):
15
- if field not in self.schema:
16
- raise ValueError(
17
- f'Unknown field "{field}" in table "{self.name}"'
18
- )
19
-
20
- def _serialize_value(self, field, value):
21
- field_type = self.schema[field].get("type")
22
-
23
- if field_type == "string" and not isinstance(value, str):
24
- raise ValueError(f"{field} must be a string")
25
-
26
- if field_type in ("integer", "number"):
27
- if isinstance(value, bool) or not isinstance(value, (int, float)):
28
- raise ValueError(f"{field} must be a number")
29
-
30
- if field_type == "boolean":
31
- if not isinstance(value, bool):
32
- raise ValueError(f"{field} must be a boolean")
33
- return 1 if value else 0
34
-
35
- if field_type == "json":
36
- return json.dumps(value, separators=(",", ":"))
37
-
38
- return value
39
-
40
- def _row_to_dict(self, row):
41
- if row is None:
42
- return None
43
-
44
- result = dict(row)
45
-
46
- for field, config in self.schema.items():
47
- if field not in result:
48
- continue
49
-
50
- if config.get("type") == "boolean" and result[field] is not None:
51
- result[field] = bool(result[field])
52
-
53
- return result
54
-
55
- def insert(self, data):
56
- if not isinstance(data, dict):
57
- raise ValueError("Insert data must be an object")
58
-
59
- fields = list(data.keys())
60
-
61
- if not fields:
62
- raise ValueError("Insert data cannot be empty")
63
-
64
- for field in fields:
65
- self._check_field(field)
66
-
67
- columns = ", ".join(fields)
68
- placeholders = ", ".join("?" for _ in fields)
69
- values = [
70
- self._serialize_value(field, data[field])
71
- for field in fields
72
- ]
73
-
74
- sql = f"""
75
- INSERT INTO {self.name} ({columns})
76
- VALUES ({placeholders})
77
- """
78
-
79
- cursor = self.db.sqlite.execute(sql, values)
80
- self.db.sqlite.commit()
81
-
82
- # Preserve the JS SDK's insert -> findById behavior.
83
- return self.find_by_id(cursor.lastrowid)
84
-
85
- def find_by_id(self, id):
86
- sql = f"""
87
- SELECT *
88
- FROM {self.name}
89
- WHERE {self.primary_key} = ?
90
- LIMIT 1
91
- """
92
-
93
- row = self.db.sqlite.execute(sql, (id,)).fetchone()
94
- return self._row_to_dict(row)
95
-
96
- def find_all(self):
97
- rows = self.db.sqlite.execute(
98
- f"SELECT * FROM {self.name}"
99
- ).fetchall()
100
- return [self._row_to_dict(row) for row in rows]
101
-
102
- def find(self, query=None, options=None):
103
- if query is None:
104
- query = {}
105
- if options is None:
106
- options = {}
107
-
108
- if not isinstance(query, dict):
109
- raise ValueError("Query must be an object")
110
-
111
- if not isinstance(options, dict):
112
- raise ValueError("Options must be an object")
113
-
114
- conditions = []
115
- values = []
116
-
117
- for field, condition in query.items():
118
- self._check_field(field)
119
-
120
- if isinstance(condition, dict):
121
- for operator, value in condition.items():
122
- if operator == "$eq":
123
- conditions.append(f"{field} = ?")
124
- values.append(self._serialize_query_value(field, value))
125
-
126
- elif operator == "$ne":
127
- conditions.append(f"{field} != ?")
128
- values.append(self._serialize_query_value(field, value))
129
-
130
- elif operator == "$gt":
131
- conditions.append(f"{field} > ?")
132
- values.append(self._serialize_query_value(field, value))
133
-
134
- elif operator == "$gte":
135
- conditions.append(f"{field} >= ?")
136
- values.append(self._serialize_query_value(field, value))
137
-
138
- elif operator == "$lt":
139
- conditions.append(f"{field} < ?")
140
- values.append(self._serialize_query_value(field, value))
141
-
142
- elif operator == "$lte":
143
- conditions.append(f"{field} <= ?")
144
- values.append(self._serialize_query_value(field, value))
145
-
146
- elif operator == "$in":
147
- if not isinstance(value, list) or not value:
148
- raise ValueError("$in requires a non-empty array")
149
-
150
- placeholders = ", ".join("?" for _ in value)
151
- conditions.append(f"{field} IN ({placeholders})")
152
- values.extend(
153
- self._serialize_query_value(field, item)
154
- for item in value
155
- )
156
-
157
- else:
158
- raise ValueError(f'Unsupported operator "{operator}"')
159
- else:
160
- conditions.append(f"{field} = ?")
161
- values.append(self._serialize_query_value(field, condition))
162
-
163
- sql = f"SELECT * FROM {self.name}"
164
-
165
- if conditions:
166
- sql += " WHERE " + " AND ".join(conditions)
167
-
168
- order_by = options.get("orderBy")
169
- if order_by:
170
- self._check_field(order_by)
171
- order = "DESC" if options.get("order") == "desc" else "ASC"
172
- sql += f" ORDER BY {order_by} {order}"
173
-
174
- if "limit" in options:
175
- limit = options["limit"]
176
- if isinstance(limit, bool) or not isinstance(limit, int) or limit < 0:
177
- raise ValueError("limit must be a positive integer")
178
- sql += f" LIMIT {limit}"
179
-
180
- if "offset" in options:
181
- offset = options["offset"]
182
- if isinstance(offset, bool) or not isinstance(offset, int) or offset < 0:
183
- raise ValueError("offset must be a positive integer")
184
- sql += f" OFFSET {offset}"
185
-
186
- rows = self.db.sqlite.execute(sql, values).fetchall()
187
- return [self._row_to_dict(row) for row in rows]
188
-
189
- def _serialize_query_value(self, field, value):
190
- field_type = self.schema[field].get("type")
191
- if field_type == "boolean":
192
- if not isinstance(value, bool):
193
- raise ValueError(f"{field} must be a boolean")
194
- return 1 if value else 0
195
- if field_type == "json":
196
- return json.dumps(value, separators=(",", ":"))
197
- return value
198
-
199
- def update_by_id(self, id, data):
200
- if not isinstance(data, dict):
201
- raise ValueError("Update data must be an object")
202
-
203
- fields = list(data.keys())
204
-
205
- if not fields:
206
- raise ValueError("Update data cannot be empty")
207
-
208
- for field in fields:
209
- self._check_field(field)
210
-
211
- if field == self.primary_key:
212
- raise ValueError("Primary key cannot be updated")
213
-
214
- set_clause = ", ".join(f"{field} = ?" for field in fields)
215
- values = [
216
- self._serialize_value(field, data[field])
217
- for field in fields
218
- ]
219
- values.append(id)
220
-
221
- sql = f"""
222
- UPDATE {self.name}
223
- SET {set_clause}
224
- WHERE {self.primary_key} = ?
225
- """
226
-
227
- cursor = self.db.sqlite.execute(sql, values)
228
- self.db.sqlite.commit()
229
-
230
- if cursor.rowcount == 0:
231
- return None
232
-
233
- return self.find_by_id(id)
234
-
235
- def delete_by_id(self, id):
236
- cursor = self.db.sqlite.execute(
237
- f"DELETE FROM {self.name} WHERE {self.primary_key} = ?",
238
- (id,),
239
- )
240
- self.db.sqlite.commit()
241
- return cursor.rowcount > 0
242
-
243
- def drop(self):
244
- self.db.sqlite.execute(f"DROP TABLE IF EXISTS {self.name}")
245
- self.db.sqlite.commit()
246
- self.db._tables.pop(self.name, None)
247
- return True
248
-
249
- # JS-compatible aliases
250
- findById = find_by_id
251
- findAll = find_all
252
- updateById = update_by_id
253
- deleteById = delete_by_id
@@ -1 +0,0 @@
1
- devdb
File without changes
File without changes
File without changes
File without changes