|
22 | 22 | from abc import ABC, abstractmethod
|
23 | 23 | from collections import abc
|
24 | 24 | from collections.abc import Sequence
|
25 |
| -from dataclasses import dataclass |
26 | 25 | from datetime import datetime
|
27 | 26 | from typing import TYPE_CHECKING, Any, Generic, Literal, Optional, Union
|
28 | 27 |
|
@@ -74,7 +73,7 @@ def append_to_statement(self, statement: "SQL") -> "SQL":
|
74 | 73 | Parameters should be provided via extract_parameters().
|
75 | 74 | """
|
76 | 75 |
|
77 |
| - def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]: |
| 76 | + def extract_parameters(self) -> "tuple[list[Any], dict[str, Any]]": |
78 | 77 | """Extract parameters that this filter contributes.
|
79 | 78 |
|
80 | 79 | Returns:
|
@@ -118,16 +117,30 @@ def get_cache_key(self) -> tuple[Any, ...]:
|
118 | 117 | """
|
119 | 118 |
|
120 | 119 |
|
121 |
| -@dataclass(frozen=True) |
122 | 120 | class BeforeAfterFilter(StatementFilter):
|
123 | 121 | """Filter for datetime range queries.
|
124 | 122 |
|
125 | 123 | Applies WHERE clauses for before/after datetime filtering.
|
126 | 124 | """
|
127 | 125 |
|
128 |
| - field_name: str |
129 |
| - before: Optional[datetime] = None |
130 |
| - after: Optional[datetime] = None |
| 126 | + __slots__ = ("_after", "_before", "_field_name") |
| 127 | + |
| 128 | + def __init__(self, field_name: str, before: Optional[datetime] = None, after: Optional[datetime] = None) -> None: |
| 129 | + self._field_name = field_name |
| 130 | + self._before = before |
| 131 | + self._after = after |
| 132 | + |
| 133 | + @property |
| 134 | + def field_name(self) -> str: |
| 135 | + return self._field_name |
| 136 | + |
| 137 | + @property |
| 138 | + def before(self) -> Optional[datetime]: |
| 139 | + return self._before |
| 140 | + |
| 141 | + @property |
| 142 | + def after(self) -> Optional[datetime]: |
| 143 | + return self._after |
131 | 144 |
|
132 | 145 | def get_param_names(self) -> list[str]:
|
133 | 146 | """Get parameter names without storing them."""
|
@@ -184,16 +197,32 @@ def get_cache_key(self) -> tuple[Any, ...]:
|
184 | 197 | return ("BeforeAfterFilter", self.field_name, self.before, self.after)
|
185 | 198 |
|
186 | 199 |
|
187 |
| -@dataclass(frozen=True) |
188 | 200 | class OnBeforeAfterFilter(StatementFilter):
|
189 | 201 | """Filter for inclusive datetime range queries.
|
190 | 202 |
|
191 | 203 | Applies WHERE clauses for on-or-before/on-or-after datetime filtering.
|
192 | 204 | """
|
193 | 205 |
|
194 |
| - field_name: str |
195 |
| - on_or_before: Optional[datetime] = None |
196 |
| - on_or_after: Optional[datetime] = None |
| 206 | + __slots__ = ("_field_name", "_on_or_after", "_on_or_before") |
| 207 | + |
| 208 | + def __init__( |
| 209 | + self, field_name: str, on_or_before: Optional[datetime] = None, on_or_after: Optional[datetime] = None |
| 210 | + ) -> None: |
| 211 | + self._field_name = field_name |
| 212 | + self._on_or_before = on_or_before |
| 213 | + self._on_or_after = on_or_after |
| 214 | + |
| 215 | + @property |
| 216 | + def field_name(self) -> str: |
| 217 | + return self._field_name |
| 218 | + |
| 219 | + @property |
| 220 | + def on_or_before(self) -> Optional[datetime]: |
| 221 | + return self._on_or_before |
| 222 | + |
| 223 | + @property |
| 224 | + def on_or_after(self) -> Optional[datetime]: |
| 225 | + return self._on_or_after |
197 | 226 |
|
198 | 227 | def get_param_names(self) -> list[str]:
|
199 | 228 | """Get parameter names without storing them."""
|
@@ -261,15 +290,25 @@ def append_to_statement(self, statement: "SQL") -> "SQL":
|
261 | 290 | raise NotImplementedError
|
262 | 291 |
|
263 | 292 |
|
264 |
| -@dataclass(frozen=True) |
265 | 293 | class InCollectionFilter(InAnyFilter[T]):
|
266 | 294 | """Filter for IN clause queries.
|
267 | 295 |
|
268 | 296 | Constructs WHERE ... IN (...) clauses.
|
269 | 297 | """
|
270 | 298 |
|
271 |
| - field_name: str |
272 |
| - values: Optional[abc.Collection[T]] = None |
| 299 | + __slots__ = ("_field_name", "_values") |
| 300 | + |
| 301 | + def __init__(self, field_name: str, values: Optional[abc.Collection[T]] = None) -> None: |
| 302 | + self._field_name = field_name |
| 303 | + self._values = values |
| 304 | + |
| 305 | + @property |
| 306 | + def field_name(self) -> str: |
| 307 | + return self._field_name |
| 308 | + |
| 309 | + @property |
| 310 | + def values(self) -> Optional[abc.Collection[T]]: |
| 311 | + return self._values |
273 | 312 |
|
274 | 313 | def get_param_names(self) -> list[str]:
|
275 | 314 | """Get parameter names without storing them."""
|
@@ -311,15 +350,25 @@ def get_cache_key(self) -> tuple[Any, ...]:
|
311 | 350 | return ("InCollectionFilter", self.field_name, values_tuple)
|
312 | 351 |
|
313 | 352 |
|
314 |
| -@dataclass(frozen=True) |
315 | 353 | class NotInCollectionFilter(InAnyFilter[T]):
|
316 | 354 | """Filter for NOT IN clause queries.
|
317 | 355 |
|
318 | 356 | Constructs WHERE ... NOT IN (...) clauses.
|
319 | 357 | """
|
320 | 358 |
|
321 |
| - field_name: str |
322 |
| - values: Optional[abc.Collection[T]] = None |
| 359 | + __slots__ = ("_field_name", "_values") |
| 360 | + |
| 361 | + def __init__(self, field_name: str, values: Optional[abc.Collection[T]] = None) -> None: |
| 362 | + self._field_name = field_name |
| 363 | + self._values = values |
| 364 | + |
| 365 | + @property |
| 366 | + def field_name(self) -> str: |
| 367 | + return self._field_name |
| 368 | + |
| 369 | + @property |
| 370 | + def values(self) -> Optional[abc.Collection[T]]: |
| 371 | + return self._values |
323 | 372 |
|
324 | 373 | def get_param_names(self) -> list[str]:
|
325 | 374 | """Get parameter names without storing them."""
|
@@ -361,15 +410,25 @@ def get_cache_key(self) -> tuple[Any, ...]:
|
361 | 410 | return ("NotInCollectionFilter", self.field_name, values_tuple)
|
362 | 411 |
|
363 | 412 |
|
364 |
| -@dataclass(frozen=True) |
365 | 413 | class AnyCollectionFilter(InAnyFilter[T]):
|
366 | 414 | """Filter for PostgreSQL-style ANY clause queries.
|
367 | 415 |
|
368 | 416 | Constructs WHERE column_name = ANY (array_expression) clauses.
|
369 | 417 | """
|
370 | 418 |
|
371 |
| - field_name: str |
372 |
| - values: Optional[abc.Collection[T]] = None |
| 419 | + __slots__ = ("_field_name", "_values") |
| 420 | + |
| 421 | + def __init__(self, field_name: str, values: Optional[abc.Collection[T]] = None) -> None: |
| 422 | + self._field_name = field_name |
| 423 | + self._values = values |
| 424 | + |
| 425 | + @property |
| 426 | + def field_name(self) -> str: |
| 427 | + return self._field_name |
| 428 | + |
| 429 | + @property |
| 430 | + def values(self) -> Optional[abc.Collection[T]]: |
| 431 | + return self._values |
373 | 432 |
|
374 | 433 | def get_param_names(self) -> list[str]:
|
375 | 434 | """Get parameter names without storing them."""
|
@@ -412,15 +471,25 @@ def get_cache_key(self) -> tuple[Any, ...]:
|
412 | 471 | return ("AnyCollectionFilter", self.field_name, values_tuple)
|
413 | 472 |
|
414 | 473 |
|
415 |
| -@dataclass(frozen=True) |
416 | 474 | class NotAnyCollectionFilter(InAnyFilter[T]):
|
417 | 475 | """Filter for PostgreSQL-style NOT ANY clause queries.
|
418 | 476 |
|
419 | 477 | Constructs WHERE NOT (column_name = ANY (array_expression)) clauses.
|
420 | 478 | """
|
421 | 479 |
|
422 |
| - field_name: str |
423 |
| - values: Optional[abc.Collection[T]] = None |
| 480 | + __slots__ = ("_field_name", "_values") |
| 481 | + |
| 482 | + def __init__(self, field_name: str, values: Optional[abc.Collection[T]] = None) -> None: |
| 483 | + self._field_name = field_name |
| 484 | + self._values = values |
| 485 | + |
| 486 | + @property |
| 487 | + def field_name(self) -> str: |
| 488 | + return self._field_name |
| 489 | + |
| 490 | + @property |
| 491 | + def values(self) -> Optional[abc.Collection[T]]: |
| 492 | + return self._values |
424 | 493 |
|
425 | 494 | def get_param_names(self) -> list[str]:
|
426 | 495 | """Get parameter names without storing them."""
|
@@ -471,15 +540,25 @@ def append_to_statement(self, statement: "SQL") -> "SQL":
|
471 | 540 | raise NotImplementedError
|
472 | 541 |
|
473 | 542 |
|
474 |
| -@dataclass(frozen=True) |
475 | 543 | class LimitOffsetFilter(PaginationFilter):
|
476 | 544 | """Filter for LIMIT and OFFSET clauses.
|
477 | 545 |
|
478 | 546 | Adds pagination support through LIMIT/OFFSET SQL clauses.
|
479 | 547 | """
|
480 | 548 |
|
481 |
| - limit: int |
482 |
| - offset: int |
| 549 | + __slots__ = ("_limit", "_offset") |
| 550 | + |
| 551 | + def __init__(self, limit: int, offset: int) -> None: |
| 552 | + self._limit = limit |
| 553 | + self._offset = offset |
| 554 | + |
| 555 | + @property |
| 556 | + def limit(self) -> int: |
| 557 | + return self._limit |
| 558 | + |
| 559 | + @property |
| 560 | + def offset(self) -> int: |
| 561 | + return self._offset |
483 | 562 |
|
484 | 563 | def get_param_names(self) -> list[str]:
|
485 | 564 | """Get parameter names without storing them."""
|
@@ -517,15 +596,25 @@ def get_cache_key(self) -> tuple[Any, ...]:
|
517 | 596 | return ("LimitOffsetFilter", self.limit, self.offset)
|
518 | 597 |
|
519 | 598 |
|
520 |
| -@dataclass(frozen=True) |
521 | 599 | class OrderByFilter(StatementFilter):
|
522 | 600 | """Filter for ORDER BY clauses.
|
523 | 601 |
|
524 | 602 | Adds sorting capability to SQL queries.
|
525 | 603 | """
|
526 | 604 |
|
527 |
| - field_name: str |
528 |
| - sort_order: Literal["asc", "desc"] = "asc" |
| 605 | + __slots__ = ("_field_name", "_sort_order") |
| 606 | + |
| 607 | + def __init__(self, field_name: str, sort_order: Literal["asc", "desc"] = "asc") -> None: |
| 608 | + self._field_name = field_name |
| 609 | + self._sort_order = sort_order |
| 610 | + |
| 611 | + @property |
| 612 | + def field_name(self) -> str: |
| 613 | + return self._field_name |
| 614 | + |
| 615 | + @property |
| 616 | + def sort_order(self) -> Literal["asc", "desc"]: |
| 617 | + return self._sort_order # pyright: ignore |
529 | 618 |
|
530 | 619 | def extract_parameters(self) -> tuple[list[Any], dict[str, Any]]:
|
531 | 620 | """Extract filter parameters."""
|
@@ -553,16 +642,30 @@ def get_cache_key(self) -> tuple[Any, ...]:
|
553 | 642 | return ("OrderByFilter", self.field_name, self.sort_order)
|
554 | 643 |
|
555 | 644 |
|
556 |
| -@dataclass(frozen=True) |
557 | 645 | class SearchFilter(StatementFilter):
|
558 | 646 | """Filter for text search queries.
|
559 | 647 |
|
560 | 648 | Constructs WHERE field_name LIKE '%value%' clauses.
|
561 | 649 | """
|
562 | 650 |
|
563 |
| - field_name: Union[str, set[str]] |
564 |
| - value: str |
565 |
| - ignore_case: Optional[bool] = False |
| 651 | + __slots__ = ("_field_name", "_ignore_case", "_value") |
| 652 | + |
| 653 | + def __init__(self, field_name: Union[str, set[str]], value: str, ignore_case: Optional[bool] = False) -> None: |
| 654 | + self._field_name = field_name |
| 655 | + self._value = value |
| 656 | + self._ignore_case = ignore_case |
| 657 | + |
| 658 | + @property |
| 659 | + def field_name(self) -> Union[str, set[str]]: |
| 660 | + return self._field_name |
| 661 | + |
| 662 | + @property |
| 663 | + def value(self) -> str: |
| 664 | + return self._value |
| 665 | + |
| 666 | + @property |
| 667 | + def ignore_case(self) -> Optional[bool]: |
| 668 | + return self._ignore_case |
566 | 669 |
|
567 | 670 | def get_param_name(self) -> Optional[str]:
|
568 | 671 | """Get parameter name without storing it."""
|
@@ -617,7 +720,6 @@ def get_cache_key(self) -> tuple[Any, ...]:
|
617 | 720 | return ("SearchFilter", field_names, self.value, self.ignore_case)
|
618 | 721 |
|
619 | 722 |
|
620 |
| -@dataclass(frozen=True) |
621 | 723 | class NotInSearchFilter(SearchFilter):
|
622 | 724 | """Filter for negated text search queries.
|
623 | 725 |
|
@@ -732,7 +834,7 @@ def apply_filter(statement: "SQL", filter_obj: StatementFilter) -> "SQL":
|
732 | 834 | def create_filters(filters: "list[StatementFilter]") -> tuple["StatementFilter", ...]:
|
733 | 835 | """Convert mutable filters to immutable tuple.
|
734 | 836 |
|
735 |
| - Since StatementFilter classes are now immutable (frozen dataclasses), |
| 837 | + Since StatementFilter classes are now immutable (with read-only properties), |
736 | 838 | we just need to convert to a tuple for consistent sharing.
|
737 | 839 |
|
738 | 840 | Args:
|
|
0 commit comments