Skip to content

Commit

Permalink
Merge pull request #358 from rga-alex-deng/feature/optional-list-link
Browse files Browse the repository at this point in the history
Feature/optional list link
  • Loading branch information
roman-right authored Oct 6, 2022
2 parents 8cde50f + 4ac356f commit 94ecf21
Show file tree
Hide file tree
Showing 12 changed files with 127 additions and 35 deletions.
2 changes: 1 addition & 1 deletion beanie/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from beanie.odm.views import View
from beanie.odm.union_doc import UnionDoc

__version__ = "1.11.12"
__version__ = "1.12.0"
__all__ = [
# ODM
"Document",
Expand Down
68 changes: 42 additions & 26 deletions beanie/odm/documents.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,14 @@ async def insert(
]:
if isinstance(value, Document):
await value.insert(link_rule=WriteRules.WRITE)
if field_info.link_type == LinkTypes.LIST:
for obj in value:
if isinstance(obj, Document):
await obj.insert(link_rule=WriteRules.WRITE)
if field_info.link_type in [
LinkTypes.LIST,
LinkTypes.OPTIONAL_LIST
]:
if isinstance(value, List):
for obj in value:
if isinstance(obj, Document):
await obj.insert(link_rule=WriteRules.WRITE)

result = await self.get_motor_collection().insert_one(
get_dict(self, to_db=True), session=session
Expand Down Expand Up @@ -343,15 +347,19 @@ async def replace(
ignore_revision=ignore_revision,
session=session,
)
if field_info.link_type == LinkTypes.LIST:
for obj in value:
if isinstance(obj, Document):
await obj.replace(
link_rule=link_rule,
bulk_writer=bulk_writer,
ignore_revision=ignore_revision,
session=session,
)
if field_info.link_type in [
LinkTypes.LIST,
LinkTypes.OPTIONAL_LIST
]:
if isinstance(value, List):
for obj in value:
if isinstance(obj, Document):
await obj.replace(
link_rule=link_rule,
bulk_writer=bulk_writer,
ignore_revision=ignore_revision,
session=session,
)

use_revision_id = self.get_settings().use_revision
find_query: Dict[str, Any] = {"_id": self.id}
Expand Down Expand Up @@ -396,12 +404,16 @@ async def save(
await value.save(
link_rule=link_rule, session=session
)
if field_info.link_type == LinkTypes.LIST:
for obj in value:
if isinstance(obj, Document):
await obj.save(
link_rule=link_rule, session=session
)
if field_info.link_type in [
LinkTypes.LIST,
LinkTypes.OPTIONAL_LIST
]:
if isinstance(value, List):
for obj in value:
if isinstance(obj, Document):
await obj.save(
link_rule=link_rule, session=session
)

try:
return await self.replace(session=session, **kwargs)
Expand Down Expand Up @@ -658,13 +670,17 @@ async def delete(
link_rule=DeleteRules.DELETE_LINKS,
**pymongo_kwargs,
)
if field_info.link_type == LinkTypes.LIST:
for obj in value:
if isinstance(obj, Document):
await obj.delete(
link_rule=DeleteRules.DELETE_LINKS,
**pymongo_kwargs,
)
if field_info.link_type in [
LinkTypes.LIST,
LinkTypes.OPTIONAL_LIST
]:
if isinstance(value, List):
for obj in value:
if isinstance(obj, Document):
await obj.delete(
link_rule=DeleteRules.DELETE_LINKS,
**pymongo_kwargs,
)

return await self.find_one({"_id": self.id}).delete(
session=session, bulk_writer=bulk_writer, **pymongo_kwargs
Expand Down
1 change: 1 addition & 0 deletions beanie/odm/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ class LinkTypes(str, Enum):
DIRECT = "DIRECT"
OPTIONAL_DIRECT = "OPTIONAL_DIRECT"
LIST = "LIST"
OPTIONAL_LIST = "OPTIONAL_LIST"


class LinkInfo(BaseModel):
Expand Down
5 changes: 5 additions & 0 deletions beanie/odm/utils/encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ def encode_document(self, obj):
obj_dict[k] = o.to_ref()
else:
obj_dict[k] = o
if link_fields[k].link_type == LinkTypes.OPTIONAL_LIST:
if o is not None:
obj_dict[k] = [link.to_ref() for link in o]
else:
obj_dict[k] = o
else:
obj_dict[k] = o
obj_dict[k] = encoder.encode(obj_dict[k])
Expand Down
8 changes: 6 additions & 2 deletions beanie/odm/utils/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ def detect_link(field: ModelField) -> Optional[LinkInfo]:
):
internal_field = field.sub_fields[0] # type: ignore
if internal_field.type_ == Link:
if internal_field.allow_none is True:
return None
if field.allow_none is True:
return LinkInfo(
field=field.name,
model_class=internal_field.sub_fields[0].type_, # type: ignore
link_type=LinkTypes.OPTIONAL_LIST,
)
return LinkInfo(
field=field.name,
model_class=internal_field.sub_fields[0].type_, # type: ignore
Expand Down
15 changes: 14 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

Beanie project

## [1.12.0] - 2022-10-06

### Improvement

- Optional list of links field

### Implementation

- Author - [Alex Deng](https://github.com/rga-alex-deng)
- PR <https://github.com/roman-right/beanie/pull/358>

## [1.11.12] - 2022-09-28

### Improvement
Expand Down Expand Up @@ -973,4 +984,6 @@ how specific type should be presented in the database

[1.11.11]: https://pypi.org/project/beanie/1.11.11

[1.11.12]: https://pypi.org/project/beanie/1.11.12
[1.11.12]: https://pypi.org/project/beanie/1.11.12

[1.12.0]: https://pypi.org/project/beanie/1.12.0
23 changes: 23 additions & 0 deletions docs/tutorial/relations.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The next field types are supported:
- `Link[...]`
- `Optional[Link[...]]`
- `List[Link[...]]`
- `Optional[List[Link[...]]]`

Direct link to the document:

Expand Down Expand Up @@ -63,6 +64,28 @@ class House(Document):
windows: List[Link[Window]]
```

Optional List of the links:

```python
from typing import List, Optional

from beanie import Document, Link

class Window(Document):
x: int = 10
y: int = 10

class Yard(Document):
v: int = 10
y: int = 10

class House(Document):
name: str
door: Link[Door]
windows: List[Link[Window]]
yards: Optional[List[Link[Yard]]]
```

Other link patterns are not supported for at this moment. If you need something more specific for your use-case,
please open an issue on the GitHub page - <https://github.com/roman-right/beanie>

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "beanie"
version = "1.11.12"
version = "1.12.0"
description = "Asynchronous Python ODM for MongoDB"
authors = ["Roman <[email protected]>"]
license = "Apache-2.0"
Expand Down
8 changes: 6 additions & 2 deletions tests/odm/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
Window,
Door,
Roof,
Yard,
InheritedDocumentWithActions,
DocumentForEncodingTest,
DocumentForEncodingTestDate,
Expand All @@ -38,7 +39,8 @@
DocumentUnion,
HouseWithRevision,
WindowWithRevision,
DocumentWithActions2,
YardWithRevision,
DocumentWithActions2
)
from tests.odm.views import TestView
from tests.odm.models import (
Expand Down Expand Up @@ -160,6 +162,7 @@ async def init(loop, db):
Window,
Door,
Roof,
Yard,
InheritedDocumentWithActions,
DocumentForEncodingTest,
DocumentForEncodingTestDate,
Expand All @@ -169,7 +172,8 @@ async def init(loop, db):
DocumentUnion,
HouseWithRevision,
WindowWithRevision,
DocumentWithActions2,
YardWithRevision,
DocumentWithActions2
]
await init_beanie(
database=db,
Expand Down
15 changes: 15 additions & 0 deletions tests/odm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,11 @@ class DocumentWithExtrasKw(Document, extra=Extra.allow):
num_1: int


class Yard(Document):
v: int
w: int


class Window(Document):
x: int
y: int
Expand All @@ -393,6 +398,7 @@ class House(Document):
windows: List[Link[Window]]
door: Link[Door]
roof: Optional[Link[Roof]]
yards: Optional[List[Link[Yard]]]
name: Indexed(str) = Field(hidden=True)
height: Indexed(int) = 2

Expand Down Expand Up @@ -448,6 +454,15 @@ class Settings:
union_doc = DocumentUnion


class YardWithRevision(Document):
v: int
w: int

class Settings:
use_revision = True
use_state_management = True


class WindowWithRevision(Document):
x: int
y: int
Expand Down
13 changes: 12 additions & 1 deletion tests/odm/test_relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from beanie.exceptions import DocumentWasNotSaved
from beanie.odm.fields import WriteRules, Link, DeleteRules
from tests.odm.models import Window, Door, House, Roof
from tests.odm.models import Window, Door, House, Roof, Yard


@pytest.fixture
Expand Down Expand Up @@ -31,9 +31,14 @@ async def house(house_not_inserted):
async def houses():
for i in range(10):
roof = Roof() if i % 2 == 0 else None
if i % 2 == 0:
yards = [Yard(v=10, w=10 + i), Yard(v=11, w=10 + i)]
else:
yards = None
house = await House(
door=Door(t=i),
windows=[Window(x=10, y=10 + i), Window(x=11, y=11 + i)],
yards=yards,
roof=roof,
name="test",
height=i,
Expand Down Expand Up @@ -74,6 +79,9 @@ async def test_prefetch_find_many(self, houses):
assert len(items) == 7
for window in items[0].windows:
assert isinstance(window, Link)
assert items[0].yards is None
for yard in items[1].yards:
assert isinstance(yard, Link)
assert isinstance(items[0].door, Link)
assert items[0].roof is None
assert isinstance(items[1].roof, Link)
Expand All @@ -86,6 +94,9 @@ async def test_prefetch_find_many(self, houses):
assert len(items) == 7
for window in items[0].windows:
assert isinstance(window, Window)
assert items[0].yards == []
for yard in items[1].yards:
assert isinstance(yard, Yard)
assert isinstance(items[0].door, Door)
assert items[0].roof is None
assert isinstance(items[1].roof, Roof)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_beanie.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@


def test_version():
assert __version__ == "1.11.12"
assert __version__ == "1.12.0"

0 comments on commit 94ecf21

Please sign in to comment.