Skip to content

Commit 43b3455

Browse files
authored
Merge branch 'main' into dependabot/pip/unasync-tw-0.6.0
2 parents 776662c + e0d62fb commit 43b3455

File tree

9 files changed

+670
-61
lines changed

9 files changed

+670
-61
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ sync: $(INSTALL_STAMP)
5353
lint: $(INSTALL_STAMP) dist
5454
$(POETRY) run isort --profile=black --lines-after-imports=2 ./tests/ $(NAME) $(SYNC_NAME)
5555
$(POETRY) run black ./tests/ $(NAME)
56-
$(POETRY) run flake8 --ignore=W503,E501,F401,E731,E712 ./tests/ $(NAME) $(SYNC_NAME)
56+
$(POETRY) run flake8 --ignore=E231,E501,E712,E731,F401,W503 ./tests/ $(NAME) $(SYNC_NAME)
5757
$(POETRY) run mypy ./tests/ $(NAME) $(SYNC_NAME) --ignore-missing-imports --exclude migrate.py --exclude _compat\.py$
5858
$(POETRY) run bandit -r $(NAME) $(SYNC_NAME) -s B608
5959

aredis_om/model/encoders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def jsonable_encoder(
9090
sqlalchemy_safe=sqlalchemy_safe,
9191
)
9292
if dataclasses.is_dataclass(obj):
93-
return dataclasses.asdict(obj)
93+
return dataclasses.asdict(obj) # type: ignore[call-overload]
9494
if isinstance(obj, Enum):
9595
return obj.value
9696
if isinstance(obj, PurePath):

aredis_om/model/model.py

Lines changed: 66 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
AbstractSet,
1212
Any,
1313
Callable,
14+
ClassVar,
1415
Dict,
1516
List,
17+
Literal,
1618
Mapping,
1719
Optional,
1820
Sequence,
@@ -32,7 +34,7 @@
3234
from ulid import ULID
3335

