2021-09-16 02:41:45 +02:00
import abc
2021-08-31 21:03:53 +02:00
import datetime
2021-10-20 22:01:46 +02:00
import decimal
2021-10-21 08:24:31 +02:00
from collections import namedtuple
2021-10-20 22:01:46 +02:00
from typing import List , Optional
2021-10-04 22:55:33 +02:00
from unittest import mock
2021-08-31 21:03:53 +02:00
import pytest
from pydantic import ValidationError
2021-11-03 20:37:09 +01:00
from redis_om . checks import has_redis_json
2021-10-22 15:36:15 +02:00
from redis_om . model import EmbeddedJsonModel , Field , JsonModel
from redis_om . model . migrations . migrator import Migrator
2021-10-22 15:53:39 +02:00
from redis_om . model . model import NotFoundError , QueryNotSupportedError , RedisModelError
2021-10-20 22:01:46 +02:00
2021-08-31 21:03:53 +02:00
2021-11-03 20:37:09 +01:00
if not has_redis_json ( ) :
pytestmark = pytest . mark . skip
2021-10-04 22:55:33 +02:00
today = datetime . date . today ( )
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
@pytest.fixture
def m ( key_prefix ) :
class BaseJsonModel ( JsonModel , abc . ABC ) :
class Meta :
global_key_prefix = key_prefix
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
class Note ( EmbeddedJsonModel ) :
# TODO: This was going to be a full-text search example, but
# we can't index embedded documents for full-text search in
# the preview release.
description : str = Field ( index = True )
created_on : datetime . datetime
2021-10-12 23:22:57 +02:00
2021-10-21 08:24:31 +02:00
class Address ( EmbeddedJsonModel ) :
address_line_1 : str
address_line_2 : Optional [ str ]
city : str = Field ( index = True )
state : str
country : str
postal_code : str = Field ( index = True )
note : Optional [ Note ]
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
class Item ( EmbeddedJsonModel ) :
price : decimal . Decimal
name : str = Field ( index = True )
2021-09-01 22:06:23 +02:00
2021-10-21 08:24:31 +02:00
class Order ( EmbeddedJsonModel ) :
items : List [ Item ]
created_on : datetime . datetime
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
class Member ( BaseJsonModel ) :
first_name : str = Field ( index = True )
last_name : str = Field ( index = True )
email : str = Field ( index = True )
join_date : datetime . date
age : int = Field ( index = True )
bio : Optional [ str ] = Field ( index = True , full_text_search = True , default = " " )
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
# Creates an embedded model.
address : Address
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
# Creates an embedded list of models.
orders : Optional [ List [ Order ] ]
Migrator ( ) . run ( )
2021-10-21 08:31:11 +02:00
return namedtuple (
" Models " , [ " BaseJsonModel " , " Note " , " Address " , " Item " , " Order " , " Member " ]
) ( BaseJsonModel , Note , Address , Item , Order , Member )
2021-08-31 21:03:53 +02:00
2021-09-01 22:06:23 +02:00
2021-10-04 22:55:33 +02:00
@pytest.fixture ( )
2021-10-21 08:24:31 +02:00
def address ( m ) :
yield m . Address (
2021-10-04 22:55:33 +02:00
address_line_1 = " 1 Main St. " ,
city = " Portland " ,
state = " OR " ,
country = " USA " ,
2021-10-20 22:01:46 +02:00
postal_code = 11111 ,
2021-10-04 22:55:33 +02:00
)
2021-09-01 22:06:23 +02:00
2021-08-31 21:03:53 +02:00
2021-10-04 22:55:33 +02:00
@pytest.fixture ( )
2021-10-21 08:24:31 +02:00
def members ( address , m ) :
member1 = m . Member (
2021-10-04 22:55:33 +02:00
first_name = " Andrew " ,
last_name = " Brookins " ,
email = " a@example.com " ,
age = 38 ,
join_date = today ,
2021-10-20 22:01:46 +02:00
address = address ,
2021-10-04 22:55:33 +02:00
)
2021-10-21 08:24:31 +02:00
member2 = m . Member (
2021-10-04 22:55:33 +02:00
first_name = " Kim " ,
last_name = " Brookins " ,
email = " k@example.com " ,
age = 34 ,
join_date = today ,
2021-10-20 22:01:46 +02:00
address = address ,
2021-10-04 22:55:33 +02:00
)
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
member3 = m . Member (
2021-10-04 22:55:33 +02:00
first_name = " Andrew " ,
last_name = " Smith " ,
email = " as@example.com " ,
age = 100 ,
join_date = today ,
2021-10-20 22:01:46 +02:00
address = address ,
2021-10-04 22:55:33 +02:00
)
member1 . save ( )
member2 . save ( )
member3 . save ( )
yield member1 , member2 , member3
2021-10-21 08:24:31 +02:00
def test_validates_required_fields ( address , m ) :
2021-10-04 22:55:33 +02:00
# Raises ValidationError address is required
2021-08-31 21:03:53 +02:00
with pytest . raises ( ValidationError ) :
2021-10-21 08:24:31 +02:00
m . Member (
2021-08-31 21:03:53 +02:00
first_name = " Andrew " ,
2021-10-04 22:55:33 +02:00
last_name = " Brookins " ,
2021-08-31 21:03:53 +02:00
zipcode = " 97086 " ,
2021-10-04 22:55:33 +02:00
join_date = today ,
2021-08-31 21:03:53 +02:00
)
2021-10-21 08:24:31 +02:00
def test_validates_field ( address , m ) :
2021-08-31 21:03:53 +02:00
# Raises ValidationError: join_date is not a date
with pytest . raises ( ValidationError ) :
2021-10-21 08:24:31 +02:00
m . Member (
2021-08-31 21:03:53 +02:00
first_name = " Andrew " ,
last_name = " Brookins " ,
2021-10-04 22:55:33 +02:00
join_date = " yesterday " ,
2021-10-20 22:01:46 +02:00
address = address ,
2021-08-31 21:03:53 +02:00
)
# Passes validation
2021-10-21 08:24:31 +02:00
def test_validation_passes ( address , m ) :
member = m . Member (
2021-08-31 21:03:53 +02:00
first_name = " Andrew " ,
last_name = " Brookins " ,
email = " a@example.com " ,
2021-10-04 22:55:33 +02:00
join_date = today ,
age = 38 ,
2021-10-20 22:01:46 +02:00
address = address ,
2021-08-31 21:03:53 +02:00
)
assert member . first_name == " Andrew "
2021-10-21 08:24:31 +02:00
def test_saves_model_and_creates_pk ( address , m ) :
member = m . Member (
2021-08-31 21:03:53 +02:00
first_name = " Andrew " ,
last_name = " Brookins " ,
email = " a@example.com " ,
2021-10-04 22:55:33 +02:00
join_date = today ,
age = 38 ,
2021-10-20 22:01:46 +02:00
address = address ,
2021-08-31 21:03:53 +02:00
)
2021-10-04 22:55:33 +02:00
# Save a model instance to Redis
2021-08-31 21:03:53 +02:00
member . save ( )
2021-10-21 08:24:31 +02:00
member2 = m . Member . get ( member . pk )
2021-10-04 22:55:33 +02:00
assert member2 == member
2021-09-01 21:56:06 +02:00
assert member2 . address == address
2021-08-31 21:03:53 +02:00
2021-09-01 00:52:21 +02:00
@pytest.mark.skip ( " Not implemented yet " )
2021-10-21 08:24:31 +02:00
def test_saves_many ( address , m ) :
2021-08-31 21:03:53 +02:00
members = [
2021-10-21 08:24:31 +02:00
m . Member (
2021-08-31 21:03:53 +02:00
first_name = " Andrew " ,
last_name = " Brookins " ,
email = " a@example.com " ,
2021-10-04 22:55:33 +02:00
join_date = today ,
2021-08-31 21:03:53 +02:00
address = address ,
2021-10-20 22:01:46 +02:00
age = 38 ,
2021-08-31 21:03:53 +02:00
) ,
2021-10-21 08:24:31 +02:00
m . Member (
2021-08-31 21:03:53 +02:00
first_name = " Kim " ,
last_name = " Brookins " ,
email = " k@example.com " ,
2021-10-04 22:55:33 +02:00
join_date = today ,
2021-08-31 21:03:53 +02:00
address = address ,
2021-10-20 22:01:46 +02:00
age = 34 ,
) ,
2021-08-31 21:03:53 +02:00
]
2021-10-21 08:24:31 +02:00
m . Member . add ( members )
2021-08-31 21:03:53 +02:00
2021-10-04 22:55:33 +02:00
@pytest.mark.skip ( " Not ready yet " )
2021-10-21 08:24:31 +02:00
def test_updates_a_model ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
# Or, with an implicit save:
member1 . update ( last_name = " Smith " )
2021-10-21 08:24:31 +02:00
assert m . Member . find ( m . Member . pk == member1 . pk ) . first ( ) == member1
2021-10-04 22:55:33 +02:00
# Or, affecting multiple model instances with an implicit save:
2021-10-21 08:24:31 +02:00
m . Member . find ( m . Member . last_name == " Brookins " ) . update ( last_name = " Smith " )
results = m . Member . find ( m . Member . last_name == " Smith " )
2021-10-06 01:40:02 +02:00
assert results == members
2021-10-04 22:55:33 +02:00
# Or, updating a field in an embedded model:
member2 . update ( address__city = " Happy Valley " )
2021-10-21 08:31:11 +02:00
assert (
m . Member . find ( m . Member . pk == member2 . pk ) . first ( ) . address . city == " Happy Valley "
)
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
def test_paginate_query ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-21 08:31:11 +02:00
actual = m . Member . find ( ) . sort_by ( " age " ) . all ( batch_size = 1 )
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 , member3 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
def test_access_result_by_index_cached ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-21 08:24:31 +02:00
query = m . Member . find ( ) . sort_by ( " age " )
2021-10-04 22:55:33 +02:00
# Load the cache, throw away the result.
assert query . _model_cache == [ ]
query . execute ( )
assert query . _model_cache == [ member2 , member1 , member3 ]
# Access an item that should be in the cache.
2021-10-20 22:01:46 +02:00
with mock . patch . object ( query . model , " db " ) as mock_db :
2021-10-04 22:55:33 +02:00
assert query [ 0 ] == member2
assert not mock_db . called
2021-10-21 08:24:31 +02:00
def test_access_result_by_index_not_cached ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-21 08:24:31 +02:00
query = m . Member . find ( ) . sort_by ( " age " )
2021-10-04 22:55:33 +02:00
# Assert that we don't have any models in the cache yet -- we
# haven't made any requests of Redis.
assert query . _model_cache == [ ]
assert query [ 0 ] == member2
assert query [ 1 ] == member1
assert query [ 2 ] == member3
2021-10-21 08:24:31 +02:00
def test_in_query ( members , m ) :
2021-10-14 02:16:20 +02:00
member1 , member2 , member3 = members
2021-10-21 08:31:11 +02:00
actual = (
m . Member . find ( m . Member . pk << [ member1 . pk , member2 . pk , member3 . pk ] )
. sort_by ( " age " )
. all ( )
)
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 , member3 ]
2021-10-14 02:16:20 +02:00
@pytest.mark.skip ( " Not implemented yet " )
2021-10-21 08:24:31 +02:00
def test_update_query ( members , m ) :
2021-10-14 02:16:20 +02:00
member1 , member2 , member3 = members
2021-10-21 08:24:31 +02:00
m . Member . find ( m . Member . pk << [ member1 . pk , member2 . pk , member3 . pk ] ) . update (
2021-10-14 02:16:20 +02:00
first_name = " Bobby "
)
2021-10-20 22:01:46 +02:00
actual = (
2021-10-21 08:24:31 +02:00
m . Member . find ( m . Member . pk << [ member1 . pk , member2 . pk , member3 . pk ] )
2021-10-20 22:01:46 +02:00
. sort_by ( " age " )
. all ( )
)
2021-10-14 02:16:20 +02:00
assert actual == [ member1 , member2 , member3 ]
assert all ( [ m . name == " Bobby " for m in actual ] )
2021-10-21 08:24:31 +02:00
def test_exact_match_queries ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-21 08:31:11 +02:00
actual = m . Member . find ( m . Member . last_name == " Brookins " ) . sort_by ( " age " ) . all ( )
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 ]
2021-10-20 22:01:46 +02:00
2021-10-21 08:24:31 +02:00
actual = m . Member . find (
( m . Member . last_name == " Brookins " ) & ~ ( m . Member . first_name == " Andrew " )
2021-10-20 22:01:46 +02:00
) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member2 ]
2021-10-20 22:01:46 +02:00
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( ~ ( m . Member . last_name == " Brookins " ) ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member3 ]
2021-10-20 22:01:46 +02:00
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . last_name != " Brookins " ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member3 ]
2021-10-20 22:01:46 +02:00
2021-10-21 08:31:11 +02:00
actual = (
m . Member . find (
( m . Member . last_name == " Brookins " ) & ( m . Member . first_name == " Andrew " )
| ( m . Member . first_name == " Kim " )
)
. sort_by ( " age " )
. all ( )
)
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 ]
2021-10-20 22:01:46 +02:00
2021-10-21 08:24:31 +02:00
actual = m . Member . find (
m . Member . first_name == " Kim " , m . Member . last_name == " Brookins "
2021-10-20 22:01:46 +02:00
) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member2 ]
2021-10-21 08:31:11 +02:00
actual = m . Member . find ( m . Member . address . city == " Portland " ) . sort_by ( " age " ) . all ( )
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 , member3 ]
2021-10-04 22:55:33 +02:00
2021-10-13 17:12:22 +02:00
2021-10-21 08:24:31 +02:00
def test_recursive_query_expression_resolution ( members , m ) :
2021-10-13 17:12:22 +02:00
member1 , member2 , member3 = members
2021-10-21 08:31:11 +02:00
actual = (
m . Member . find (
( m . Member . last_name == " Brookins " )
| ( m . Member . age == 100 ) & ( m . Member . last_name == " Smith " )
)
. sort_by ( " age " )
. all ( )
)
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 , member3 ]
2021-10-13 17:12:22 +02:00
2021-10-21 08:24:31 +02:00
def test_recursive_query_field_resolution ( members , m ) :
2021-10-13 17:12:22 +02:00
member1 , _ , _ = members
2021-10-21 08:24:31 +02:00
member1 . address . note = m . Note (
2021-10-20 22:01:46 +02:00
description = " Weird house " , created_on = datetime . datetime . now ( )
)
2021-10-12 23:22:57 +02:00
member1 . save ( )
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . address . note . description == " Weird house " ) . all ( )
2021-10-12 23:22:57 +02:00
assert actual == [ member1 ]
member1 . orders = [
2021-10-21 08:24:31 +02:00
m . Order (
items = [ m . Item ( price = 10.99 , name = " Ball " ) ] ,
2021-10-20 22:01:46 +02:00
total = 10.99 ,
created_on = datetime . datetime . now ( ) ,
)
2021-10-12 23:22:57 +02:00
]
member1 . save ( )
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . orders . items . name == " Ball " ) . all ( )
2021-10-12 23:22:57 +02:00
assert actual == [ member1 ]
2021-10-13 21:58:10 +02:00
assert actual [ 0 ] . orders [ 0 ] . items [ 0 ] . name == " Ball "
2021-10-12 23:22:57 +02:00
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
def test_full_text_search ( members , m ) :
2021-10-13 19:07:13 +02:00
member1 , member2 , _ = members
2021-10-19 06:16:48 +02:00
member1 . update ( bio = " Hates sunsets, likes beaches " )
member2 . update ( bio = " Hates beaches, likes forests " )
2021-10-13 19:07:13 +02:00
2021-10-21 08:31:11 +02:00
actual = m . Member . find ( m . Member . bio % " beaches " ) . sort_by ( " age " ) . all ( )
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 ]
2021-10-13 19:07:13 +02:00
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . bio % " forests " ) . all ( )
2021-10-19 06:16:48 +02:00
assert actual == [ member2 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
def test_tag_queries_boolean_logic ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-21 08:31:11 +02:00
actual = (
m . Member . find (
( m . Member . first_name == " Andrew " ) & ( m . Member . last_name == " Brookins " )
| ( m . Member . last_name == " Smith " )
)
. sort_by ( " age " )
. all ( )
)
2021-10-06 01:40:02 +02:00
assert actual == [ member1 , member3 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
def test_tag_queries_punctuation ( address , m ) :
member1 = m . Member (
2021-10-06 01:40:02 +02:00
first_name = " Andrew, the Michael " ,
2021-10-04 22:55:33 +02:00
last_name = " St. Brookins-on-Pier " ,
2021-10-06 01:40:02 +02:00
email = " a|b@example.com " , # NOTE: This string uses the TAG field separator.
2021-10-04 22:55:33 +02:00
age = 38 ,
2021-10-06 01:40:02 +02:00
join_date = today ,
2021-10-20 22:01:46 +02:00
address = address ,
2021-08-31 22:31:14 +02:00
)
2021-10-06 01:40:02 +02:00
member1 . save ( )
2021-10-21 08:24:31 +02:00
member2 = m . Member (
2021-10-06 01:40:02 +02:00
first_name = " Bob " ,
last_name = " the Villain " ,
email = " a|villain@example.com " , # NOTE: This string uses the TAG field separator.
age = 38 ,
join_date = today ,
2021-10-20 22:01:46 +02:00
address = address ,
2021-10-06 01:40:02 +02:00
)
member2 . save ( )
2021-10-21 08:31:11 +02:00
assert (
m . Member . find ( m . Member . first_name == " Andrew, the Michael " ) . first ( ) == member1
)
assert (
m . Member . find ( m . Member . last_name == " St. Brookins-on-Pier " ) . first ( ) == member1
)
2021-08-31 21:03:53 +02:00
2021-10-06 01:40:02 +02:00
# Notice that when we index and query multiple values that use the internal
# TAG separator for single-value exact-match fields, like an indexed string,
# the queries will succeed. We apply a workaround that queries for the union
# of the two values separated by the tag separator.
2021-10-21 08:24:31 +02:00
assert m . Member . find ( m . Member . email == " a|b@example.com " ) . all ( ) == [ member1 ]
assert m . Member . find ( m . Member . email == " a|villain@example.com " ) . all ( ) == [ member2 ]
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
def test_tag_queries_negation ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-06 01:40:02 +02:00
"""
┌ first_name
NOT EQ ┤
└ Andrew
"""
2021-10-21 08:24:31 +02:00
query = m . Member . find ( ~ ( m . Member . first_name == " Andrew " ) )
2021-10-06 01:40:02 +02:00
assert query . all ( ) == [ member2 ]
"""
┌ first_name
┌ NOT EQ ┤
| └ Andrew
AND ┤
| ┌ last_name
└ EQ ┤
└ Brookins
"""
2021-10-21 08:24:31 +02:00
query = m . Member . find (
~ ( m . Member . first_name == " Andrew " ) & ( m . Member . last_name == " Brookins " )
2021-10-06 01:40:02 +02:00
)
assert query . all ( ) == [ member2 ]
"""
┌ first_name
┌ NOT EQ ┤
| └ Andrew
AND ┤
| ┌ last_name
| ┌ EQ ┤
| | └ Brookins
└ OR ┤
| ┌ last_name
└ EQ ┤
└ Smith
"""
2021-10-21 08:24:31 +02:00
query = m . Member . find (
~ ( m . Member . first_name == " Andrew " )
& ( ( m . Member . last_name == " Brookins " ) | ( m . Member . last_name == " Smith " ) )
2021-10-20 22:01:46 +02:00
)
2021-10-06 01:40:02 +02:00
assert query . all ( ) == [ member2 ]
"""
┌ first_name
┌ NOT EQ ┤
| └ Andrew
┌ AND ┤
| | ┌ last_name
| └ EQ ┤
| └ Brookins
OR ┤
| ┌ last_name
└ EQ ┤
└ Smith
"""
2021-10-21 08:24:31 +02:00
query = m . Member . find (
~ ( m . Member . first_name == " Andrew " ) & ( m . Member . last_name == " Brookins " )
| ( m . Member . last_name == " Smith " )
2021-10-20 22:01:46 +02:00
)
2021-10-21 08:31:11 +02:00
assert query . sort_by ( " age " ) . all ( ) == [ member2 , member3 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
actual = m . Member . find (
( m . Member . first_name == " Andrew " ) & ~ ( m . Member . last_name == " Brookins " )
2021-10-20 22:01:46 +02:00
) . all ( )
2021-10-06 01:40:02 +02:00
assert actual == [ member3 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
def test_numeric_queries ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . age == 34 ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member2 ]
2021-11-03 20:37:09 +01:00
actual = m . Member . find ( m . Member . age > 34 ) . sort_by ( " age " ) . all ( )
2021-10-06 01:40:02 +02:00
assert actual == [ member1 , member3 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . age < 35 ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member2 ]
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . age < = 34 ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member2 ]
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . age > = 100 ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member3 ]
2021-10-21 08:31:11 +02:00
actual = m . Member . find ( ~ ( m . Member . age == 100 ) ) . sort_by ( " age " ) . all ( )
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:31:11 +02:00
actual = m . Member . find ( m . Member . age > 30 , m . Member . age < 40 ) . sort_by ( " age " ) . all ( )
2021-10-21 08:24:31 +02:00
assert actual == [ member2 , member1 ]
2021-10-14 02:16:20 +02:00
2021-10-21 08:31:11 +02:00
actual = m . Member . find ( m . Member . age != 34 ) . sort_by ( " age " ) . all ( )
2021-10-14 02:16:20 +02:00
assert actual == [ member1 , member3 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
def test_sorting ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . age > 34 ) . sort_by ( " age " ) . all ( )
2021-10-06 01:40:02 +02:00
assert actual == [ member1 , member3 ]
2021-10-04 22:55:33 +02:00
2021-10-21 08:24:31 +02:00
actual = m . Member . find ( m . Member . age > 34 ) . sort_by ( " -age " ) . all ( )
2021-10-06 01:40:02 +02:00
assert actual == [ member3 , member1 ]
2021-10-04 22:55:33 +02:00
with pytest . raises ( QueryNotSupportedError ) :
# This field does not exist.
2021-10-21 08:24:31 +02:00
m . Member . find ( ) . sort_by ( " not-a-real-field " ) . all ( )
2021-10-04 22:55:33 +02:00
with pytest . raises ( QueryNotSupportedError ) :
# This field is not sortable.
2021-10-21 08:24:31 +02:00
m . Member . find ( ) . sort_by ( " join_date " ) . all ( )
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
def test_not_found ( m ) :
2021-10-04 22:55:33 +02:00
with pytest . raises ( NotFoundError ) :
# This ID does not exist.
2021-10-21 08:24:31 +02:00
m . Member . get ( 1000 )
2021-08-31 21:03:53 +02:00
2021-10-21 08:24:31 +02:00
def test_list_field_limitations ( m ) :
2021-10-19 06:16:48 +02:00
with pytest . raises ( RedisModelError ) :
2021-10-20 22:01:46 +02:00
2021-10-21 08:24:31 +02:00
class SortableTarotWitch ( m . BaseJsonModel ) :
2021-10-19 06:16:48 +02:00
# We support indexing lists of strings for quality and membership
# queries. Sorting is not supported, but is planned.
tarot_cards : List [ str ] = Field ( index = True , sortable = True )
with pytest . raises ( RedisModelError ) :
2021-10-20 22:01:46 +02:00
2021-10-21 08:31:11 +02:00
class SortableFullTextSearchAlchemicalWitch ( m . BaseJsonModel ) :
2021-10-19 06:16:48 +02:00
# We don't support indexing a list of strings for full-text search
# queries. Support for this feature is not planned.
potions : List [ str ] = Field ( index = True , full_text_search = True )
with pytest . raises ( RedisModelError ) :
2021-10-20 22:01:46 +02:00
2021-10-21 08:24:31 +02:00
class NumerologyWitch ( m . BaseJsonModel ) :
2021-10-19 06:16:48 +02:00
# We don't support indexing a list of numbers. Support for this
# feature is To Be Determined.
lucky_numbers : List [ int ] = Field ( index = True )
with pytest . raises ( RedisModelError ) :
2021-10-20 22:01:46 +02:00
2021-10-19 06:16:48 +02:00
class ReadingWithPrice ( EmbeddedJsonModel ) :
gold_coins_charged : int = Field ( index = True )
2021-10-21 08:24:31 +02:00
class TarotWitchWhoCharges ( m . BaseJsonModel ) :
2021-10-19 06:16:48 +02:00
tarot_cards : List [ str ] = Field ( index = True )
# The preview release does not support indexing numeric fields on models
# found within a list or tuple. This is the same limitation that stops
# us from indexing plain lists (or tuples) containing numeric values.
# The fate of this feature is To Be Determined.
readings : List [ ReadingWithPrice ]
2021-10-21 08:24:31 +02:00
class TarotWitch ( m . BaseJsonModel ) :
2021-10-19 06:16:48 +02:00
# We support indexing lists of strings for quality and membership
# queries. Sorting is not supported, but is planned.
tarot_cards : List [ str ] = Field ( index = True )
# We need to import and run this manually because we defined
# our model classes within a function that runs after the test
# suite's migrator has already looked for migrations to run.
Migrator ( ) . run ( )
2021-10-20 22:01:46 +02:00
witch = TarotWitch ( tarot_cards = [ " death " ] )
2021-10-19 06:16:48 +02:00
witch . save ( )
2021-10-20 22:01:46 +02:00
actual = TarotWitch . find ( TarotWitch . tarot_cards << " death " ) . all ( )
2021-10-19 06:16:48 +02:00
assert actual == [ witch ]
2021-10-21 08:24:31 +02:00
def test_schema ( m , key_prefix ) :
2021-10-20 22:01:46 +02:00
assert (
2021-10-21 08:24:31 +02:00
m . Member . redisearch_schema ( )
== f " ON JSON PREFIX 1 { key_prefix } :tests.test_json_model.Member: SCHEMA $.pk AS pk TAG SEPARATOR | $.first_name AS first_name TAG SEPARATOR | $.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 | "
2021-10-20 22:01:46 +02:00
)