Avoid adding same parent to attr multiple times
This commit is contained in:
		
							parent
							
								
									ae8baa0339
								
							
						
					
					
						commit
						e59e210834
					
				
					 2 changed files with 50 additions and 14 deletions
				
			
		| 
						 | 
					@ -220,6 +220,9 @@ class ExpressionProxy:
 | 
				
			||||||
    def __ge__(self, other: Any) -> Expression:  # type: ignore[override]
 | 
					    def __ge__(self, other: Any) -> Expression:  # type: ignore[override]
 | 
				
			||||||
        return Expression(left=self.field, op=Operators.GE, right=other, parents=self.parents)
 | 
					        return Expression(left=self.field, op=Operators.GE, right=other, parents=self.parents)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __mod__(self, other: Any) -> Expression:  # type: ignore[override]
 | 
				
			||||||
 | 
					        return Expression(left=self.field, op=Operators.LIKE, right=other, parents=self.parents)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __getattr__(self, item):
 | 
					    def __getattr__(self, item):
 | 
				
			||||||
        if get_origin(self.field.outer_type_) == list:
 | 
					        if get_origin(self.field.outer_type_) == list:
 | 
				
			||||||
            embedded_cls = get_args(self.field.outer_type_)
 | 
					            embedded_cls = get_args(self.field.outer_type_)
 | 
				
			||||||
| 
						 | 
					@ -233,8 +236,12 @@ class ExpressionProxy:
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            attr = getattr(self.field.outer_type_, item)
 | 
					            attr = getattr(self.field.outer_type_, item)
 | 
				
			||||||
        if isinstance(attr, self.__class__):
 | 
					        if isinstance(attr, self.__class__):
 | 
				
			||||||
            attr.parents.append((self.field.name, self.field.outer_type_))
 | 
					            new_parent = (self.field.name, self.field.outer_type_)
 | 
				
			||||||
            attr.parents = self.parents + attr.parents
 | 
					            if not new_parent in attr.parents:
 | 
				
			||||||
 | 
					                attr.parents.append(new_parent)
 | 
				
			||||||
 | 
					            new_parents = list(set(self.parents) - set(attr.parents))
 | 
				
			||||||
 | 
					            if new_parents:
 | 
				
			||||||
 | 
					                attr.parents = new_parents + attr.parents
 | 
				
			||||||
        return attr
 | 
					        return attr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -315,10 +322,15 @@ class FindQuery:
 | 
				
			||||||
        return sort_fields
 | 
					        return sort_fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @staticmethod
 | 
					    @staticmethod
 | 
				
			||||||
    def resolve_field_type(field: ModelField) -> RediSearchFieldTypes:
 | 
					    def resolve_field_type(field: ModelField, operator: Operators) -> RediSearchFieldTypes:
 | 
				
			||||||
        if getattr(field.field_info, 'primary_key', None) is True:
 | 
					        if getattr(field.field_info, 'primary_key', None) is True:
 | 
				
			||||||
            return RediSearchFieldTypes.TAG
 | 
					            return RediSearchFieldTypes.TAG
 | 
				
			||||||
        elif getattr(field.field_info, 'full_text_search', None) is True:
 | 
					        elif operator is Operators.LIKE:
 | 
				
			||||||
 | 
					            fts = getattr(field.field_info, 'full_text_search', None)
 | 
				
			||||||
 | 
					            if fts is not True:  # Could be PydanticUndefined
 | 
				
			||||||
 | 
					                raise QuerySyntaxError(f"You tried to do a full-text search on the field '{field.name}', "
 | 
				
			||||||
 | 
					                                       f"but the field is not indexed for full-text search. Use the "
 | 
				
			||||||
 | 
					                                       f"full_text_search=True option. Docs: TODO")
 | 
				
			||||||
            return RediSearchFieldTypes.TEXT
 | 
					            return RediSearchFieldTypes.TEXT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        field_type = field.outer_type_
 | 
					        field_type = field.outer_type_
 | 
				
			||||||
