diff --git a/Makefile b/Makefile index 827a367..1e261c6 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ sync: $(INSTALL_STAMP) lint: $(INSTALL_STAMP) dist $(POETRY) run isort --profile=black --lines-after-imports=2 ./tests/ $(NAME) $(SYNC_NAME) $(POETRY) run black ./tests/ $(NAME) - $(POETRY) run flake8 --ignore=W503,E501,F401,E731,E712 ./tests/ $(NAME) $(SYNC_NAME) + $(POETRY) run flake8 --ignore=E231,E501,E712,E731,F401,W503 ./tests/ $(NAME) $(SYNC_NAME) $(POETRY) run mypy ./tests/ $(NAME) $(SYNC_NAME) --ignore-missing-imports --exclude migrate.py --exclude _compat\.py$ $(POETRY) run bandit -r $(NAME) $(SYNC_NAME) -s B608 diff --git a/aredis_om/model/model.py b/aredis_om/model/model.py index 2e45b80..42fd23a 100644 --- a/aredis_om/model/model.py +++ b/aredis_om/model/model.py @@ -11,6 +11,7 @@ AbstractSet, Any, Callable, + ClassVar, Dict, List, Mapping, @@ -32,7 +33,7 @@ from ulid import ULID from .. import redis -from .._compat import BaseModel +from .._compat import PYDANTIC_V2, BaseModel from .._compat import FieldInfo as PydanticFieldInfo from .._compat import ( ModelField, @@ -1441,13 +1442,22 @@ def outer_type_or_annotation(field): class RedisModel(BaseModel, abc.ABC, metaclass=ModelMeta): pk: Optional[str] = Field(default=None, primary_key=True) + ConfigDict: ClassVar Meta = DefaultMeta - class Config: - orm_mode = True - arbitrary_types_allowed = True - extra = "allow" + if PYDANTIC_V2: + from pydantic import ConfigDict + + model_config = ConfigDict( + from_attributes=True, arbitrary_types_allowed=True, extra="allow" + ) + else: + + class Config: + orm_mode = True + arbitrary_types_allowed = True + extra = "allow" def __init__(__pydantic_self__, **data: Any) -> None: __pydantic_self__.validate_primary_key() @@ -1657,9 +1667,6 @@ def redisearch_schema(cls): def check(self): """Run all validations.""" - from pydantic.version import VERSION as PYDANTIC_VERSION - - PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.") if not PYDANTIC_V2: *_, validation_error = validate_model(self.__class__, self.__dict__) if validation_error: @@ -1681,8 +1688,8 @@ def __init_subclass__(cls, **kwargs): for typ in (Set, Mapping, List): if isinstance(origin, type) and issubclass(origin, typ): raise RedisModelError( - f"HashModels cannot index set, list," - f" or mapping fields. Field: {name}" + f"HashModels cannot index set, list, " + f"or mapping fields. Field: {name}" ) if isinstance(field_type, type) and issubclass(field_type, RedisModel): raise RedisModelError( @@ -1702,8 +1709,8 @@ def __init_subclass__(cls, **kwargs): for typ in (Set, Mapping, List): if issubclass(origin, typ): raise RedisModelError( - f"HashModels cannot index set, list," - f" or mapping fields. Field: {name}" + f"HashModels cannot index set, list, " + f"or mapping fields. Field: {name}" ) if issubclass(outer_type, RedisModel): @@ -2008,7 +2015,9 @@ def schema_for_fields(cls): if issubclass(_type, str): redisearch_field = f"$.{name} AS {name} TAG SEPARATOR {SINGLE_VALUE_TAG_FIELD_SEPARATOR}" else: - redisearch_field = cls.schema_for_type(name, _type, field_info) + redisearch_field = cls.schema_for_type( + json_path, name, "", _type, field_info + ) schema_parts.append(redisearch_field) continue schema_parts.append( diff --git a/docker-compose.yml b/docker-compose.yml index e7b0f7d..4a3b061 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,4 @@ -version: "3.8" - +--- services: redis: image: "redis/redis-stack:latest" diff --git a/docs/models.md b/docs/models.md index 31d1c7f..79f174c 100644 --- a/docs/models.md +++ b/docs/models.md @@ -142,10 +142,11 @@ from redis_om import HashModel class Customer(HashModel): # ... Fields ... - class Config: - orm_mode = True - arbitrary_types_allowed = True - extra = "allow" + model_config = ConfigDict( + from_attributes=True, + arbitrary_types_allowed=True, + extra="allow", + ) ``` Some features may not work correctly if you change these settings. diff --git a/tests/test_hash_model.py b/tests/test_hash_model.py index 209c8a5..de4bdb8 100644 --- a/tests/test_hash_model.py +++ b/tests/test_hash_model.py @@ -900,6 +900,7 @@ class ModelWithStringDefault(HashModel): assert res.test == "None" +@py_test_mark_asyncio async def test_update_validation(): class TestUpdate(HashModel): name: str diff --git a/tests/test_json_model.py b/tests/test_json_model.py index 0c1db1d..3ad7b5d 100644 --- a/tests/test_json_model.py +++ b/tests/test_json_model.py @@ -1,6 +1,7 @@ # type: ignore import abc +import asyncio import dataclasses import datetime import decimal @@ -882,9 +883,23 @@ async def test_schema(m, key_prefix): # We need to build the key prefix because it will differ based on whether # these tests were copied into the tests_sync folder and unasynce'd. key_prefix = m.Member.make_key(m.Member._meta.primary_key_pattern.format(pk="")) - assert ( - m.Member.redisearch_schema() - == f"ON JSON PREFIX 1 {key_prefix} SCHEMA $.pk AS pk TAG SEPARATOR | $.first_name AS first_name TAG SEPARATOR | CASESENSITIVE $.last_name AS last_name TAG SEPARATOR | $.email AS email TAG SEPARATOR | $.age AS age NUMERIC $.bio AS bio TAG SEPARATOR | $.bio AS bio_fts TEXT $.address.pk AS address_pk TAG SEPARATOR | $.address.city AS address_city TAG SEPARATOR | $.address.postal_code AS address_postal_code TAG SEPARATOR | $.address.note.pk AS address_note_pk TAG SEPARATOR | $.address.note.description AS address_note_description TAG SEPARATOR | $.orders[*].pk AS orders_pk TAG SEPARATOR | $.orders[*].items[*].pk AS orders_items_pk TAG SEPARATOR | $.orders[*].items[*].name AS orders_items_name TAG SEPARATOR |" + assert m.Member.redisearch_schema() == ( + f"ON JSON PREFIX 1 {key_prefix} SCHEMA " + "$.pk AS pk TAG SEPARATOR | " + "$.first_name AS first_name TAG SEPARATOR | CASESENSITIVE " + "$.last_name AS last_name TAG SEPARATOR | " + "$.email AS email TAG SEPARATOR | " + "$.age AS age NUMERIC " + "$.bio AS bio TAG SEPARATOR | " + "$.bio AS bio_fts TEXT " + "$.address.pk AS address_pk TAG SEPARATOR | " + "$.address.city AS address_city TAG SEPARATOR | " + "$.address.postal_code AS address_postal_code TAG SEPARATOR | " + "$.address.note.pk AS address_note_pk TAG SEPARATOR | " + "$.address.note.description AS address_note_description TAG SEPARATOR | " + "$.orders[*].pk AS orders_pk TAG SEPARATOR | " + "$.orders[*].items[*].pk AS orders_items_pk TAG SEPARATOR | " + "$.orders[*].items[*].name AS orders_items_name TAG SEPARATOR |" ) @@ -1006,8 +1021,8 @@ class ModelWithStringDefault(JsonModel): assert res.test == "None" +@py_test_mark_asyncio async def test_update_validation(): - class Embedded(EmbeddedJsonModel): price: float name: str = Field(index=True) @@ -1037,6 +1052,7 @@ class TestUpdatesClass(JsonModel): assert rematerialized.age == 42 +@py_test_mark_asyncio async def test_model_with_dict(): class EmbeddedJsonModelWithDict(EmbeddedJsonModel): dict: Dict @@ -1083,6 +1099,17 @@ class Example(JsonModel): assert res.name == ex.name +@py_test_mark_asyncio +async def test_int_pk(): + class ModelWithIntPk(JsonModel): + my_id: int = Field(index=True, primary_key=True) + + await Migrator().run() + await ModelWithIntPk(my_id=42).save() + + m = await ModelWithIntPk.find(ModelWithIntPk.my_id == 42).first() + assert m.my_id == 42 + @py_test_mark_asyncio async def test_pagination(): class Test(JsonModel):