diff --git a/src/querytyper/__init__.py b/src/querytyper/__init__.py index 0cabab0..de1b9bf 100644 --- a/src/querytyper/__init__.py +++ b/src/querytyper/__init__.py @@ -1,7 +1,9 @@ """querytyper package.""" -from querytyper.mongo import MongoQuery, MongoModelMetaclass +from querytyper.mongo import MongoModelMetaclass, MongoQuery, exists, regex_query __all__ = [ "MongoQuery", "MongoModelMetaclass", + "regex_query", + "exists", ] diff --git a/src/querytyper/mongo/__init__.py b/src/querytyper/mongo/__init__.py index 97f07d0..d0ae42d 100644 --- a/src/querytyper/mongo/__init__.py +++ b/src/querytyper/mongo/__init__.py @@ -1,7 +1,9 @@ """querytyper package.""" -from querytyper.mongo.query import MongoQuery, MongoModelMetaclass +from querytyper.mongo.query import MongoModelMetaclass, MongoQuery, exists, regex_query __all__ = [ "MongoQuery", "MongoModelMetaclass", + "regex_query", + "exists", ] diff --git a/src/querytyper/mongo/query.py b/src/querytyper/mongo/query.py index bbc19bd..8b4ad18 100644 --- a/src/querytyper/mongo/query.py +++ b/src/querytyper/mongo/query.py @@ -1,8 +1,8 @@ """MongoQuery .""" +import re from collections.abc import Iterable from pprint import pformat -import re -from typing import Any, Generic, Tuple, Type, TypeVar, Union, cast, Dict +from typing import Any, Dict, Generic, Tuple, Type, TypeVar, Union, cast from pydantic import BaseModel from pydantic.main import ModelMetaclass @@ -28,6 +28,11 @@ def __init__(self, *args: Any) -> None: # clean up MongoQuery _dict at each instantiation MongoQuery._dict = {} + def __del__(self) -> None: + """MongoQuery destructor.""" + self._dict = {} + MongoQuery._dict = {} + def __repr__(self) -> str: """Overload repr method to pretty format it.""" return pformat(self._dict) @@ -102,9 +107,7 @@ def __eq__( # type: ignore[override] """Overload == operator.""" _dict = MongoQuery._dict field = _dict.get(self.name) - if isinstance(other, re.Pattern): - _dict[self.name] = {"$regex": other.pattern} - elif field is None: + if field is None: _dict[self.name] = other else: _dict[self.name] = ( @@ -154,7 +157,7 @@ def exists( raise TypeError(f"Field must be a QueryField or str, {type(field)} is not supported.") -def field_regex( +def regex_query( field: Union[QueryField[str], str], regex: re.Pattern[str], ) -> QueryCondition: diff --git a/tests/mongo/test_query.py b/tests/mongo/test_query.py index ddfde90..062f9d5 100644 --- a/tests/mongo/test_query.py +++ b/tests/mongo/test_query.py @@ -1,8 +1,13 @@ """Test mongo query implementation.""" +import re from typing import Dict, List, Optional + +import pytest from pydantic import BaseModel -from querytyper import MongoQuery, MongoModelMetaclass + +from querytyper import MongoModelMetaclass, MongoQuery, exists, regex_query +from querytyper.mongo.query import QueryCondition class Dummy(BaseModel): @@ -28,6 +33,16 @@ def test_query_equals() -> None: assert query._dict == {"str_field": "a"} +def test_query_less_then() -> None: + """Test MongoQuery less_then override.""" + query = MongoQuery(QueryModel.int_field <= 1) + assert isinstance(query, MongoQuery) + assert query._dict == {"int_field": {"$lte": 1}} + query = MongoQuery(QueryModel.int_field < 1) + assert isinstance(query, MongoQuery) + assert query._dict == {"int_field": {"$lt": 1}} + + def test_query_and() -> None: """Test MongoQuery equals override.""" query = MongoQuery((QueryModel.str_field == "a") & (QueryModel.int_field >= 1)) @@ -47,3 +62,94 @@ def test_query_in() -> None: query = MongoQuery(QueryModel.str_field in ["a", "b"]) assert isinstance(query, MongoQuery) assert query._dict == {"str_field": ["a", "b"]} + + +def test_query_init_error() -> None: + """Test MongoQuery equals override.""" + with pytest.raises( + TypeError, + match="MongoQuery argument must be a QueryCondition or a boolean value, is not supported.", + ): + MongoQuery("random string") + + +def test_query_repr() -> None: + """Test MongoQuery equals override.""" + query = MongoQuery(QueryModel.str_field == "a") + assert isinstance(query, MongoQuery) + assert repr(query) == "{'str_field': 'a'}" + + +def test_query_condition_repr() -> None: + """Test MongoQuery equals override.""" + condition = QueryCondition(QueryModel.str_field == "a") + assert isinstance(condition, QueryCondition) + assert repr(condition) == "{'str_field': 'a'}" + + +def test_querycondition_and() -> None: + """Test Query condition & override.""" + conditions = QueryCondition(QueryModel.str_field == "a") & QueryCondition( + QueryModel.int_field >= 1 + ) + assert isinstance(conditions, QueryCondition) + # test also boolean is supported + conditions = QueryCondition(QueryModel.str_field == "a") & True + assert isinstance(conditions, QueryCondition) + condition = QueryCondition(QueryModel.str_field == "a") + conditions = True & condition + assert isinstance(conditions, QueryCondition) + assert isinstance(condition, QueryCondition) + assert condition == MongoQuery._dict + + +def test_metaclass_type_errors() -> None: + """Test MongoModelMetaclass.""" + with pytest.raises( + TypeError, + match="The class with metaclass MongoModelMetaclass requires a base class that inherits from BaseModel", + ): + + class Test(metaclass=MongoModelMetaclass): + """Test class.""" + + class AnotherDummy(BaseModel): + """Dummy base model.""" + + another_field: str + + with pytest.raises( + TypeError, match="MongoModelMetaclass does not support multiple inheritance" + ): + + class MultipleInheritanceTest(Dummy, AnotherDummy, metaclass=MongoModelMetaclass): + """Test class.""" + + with pytest.raises(TypeError, match="The base class must inherit from BaseModel"): + + class TestNoBaseModel(str, metaclass=MongoModelMetaclass): + """Test class.""" + + +def test_regex_query() -> None: + """Test regex query.""" + condition = regex_query(QueryModel.str_field, re.compile("^a")) + assert isinstance(condition, QueryCondition) + assert condition == {"str_field": {"$regex": "^a"}} + condition = regex_query("str_field", re.compile("^a")) + assert isinstance(condition, QueryCondition) + assert condition == {"str_field": {"$regex": "^a"}} + + +def test_exists_query() -> None: + """Test exists query.""" + condition = exists(QueryModel.str_field) + assert isinstance(condition, QueryCondition) + assert condition == {"str_field": {"$exists": True}} + condition = exists("str_field") + assert isinstance(condition, QueryCondition) + assert condition == {"str_field": {"$exists": True}} + with pytest.raises( + TypeError, match="Field must be a QueryField or str, is not supported." + ): + exists(1)