274 lines
8.5 KiB
Python
274 lines
8.5 KiB
Python
from enum import Enum
|
|
from typing import Any
|
|
|
|
|
|
class Operation(str, Enum):
|
|
"""Comparison operations for filtering items."""
|
|
|
|
EQUAL = "=="
|
|
"""Exact equality comparison."""
|
|
NOT_EQUAL = "!="
|
|
"""Not equal comparison."""
|
|
CONTAIN = "in"
|
|
"""Check if value is contained in the field (substring match)."""
|
|
NOT_CONTAIN = "not_in"
|
|
"""Check if value is not contained in the field."""
|
|
GREATER_THAN = ">"
|
|
"""Greater than comparison."""
|
|
LESS_THAN = "<"
|
|
"""Less than comparison."""
|
|
GREATER_EQUAL = ">="
|
|
"""Greater than or equal comparison."""
|
|
LESS_EQUAL = "<="
|
|
"""Less than or equal comparison."""
|
|
STARTS_WITH = "startswith"
|
|
"""Check if field starts with value."""
|
|
ENDS_WITH = "endswith"
|
|
"""Check if field ends with value."""
|
|
|
|
def __str__(self) -> str:
|
|
return self.value
|
|
|
|
|
|
def matches(operation: Operation | str, haystack: Any, needle: Any) -> bool:
|
|
"""
|
|
Generic comparison function that compares two values using the specified operation.
|
|
|
|
Args:
|
|
operation: The comparison operation to perform (Operation enum or string)
|
|
haystack: The first value (usually the field value from data)
|
|
needle: The second value (usually the comparison value)
|
|
|
|
Returns:
|
|
bool: True if the comparison matches, False otherwise
|
|
|
|
Examples:
|
|
>>> matches(Operation.EQUAL, "test", "test")
|
|
True
|
|
>>> matches(Operation.CONTAIN, "Python Tutorial", "Python")
|
|
True
|
|
>>> matches(Operation.GREATER_THAN, 100, 50)
|
|
True
|
|
>>> matches("==", "test", "test")
|
|
True
|
|
|
|
"""
|
|
# Parse operation if it's a string
|
|
if isinstance(operation, str):
|
|
try:
|
|
operation = Operation(operation)
|
|
except ValueError:
|
|
operation = Operation.EQUAL
|
|
|
|
try:
|
|
if Operation.EQUAL == operation:
|
|
return haystack == needle
|
|
|
|
if Operation.NOT_EQUAL == operation:
|
|
return haystack != needle
|
|
|
|
if Operation.CONTAIN == operation:
|
|
return str(needle) in str(haystack) if haystack is not None else False
|
|
|
|
if Operation.NOT_CONTAIN == operation:
|
|
return str(needle) not in str(haystack) if haystack is not None else True
|
|
|
|
if Operation.GREATER_THAN == operation:
|
|
if haystack is None or needle is None:
|
|
return False
|
|
return haystack > needle
|
|
|
|
if Operation.LESS_THAN == operation:
|
|
if haystack is None or needle is None:
|
|
return False
|
|
return haystack < needle
|
|
|
|
if Operation.GREATER_EQUAL == operation:
|
|
if haystack is None or needle is None:
|
|
return False
|
|
return haystack >= needle
|
|
|
|
if Operation.LESS_EQUAL == operation:
|
|
if haystack is None or needle is None:
|
|
return False
|
|
return haystack <= needle
|
|
|
|
if Operation.STARTS_WITH == operation:
|
|
return str(haystack).startswith(str(needle)) if haystack is not None else False
|
|
|
|
if Operation.ENDS_WITH == operation:
|
|
return str(haystack).endswith(str(needle)) if haystack is not None else False
|
|
|
|
# Unknown operation, default to equality
|
|
return haystack == needle
|
|
|
|
except (TypeError, AttributeError):
|
|
# Comparison failed (e.g., comparing incompatible types)
|
|
return False
|
|
|
|
|
|
def matches_condition(key: str, value: tuple | str | float | bool, data: dict) -> bool:
|
|
"""
|
|
Check if a field in a dictionary matches the given condition.
|
|
|
|
This is a helper function that extracts values from a dictionary and uses the generic
|
|
matches() function to perform the comparison.
|
|
|
|
Args:
|
|
key: The field name to check in the data dictionary
|
|
value: Either:
|
|
- A direct value for equality check: "test"
|
|
- A tuple of (Operation, value): (Operation.CONTAIN, "test")
|
|
- A tuple of (str, value): ("in", "test") for backward compatibility
|
|
data: Dictionary containing the data to check against
|
|
|
|
Returns:
|
|
bool: True if the condition matches, False otherwise
|
|
|
|
Examples:
|
|
>>> data = {"title": "Python Tutorial", "size": 1000}
|
|
>>> matches_condition("title", "Python Tutorial", data)
|
|
True
|
|
>>> matches_condition("title", (Operation.CONTAIN, "Python"), data)
|
|
True
|
|
>>> matches_condition("size", (Operation.GREATER_THAN, 500), data)
|
|
True
|
|
>>> matches_condition("missing", "value", data)
|
|
False
|
|
|
|
"""
|
|
if key not in data:
|
|
return False
|
|
|
|
field_value: Any = data[key]
|
|
|
|
# Parse value to extract operation and comparison value
|
|
if isinstance(value, tuple) and len(value) == 2:
|
|
operation, compare_value = value
|
|
else:
|
|
operation = Operation.EQUAL
|
|
compare_value = value
|
|
|
|
return matches(operation, field_value, compare_value)
|
|
|
|
|
|
def matches_all(data: dict, **conditions) -> bool:
|
|
"""
|
|
Check if all conditions match (AND logic).
|
|
|
|
Args:
|
|
data: Dictionary containing the data to check against
|
|
**conditions: Keyword arguments representing conditions to check
|
|
|
|
Returns:
|
|
bool: True if all conditions match, False otherwise
|
|
|
|
Examples:
|
|
>>> data = {"title": "Python Tutorial", "size": 1000, "status": "active"}
|
|
>>> matches_all(data, title=(Operation.CONTAIN, "Python"), size=(Operation.GREATER_THAN, 500))
|
|
True
|
|
>>> matches_all(data, title="Python Tutorial", status="active")
|
|
True
|
|
|
|
"""
|
|
if not conditions:
|
|
return True
|
|
|
|
return all(matches_condition(key, value, data) for key, value in conditions.items())
|
|
|
|
|
|
def matches_any(data: dict, **conditions) -> bool:
|
|
"""
|
|
Check if any condition matches (OR logic).
|
|
|
|
Args:
|
|
data: Dictionary containing the data to check against
|
|
**conditions: Keyword arguments representing conditions to check
|
|
|
|
Returns:
|
|
bool: True if any condition matches, False if none match
|
|
|
|
Examples:
|
|
>>> data = {"title": "Python Tutorial", "size": 1000}
|
|
>>> matches_any(data, title=(Operation.CONTAIN, "Java"), size=(Operation.GREATER_THAN, 500))
|
|
True
|
|
>>> matches_any(data, title="Wrong", status="Wrong")
|
|
False
|
|
|
|
"""
|
|
if not conditions:
|
|
return False
|
|
|
|
return any(matches_condition(key, value, data) for key, value in conditions.items())
|
|
|
|
|
|
def filter_items(items: list[dict], **conditions) -> list[dict]:
|
|
"""
|
|
Filter a list of dictionaries based on conditions (AND logic).
|
|
|
|
Args:
|
|
items: List of dictionaries to filter
|
|
**conditions: Keyword arguments representing conditions to check
|
|
|
|
Returns:
|
|
list[dict]: Filtered list of dictionaries that match all conditions
|
|
|
|
Examples:
|
|
>>> items = [
|
|
... {"title": "Python Tutorial", "size": 1000},
|
|
... {"title": "JavaScript Course", "size": 2000},
|
|
... {"title": "Python Advanced", "size": 1500}
|
|
... ]
|
|
>>> filter_items(items, title=(Operation.CONTAIN, "Python"))
|
|
[{"title": "Python Tutorial", "size": 1000}, {"title": "Python Advanced", "size": 1500}]
|
|
>>> filter_items(items, size=(Operation.GREATER_THAN, 1200))
|
|
[{"title": "JavaScript Course", "size": 2000}, {"title": "Python Advanced", "size": 1500}]
|
|
|
|
"""
|
|
if not conditions:
|
|
return items
|
|
|
|
return [item for item in items if matches_all(item, **conditions)]
|
|
|
|
|
|
def find_first(items: list[dict], **conditions) -> dict | None:
|
|
"""
|
|
Find the first dictionary that matches all conditions (AND logic).
|
|
|
|
Args:
|
|
items: List of dictionaries to search
|
|
**conditions: Keyword arguments representing conditions to check
|
|
|
|
Returns:
|
|
dict | None: First matching dictionary or None if no match found
|
|
|
|
Examples:
|
|
>>> items = [
|
|
... {"title": "Python Tutorial", "size": 1000},
|
|
... {"title": "JavaScript Course", "size": 2000}
|
|
... ]
|
|
>>> find_first(items, title=(Operation.CONTAIN, "Python"))
|
|
{"title": "Python Tutorial", "size": 1000}
|
|
>>> find_first(items, title="Nonexistent")
|
|
None
|
|
|
|
"""
|
|
for item in items:
|
|
if matches_all(item, **conditions):
|
|
return item
|
|
return None
|
|
|
|
|
|
def find_all(items: list[dict], **conditions) -> list[dict]:
|
|
"""
|
|
Alias for filter_items() - find all dictionaries matching conditions.
|
|
|
|
Args:
|
|
items: List of dictionaries to search
|
|
**conditions: Keyword arguments representing conditions to check
|
|
|
|
Returns:
|
|
list[dict]: List of matching dictionaries
|
|
|
|
"""
|
|
return filter_items(items, **conditions)
|