| 
						 | 
					@ -353,7 +365,7 @@ class FindQuery:
 | 
				
			||||||
            field_name = f"{prefix}_{field_name}"
 | 
					            field_name = f"{prefix}_{field_name}"
 | 
				
			||||||
        result = ""
 | 
					        result = ""
 | 
				
			||||||
        if field_type is RediSearchFieldTypes.TEXT:
 | 
					        if field_type is RediSearchFieldTypes.TEXT:
 | 
				
			||||||
            result = f"@{field_name}:"
 | 
					            result = f"@{field_name}_fts:"
 | 
				
			||||||
            if op is Operators.EQ:
 | 
					            if op is Operators.EQ:
 | 
				
			||||||
                result += f'"{value}"'
 | 
					                result += f'"{value}"'
 | 
				
			||||||
            elif op is Operators.NE:
 | 
					            elif op is Operators.NE:
 | 
				
			||||||
| 
						 | 
					@ -458,7 +470,7 @@ class FindQuery:
 | 
				
			||||||
                isinstance(expression.left, NegatedExpression):
 | 
					                isinstance(expression.left, NegatedExpression):
 | 
				
			||||||
            result += f"({cls.resolve_redisearch_query(expression.left)})"
 | 
					            result += f"({cls.resolve_redisearch_query(expression.left)})"
 | 
				
			||||||
        elif isinstance(expression.left, ModelField):
 | 
					        elif isinstance(expression.left, ModelField):
 | 
				
			||||||
            field_type = cls.resolve_field_type(expression.left)
 | 
					            field_type = cls.resolve_field_type(expression.left, expression.op)
 | 
				
			||||||
            field_name = expression.left.name
 | 
					            field_name = expression.left.name
 | 
				
			||||||
            field_info = expression.left.field_info
 | 
					            field_info = expression.left.field_info
 | 
				
			||||||
            if not field_info or not getattr(field_info, "index", None):
 | 
					            if not field_info or not getattr(field_info, "index", None):
 | 
				
			||||||
| 
						 | 
					@ -796,8 +808,6 @@ class ModelMeta(ModelMetaclass):
 | 
				
			||||||
        # Create proxies for each model field so that we can use the field
 | 
					        # Create proxies for each model field so that we can use the field
 | 
				
			||||||
        # in queries, like Model.get(Model.field_name == 1)
 | 
					        # in queries, like Model.get(Model.field_name == 1)
 | 
				
			||||||
        for field_name, field in new_class.__fields__.items():
 | 
					        for field_name, field in new_class.__fields__.items():
 | 
				
			||||||
            if new_class.__name__ == "Order":
 | 
					 | 
				
			||||||
                print(new_class.__fields__)
 | 
					 | 
				
			||||||
            setattr(new_class, field_name, ExpressionProxy(field, []))
 | 
					            setattr(new_class, field_name, ExpressionProxy(field, []))
 | 
				
			||||||
            # Check if this is our FieldInfo version with extended ORM metadata.
 | 
					            # Check if this is our FieldInfo version with extended ORM metadata.
 | 
				
			||||||
            if isinstance(field.field_info, FieldInfo):
 | 
					            if isinstance(field.field_info, FieldInfo):
 | 
				
			||||||
| 
						 | 
					@ -1160,7 +1170,6 @@ class JsonModel(RedisModel, abc.ABC):
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                schema_part = f"{path} AS {index_field_name} TAG"
 | 
					                schema_part = f"{path} AS {index_field_name} TAG"
 | 
				
			||||||
            # TODO: GEO field
 | 
					            # TODO: GEO field
 | 
				
			||||||
            if should_index:
 | 
					 | 
				
			||||||
            schema_part += " SORTABLE"
 | 
					            schema_part += " SORTABLE"
 | 
				
			||||||
            return schema_part
 | 
					            return schema_part
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,7 +29,7 @@ class EmbeddedJsonModel(BaseJsonModel, abc.ABC):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Note(EmbeddedJsonModel):
 | 
					class Note(EmbeddedJsonModel):
 | 
				
			||||||
    description: str = Field(index=True)
 | 
					    description: str = Field(index=True, full_text_search=True)
 | 
				
			||||||
    created_on: datetime.datetime
 | 
					    created_on: datetime.datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,8 +45,7 @@ class Address(EmbeddedJsonModel):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Item(EmbeddedJsonModel):
 | 
					class Item(EmbeddedJsonModel):
 | 
				
			||||||
    price: decimal.Decimal
 | 
					    price: decimal.Decimal
 | 
				
			||||||
    # name: str = Field(index=True, full_text_search=True)
 | 
					    name: str = Field(index=True, full_text_search=True)
 | 
				
			||||||
    name: str = Field(index=True)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Order(EmbeddedJsonModel):
 | 
					class Order(EmbeddedJsonModel):
 | 
				
			||||||
