Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/pip/unasync-tw-0.6.0
Browse files Browse the repository at this point in the history
  • Loading branch information
slorello89 committed Aug 6, 2024
2 parents 776662c + e0d62fb commit 43b3455
Show file tree
Hide file tree
Showing 9 changed files with 670 additions and 61 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion aredis_om/model/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def jsonable_encoder(
sqlalchemy_safe=sqlalchemy_safe,
)
if dataclasses.is_dataclass(obj):
return dataclasses.asdict(obj)
return dataclasses.asdict(obj) # type: ignore[call-overload]
if isinstance(obj, Enum):
return obj.value
if isinstance(obj, PurePath):
Expand Down
97 changes: 66 additions & 31 deletions aredis_om/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
AbstractSet,
Any,
Callable,
ClassVar,
Dict,
List,
Literal,
Mapping,
Optional,
Sequence,
Expand All @@ -32,7 +34,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,
Expand Down Expand Up @@ -140,10 +142,10 @@ def embedded(cls):

def is_supported_container_type(typ: Optional[type]) -> bool:
# TODO: Wait, why don't we support indexing sets?
if typ == list or typ == tuple:
if typ == list or typ == tuple or typ == Literal:
return True
unwrapped = get_origin(typ)
return unwrapped == list or unwrapped == tuple
return unwrapped == list or unwrapped == tuple or unwrapped == Literal


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

return result

async def execute(self, exhaust_results=True, return_raw_result=False):
async def execute(
self, exhaust_results=True, return_raw_result=False, return_query_args=False
):
args: List[Union[str, bytes]] = [
"FT.SEARCH",
self.model.Meta.index_name,
Expand All @@ -896,6 +900,9 @@ async def execute(self, exhaust_results=True, return_raw_result=False):
if self.nocontent:
args.append("NOCONTENT")

if return_query_args:
return self.model.Meta.index_name, args

# Reset the cache if we're executing from offset 0.
if self.offset == 0:
self._model_cache.clear()
Expand Down Expand Up @@ -929,6 +936,10 @@ async def execute(self, exhaust_results=True, return_raw_result=False):
self._model_cache += _results
return self._model_cache

async def get_query(self):
query = self.copy()
return await query.execute(return_query_args=True)

async def first(self):
query = self.copy(offset=0, limit=1, sort_fields=self.sort_fields)
results = await query.execute(exhaust_results=False)
Expand All @@ -948,7 +959,9 @@ async def all(self, batch_size=DEFAULT_PAGE_SIZE):
return await self.execute()

async def page(self, offset=0, limit=10):
return await self.copy(offset=offset, limit=limit).execute()
return await self.copy(offset=offset, limit=limit).execute(
exhaust_results=False
)

def sort_by(self, *fields: str):
if not fields:
Expand Down Expand Up @@ -1411,19 +1424,30 @@ def outer_type_or_annotation(field):
if not isinstance(field.annotation, type):
raise AttributeError(f"could not extract outer type from field {field}")
return field.annotation
elif get_origin(field.annotation) == Literal:
return str
else:
return field.annotation.__args__[0]


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()
Expand Down Expand Up @@ -1631,9 +1655,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:
Expand All @@ -1655,8 +1676,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(
Expand All @@ -1676,8 +1697,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):
Expand Down Expand Up @@ -1982,7 +2003,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(
Expand Down Expand Up @@ -2046,21 +2069,33 @@ def schema_for_type(
# find any values marked as indexed.
if is_container_type and not is_vector:
field_type = get_origin(typ)
embedded_cls = get_args(typ)
if not embedded_cls:
log.warning(
"Model %s defined an empty list or tuple field: %s", cls, name
if field_type == Literal:
path = f"{json_path}.{name}"
return cls.schema_for_type(
path,
name,
name_prefix,
str,
field_info,
parent_type=field_type,
)
else:
embedded_cls = get_args(typ)
if not embedded_cls:
log.warning(
"Model %s defined an empty list or tuple field: %s", cls, name
)
return ""
path = f"{json_path}.{name}[*]"
embedded_cls = embedded_cls[0]
return cls.schema_for_type(
path,
name,
name_prefix,
embedded_cls,
field_info,
parent_type=field_type,
)
return ""
embedded_cls = embedded_cls[0]
return cls.schema_for_type(
f"{json_path}.{name}[*]",
name,
name_prefix,
embedded_cls,
field_info,
parent_type=field_type,
)
elif field_is_model:
name_prefix = f"{name_prefix}_{name}" if name_prefix else name
sub_fields = []
Expand Down
3 changes: 1 addition & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
version: "3.8"

---
services:
redis:
image: "redis/redis-stack:latest"
Expand Down
9 changes: 5 additions & 4 deletions docs/models.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ python-ulid = "^1.0.3"
typing-extensions = "^4.4.0"
hiredis = "^2.2.3"
more-itertools = ">=8.14,<11.0"
setuptools = {version = "^69.2.0", markers = "python_version >= '3.12'"}
setuptools = {version = ">=69.2,<73.0", markers = "python_version >= '3.12'"}

[tool.poetry.dev-dependencies]
mypy = "^1.9.0"
Expand Down
Loading

0 comments on commit 43b3455

Please sign in to comment.