sqliter-py 0.3.0__py3-none-any.whl → 0.5.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 +18 -1
- sqliter/exceptions.py +40 -13
- sqliter/helpers.py +35 -0
- sqliter/model/__init__.py +3 -2
- sqliter/model/model.py +49 -19
- sqliter/query/__init__.py +5 -1
- sqliter/query/query.py +297 -46
- sqliter/sqliter.py +322 -43
- sqliter_py-0.5.0.dist-info/METADATA +199 -0
- sqliter_py-0.5.0.dist-info/RECORD +13 -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 → sqliter_py-0.5.0.dist-info}/WHEEL +0 -0
- {sqliter_py-0.3.0.dist-info → sqliter_py-0.5.0.dist-info}/licenses/LICENSE.txt +0 -0
sqliter/query/query.py
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Implements the query building and execution logic for SQLiter.
|
|
2
|
+
|
|
3
|
+
This module defines the QueryBuilder class, which provides a fluent
|
|
4
|
+
interface for constructing SQL queries. It supports operations such
|
|
5
|
+
as filtering, ordering, limiting, and various data retrieval methods,
|
|
6
|
+
allowing for flexible and expressive database queries without writing
|
|
7
|
+
raw SQL.
|
|
8
|
+
"""
|
|
2
9
|
|
|
3
10
|
from __future__ import annotations
|
|
4
11
|
|
|
@@ -37,7 +44,21 @@ FilterValue = Union[
|
|
|
37
44
|
|
|
38
45
|
|
|
39
46
|
class QueryBuilder:
|
|
40
|
-
"""
|
|
47
|
+
"""Builds and executes database queries for a specific model.
|
|
48
|
+
|
|
49
|
+
This class provides methods to construct SQL queries, apply filters,
|
|
50
|
+
set ordering, and execute the queries against the database.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
db (SqliterDB): The database connection object.
|
|
54
|
+
model_class (type[BaseDBModel]): The Pydantic model class.
|
|
55
|
+
table_name (str): The name of the database table.
|
|
56
|
+
filters (list): List of applied filter conditions.
|
|
57
|
+
_limit (Optional[int]): The LIMIT clause value, if any.
|
|
58
|
+
_offset (Optional[int]): The OFFSET clause value, if any.
|
|
59
|
+
_order_by (Optional[str]): The ORDER BY clause, if any.
|
|
60
|
+
_fields (Optional[list[str]]): List of fields to select, if specified.
|
|
61
|
+
"""
|
|
41
62
|
|
|
42
63
|
def __init__(
|
|
43
64
|
self,
|
|
@@ -45,13 +66,11 @@ class QueryBuilder:
|
|
|
45
66
|
model_class: type[BaseDBModel],
|
|
46
67
|
fields: Optional[list[str]] = None,
|
|
47
68
|
) -> None:
|
|
48
|
-
"""Initialize
|
|
49
|
-
|
|
50
|
-
Pass the database, model class, and optional fields.
|
|
69
|
+
"""Initialize a new QueryBuilder instance.
|
|
51
70
|
|
|
52
71
|
Args:
|
|
53
|
-
db: The
|
|
54
|
-
model_class: The model class
|
|
72
|
+
db: The database connection object.
|
|
73
|
+
model_class: The Pydantic model class for the table.
|
|
55
74
|
fields: Optional list of field names to select. If None, all fields
|
|
56
75
|
are selected.
|
|
57
76
|
"""
|
|
@@ -68,7 +87,11 @@ class QueryBuilder:
|
|
|
68
87
|
self._validate_fields()
|
|
69
88
|
|
|
70
89
|
def _validate_fields(self) -> None:
|
|
71
|
-
"""Validate that the specified fields exist in the model.
|
|
90
|
+
"""Validate that the specified fields exist in the model.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
ValueError: If any specified field is not in the model.
|
|
94
|
+
"""
|
|
72
95
|
if self._fields is None:
|
|
73
96
|
return
|
|
74
97
|
valid_fields = set(self.model_class.model_fields.keys())
|
|
@@ -80,28 +103,75 @@ class QueryBuilder:
|
|
|
80
103
|
raise ValueError(err_message)
|
|
81
104
|
|
|
82
105
|
def filter(self, **conditions: str | float | None) -> QueryBuilder:
|
|
83
|
-
"""
|
|
106
|
+
"""Apply filter conditions to the query.
|
|
107
|
+
|
|
108
|
+
This method allows adding one or more filter conditions to the query.
|
|
109
|
+
Each condition is specified as a keyword argument, where the key is
|
|
110
|
+
the field name and the value is the condition to apply.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
**conditions: Arbitrary keyword arguments representing filter
|
|
114
|
+
conditions. The key is the field name, and the value is the
|
|
115
|
+
condition to apply. Supported operators include equality,
|
|
116
|
+
comparison, and special operators like __in, __isnull, etc.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
QueryBuilder: The current QueryBuilder instance for method
|
|
120
|
+
chaining.
|
|
121
|
+
|
|
122
|
+
Examples:
|
|
123
|
+
>>> query.filter(name="John", age__gt=30)
|
|
124
|
+
>>> query.filter(status__in=["active", "pending"])
|
|
125
|
+
"""
|
|
84
126
|
valid_fields = self.model_class.model_fields
|
|
85
127
|
|
|
86
128
|
for field, value in conditions.items():
|
|
87
129
|
field_name, operator = self._parse_field_operator(field)
|
|
88
130
|
self._validate_field(field_name, valid_fields)
|
|
89
131
|
|
|
90
|
-
|
|
91
|
-
|
|
132
|
+
if operator in ["__isnull", "__notnull"]:
|
|
133
|
+
self._handle_null(field_name, value, operator)
|
|
134
|
+
else:
|
|
135
|
+
handler = self._get_operator_handler(operator)
|
|
136
|
+
handler(field_name, value, operator)
|
|
92
137
|
|
|
93
138
|
return self
|
|
94
139
|
|
|
95
140
|
def fields(self, fields: Optional[list[str]] = None) -> QueryBuilder:
|
|
96
|
-
"""
|
|
141
|
+
"""Specify which fields to select in the query.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
fields: List of field names to select. If None, all fields are
|
|
145
|
+
selected.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The QueryBuilder instance for method chaining.
|
|
149
|
+
"""
|
|
97
150
|
if fields:
|
|
151
|
+
if "pk" not in fields:
|
|
152
|
+
fields.append("pk")
|
|
98
153
|
self._fields = fields
|
|
99
154
|
self._validate_fields()
|
|
100
155
|
return self
|
|
101
156
|
|
|
102
157
|
def exclude(self, fields: Optional[list[str]] = None) -> QueryBuilder:
|
|
103
|
-
"""
|
|
158
|
+
"""Specify which fields to exclude from the query results.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
fields: List of field names to exclude. If None, no fields are
|
|
162
|
+
excluded.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
The QueryBuilder instance for method chaining.
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
ValueError: If exclusion results in no fields being selected or if
|
|
169
|
+
invalid fields are specified.
|
|
170
|
+
"""
|
|
104
171
|
if fields:
|
|
172
|
+
if "pk" in fields:
|
|
173
|
+
err = "The primary key 'pk' cannot be excluded."
|
|
174
|
+
raise ValueError(err)
|
|
105
175
|
all_fields = set(self.model_class.model_fields.keys())
|
|
106
176
|
|
|
107
177
|
# Check for invalid fields before subtraction
|
|
@@ -117,7 +187,7 @@ class QueryBuilder:
|
|
|
117
187
|
self._fields = list(all_fields - set(fields))
|
|
118
188
|
|
|
119
189
|
# Explicit check: raise an error if no fields remain
|
|
120
|
-
if
|
|
190
|
+
if self._fields == ["pk"]:
|
|
121
191
|
err = "Exclusion results in no fields being selected."
|
|
122
192
|
raise ValueError(err)
|
|
123
193
|
|
|
@@ -127,7 +197,17 @@ class QueryBuilder:
|
|
|
127
197
|
return self
|
|
128
198
|
|
|
129
199
|
def only(self, field: str) -> QueryBuilder:
|
|
130
|
-
"""
|
|
200
|
+
"""Specify a single field to select in the query.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
field: The name of the field to select.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
The QueryBuilder instance for method chaining.
|
|
207
|
+
|
|
208
|
+
Raises:
|
|
209
|
+
ValueError: If the specified field is invalid.
|
|
210
|
+
"""
|
|
131
211
|
all_fields = set(self.model_class.model_fields.keys())
|
|
132
212
|
|
|
133
213
|
# Validate that the field exists
|
|
@@ -136,12 +216,20 @@ class QueryBuilder:
|
|
|
136
216
|
raise ValueError(err)
|
|
137
217
|
|
|
138
218
|
# Set self._fields to just the single field
|
|
139
|
-
self._fields = [field]
|
|
219
|
+
self._fields = [field, "pk"]
|
|
140
220
|
return self
|
|
141
221
|
|
|
142
222
|
def _get_operator_handler(
|
|
143
223
|
self, operator: str
|
|
144
224
|
) -> Callable[[str, Any, str], None]:
|
|
225
|
+
"""Get the appropriate handler function for the given operator.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
operator: The filter operator string.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
A callable that handles the specific operator type.
|
|
232
|
+
"""
|
|
145
233
|
handlers = {
|
|
146
234
|
"__isnull": self._handle_null,
|
|
147
235
|
"__notnull": self._handle_null,
|
|
@@ -164,30 +252,70 @@ class QueryBuilder:
|
|
|
164
252
|
def _validate_field(
|
|
165
253
|
self, field_name: str, valid_fields: dict[str, FieldInfo]
|
|
166
254
|
) -> None:
|
|
255
|
+
"""Validate that a field exists in the model.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
field_name: The name of the field to validate.
|
|
259
|
+
valid_fields: Dictionary of valid fields from the model.
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
InvalidFilterError: If the field is not in the model.
|
|
263
|
+
"""
|
|
167
264
|
if field_name not in valid_fields:
|
|
168
265
|
raise InvalidFilterError(field_name)
|
|
169
266
|
|
|
170
267
|
def _handle_equality(
|
|
171
268
|
self, field_name: str, value: FilterValue, operator: str
|
|
172
269
|
) -> None:
|
|
270
|
+
"""Handle equality filter conditions.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
field_name: The name of the field to filter on.
|
|
274
|
+
value: The value to compare against.
|
|
275
|
+
operator: The operator string (usually '__eq').
|
|
276
|
+
|
|
277
|
+
This method adds an equality condition to the filters list, handling
|
|
278
|
+
NULL values separately.
|
|
279
|
+
"""
|
|
173
280
|
if value is None:
|
|
174
281
|
self.filters.append((f"{field_name} IS NULL", None, "__isnull"))
|
|
175
282
|
else:
|
|
176
283
|
self.filters.append((field_name, value, operator))
|
|
177
284
|
|
|
178
285
|
def _handle_null(
|
|
179
|
-
self, field_name: str,
|
|
286
|
+
self, field_name: str, value: Union[str, float, None], operator: str
|
|
180
287
|
) -> None:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
288
|
+
"""Handle IS NULL and IS NOT NULL filter conditions.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
field_name: The name of the field to filter on. _: Placeholder for
|
|
292
|
+
unused value parameter.
|
|
293
|
+
operator: The operator string ('__isnull' or '__notnull').
|
|
294
|
+
value: The value to check for.
|
|
295
|
+
|
|
296
|
+
This method adds an IS NULL or IS NOT NULL condition to the filters
|
|
297
|
+
list.
|
|
298
|
+
"""
|
|
299
|
+
is_null = operator == "__isnull"
|
|
300
|
+
check_null = bool(value) if is_null else not bool(value)
|
|
301
|
+
condition = f"{field_name} IS {'NOT ' if not check_null else ''}NULL"
|
|
186
302
|
self.filters.append((condition, None, operator))
|
|
187
303
|
|
|
188
304
|
def _handle_in(
|
|
189
305
|
self, field_name: str, value: FilterValue, operator: str
|
|
190
306
|
) -> None:
|
|
307
|
+
"""Handle IN and NOT IN filter conditions.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
field_name: The name of the field to filter on.
|
|
311
|
+
value: A list of values to check against.
|
|
312
|
+
operator: The operator string ('__in' or '__not_in').
|
|
313
|
+
|
|
314
|
+
Raises:
|
|
315
|
+
TypeError: If the value is not a list.
|
|
316
|
+
|
|
317
|
+
This method adds an IN or NOT IN condition to the filters list.
|
|
318
|
+
"""
|
|
191
319
|
if not isinstance(value, list):
|
|
192
320
|
err = f"{field_name} requires a list for '{operator}'"
|
|
193
321
|
raise TypeError(err)
|
|
@@ -204,6 +332,19 @@ class QueryBuilder:
|
|
|
204
332
|
def _handle_like(
|
|
205
333
|
self, field_name: str, value: FilterValue, operator: str
|
|
206
334
|
) -> None:
|
|
335
|
+
"""Handle LIKE and GLOB filter conditions.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
field_name: The name of the field to filter on.
|
|
339
|
+
value: The pattern to match against.
|
|
340
|
+
operator: The operator string (e.g., '__startswith', '__contains').
|
|
341
|
+
|
|
342
|
+
Raises:
|
|
343
|
+
TypeError: If the value is not a string.
|
|
344
|
+
|
|
345
|
+
This method adds a LIKE or GLOB condition to the filters list, depending
|
|
346
|
+
on whether the operation is case-sensitive or not.
|
|
347
|
+
"""
|
|
207
348
|
if not isinstance(value, str):
|
|
208
349
|
err = f"{field_name} requires a string value for '{operator}'"
|
|
209
350
|
raise TypeError(err)
|
|
@@ -228,11 +369,29 @@ class QueryBuilder:
|
|
|
228
369
|
def _handle_comparison(
|
|
229
370
|
self, field_name: str, value: FilterValue, operator: str
|
|
230
371
|
) -> None:
|
|
372
|
+
"""Handle comparison filter conditions.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
field_name: The name of the field to filter on.
|
|
376
|
+
value: The value to compare against.
|
|
377
|
+
operator: The comparison operator string (e.g., '__lt', '__gte').
|
|
378
|
+
|
|
379
|
+
This method adds a comparison condition to the filters list.
|
|
380
|
+
"""
|
|
231
381
|
sql_operator = OPERATOR_MAPPING[operator]
|
|
232
382
|
self.filters.append((f"{field_name} {sql_operator} ?", value, operator))
|
|
233
383
|
|
|
234
384
|
# Helper method for parsing field and operator
|
|
235
385
|
def _parse_field_operator(self, field: str) -> tuple[str, str]:
|
|
386
|
+
"""Parse a field string to separate the field name and operator.
|
|
387
|
+
|
|
388
|
+
Args:
|
|
389
|
+
field: The field string, potentially including an operator.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
A tuple containing the field name and the operator (or '__eq' if
|
|
393
|
+
no operator was specified).
|
|
394
|
+
"""
|
|
236
395
|
for operator in OPERATOR_MAPPING:
|
|
237
396
|
if field.endswith(operator):
|
|
238
397
|
return field[: -len(operator)], operator
|
|
@@ -240,7 +399,15 @@ class QueryBuilder:
|
|
|
240
399
|
|
|
241
400
|
# Helper method for formatting string operators (like startswith)
|
|
242
401
|
def _format_string_for_operator(self, operator: str, value: str) -> str:
|
|
243
|
-
|
|
402
|
+
"""Format a string value based on the specified operator.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
operator: The operator string (e.g., '__startswith', '__contains').
|
|
406
|
+
value: The original string value.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
The formatted string value suitable for the given operator.
|
|
410
|
+
"""
|
|
244
411
|
format_map = {
|
|
245
412
|
"__startswith": f"{value}*",
|
|
246
413
|
"__endswith": f"*{value}",
|
|
@@ -254,12 +421,29 @@ class QueryBuilder:
|
|
|
254
421
|
return format_map.get(operator, value)
|
|
255
422
|
|
|
256
423
|
def limit(self, limit_value: int) -> Self:
|
|
257
|
-
"""Limit the number of results returned by the query.
|
|
424
|
+
"""Limit the number of results returned by the query.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
limit_value: The maximum number of records to return.
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
The QueryBuilder instance for method chaining.
|
|
431
|
+
"""
|
|
258
432
|
self._limit = limit_value
|
|
259
433
|
return self
|
|
260
434
|
|
|
261
435
|
def offset(self, offset_value: int) -> Self:
|
|
262
|
-
"""Set an offset value for the query.
|
|
436
|
+
"""Set an offset value for the query.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
offset_value: The number of records to skip.
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
The QueryBuilder instance for method chaining.
|
|
443
|
+
|
|
444
|
+
Raises:
|
|
445
|
+
InvalidOffsetError: If the offset value is negative.
|
|
446
|
+
"""
|
|
263
447
|
if offset_value < 0:
|
|
264
448
|
raise InvalidOffsetError(offset_value)
|
|
265
449
|
self._offset = offset_value
|
|
@@ -270,7 +454,7 @@ class QueryBuilder:
|
|
|
270
454
|
|
|
271
455
|
def order(
|
|
272
456
|
self,
|
|
273
|
-
order_by_field: str,
|
|
457
|
+
order_by_field: Optional[str] = None,
|
|
274
458
|
direction: Optional[str] = None,
|
|
275
459
|
*,
|
|
276
460
|
reverse: bool = False,
|
|
@@ -278,19 +462,19 @@ class QueryBuilder:
|
|
|
278
462
|
"""Order the query results by the specified field.
|
|
279
463
|
|
|
280
464
|
Args:
|
|
281
|
-
order_by_field
|
|
282
|
-
direction
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
465
|
+
order_by_field: The field to order by [optional].
|
|
466
|
+
direction: Deprecated. Use 'reverse' instead.
|
|
467
|
+
reverse: If True, sort in descending order.
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
The QueryBuilder instance for method chaining.
|
|
286
471
|
|
|
287
472
|
Raises:
|
|
288
|
-
InvalidOrderError: If the field doesn't exist
|
|
289
|
-
|
|
473
|
+
InvalidOrderError: If the field doesn't exist or if both 'direction'
|
|
474
|
+
and 'reverse' are specified.
|
|
290
475
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
ordering.
|
|
476
|
+
Warns:
|
|
477
|
+
DeprecationWarning: If 'direction' is used instead of 'reverse'.
|
|
294
478
|
"""
|
|
295
479
|
if direction:
|
|
296
480
|
warnings.warn(
|
|
@@ -300,6 +484,9 @@ class QueryBuilder:
|
|
|
300
484
|
stacklevel=2,
|
|
301
485
|
)
|
|
302
486
|
|
|
487
|
+
if order_by_field is None:
|
|
488
|
+
order_by_field = self.model_class.get_primary_key()
|
|
489
|
+
|
|
303
490
|
if order_by_field not in self.model_class.model_fields:
|
|
304
491
|
err = f"'{order_by_field}' does not exist in the model fields."
|
|
305
492
|
raise InvalidOrderError(err)
|
|
@@ -331,10 +518,24 @@ class QueryBuilder:
|
|
|
331
518
|
fetch_one: bool = False,
|
|
332
519
|
count_only: bool = False,
|
|
333
520
|
) -> list[tuple[Any, ...]] | Optional[tuple[Any, ...]]:
|
|
334
|
-
"""
|
|
521
|
+
"""Execute the constructed SQL query.
|
|
522
|
+
|
|
523
|
+
Args:
|
|
524
|
+
fetch_one: If True, fetch only one result.
|
|
525
|
+
count_only: If True, return only the count of results.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
A list of tuples (all results), a single tuple (one result),
|
|
529
|
+
or None if no results are found.
|
|
530
|
+
|
|
531
|
+
Raises:
|
|
532
|
+
RecordFetchError: If there's an error executing the query.
|
|
533
|
+
"""
|
|
335
534
|
if count_only:
|
|
336
535
|
fields = "COUNT(*)"
|
|
337
536
|
elif self._fields:
|
|
537
|
+
if "pk" not in self._fields:
|
|
538
|
+
self._fields.append("pk")
|
|
338
539
|
fields = ", ".join(f'"{field}"' for field in self._fields)
|
|
339
540
|
else:
|
|
340
541
|
fields = ", ".join(
|
|
@@ -360,6 +561,11 @@ class QueryBuilder:
|
|
|
360
561
|
sql += " OFFSET ?"
|
|
361
562
|
values.append(self._offset)
|
|
362
563
|
|
|
564
|
+
# Print the raw SQL and values if debug is enabled
|
|
565
|
+
# Log the SQL if debug is enabled
|
|
566
|
+
if self.db.debug:
|
|
567
|
+
self.db._log_sql(sql, values) # noqa: SLF001
|
|
568
|
+
|
|
363
569
|
try:
|
|
364
570
|
with self.db.connect() as conn:
|
|
365
571
|
cursor = conn.cursor()
|
|
@@ -369,7 +575,13 @@ class QueryBuilder:
|
|
|
369
575
|
raise RecordFetchError(self.table_name) from exc
|
|
370
576
|
|
|
371
577
|
def _parse_filter(self) -> tuple[list[Any], LiteralString]:
|
|
372
|
-
"""
|
|
578
|
+
"""Parse the filter conditions into SQL clauses and values.
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
A tuple containing:
|
|
582
|
+
- A list of values to be used in the SQL query.
|
|
583
|
+
- A string representing the WHERE clause of the SQL query.
|
|
584
|
+
"""
|
|
373
585
|
where_clauses = []
|
|
374
586
|
values = []
|
|
375
587
|
for field, value, operator in self.filters:
|
|
@@ -388,7 +600,14 @@ class QueryBuilder:
|
|
|
388
600
|
return values, where_clause
|
|
389
601
|
|
|
390
602
|
def _convert_row_to_model(self, row: tuple[Any, ...]) -> BaseDBModel:
|
|
391
|
-
"""Convert a
|
|
603
|
+
"""Convert a database row to a model instance.
|
|
604
|
+
|
|
605
|
+
Args:
|
|
606
|
+
row: A tuple representing a database row.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
An instance of the model class populated with the row data.
|
|
610
|
+
"""
|
|
392
611
|
if self._fields:
|
|
393
612
|
return self.model_class.model_validate_partial(
|
|
394
613
|
{field: row[idx] for idx, field in enumerate(self._fields)}
|
|
@@ -413,7 +632,15 @@ class QueryBuilder:
|
|
|
413
632
|
def _fetch_result(
|
|
414
633
|
self, *, fetch_one: bool = False
|
|
415
634
|
) -> Union[list[BaseDBModel], Optional[BaseDBModel]]:
|
|
416
|
-
"""Fetch
|
|
635
|
+
"""Fetch and convert query results to model instances.
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
fetch_one: If True, fetch only one result.
|
|
639
|
+
|
|
640
|
+
Returns:
|
|
641
|
+
A list of model instances, a single model instance, or None if no
|
|
642
|
+
results are found.
|
|
643
|
+
"""
|
|
417
644
|
result = self._execute_query(fetch_one=fetch_one)
|
|
418
645
|
|
|
419
646
|
if not result:
|
|
@@ -432,30 +659,54 @@ class QueryBuilder:
|
|
|
432
659
|
return [self._convert_row_to_model(row) for row in result]
|
|
433
660
|
|
|
434
661
|
def fetch_all(self) -> list[BaseDBModel]:
|
|
435
|
-
"""Fetch all results
|
|
662
|
+
"""Fetch all results of the query.
|
|
663
|
+
|
|
664
|
+
Returns:
|
|
665
|
+
A list of model instances representing all query results.
|
|
666
|
+
"""
|
|
436
667
|
return self._fetch_result(fetch_one=False)
|
|
437
668
|
|
|
438
669
|
def fetch_one(self) -> Optional[BaseDBModel]:
|
|
439
|
-
"""Fetch
|
|
670
|
+
"""Fetch a single result of the query.
|
|
671
|
+
|
|
672
|
+
Returns:
|
|
673
|
+
A single model instance or None if no result is found.
|
|
674
|
+
"""
|
|
440
675
|
return self._fetch_result(fetch_one=True)
|
|
441
676
|
|
|
442
677
|
def fetch_first(self) -> Optional[BaseDBModel]:
|
|
443
|
-
"""Fetch the first result of the query.
|
|
678
|
+
"""Fetch the first result of the query.
|
|
679
|
+
|
|
680
|
+
Returns:
|
|
681
|
+
The first model instance or None if no result is found.
|
|
682
|
+
"""
|
|
444
683
|
self._limit = 1
|
|
445
684
|
return self._fetch_result(fetch_one=True)
|
|
446
685
|
|
|
447
686
|
def fetch_last(self) -> Optional[BaseDBModel]:
|
|
448
|
-
"""Fetch the last result of the query
|
|
687
|
+
"""Fetch the last result of the query.
|
|
688
|
+
|
|
689
|
+
Returns:
|
|
690
|
+
The last model instance or None if no result is found.
|
|
691
|
+
"""
|
|
449
692
|
self._limit = 1
|
|
450
693
|
self._order_by = "rowid DESC"
|
|
451
694
|
return self._fetch_result(fetch_one=True)
|
|
452
695
|
|
|
453
696
|
def count(self) -> int:
|
|
454
|
-
"""
|
|
697
|
+
"""Count the number of results for the current query.
|
|
698
|
+
|
|
699
|
+
Returns:
|
|
700
|
+
The number of results that match the current query conditions.
|
|
701
|
+
"""
|
|
455
702
|
result = self._execute_query(count_only=True)
|
|
456
703
|
|
|
457
704
|
return int(result[0][0]) if result else 0
|
|
458
705
|
|
|
459
706
|
def exists(self) -> bool:
|
|
460
|
-
"""
|
|
707
|
+
"""Check if any results exist for the current query.
|
|
708
|
+
|
|
709
|
+
Returns:
|
|
710
|
+
True if at least one result exists, False otherwise.
|
|
711
|
+
"""
|
|
461
712
|
return self.count() > 0
|