3436
from .. import redis
35-
from .._compat import BaseModel
37+
from .._compat import PYDANTIC_V2, BaseModel
3638
from .._compat import FieldInfo as PydanticFieldInfo
3739
from .._compat import (
3840
ModelField,
@@ -140,10 +142,10 @@ def embedded(cls):
140142

141143
def is_supported_container_type(typ: Optional[type]) -> bool:
142144
# TODO: Wait, why don't we support indexing sets?
143-
if typ == list or typ == tuple:
145+
if typ == list or typ == tuple or typ == Literal:
144146
return True
145147
unwrapped = get_origin(typ)
146-
return unwrapped == list or unwrapped == tuple
148+
return unwrapped == list or unwrapped == tuple or unwrapped == Literal
147149

148150

149151
def validate_model_fields(model: Type["RedisModel"], field_values: Dict[str, Any]):
@@ -871,7 +873,9 @@ def resolve_redisearch_query(cls, expression: ExpressionOrNegated) -> str:
871873

872874
return result
873875

874-
async def execute(self, exhaust_results=True, return_raw_result=False):
876+
async def execute(
877+
self, exhaust_results=True, return_raw_result=False, return_query_args=False
878+
):
875879
args: List[Union[str, bytes]] = [
876880
"FT.SEARCH",
877881
self.model.Meta.index_name,
@@ -896,6 +900,9 @@ async def execute(self, exhaust_results=True, return_raw_result=False):
896900
if self.nocontent:
897901
args.append("NOCONTENT")
898902

903+
if return_query_args:
904+
return self.model.Meta.index_name, args
905+
899906
# Reset the cache if we're executing from offset 0.
900907
if self.offset == 0:
901908
self._model_cache.clear()
@@ -929,6 +936,10 @@ async def execute(self, exhaust_results=True, return_raw_result=False):
929936
self._model_cache += _results
930937
return self._model_cache
931938

939+
async def get_query(self):
940+
query = self.copy()
941+
return await query.execute(return_query_args=True)
942+
932943
async def first(self):
933944
query = self.copy(offset=0, limit=1, sort_fields=self.sort_fields)
934945
results = await query.execute(exhaust_results=False)
@@ -948,7 +959,9 @@ async def all(self, batch_size=DEFAULT_PAGE_SIZE):
948959
return await self.execute()
949960

950961
async def page(self, offset=0, limit=10):
951-
return await self.copy(offset=offset, limit=limit).execute()
962+
return await self.copy(offset=offset, limit=limit).execute(
963+
exhaust_results=False
964+
)
952965

953966
def sort_by(self, *fields: str):
954967
if not fields:
@@ -1411,19 +1424,30 @@ def outer_type_or_annotation(field):
14111424
if not isinstance(field.annotation, type):
14121425
raise AttributeError(f"could not extract outer type from field {field}")
14131426
return field.annotation
1427+
elif get_origin(field.annotation) == Literal:
1428+
return str
14141429
else:
14151430
return field.annotation.__args__[0]
14161431

14171432

14181433
class RedisModel(BaseModel, abc.ABC, metaclass=ModelMeta):
14191434
pk: Optional[str] = Field(default=None, primary_key=True)
1435+
ConfigDict: ClassVar
14201436

14211437
Meta = DefaultMeta
14221438

1423-
class Config:
1424-
orm_mode = True
1425-
arbitrary_types_allowed = True
1426-
extra = "allow"
1439+
if PYDANTIC_V2:
1440+
from pydantic import ConfigDict
1441+
1442+
model_config = ConfigDict(
1443+
from_attributes=True, arbitrary_types_allowed=True, extra="allow"
1444+
)
1445+
else:
1446+
1447+
class Config:
1448+
orm_mode = True
1449+
arbitrary_types_allowed = True
1450+
extra = "allow"
14271451

14281452
def __init__(__pydantic_self__, **data: Any) -> None:
14291453
__pydantic_self__.validate_primary_key()
@@ -1631,9 +1655,6 @@ def redisearch_schema(cls):
16311655

16321656
def check(self):
16331657
"""Run all validations."""
1634-
from pydantic.version import VERSION as PYDANTIC_VERSION
1635-
1636-
PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
16371658
if not PYDANTIC_V2:
16381659
*_, validation_error = validate_model(self.__class__, self.__dict__)
16391660
if validation_error:
@@ -1655,8 +1676,8 @@ def __init_subclass__(cls, **kwargs):
16551676
for typ in (Set, Mapping, List):
16561677
if isinstance(origin, type) and issubclass(origin, typ):
16571678
raise RedisModelError(
1658-
f"HashModels cannot index set, list,"
1659-
f" or mapping fields. Field: {name}"
1679+
f"HashModels cannot index set, list, "
1680+
f"or mapping fields. Field: {name}"
16601681
)
16611682
if isinstance(field_type, type) and issubclass(field_type, RedisModel):
16621683
raise RedisModelError(
@@ -1676,8 +1697,8 @@ def __init_subclass__(cls, **kwargs):
16761697
for typ in (Set, Mapping, List):
16771698
if issubclass(origin, typ):
16781699
raise RedisModelError(
1679-
f"HashModels cannot index set, list,"
1680-
f" or mapping fields. Field: {name}"
1700+
f"HashModels cannot index set, list, "
1701+
f"or mapping fields. Field: {name}"
16811702
)
16821703

16831704
if issubclass(outer_type, RedisModel):
@@ -1982,7 +2003,9 @@ def schema_for_fields(cls):
19822003
if issubclass(_type, str):
19832004
redisearch_field = f"$.{name} AS {name} TAG SEPARATOR {SINGLE_VALUE_TAG_FIELD_SEPARATOR}"
19842005
else:
1985-
redisearch_field = cls.schema_for_type(name, _type, field_info)
2006+
redisearch_field = cls.schema_for_type(
2007+
json_path, name, "", _type, field_info
2008+
)
19862009
schema_parts.append(redisearch_field)
19872010
continue
19882011
schema_parts.append(
@@ -2046,21 +2069,33 @@ def schema_for_type(
20462069
# find any values marked as indexed.
20472070
if is_container_type and not is_vector:
20482071
field_type = get_origin(typ)
2049-
embedded_cls = get_args(typ)
2050-
if not embedded_cls:
2051-
log.warning(
2052-
"Model %s defined an empty list or tuple field: %s", cls, name
2072+
if field_type == Literal:
2073+
path = f"{json_path}.{name}"
2074+
return cls.schema_for_type(
2075+
path,
2076+
name,
2077+
name_prefix,
2078+
str,
2079+
field_info,
2080+
parent_type=field_type,
2081+
)
2082+
else:
2083+
embedded_cls = get_args(typ)
2084+
if not embedded_cls:
2085+
log.warning(
2086+
"Model %s defined an empty list or tuple field: %s", cls, name
2087+
)
2088+
return ""
2089+
path = f"{json_path}.{name}[*]"
2090+
embedded_cls = embedded_cls[0]
2091+
return cls.schema_for_type(
2092+
path,
2093+
name,
2094+
name_prefix,
2095+
embedded_cls,
2096+
field_info,
2097+
parent_type=field_type,
20532098
)
2054-
return ""
2055-
embedded_cls = embedded_cls[0]
2056-
return cls.schema_for_type(
2057-
f"{json_path}.{name}[*]",
2058-
name,
2059-
name_prefix,
2060-
embedded_cls,
2061-
field_info,
2062-
parent_type=field_type,
2063-
)
20642099
elif field_is_model:
20652100
name_prefix = f"{name_prefix}_{name}" if name_prefix else name
20662101
sub_fields = []

docker-compose.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
version: "3.8"
2-
1+
---
32
services:
43
redis:
54
image: "redis/redis-stack:latest"

docs/models.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,11 @@ from redis_om import HashModel
142142
class Customer(HashModel):
143143
# ... Fields ...
144144

145-
class Config:
146-
orm_mode = True
147-
arbitrary_types_allowed = True
148-
extra = "allow"
145+
model_config = ConfigDict(
146+
from_attributes=True,
147+
arbitrary_types_allowed=True,
148+
extra="allow",
149+
)
149150
```
150151

151152
Some features may not work correctly if you change these settings.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ python-ulid = "^1.0.3"
4444
typing-extensions = "^4.4.0"
4545
hiredis = "^2.2.3"
4646
more-itertools = ">=8.14,<11.0"
47-
setuptools = {version = "^69.2.0", markers = "python_version >= '3.12'"}
47+
setuptools = {version = ">=69.2,<73.0", markers = "python_version >= '3.12'"}
4848

4949
[tool.poetry.dev-dependencies]
5050
mypy = "^1.9.0"

0 commit comments

Comments
 (0)