sqliter-py 0.6.0__tar.gz → 0.7.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.6.0 → sqliter_py-0.7.0}/PKG-INFO +3 -3
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/README.md +2 -2
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/pyproject.toml +1 -1
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/constants.py +5 -0
- sqliter_py-0.7.0/sqliter/helpers.py +100 -0
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/model/__init__.py +2 -2
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/model/model.py +63 -0
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/query/query.py +26 -8
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/sqliter.py +100 -16
- sqliter_py-0.6.0/sqliter/helpers.py +0 -35
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/.gitignore +0 -0
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/LICENSE.txt +0 -0
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/__init__.py +0 -0
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/exceptions.py +0 -0
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/model/unique.py +0 -0
- {sqliter_py-0.6.0 → sqliter_py-0.7.0}/sqliter/query/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sqliter-py
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Interact with SQLite databases using Python and Pydantic
|
|
5
5
|
Project-URL: Pull Requests, https://github.com/seapagan/sqliter-py/pulls
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/seapagan/sqliter-py/issues
|
|
@@ -60,8 +60,7 @@ Website](https://sqliter.grantramsay.dev)
|
|
|
60
60
|
> Also, structures like `list`, `dict`, `set` etc are not supported **at this
|
|
61
61
|
> time** as field types, since SQLite does not have a native column type for
|
|
62
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
|
-
> which can be stored as a Unix timestamp in an integer field.
|
|
63
|
+
> `pickled` first then stored as a BLOB in the database.
|
|
65
64
|
>
|
|
66
65
|
> See the [TODO](TODO.md) for planned features and improvements.
|
|
67
66
|
|
|
@@ -75,6 +74,7 @@ Website](https://sqliter.grantramsay.dev)
|
|
|
75
74
|
## Features
|
|
76
75
|
|
|
77
76
|
- Table creation based on Pydantic models
|
|
77
|
+
- Supports `date` and `datetime` fields. List/Dict/Set fields are planned.
|
|
78
78
|
- Automatic primary key generation
|
|
79
79
|
- User defined indexes on any field
|
|
80
80
|
- Set any field as UNIQUE
|
|
@@ -32,8 +32,7 @@ Website](https://sqliter.grantramsay.dev)
|
|
|
32
32
|
> Also, structures like `list`, `dict`, `set` etc are not supported **at this
|
|
33
33
|
> time** as field types, since SQLite does not have a native column type for
|
|
34
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
|
-
> which can be stored as a Unix timestamp in an integer field.
|
|
35
|
+
> `pickled` first then stored as a BLOB in the database.
|
|
37
36
|
>
|
|
38
37
|
> See the [TODO](TODO.md) for planned features and improvements.
|
|
39
38
|
|
|
@@ -47,6 +46,7 @@ Website](https://sqliter.grantramsay.dev)
|
|
|
47
46
|
## Features
|
|
48
47
|
|
|
49
48
|
- Table creation based on Pydantic models
|
|
49
|
+
- Supports `date` and `datetime` fields. List/Dict/Set fields are planned.
|
|
50
50
|
- Automatic primary key generation
|
|
51
51
|
- User defined indexes on any field
|
|
52
52
|
- Set any field as UNIQUE
|
|
@@ -6,8 +6,11 @@ operators and data types, which are crucial for translating between
|
|
|
6
6
|
Pydantic models and SQLite database operations.
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
import datetime
|
|
10
|
+
|
|
9
11
|
# A dictionary mapping SQLiter filter operators to their corresponding SQL
|
|
10
12
|
# operators.
|
|
13
|
+
|
|
11
14
|
OPERATOR_MAPPING = {
|
|
12
15
|
"__lt": "<",
|
|
13
16
|
"__lte": "<=",
|
|
@@ -34,4 +37,6 @@ SQLITE_TYPE_MAPPING = {
|
|
|
34
37
|
str: "TEXT",
|
|
35
38
|
bool: "INTEGER", # SQLite stores booleans as integers (0 or 1)
|
|
36
39
|
bytes: "BLOB",
|
|
40
|
+
datetime.datetime: "INTEGER", # Store as Unix timestamp
|
|
41
|
+
datetime.date: "INTEGER", # Store as Unix timestamp
|
|
37
42
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Utility functions for SQLiter internal operations.
|
|
2
|
+
|
|
3
|
+
This module provides helper functions used across the SQLiter library,
|
|
4
|
+
primarily for type inference and mapping between Python and SQLite
|
|
5
|
+
data types. These utilities support the core functionality of model
|
|
6
|
+
to database schema translation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import datetime
|
|
12
|
+
from typing import Union
|
|
13
|
+
|
|
14
|
+
from sqliter.constants import SQLITE_TYPE_MAPPING
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def infer_sqlite_type(field_type: Union[type, None]) -> str:
|
|
18
|
+
"""Infer the SQLite column type based on the Python type.
|
|
19
|
+
|
|
20
|
+
This function maps Python types to their corresponding SQLite column
|
|
21
|
+
types. It's used when creating database tables to ensure that the
|
|
22
|
+
correct SQLite types are used for each field.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
field_type: The Python type of the field, or None.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
A string representing the corresponding SQLite column type.
|
|
29
|
+
|
|
30
|
+
Note:
|
|
31
|
+
If the input type is None or not recognized, it defaults to 'TEXT'.
|
|
32
|
+
"""
|
|
33
|
+
# If field_type is None, default to TEXT
|
|
34
|
+
if field_type is None:
|
|
35
|
+
return "TEXT"
|
|
36
|
+
|
|
37
|
+
# Map the simplified type to an SQLite type
|
|
38
|
+
return SQLITE_TYPE_MAPPING.get(field_type, "TEXT")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def to_unix_timestamp(value: datetime.date | datetime.datetime) -> int:
|
|
42
|
+
"""Convert datetime or date to a Unix timestamp in UTC.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
value: The datetime or date object to convert.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
An integer Unix timestamp.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
TypeError: If the value is not a datetime or date object.
|
|
52
|
+
"""
|
|
53
|
+
if isinstance(value, datetime.datetime):
|
|
54
|
+
# If no timezone is provided, assume local time and convert to UTC
|
|
55
|
+
if value.tzinfo is None:
|
|
56
|
+
value = value.astimezone() # Convert to user's local timezone
|
|
57
|
+
# Convert to UTC before storing
|
|
58
|
+
value = value.astimezone(datetime.timezone.utc)
|
|
59
|
+
return int(value.timestamp())
|
|
60
|
+
if isinstance(value, datetime.date):
|
|
61
|
+
# Convert date to datetime at midnight in UTC
|
|
62
|
+
dt = datetime.datetime.combine(
|
|
63
|
+
value, datetime.time(0, 0), tzinfo=datetime.timezone.utc
|
|
64
|
+
)
|
|
65
|
+
return int(dt.timestamp())
|
|
66
|
+
|
|
67
|
+
err_msg = "Expected datetime or date object."
|
|
68
|
+
raise TypeError(err_msg)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def from_unix_timestamp(
|
|
72
|
+
value: int, to_type: type, *, localize: bool = True
|
|
73
|
+
) -> datetime.date | datetime.datetime:
|
|
74
|
+
"""Convert a Unix timestamp to datetime or date, optionally to local time.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
value: The Unix timestamp as an integer.
|
|
78
|
+
to_type: The expected output type, either datetime or date.
|
|
79
|
+
localize: If True, convert the datetime to the user's local timezone.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
The corresponding datetime or date object.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
TypeError: If to_type is not datetime or date.
|
|
86
|
+
"""
|
|
87
|
+
if to_type is datetime.datetime:
|
|
88
|
+
# Convert the Unix timestamp to UTC datetime
|
|
89
|
+
dt = datetime.datetime.fromtimestamp(value, tz=datetime.timezone.utc)
|
|
90
|
+
# Convert to local time if requested
|
|
91
|
+
return dt.astimezone() if localize else dt
|
|
92
|
+
if to_type is datetime.date:
|
|
93
|
+
# Convert to UTC datetime first
|
|
94
|
+
dt = datetime.datetime.fromtimestamp(value, tz=datetime.timezone.utc)
|
|
95
|
+
# Convert to local time if requested, then return the date part
|
|
96
|
+
dt_local = dt.astimezone() if localize else dt
|
|
97
|
+
return dt_local.date() # Extract the date part
|
|
98
|
+
|
|
99
|
+
err_msg = "Expected datetime or date type."
|
|
100
|
+
raise TypeError(err_msg)
|
|
@@ -5,7 +5,7 @@ models in SQLiter applications, and the Unique class, which is used to
|
|
|
5
5
|
define unique constraints on model fields.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from .model import BaseDBModel
|
|
8
|
+
from .model import BaseDBModel, SerializableField
|
|
9
9
|
from .unique import Unique
|
|
10
10
|
|
|
11
|
-
__all__ = ["BaseDBModel", "Unique"]
|
|
11
|
+
__all__ = ["BaseDBModel", "Unique", "SerializableField"]
|
|
@@ -9,11 +9,13 @@ in SQLiter applications.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import datetime
|
|
12
13
|
import re
|
|
13
14
|
from typing import (
|
|
14
15
|
Any,
|
|
15
16
|
ClassVar,
|
|
16
17
|
Optional,
|
|
18
|
+
Protocol,
|
|
17
19
|
TypeVar,
|
|
18
20
|
Union,
|
|
19
21
|
cast,
|
|
@@ -23,9 +25,15 @@ from typing import (
|
|
|
23
25
|
|
|
24
26
|
from pydantic import BaseModel, ConfigDict, Field
|
|
25
27
|
|
|
28
|
+
from sqliter.helpers import from_unix_timestamp, to_unix_timestamp
|
|
29
|
+
|
|
26
30
|
T = TypeVar("T", bound="BaseDBModel")
|
|
27
31
|
|
|
28
32
|
|
|
33
|
+
class SerializableField(Protocol):
|
|
34
|
+
"""Protocol for fields that can be serialized or deserialized."""
|
|
35
|
+
|
|
36
|
+
|
|
29
37
|
class BaseDBModel(BaseModel):
|
|
30
38
|
"""Base model class for SQLiter database models.
|
|
31
39
|
|
|
@@ -38,6 +46,14 @@ class BaseDBModel(BaseModel):
|
|
|
38
46
|
"""
|
|
39
47
|
|
|
40
48
|
pk: int = Field(0, description="The mandatory primary key of the table.")
|
|
49
|
+
created_at: int = Field(
|
|
50
|
+
default=0,
|
|
51
|
+
description="Unix timestamp when the record was created.",
|
|
52
|
+
)
|
|
53
|
+
updated_at: int = Field(
|
|
54
|
+
default=0,
|
|
55
|
+
description="Unix timestamp when the record was last updated.",
|
|
56
|
+
)
|
|
41
57
|
|
|
42
58
|
model_config = ConfigDict(
|
|
43
59
|
extra="ignore",
|
|
@@ -151,3 +167,50 @@ class BaseDBModel(BaseModel):
|
|
|
151
167
|
def should_create_pk(cls) -> bool:
|
|
152
168
|
"""Returns True since the primary key is always created."""
|
|
153
169
|
return True
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def serialize_field(cls, value: SerializableField) -> SerializableField:
|
|
173
|
+
"""Serialize datetime or date fields to Unix timestamp.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
field_name: The name of the field.
|
|
177
|
+
value: The value of the field.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
An integer Unix timestamp if the field is a datetime or date.
|
|
181
|
+
"""
|
|
182
|
+
if isinstance(value, (datetime.datetime, datetime.date)):
|
|
183
|
+
return to_unix_timestamp(value)
|
|
184
|
+
return value # Return value as-is for non-datetime fields
|
|
185
|
+
|
|
186
|
+
# Deserialization after fetching from the database
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def deserialize_field(
|
|
190
|
+
cls,
|
|
191
|
+
field_name: str,
|
|
192
|
+
value: SerializableField,
|
|
193
|
+
*,
|
|
194
|
+
return_local_time: bool,
|
|
195
|
+
) -> object:
|
|
196
|
+
"""Deserialize fields from Unix timestamp to datetime or date.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
field_name: The name of the field being deserialized.
|
|
200
|
+
value: The Unix timestamp value fetched from the database.
|
|
201
|
+
return_local_time: Flag to control whether the datetime is localized
|
|
202
|
+
to the user's timezone.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
A datetime or date object if the field type is datetime or date,
|
|
206
|
+
otherwise returns the value as-is.
|
|
207
|
+
"""
|
|
208
|
+
field_type = cls.__annotations__.get(field_name)
|
|
209
|
+
|
|
210
|
+
if field_type in (datetime.datetime, datetime.date) and isinstance(
|
|
211
|
+
value, int
|
|
212
|
+
):
|
|
213
|
+
return from_unix_timestamp(
|
|
214
|
+
value, field_type, localize=return_local_time
|
|
215
|
+
)
|
|
216
|
+
return value # Return value as-is for non-datetime fields
|
|
@@ -35,7 +35,7 @@ if TYPE_CHECKING: # pragma: no cover
|
|
|
35
35
|
from pydantic.fields import FieldInfo
|
|
36
36
|
|
|
37
37
|
from sqliter import SqliterDB
|
|
38
|
-
from sqliter.model import BaseDBModel
|
|
38
|
+
from sqliter.model import BaseDBModel, SerializableField
|
|
39
39
|
|
|
40
40
|
# Define a type alias for the possible value types
|
|
41
41
|
FilterValue = Union[
|
|
@@ -609,14 +609,32 @@ class QueryBuilder:
|
|
|
609
609
|
An instance of the model class populated with the row data.
|
|
610
610
|
"""
|
|
611
611
|
if self._fields:
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
return self.model_class(
|
|
616
|
-
**{
|
|
617
|
-
field: row[idx]
|
|
618
|
-
for idx, field in enumerate(self.model_class.model_fields)
|
|
612
|
+
data = {
|
|
613
|
+
field: self._deserialize(field, row[idx])
|
|
614
|
+
for idx, field in enumerate(self._fields)
|
|
619
615
|
}
|
|
616
|
+
return self.model_class.model_validate_partial(data)
|
|
617
|
+
|
|
618
|
+
data = {
|
|
619
|
+
field: self._deserialize(field, row[idx])
|
|
620
|
+
for idx, field in enumerate(self.model_class.model_fields)
|
|
621
|
+
}
|
|
622
|
+
return self.model_class(**data)
|
|
623
|
+
|
|
624
|
+
def _deserialize(
|
|
625
|
+
self, field_name: str, value: SerializableField
|
|
626
|
+
) -> SerializableField:
|
|
627
|
+
"""Deserialize a field value if needed.
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
field_name: Name of the field being deserialized.
|
|
631
|
+
value: Value from the database.
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
The deserialized value.
|
|
635
|
+
"""
|
|
636
|
+
return self.model_class.deserialize_field(
|
|
637
|
+
field_name, value, return_local_time=self.db.return_local_time
|
|
620
638
|
)
|
|
621
639
|
|
|
622
640
|
@overload
|
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
|
|
11
11
|
import logging
|
|
12
12
|
import sqlite3
|
|
13
|
+
import time
|
|
13
14
|
from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union
|
|
14
15
|
|
|
15
16
|
from typing_extensions import Self
|
|
@@ -51,7 +52,9 @@ class SqliterDB:
|
|
|
51
52
|
logger (Optional[logging.Logger]): Custom logger for debug output.
|
|
52
53
|
"""
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
MEMORY_DB = ":memory:"
|
|
56
|
+
|
|
57
|
+
def __init__( # noqa: PLR0913
|
|
55
58
|
self,
|
|
56
59
|
db_filename: Optional[str] = None,
|
|
57
60
|
*,
|
|
@@ -60,6 +63,7 @@ class SqliterDB:
|
|
|
60
63
|
debug: bool = False,
|
|
61
64
|
logger: Optional[logging.Logger] = None,
|
|
62
65
|
reset: bool = False,
|
|
66
|
+
return_local_time: bool = True,
|
|
63
67
|
) -> None:
|
|
64
68
|
"""Initialize a new SqliterDB instance.
|
|
65
69
|
|
|
@@ -71,12 +75,13 @@ class SqliterDB:
|
|
|
71
75
|
logger: Custom logger for debug output.
|
|
72
76
|
reset: Whether to reset the database on initialization. This will
|
|
73
77
|
basically drop all existing tables.
|
|
78
|
+
return_local_time: Whether to return local time for datetime fields.
|
|
74
79
|
|
|
75
80
|
Raises:
|
|
76
81
|
ValueError: If no filename is provided for a non-memory database.
|
|
77
82
|
"""
|
|
78
83
|
if memory:
|
|
79
|
-
self.db_filename =
|
|
84
|
+
self.db_filename = self.MEMORY_DB
|
|
80
85
|
elif db_filename:
|
|
81
86
|
self.db_filename = db_filename
|
|
82
87
|
else:
|
|
@@ -90,6 +95,7 @@ class SqliterDB:
|
|
|
90
95
|
self.logger = logger
|
|
91
96
|
self.conn: Optional[sqlite3.Connection] = None
|
|
92
97
|
self.reset = reset
|
|
98
|
+
self.return_local_time = return_local_time
|
|
93
99
|
|
|
94
100
|
self._in_transaction = False
|
|
95
101
|
|
|
@@ -99,6 +105,54 @@ class SqliterDB:
|
|
|
99
105
|
if self.reset:
|
|
100
106
|
self._reset_database()
|
|
101
107
|
|
|
108
|
+
@property
|
|
109
|
+
def filename(self) -> Optional[str]:
|
|
110
|
+
"""Returns the filename of the current database or None if in-memory."""
|
|
111
|
+
return None if self.db_filename == self.MEMORY_DB else self.db_filename
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def is_memory(self) -> bool:
|
|
115
|
+
"""Returns True if the database is in-memory."""
|
|
116
|
+
return self.db_filename == self.MEMORY_DB
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def is_autocommit(self) -> bool:
|
|
120
|
+
"""Returns True if auto-commit is enabled."""
|
|
121
|
+
return self.auto_commit
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def is_connected(self) -> bool:
|
|
125
|
+
"""Returns True if the database is connected, False otherwise."""
|
|
126
|
+
return self.conn is not None
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def table_names(self) -> list[str]:
|
|
130
|
+
"""Returns a list of all table names in the database.
|
|
131
|
+
|
|
132
|
+
Temporarily connects to the database if not connected and restores
|
|
133
|
+
the connection state afterward.
|
|
134
|
+
"""
|
|
135
|
+
was_connected = self.is_connected
|
|
136
|
+
if not was_connected:
|
|
137
|
+
self.connect()
|
|
138
|
+
|
|
139
|
+
if self.conn is None:
|
|
140
|
+
err_msg = "Failed to establish a database connection."
|
|
141
|
+
raise DatabaseConnectionError(err_msg)
|
|
142
|
+
|
|
143
|
+
cursor = self.conn.cursor()
|
|
144
|
+
cursor.execute(
|
|
145
|
+
"SELECT name FROM sqlite_master WHERE type='table' "
|
|
146
|
+
"AND name NOT LIKE 'sqlite_%';"
|
|
147
|
+
)
|
|
148
|
+
tables = [row[0] for row in cursor.fetchall()]
|
|
149
|
+
|
|
150
|
+
# Restore the connection state
|
|
151
|
+
if not was_connected:
|
|
152
|
+
self.close()
|
|
153
|
+
|
|
154
|
+
return tables
|
|
155
|
+
|
|
102
156
|
def _reset_database(self) -> None:
|
|
103
157
|
"""Drop all user-created tables in the database."""
|
|
104
158
|
with self.connect() as conn:
|
|
@@ -379,11 +433,18 @@ class SqliterDB:
|
|
|
379
433
|
if not self._in_transaction and self.auto_commit and self.conn:
|
|
380
434
|
self.conn.commit()
|
|
381
435
|
|
|
382
|
-
def insert(
|
|
436
|
+
def insert(
|
|
437
|
+
self, model_instance: T, *, timestamp_override: bool = False
|
|
438
|
+
) -> T:
|
|
383
439
|
"""Insert a new record into the database.
|
|
384
440
|
|
|
385
441
|
Args:
|
|
386
442
|
model_instance: The instance of the model class to insert.
|
|
443
|
+
timestamp_override: If True, override the created_at and updated_at
|
|
444
|
+
timestamps with provided values. Default is False. If the values
|
|
445
|
+
are not provided, they will be set to the current time as
|
|
446
|
+
normal. Without this flag, the timestamps will always be set to
|
|
447
|
+
the current time, even if provided.
|
|
387
448
|
|
|
388
449
|
Returns:
|
|
389
450
|
The updated model instance with the primary key (pk) set.
|
|
@@ -394,8 +455,28 @@ class SqliterDB:
|
|
|
394
455
|
model_class = type(model_instance)
|
|
395
456
|
table_name = model_class.get_table_name()
|
|
396
457
|
|
|
458
|
+
# Always set created_at and updated_at timestamps
|
|
459
|
+
current_timestamp = int(time.time())
|
|
460
|
+
|
|
461
|
+
# Handle the case where timestamp_override is False
|
|
462
|
+
if not timestamp_override:
|
|
463
|
+
# Always override both timestamps with the current time
|
|
464
|
+
model_instance.created_at = current_timestamp
|
|
465
|
+
model_instance.updated_at = current_timestamp
|
|
466
|
+
else:
|
|
467
|
+
# Respect provided values, but set to current time if they are 0
|
|
468
|
+
if model_instance.created_at == 0:
|
|
469
|
+
model_instance.created_at = current_timestamp
|
|
470
|
+
if model_instance.updated_at == 0:
|
|
471
|
+
model_instance.updated_at = current_timestamp
|
|
472
|
+
|
|
397
473
|
# Get the data from the model
|
|
398
474
|
data = model_instance.model_dump()
|
|
475
|
+
|
|
476
|
+
# Serialize the data
|
|
477
|
+
for field_name, value in list(data.items()):
|
|
478
|
+
data[field_name] = model_instance.serialize_field(value)
|
|
479
|
+
|
|
399
480
|
# remove the primary key field if it exists, otherwise we'll get
|
|
400
481
|
# TypeErrors as multiple primary keys will exist
|
|
401
482
|
if data.get("pk", None) == 0:
|
|
@@ -473,24 +554,27 @@ class SqliterDB:
|
|
|
473
554
|
|
|
474
555
|
Raises:
|
|
475
556
|
RecordUpdateError: If there's an error updating the record or if it
|
|
476
|
-
|
|
557
|
+
is not found.
|
|
477
558
|
"""
|
|
478
559
|
model_class = type(model_instance)
|
|
479
560
|
table_name = model_class.get_table_name()
|
|
480
|
-
|
|
481
561
|
primary_key = model_class.get_primary_key()
|
|
482
562
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
primary_key_value =
|
|
563
|
+
# Set updated_at timestamp
|
|
564
|
+
current_timestamp = int(time.time())
|
|
565
|
+
model_instance.updated_at = current_timestamp
|
|
566
|
+
|
|
567
|
+
# Get the data and serialize any datetime/date fields
|
|
568
|
+
data = model_instance.model_dump()
|
|
569
|
+
for field_name, value in list(data.items()):
|
|
570
|
+
data[field_name] = model_instance.serialize_field(value)
|
|
571
|
+
|
|
572
|
+
# Remove the primary key from the update data
|
|
573
|
+
primary_key_value = data.pop(primary_key)
|
|
574
|
+
|
|
575
|
+
# Create the SQL using the processed data
|
|
576
|
+
fields = ", ".join(f"{field} = ?" for field in data)
|
|
577
|
+
values = tuple(data.values())
|
|
494
578
|
|
|
495
579
|
update_sql = f"""
|
|
496
580
|
UPDATE {table_name}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
"""Utility functions for SQLiter internal operations.
|
|
2
|
-
|
|
3
|
-
This module provides helper functions used across the SQLiter library,
|
|
4
|
-
primarily for type inference and mapping between Python and SQLite
|
|
5
|
-
data types. These utilities support the core functionality of model
|
|
6
|
-
to database schema translation.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from typing import Union
|
|
10
|
-
|
|
11
|
-
from sqliter.constants import SQLITE_TYPE_MAPPING
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def infer_sqlite_type(field_type: Union[type, None]) -> str:
|
|
15
|
-
"""Infer the SQLite column type based on the Python type.
|
|
16
|
-
|
|
17
|
-
This function maps Python types to their corresponding SQLite column
|
|
18
|
-
types. It's used when creating database tables to ensure that the
|
|
19
|
-
correct SQLite types are used for each field.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
field_type: The Python type of the field, or None.
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
A string representing the corresponding SQLite column type.
|
|
26
|
-
|
|
27
|
-
Note:
|
|
28
|
-
If the input type is None or not recognized, it defaults to 'TEXT'.
|
|
29
|
-
"""
|
|
30
|
-
# If field_type is None, default to TEXT
|
|
31
|
-
if field_type is None:
|
|
32
|
-
return "TEXT"
|
|
33
|
-
|
|
34
|
-
# Map the simplified type to an SQLite type
|
|
35
|
-
return SQLITE_TYPE_MAPPING.get(field_type, "TEXT")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|