| 
						 | 
					@ -297,6 +296,34 @@ def test_recursive_query_field_resolution(members):
 | 
				
			||||||
    assert actual == [member1]
 | 
					    assert actual == [member1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_full_text_search(members):
 | 
				
			||||||
 | 
					    member1, member2, _ = members
 | 
				
			||||||
 | 
					    member1.address.note = Note(description="white house",
 | 
				
			||||||
 | 
					                                created_on=datetime.datetime.now())
 | 
				
			||||||
 | 
					    member2.address.note = Note(description="blue house",
 | 
				
			||||||
 | 
					                                created_on=datetime.datetime.now())
 | 
				
			||||||
 | 
					    member1.save()
 | 
				
			||||||
 | 
					    member2.save()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    actual = Member.find(Member.address.note.description % "white").all()
 | 
				
			||||||
 | 
					    assert actual == [member1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    member1.orders = [
 | 
				
			||||||
 | 
					        Order(items=[Item(price=10.99, name="balls")],
 | 
				
			||||||
 | 
					              total=10.99,
 | 
				
			||||||
 | 
					              created_on=datetime.datetime.now())
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    member2.orders = [
 | 
				
			||||||
 | 
					        Order(items=[Item(price=10.99, name="white ball")],
 | 
				
			||||||
 | 
					              total=10.99,
 | 
				
			||||||
 | 
					              created_on=datetime.datetime.now())
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    member1.save()
 | 
				
			||||||
 | 
					    member2.save()
 | 
				
			||||||
 | 
					    actual = Member.find(Member.orders.items.name % "ball").all()
 | 
				
			||||||
 | 
					    assert actual == [member1, member2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_tag_queries_boolean_logic(members):
 | 
					def test_tag_queries_boolean_logic(members):
 | 
				
			||||||
    member1, member2, member3 = members
 | 
					    member1, member2, member3 = members
 | 
				
			||||||
| 
						 | 
					@ -456,4 +483,4 @@ def test_not_found():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_schema():
 | 
					def test_schema():
 | 
				
			||||||
    assert Member.redisearch_schema() == "ON JSON PREFIX 1 redis-developer:tests.test_json_model.Member: SCHEMA $.pk AS pk TAG SEPARATOR | SORTABLE $.first_name AS first_name TAG SEPARATOR | SORTABLE $.last_name AS last_name TAG SEPARATOR | SORTABLE $.email AS email TAG SEPARATOR | SORTABLE  $.age AS age NUMERIC SORTABLE $.address.pk AS address_pk TAG SEPARATOR | SORTABLE $.address.city AS address_city TAG SEPARATOR | SORTABLE $.address.postal_code AS address_postal_code TAG SEPARATOR | SORTABLE $.address.note.pk AS address_note_pk TAG SEPARATOR | SORTABLE $.address.note.description AS address_note_description TAG SEPARATOR | SORTABLE $.orders[*].pk AS orders_pk TAG SEPARATOR | SORTABLE $.orders[*].items[*].pk AS orders_items_pk TAG SEPARATOR | SORTABLE $.orders[*].items[*].name AS orders_items_name TAG SEPARATOR | SORTABLE"
 | 
					    assert Member.redisearch_schema() == "ON JSON PREFIX 1 redis-developer:tests.test_json_model.Member: SCHEMA $.pk AS pk TAG SEPARATOR | SORTABLE $.first_name AS first_name TAG SEPARATOR | SORTABLE $.last_name AS last_name TAG SEPARATOR | SORTABLE $.email AS email TAG SEPARATOR | SORTABLE  $.age AS age NUMERIC SORTABLE $.address.pk AS address_pk TAG SEPARATOR | SORTABLE $.address.city AS address_city TAG SEPARATOR | SORTABLE $.address.postal_code AS address_postal_code TAG SEPARATOR | SORTABLE $.address.note.pk AS address_note_pk TAG SEPARATOR | SORTABLE $.address.note.description AS address_note_description TAG SEPARATOR | $.address.note.description AS address_note_description_fts TEXT SORTABLE $.orders[*].pk AS orders_pk TAG SEPARATOR | SORTABLE $.orders[*].items[*].pk AS orders_items_pk TAG SEPARATOR | SORTABLE $.orders[*].items[*].name AS orders_items_name TAG SEPARATOR | $.orders[*].items[*].name AS orders_items_name_fts TEXT SORTABLE"
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue