From d96fb276e2efff3ddfda2f97b759ffb1baa71a7d Mon Sep 17 00:00:00 2001 From: Amin Alaee Date: Thu, 23 Sep 2021 09:35:59 +0330 Subject: [PATCH 1/3] add update-or-create method --- docs/making_queries.md | 26 +++++++++++++++++++++++++- orm/models.py | 17 ++++++++++++++++- tests/test_models.py | 30 ++++++++++++++++++++++++++---- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/docs/making_queries.md b/docs/making_queries.md index c0d8666..ab6093f 100644 --- a/docs/making_queries.md +++ b/docs/making_queries.md @@ -210,8 +210,32 @@ To get an existing instance matching the query, or create a new one. This will retuurn a tuple of `instance` and `created`. ```python -note, created = await Note.objects.get_or_create(text="Going to car wash") +note, created = await Note.objects.get_or_create( + text="Going to car wash", defaults={"completed": False} +) ``` +This will query a `Note` with `text` as `"Going to car wash"`, +if it doesn't exist, it will use `defaults` argument to create the new instance. + !!! note Since `get_or_create()` is doing a [get()](#get), it can raise `MultipleMatches` exception. + + +### .update_or_create() + +To update an existing instance matching the query, or create a new one. +This will retuurn a tuple of `instance` and `created`. + +```python +note, created = await Note.objects.update_or_create( + text="Going to car wash", defaults={"completed": True} +) +``` + +This will query a `Note` with `text` as `"Going to car wash"`, +if an instance is found, it will use the `defaults` argument to update the instance. +If it matches no records, it will use the comibnation of arguments to create the new instance. + +!!! note + Since `update_or_create()` is doing a [get()](#get), it can raise `MultipleMatches` exception. diff --git a/orm/models.py b/orm/models.py index b63ef29..aec400e 100644 --- a/orm/models.py +++ b/orm/models.py @@ -425,11 +425,26 @@ async def update(self, **kwargs) -> None: await self.database.execute(expr) - async def get_or_create(self, **kwargs) -> typing.Tuple[typing.Any, bool]: + async def get_or_create( + self, defaults: typing.Dict[str, typing.Any], **kwargs + ) -> typing.Tuple[typing.Any, bool]: try: instance = await self.get(**kwargs) return instance, False except NoMatch: + kwargs.update(defaults) + instance = await self.create(**kwargs) + return instance, True + + async def update_or_create( + self, defaults: typing.Dict[str, typing.Any], **kwargs + ) -> typing.Tuple[typing.Any, bool]: + try: + instance = await self.get(**kwargs) + await instance.update(**defaults) + return instance, False + except NoMatch: + kwargs.update(defaults) instance = await self.create(**kwargs) return instance, True diff --git a/tests/test_models.py b/tests/test_models.py index 6dab6e6..4f3a6e7 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -254,13 +254,19 @@ async def test_model_search(): async def test_model_get_or_create(): - user, created = await User.objects.get_or_create(name="Tom") - + user, created = await User.objects.get_or_create( + name="Tom", defaults={"language": "Spanish"} + ) assert created is True - assert await User.objects.get(pk=user.id) == user + assert user.name == "Tom" + assert user.language == "Spanish" - user, created = await User.objects.get_or_create(name="Tom") + user, created = await User.objects.get_or_create( + name="Tom", defaults={"language": "English"} + ) assert created is False + assert user.name == "Tom" + assert user.language == "Spanish" async def test_queryset_delete(): @@ -287,3 +293,19 @@ async def test_queryset_update(): await Product.objects.update(rating=3) tie = await Product.objects.get(pk=tie.id) assert tie.rating == 3 + + +async def test_model_update_or_create(): + user, created = await User.objects.update_or_create( + name="Tom", language="English", defaults={"name": "Jane"} + ) + assert created is True + assert user.name == "Jane" + assert user.language == "English" + + user, created = await User.objects.update_or_create( + name="Jane", language="English", defaults={"name": "Tom"} + ) + assert created is False + assert user.name == "Tom" + assert user.language == "English" From 81ad0f45f19b4261fdc145cb61b4218e37881749 Mon Sep 17 00:00:00 2001 From: Amin Alaee Date: Fri, 24 Sep 2021 12:10:34 +0330 Subject: [PATCH 2/3] update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 629a8d1..c26d4ba 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def get_packages(package): packages=get_packages(PACKAGE), package_data={PACKAGE: ["py.typed"]}, data_files=[("", ["LICENSE.md"])], - install_requires=["anyio>=3.0.0,<4", "databases>=0.5.0", "typesystem>=0.3.0"], + install_requires=["anyio~=3", "databases~=0.5", "typesystem~=0.3"], extras_require={ "postgresql": ["asyncpg"], "mysql": ["aiomysql"], From d9409ad0b54911f1ef1dc3a5a88cea92ae4e6a10 Mon Sep 17 00:00:00 2001 From: Amin Alaee Date: Fri, 24 Sep 2021 13:08:58 +0330 Subject: [PATCH 3/3] Revert anyio --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c26d4ba..014e814 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ def get_packages(package): packages=get_packages(PACKAGE), package_data={PACKAGE: ["py.typed"]}, data_files=[("", ["LICENSE.md"])], - install_requires=["anyio~=3", "databases~=0.5", "typesystem~=0.3"], + install_requires=["anyio>=3.0.0,<4", "databases~=0.5", "typesystem~=0.3"], extras_require={ "postgresql": ["asyncpg"], "mysql": ["aiomysql"],