88from copy import copy
99from enum import Enum
1010from functools import reduce
11- from typing import (
12- Any ,
13- Callable ,
14- Dict ,
15- List ,
16- Literal ,
17- Mapping ,
18- Optional ,
19- Sequence ,
20- Set ,
21- Tuple ,
22- Type ,
23- TypeVar ,
24- Union ,
25- )
11+ from typing import (Any , Callable , Dict , List , Literal , Mapping , Optional ,
12+ Sequence , Set , Tuple , Type , TypeVar , Union )
2613from typing import get_args as typing_get_args
27- from typing import (
28- no_type_check ,
29- )
14+ from typing import no_type_check
3015
3116from more_itertools import ichunked
3217from pydantic import BaseModel
@@ -2142,6 +2127,7 @@ def __init__(self, default: Any = ..., **kwargs: Any) -> None:
21422127 full_text_search = kwargs .pop ("full_text_search" , None )
21432128 vector_options = kwargs .pop ("vector_options" , None )
21442129 expire = kwargs .pop ("expire" , None )
2130+ separator = kwargs .pop ("separator" , SINGLE_VALUE_TAG_FIELD_SEPARATOR )
21452131 super ().__init__ (default = default , ** kwargs )
21462132 self .primary_key = primary_key
21472133 self .sortable = sortable
@@ -2150,6 +2136,7 @@ def __init__(self, default: Any = ..., **kwargs: Any) -> None:
21502136 self .full_text_search = full_text_search
21512137 self .vector_options = vector_options
21522138 self .expire = expire
2139+ self .separator = separator
21532140
21542141
21552142class RelationshipInfo (Representation ):
@@ -2261,6 +2248,7 @@ def Field(
22612248 full_text_search : Union [bool , UndefinedType ] = Undefined ,
22622249 vector_options : Optional [VectorFieldOptions ] = None ,
22632250 expire : Optional [int ] = None ,
2251+ separator : str = SINGLE_VALUE_TAG_FIELD_SEPARATOR ,
22642252 ** kwargs : Unpack [_FromFieldInfoInputs ],
22652253) -> Any :
22662254 """
@@ -2276,6 +2264,8 @@ def Field(
22762264 vector_options: Vector field configuration for similarity search.
22772265 expire: TTL in seconds for this field (HashModel only, requires Redis 7.4+).
22782266 When set, the field will automatically expire after save().
2267+ separator: TAG field separator character for RediSearch indexing.
2268+ Defaults to "|". Use "," for comma-separated multi-value fields.
22792269 **kwargs: Additional Pydantic field options.
22802270
22812271 Returns:
@@ -2291,6 +2281,7 @@ def Field(
22912281 full_text_search = full_text_search ,
22922282 vector_options = vector_options ,
22932283 expire = expire ,
2284+ separator = separator ,
22942285 )
22952286 return field_info
22962287
@@ -3286,9 +3277,10 @@ def schema_for_fields(cls):
32863277
32873278 if getattr (field_info , "primary_key" , None ) is True :
32883279 if issubclass (_type , str ):
3289- redisearch_field = (
3290- f" { name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3280+ separator = getattr (
3281+ field_info , "separator" , SINGLE_VALUE_TAG_FIELD_SEPARATOR
32913282 )
3283+ redisearch_field = f"{ name } TAG SEPARATOR { separator } "
32923284 else :
32933285 redisearch_field = cls .schema_for_type (name , _type , field_info )
32943286 schema_parts .append (redisearch_field )
@@ -3346,13 +3338,15 @@ def schema_for_type(cls, name, typ: Any, field_info: PydanticFieldInfo):
33463338 else :
33473339 schema = f"{ name } NUMERIC"
33483340 elif issubclass (typ , str ):
3341+ separator = getattr (
3342+ field_info , "separator" , SINGLE_VALUE_TAG_FIELD_SEPARATOR
3343+ )
33493344 if getattr (field_info , "full_text_search" , False ) is True :
33503345 schema = (
3351- f"{ name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3352- f"{ name } AS { name } _fts TEXT"
3346+ f"{ name } TAG SEPARATOR { separator } " f"{ name } AS { name } _fts TEXT"
33533347 )
33543348 else :
3355- schema = f"{ name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3349+ schema = f"{ name } TAG SEPARATOR { separator } "
33563350 elif issubclass (typ , RedisModel ):
33573351 sub_fields = []
33583352 for embedded_name , field in typ .model_fields .items ():
@@ -3363,7 +3357,10 @@ def schema_for_type(cls, name, typ: Any, field_info: PydanticFieldInfo):
33633357 )
33643358 schema = " " .join (sub_fields )
33653359 else :
3366- schema = f"{ name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3360+ separator = getattr (
3361+ field_info , "separator" , SINGLE_VALUE_TAG_FIELD_SEPARATOR
3362+ )
3363+ schema = f"{ name } TAG SEPARATOR { separator } "
33673364 if schema and sortable is True :
33683365 schema += " SORTABLE"
33693366 if schema and case_sensitive is True :
@@ -3627,7 +3624,10 @@ def schema_for_fields(cls):
36273624
36283625 if getattr (field_info , "primary_key" , None ) is True :
36293626 if issubclass (_type , str ):
3630- redisearch_field = f"$.{ name } AS { name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3627+ separator = getattr (
3628+ field_info , "separator" , SINGLE_VALUE_TAG_FIELD_SEPARATOR
3629+ )
3630+ redisearch_field = f"$.{ name } AS { name } TAG SEPARATOR { separator } "
36313631 else :
36323632 redisearch_field = cls .schema_for_type (
36333633 json_path , name , "" , _type , field_info
@@ -3781,6 +3781,11 @@ def schema_for_type(
37813781 else typ
37823782 )
37833783
3784+ # Get separator from field_info, defaulting to pipe
3785+ separator = getattr (
3786+ field_info , "separator" , SINGLE_VALUE_TAG_FIELD_SEPARATOR
3787+ )
3788+
37843789 if is_vector and vector_options :
37853790 schema = f"{ path } AS { index_field_name } { vector_options .schema } "
37863791 elif parent_is_container_type or parent_is_model_in_container :
@@ -3795,7 +3800,7 @@ def schema_for_type(
37953800 f"search. Problem field: { name } . Docs: { ERRORS_URL } #E13"
37963801 )
37973802 # List/tuple fields are indexed as TAG fields and can be sortable
3798- schema = f"{ path } AS { index_field_name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3803+ schema = f"{ path } AS { index_field_name } TAG SEPARATOR { separator } "
37993804 if sortable is True :
38003805 schema += " SORTABLE"
38013806 if case_sensitive is True :
@@ -3815,7 +3820,7 @@ def schema_for_type(
38153820 elif issubclass (typ , str ):
38163821 if full_text_search is True :
38173822 schema = (
3818- f"{ path } AS { index_field_name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3823+ f"{ path } AS { index_field_name } TAG SEPARATOR { separator } "
38193824 f"{ path } AS { index_field_name } _fts TEXT"
38203825 )
38213826 if sortable is True :
@@ -3829,14 +3834,14 @@ def schema_for_type(
38293834 raise RedisModelError ("Text fields cannot be case-sensitive." )
38303835 else :
38313836 # String fields are indexed as TAG fields and can be sortable
3832- schema = f"{ path } AS { index_field_name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3837+ schema = f"{ path } AS { index_field_name } TAG SEPARATOR { separator } "
38333838 if sortable is True :
38343839 schema += " SORTABLE"
38353840 if case_sensitive is True :
38363841 schema += " CASESENSITIVE"
38373842 else :
38383843 # Default to TAG field, which can be sortable
3839- schema = f"{ path } AS { index_field_name } TAG SEPARATOR { SINGLE_VALUE_TAG_FIELD_SEPARATOR } "
3844+ schema = f"{ path } AS { index_field_name } TAG SEPARATOR { separator } "
38403845 if sortable is True :
38413846 schema += " SORTABLE"
38423847
0 commit comments