sqliter-py 0.3.0__py3-none-any.whl → 0.9.0__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.
- sqliter/__init__.py +5 -1
- sqliter/constants.py +26 -1
- sqliter/exceptions.py +61 -13
- sqliter/helpers.py +100 -0
- sqliter/model/__init__.py +7 -4
- sqliter/model/model.py +155 -23
- sqliter/model/unique.py +19 -0
- sqliter/py.typed +0 -0
- sqliter/query/__init__.py +5 -1
- sqliter/query/query.py +355 -54
- sqliter/sqliter.py +506 -57
- sqliter_py-0.9.0.dist-info/METADATA +209 -0
- sqliter_py-0.9.0.dist-info/RECORD +14 -0
- sqliter_py-0.9.0.dist-info/WHEEL +4 -0
- sqliter_py-0.3.0.dist-info/METADATA +0 -601
- sqliter_py-0.3.0.dist-info/RECORD +0 -12
- sqliter_py-0.3.0.dist-info/WHEEL +0 -4
- sqliter_py-0.3.0.dist-info/licenses/LICENSE.txt +0 -20
sqliter/__init__.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""SQLiter: A lightweight ORM-like library for SQLite databases in Python.
|
|
2
|
+
|
|
3
|
+
This module provides the main SqliterDB class for interacting with
|
|
4
|
+
SQLite databases using Pydantic models.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from .sqliter import SqliterDB
|
|
4
8
|
|
sqliter/constants.py
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Constant values and mappings used throughout SQLiter.
|
|
2
2
|
|
|
3
|
+
This module defines constant dictionaries that map SQLiter-specific
|
|
4
|
+
concepts to their SQLite equivalents. It includes mappings for query
|
|
5
|
+
operators and data types, which are crucial for translating between
|
|
6
|
+
Pydantic models and SQLite database operations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import datetime
|
|
10
|
+
|
|
11
|
+
# A dictionary mapping SQLiter filter operators to their corresponding SQL
|
|
12
|
+
# operators.
|
|
3
13
|
OPERATOR_MAPPING = {
|
|
4
14
|
"__lt": "<",
|
|
5
15
|
"__lte": "<=",
|
|
@@ -18,3 +28,18 @@ OPERATOR_MAPPING = {
|
|
|
18
28
|
"__iendswith": "LIKE",
|
|
19
29
|
"__icontains": "LIKE",
|
|
20
30
|
}
|
|
31
|
+
|
|
32
|
+
# A dictionary mapping Python types to their corresponding SQLite column types.
|
|
33
|
+
SQLITE_TYPE_MAPPING = {
|
|
34
|
+
int: "INTEGER",
|
|
35
|
+
float: "REAL",
|
|
36
|
+
str: "TEXT",
|
|
37
|
+
bool: "INTEGER", # SQLite stores booleans as integers (0 or 1)
|
|
38
|
+
bytes: "BLOB",
|
|
39
|
+
datetime.datetime: "INTEGER", # Store as Unix timestamp
|
|
40
|
+
datetime.date: "INTEGER", # Store as Unix timestamp
|
|
41
|
+
list: "BLOB",
|
|
42
|
+
dict: "BLOB",
|
|
43
|
+
set: "BLOB",
|
|
44
|
+
tuple: "BLOB",
|
|
45
|
+
}
|
sqliter/exceptions.py
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Custom exception classes for SQLiter error handling.
|
|
2
|
+
|
|
3
|
+
This module defines a hierarchy of exception classes specific to
|
|
4
|
+
SQLiter operations. These exceptions provide detailed error information
|
|
5
|
+
for various scenarios such as connection issues, invalid queries,
|
|
6
|
+
and CRUD operation failures, enabling more precise error handling
|
|
7
|
+
in applications using SQLiter.
|
|
8
|
+
"""
|
|
2
9
|
|
|
3
10
|
import os
|
|
4
11
|
import sys
|
|
@@ -6,7 +13,15 @@ import traceback
|
|
|
6
13
|
|
|
7
14
|
|
|
8
15
|
class SqliterError(Exception):
|
|
9
|
-
"""Base class for all
|
|
16
|
+
"""Base exception class for all SQLiter-specific errors.
|
|
17
|
+
|
|
18
|
+
This class serves as the parent for all custom exceptions in SQLiter,
|
|
19
|
+
providing a consistent interface and message formatting.
|
|
20
|
+
|
|
21
|
+
Attributes:
|
|
22
|
+
message_template (str): A template string for the error message.
|
|
23
|
+
original_exception (Exception): The original exception that was caught.
|
|
24
|
+
"""
|
|
10
25
|
|
|
11
26
|
message_template: str = "An error occurred in the SQLiter package."
|
|
12
27
|
|
|
@@ -59,13 +74,13 @@ class SqliterError(Exception):
|
|
|
59
74
|
|
|
60
75
|
|
|
61
76
|
class DatabaseConnectionError(SqliterError):
|
|
62
|
-
"""
|
|
77
|
+
"""Exception raised when a database connection cannot be established."""
|
|
63
78
|
|
|
64
79
|
message_template = "Failed to connect to the database: '{}'"
|
|
65
80
|
|
|
66
81
|
|
|
67
82
|
class InvalidOffsetError(SqliterError):
|
|
68
|
-
"""
|
|
83
|
+
"""Exception raised when an invalid offset value is provided."""
|
|
69
84
|
|
|
70
85
|
message_template = (
|
|
71
86
|
"Invalid offset value: '{}'. Offset must be a positive integer."
|
|
@@ -73,48 +88,81 @@ class InvalidOffsetError(SqliterError):
|
|
|
73
88
|
|
|
74
89
|
|
|
75
90
|
class InvalidOrderError(SqliterError):
|
|
76
|
-
"""
|
|
91
|
+
"""Exception raised when an invalid order specification is provided."""
|
|
77
92
|
|
|
78
93
|
message_template = "Invalid order value - {}"
|
|
79
94
|
|
|
80
95
|
|
|
81
96
|
class TableCreationError(SqliterError):
|
|
82
|
-
"""
|
|
97
|
+
"""Exception raised when a table cannot be created in the database."""
|
|
83
98
|
|
|
84
99
|
message_template = "Failed to create the table: '{}'"
|
|
85
100
|
|
|
86
101
|
|
|
87
102
|
class RecordInsertionError(SqliterError):
|
|
88
|
-
"""
|
|
103
|
+
"""Exception raised when a record cannot be inserted into the database."""
|
|
89
104
|
|
|
90
105
|
message_template = "Failed to insert record into table: '{}'"
|
|
91
106
|
|
|
92
107
|
|
|
93
108
|
class RecordUpdateError(SqliterError):
|
|
94
|
-
"""
|
|
109
|
+
"""Exception raised when a record cannot be updated in the database."""
|
|
95
110
|
|
|
96
111
|
message_template = "Failed to update record in table: '{}'"
|
|
97
112
|
|
|
98
113
|
|
|
99
114
|
class RecordNotFoundError(SqliterError):
|
|
100
|
-
"""
|
|
115
|
+
"""Exception raised when a requested record is not found in the database."""
|
|
101
116
|
|
|
102
|
-
message_template = "Failed to find
|
|
117
|
+
message_template = "Failed to find that record in the table (key '{}') "
|
|
103
118
|
|
|
104
119
|
|
|
105
120
|
class RecordFetchError(SqliterError):
|
|
106
|
-
"""
|
|
121
|
+
"""Exception raised on an error fetching records from the database."""
|
|
107
122
|
|
|
108
123
|
message_template = "Failed to fetch record from table: '{}'"
|
|
109
124
|
|
|
110
125
|
|
|
111
126
|
class RecordDeletionError(SqliterError):
|
|
112
|
-
"""
|
|
127
|
+
"""Exception raised when a record cannot be deleted from the database."""
|
|
113
128
|
|
|
114
129
|
message_template = "Failed to delete record from table: '{}'"
|
|
115
130
|
|
|
116
131
|
|
|
117
132
|
class InvalidFilterError(SqliterError):
|
|
118
|
-
"""
|
|
133
|
+
"""Exception raised when an invalid filter is applied to a query."""
|
|
119
134
|
|
|
120
135
|
message_template = "Failed to apply filter: invalid field '{}'"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class TableDeletionError(SqliterError):
|
|
139
|
+
"""Raised when a table cannot be deleted from the database."""
|
|
140
|
+
|
|
141
|
+
message_template = "Failed to delete the table: '{}'"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class SqlExecutionError(SqliterError):
|
|
145
|
+
"""Raised when an SQL execution fails."""
|
|
146
|
+
|
|
147
|
+
message_template = "Failed to execute SQL: '{}'"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class InvalidIndexError(SqliterError):
|
|
151
|
+
"""Exception raised when an invalid index field is specified.
|
|
152
|
+
|
|
153
|
+
This error is triggered if one or more fields specified for an index
|
|
154
|
+
do not exist in the model's fields.
|
|
155
|
+
|
|
156
|
+
Attributes:
|
|
157
|
+
invalid_fields (list[str]): The list of fields that were invalid.
|
|
158
|
+
model_class (str): The name of the model where the error occurred.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
message_template = "Invalid fields for indexing in model '{}': {}"
|
|
162
|
+
|
|
163
|
+
def __init__(self, invalid_fields: list[str], model_class: str) -> None:
|
|
164
|
+
"""Tidy up the error message by joining the invalid fields."""
|
|
165
|
+
# Join invalid fields into a comma-separated string
|
|
166
|
+
invalid_fields_str = ", ".join(invalid_fields)
|
|
167
|
+
# Pass the formatted message to the parent class
|
|
168
|
+
super().__init__(model_class, invalid_fields_str)
|
sqliter/helpers.py
ADDED
|
@@ -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)
|
sqliter/model/__init__.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
"""This module
|
|
1
|
+
"""This module provides the base model class for SQLiter database models.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
It exports the BaseDBModel class, which is used to define database
|
|
4
|
+
models in SQLiter applications, and the Unique class, which is used to
|
|
5
|
+
define unique constraints on model fields.
|
|
4
6
|
"""
|
|
5
7
|
|
|
6
|
-
from .model import BaseDBModel
|
|
8
|
+
from .model import BaseDBModel, SerializableField
|
|
9
|
+
from .unique import Unique
|
|
7
10
|
|
|
8
|
-
__all__ = ["BaseDBModel"]
|
|
11
|
+
__all__ = ["BaseDBModel", "SerializableField", "Unique"]
|
sqliter/model/model.py
CHANGED
|
@@ -1,39 +1,102 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Defines the base model class for SQLiter ORM functionality.
|
|
2
|
+
|
|
3
|
+
This module provides the BaseDBModel class, which extends Pydantic's
|
|
4
|
+
BaseModel to add SQLiter-specific functionality. It includes methods
|
|
5
|
+
for table name inference, primary key management, and partial model
|
|
6
|
+
validation, forming the foundation for defining database-mapped models
|
|
7
|
+
in SQLiter applications.
|
|
8
|
+
"""
|
|
2
9
|
|
|
3
10
|
from __future__ import annotations
|
|
4
11
|
|
|
12
|
+
import datetime
|
|
13
|
+
import pickle
|
|
5
14
|
import re
|
|
6
|
-
from typing import
|
|
15
|
+
from typing import (
|
|
16
|
+
Any,
|
|
17
|
+
ClassVar,
|
|
18
|
+
Optional,
|
|
19
|
+
Protocol,
|
|
20
|
+
Union,
|
|
21
|
+
cast,
|
|
22
|
+
get_args,
|
|
23
|
+
get_origin,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
27
|
+
from typing_extensions import Self
|
|
7
28
|
|
|
8
|
-
from
|
|
29
|
+
from sqliter.helpers import from_unix_timestamp, to_unix_timestamp
|
|
9
30
|
|
|
10
|
-
|
|
31
|
+
|
|
32
|
+
class SerializableField(Protocol):
|
|
33
|
+
"""Protocol for fields that can be serialized or deserialized."""
|
|
11
34
|
|
|
12
35
|
|
|
13
36
|
class BaseDBModel(BaseModel):
|
|
14
|
-
"""
|
|
37
|
+
"""Base model class for SQLiter database models.
|
|
38
|
+
|
|
39
|
+
This class extends Pydantic's BaseModel to provide additional functionality
|
|
40
|
+
for database operations. It includes configuration options and methods
|
|
41
|
+
specific to SQLiter's ORM-like functionality.
|
|
42
|
+
|
|
43
|
+
This should not be used directly, but should be inherited by subclasses
|
|
44
|
+
representing database models.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
pk: int = Field(0, description="The mandatory primary key of the table.")
|
|
48
|
+
created_at: int = Field(
|
|
49
|
+
default=0,
|
|
50
|
+
description="Unix timestamp when the record was created.",
|
|
51
|
+
)
|
|
52
|
+
updated_at: int = Field(
|
|
53
|
+
default=0,
|
|
54
|
+
description="Unix timestamp when the record was last updated.",
|
|
55
|
+
)
|
|
15
56
|
|
|
16
57
|
model_config = ConfigDict(
|
|
17
58
|
extra="ignore",
|
|
18
59
|
populate_by_name=True,
|
|
19
|
-
validate_assignment=
|
|
60
|
+
validate_assignment=True,
|
|
20
61
|
from_attributes=True,
|
|
21
62
|
)
|
|
22
63
|
|
|
23
64
|
class Meta:
|
|
24
|
-
"""
|
|
65
|
+
"""Metadata class for configuring database-specific attributes.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
table_name (Optional[str]): The name of the database table. If not
|
|
69
|
+
specified, the table name will be inferred from the model class
|
|
70
|
+
name and converted to snake_case.
|
|
71
|
+
indexes (ClassVar[list[Union[str, tuple[str]]]]): A list of fields
|
|
72
|
+
or tuples of fields for which regular (non-unique) indexes
|
|
73
|
+
should be created. Indexes improve query performance on these
|
|
74
|
+
fields.
|
|
75
|
+
unique_indexes (ClassVar[list[Union[str, tuple[str]]]]): A list of
|
|
76
|
+
fields or tuples of fields for which unique indexes should be
|
|
77
|
+
created. Unique indexes enforce that all values in these fields
|
|
78
|
+
are distinct across the table.
|
|
79
|
+
"""
|
|
25
80
|
|
|
26
|
-
create_pk: bool = (
|
|
27
|
-
True # Whether to create an auto-increment primary key
|
|
28
|
-
)
|
|
29
|
-
primary_key: str = "id" # Default primary key name
|
|
30
81
|
table_name: Optional[str] = (
|
|
31
82
|
None # Table name, defaults to class name if not set
|
|
32
83
|
)
|
|
84
|
+
indexes: ClassVar[list[Union[str, tuple[str]]]] = []
|
|
85
|
+
unique_indexes: ClassVar[list[Union[str, tuple[str]]]] = []
|
|
33
86
|
|
|
34
87
|
@classmethod
|
|
35
|
-
def model_validate_partial(cls
|
|
36
|
-
"""Validate a partial
|
|
88
|
+
def model_validate_partial(cls, obj: dict[str, Any]) -> Self:
|
|
89
|
+
"""Validate and create a model instance from partial data.
|
|
90
|
+
|
|
91
|
+
This method allows for the creation of a model instance even when
|
|
92
|
+
not all fields are present in the input data.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
obj: A dictionary of field names and values.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
An instance of the model class with the provided data.
|
|
99
|
+
"""
|
|
37
100
|
converted_obj: dict[str, Any] = {}
|
|
38
101
|
for field_name, value in obj.items():
|
|
39
102
|
field = cls.model_fields[field_name]
|
|
@@ -58,16 +121,17 @@ class BaseDBModel(BaseModel):
|
|
|
58
121
|
else:
|
|
59
122
|
converted_obj[field_name] = field_type(value)
|
|
60
123
|
|
|
61
|
-
return cls.model_construct(**converted_obj)
|
|
124
|
+
return cast("Self", cls.model_construct(**converted_obj))
|
|
62
125
|
|
|
63
126
|
@classmethod
|
|
64
127
|
def get_table_name(cls) -> str:
|
|
65
|
-
"""Get the table name
|
|
128
|
+
"""Get the database table name for the model.
|
|
129
|
+
|
|
130
|
+
This method determines the table name based on the Meta configuration
|
|
131
|
+
or derives it from the class name if not explicitly set.
|
|
66
132
|
|
|
67
133
|
Returns:
|
|
68
|
-
|
|
69
|
-
generated by converting the class name to pluralized snake_case
|
|
70
|
-
and removing any 'Model' suffix.
|
|
134
|
+
The name of the database table for this model.
|
|
71
135
|
"""
|
|
72
136
|
table_name: str | None = getattr(cls.Meta, "table_name", None)
|
|
73
137
|
if table_name is not None:
|
|
@@ -81,7 +145,7 @@ class BaseDBModel(BaseModel):
|
|
|
81
145
|
|
|
82
146
|
# Pluralize the table name
|
|
83
147
|
try:
|
|
84
|
-
import inflect
|
|
148
|
+
import inflect # noqa: PLC0415
|
|
85
149
|
|
|
86
150
|
p = inflect.engine()
|
|
87
151
|
return p.plural(snake_case_name)
|
|
@@ -95,10 +159,78 @@ class BaseDBModel(BaseModel):
|
|
|
95
159
|
|
|
96
160
|
@classmethod
|
|
97
161
|
def get_primary_key(cls) -> str:
|
|
98
|
-
"""
|
|
99
|
-
return
|
|
162
|
+
"""Returns the mandatory primary key, always 'pk'."""
|
|
163
|
+
return "pk"
|
|
100
164
|
|
|
101
165
|
@classmethod
|
|
102
166
|
def should_create_pk(cls) -> bool:
|
|
103
|
-
"""
|
|
104
|
-
return
|
|
167
|
+
"""Returns True since the primary key is always created."""
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def serialize_field(cls, value: SerializableField) -> SerializableField:
|
|
172
|
+
"""Serialize datetime or date fields to Unix timestamp.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
field_name: The name of the field.
|
|
176
|
+
value: The value of the field.
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
An integer Unix timestamp if the field is a datetime or date.
|
|
180
|
+
"""
|
|
181
|
+
if isinstance(value, (datetime.datetime, datetime.date)):
|
|
182
|
+
return to_unix_timestamp(value)
|
|
183
|
+
if isinstance(value, (list, dict, set, tuple)):
|
|
184
|
+
return pickle.dumps(value)
|
|
185
|
+
return value # Return value as-is for other fields
|
|
186
|
+
|
|
187
|
+
# Deserialization after fetching from the database
|
|
188
|
+
|
|
189
|
+
@classmethod
|
|
190
|
+
def deserialize_field(
|
|
191
|
+
cls,
|
|
192
|
+
field_name: str,
|
|
193
|
+
value: SerializableField,
|
|
194
|
+
*,
|
|
195
|
+
return_local_time: bool,
|
|
196
|
+
) -> object:
|
|
197
|
+
"""Deserialize fields from Unix timestamp to datetime or date.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
field_name: The name of the field being deserialized.
|
|
201
|
+
value: The Unix timestamp value fetched from the database.
|
|
202
|
+
return_local_time: Flag to control whether the datetime is localized
|
|
203
|
+
to the user's timezone.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
A datetime or date object if the field type is datetime or date,
|
|
207
|
+
otherwise returns the value as-is.
|
|
208
|
+
"""
|
|
209
|
+
if value is None:
|
|
210
|
+
return None
|
|
211
|
+
|
|
212
|
+
# Get field type if it exists in model_fields
|
|
213
|
+
field_info = cls.model_fields.get(field_name)
|
|
214
|
+
if field_info is None:
|
|
215
|
+
# If field doesn't exist in model, return value as-is
|
|
216
|
+
return value
|
|
217
|
+
|
|
218
|
+
field_type = field_info.annotation
|
|
219
|
+
|
|
220
|
+
if (
|
|
221
|
+
isinstance(field_type, type)
|
|
222
|
+
and issubclass(field_type, (datetime.datetime, datetime.date))
|
|
223
|
+
and isinstance(value, int)
|
|
224
|
+
):
|
|
225
|
+
return from_unix_timestamp(
|
|
226
|
+
value, field_type, localize=return_local_time
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
origin_type = get_origin(field_type) or field_type
|
|
230
|
+
if origin_type in (list, dict, set, tuple) and isinstance(value, bytes):
|
|
231
|
+
try:
|
|
232
|
+
return pickle.loads(value)
|
|
233
|
+
except pickle.UnpicklingError:
|
|
234
|
+
return value
|
|
235
|
+
|
|
236
|
+
return value
|
sqliter/model/unique.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Define a custom field type for unique constraints in SQLiter."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic.fields import FieldInfo
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Unique(FieldInfo):
|
|
9
|
+
"""A custom field type for unique constraints in SQLiter."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, default: Any = ..., **kwargs: Any) -> None: # noqa: ANN401
|
|
12
|
+
"""Initialize a Unique field.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
default: The default value for the field.
|
|
16
|
+
**kwargs: Additional keyword arguments to pass to FieldInfo.
|
|
17
|
+
"""
|
|
18
|
+
super().__init__(default=default, **kwargs)
|
|
19
|
+
self.unique = True
|
sqliter/py.typed
ADDED
|
File without changes
|
sqliter/query/__init__.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""This module provides the query building functionality for SQLiter.
|
|
2
|
+
|
|
3
|
+
It exports the QueryBuilder class, which is used to construct and
|
|
4
|
+
execute database queries in SQLiter.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from .query import QueryBuilder
|
|
4
8
|
|