diff --git a/README.md b/README.md index a58e0b06..a1448ff3 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Most Notable Features are: ## Benchmarks +### v0. On an average PC ~16GB RAM, i7 Core ``` @@ -55,6 +56,37 @@ benchmark_bulk_insert[redis_store] 1,025.0436 (8.29) ------------------------------------------------------------------------------------------------------------------------- ``` +# v1. (with pydantic v2) + +``` +------------------------------------------------- benchmark: 22 tests ------------------------------------------------- +Name (time in us) Mean Min Max +----------------------------------------------------------------------------------------------------------------------- +benchmark_delete[redis_store-Wuthering Heights] 124.1668 (1.0) 108.9610 (1.0) 418.1310 (1.04) +benchmark_bulk_delete[redis_store] 137.7564 (1.11) 121.6380 (1.12) 470.7510 (1.17) +benchmark_select_columns_for_one_id[redis_store-book2] 166.7328 (1.34) 147.9490 (1.36) 430.3780 (1.07) +benchmark_select_columns_for_one_id[redis_store-book1] 171.0826 (1.38) 148.6430 (1.36) 426.0820 (1.06) +benchmark_select_columns_for_one_id[redis_store-book0] 171.7202 (1.38) 148.6460 (1.36) 431.3730 (1.07) +benchmark_select_columns_for_one_id[redis_store-book3] 172.1800 (1.39) 148.9410 (1.37) 471.5910 (1.17) +benchmark_select_all_for_one_id[redis_store-book1] 189.0068 (1.52) 163.5860 (1.50) 457.3090 (1.14) +benchmark_select_all_for_one_id[redis_store-book2] 188.5258 (1.52) 163.6650 (1.50) 401.7030 (1.0) +benchmark_select_all_for_one_id[redis_store-book3] 187.5434 (1.51) 165.3890 (1.52) 460.7100 (1.15) +benchmark_select_all_for_one_id[redis_store-book0] 190.3049 (1.53) 165.7280 (1.52) 459.8080 (1.14) +benchmark_select_columns_for_some_items[redis_store] 222.1405 (1.79) 198.9940 (1.83) 485.6230 (1.21) +benchmark_select_columns_paginated[redis_store] 229.5429 (1.85) 200.4560 (1.84) 494.4250 (1.23) +benchmark_select_default_paginated[redis_store] 262.3155 (2.11) 231.3960 (2.12) 568.8410 (1.42) +benchmark_select_some_items[redis_store] 270.4251 (2.18) 232.3230 (2.13) 537.2130 (1.34) +benchmark_update[redis_store-Wuthering Heights-data0] 280.6308 (2.26) 248.7310 (2.28) 676.0330 (1.68) +benchmark_select_columns[redis_store] 316.7642 (2.55) 283.6720 (2.60) 560.9610 (1.40) +benchmark_single_insert[redis_store-book2] 343.9583 (2.77) 284.1000 (2.61) 585.6200 (1.46) +benchmark_single_insert[redis_store-book1] 328.5308 (2.65) 291.8760 (2.68) 600.8130 (1.50) +benchmark_single_insert[redis_store-book3] 341.0249 (2.75) 292.2800 (2.68) 575.1020 (1.43) +benchmark_single_insert[redis_store-book0] 349.9540 (2.82) 299.6660 (2.75) 606.0370 (1.51) +benchmark_select_default[redis_store] 381.0231 (3.07) 346.2910 (3.18) 669.7460 (1.67) +benchmark_bulk_insert[redis_store] 840.4876 (6.77) 790.2340 (7.25) 1,049.8260 (2.61) +----------------------------------------------------------------------------------------------------------------------- +``` + ## Contributions Contributions are welcome. The docs have to maintained, the code has to be made cleaner, more idiomatic and faster, diff --git a/pydantic_redis/_shared/model/base.py b/pydantic_redis/_shared/model/base.py index 1ce487bb..cb68a858 100644 --- a/pydantic_redis/_shared/model/base.py +++ b/pydantic_redis/_shared/model/base.py @@ -4,7 +4,8 @@ import typing from typing import Dict, Tuple, Any, Type, Union, List, Optional -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel +from pydantic.fields import ModelPrivateAttr from pydantic_redis._shared.utils import ( typing_get_origin, @@ -45,9 +46,7 @@ class AbstractModel(BaseModel): _nested_model_tuple_fields: Dict[str, Tuple[Any, ...]] = {} _nested_model_list_fields: Dict[str, Type["AbstractModel"]] = {} _nested_model_fields: Dict[str, Type["AbstractModel"]] = {} - - class Config: - arbitrary_types_allowed = True + model_config = ConfigDict(arbitrary_types_allowed=True) @classmethod def get_store(cls) -> AbstractStore: @@ -93,7 +92,10 @@ def get_primary_key_field(cls): Returns: the field that can be used to uniquely identify each record of current Model """ - return cls._primary_key_field + try: + return cls._primary_key_field.get_default() + except AttributeError: + return cls._primary_key_field @classmethod def get_field_types(cls) -> Dict[str, Any]: diff --git a/pydantic_redis/_shared/store.py b/pydantic_redis/_shared/store.py index 679cae85..5ecd7772 100644 --- a/pydantic_redis/_shared/store.py +++ b/pydantic_redis/_shared/store.py @@ -3,9 +3,10 @@ """ from typing import Optional, Union, Type, Dict, Any +from pydantic.fields import ModelPrivateAttr from redis import Redis from redis.asyncio import Redis as AioRedis -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel from redis.commands.core import Script, AsyncScript from ..config import RedisConfig @@ -45,10 +46,7 @@ class AbstractStore(BaseModel): ] = None select_some_fields_for_some_ids_script: Optional[Union[AsyncScript, Script]] = None models: Dict[str, Type["AbstractModel"]] = {} - - class Config: - arbitrary_types_allowed = True - orm_mode = True + model_config = ConfigDict(arbitrary_types_allowed=True, from_attributes=True) def __init__( self, @@ -122,7 +120,7 @@ def register_model(self, model_class: Type["AbstractModel"]): a certain type of records to be saved in redis. """ if not isinstance(model_class.get_primary_key_field(), str): - raise NotImplementedError( + raise AttributeError( f"{model_class.__name__} should have a _primary_key_field" ) diff --git a/pydantic_redis/_shared/utils.py b/pydantic_redis/_shared/utils.py index 5ecc2130..246cfcc2 100644 --- a/pydantic_redis/_shared/utils.py +++ b/pydantic_redis/_shared/utils.py @@ -2,7 +2,7 @@ """ import typing -from typing import Any, Tuple, Optional, Union, Dict, Callable, Type, List +from typing import Any, Tuple, Optional, Union, Dict, Type, List import orjson @@ -63,15 +63,16 @@ def from_bytes_to_str(value: Union[str, bytes]) -> str: Returns: the string value of the argument passed """ - if isinstance(value, bytes): + try: return str(value, "utf-8") - return value + except TypeError: + return value def from_str_or_bytes_to_any(value: Any, field_type: Type) -> Any: """Converts str or bytes to arbitrary data. - Converts the the `value` from a string or bytes to the `field_type`. + Converts the `value` from a string or bytes to the `field_type`. Args: value: the string or bytes to be transformed to the `field_type` @@ -116,9 +117,10 @@ def default_json_dump(obj: Any): Returns: the bytes or string value of the object """ - if hasattr(obj, "json") and isinstance(obj.json, Callable): - return obj.json() - return obj + try: + return obj.model_dump_json() + except AttributeError: + return obj def from_dict_to_key_value_list(data: Dict[str, Any]) -> List[Any]: diff --git a/pydantic_redis/asyncio/model.py b/pydantic_redis/asyncio/model.py index 7edcd71c..62aa5dde 100644 --- a/pydantic_redis/asyncio/model.py +++ b/pydantic_redis/asyncio/model.py @@ -184,3 +184,6 @@ async def select( return parse_select_response( model=cls, response=response, as_models=(columns is None) ) + + +Store.model_rebuild() diff --git a/pydantic_redis/config.py b/pydantic_redis/config.py index b9c88676..a64b400e 100644 --- a/pydantic_redis/config.py +++ b/pydantic_redis/config.py @@ -2,7 +2,7 @@ """ from typing import Optional -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel class RedisConfig(BaseModel): @@ -24,6 +24,8 @@ class RedisConfig(BaseModel): (default: utf-8) """ + model_config = ConfigDict(from_attributes=True) + host: str = "localhost" port: int = 6379 db: int = 0 @@ -38,6 +40,3 @@ def redis_url(self) -> str: if self.password is None: return f"{proto}://{self.host}:{self.port}/{self.db}" return f"{proto}://:{self.password}@{self.host}:{self.port}/{self.db}" - - class Config: - orm_mode = True diff --git a/pydantic_redis/syncio/model.py b/pydantic_redis/syncio/model.py index f525d91d..d8ff5833 100644 --- a/pydantic_redis/syncio/model.py +++ b/pydantic_redis/syncio/model.py @@ -180,3 +180,6 @@ def select( return parse_select_response( model=cls, response=response, as_models=(columns is None) ) + + +Store.model_rebuild() diff --git a/requirements.txt b/requirements.txt index d023eccf..0d9b6c4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ orjson==3.6.1 hiredis==2.0.0 -pydantic==1.9.2 +pydantic==2.6.1 pytest==7.0.1 pytest-benchmark==3.4.1 pytest-lazy-fixture==0.6.3 diff --git a/test/conftest.py b/test/conftest.py index 80fc08bb..f0f0f9c5 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -50,10 +50,10 @@ class Library(syn.Model): _primary_key_field: str = "name" name: str address: str - books: List[Book] = None + books: List[Book] = [] lost: Optional[List[Book]] = None popular: Optional[Tuple[Book, Book]] = None - new: Tuple[Book, Author, Book, int] = None + new: Optional[Tuple[Book, Author, Book, int]] = None class AsyncLibrary(asy.Model): @@ -61,10 +61,10 @@ class AsyncLibrary(asy.Model): _primary_key_field: str = "name" name: str address: str - books: List[AsyncBook] = None + books: List[AsyncBook] = [] lost: Optional[List[AsyncBook]] = None popular: Optional[Tuple[AsyncBook, AsyncBook]] = None - new: Tuple[AsyncBook, AsyncAuthor, AsyncBook, int] = None + new: Optional[Tuple[AsyncBook, AsyncAuthor, AsyncBook, int]] = None authors = { @@ -113,7 +113,7 @@ class AsyncLibrary(asy.Model): async_books = [ AsyncBook( title="Oliver Twist", - author=authors["charles"], + author=async_authors["charles"], published_on=date(year=1215, month=4, day=4), in_stock=False, rating=2, @@ -121,14 +121,14 @@ class AsyncLibrary(asy.Model): ), AsyncBook( title="Great Expectations", - author=authors["charles"], + author=async_authors["charles"], published_on=date(year=1220, month=4, day=4), rating=5, tags=["Classic"], ), AsyncBook( title="Jane Eyre", - author=authors["charles"], + author=async_authors["charles"], published_on=date(year=1225, month=6, day=4), in_stock=False, rating=3.4, @@ -136,7 +136,7 @@ class AsyncLibrary(asy.Model): ), AsyncBook( title="Wuthering Heights", - author=authors["jane"], + author=async_authors["jane"], published_on=date(year=1600, month=4, day=4), rating=4.0, tags=["Classic", "Romance"], diff --git a/test/test_async_pydantic_redis.py b/test/test_async_pydantic_redis.py index 0d8e3a1e..f7191bf5 100644 --- a/test/test_async_pydantic_redis.py +++ b/test/test_async_pydantic_redis.py @@ -6,7 +6,7 @@ from pydantic_redis._shared.model.prop_utils import NESTED_MODEL_PREFIX # noqa from pydantic_redis._shared.utils import strip_leading # noqa -from pydantic_redis.asyncio import Model, RedisConfig +from pydantic_redis.asyncio import Model, RedisConfig, Store from test.conftest import ( async_redis_store_fixture, AsyncBook, @@ -36,12 +36,12 @@ def test_register_model_without_primary_key(async_redis_store): class ModelWithoutPrimaryKey(Model): title: str - with pytest.raises(AttributeError, match=r"_primary_key_field"): + with pytest.raises(AttributeError, match=r"should have a _primary_key_field"): async_redis_store.register_model(ModelWithoutPrimaryKey) ModelWithoutPrimaryKey._primary_key_field = None - with pytest.raises(Exception, match=r"should have a _primary_key_field"): + with pytest.raises(AttributeError, match=r"should have a _primary_key_field"): async_redis_store.register_model(ModelWithoutPrimaryKey) @@ -55,7 +55,7 @@ def test_store_model(async_redis_store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_bulk_insert(store): +async def test_bulk_insert(store: Store): """Providing a list of Model instances to the insert method inserts the records in redis""" book_keys = [f"asyncbook_%&_{book.title}" for book in async_books] keys = book_keys + [ @@ -82,7 +82,7 @@ async def test_bulk_insert(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_bulk_nested_insert(store): +async def test_bulk_nested_insert(store: Store): """Providing a list of Model instances to the insert method also upserts their nested records in redis""" book_keys = [f"asyncbook_%&_{book.title}" for book in async_books] author_keys = [f"asyncauthor_%&_{author.name}" for author in async_authors.values()] @@ -111,7 +111,7 @@ async def test_bulk_nested_insert(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_insert_single(store): +async def test_insert_single(store: Store): """ Providing a single Model instance inserts that record in redis """ @@ -128,7 +128,7 @@ async def test_insert_single(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_insert_single_nested(store): +async def test_insert_single_nested(store: Store): """ Providing a single Model instance upserts also any nested model into redis """ @@ -145,7 +145,7 @@ async def test_insert_single_nested(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_update_nested_list_of_models(store): +async def test_update_nested_list_of_models(store: Store): data = [ AsyncLibrary(name="Babel AsyncLibrary", address="In a book", books=async_books) ] @@ -162,7 +162,7 @@ async def test_update_nested_list_of_models(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_update_optional_nested_list_of_models(store): +async def test_update_optional_nested_list_of_models(store: Store): data = [ AsyncLibrary(name="Babel AsyncLibrary", address="In a book", lost=async_books) ] @@ -179,7 +179,7 @@ async def test_update_optional_nested_list_of_models(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_update_nested_tuple_of_models(store): +async def test_update_nested_tuple_of_models(store: Store): jane = async_authors["jane"] new_stuff = (async_books[0], jane, async_books[1], 8) data = [AsyncLibrary(name="Babel AsyncLibrary", address="In a book", new=new_stuff)] @@ -200,7 +200,7 @@ async def test_update_nested_tuple_of_models(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_update_optional_nested_tuple_of_models(store): +async def test_update_optional_nested_tuple_of_models(store: Store): popular_async_books = (async_books[0], async_books[2]) data = [ AsyncLibrary( @@ -220,7 +220,7 @@ async def test_update_optional_nested_tuple_of_models(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_select_default(store): +async def test_select_default(store: Store): """Selecting without arguments returns all the book models""" await AsyncBook.insert(async_books) response = await AsyncBook.select() @@ -231,7 +231,7 @@ async def test_select_default(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_select_default_paginated(store): +async def test_select_default_paginated(store: Store): """ Selecting without arguments returns the book models after skipping `skip` number of models and returning upto `limit` number of items @@ -253,7 +253,7 @@ async def test_select_default_paginated(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_select_some_columns(store): +async def test_select_some_columns(store: Store): """ Selecting some columns returns a list of dictionaries of all async_books models with only those columns """ @@ -277,7 +277,7 @@ async def test_select_some_columns(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_select_some_columns_paginated(store): +async def test_select_some_columns_paginated(store: Store): """ Selecting some columns returns a list of dictionaries of all books models with only those columns skipping `skip` number of models and returning upto `limit` number of items @@ -315,7 +315,7 @@ async def test_select_some_columns_paginated(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_select_some_ids(store): +async def test_select_some_ids(store: Store): """ Selecting some ids returns only those elements with the given ids """ @@ -327,7 +327,7 @@ async def test_select_some_ids(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_update(store): +async def test_update(store: Store): """ Updating an item of a given primary key updates it in redis """ @@ -359,14 +359,14 @@ async def test_update(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_update_nested_model(store): +async def test_update_nested_model(store: Store): """ Updating a nested model, without changing its primary key, also updates it its collection in redis """ await AsyncBook.insert(async_books) new_in_stock = not async_books[0].in_stock - updated_author = AsyncAuthor(**async_books[0].author.dict()) + updated_author = AsyncAuthor(**async_books[0].author.model_dump()) updated_author.active_years = (2020, 2045) book_key = f"asyncbook_%&_{async_books[0].title}" author_key = f"asyncauthor_%&_{updated_author.name}" @@ -397,7 +397,7 @@ async def test_update_nested_model(store): @pytest.mark.asyncio @pytest.mark.parametrize("store", async_redis_store_fixture) -async def test_delete_multiple(store): +async def test_delete_multiple(store: Store): """ Providing a list of ids to the delete function will remove the items from redis, but leave the nested models intact diff --git a/test/test_pydantic_redis.py b/test/test_pydantic_redis.py index af5eec7f..d0bd9a34 100644 --- a/test/test_pydantic_redis.py +++ b/test/test_pydantic_redis.py @@ -4,6 +4,7 @@ import pytest +from pydantic_redis import Store from pydantic_redis.config import RedisConfig # noqa from pydantic_redis._shared.model.prop_utils import NESTED_MODEL_PREFIX # noqa from pydantic_redis._shared.utils import strip_leading # noqa @@ -37,12 +38,12 @@ def test_register_model_without_primary_key(redis_store): class ModelWithoutPrimaryKey(Model): title: str - with pytest.raises(AttributeError, match=r"_primary_key_field"): + with pytest.raises(AttributeError, match=r"should have a _primary_key_field"): redis_store.register_model(ModelWithoutPrimaryKey) ModelWithoutPrimaryKey._primary_key_field = None - with pytest.raises(Exception, match=r"should have a _primary_key_field"): + with pytest.raises(AttributeError, match=r"should have a _primary_key_field"): redis_store.register_model(ModelWithoutPrimaryKey) @@ -55,7 +56,7 @@ def test_store_model(redis_store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_bulk_insert(store): +def test_bulk_insert(store: Store): """Providing a list of Model instances to the insert method inserts the records in redis""" book_keys = [f"book_%&_{book.title}" for book in books] keys = book_keys + [f"author_%&_{author.name}" for author in authors.values()] @@ -78,7 +79,7 @@ def test_bulk_insert(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_bulk_nested_insert(store): +def test_bulk_nested_insert(store: Store): """Providing a list of Model instances to the insert method also upserts their nested records in redis""" book_keys = [f"book_%&_{book.title}" for book in books] author_keys = [f"author_%&_{author.name}" for author in authors.values()] @@ -103,7 +104,7 @@ def test_bulk_nested_insert(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_insert_single(store): +def test_insert_single(store: Store): """ Providing a single Model instance inserts that record in redis """ @@ -119,7 +120,7 @@ def test_insert_single(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_insert_single_nested(store): +def test_insert_single_nested(store: Store): """ Providing a single Model instance upserts also any nested model into redis """ @@ -135,7 +136,7 @@ def test_insert_single_nested(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_update_nested_list_of_models(store): +def test_update_nested_list_of_models(store: Store): data = [Library(name="Babel Library", address="In a book", books=books)] Library.insert(data) # the list of nested models is automatically inserted @@ -149,7 +150,7 @@ def test_update_nested_list_of_models(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_update_optional_nested_list_of_models(store): +def test_update_optional_nested_list_of_models(store: Store): data = [Library(name="Babel Library", address="In a book", lost=books)] Library.insert(data) # the list of nested models is automatically inserted @@ -163,7 +164,7 @@ def test_update_optional_nested_list_of_models(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_update_nested_tuple_of_models(store): +def test_update_nested_tuple_of_models(store: Store): jane = authors["jane"] new_stuff = (books[0], jane, books[1], 8) data = [Library(name="Babel Library", address="In a book", new=new_stuff)] @@ -183,7 +184,7 @@ def test_update_nested_tuple_of_models(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_update_optional_nested_tuple_of_models(store): +def test_update_optional_nested_tuple_of_models(store: Store): popular_books = (books[0], books[2]) data = [Library(name="Babel Library", address="In a book", popular=popular_books)] Library.insert(data) @@ -198,7 +199,7 @@ def test_update_optional_nested_tuple_of_models(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_select_default(store): +def test_select_default(store: Store): """Selecting without arguments returns all the book models""" Book.insert(books) response = Book.select() @@ -208,7 +209,7 @@ def test_select_default(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_select_default_paginated(store): +def test_select_default_paginated(store: Store): """ Selecting without arguments returns the book models after skipping `skip` number of models and returning upto `limit` number of items @@ -229,7 +230,7 @@ def test_select_default_paginated(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_select_some_columns(store): +def test_select_some_columns(store: Store): """ Selecting some columns returns a list of dictionaries of all books models with only those columns """ @@ -250,7 +251,7 @@ def test_select_some_columns(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_select_some_columns_paginated(store): +def test_select_some_columns_paginated(store: Store): """ Selecting some columns returns a list of dictionaries of all books models with only those columns skipping `skip` number of models and returning upto `limit` number of items @@ -282,7 +283,7 @@ def test_select_some_columns_paginated(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_select_some_ids(store): +def test_select_some_ids(store: Store): """ Selecting some ids returns only those elements with the given ids """ @@ -293,7 +294,7 @@ def test_select_some_ids(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_select_some_columns_for_some_ids(store): +def test_select_some_columns_for_some_ids(store: Store): """ Selecting some columns for some ids returns only dicts for the given ids with only the given columns """ @@ -315,7 +316,7 @@ def test_select_some_columns_for_some_ids(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_update(store): +def test_update(store: Store): """ Updating an item of a given primary key updates it in redis """ @@ -344,14 +345,14 @@ def test_update(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_update_nested_model(store): +def test_update_nested_model(store: Store): """ Updating a nested model, without changing its primary key, also updates it its collection in redis """ Book.insert(books) new_in_stock = not books[0].in_stock - updated_author = Author(**books[0].author.dict()) + updated_author = Author(**books[0].author.model_dump()) updated_author.active_years = (2020, 2045) book_key = f"book_%&_{books[0].title}" author_key = f"author_%&_{updated_author.name}" @@ -380,7 +381,7 @@ def test_update_nested_model(store): @pytest.mark.parametrize("store", redis_store_fixture) -def test_delete_multiple(store): +def test_delete_multiple(store: Store): """ Providing a list of ids to the delete function will remove the items from redis, but leave the nested models intact