2021-11-27 00:25:18 +01:00
# type: ignore
2021-09-16 02:41:45 +02:00
import abc
2021-11-25 03:12:27 +01:00
import dataclasses
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-11-27 00:25:18 +01:00
from typing import Dict , List , Optional , Set
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-10 00:59:10 +01:00
from aredis_om import (
EmbeddedJsonModel ,
Field ,
JsonModel ,
Migrator ,
2021-10-20 22:01:46 +02:00
NotFoundError ,
QueryNotSupportedError ,
RedisModelError ,
)
2021-11-10 20:31:02 +01:00
# We need to run this check as sync code (during tests) even in async mode
# because we call it in the top-level module scope.
from redis_om import has_redis_json
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
2021-10-22 15:33:05 +02:00
async def m ( key_prefix , redis ) :
2021-10-21 08:24:31 +02:00
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 ] ]
2021-11-25 03:12:27 +01:00
await Migrator ( ) . run ( )
2021-10-21 08:24:31 +02:00
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-22 15:33:05 +02:00
async def members ( address , m ) :
2021-10-21 08:24:31 +02:00
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
)
2021-10-22 15:33:05 +02:00
await member1 . save ( )
await member2 . save ( )
await member3 . save ( )
2021-10-04 22:55:33 +02:00
yield member1 , member2 , member3
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async 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-22 15:33:05 +02:00
@pytest.mark.asyncio
async 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
)
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_validation_passes ( address , m ) :
2021-10-21 08:24:31 +02:00
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-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_saves_model_and_creates_pk ( address , m , redis ) :
2021-11-25 03:12:27 +01:00
await Migrator ( ) . run ( )
2021-11-10 00:59:10 +01:00
2021-10-21 08:24:31 +02:00
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-10-22 15:33:05 +02:00
await member . save ( )
2021-08-31 21:03:53 +02:00
2021-10-22 15:33:05 +02:00
member2 = await 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
2022-04-12 12:31:09 +02:00
@pytest.mark.asyncio
async def test_all_pks ( address , m , redis ) :
member = m . Member (
first_name = " Andrew " ,
last_name = " Brookins " ,
email = " a@example.com " ,
join_date = today ,
age = 38 ,
address = address ,
)
await member . save ( )
member1 = m . Member (
first_name = " Simon " ,
last_name = " Prickett " ,
email = " s@example.com " ,
join_date = today ,
age = 99 ,
address = address ,
)
await member1 . save ( )
pk_list = [ ]
async for pk in await m . Member . all_pks ( ) :
pk_list . append ( pk )
assert len ( pk_list ) == 2
2022-02-15 19:46:50 +01:00
2022-02-14 21:44:49 +01:00
@pytest.mark.asyncio
async def test_delete ( address , m , redis ) :
member = m . Member (
first_name = " Simon " ,
last_name = " Prickett " ,
email = " s@example.com " ,
join_date = today ,
age = 38 ,
address = address ,
)
await member . save ( )
response = await m . Member . delete ( member . pk )
assert response == 1
2021-08-31 21:03:53 +02:00
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
2021-11-10 00:59:10 +01:00
async def test_saves_many_implicit_pipeline ( address , m ) :
member1 = m . Member (
first_name = " Andrew " ,
last_name = " Brookins " ,
email = " a@example.com " ,
join_date = today ,
address = address ,
age = 38 ,
)
member2 = m . Member (
first_name = " Kim " ,
last_name = " Brookins " ,
email = " k@example.com " ,
join_date = today ,
address = address ,
age = 34 ,
)
members = [ member1 , member2 ]
result = await m . Member . add ( members )
assert result == [ member1 , member2 ]
2021-08-31 21:03:53 +02:00
2021-11-10 00:59:10 +01:00
assert await m . Member . get ( pk = member1 . pk ) == member1
assert await m . Member . get ( pk = member2 . pk ) == member2
2021-08-31 21:03:53 +02:00
2021-10-04 22:55:33 +02:00
2021-11-10 00:59:10 +01:00
@pytest.mark.asyncio
async def test_saves_many_explicit_transaction ( address , m ) :
member1 = m . Member (
first_name = " Andrew " ,
last_name = " Brookins " ,
email = " a@example.com " ,
join_date = today ,
address = address ,
age = 38 ,
)
member2 = m . Member (
first_name = " Kim " ,
last_name = " Brookins " ,
email = " k@example.com " ,
join_date = today ,
address = address ,
age = 34 ,
)
members = [ member1 , member2 ]
result = await m . Member . add ( members )
assert result == [ member1 , member2 ]
assert await m . Member . get ( pk = member1 . pk ) == member1
assert await m . Member . get ( pk = member2 . pk ) == member2
2021-10-04 22:55:33 +02:00
2021-11-10 00:59:10 +01:00
# Test the explicit pipeline path -- here, we add multiple Members
# using a single Redis transaction, with MULTI/EXEC.
async with m . Member . db ( ) . pipeline ( transaction = True ) as pipeline :
await m . Member . add ( members , pipeline = pipeline )
assert result == [ member1 , member2 ]
assert await pipeline . execute ( ) == [ " OK " , " OK " ]
assert await m . Member . get ( pk = member1 . pk ) == member1
assert await m . Member . get ( pk = member2 . pk ) == member2
2021-08-31 21:03:53 +02:00
2021-10-22 15:33:05 +02:00
async def save ( members ) :
for m in members :
await m . save ( )
return members
@pytest.mark.asyncio
async def test_updates_a_model ( members , m ) :
member1 , member2 , member3 = await save ( members )
2021-10-04 22:55:33 +02:00
2021-11-10 00:59:10 +01:00
# Update a field directly on the model
await member1 . update ( last_name = " Apples to oranges " )
member = await m . Member . get ( member1 . pk )
assert member . last_name == " Apples to oranges "
2021-10-04 22:55:33 +02:00
2021-11-10 00:59:10 +01:00
# Update a field in an embedded model
await member2 . update ( address__city = " Happy Valley " )
member = await m . Member . get ( member2 . pk )
assert member . address . city == " Happy Valley "
2021-10-04 22:55:33 +02:00
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_paginate_query ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
@pytest.mark.asyncio
async 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 == [ ]
2021-10-22 15:33:05 +02:00
await query . execute ( )
2021-10-04 22:55:33 +02:00
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-11-10 00:59:10 +01:00
assert await query . get_item ( 0 ) == member2
2021-10-04 22:55:33 +02:00
assert not mock_db . called
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async 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 == [ ]
2021-11-10 00:59:10 +01:00
assert await query . get_item ( 0 ) == member2
assert await query . get_item ( 1 ) == member1
assert await query . get_item ( 2 ) == member3
2021-10-04 22:55:33 +02:00
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_in_query ( members , m ) :
2021-10-14 02:16:20 +02:00
member1 , member2 , member3 = members
2021-10-22 15:33:05 +02:00
actual = await (
2021-10-21 08:31:11 +02:00
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
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_update_query ( members , m ) :
2021-10-14 02:16:20 +02:00
member1 , member2 , member3 = members
2021-10-22 15:33:05 +02:00
await 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-22 15:33:05 +02:00
actual = await (
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-11-10 00:59:10 +01:00
assert len ( actual ) == 3
assert all ( [ m . first_name == " Bobby " for m in actual ] )
2021-10-14 02:16:20 +02:00
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_exact_match_queries ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
actual = await m . Member . find (
2021-10-21 08:24:31 +02:00
( 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-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
actual = await (
2021-10-21 08:31:11 +02:00
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-22 15:33:05 +02:00
actual = await m . Member . find (
2021-10-21 08:24:31 +02:00
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-11-10 00:59:10 +01:00
actual = (
await 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-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_recursive_query_expression_resolution ( members , m ) :
2021-10-13 17:12:22 +02:00
member1 , member2 , member3 = members
2021-10-22 15:33:05 +02:00
actual = await (
2021-10-21 08:31:11 +02:00
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-22 15:33:05 +02:00
@pytest.mark.asyncio
async 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-22 15:33:05 +02:00
await member1 . save ( )
2021-11-10 00:59:10 +01:00
actual = await 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
]
2021-10-22 15:33:05 +02:00
await member1 . save ( )
actual = await 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-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_full_text_search ( members , m ) :
2021-10-13 19:07:13 +02:00
member1 , member2 , _ = members
2021-10-22 15:33:05 +02:00
await member1 . update ( bio = " Hates sunsets, likes beaches " )
await member2 . update ( bio = " Hates beaches, likes forests " )
2021-10-13 19:07:13 +02:00
2021-10-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
@pytest.mark.asyncio
async 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 = (
2021-10-22 15:33:05 +02:00
await m . Member . find (
2021-10-21 08:31:11 +02:00
( 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-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_tag_queries_punctuation ( address , m ) :
2021-10-21 08:24:31 +02:00
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-22 15:33:05 +02:00
await member1 . save ( )
2021-10-06 01:40:02 +02:00
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
)
2021-10-22 15:33:05 +02:00
await member2 . save ( )
2021-10-06 01:40:02 +02:00
2021-10-21 08:31:11 +02:00
assert (
2021-11-10 00:59:10 +01:00
await m . Member . find ( m . Member . first_name == " Andrew, the Michael " ) . first ( )
== member1
2021-10-21 08:31:11 +02:00
)
assert (
2021-11-10 00:59:10 +01:00
await m . Member . find ( m . Member . last_name == " St. Brookins-on-Pier " ) . first ( )
== member1
2021-10-21 08:31:11 +02:00
)
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-22 15:33:05 +02:00
assert await m . Member . find ( m . Member . email == " a|b@example.com " ) . all ( ) == [ member1 ]
2021-11-10 00:59:10 +01:00
assert await m . Member . find ( m . Member . email == " a|villain@example.com " ) . all ( ) == [
member2
]
2021-08-31 21:03:53 +02:00
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async 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-22 15:33:05 +02:00
assert await query . all ( ) == [ member2 ]
2021-10-06 01:40:02 +02:00
"""
┌ 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
)
2021-10-22 15:33:05 +02:00
assert await query . all ( ) == [ member2 ]
2021-10-06 01:40:02 +02:00
"""
┌ 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-22 15:33:05 +02:00
assert await query . all ( ) == [ member2 ]
2021-10-06 01:40:02 +02:00
"""
┌ 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-22 15:33:05 +02:00
assert await query . sort_by ( " age " ) . all ( ) == [ member2 , member3 ]
2021-10-04 22:55:33 +02:00
2021-10-22 15:33:05 +02:00
actual = await m . Member . find (
2021-10-21 08:24:31 +02:00
( 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-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_numeric_queries ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-22 15:33:05 +02:00
actual = await m . Member . find ( m . Member . age == 34 ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member2 ]
2021-11-10 00:59:10 +01:00
actual = await 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-22 15:33:05 +02:00
actual = await m . Member . find ( m . Member . age < 35 ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member2 ]
2021-10-22 15:33:05 +02:00
actual = await m . Member . find ( m . Member . age < = 34 ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member2 ]
2021-10-22 15:33:05 +02:00
actual = await m . Member . find ( m . Member . age > = 100 ) . all ( )
2021-10-04 22:55:33 +02:00
assert actual == [ member3 ]
2021-10-22 15:33:05 +02:00
actual = await 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-11-10 00:59:10 +01:00
actual = (
await 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-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_sorting ( members , m ) :
2021-10-04 22:55:33 +02:00
member1 , member2 , member3 = members
2021-10-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
actual = await 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-22 15:33:05 +02:00
await 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-22 15:33:05 +02:00
await m . Member . find ( ) . sort_by ( " join_date " ) . all ( )
2021-08-31 21:03:53 +02:00
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_not_found ( m ) :
2021-10-04 22:55:33 +02:00
with pytest . raises ( NotFoundError ) :
# This ID does not exist.
2021-10-22 15:33:05 +02:00
await m . Member . get ( 1000 )
2021-08-31 21:03:53 +02:00
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_list_field_limitations ( m , redis ) :
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.
2021-11-25 03:12:27 +01:00
await Migrator ( ) . run ( )
2021-10-19 06:16:48 +02:00
2021-10-20 22:01:46 +02:00
witch = TarotWitch ( tarot_cards = [ " death " ] )
2021-10-22 15:33:05 +02:00
await witch . save ( )
actual = await TarotWitch . find ( TarotWitch . tarot_cards << " death " ) . all ( )
2021-10-19 06:16:48 +02:00
assert actual == [ witch ]
2021-11-25 03:12:27 +01:00
@pytest.mark.asyncio
async def test_allows_dataclasses ( m ) :
@dataclasses.dataclass
class Address :
address_line_1 : str
class ValidMember ( m . BaseJsonModel ) :
address : Address
address = Address ( address_line_1 = " hey " )
member = ValidMember ( address = address )
await member . save ( )
member2 = await ValidMember . get ( member . pk )
assert member2 == member
assert member2 . address . address_line_1 == " hey "
@pytest.mark.asyncio
async def test_allows_and_serializes_dicts ( m ) :
class ValidMember ( m . BaseJsonModel ) :
address : Dict [ str , str ]
member = ValidMember ( address = { " address_line_1 " : " hey " } )
await member . save ( )
member2 = await ValidMember . get ( member . pk )
assert member2 == member
2021-11-27 00:25:18 +01:00
assert member2 . address [ " address_line_1 " ] == " hey "
2021-11-25 03:12:27 +01:00
@pytest.mark.asyncio
async def test_allows_and_serializes_sets ( m ) :
class ValidMember ( m . BaseJsonModel ) :
friend_ids : Set [ int ]
member = ValidMember ( friend_ids = { 1 , 2 } )
await member . save ( )
member2 = await ValidMember . get ( member . pk )
assert member2 == member
assert member2 . friend_ids == { 1 , 2 }
@pytest.mark.asyncio
async def test_allows_and_serializes_lists ( m ) :
class ValidMember ( m . BaseJsonModel ) :
friend_ids : List [ int ]
member = ValidMember ( friend_ids = [ 1 , 2 ] )
await member . save ( )
member2 = await ValidMember . get ( member . pk )
assert member2 == member
assert member2 . friend_ids == [ 1 , 2 ]
2021-10-22 15:33:05 +02:00
@pytest.mark.asyncio
async def test_schema ( m , key_prefix ) :
2021-11-10 20:31:02 +01:00
# 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 = " " ) )
2021-10-20 22:01:46 +02:00
assert (
2021-10-21 08:24:31 +02:00
m . Member . redisearch_schema ( )
2021-11-10 20:31:02 +01:00
== f " ON JSON PREFIX 1 { key_prefix } 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
)