-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Logical and Selector filters (#28)
- Loading branch information
1 parent
bd93355
commit 03876ff
Showing
12 changed files
with
294 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
from unittest import TestCase | ||
|
||
from tigrisdb.types.filters import GT, GTE, LTE, And, Contains, Eq, Not, Or, Regex | ||
|
||
|
||
class LogicalFiltersTest(TestCase): | ||
def test_or(self): | ||
f = Or(Eq("f1", True), LTE("f2", 25), Contains("f3", "v1")) | ||
self.assertEqual( | ||
{"$or": [{"f1": True}, {"f2": {"$lte": 25}}, {"f3": {"$contains": "v1"}}]}, | ||
f.query(), | ||
) | ||
|
||
def test_and(self): | ||
f = And(Eq("f1", True), LTE("f2", 25), Contains("f3", "v1")) | ||
self.assertEqual( | ||
{"$and": [{"f1": True}, {"f2": {"$lte": 25}}, {"f3": {"$contains": "v1"}}]}, | ||
f.query(), | ||
) | ||
|
||
def test_empty(self): | ||
self.assertEqual({}, And().query()) | ||
self.assertEqual({}, Or().query()) | ||
|
||
def test_single(self): | ||
self.assertEqual({"f1": {"$gt": 10.5}}, Or(GT("f1", 10.5)).query()) | ||
|
||
def test_complex_or_filter(self): | ||
f = Or( | ||
Or(Eq("name", "alice"), GTE("rank", 2)), | ||
Or(Eq("name", "emma"), LTE("rank", 6), Not("city", "sfo")), | ||
And(GTE("f1", 1.5), LTE("f1", 3.14), Contains("f2", "hello")), | ||
Not("f3", False), | ||
Regex("f4", "/andy/i"), | ||
) | ||
self.assertEqual( | ||
{ | ||
"$or": [ | ||
{"$or": [{"name": "alice"}, {"rank": {"$gte": 2}}]}, | ||
{ | ||
"$or": [ | ||
{"name": "emma"}, | ||
{"rank": {"$lte": 6}}, | ||
{"city": {"$not": "sfo"}}, | ||
] | ||
}, | ||
{ | ||
"$and": [ | ||
{"f1": {"$gte": 1.5}}, | ||
{"f1": {"$lte": 3.14}}, | ||
{"f2": {"$contains": "hello"}}, | ||
] | ||
}, | ||
{"f3": {"$not": False}}, | ||
{"f4": {"$regex": "/andy/i"}}, | ||
] | ||
}, | ||
f.query(), | ||
) | ||
|
||
def test_complex_and_filter(self): | ||
f = And( | ||
Or(Eq("name", "alice"), GTE("rank", 2)), | ||
Or(Eq("name", "emma"), LTE("rank", 6), Not("city", "sfo")), | ||
) | ||
|
||
self.assertEqual( | ||
{ | ||
"$and": [ | ||
{"$or": [{"name": "alice"}, {"rank": {"$gte": 2}}]}, | ||
{ | ||
"$or": [ | ||
{"name": "emma"}, | ||
{"rank": {"$lte": 6}}, | ||
{"city": {"$not": "sfo"}}, | ||
] | ||
}, | ||
] | ||
}, | ||
f.query(), | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import datetime | ||
from unittest import TestCase | ||
|
||
from tigrisdb.types import RFC3339_format | ||
from tigrisdb.types.filters import GT, GTE, LT, LTE, Contains, Eq, Not, Regex | ||
|
||
|
||
class SelectorTestCase(TestCase): | ||
def test_equals(self): | ||
f = Eq("f1", 25) | ||
self.assertEqual({"f1": 25}, f.query()) | ||
|
||
def test_gte(self): | ||
f = GTE("f1", 25) | ||
self.assertEqual({"f1": {"$gte": 25}}, f.query()) | ||
|
||
def test_gt(self): | ||
dt = datetime.datetime.strptime("2023-05-05T10:00:00+00:00", RFC3339_format) | ||
f = GT("f1", dt) | ||
self.assertEqual({"f1": {"$gt": dt}}, f.query()) | ||
|
||
def test_lte(self): | ||
f = LTE("f1", 25) | ||
self.assertEqual({"f1": {"$lte": 25}}, f.query()) | ||
|
||
def test_lt(self): | ||
f = LT("f1", 25) | ||
self.assertEqual({"f1": {"$lt": 25}}, f.query()) | ||
|
||
def test_regex(self): | ||
f = Regex("f1", "emma*") | ||
self.assertEqual({"f1": {"$regex": "emma*"}}, f.query()) | ||
|
||
def test_contains(self): | ||
f = Contains("f1", "cars") | ||
self.assertEqual({"f1": {"$contains": "cars"}}, f.query()) | ||
|
||
def test_not(self): | ||
f = Not("f1", "Alex") | ||
self.assertEqual({"f1": {"$not": "Alex"}}, f.query()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from unittest import TestCase | ||
|
||
from tigrisdb.types.sort import Ascending, Descending, Sort | ||
|
||
|
||
class SortTests(TestCase): | ||
def test_ascending(self): | ||
sort = Ascending("f1") | ||
self.assertEqual({"f1": "$asc"}, sort.query()) | ||
|
||
def test_descending(self): | ||
sort = Descending("f1") | ||
self.assertEqual({"f1": "$desc"}, sort.query()) | ||
|
||
def test_base_sort_error(self): | ||
with self.assertRaisesRegex(TypeError, "abstract class"): | ||
Sort() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .logical import And, Or # noqa: F401 | ||
from .selector import GT, GTE, LT, LTE, Contains, Eq, Not, Regex # noqa: F401 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import abc | ||
from typing import Any, Dict, Union | ||
|
||
from tigrisdb.types import BaseOperator, Serializable | ||
|
||
from .selector import SelectorFilter | ||
|
||
|
||
class LogicalFilter(Serializable, BaseOperator, abc.ABC): | ||
def __init__(self, *args: Union[SelectorFilter, Any]): | ||
self.filters = args | ||
|
||
def query(self) -> Dict: | ||
if not self.filters: | ||
return {} | ||
if len(self.filters) == 1: | ||
return self.filters[0].query() | ||
gen = [f.query() for f in self.filters] | ||
return {self.operator: gen} | ||
|
||
|
||
class And(LogicalFilter): | ||
@property | ||
def operator(self): | ||
return "$and" | ||
|
||
|
||
class Or(LogicalFilter): | ||
@property | ||
def operator(self): | ||
return "$or" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import abc | ||
from typing import Any, Dict | ||
|
||
from tigrisdb.types import BaseOperator, Serializable | ||
|
||
|
||
class SelectorFilter(Serializable, BaseOperator, abc.ABC): | ||
def __init__(self, field: str, value: Any): | ||
self.field = field | ||
self.value = value | ||
|
||
def query(self) -> Dict: | ||
return {self.field: {self.operator: self.value}} | ||
|
||
|
||
class Eq(SelectorFilter): | ||
@property | ||
def operator(self): | ||
return "" | ||
|
||
def query(self) -> Dict: | ||
return {self.field: self.value} | ||
|
||
|
||
class Not(SelectorFilter): | ||
@property | ||
def operator(self): | ||
return "$not" | ||
|
||
|
||
class GT(SelectorFilter): | ||
@property | ||
def operator(self): | ||
return "$gt" | ||
|
||
|
||
class GTE(SelectorFilter): | ||
@property | ||
def operator(self): | ||
return "$gte" | ||
|
||
|
||
class LT(SelectorFilter): | ||
@property | ||
def operator(self): | ||
return "$lt" | ||
|
||
|
||
class LTE(SelectorFilter): | ||
@property | ||
def operator(self): | ||
return "$lte" | ||
|
||
|
||
class Regex(SelectorFilter): | ||
@property | ||
def operator(self): | ||
return "$regex" | ||
|
||
|
||
class Contains(SelectorFilter): | ||
@property | ||
def operator(self): | ||
return "$contains" |
Oops, something went wrong.