From fc32001fe72b2cd5aab225dba111c59b392da272 Mon Sep 17 00:00:00 2001 From: Mojix Coder Date: Tue, 1 Feb 2022 09:44:07 +0330 Subject: [PATCH 1/6] Update get_or_create method --- ormar/protocols/queryset_protocol.py | 8 +++-- ormar/queryset/queryset.py | 22 ++++++++---- ormar/relations/querysetproxy.py | 25 +++++++++---- tests/test_fastapi/test_m2m_forwardref.py | 2 +- tests/test_model_definition/test_aliases.py | 6 ++-- .../test_model_definition/test_save_status.py | 6 ++-- .../test_queryproxy_on_m2m_models.py | 9 +++-- .../test_queryset_level_methods.py | 35 +++++++++++++++---- .../test_queries/test_reverse_fk_queryset.py | 12 ++++--- 9 files changed, 92 insertions(+), 33 deletions(-) diff --git a/ormar/protocols/queryset_protocol.py b/ormar/protocols/queryset_protocol.py index 397f58b..e2ba329 100644 --- a/ormar/protocols/queryset_protocol.py +++ b/ormar/protocols/queryset_protocol.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Optional, Sequence, Set, TYPE_CHECKING, Union +from typing import Any, Dict, List, Optional, Sequence, Set, TYPE_CHECKING, Tuple, Union try: from typing import Protocol @@ -55,7 +55,11 @@ class QuerySetProtocol(Protocol): # pragma: nocover async def update(self, each: bool = False, **kwargs: Any) -> int: ... - async def get_or_create(self, **kwargs: Any) -> "Model": + async def get_or_create( + self, + _defaults: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> Tuple["Model", bool]: ... async def update_or_create(self, **kwargs: Any) -> "Model": diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index beae8d7..95fc97c 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -972,26 +972,36 @@ class QuerySet(Generic[T]): self.check_single_result_rows_count(processed_rows) return processed_rows[0] # type: ignore - async def get_or_create(self, *args: Any, **kwargs: Any) -> "T": + async def get_or_create( + self, + _defaults: Optional[Dict[str, Any]] = None, + *args: Any, + **kwargs: Any, + ) -> Tuple["T", bool]: """ Combination of create and get methods. Tries to get a row meeting the criteria for kwargs and if `NoMatch` exception is raised - it creates a new one with given kwargs. + it creates a new one with given kwargs and _defaults. Passing a criteria is actually calling filter(*args, **kwargs) method described below. :param kwargs: fields names and proper value types :type kwargs: Any - :return: returned or created Model - :rtype: Model + :param _defaults: default values for creating object + :type _defaults: Optional[Dict[str, Any]] + :return: model instance and a boolean + :rtype: Tuple("T", bool) """ try: - return await self.get(*args, **kwargs) + return await self.get(*args, **kwargs), False except NoMatch: - return await self.create(**kwargs) + if _defaults is None: + return await self.create(**kwargs), True + else: + return await self.create(**kwargs, **_defaults), True async def update_or_create(self, **kwargs: Any) -> "T": """ diff --git a/ormar/relations/querysetproxy.py b/ormar/relations/querysetproxy.py index 8170cf3..ec3f9fd 100644 --- a/ormar/relations/querysetproxy.py +++ b/ormar/relations/querysetproxy.py @@ -11,6 +11,7 @@ from typing import ( # noqa: I100, I201 TYPE_CHECKING, Type, TypeVar, + Tuple, Union, cast, ) @@ -483,23 +484,33 @@ class QuerysetProxy(Generic[T]): ) return len(children) - async def get_or_create(self, *args: Any, **kwargs: Any) -> "T": + async def get_or_create( + self, + _defaults: Optional[Dict[str, Any]] = None, + *args: Any, + **kwargs: Any, + ) -> Tuple["T", bool]: """ Combination of create and get methods. Tries to get a row meeting the criteria fro kwargs and if `NoMatch` exception is raised - it creates a new one with given kwargs. + it creates a new one with given kwargs and _defaults. :param kwargs: fields names and proper value types :type kwargs: Any - :return: returned or created Model - :rtype: Model + :param _defaults: default values for creating object + :type _defaults: Optional[Dict[str, Any]] + :return: model instance and a boolean + :rtype: Tuple("T", bool) """ try: - return await self.get(*args, **kwargs) - except ormar.NoMatch: - return await self.create(**kwargs) + return await self.get(*args, **kwargs), False + except NoMatch: + if _defaults is None: + return await self.create(**kwargs), True + else: + return await self.create(**kwargs, **_defaults), True async def update_or_create(self, **kwargs: Any) -> "T": """ diff --git a/tests/test_fastapi/test_m2m_forwardref.py b/tests/test_fastapi/test_m2m_forwardref.py index bfccaef..c75791a 100644 --- a/tests/test_fastapi/test_m2m_forwardref.py +++ b/tests/test_fastapi/test_m2m_forwardref.py @@ -102,7 +102,7 @@ def test_payload(): "native_name": "Thailand", } resp = client.post("/", json=payload, headers={"application-type": "json"}) - print(resp.content) + # print(resp.content) assert resp.status_code == 201 resp_country = Country(**resp.json()) diff --git a/tests/test_model_definition/test_aliases.py b/tests/test_model_definition/test_aliases.py index bb6b40f..d403304 100644 --- a/tests/test_model_definition/test_aliases.py +++ b/tests/test_model_definition/test_aliases.py @@ -157,15 +157,17 @@ async def test_bulk_operations_and_fields(): async def test_working_with_aliases_get_or_create(): async with database: async with database.transaction(force_rollback=True): - artist = await Artist.objects.get_or_create( + artist, created = await Artist.objects.get_or_create( first_name="Teddy", last_name="Bear", born_year=2020 ) assert artist.pk is not None + assert created is True - artist2 = await Artist.objects.get_or_create( + artist2, created = await Artist.objects.get_or_create( first_name="Teddy", last_name="Bear", born_year=2020 ) assert artist == artist2 + assert created is False art3 = artist2.dict() art3["born_year"] = 2019 diff --git a/tests/test_model_definition/test_save_status.py b/tests/test_model_definition/test_save_status.py index 9762810..0bbda39 100644 --- a/tests/test_model_definition/test_save_status.py +++ b/tests/test_model_definition/test_save_status.py @@ -195,12 +195,14 @@ async def test_queryset_methods(): comps = await Company.objects.all() assert [comp.saved for comp in comps] - comp2 = await Company.objects.get_or_create(name="Banzai_new", founded=2001) + comp2, created = await Company.objects.get_or_create(name="Banzai_new", founded=2001) assert comp2.saved + assert created is True - comp3 = await Company.objects.get_or_create(name="Banzai", founded=1988) + comp3, created = await Company.objects.get_or_create(name="Banzai", founded=1988) assert comp3.saved assert comp3.pk == comp.pk + assert created is False update_dict = comp.dict() update_dict["founded"] = 2010 diff --git a/tests/test_queries/test_queryproxy_on_m2m_models.py b/tests/test_queries/test_queryproxy_on_m2m_models.py index a91c4f8..1fe8fc5 100644 --- a/tests/test_queries/test_queryproxy_on_m2m_models.py +++ b/tests/test_queries/test_queryproxy_on_m2m_models.py @@ -102,20 +102,23 @@ async def test_queryset_methods(): await post.categories.add(news) await post.categories.add(breaking) - category = await post.categories.get_or_create(name="News") + category, created = await post.categories.get_or_create(name="News") assert category == news assert len(post.categories) == 1 + assert created is False - category = await post.categories.get_or_create(name="Breaking News") + category, created = await post.categories.get_or_create(name="Breaking News") assert category != breaking assert category.pk is not None assert len(post.categories) == 2 + assert created is True await post.categories.update_or_create(pk=category.pk, name="Urgent News") assert len(post.categories) == 2 - cat = await post.categories.get_or_create(name="Urgent News") + cat, created = await post.categories.get_or_create(name="Urgent News") assert cat.pk == category.pk assert len(post.categories) == 1 + assert created is False await post.categories.remove(cat) await cat.delete() diff --git a/tests/test_queries/test_queryset_level_methods.py b/tests/test_queries/test_queryset_level_methods.py index 7176010..ee308dc 100644 --- a/tests/test_queries/test_queryset_level_methods.py +++ b/tests/test_queries/test_queryset_level_methods.py @@ -166,17 +166,18 @@ async def test_delete_and_update(): @pytest.mark.asyncio async def test_get_or_create(): async with database: - tom = await Book.objects.get_or_create( + tom, created = await Book.objects.get_or_create( title="Volume I", author="Anonymous", genre="Fiction" ) assert await Book.objects.count() == 1 + assert created is True - assert ( - await Book.objects.get_or_create( - title="Volume I", author="Anonymous", genre="Fiction" - ) - == tom + second_tom, created = await Book.objects.get_or_create( + title="Volume I", author="Anonymous", genre="Fiction" ) + + assert second_tom.pk == tom.pk + assert created is False assert await Book.objects.count() == 1 assert await Book.objects.create( @@ -188,6 +189,28 @@ async def test_get_or_create(): ) +@pytest.mark.asyncio +async def test_get_or_create_with_defaults(): + async with database: + book, created = await Book.objects.get_or_create( + title="Nice book", _defaults={"author": "Mojix", "genre": "Historic"} + ) + assert created is True + assert book.author == "Mojix" + assert book.title == "Nice book" + assert book.genre == "Historic" + + book2, created = await Book.objects.get_or_create( + author="Mojix", _defaults={"title": "Book2"} + ) + assert created is False + assert book2 == book + assert book2.title == "Nice book" + assert book2.author == "Mojix" + assert book2.genre == "Historic" + assert await Book.objects.count() == 1 + + @pytest.mark.asyncio async def test_update_or_create(): async with database: diff --git a/tests/test_queries/test_reverse_fk_queryset.py b/tests/test_queries/test_reverse_fk_queryset.py index 80193bc..25f54a0 100644 --- a/tests/test_queries/test_reverse_fk_queryset.py +++ b/tests/test_queries/test_reverse_fk_queryset.py @@ -87,22 +87,26 @@ async def test_quering_by_reverse_fk(): assert await album.tracks.exists() assert await album.tracks.count() == 3 - track = await album.tracks.get_or_create( + track, created = await album.tracks.get_or_create( title="The Bird", position=1, play_count=30 ) assert track == track1 + assert created is False assert len(album.tracks) == 1 - track = await album.tracks.get_or_create( - title="The Bird2", position=4, play_count=5 + track, created = await album.tracks.get_or_create( + title="The Bird2", _defaults={"position": 4, "play_count": 5} ) assert track != track1 + assert created is True assert track.pk is not None + assert track.position == 4 and track.play_count == 5 assert len(album.tracks) == 2 await album.tracks.update_or_create(pk=track.pk, play_count=50) assert len(album.tracks) == 2 - track = await album.tracks.get_or_create(title="The Bird2") + track, created = await album.tracks.get_or_create(title="The Bird2") + assert created is False assert track.play_count == 50 assert len(album.tracks) == 1 From 0791e7cc4b9574bdff9453f5b1e80c4adf76eb25 Mon Sep 17 00:00:00 2001 From: Mojix Coder Date: Tue, 1 Feb 2022 10:28:11 +0330 Subject: [PATCH 2/6] Update docs --- README.md | 2 +- docs/api/query-set/query-set.md | 2 +- docs/api/relations/queryset-proxy.md | 2 +- docs/index.md | 2 +- docs/queries/create.md | 17 +++++++++++------ docs/queries/filter-and-sort.md | 6 +++--- docs/queries/index.md | 12 ++++++------ docs/queries/read.md | 17 +++++++++++------ docs/relations/queryset-proxy.md | 6 +++--- 9 files changed, 38 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 8183bbb..f8af8ce 100644 --- a/README.md +++ b/README.md @@ -587,7 +587,7 @@ metadata.drop_all(engine) * `create(**kwargs): -> Model` * `get(*args, **kwargs): -> Model` * `get_or_none(*args, **kwargs): -> Optional[Model]` -* `get_or_create(*args, **kwargs) -> Model` +* `get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs) -> Tuple[Model, bool]` * `first(*args, **kwargs): -> Model` * `update(each: bool = False, **kwargs) -> int` * `update_or_create(**kwargs) -> Model` diff --git a/docs/api/query-set/query-set.md b/docs/api/query-set/query-set.md index b7aa53e..9c8d2ef 100644 --- a/docs/api/query-set/query-set.md +++ b/docs/api/query-set/query-set.md @@ -745,7 +745,7 @@ below. #### get\_or\_create ```python - | async get_or_create(*args: Any, **kwargs: Any) -> "T" + | async get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args: Any, **kwargs: Any) -> Tuple["T", bool] ``` Combination of create and get methods. diff --git a/docs/api/relations/queryset-proxy.md b/docs/api/relations/queryset-proxy.md index 009c466..1710f11 100644 --- a/docs/api/relations/queryset-proxy.md +++ b/docs/api/relations/queryset-proxy.md @@ -396,7 +396,7 @@ each=True flag to affect whole table. #### get\_or\_create ```python - | async get_or_create(*args: Any, **kwargs: Any) -> "T" + | async get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args: Any, **kwargs: Any) -> Tuple["T", bool] ``` Combination of create and get methods. diff --git a/docs/index.md b/docs/index.md index 9ecee7d..1b8618c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -596,7 +596,7 @@ metadata.drop_all(engine) * `create(**kwargs): -> Model` * `get(*args, **kwargs): -> Model` * `get_or_none(*args, **kwargs): -> Optional[Model]` -* `get_or_create(*args, **kwargs) -> Model` +* `get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs) -> Tuple[Model, bool]` * `first(*args, **kwargs): -> Model` * `update(each: bool = False, **kwargs) -> int` * `update_or_create(**kwargs) -> Model` diff --git a/docs/queries/create.md b/docs/queries/create.md index 560ccae..09cc560 100644 --- a/docs/queries/create.md +++ b/docs/queries/create.md @@ -3,7 +3,7 @@ Following methods allow you to insert data into the database. * `create(**kwargs) -> Model` -* `get_or_create(**kwargs) -> Model` +* `get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]` * `update_or_create(**kwargs) -> Model` * `bulk_create(objects: List[Model]) -> None` @@ -16,7 +16,7 @@ Following methods allow you to insert data into the database. * `QuerysetProxy` * `QuerysetProxy.create(**kwargs)` method - * `QuerysetProxy.get_or_create(**kwargs)` method + * `QuerysetProxy.get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs)` method * `QuerysetProxy.update_or_create(**kwargs)` method ## create @@ -56,12 +56,12 @@ await malibu.save() ## get_or_create -`get_or_create(**kwargs) -> Model` +`get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]` Combination of create and get methods. Tries to get a row meeting the criteria and if `NoMatch` exception is raised it creates -a new one with given kwargs. +a new one with given kwargs and _defaults. ```python class Album(ormar.Model): @@ -72,12 +72,17 @@ class Album(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) + year: int = ormar.Integer() ``` ```python -album = await Album.objects.get_or_create(name='The Cat') +album, created = await Album.objects.get_or_create(name='The Cat', _defaults={"year": 1999}) +assert created is True +assert album.name == "The Cat" +assert album.year == 1999 # object is created as it does not exist -album2 = await Album.objects.get_or_create(name='The Cat') +album2, created = await Album.objects.get_or_create(name='The Cat') +assert created is False assert album == album2 # return True as the same db row is returned ``` diff --git a/docs/queries/filter-and-sort.md b/docs/queries/filter-and-sort.md index 1f778d9..ac7aee9 100644 --- a/docs/queries/filter-and-sort.md +++ b/docs/queries/filter-and-sort.md @@ -6,7 +6,7 @@ You can use following methods to filter the data (sql where clause). * `exclude(*args, **kwargs) -> QuerySet` * `get(*args, **kwargs) -> Model` * `get_or_none(*args, **kwargs) -> Optional[Model]` -* `get_or_create(*args, **kwargs) -> Model` +* `get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs) -> Tuple[Model, bool]` * `all(*args, **kwargs) -> List[Optional[Model]]` @@ -15,7 +15,7 @@ You can use following methods to filter the data (sql where clause). * `QuerysetProxy.exclude(*args, **kwargs)` method * `QuerysetProxy.get(*args, **kwargs)` method * `QuerysetProxy.get_or_none(*args, **kwargs)` method - * `QuerysetProxy.get_or_create(*args, **kwargs)` method + * `QuerysetProxy.get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs)` method * `QuerysetProxy.all(*args, **kwargs)` method And following methods to sort the data (sql order by clause). @@ -598,7 +598,7 @@ Exact equivalent of get described above but instead of raising the exception ret ## get_or_create -`get_or_create(*args, **kwargs) -> Model` +`get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs) -> Tuple[Model, bool]` Combination of create and get methods. diff --git a/docs/queries/index.md b/docs/queries/index.md index e2c5a0a..8d8c1e2 100644 --- a/docs/queries/index.md +++ b/docs/queries/index.md @@ -24,7 +24,7 @@ To read more about any specific section or function please refer to the details ###[Insert data into database](./create.md) * `create(**kwargs) -> Model` -* `get_or_create(**kwargs) -> Model` +* `get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]` * `update_or_create(**kwargs) -> Model` * `bulk_create(objects: List[Model]) -> None` @@ -37,7 +37,7 @@ To read more about any specific section or function please refer to the details * `QuerysetProxy` * `QuerysetProxy.create(**kwargs)` method - * `QuerysetProxy.get_or_create(**kwargs)` method + * `QuerysetProxy.get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs)` method * `QuerysetProxy.update_or_create(**kwargs)` method !!!tip @@ -47,7 +47,7 @@ To read more about any specific section or function please refer to the details * `get(**kwargs) -> Model` * `get_or_none(**kwargs) -> Optional[Model]` -* `get_or_create(**kwargs) -> Model` +* `get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]` * `first() -> Model` * `all(**kwargs) -> List[Optional[Model]]` @@ -59,7 +59,7 @@ To read more about any specific section or function please refer to the details * `QuerysetProxy` * `QuerysetProxy.get(**kwargs)` method * `QuerysetProxy.get_or_none(**kwargs)` method - * `QuerysetProxy.get_or_create(**kwargs)` method + * `QuerysetProxy.get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs)` method * `QuerysetProxy.first()` method * `QuerysetProxy.all(**kwargs)` method @@ -140,7 +140,7 @@ Instead of ormar models return raw data in form list of dictionaries or tuples. * `order_by(columns:Union[List, str]) -> QuerySet` * `get(**kwargs) -> Model` * `get_or_none(**kwargs) -> Optional[Model]` -* `get_or_create(**kwargs) -> Model` +* `get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]` * `all(**kwargs) -> List[Optional[Model]]` @@ -150,7 +150,7 @@ Instead of ormar models return raw data in form list of dictionaries or tuples. * `QuerysetProxy.order_by(columns:Union[List, str])` method * `QuerysetProxy.get(**kwargs)` method * `QuerysetProxy.get_or_none(**kwargs)` method - * `QuerysetProxy.get_or_create(**kwargs)` method + * `QuerysetProxy.get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs)` method * `QuerysetProxy.all(**kwargs)` method !!!tip diff --git a/docs/queries/read.md b/docs/queries/read.md index 17972b5..9fa6794 100644 --- a/docs/queries/read.md +++ b/docs/queries/read.md @@ -3,7 +3,7 @@ Following methods allow you to load data from the database. * `get(*args, **kwargs) -> Model` -* `get_or_create(*args, **kwargs) -> Model` +* `get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs) -> Tuple[Model, bool]` * `first(*args, **kwargs) -> Model` * `all(*args, **kwargs) -> List[Optional[Model]]` @@ -14,7 +14,7 @@ Following methods allow you to load data from the database. * `QuerysetProxy` * `QuerysetProxy.get(*args, **kwargs)` method - * `QuerysetProxy.get_or_create(*args, **kwargs)` method + * `QuerysetProxy.get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs)` method * `QuerysetProxy.first(*args, **kwargs)` method * `QuerysetProxy.all(*args, **kwargs)` method @@ -64,12 +64,12 @@ Exact equivalent of get described above but instead of raising the exception ret ## get_or_create -`get_or_create(*args, **kwargs) -> Model` +`get_or_create(_defaults: Optional[Dict[str, Any]] = None, *args, **kwargs) -> Tuple[Model, bool]` Combination of create and get methods. Tries to get a row meeting the criteria and if `NoMatch` exception is raised it creates -a new one with given kwargs. +a new one with given kwargs and _defaults. ```python class Album(ormar.Model): @@ -80,12 +80,17 @@ class Album(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) + year: int = ormar.Integer() ``` ```python -album = await Album.objects.get_or_create(name='The Cat') +album, created = await Album.objects.get_or_create(name='The Cat', _defaults={"year": 1999}) +assert created is True +assert album.name == "The Cat" +assert album.year == 1999 # object is created as it does not exist -album2 = await Album.objects.get_or_create(name='The Cat') +album2, created = await Album.objects.get_or_create(name='The Cat') +assert created is False assert album == album2 # return True as the same db row is returned ``` diff --git a/docs/relations/queryset-proxy.md b/docs/relations/queryset-proxy.md index fc22d75..736d395 100644 --- a/docs/relations/queryset-proxy.md +++ b/docs/relations/queryset-proxy.md @@ -56,9 +56,9 @@ assert post.categories[0] == news ### get_or_create -`get_or_create(**kwargs) -> Model` +`get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]` -Tries to get a row meeting the criteria and if NoMatch exception is raised it creates a new one with given kwargs. +Tries to get a row meeting the criteria and if NoMatch exception is raised it creates a new one with given kwargs and _defaults. !!!tip Read more in queries documentation [get_or_create][get_or_create] @@ -129,7 +129,7 @@ await post.categories.create( ### get_or_create -`get_or_create(**kwargs) -> Model` +`get_or_create(_defaults: Optional[Dict[str, Any]] = None, **kwargs) -> Tuple[Model, bool]` Tries to get a row meeting the criteria and if NoMatch exception is raised it creates a new one with given kwargs. From 2517572a14bbc8471650f4f7000088dd0e075d23 Mon Sep 17 00:00:00 2001 From: Mojix Coder Date: Tue, 1 Feb 2022 10:33:29 +0330 Subject: [PATCH 3/6] Reformat project --- ormar/relations/querysetproxy.py | 2 +- tests/test_model_definition/test_save_status.py | 8 ++++++-- tests/test_queries/test_queryproxy_on_m2m_models.py | 4 +++- tests/test_queries/test_queryset_level_methods.py | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/ormar/relations/querysetproxy.py b/ormar/relations/querysetproxy.py index ec3f9fd..255839e 100644 --- a/ormar/relations/querysetproxy.py +++ b/ormar/relations/querysetproxy.py @@ -9,9 +9,9 @@ from typing import ( # noqa: I100, I201 Sequence, Set, TYPE_CHECKING, + Tuple, Type, TypeVar, - Tuple, Union, cast, ) diff --git a/tests/test_model_definition/test_save_status.py b/tests/test_model_definition/test_save_status.py index 0bbda39..b104072 100644 --- a/tests/test_model_definition/test_save_status.py +++ b/tests/test_model_definition/test_save_status.py @@ -195,11 +195,15 @@ async def test_queryset_methods(): comps = await Company.objects.all() assert [comp.saved for comp in comps] - comp2, created = await Company.objects.get_or_create(name="Banzai_new", founded=2001) + comp2, created = await Company.objects.get_or_create( + name="Banzai_new", founded=2001 + ) assert comp2.saved assert created is True - comp3, created = await Company.objects.get_or_create(name="Banzai", founded=1988) + comp3, created = await Company.objects.get_or_create( + name="Banzai", founded=1988 + ) assert comp3.saved assert comp3.pk == comp.pk assert created is False diff --git a/tests/test_queries/test_queryproxy_on_m2m_models.py b/tests/test_queries/test_queryproxy_on_m2m_models.py index 1fe8fc5..b9ac07e 100644 --- a/tests/test_queries/test_queryproxy_on_m2m_models.py +++ b/tests/test_queries/test_queryproxy_on_m2m_models.py @@ -107,7 +107,9 @@ async def test_queryset_methods(): assert len(post.categories) == 1 assert created is False - category, created = await post.categories.get_or_create(name="Breaking News") + category, created = await post.categories.get_or_create( + name="Breaking News" + ) assert category != breaking assert category.pk is not None assert len(post.categories) == 2 diff --git a/tests/test_queries/test_queryset_level_methods.py b/tests/test_queries/test_queryset_level_methods.py index ee308dc..497b40d 100644 --- a/tests/test_queries/test_queryset_level_methods.py +++ b/tests/test_queries/test_queryset_level_methods.py @@ -209,7 +209,7 @@ async def test_get_or_create_with_defaults(): assert book2.author == "Mojix" assert book2.genre == "Historic" assert await Book.objects.count() == 1 - + @pytest.mark.asyncio async def test_update_or_create(): From 4431a643d5ecc02165a7527135e7bef571017a24 Mon Sep 17 00:00:00 2001 From: Mojix Coder Date: Sun, 6 Feb 2022 19:14:17 +0330 Subject: [PATCH 4/6] Update get_or_create method and its tests --- ormar/queryset/queryset.py | 6 ++---- ormar/relations/querysetproxy.py | 6 ++---- tests/test_queries/test_queryset_level_methods.py | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 95fc97c..8bb4ce9 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -998,10 +998,8 @@ class QuerySet(Generic[T]): try: return await self.get(*args, **kwargs), False except NoMatch: - if _defaults is None: - return await self.create(**kwargs), True - else: - return await self.create(**kwargs, **_defaults), True + _defaults = _defaults or {} + return await self.create(**{**kwargs, **_defaults}), True async def update_or_create(self, **kwargs: Any) -> "T": """ diff --git a/ormar/relations/querysetproxy.py b/ormar/relations/querysetproxy.py index 255839e..4b4b9ea 100644 --- a/ormar/relations/querysetproxy.py +++ b/ormar/relations/querysetproxy.py @@ -507,10 +507,8 @@ class QuerysetProxy(Generic[T]): try: return await self.get(*args, **kwargs), False except NoMatch: - if _defaults is None: - return await self.create(**kwargs), True - else: - return await self.create(**kwargs, **_defaults), True + _defaults = _defaults or {} + return await self.create(**{**kwargs, **_defaults}), True async def update_or_create(self, **kwargs: Any) -> "T": """ diff --git a/tests/test_queries/test_queryset_level_methods.py b/tests/test_queries/test_queryset_level_methods.py index 497b40d..32e2f38 100644 --- a/tests/test_queries/test_queryset_level_methods.py +++ b/tests/test_queries/test_queryset_level_methods.py @@ -210,6 +210,20 @@ async def test_get_or_create_with_defaults(): assert book2.genre == "Historic" assert await Book.objects.count() == 1 + book, created = await Book.objects.get_or_create( + title="doesn't exist", + _defaults={"title": "overwritten", "author": "Mojix", "genre": "Historic"}, + ) + assert created is True + assert book.title == "overwritten" + + book2, created = await Book.objects.get_or_create( + title="overwritten", _defaults={"title": "doesn't work"} + ) + assert created is False + assert book2.title == "overwritten" + assert book2 == book + @pytest.mark.asyncio async def test_update_or_create(): From cd6d9c85afb65356ee4cf69e6a617906acfbe509 Mon Sep 17 00:00:00 2001 From: Mojix Coder Date: Sun, 6 Feb 2022 19:20:27 +0330 Subject: [PATCH 5/6] Lint code base using black --- tests/test_queries/test_queryset_level_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_queries/test_queryset_level_methods.py b/tests/test_queries/test_queryset_level_methods.py index 32e2f38..fa9a6fb 100644 --- a/tests/test_queries/test_queryset_level_methods.py +++ b/tests/test_queries/test_queryset_level_methods.py @@ -211,7 +211,7 @@ async def test_get_or_create_with_defaults(): assert await Book.objects.count() == 1 book, created = await Book.objects.get_or_create( - title="doesn't exist", + title="doesn't exist", _defaults={"title": "overwritten", "author": "Mojix", "genre": "Historic"}, ) assert created is True From 1b4daa2c01acda1a47503312f9d1ccb39c7c5fa5 Mon Sep 17 00:00:00 2001 From: collerek Date: Mon, 28 Mar 2022 12:57:48 +0200 Subject: [PATCH 6/6] add _default clarification in docs --- docs/queries/create.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/queries/create.md b/docs/queries/create.md index 09cc560..76b5d2e 100644 --- a/docs/queries/create.md +++ b/docs/queries/create.md @@ -63,6 +63,9 @@ Combination of create and get methods. Tries to get a row meeting the criteria and if `NoMatch` exception is raised it creates a new one with given kwargs and _defaults. +When `_defaults` dictionary is provided the values set in `_defaults` will **always** be set, including overwriting explicitly provided values. +i.e. `get_or_create(_defaults: {"title": "I win"}, title="never used")` will always use "I win" as title whether you provide your own value in kwargs or not. + ```python class Album(ormar.Model): class Meta: @@ -187,4 +190,4 @@ from other side of the relation. [models-save]: ../models/methods.md#save [models-upsert]: ../models/methods.md#upsert [models-save-related]: ../models/methods.md#save_related -[querysetproxy]: ../relations/queryset-proxy.md \ No newline at end of file +[querysetproxy]: ../relations/queryset-proxy.md