redis / redis-om-python

Object mapping, and more, for Redis and Python
MIT License
1.07k stars 108 forks source link

Can't search on nested/embedded list of JsonModels #480

Open eric-musliner opened 1 year ago

eric-musliner commented 1 year ago

I have the following schema defined

class Workspace(JsonModel):
    id: str = Field(index=True)
    title: str = Field(index=True)
    users: List[User] = Field(index=True)

class User(EmbeddedJsonModel):
    id: str = Field(index=True)
    name: str = Field(index=True)
    type: str = Field(index=True)

I can't find an example like this in the documentation but if I had a Workspace saved how could I search for a User associated with that Workspace. From what I understand I have to start the search on Workspace and drill down to the users list.

From the documentation on the "skill" of a Person example I assume it would look something like this, but I haven't had success getting this to work.

user = Workspace.find(Workspace.users << User(**{"name": "John Doe"})).all()

Update/Edit.

The schema update to have User not be an EmbeddedJsonModel still doesn't function as expected, and no results are found.

The Migrator created a search index for both Workspace as well as User but it's not clear how to take advantage of the User search index because no searches return values

class Workspace(JsonModel):
    id: str = Field(index=True)
    title: str = Field(index=True)
    users: List[User] = Field(index=True)

class Users(JsonModel):
    id: str = Field(index=True)
    name: str = Field(index=True)
    type: str = Field(index=True)

Stored Json

{
  "pk": "01GSWNGTT7WXD7NGHPN1XPGNQQ",
  "id": "6",
  "title": "Test Workspace",
  "users": [
     "0": {
       "pk": "01GSWNGTT7CMACEE55FSWKCVHQ",
       "id": "30",
       "name": "John",
       "type": "registered",
      }
   ]
}
User.find((User.name == "John")).all()

Returns "[]"

sergenciftci commented 1 year ago

This should work

user = User.find(Workspace.users.name == "John Doe")

You have a Typo, class User instead of class Users

But this only works for one attribute. If you want to compare multiple attributes it does not work as expect.

from redis_om import Migrator, JsonModel, Field
from typing import List

class User(JsonModel):
    first_name: str = Field(index=True)
    last_name: str = Field(index=True)

class Workspace(JsonModel):
    title: str = Field(index=True)
    users: List[User] = Field(index=True)

Migrator().run()

jason_doe = User(first_name="Jason", last_name="Doe")
jason_doe.save()
john_bourne = User(first_name="John", last_name="Bourne")
john_bourne.save()
jason_bourne = User(first_name="Jason", last_name="Bourne")
jason_bourne.save()

first_workspace = Workspace(title="first workspace", users=[jason_doe, john_bourne])
first_workspace.save()
second_workspace = Workspace(title="second workspace", users=[jason_bourne])
second_workspace.save()

print(Workspace.find().all())
# [Workspace(pk='01GSCYNT0366QJ4MAKW049GKFP', title='first workspace', users=[User(pk='01GSCYNT012PQPV8Y5N7S6J0B3', first_name='Jason', last_name='Doe'), User(pk='01GSCYNT022ZP2AAKZCVEQ6SZP', first_name='John', last_name='Bourne')]), Workspace(pk='01GSCYNT03B8Y67RW9VK5FGQWV', title='second workspace', users=[User(pk='01GSCYNT02PE0P2XT4K2A3XCDV', first_name='Jason', last_name='Bourne')])]
print(Workspace.find((Workspace.users.first_name == "Jason"), (Workspace.users.last_name == "Bourne")).all())
# [Workspace(pk='01GSCYNT0366QJ4MAKW049GKFP', title='first workspace', users=[User(pk='01GSCYNT012PQPV8Y5N7S6J0B3', first_name='Jason', last_name='Doe'), User(pk='01GSCYNT022ZP2AAKZCVEQ6SZP', first_name='John', last_name='Bourne')]), Workspace(pk='01GSCYNT03B8Y67RW9VK5FGQWV', title='second workspace', users=[User(pk='01GSCYNT02PE0P2XT4K2A3XCDV', first_name='Jason', last_name='Bourne')])]
# I would have expected only the second workspace, it's more intuitive.

# Does only work with "class User(JsonModel)"
# This would maybe another way to initially filter on the User model to get the pks and use that to get the Workspaces, in which the user participates.
# But somehow it does not work, when the User is embedded into the Workspace
users = User.find((User.first_name == "Jason"), (User.last_name == "Bourne")).all()
print(users)
# []
for user in users:
    print(Workspace.find(Workspace.users.pk == user.pk).all())

I also tried using class User(EmbeddedJsonModel) but that also did not work. The only difference was that the query User.find((User.first_name == "Jason") & (User.last_name == "Bourne")).all() led to redis.exceptions.ResponseError: :__main__.User:index: no such index

My testing environment: https://github.com/sergenciftci/redis-om-test-project.git

eric-musliner commented 1 year ago

Thank you for the response! This seemed to work for me as well. Unfortunately the use case I'm most interested in is getting the User object and not the entire Workspace since then I'd have to iterate the list of users anyway.

It's weird that this doesn't work as expected

users = User.find((User.first_name == "Jason"), (User.last_name == "Bourne")).all()
print(users)
# []

I get an empty list on this search even when I changed it to not use an EmbeddedModel based on your suggestion