sqliter-py 0.7.0__tar.gz → 0.8.0__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.
Potentially problematic release.
This version of sqliter-py might be problematic. Click here for more details.
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/.gitignore +2 -0
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/LICENSE.txt +1 -1
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/PKG-INFO +13 -12
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/README.md +8 -10
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/pyproject.toml +5 -4
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/constants.py +4 -1
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/model/__init__.py +1 -1
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/model/model.py +28 -6
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/query/query.py +32 -0
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/sqliter.py +18 -2
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/__init__.py +0 -0
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/exceptions.py +0 -0
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/helpers.py +0 -0
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/model/unique.py +0 -0
- {sqliter_py-0.7.0 → sqliter_py-0.8.0}/sqliter/query/__init__.py +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: sqliter-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Interact with SQLite databases using Python and Pydantic
|
|
5
|
+
Project-URL: Homepage, http://sqliter.grantramsay.dev
|
|
5
6
|
Project-URL: Pull Requests, https://github.com/seapagan/sqliter-py/pulls
|
|
6
7
|
Project-URL: Bug Tracker, https://github.com/seapagan/sqliter-py/issues
|
|
7
8
|
Project-URL: Changelog, https://github.com/seapagan/sqliter-py/blob/main/CHANGELOG.md
|
|
@@ -18,6 +19,8 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Database :: Front-Ends
|
|
21
24
|
Classifier: Topic :: Software Development
|
|
22
25
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
26
|
Requires-Python: >=3.9
|
|
@@ -47,8 +50,7 @@ time).
|
|
|
47
50
|
The ideal use case is more for Python CLI tools that need to store data in a
|
|
48
51
|
database-like format without needing to learn SQL or use a full ORM.
|
|
49
52
|
|
|
50
|
-
Full documentation is available on the [
|
|
51
|
-
Website](https://sqliter.grantramsay.dev)
|
|
53
|
+
Full documentation is available on the [Website](https://sqliter.grantramsay.dev)
|
|
52
54
|
|
|
53
55
|
> [!CAUTION]
|
|
54
56
|
> This project is still in the early stages of development and is lacking some
|
|
@@ -57,11 +59,6 @@ Website](https://sqliter.grantramsay.dev)
|
|
|
57
59
|
> minimum and the releases and documentation will be very clear about any
|
|
58
60
|
> breaking changes.
|
|
59
61
|
>
|
|
60
|
-
> Also, structures like `list`, `dict`, `set` etc are not supported **at this
|
|
61
|
-
> time** as field types, since SQLite does not have a native column type for
|
|
62
|
-
> these. This is the **next planned enhancement**. These will need to be
|
|
63
|
-
> `pickled` first then stored as a BLOB in the database.
|
|
64
|
-
>
|
|
65
62
|
> See the [TODO](TODO.md) for planned features and improvements.
|
|
66
63
|
|
|
67
64
|
- [Features](#features)
|
|
@@ -74,7 +71,8 @@ Website](https://sqliter.grantramsay.dev)
|
|
|
74
71
|
## Features
|
|
75
72
|
|
|
76
73
|
- Table creation based on Pydantic models
|
|
77
|
-
- Supports `date` and `datetime` fields
|
|
74
|
+
- Supports `date` and `datetime` fields
|
|
75
|
+
- Support for complex data types (`list`, `dict`, `set`, `tuple`) stored as BLOBs
|
|
78
76
|
- Automatic primary key generation
|
|
79
77
|
- User defined indexes on any field
|
|
80
78
|
- Set any field as UNIQUE
|
|
@@ -159,8 +157,11 @@ for user in results:
|
|
|
159
157
|
new_user.age = 31
|
|
160
158
|
db.update(new_user)
|
|
161
159
|
|
|
162
|
-
# Delete a record
|
|
160
|
+
# Delete a record by primary key
|
|
163
161
|
db.delete(User, new_user.pk)
|
|
162
|
+
|
|
163
|
+
# Delete all records returned from a query:
|
|
164
|
+
delete_count = db.select(User).filter(age__gt=30).delete()
|
|
164
165
|
```
|
|
165
166
|
|
|
166
167
|
See the [Usage](https://sqliter.grantramsay.dev/usage) section of the documentation
|
|
@@ -180,7 +181,7 @@ which you can read in the [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) file.
|
|
|
180
181
|
This project is licensed under the MIT License.
|
|
181
182
|
|
|
182
183
|
```pre
|
|
183
|
-
Copyright (c) 2024 Grant Ramsay
|
|
184
|
+
Copyright (c) 2024-2025 Grant Ramsay
|
|
184
185
|
|
|
185
186
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
186
187
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -19,8 +19,7 @@ time).
|
|
|
19
19
|
The ideal use case is more for Python CLI tools that need to store data in a
|
|
20
20
|
database-like format without needing to learn SQL or use a full ORM.
|
|
21
21
|
|
|
22
|
-
Full documentation is available on the [
|
|
23
|
-
Website](https://sqliter.grantramsay.dev)
|
|
22
|
+
Full documentation is available on the [Website](https://sqliter.grantramsay.dev)
|
|
24
23
|
|
|
25
24
|
> [!CAUTION]
|
|
26
25
|
> This project is still in the early stages of development and is lacking some
|
|
@@ -29,11 +28,6 @@ Website](https://sqliter.grantramsay.dev)
|
|
|
29
28
|
> minimum and the releases and documentation will be very clear about any
|
|
30
29
|
> breaking changes.
|
|
31
30
|
>
|
|
32
|
-
> Also, structures like `list`, `dict`, `set` etc are not supported **at this
|
|
33
|
-
> time** as field types, since SQLite does not have a native column type for
|
|
34
|
-
> these. This is the **next planned enhancement**. These will need to be
|
|
35
|
-
> `pickled` first then stored as a BLOB in the database.
|
|
36
|
-
>
|
|
37
31
|
> See the [TODO](TODO.md) for planned features and improvements.
|
|
38
32
|
|
|
39
33
|
- [Features](#features)
|
|
@@ -46,7 +40,8 @@ Website](https://sqliter.grantramsay.dev)
|
|
|
46
40
|
## Features
|
|
47
41
|
|
|
48
42
|
- Table creation based on Pydantic models
|
|
49
|
-
- Supports `date` and `datetime` fields
|
|
43
|
+
- Supports `date` and `datetime` fields
|
|
44
|
+
- Support for complex data types (`list`, `dict`, `set`, `tuple`) stored as BLOBs
|
|
50
45
|
- Automatic primary key generation
|
|
51
46
|
- User defined indexes on any field
|
|
52
47
|
- Set any field as UNIQUE
|
|
@@ -131,8 +126,11 @@ for user in results:
|
|
|
131
126
|
new_user.age = 31
|
|
132
127
|
db.update(new_user)
|
|
133
128
|
|
|
134
|
-
# Delete a record
|
|
129
|
+
# Delete a record by primary key
|
|
135
130
|
db.delete(User, new_user.pk)
|
|
131
|
+
|
|
132
|
+
# Delete all records returned from a query:
|
|
133
|
+
delete_count = db.select(User).filter(age__gt=30).delete()
|
|
136
134
|
```
|
|
137
135
|
|
|
138
136
|
See the [Usage](https://sqliter.grantramsay.dev/usage) section of the documentation
|
|
@@ -152,7 +150,7 @@ which you can read in the [CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) file.
|
|
|
152
150
|
This project is licensed under the MIT License.
|
|
153
151
|
|
|
154
152
|
```pre
|
|
155
|
-
Copyright (c) 2024 Grant Ramsay
|
|
153
|
+
Copyright (c) 2024-2025 Grant Ramsay
|
|
156
154
|
|
|
157
155
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
158
156
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
[project]
|
|
5
5
|
name = "sqliter-py"
|
|
6
|
-
version = "0.
|
|
6
|
+
version = "0.8.0"
|
|
7
7
|
description = "Interact with SQLite databases using Python and Pydantic"
|
|
8
8
|
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.9"
|
|
@@ -21,6 +21,8 @@ classifiers = [
|
|
|
21
21
|
"Programming Language :: Python :: 3.10",
|
|
22
22
|
"Programming Language :: Python :: 3.11",
|
|
23
23
|
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Database :: Front-Ends",
|
|
24
26
|
"Topic :: Software Development",
|
|
25
27
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
28
|
]
|
|
@@ -29,7 +31,7 @@ classifiers = [
|
|
|
29
31
|
extras = ["inflect==7.0.0"]
|
|
30
32
|
|
|
31
33
|
[project.urls]
|
|
32
|
-
|
|
34
|
+
"Homepage" = "http://sqliter.grantramsay.dev"
|
|
33
35
|
"Pull Requests" = "https://github.com/seapagan/sqliter-py/pulls"
|
|
34
36
|
"Bug Tracker" = "https://github.com/seapagan/sqliter-py/issues"
|
|
35
37
|
"Changelog" = "https://github.com/seapagan/sqliter-py/blob/main/CHANGELOG.md"
|
|
@@ -100,12 +102,11 @@ changelog.help = "Generate a changelog"
|
|
|
100
102
|
line-length = 80
|
|
101
103
|
lint.select = ["ALL"] # we are being very strict!
|
|
102
104
|
lint.ignore = [
|
|
103
|
-
"ANN101",
|
|
104
|
-
"ANN102",
|
|
105
105
|
"PGH003",
|
|
106
106
|
"FBT002",
|
|
107
107
|
"FBT003",
|
|
108
108
|
"B006",
|
|
109
|
+
"S301", # in this library we use 'pickle' for saving and loading list etc
|
|
109
110
|
] # These rules are too strict even for us 😝
|
|
110
111
|
lint.extend-ignore = [
|
|
111
112
|
"COM812",
|
|
@@ -10,7 +10,6 @@ import datetime
|
|
|
10
10
|
|
|
11
11
|
# A dictionary mapping SQLiter filter operators to their corresponding SQL
|
|
12
12
|
# operators.
|
|
13
|
-
|
|
14
13
|
OPERATOR_MAPPING = {
|
|
15
14
|
"__lt": "<",
|
|
16
15
|
"__lte": "<=",
|
|
@@ -39,4 +38,8 @@ SQLITE_TYPE_MAPPING = {
|
|
|
39
38
|
bytes: "BLOB",
|
|
40
39
|
datetime.datetime: "INTEGER", # Store as Unix timestamp
|
|
41
40
|
datetime.date: "INTEGER", # Store as Unix timestamp
|
|
41
|
+
list: "BLOB",
|
|
42
|
+
dict: "BLOB",
|
|
43
|
+
set: "BLOB",
|
|
44
|
+
tuple: "BLOB",
|
|
42
45
|
}
|
|
@@ -10,6 +10,7 @@ in SQLiter applications.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import datetime
|
|
13
|
+
import pickle
|
|
13
14
|
import re
|
|
14
15
|
from typing import (
|
|
15
16
|
Any,
|
|
@@ -58,7 +59,7 @@ class BaseDBModel(BaseModel):
|
|
|
58
59
|
model_config = ConfigDict(
|
|
59
60
|
extra="ignore",
|
|
60
61
|
populate_by_name=True,
|
|
61
|
-
validate_assignment=
|
|
62
|
+
validate_assignment=True,
|
|
62
63
|
from_attributes=True,
|
|
63
64
|
)
|
|
64
65
|
|
|
@@ -181,7 +182,9 @@ class BaseDBModel(BaseModel):
|
|
|
181
182
|
"""
|
|
182
183
|
if isinstance(value, (datetime.datetime, datetime.date)):
|
|
183
184
|
return to_unix_timestamp(value)
|
|
184
|
-
|
|
185
|
+
if isinstance(value, (list, dict, set, tuple)):
|
|
186
|
+
return pickle.dumps(value)
|
|
187
|
+
return value # Return value as-is for other fields
|
|
185
188
|
|
|
186
189
|
# Deserialization after fetching from the database
|
|
187
190
|
|
|
@@ -205,12 +208,31 @@ class BaseDBModel(BaseModel):
|
|
|
205
208
|
A datetime or date object if the field type is datetime or date,
|
|
206
209
|
otherwise returns the value as-is.
|
|
207
210
|
"""
|
|
208
|
-
|
|
211
|
+
if value is None:
|
|
212
|
+
return None
|
|
209
213
|
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
# Get field type if it exists in model_fields
|
|
215
|
+
field_info = cls.model_fields.get(field_name)
|
|
216
|
+
if field_info is None:
|
|
217
|
+
# If field doesn't exist in model, return value as-is
|
|
218
|
+
return value
|
|
219
|
+
|
|
220
|
+
field_type = field_info.annotation
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
isinstance(field_type, type)
|
|
224
|
+
and issubclass(field_type, (datetime.datetime, datetime.date))
|
|
225
|
+
and isinstance(value, int)
|
|
212
226
|
):
|
|
213
227
|
return from_unix_timestamp(
|
|
214
228
|
value, field_type, localize=return_local_time
|
|
215
229
|
)
|
|
216
|
-
|
|
230
|
+
|
|
231
|
+
origin_type = get_origin(field_type) or field_type
|
|
232
|
+
if origin_type in (list, dict, set, tuple) and isinstance(value, bytes):
|
|
233
|
+
try:
|
|
234
|
+
return pickle.loads(value)
|
|
235
|
+
except pickle.UnpicklingError:
|
|
236
|
+
return value
|
|
237
|
+
|
|
238
|
+
return value
|
|
@@ -28,6 +28,7 @@ from sqliter.exceptions import (
|
|
|
28
28
|
InvalidFilterError,
|
|
29
29
|
InvalidOffsetError,
|
|
30
30
|
InvalidOrderError,
|
|
31
|
+
RecordDeletionError,
|
|
31
32
|
RecordFetchError,
|
|
32
33
|
)
|
|
33
34
|
|
|
@@ -728,3 +729,34 @@ class QueryBuilder:
|
|
|
728
729
|
True if at least one result exists, False otherwise.
|
|
729
730
|
"""
|
|
730
731
|
return self.count() > 0
|
|
732
|
+
|
|
733
|
+
def delete(self) -> int:
|
|
734
|
+
"""Delete records that match the current query conditions.
|
|
735
|
+
|
|
736
|
+
Returns:
|
|
737
|
+
The number of records deleted.
|
|
738
|
+
|
|
739
|
+
Raises:
|
|
740
|
+
RecordDeletionError: If there's an error deleting the records.
|
|
741
|
+
"""
|
|
742
|
+
sql = f'DELETE FROM "{self.table_name}"' # noqa: S608 # nosec
|
|
743
|
+
|
|
744
|
+
# Build the WHERE clause with special handling for None (NULL in SQL)
|
|
745
|
+
values, where_clause = self._parse_filter()
|
|
746
|
+
|
|
747
|
+
if self.filters:
|
|
748
|
+
sql += f" WHERE {where_clause}"
|
|
749
|
+
|
|
750
|
+
# Print the raw SQL and values if debug is enabled
|
|
751
|
+
if self.db.debug:
|
|
752
|
+
self.db._log_sql(sql, values) # noqa: SLF001
|
|
753
|
+
|
|
754
|
+
try:
|
|
755
|
+
with self.db.connect() as conn:
|
|
756
|
+
cursor = conn.cursor()
|
|
757
|
+
cursor.execute(sql, values)
|
|
758
|
+
deleted_count = cursor.rowcount
|
|
759
|
+
self.db._maybe_commit() # noqa: SLF001
|
|
760
|
+
return deleted_count
|
|
761
|
+
except sqlite3.Error as exc:
|
|
762
|
+
raise RecordDeletionError(self.table_name) from exc
|
|
@@ -503,7 +503,13 @@ class SqliterDB:
|
|
|
503
503
|
raise RecordInsertionError(table_name) from exc
|
|
504
504
|
else:
|
|
505
505
|
data.pop("pk", None)
|
|
506
|
-
|
|
506
|
+
# Deserialize each field before creating the model instance
|
|
507
|
+
deserialized_data = {}
|
|
508
|
+
for field_name, value in data.items():
|
|
509
|
+
deserialized_data[field_name] = model_class.deserialize_field(
|
|
510
|
+
field_name, value, return_local_time=self.return_local_time
|
|
511
|
+
)
|
|
512
|
+
return model_class(pk=cursor.lastrowid, **deserialized_data)
|
|
507
513
|
|
|
508
514
|
def get(
|
|
509
515
|
self, model_class: type[BaseDBModel], primary_key_value: int
|
|
@@ -540,7 +546,17 @@ class SqliterDB:
|
|
|
540
546
|
field: result[idx]
|
|
541
547
|
for idx, field in enumerate(model_class.model_fields)
|
|
542
548
|
}
|
|
543
|
-
|
|
549
|
+
# Deserialize each field before creating the model instance
|
|
550
|
+
deserialized_data = {}
|
|
551
|
+
for field_name, value in result_dict.items():
|
|
552
|
+
deserialized_data[field_name] = (
|
|
553
|
+
model_class.deserialize_field(
|
|
554
|
+
field_name,
|
|
555
|
+
value,
|
|
556
|
+
return_local_time=self.return_local_time,
|
|
557
|
+
)
|
|
558
|
+
)
|
|
559
|
+
return model_class(**deserialized_data)
|
|
544
560
|
except sqlite3.Error as exc:
|
|
545
561
|
raise RecordFetchError(table_name) from exc
|
|
546
562
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|