Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix | August 2023 #669

Merged
merged 24 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion beanie/odm/operators/find/logical.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,19 @@ def __init__(self, expression: Mapping[str, Any]):

@property
def query(self):
return {"$not": self.expression}
if len(self.expression) == 1:
expression_key = list(self.expression.keys())[0]
if expression_key.startswith("$"):
raise AttributeError(
"Not operator can not be used with operators"
)
value = self.expression[expression_key]
if isinstance(value, dict):
internal_key = list(value.keys())[0]
if internal_key.startswith("$"):
return {expression_key: {"$not": value}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roman-right is the (omitted) else part here even valid, e.g. {"$not": {"foo": 3}}? All the examples I have seen with dict value are operator expressions, i.e. the condition here is always true. If that's the case this can be simplified. The added test doesn't cover this case so I can't tell for sure.


return {expression_key: {"$not": {"$eq": value}}}
raise AttributeError(
"Not operator can only be used with one expression"
)
7 changes: 6 additions & 1 deletion beanie/odm/queries/find.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,8 +622,13 @@ def build_aggregation_pipeline(self):
aggregation_pipeline: List[Dict[str, Any]] = construct_lookup_queries(
self.document_model
)
filter_query = self.get_filter_query()
if "$text" in filter_query:
text_query = filter_query["$text"]
aggregation_pipeline.insert(0, {"$match": {"$text": text_query}})
del filter_query["$text"]

aggregation_pipeline.append({"$match": self.get_filter_query()})
aggregation_pipeline.append({"$match": filter_query})

sort_pipeline = {"$sort": {i[0]: i[1] for i in self.sort_expressions}}
if sort_pipeline["$sort"]:
Expand Down
13 changes: 11 additions & 2 deletions beanie/odm/utils/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
DocWasNotRegisteredInUnionClass,
)
from beanie.odm.interfaces.detector import ModelType
from beanie.odm.utils.pydantic import parse_model
from beanie.odm.utils.pydantic import parse_model, get_config_value

if TYPE_CHECKING:
from beanie.odm.documents import Document


def merge_models(left: BaseModel, right: BaseModel) -> None:
"""
Merge two models
:param left: left model
:param right: right model
:return: None
"""
from beanie.odm.fields import Link

if hasattr(left, "_previous_revision_id") and hasattr(
Expand All @@ -24,7 +30,10 @@ def merge_models(left: BaseModel, right: BaseModel) -> None:
if isinstance(right_value, BaseModel) and isinstance(
left_value, BaseModel
):
merge_models(left_value, right_value)
if get_config_value(left_value, "frozen"):
left.__setattr__(k, right_value)
else:
merge_models(left_value, right_value)
continue
if isinstance(right_value, list):
links_found = False
Expand Down
4 changes: 4 additions & 0 deletions tests/odm/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
DocumentWithIndexMerging1,
DocumentWithIndexMerging2,
DocumentWithCustomInit,
DocumentWithTextIndexAndLink,
LinkDocumentForTextSeacrh,
)
from tests.odm.views import TestView, TestViewWithLink

Expand Down Expand Up @@ -253,6 +255,8 @@ async def init(db):
DocumentWithIndexMerging1,
DocumentWithIndexMerging2,
DocumentWithCustomInit,
DocumentWithTextIndexAndLink,
LinkDocumentForTextSeacrh,
]
await init_beanie(
database=db,
Expand Down
17 changes: 17 additions & 0 deletions tests/odm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,3 +837,20 @@ class DocumentWithCustomInit(Document):
@classmethod
async def custom_init(cls):
cls.s = "TEST2"


class LinkDocumentForTextSeacrh(Document):
i: int


class DocumentWithTextIndexAndLink(Document):
s: str
link: Link[LinkDocumentForTextSeacrh]

class Settings:
indexes = [
pymongo.IndexModel(
[("s", pymongo.TEXT)],
name="text_index",
)
]
13 changes: 11 additions & 2 deletions tests/odm/operators/find/test_logical.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from beanie.odm.operators.find.logical import And, Not, Nor, Or
from tests.odm.models import Sample

Expand All @@ -10,9 +12,16 @@ async def test_and():
assert q == {"$and": [{"integer": 1}, {"nested.integer": {"$gt": 3}}]}


async def test_not():
async def test_not(preset_documents):
q = Not(Sample.integer == 1)
assert q == {"$not": {"integer": 1}}
assert q == {"integer": {"$not": {"$eq": 1}}}

docs = await Sample.find(q).to_list()
assert len(docs) == 7

with pytest.raises(AttributeError):
q = Not(And(Sample.integer == 1, Sample.nested.integer > 3))
await Sample.find(q).to_list()


async def test_nor():
Expand Down
18 changes: 18 additions & 0 deletions tests/odm/test_relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
DocumentWithListLink,
DocumentWithListOfLinks,
DocumentToBeLinked,
DocumentWithTextIndexAndLink,
LinkDocumentForTextSeacrh,
)


Expand Down Expand Up @@ -335,6 +337,22 @@ async def test_fetch_list_with_some_prefetched(self):
for i in range(10):
assert doc_with_links.links[i].id == docs[i].id

async def test_text_search(self):
doc = DocumentWithTextIndexAndLink(
s="hello world", link=LinkDocumentForTextSeacrh(i=1)
)
await doc.insert(link_rule=WriteRules.WRITE)

doc2 = DocumentWithTextIndexAndLink(
s="hi world", link=LinkDocumentForTextSeacrh(i=2)
)
await doc2.insert(link_rule=WriteRules.WRITE)

docs = await DocumentWithTextIndexAndLink.find(
{"$text": {"$search": "hello"}}, fetch_links=True
).to_list()
assert len(docs) == 1


class TestReplace:
async def test_do_nothing(self, house):
Expand Down
Loading