diff --git a/src/prisma/generator/models.py b/src/prisma/generator/models.py index 876633fec..181af1a6c 100644 --- a/src/prisma/generator/models.py +++ b/src/prisma/generator/models.py @@ -787,6 +787,8 @@ def where_input_type(self) -> str: return f"'types.{typ}ListFilter'" if typ in FILTER_TYPES: + if self.is_optional: + return f"Union[None, {self._actual_python_type}, 'types.{typ}Filter']" return f"Union[{self._actual_python_type}, 'types.{typ}Filter']" return self.python_type diff --git a/src/prisma/generator/templates/fields.py.jinja b/src/prisma/generator/templates/fields.py.jinja index ffebd1fbb..fe0a47ead 100644 --- a/src/prisma/generator/templates/fields.py.jinja +++ b/src/prisma/generator/templates/fields.py.jinja @@ -116,8 +116,11 @@ class Base64: @classmethod def _internal_from_prisma( cls, - value: Union[str, 'Base64', List[Union[str, 'Base64']]] - ) -> Union['Base64', List['Base64']]: + value: Union[None, str, 'Base64', List[Union[str, 'Base64']]] + ) -> Union[None, 'Base64', List['Base64']]: + if value is None: + return None + if isinstance(value, Base64): return value diff --git a/src/prisma/generator/templates/models.py.jinja b/src/prisma/generator/templates/models.py.jinja index 3206462d1..dff6c9dd3 100644 --- a/src/prisma/generator/templates/models.py.jinja +++ b/src/prisma/generator/templates/models.py.jinja @@ -218,7 +218,7 @@ class {{ model.name }}(BaseModel): {% for field in model.get_fields_of_type('Bytes') %} @validator('{{ field.name }}', pre=True, allow_reuse=True) @classmethod - def _prisma_{{ field.name }}_bytes_validator(cls, value: Any) -> Union[fields.Base64, List[fields.Base64]]: + def _prisma_{{ field.name }}_bytes_validator(cls, value: Any) -> Union[None, fields.Base64, List[fields.Base64]]: return fields.Base64._internal_from_prisma(value) {% endfor %} diff --git a/tests/data/schema.prisma b/tests/data/schema.prisma index 3566ba10f..695cf39c2 100644 --- a/tests/data/schema.prisma +++ b/tests/data/schema.prisma @@ -51,13 +51,23 @@ model Profile { // model that just exists for testing different schema types model Types { - id Int @id @default(autoincrement()) - bool_ Boolean @default(false) - string String @default("") - bytes Bytes @default("") - bigint BigInt @default(0) - integer Int @default(0) - float_ Float @default(0) - datetime DateTime @default(now()) - decimal Decimal @default(1) + id Int @id @default(autoincrement()) + bool_ Boolean @default(false) + string String @default("") + bytes Bytes @default("") + bigint BigInt @default(0) + integer Int @default(0) + float_ Float @default(0) + datetime_ DateTime @default(now()) + decimal_ Decimal @default(1) + + optional_int Int? @default(0) + optional_bool Boolean? @default(false) + optional_string String? @default("") + optional_bytes Bytes? @default("") + optional_bigint BigInt? @default(0) + optional_integer Int? @default(0) + optional_float Float? @default(0) + optional_datetime DateTime? @default(now()) + optional_decimal Decimal? @default(1) } diff --git a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[fields.py].raw b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[fields.py].raw index 6716016be..72cad98be 100644 --- a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[fields.py].raw +++ b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[fields.py].raw @@ -144,8 +144,11 @@ class Base64: @classmethod def _internal_from_prisma( cls, - value: Union[str, 'Base64', List[Union[str, 'Base64']]] - ) -> Union['Base64', List['Base64']]: + value: Union[None, str, 'Base64', List[Union[str, 'Base64']]] + ) -> Union[None, 'Base64', List['Base64']]: + if value is None: + return None + if isinstance(value, Base64): return value diff --git a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[models.py].raw b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[models.py].raw index ae2a78779..bba79b890 100644 --- a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[models.py].raw +++ b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[models.py].raw @@ -1025,7 +1025,7 @@ class Lists(BaseModel): @validator('bytes', pre=True, allow_reuse=True) @classmethod - def _prisma_bytes_bytes_validator(cls, value: Any) -> Union[fields.Base64, List[fields.Base64]]: + def _prisma_bytes_bytes_validator(cls, value: Any) -> Union[None, fields.Base64, List[fields.Base64]]: return fields.Base64._internal_from_prisma(value) class A(BaseModel): @@ -1455,7 +1455,7 @@ class D(BaseModel): @validator('binary', pre=True, allow_reuse=True) @classmethod - def _prisma_binary_bytes_validator(cls, value: Any) -> Union[fields.Base64, List[fields.Base64]]: + def _prisma_binary_bytes_validator(cls, value: Any) -> Union[None, fields.Base64, List[fields.Base64]]: return fields.Base64._internal_from_prisma(value) class E(BaseModel): diff --git a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[types.py].raw b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[types.py].raw index 03384d23d..b84e78ecc 100644 --- a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[types.py].raw +++ b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_async[types.py].raw @@ -1885,7 +1885,7 @@ class PostWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] created_at: Union[datetime.datetime, 'types.DateTimeFilter'] title: Union[str, 'types.StringFilter'] - content: Union[str, 'types.StringFilter'] + content: Union[None, str, 'types.StringFilter'] published: Union[bool, 'types.BooleanFilter'] author: 'UserRelationFilter' author_id: Union[int, 'types.IntFilter'] @@ -1902,7 +1902,7 @@ class PostWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] created_at: Union[datetime.datetime, 'types.DateTimeFilter'] title: Union[str, 'types.StringFilter'] - content: Union[str, 'types.StringFilter'] + content: Union[None, str, 'types.StringFilter'] published: Union[bool, 'types.BooleanFilter'] author: 'UserRelationFilter' author_id: Union[int, 'types.IntFilter'] @@ -1919,7 +1919,7 @@ class PostWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] created_at: Union[datetime.datetime, 'types.DateTimeFilter'] title: Union[str, 'types.StringFilter'] - content: Union[str, 'types.StringFilter'] + content: Union[None, str, 'types.StringFilter'] published: Union[bool, 'types.BooleanFilter'] author: 'UserRelationFilter' author_id: Union[int, 'types.IntFilter'] @@ -3098,15 +3098,15 @@ class UserWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] email: Union[str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] posts: 'PostListRelationFilter' # should be noted that AND and NOT should be Union['UserWhereInputRecursive1', List['UserWhereInputRecursive1']] @@ -3121,15 +3121,15 @@ class UserWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] email: Union[str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] posts: 'PostListRelationFilter' # should be noted that AND and NOT should be Union['UserWhereInputRecursive2', List['UserWhereInputRecursive2']] @@ -3144,15 +3144,15 @@ class UserWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] email: Union[str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] posts: 'PostListRelationFilter' @@ -4380,15 +4380,15 @@ class MWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] n: 'NListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['MWhereInputRecursive1', List['MWhereInputRecursive1']] # but this causes mypy to hang :/ @@ -4402,15 +4402,15 @@ class MWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] n: 'NListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['MWhereInputRecursive2', List['MWhereInputRecursive2']] # but this causes mypy to hang :/ @@ -4424,15 +4424,15 @@ class MWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] n: 'NListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] @@ -5674,17 +5674,17 @@ class NWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] m: 'MListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] json_: Union['fields.Json', 'types.JsonFilter'] - optional_json: Union['fields.Json', 'types.JsonFilter'] + optional_json: Union[None, 'fields.Json', 'types.JsonFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['NWhereInputRecursive1', List['NWhereInputRecursive1']] # but this causes mypy to hang :/ @@ -5698,17 +5698,17 @@ class NWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] m: 'MListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] json_: Union['fields.Json', 'types.JsonFilter'] - optional_json: Union['fields.Json', 'types.JsonFilter'] + optional_json: Union[None, 'fields.Json', 'types.JsonFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['NWhereInputRecursive2', List['NWhereInputRecursive2']] # but this causes mypy to hang :/ @@ -5722,17 +5722,17 @@ class NWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] m: 'MListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] json_: Union['fields.Json', 'types.JsonFilter'] - optional_json: Union['fields.Json', 'types.JsonFilter'] + optional_json: Union[None, 'fields.Json', 'types.JsonFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] @@ -6970,15 +6970,15 @@ class OneOptionalWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] many: 'ManyRequiredListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['OneOptionalWhereInputRecursive1', List['OneOptionalWhereInputRecursive1']] # but this causes mypy to hang :/ @@ -6992,15 +6992,15 @@ class OneOptionalWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] many: 'ManyRequiredListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['OneOptionalWhereInputRecursive2', List['OneOptionalWhereInputRecursive2']] # but this causes mypy to hang :/ @@ -7014,15 +7014,15 @@ class OneOptionalWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] many: 'ManyRequiredListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] @@ -8246,17 +8246,17 @@ class ManyRequiredWhereInput(TypedDict, total=False): """ManyRequired arguments for searching""" id: Union[int, 'types.IntFilter'] one: 'OneOptionalRelationFilter' - one_optional_id: Union[int, 'types.IntFilter'] + one_optional_id: Union[None, int, 'types.IntFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['ManyRequiredWhereInputRecursive1', List['ManyRequiredWhereInputRecursive1']] # but this causes mypy to hang :/ @@ -8269,17 +8269,17 @@ class ManyRequiredWhereInputRecursive1(TypedDict, total=False): """ManyRequired arguments for searching""" id: Union[int, 'types.IntFilter'] one: 'OneOptionalRelationFilter' - one_optional_id: Union[int, 'types.IntFilter'] + one_optional_id: Union[None, int, 'types.IntFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['ManyRequiredWhereInputRecursive2', List['ManyRequiredWhereInputRecursive2']] # but this causes mypy to hang :/ @@ -8292,17 +8292,17 @@ class ManyRequiredWhereInputRecursive2(TypedDict, total=False): """ManyRequired arguments for searching""" id: Union[int, 'types.IntFilter'] one: 'OneOptionalRelationFilter' - one_optional_id: Union[int, 'types.IntFilter'] + one_optional_id: Union[None, int, 'types.IntFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] @@ -10748,7 +10748,7 @@ FindFirstAArgs = FindManyAArgsFromA class AWhereInput(TypedDict, total=False): """A arguments for searching""" email: Union[str, 'types.StringFilter'] - name: Union[str, 'types.StringFilter'] + name: Union[None, str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] sInt: Union[int, 'types.IntFilter'] inc_int: Union[int, 'types.IntFilter'] @@ -10767,7 +10767,7 @@ class AWhereInput(TypedDict, total=False): class AWhereInputRecursive1(TypedDict, total=False): """A arguments for searching""" email: Union[str, 'types.StringFilter'] - name: Union[str, 'types.StringFilter'] + name: Union[None, str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] sInt: Union[int, 'types.IntFilter'] inc_int: Union[int, 'types.IntFilter'] @@ -10786,7 +10786,7 @@ class AWhereInputRecursive1(TypedDict, total=False): class AWhereInputRecursive2(TypedDict, total=False): """A arguments for searching""" email: Union[str, 'types.StringFilter'] - name: Union[str, 'types.StringFilter'] + name: Union[None, str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] sInt: Union[int, 'types.IntFilter'] inc_int: Union[int, 'types.IntFilter'] diff --git a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[fields.py].raw b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[fields.py].raw index 6716016be..72cad98be 100644 --- a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[fields.py].raw +++ b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[fields.py].raw @@ -144,8 +144,11 @@ class Base64: @classmethod def _internal_from_prisma( cls, - value: Union[str, 'Base64', List[Union[str, 'Base64']]] - ) -> Union['Base64', List['Base64']]: + value: Union[None, str, 'Base64', List[Union[str, 'Base64']]] + ) -> Union[None, 'Base64', List['Base64']]: + if value is None: + return None + if isinstance(value, Base64): return value diff --git a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[models.py].raw b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[models.py].raw index ae2a78779..bba79b890 100644 --- a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[models.py].raw +++ b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[models.py].raw @@ -1025,7 +1025,7 @@ class Lists(BaseModel): @validator('bytes', pre=True, allow_reuse=True) @classmethod - def _prisma_bytes_bytes_validator(cls, value: Any) -> Union[fields.Base64, List[fields.Base64]]: + def _prisma_bytes_bytes_validator(cls, value: Any) -> Union[None, fields.Base64, List[fields.Base64]]: return fields.Base64._internal_from_prisma(value) class A(BaseModel): @@ -1455,7 +1455,7 @@ class D(BaseModel): @validator('binary', pre=True, allow_reuse=True) @classmethod - def _prisma_binary_bytes_validator(cls, value: Any) -> Union[fields.Base64, List[fields.Base64]]: + def _prisma_binary_bytes_validator(cls, value: Any) -> Union[None, fields.Base64, List[fields.Base64]]: return fields.Base64._internal_from_prisma(value) class E(BaseModel): diff --git a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[types.py].raw b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[types.py].raw index 03384d23d..b84e78ecc 100644 --- a/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[types.py].raw +++ b/tests/test_generation/exhaustive/__snapshots__/test_exhaustive/test_sync[types.py].raw @@ -1885,7 +1885,7 @@ class PostWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] created_at: Union[datetime.datetime, 'types.DateTimeFilter'] title: Union[str, 'types.StringFilter'] - content: Union[str, 'types.StringFilter'] + content: Union[None, str, 'types.StringFilter'] published: Union[bool, 'types.BooleanFilter'] author: 'UserRelationFilter' author_id: Union[int, 'types.IntFilter'] @@ -1902,7 +1902,7 @@ class PostWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] created_at: Union[datetime.datetime, 'types.DateTimeFilter'] title: Union[str, 'types.StringFilter'] - content: Union[str, 'types.StringFilter'] + content: Union[None, str, 'types.StringFilter'] published: Union[bool, 'types.BooleanFilter'] author: 'UserRelationFilter' author_id: Union[int, 'types.IntFilter'] @@ -1919,7 +1919,7 @@ class PostWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] created_at: Union[datetime.datetime, 'types.DateTimeFilter'] title: Union[str, 'types.StringFilter'] - content: Union[str, 'types.StringFilter'] + content: Union[None, str, 'types.StringFilter'] published: Union[bool, 'types.BooleanFilter'] author: 'UserRelationFilter' author_id: Union[int, 'types.IntFilter'] @@ -3098,15 +3098,15 @@ class UserWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] email: Union[str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] posts: 'PostListRelationFilter' # should be noted that AND and NOT should be Union['UserWhereInputRecursive1', List['UserWhereInputRecursive1']] @@ -3121,15 +3121,15 @@ class UserWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] email: Union[str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] posts: 'PostListRelationFilter' # should be noted that AND and NOT should be Union['UserWhereInputRecursive2', List['UserWhereInputRecursive2']] @@ -3144,15 +3144,15 @@ class UserWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] email: Union[str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] posts: 'PostListRelationFilter' @@ -4380,15 +4380,15 @@ class MWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] n: 'NListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['MWhereInputRecursive1', List['MWhereInputRecursive1']] # but this causes mypy to hang :/ @@ -4402,15 +4402,15 @@ class MWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] n: 'NListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['MWhereInputRecursive2', List['MWhereInputRecursive2']] # but this causes mypy to hang :/ @@ -4424,15 +4424,15 @@ class MWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] n: 'NListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] @@ -5674,17 +5674,17 @@ class NWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] m: 'MListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] json_: Union['fields.Json', 'types.JsonFilter'] - optional_json: Union['fields.Json', 'types.JsonFilter'] + optional_json: Union[None, 'fields.Json', 'types.JsonFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['NWhereInputRecursive1', List['NWhereInputRecursive1']] # but this causes mypy to hang :/ @@ -5698,17 +5698,17 @@ class NWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] m: 'MListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] json_: Union['fields.Json', 'types.JsonFilter'] - optional_json: Union['fields.Json', 'types.JsonFilter'] + optional_json: Union[None, 'fields.Json', 'types.JsonFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['NWhereInputRecursive2', List['NWhereInputRecursive2']] # but this causes mypy to hang :/ @@ -5722,17 +5722,17 @@ class NWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] m: 'MListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] json_: Union['fields.Json', 'types.JsonFilter'] - optional_json: Union['fields.Json', 'types.JsonFilter'] + optional_json: Union[None, 'fields.Json', 'types.JsonFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] @@ -6970,15 +6970,15 @@ class OneOptionalWhereInput(TypedDict, total=False): id: Union[int, 'types.IntFilter'] many: 'ManyRequiredListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['OneOptionalWhereInputRecursive1', List['OneOptionalWhereInputRecursive1']] # but this causes mypy to hang :/ @@ -6992,15 +6992,15 @@ class OneOptionalWhereInputRecursive1(TypedDict, total=False): id: Union[int, 'types.IntFilter'] many: 'ManyRequiredListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['OneOptionalWhereInputRecursive2', List['OneOptionalWhereInputRecursive2']] # but this causes mypy to hang :/ @@ -7014,15 +7014,15 @@ class OneOptionalWhereInputRecursive2(TypedDict, total=False): id: Union[int, 'types.IntFilter'] many: 'ManyRequiredListRelationFilter' int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] @@ -8246,17 +8246,17 @@ class ManyRequiredWhereInput(TypedDict, total=False): """ManyRequired arguments for searching""" id: Union[int, 'types.IntFilter'] one: 'OneOptionalRelationFilter' - one_optional_id: Union[int, 'types.IntFilter'] + one_optional_id: Union[None, int, 'types.IntFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['ManyRequiredWhereInputRecursive1', List['ManyRequiredWhereInputRecursive1']] # but this causes mypy to hang :/ @@ -8269,17 +8269,17 @@ class ManyRequiredWhereInputRecursive1(TypedDict, total=False): """ManyRequired arguments for searching""" id: Union[int, 'types.IntFilter'] one: 'OneOptionalRelationFilter' - one_optional_id: Union[int, 'types.IntFilter'] + one_optional_id: Union[None, int, 'types.IntFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] # should be noted that AND and NOT should be Union['ManyRequiredWhereInputRecursive2', List['ManyRequiredWhereInputRecursive2']] # but this causes mypy to hang :/ @@ -8292,17 +8292,17 @@ class ManyRequiredWhereInputRecursive2(TypedDict, total=False): """ManyRequired arguments for searching""" id: Union[int, 'types.IntFilter'] one: 'OneOptionalRelationFilter' - one_optional_id: Union[int, 'types.IntFilter'] + one_optional_id: Union[None, int, 'types.IntFilter'] int: Union[int, 'types.IntFilter'] - optional_int: Union[int, 'types.IntFilter'] + optional_int: Union[None, int, 'types.IntFilter'] float: Union[float, 'types.FloatFilter'] - optional_float: Union[float, 'types.FloatFilter'] + optional_float: Union[None, float, 'types.FloatFilter'] string: Union[str, 'types.StringFilter'] - optional_string: Union[str, 'types.StringFilter'] + optional_string: Union[None, str, 'types.StringFilter'] enum: 'enums.ABeautifulEnum' optional_enum: 'enums.ABeautifulEnum' boolean: Union[bool, 'types.BooleanFilter'] - optional_boolean: Union[bool, 'types.BooleanFilter'] + optional_boolean: Union[None, bool, 'types.BooleanFilter'] @@ -10748,7 +10748,7 @@ FindFirstAArgs = FindManyAArgsFromA class AWhereInput(TypedDict, total=False): """A arguments for searching""" email: Union[str, 'types.StringFilter'] - name: Union[str, 'types.StringFilter'] + name: Union[None, str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] sInt: Union[int, 'types.IntFilter'] inc_int: Union[int, 'types.IntFilter'] @@ -10767,7 +10767,7 @@ class AWhereInput(TypedDict, total=False): class AWhereInputRecursive1(TypedDict, total=False): """A arguments for searching""" email: Union[str, 'types.StringFilter'] - name: Union[str, 'types.StringFilter'] + name: Union[None, str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] sInt: Union[int, 'types.IntFilter'] inc_int: Union[int, 'types.IntFilter'] @@ -10786,7 +10786,7 @@ class AWhereInputRecursive1(TypedDict, total=False): class AWhereInputRecursive2(TypedDict, total=False): """A arguments for searching""" email: Union[str, 'types.StringFilter'] - name: Union[str, 'types.StringFilter'] + name: Union[None, str, 'types.StringFilter'] int: Union[int, 'types.IntFilter'] sInt: Union[int, 'types.IntFilter'] inc_int: Union[int, 'types.IntFilter'] diff --git a/tests/test_types/test_bigint.py b/tests/test_types/test_bigint.py index 636fb4efe..ee66d9159 100644 --- a/tests/test_types/test_bigint.py +++ b/tests/test_types/test_bigint.py @@ -205,3 +205,60 @@ async def test_atomic_update_invalid_input(client: Prisma) -> None: message = exc.value.args[0] assert isinstance(message, str) assert 'Expected exactly one field to be present, got 2' in message + + +@pytest.mark.asyncio +async def test_filtering_nulls(client: Prisma) -> None: + """None is a valid filter for nullable BigInt fields""" + await client.types.create( + { + 'string': 'a', + 'optional_bigint': None, + }, + ) + await client.types.create( + { + 'string': 'b', + 'optional_bigint': 12437823782382, + }, + ) + await client.types.create( + { + 'string': 'c', + 'optional_bigint': 8239829842494, + }, + ) + + found = await client.types.find_first( + where={ + 'NOT': [ + { + 'optional_bigint': None, + }, + ], + }, + order={ + 'string': 'asc', + }, + ) + assert found is not None + assert found.string == 'b' + assert found.optional_bigint == 12437823782382 + + count = await client.types.count( + where={ + 'optional_bigint': None, + }, + ) + assert count == 1 + + count = await client.types.count( + where={ + 'NOT': [ + { + 'optional_bigint': None, + }, + ], + }, + ) + assert count == 2 diff --git a/tests/test_types/test_bool.py b/tests/test_types/test_bool.py index 5e77495f2..b85116a0b 100644 --- a/tests/test_types/test_bool.py +++ b/tests/test_types/test_bool.py @@ -49,3 +49,60 @@ async def test_filtering(client: Prisma) -> None: ) assert found is not None assert found.bool_ is False + + +@pytest.mark.asyncio +async def test_filtering_nulls(client: Prisma) -> None: + """None is a valid filter for nullable Boolean fields""" + await client.types.create( + { + 'string': 'a', + 'optional_bool': None, + }, + ) + await client.types.create( + { + 'string': 'b', + 'optional_bool': True, + }, + ) + await client.types.create( + { + 'string': 'c', + 'optional_bool': False, + }, + ) + + found = await client.types.find_first( + where={ + 'NOT': [ + { + 'optional_bool': None, + }, + ], + }, + order={ + 'string': 'asc', + }, + ) + assert found is not None + assert found.string == 'b' + assert found.optional_bool is True + + count = await client.types.count( + where={ + 'optional_bool': None, + }, + ) + assert count == 1 + + count = await client.types.count( + where={ + 'NOT': [ + { + 'optional_bool': None, + }, + ], + }, + ) + assert count == 2 diff --git a/tests/test_types/test_bytes.py b/tests/test_types/test_bytes.py index d249ec53b..70c858fe9 100644 --- a/tests/test_types/test_bytes.py +++ b/tests/test_types/test_bytes.py @@ -104,3 +104,60 @@ async def test_constructing(client: Prisma) -> None: }, ) assert model.bytes == Base64.encode(b'foo') + + +@pytest.mark.asyncio +async def test_filtering_nulls(client: Prisma) -> None: + """None is a valid filter for nullable Bytes fields""" + await client.types.create( + { + 'string': 'a', + 'optional_bytes': None, + }, + ) + await client.types.create( + { + 'string': 'b', + 'optional_bytes': Base64.encode(b'foo'), + }, + ) + await client.types.create( + { + 'string': 'c', + 'optional_bytes': Base64.encode(b'bar'), + }, + ) + + found = await client.types.find_first( + where={ + 'NOT': [ + { + 'optional_bytes': None, + }, + ], + }, + order={ + 'string': 'asc', + }, + ) + assert found is not None + assert found.string == 'b' + assert found.optional_bytes == Base64.encode(b'foo') + + count = await client.types.count( + where={ + 'optional_bytes': None, + }, + ) + assert count == 1 + + count = await client.types.count( + where={ + 'NOT': [ + { + 'optional_bytes': None, + }, + ], + }, + ) + assert count == 2 diff --git a/tests/test_types/test_datetime.py b/tests/test_types/test_datetime.py index b2050ab64..185407962 100644 --- a/tests/test_types/test_datetime.py +++ b/tests/test_types/test_datetime.py @@ -12,27 +12,27 @@ async def test_filtering(client: Prisma) -> None: async with client.batch_() as batcher: for i in range(10): batcher.types.create( - {'datetime': now + datetime.timedelta(hours=i)} + {'datetime_': now + datetime.timedelta(hours=i)} ) total = await client.types.count( - where={'datetime': {'gte': now + datetime.timedelta(hours=5)}} + where={'datetime_': {'gte': now + datetime.timedelta(hours=5)}} ) assert total == 5 found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'equals': now, }, }, ) assert found is not None - assert_similar_time(found.datetime, now) + assert_similar_time(found.datetime_, now) results = await client.types.find_many( where={ - 'datetime': { + 'datetime_': { 'in': [ now + datetime.timedelta(hours=1), now + datetime.timedelta(hours=4), @@ -41,16 +41,20 @@ async def test_filtering(client: Prisma) -> None: }, }, order={ - 'datetime': 'asc', + 'datetime_': 'asc', }, ) assert len(results) == 2 - assert_similar_time(results[0].datetime, now + datetime.timedelta(hours=1)) - assert_similar_time(results[1].datetime, now + datetime.timedelta(hours=4)) + assert_similar_time( + results[0].datetime_, now + datetime.timedelta(hours=1) + ) + assert_similar_time( + results[1].datetime_, now + datetime.timedelta(hours=4) + ) found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'not_in': [ now, now + datetime.timedelta(hours=1), @@ -59,91 +63,91 @@ async def test_filtering(client: Prisma) -> None: }, }, order={ - 'datetime': 'asc', + 'datetime_': 'asc', }, ) assert found is not None - assert_similar_time(found.datetime, now + datetime.timedelta(hours=3)) + assert_similar_time(found.datetime_, now + datetime.timedelta(hours=3)) found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'lt': now + datetime.timedelta(hours=1), }, }, order={ - 'datetime': 'desc', + 'datetime_': 'desc', }, ) assert found is not None - assert_similar_time(found.datetime, now) + assert_similar_time(found.datetime_, now) found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'lte': now + datetime.timedelta(hours=1), }, }, order={ - 'datetime': 'desc', + 'datetime_': 'desc', }, ) assert found is not None - assert_similar_time(found.datetime, now + datetime.timedelta(hours=1)) + assert_similar_time(found.datetime_, now + datetime.timedelta(hours=1)) found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'gt': now, }, }, order={ - 'datetime': 'asc', + 'datetime_': 'asc', }, ) assert found is not None - assert_similar_time(found.datetime, now + datetime.timedelta(hours=1)) + assert_similar_time(found.datetime_, now + datetime.timedelta(hours=1)) found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'gte': now, }, }, order={ - 'datetime': 'asc', + 'datetime_': 'asc', }, ) assert found is not None - assert_similar_time(found.datetime, now) + assert_similar_time(found.datetime_, now) found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'not': now, }, }, order={ - 'datetime': 'asc', + 'datetime_': 'asc', }, ) assert found is not None - assert_similar_time(found.datetime, now + datetime.timedelta(hours=1)) + assert_similar_time(found.datetime_, now + datetime.timedelta(hours=1)) found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'not': { 'equals': now, }, }, }, order={ - 'datetime': 'asc', + 'datetime_': 'asc', }, ) assert found is not None - assert_similar_time(now + datetime.timedelta(hours=1), found.datetime) + assert_similar_time(now + datetime.timedelta(hours=1), found.datetime_) @pytest.mark.asyncio @@ -152,8 +156,8 @@ async def test_finds(client: Prisma) -> None: record = await client.types.create(data={}) found = await client.types.find_first( where={ - 'datetime': { - 'lt': record.datetime + datetime.timedelta(seconds=1), + 'datetime_': { + 'lt': record.datetime_ + datetime.timedelta(seconds=1), }, }, ) @@ -167,12 +171,71 @@ async def test_tz_aware(client: Prisma) -> None: record = await client.types.create(data={}) found = await client.types.find_first( where={ - 'datetime': { + 'datetime_': { 'lt': ( - record.datetime + datetime.timedelta(hours=1) + record.datetime_ + datetime.timedelta(hours=1) ).astimezone(datetime.timezone.max) } } ) assert found is not None assert found.id == record.id + + +@pytest.mark.asyncio +async def test_filtering_nulls(client: Prisma) -> None: + """None is a valid filter for nullable DateTime fields""" + now = datetime.datetime.now(datetime.timezone.utc) + await client.types.create( + { + 'string': 'a', + 'optional_datetime': None, + }, + ) + await client.types.create( + { + 'string': 'b', + 'optional_datetime': now, + }, + ) + await client.types.create( + { + 'string': 'c', + 'optional_datetime': now + datetime.timedelta(days=1), + }, + ) + + found = await client.types.find_first( + where={ + 'NOT': [ + { + 'optional_datetime': None, + }, + ], + }, + order={ + 'string': 'asc', + }, + ) + assert found is not None + assert found.string == 'b' + assert found.optional_datetime is not None + assert_similar_time(now, found.optional_datetime) + + count = await client.types.count( + where={ + 'optional_datetime': None, + }, + ) + assert count == 1 + + count = await client.types.count( + where={ + 'NOT': [ + { + 'optional_datetime': None, + }, + ], + }, + ) + assert count == 2 diff --git a/tests/test_types/test_decimal.py b/tests/test_types/test_decimal.py index 42783d566..0138b5444 100644 --- a/tests/test_types/test_decimal.py +++ b/tests/test_types/test_decimal.py @@ -19,21 +19,21 @@ async def test_serialising(client: Prisma) -> None: """Decimal values of any precision are correctly serialised / deserialised""" model = await client.types.create( data={ - 'decimal': Decimal(1), + 'decimal_': Decimal(1), }, ) - assert model.decimal == Decimal(1) + assert model.decimal_ == Decimal(1) getcontext().prec = 16 value = Decimal(1) / Decimal(7) model = await client.types.create( data={ - 'decimal': value, + 'decimal_': value, }, ) - assert value == model.decimal - assert str(model.decimal) == '0.1428571428571429' + assert value == model.decimal_ + assert str(model.decimal_) == '0.1428571428571429' # TODO: split up this test into multiple tests @@ -43,50 +43,50 @@ async def test_serialising(client: Prisma) -> None: async def test_filtering(client: Prisma) -> None: """Finding records by a Decimal value""" async with client.batch_() as batcher: - batcher.types.create({'decimal': Decimal(1)}) - batcher.types.create({'decimal': Decimal(2.1234)}) - batcher.types.create({'decimal': Decimal(3)}) + batcher.types.create({'decimal_': Decimal(1)}) + batcher.types.create({'decimal_': Decimal(2.1234)}) + batcher.types.create({'decimal_': Decimal(3)}) total = await client.types.count( where={ - 'decimal': Decimal(1), + 'decimal_': Decimal(1), }, ) assert total == 1 found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'equals': Decimal(2.1234), }, }, ) assert found is not None - assert str(found.decimal) == '2.1234' + assert str(found.decimal_) == '2.1234' results = await client.types.find_many( where={ - 'decimal': { + 'decimal_': { 'not_in': [Decimal(1), Decimal(3)], }, }, ) assert len(results) == 1 - assert results[0].decimal == Decimal('2.1234') + assert results[0].decimal_ == Decimal('2.1234') results = await client.types.find_many( where={ - 'decimal': { + 'decimal_': { 'lt': Decimal(2), }, }, ) assert len(results) == 1 - assert results[0].decimal == Decimal(1) + assert results[0].decimal_ == Decimal(1) found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'lt': Decimal(1), }, }, @@ -95,7 +95,7 @@ async def test_filtering(client: Prisma) -> None: results = await client.types.find_many( where={ - 'decimal': { + 'decimal_': { 'lte': Decimal(3), }, }, @@ -104,17 +104,17 @@ async def test_filtering(client: Prisma) -> None: found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'lte': Decimal(1), }, }, ) assert found is not None - assert found.decimal == Decimal(1) + assert found.decimal_ == Decimal(1) found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'lte': Decimal('0.99999'), }, }, @@ -123,17 +123,17 @@ async def test_filtering(client: Prisma) -> None: found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'gt': Decimal('0.99999'), }, }, ) assert found is not None - assert found.decimal == Decimal(1) + assert found.decimal_ == Decimal(1) found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'gt': Decimal('4'), }, }, @@ -142,17 +142,17 @@ async def test_filtering(client: Prisma) -> None: found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'gte': Decimal('1'), }, }, ) assert found is not None - assert found.decimal == Decimal('1') + assert found.decimal_ == Decimal('1') found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'gte': Decimal('4'), }, }, @@ -161,40 +161,97 @@ async def test_filtering(client: Prisma) -> None: results = await client.types.find_many( where={ - 'decimal': { + 'decimal_': { 'in': [Decimal(3), Decimal(1), Decimal(2)], }, }, order={ - 'decimal': 'asc', + 'decimal_': 'asc', }, ) assert len(results) == 2 - assert results[0].decimal == Decimal(1) - assert results[1].decimal == Decimal(3) + assert results[0].decimal_ == Decimal(1) + assert results[1].decimal_ == Decimal(3) found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'not': Decimal('1'), }, }, order={ - 'decimal': 'asc', + 'decimal_': 'asc', }, ) assert found is not None - assert found.decimal == Decimal('2.1234') + assert found.decimal_ == Decimal('2.1234') found = await client.types.find_first( where={ - 'decimal': { + 'decimal_': { 'not': {'equals': Decimal('1')}, }, }, order={ - 'decimal': 'asc', + 'decimal_': 'asc', }, ) assert found is not None - assert found.decimal == Decimal('2.1234') + assert found.decimal_ == Decimal('2.1234') + + +@pytest.mark.asyncio +async def test_filtering_nulls(client: Prisma) -> None: + """None is a valid filter for nullable Decimal fields""" + await client.types.create( + { + 'string': 'a', + 'optional_decimal': None, + }, + ) + await client.types.create( + { + 'string': 'b', + 'optional_decimal': Decimal('3'), + }, + ) + await client.types.create( + { + 'string': 'c', + 'optional_decimal': Decimal('4'), + }, + ) + + found = await client.types.find_first( + where={ + 'NOT': [ + { + 'optional_decimal': None, + }, + ], + }, + order={ + 'string': 'asc', + }, + ) + assert found is not None + assert found.string == 'b' + assert found.optional_decimal == Decimal('3') + + count = await client.types.count( + where={ + 'optional_decimal': None, + }, + ) + assert count == 1 + + count = await client.types.count( + where={ + 'NOT': [ + { + 'optional_decimal': None, + }, + ], + }, + ) + assert count == 2 diff --git a/tests/test_types/test_float.py b/tests/test_types/test_float.py index 55be585cb..ac522c56d 100644 --- a/tests/test_types/test_float.py +++ b/tests/test_types/test_float.py @@ -205,3 +205,60 @@ async def test_atomic_update_invalid_input(client: Prisma) -> None: message = exc.value.args[0] assert isinstance(message, str) assert 'Expected exactly one field to be present, got 2' in message + + +@pytest.mark.asyncio +async def test_filtering_nulls(client: Prisma) -> None: + """None is a valid filter for nullable Float fields""" + await client.types.create( + { + 'string': 'a', + 'optional_float': None, + }, + ) + await client.types.create( + { + 'string': 'b', + 'optional_float': 1.2, + }, + ) + await client.types.create( + { + 'string': 'c', + 'optional_float': 5, + }, + ) + + found = await client.types.find_first( + where={ + 'NOT': [ + { + 'optional_float': None, + }, + ], + }, + order={ + 'string': 'asc', + }, + ) + assert found is not None + assert found.string == 'b' + assert found.optional_float == 1.2 + + count = await client.types.count( + where={ + 'optional_float': None, + }, + ) + assert count == 1 + + count = await client.types.count( + where={ + 'NOT': [ + { + 'optional_float': None, + }, + ], + }, + ) + assert count == 2 diff --git a/tests/test_types/test_int.py b/tests/test_types/test_int.py index 82a9d162a..ca6b421b5 100644 --- a/tests/test_types/test_int.py +++ b/tests/test_types/test_int.py @@ -205,3 +205,60 @@ async def test_atomic_update_invalid_input(client: Prisma) -> None: message = exc.value.args[0] assert isinstance(message, str) assert 'Expected exactly one field to be present, got 2' in message + + +@pytest.mark.asyncio +async def test_filtering_nulls(client: Prisma) -> None: + """None is a valid filter for nullable Int fields""" + await client.types.create( + { + 'string': 'a', + 'optional_int': None, + }, + ) + await client.types.create( + { + 'string': 'b', + 'optional_int': 1, + }, + ) + await client.types.create( + { + 'string': 'c', + 'optional_int': 2, + }, + ) + + found = await client.types.find_first( + where={ + 'NOT': [ + { + 'optional_int': None, + }, + ], + }, + order={ + 'string': 'asc', + }, + ) + assert found is not None + assert found.string == 'b' + assert found.optional_int == 1 + + count = await client.types.count( + where={ + 'optional_int': None, + }, + ) + assert count == 1 + + count = await client.types.count( + where={ + 'NOT': [ + { + 'optional_int': None, + }, + ], + }, + ) + assert count == 2 diff --git a/tests/test_types/test_string.py b/tests/test_types/test_string.py index 0c23203ab..1386dbd94 100644 --- a/tests/test_types/test_string.py +++ b/tests/test_types/test_string.py @@ -114,3 +114,62 @@ async def test_filtering(client: Prisma) -> None: ) assert found is not None assert found.string == 'b' + + +@pytest.mark.asyncio +async def test_filtering_nulls(client: Prisma) -> None: + """None is a valid filter for nullable String fields""" + await client.types.create( + { + 'string': 'a', + 'optional_string': None, + }, + ) + await client.types.create( + { + 'string': 'b', + 'optional_string': 'null', + }, + ) + await client.types.create( + { + 'string': 'c', + 'optional_string': 'robert@craigie.dev', + }, + ) + + found = await client.types.find_first( + where={ + 'NOT': [ + { + 'optional_string': None, + }, + ], + }, + order={ + 'string': 'asc', + }, + ) + assert found is not None + assert found.string == 'b' + assert found.optional_string == 'null' + + count = await client.types.count( + where={ + 'optional_string': None, + }, + ) + assert count == 1 + + count = await client.types.count( + where={ + 'NOT': [ + { + 'optional_string': None, + }, + ], + }, + ) + assert count == 2 + + # TODO: test passing 'null'