From 1b2787c7c2c729ffd3dce5d604542e410709ad22 Mon Sep 17 00:00:00 2001 From: Maxim Srour Date: Wed, 21 Jan 2026 23:48:44 +1100 Subject: [PATCH 1/6] feat add overloads for field types to improve type safety --- ormar/fields/model_fields.py | 75 ++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index e968e25b8..31aeb4a44 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -792,3 +792,78 @@ def validate(cls, **kwargs: Any) -> None: def get_column_type(cls, **kwargs: Any) -> Any: enum_cls = kwargs.get("enum_class") return sqlalchemy.Enum(enum_cls) + + +if TYPE_CHECKING: # pragma: nocover + @overload + def String(*, max_length: int, nullable: Literal[False] = False, **kwargs: Any) -> str: ... + @overload + def String(*, max_length: int, nullable: Literal[True], **kwargs: Any) -> str | None: ... + + @overload + def Integer(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... + @overload + def Integer(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... + + @overload + def Text(*, nullable: Literal[False] = False, **kwargs: Any) -> str: ... + @overload + def Text(*, nullable: Literal[True], **kwargs: Any) -> str | None: ... + + @overload + def Float(*, nullable: Literal[False] = False, **kwargs: Any) -> float: ... + @overload + def Float(*, nullable: Literal[True], **kwargs: Any) -> float | None: ... + + @overload + def DateTime(*, nullable: Literal[False] = False, **kwargs: Any) -> datetime.datetime: ... + @overload + def DateTime(*, nullable: Literal[True], **kwargs: Any) -> datetime.datetime | None: ... + + @overload + def Date(*, nullable: Literal[False] = False, **kwargs: Any) -> datetime.date: ... + @overload + def Date(*, nullable: Literal[True], **kwargs: Any) -> datetime.date | None: ... + + @overload + def Time(*, nullable: Literal[False] = False, **kwargs: Any) -> datetime.time: ... + @overload + def Time(*, nullable: Literal[True], **kwargs: Any) -> datetime.time | None: ... + + @overload + def JSON(*, nullable: Literal[False] = False, **kwargs: Any) -> Any: ... + @overload + def JSON(*, nullable: Literal[True], **kwargs: Any) -> Any | None: ... + + @overload + def BigInteger(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... + @overload + def BigInteger(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... + + @overload + def SmallInteger(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... + @overload + def SmallInteger(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... + + @overload + def Decimal( + *, + max_digits: int, + decimal_places: int, + nullable: Literal[False] = False, + **kwargs: Any + ) -> decimal.Decimal: ... + + @overload + def Decimal( + *, + max_digits: int, + decimal_places: int, + nullable: Literal[True], + **kwargs: Any + ) -> decimal.Decimal | None: ... + + @overload + def UUID(*, nullable: Literal[False] = False, **kwargs: Any) -> uuid.UUID: ... + @overload + def UUID(*, nullable: Literal[True], **kwargs: Any) -> uuid.UUID | None: ... From 29b176a354b0a343be3c66de1bcb774aedef20a3 Mon Sep 17 00:00:00 2001 From: Maxim Srour Date: Thu, 22 Jan 2026 00:58:46 +1100 Subject: [PATCH 2/6] refactor into a neater format --- ormar/fields/model_fields.py | 201 ++++++++++++++++++++++------------- 1 file changed, 126 insertions(+), 75 deletions(-) diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 31aeb4a44..018cc8613 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -159,6 +159,16 @@ class String(ModelFieldFactory, str): _type = str _sample = "string" + @overload + def __new__( + cls, *, max_length: int, nullable: Literal[False] = False, **kwargs: Any + ) -> str: ... + + @overload + def __new__( + cls, *, max_length: int, nullable: Literal[True], **kwargs: Any + ) -> str | None: ... + def __new__( # type: ignore # noqa CFQ002 cls, *, @@ -212,6 +222,12 @@ class Integer(ModelFieldFactory, int): _type = int _sample = 0 + @overload + def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> int: ... + + @overload + def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> int | None: ... + def __new__( # type: ignore cls, *, @@ -260,6 +276,12 @@ class Text(ModelFieldFactory, str): _type = str _sample = "text" + @overload + def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> str: ... + + @overload + def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> str | None: ... + def __new__(cls, **kwargs: Any) -> Self: # type: ignore kwargs = { **kwargs, @@ -293,6 +315,12 @@ class Float(ModelFieldFactory, float): _type = float _sample = 0.0 + @overload + def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> float: ... + + @overload + def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> float | None: ... + def __new__( # type: ignore cls, *, @@ -364,6 +392,16 @@ class DateTime(ModelFieldFactory, datetime.datetime): _type = datetime.datetime _sample = "datetime" + @overload + def __new__( + cls, *, nullable: Literal[False] = False, **kwargs: Any + ) -> datetime.datetime: ... + + @overload + def __new__( + cls, *, nullable: Literal[True], **kwargs: Any + ) -> datetime.datetime | None: ... + def __new__( # type: ignore # noqa CFQ002 cls, *, timezone: bool = False, **kwargs: Any ) -> Self: # type: ignore @@ -399,6 +437,27 @@ class Date(ModelFieldFactory, datetime.date): _type = datetime.date _sample = "date" + @overload + def __new__( + cls, *, nullable: Literal[False] = False, **kwargs: Any + ) -> datetime.date: ... + + @overload + def __new__( + cls, *, nullable: Literal[True], **kwargs: Any + ) -> datetime.date | None: ... + + def __new__(cls, **kwargs: Any) -> Self: # type: ignore + kwargs = { + **kwargs, + **{ + k: v + for k, v in locals().items() + if k not in ["cls", "__class__", "kwargs"] + }, + } + return super().__new__(cls, **kwargs) + @classmethod def get_column_type(cls, **kwargs: Any) -> Any: """ @@ -421,6 +480,16 @@ class Time(ModelFieldFactory, datetime.time): _type = datetime.time _sample = "time" + @overload + def __new__( + cls, *, nullable: Literal[False] = False, **kwargs: Any + ) -> datetime.time: ... + + @overload + def __new__( + cls, *, nullable: Literal[True], **kwargs: Any + ) -> datetime.time | None: ... + def __new__( # type: ignore # noqa CFQ002 cls, *, timezone: bool = False, **kwargs: Any ) -> Self: # type: ignore @@ -456,6 +525,23 @@ class JSON(ModelFieldFactory, pydantic.Json): _type = pydantic.Json _sample = '{"json": "json"}' + @overload + def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> Any: ... + + @overload + def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> Any | None: ... + + def __new__(cls, **kwargs: Any) -> Self: # type: ignore + kwargs = { + **kwargs, + **{ + k: v + for k, v in locals().items() + if k not in ["cls", "__class__", "kwargs"] + }, + } + return super().__new__(cls, **kwargs) + @classmethod def get_column_type(cls, **kwargs: Any) -> Any: """ @@ -555,6 +641,12 @@ class BigInteger(Integer, int): _type = int _sample = 0 + @overload + def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> int: ... + + @overload + def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> int | None: ... + def __new__( # type: ignore cls, *, @@ -603,6 +695,12 @@ class SmallInteger(Integer, int): _type = int _sample = 0 + @overload + def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> int: ... + + @overload + def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> int | None: ... + def __new__( # type: ignore cls, *, @@ -651,6 +749,26 @@ class Decimal(ModelFieldFactory, decimal.Decimal): _type = decimal.Decimal _sample = 0.0 + @overload + def __new__( + cls, + *, + max_digits: int, + decimal_places: int, + nullable: Literal[False] = False, + **kwargs: Any + ) -> decimal.Decimal: ... + + @overload + def __new__( + cls, + *, + max_digits: int, + decimal_places: int, + nullable: Literal[True], + **kwargs: Any + ) -> decimal.Decimal | None: ... + def __new__( # type: ignore # noqa CFQ002 cls, *, @@ -724,6 +842,14 @@ class UUID(ModelFieldFactory, uuid.UUID): _type = uuid.UUID _sample = "uuid" + @overload + def __new__( + cls, *, nullable: Literal[False] = False, **kwargs: Any + ) -> uuid.UUID: ... + + @overload + def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> uuid.UUID | None: ... + def __new__( # type: ignore # noqa CFQ002 cls, *, uuid_format: str = "hex", **kwargs: Any ) -> Self: @@ -792,78 +918,3 @@ def validate(cls, **kwargs: Any) -> None: def get_column_type(cls, **kwargs: Any) -> Any: enum_cls = kwargs.get("enum_class") return sqlalchemy.Enum(enum_cls) - - -if TYPE_CHECKING: # pragma: nocover - @overload - def String(*, max_length: int, nullable: Literal[False] = False, **kwargs: Any) -> str: ... - @overload - def String(*, max_length: int, nullable: Literal[True], **kwargs: Any) -> str | None: ... - - @overload - def Integer(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... - @overload - def Integer(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... - - @overload - def Text(*, nullable: Literal[False] = False, **kwargs: Any) -> str: ... - @overload - def Text(*, nullable: Literal[True], **kwargs: Any) -> str | None: ... - - @overload - def Float(*, nullable: Literal[False] = False, **kwargs: Any) -> float: ... - @overload - def Float(*, nullable: Literal[True], **kwargs: Any) -> float | None: ... - - @overload - def DateTime(*, nullable: Literal[False] = False, **kwargs: Any) -> datetime.datetime: ... - @overload - def DateTime(*, nullable: Literal[True], **kwargs: Any) -> datetime.datetime | None: ... - - @overload - def Date(*, nullable: Literal[False] = False, **kwargs: Any) -> datetime.date: ... - @overload - def Date(*, nullable: Literal[True], **kwargs: Any) -> datetime.date | None: ... - - @overload - def Time(*, nullable: Literal[False] = False, **kwargs: Any) -> datetime.time: ... - @overload - def Time(*, nullable: Literal[True], **kwargs: Any) -> datetime.time | None: ... - - @overload - def JSON(*, nullable: Literal[False] = False, **kwargs: Any) -> Any: ... - @overload - def JSON(*, nullable: Literal[True], **kwargs: Any) -> Any | None: ... - - @overload - def BigInteger(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... - @overload - def BigInteger(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... - - @overload - def SmallInteger(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... - @overload - def SmallInteger(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... - - @overload - def Decimal( - *, - max_digits: int, - decimal_places: int, - nullable: Literal[False] = False, - **kwargs: Any - ) -> decimal.Decimal: ... - - @overload - def Decimal( - *, - max_digits: int, - decimal_places: int, - nullable: Literal[True], - **kwargs: Any - ) -> decimal.Decimal | None: ... - - @overload - def UUID(*, nullable: Literal[False] = False, **kwargs: Any) -> uuid.UUID: ... - @overload - def UUID(*, nullable: Literal[True], **kwargs: Any) -> uuid.UUID | None: ... From 3ca9888d98f55d8029f27274be3f58f1da352b5c Mon Sep 17 00:00:00 2001 From: Maxim Srour Date: Thu, 22 Jan 2026 01:37:46 +1100 Subject: [PATCH 3/6] refactor migrate the types into a stub file --- ormar/fields/model_fields.py | 126 ---------------------------------- ormar/fields/model_fields.pyi | 84 +++++++++++++++++++++++ 2 files changed, 84 insertions(+), 126 deletions(-) create mode 100644 ormar/fields/model_fields.pyi diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 018cc8613..e968e25b8 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -159,16 +159,6 @@ class String(ModelFieldFactory, str): _type = str _sample = "string" - @overload - def __new__( - cls, *, max_length: int, nullable: Literal[False] = False, **kwargs: Any - ) -> str: ... - - @overload - def __new__( - cls, *, max_length: int, nullable: Literal[True], **kwargs: Any - ) -> str | None: ... - def __new__( # type: ignore # noqa CFQ002 cls, *, @@ -222,12 +212,6 @@ class Integer(ModelFieldFactory, int): _type = int _sample = 0 - @overload - def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> int: ... - - @overload - def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> int | None: ... - def __new__( # type: ignore cls, *, @@ -276,12 +260,6 @@ class Text(ModelFieldFactory, str): _type = str _sample = "text" - @overload - def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> str: ... - - @overload - def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> str | None: ... - def __new__(cls, **kwargs: Any) -> Self: # type: ignore kwargs = { **kwargs, @@ -315,12 +293,6 @@ class Float(ModelFieldFactory, float): _type = float _sample = 0.0 - @overload - def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> float: ... - - @overload - def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> float | None: ... - def __new__( # type: ignore cls, *, @@ -392,16 +364,6 @@ class DateTime(ModelFieldFactory, datetime.datetime): _type = datetime.datetime _sample = "datetime" - @overload - def __new__( - cls, *, nullable: Literal[False] = False, **kwargs: Any - ) -> datetime.datetime: ... - - @overload - def __new__( - cls, *, nullable: Literal[True], **kwargs: Any - ) -> datetime.datetime | None: ... - def __new__( # type: ignore # noqa CFQ002 cls, *, timezone: bool = False, **kwargs: Any ) -> Self: # type: ignore @@ -437,27 +399,6 @@ class Date(ModelFieldFactory, datetime.date): _type = datetime.date _sample = "date" - @overload - def __new__( - cls, *, nullable: Literal[False] = False, **kwargs: Any - ) -> datetime.date: ... - - @overload - def __new__( - cls, *, nullable: Literal[True], **kwargs: Any - ) -> datetime.date | None: ... - - def __new__(cls, **kwargs: Any) -> Self: # type: ignore - kwargs = { - **kwargs, - **{ - k: v - for k, v in locals().items() - if k not in ["cls", "__class__", "kwargs"] - }, - } - return super().__new__(cls, **kwargs) - @classmethod def get_column_type(cls, **kwargs: Any) -> Any: """ @@ -480,16 +421,6 @@ class Time(ModelFieldFactory, datetime.time): _type = datetime.time _sample = "time" - @overload - def __new__( - cls, *, nullable: Literal[False] = False, **kwargs: Any - ) -> datetime.time: ... - - @overload - def __new__( - cls, *, nullable: Literal[True], **kwargs: Any - ) -> datetime.time | None: ... - def __new__( # type: ignore # noqa CFQ002 cls, *, timezone: bool = False, **kwargs: Any ) -> Self: # type: ignore @@ -525,23 +456,6 @@ class JSON(ModelFieldFactory, pydantic.Json): _type = pydantic.Json _sample = '{"json": "json"}' - @overload - def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> Any: ... - - @overload - def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> Any | None: ... - - def __new__(cls, **kwargs: Any) -> Self: # type: ignore - kwargs = { - **kwargs, - **{ - k: v - for k, v in locals().items() - if k not in ["cls", "__class__", "kwargs"] - }, - } - return super().__new__(cls, **kwargs) - @classmethod def get_column_type(cls, **kwargs: Any) -> Any: """ @@ -641,12 +555,6 @@ class BigInteger(Integer, int): _type = int _sample = 0 - @overload - def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> int: ... - - @overload - def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> int | None: ... - def __new__( # type: ignore cls, *, @@ -695,12 +603,6 @@ class SmallInteger(Integer, int): _type = int _sample = 0 - @overload - def __new__(cls, *, nullable: Literal[False] = False, **kwargs: Any) -> int: ... - - @overload - def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> int | None: ... - def __new__( # type: ignore cls, *, @@ -749,26 +651,6 @@ class Decimal(ModelFieldFactory, decimal.Decimal): _type = decimal.Decimal _sample = 0.0 - @overload - def __new__( - cls, - *, - max_digits: int, - decimal_places: int, - nullable: Literal[False] = False, - **kwargs: Any - ) -> decimal.Decimal: ... - - @overload - def __new__( - cls, - *, - max_digits: int, - decimal_places: int, - nullable: Literal[True], - **kwargs: Any - ) -> decimal.Decimal | None: ... - def __new__( # type: ignore # noqa CFQ002 cls, *, @@ -842,14 +724,6 @@ class UUID(ModelFieldFactory, uuid.UUID): _type = uuid.UUID _sample = "uuid" - @overload - def __new__( - cls, *, nullable: Literal[False] = False, **kwargs: Any - ) -> uuid.UUID: ... - - @overload - def __new__(cls, *, nullable: Literal[True], **kwargs: Any) -> uuid.UUID | None: ... - def __new__( # type: ignore # noqa CFQ002 cls, *, uuid_format: str = "hex", **kwargs: Any ) -> Self: diff --git a/ormar/fields/model_fields.pyi b/ormar/fields/model_fields.pyi new file mode 100644 index 000000000..dc73bd70e --- /dev/null +++ b/ormar/fields/model_fields.pyi @@ -0,0 +1,84 @@ +from __future__ import annotations + +from datetime import date, datetime, time +from decimal import Decimal as DecimalType +from enum import Enum as EnumBase +from typing import Any, Literal, Type, TypeVar, overload +from uuid import UUID as UuidType + +T = TypeVar("T", bound=EnumBase) + +def Boolean(**kwargs: Any) -> bool: ... +@overload +def String( + *, max_length: int, nullable: Literal[False] = False, **kwargs: Any +) -> str: ... +@overload +def String( + *, max_length: int, nullable: Literal[True], **kwargs: Any +) -> str | None: ... +@overload +def Integer(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... +@overload +def Integer(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... +@overload +def Text(*, nullable: Literal[False] = False, **kwargs: Any) -> str: ... +@overload +def Text(*, nullable: Literal[True], **kwargs: Any) -> str | None: ... +@overload +def Float(*, nullable: Literal[False] = False, **kwargs: Any) -> float: ... +@overload +def Float(*, nullable: Literal[True], **kwargs: Any) -> float | None: ... +@overload +def DateTime(*, nullable: Literal[False] = False, **kwargs: Any) -> datetime: ... +@overload +def DateTime(*, nullable: Literal[True], **kwargs: Any) -> datetime | None: ... +@overload +def Date(*, nullable: Literal[False] = False, **kwargs: Any) -> date: ... +@overload +def Date(*, nullable: Literal[True], **kwargs: Any) -> date | None: ... +@overload +def Time(*, nullable: Literal[False] = False, **kwargs: Any) -> time: ... +@overload +def Time(*, nullable: Literal[True], **kwargs: Any) -> time | None: ... +@overload +def JSON(*, nullable: Literal[False] = False, **kwargs: Any) -> Any: ... +@overload +def JSON(*, nullable: Literal[True], **kwargs: Any) -> Any | None: ... +@overload +def BigInteger(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... +@overload +def BigInteger(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... +@overload +def SmallInteger(*, nullable: Literal[False] = False, **kwargs: Any) -> int: ... +@overload +def SmallInteger(*, nullable: Literal[True], **kwargs: Any) -> int | None: ... +@overload +def Decimal( + *, + max_digits: int, + decimal_places: int, + nullable: Literal[False] = False, + **kwargs: Any, +) -> DecimalType: ... +@overload +def Decimal( + *, max_digits: int, decimal_places: int, nullable: Literal[True], **kwargs: Any +) -> DecimalType | None: ... +@overload +def UUID(*, nullable: Literal[False] = False, **kwargs: Any) -> UuidType: ... +@overload +def UUID(*, nullable: Literal[True], **kwargs: Any) -> UuidType | None: ... +@overload +def LargeBinary( + max_length: int, *, represent_as_base64_str: Literal[True], **kwargs: Any +) -> str: ... +@overload +def LargeBinary( + max_length: int, *, represent_as_base64_str: Literal[False], **kwargs: Any +) -> bytes: ... +@overload +def LargeBinary( + max_length: int, represent_as_base64_str: Literal[False] = ..., **kwargs: Any +) -> bytes: ... +def Enum(enum_class: Type[T], **kwargs: Any) -> T: ... From 2e117e440ecf9120884028d1dda93ee0ea736937 Mon Sep 17 00:00:00 2001 From: Maxim Srour Date: Thu, 22 Jan 2026 01:44:51 +1100 Subject: [PATCH 4/6] refactor test model fields to use Optional type hints for nullable fields --- tests/test_encryption/test_encrypted_columns.py | 4 ++-- .../test_dumping_model_to_dict.py | 2 +- .../test_excludable_items.py | 12 ++++++------ .../test_excluding_fields_in_fastapi.py | 7 ++++--- .../test_excluding_fields_with_default.py | 4 ++-- .../test_excluding_subset_of_columns.py | 10 +++++----- .../test_pydantic_dict_params.py | 4 ++-- tests/test_fastapi/test_json_field_fastapi.py | 4 ++-- tests/test_fastapi/test_recursion_error.py | 6 +++--- .../test_relations_with_nested_defaults.py | 2 +- tests/test_fastapi/test_wekref_exclusion.py | 2 +- ...est_inherited_class_is_not_abstract_by_default.py | 7 +++++-- tests/test_model_definition/test_aliases.py | 2 +- tests/test_model_definition/test_columns.py | 5 +++-- tests/test_model_definition/test_model_construct.py | 4 ++-- tests/test_model_definition/test_models.py | 5 ++++- .../test_pk_field_is_always_not_null.py | 4 +++- tests/test_model_definition/test_save_status.py | 6 +++--- tests/test_model_methods/test_load_all.py | 6 +++--- tests/test_model_methods/test_save_related.py | 6 +++--- .../test_save_related_from_dict.py | 8 ++++---- tests/test_ordering/test_default_model_order.py | 4 ++-- tests/test_ordering/test_default_relation_order.py | 4 ++-- .../test_default_through_relation_order.py | 6 +++--- .../test_proper_order_of_sorting_apply.py | 4 ++-- tests/test_queries/test_aggr_functions.py | 4 ++-- tests/test_queries/test_filter_groups.py | 2 +- tests/test_queries/test_isnull_filter.py | 2 +- tests/test_queries/test_or_filters.py | 2 +- tests/test_queries/test_queryproxy_on_m2m_models.py | 2 +- .../test_reserved_sql_keywords_escaped.py | 6 ++++-- tests/test_queries/test_reverse_fk_queryset.py | 2 +- .../test_queries/test_selecting_subset_of_columns.py | 12 ++++++------ tests/test_queries/test_values_and_values_list.py | 2 +- tests/test_relations/test_foreign_keys.py | 2 +- tests/test_relations/test_m2m_through_fields.py | 6 +++--- tests/test_relations/test_prefetch_related.py | 8 ++++---- .../test_replacing_models_with_copy.py | 2 +- tests/test_relations/test_through_relations_fail.py | 5 +++-- tests/test_types.py | 6 ++++-- 40 files changed, 103 insertions(+), 88 deletions(-) diff --git a/tests/test_encryption/test_encrypted_columns.py b/tests/test_encryption/test_encrypted_columns.py index 872fd4873..5a685065a 100644 --- a/tests/test_encryption/test_encrypted_columns.py +++ b/tests/test_encryption/test_encrypted_columns.py @@ -4,7 +4,7 @@ import decimal import hashlib import uuid -from typing import Any +from typing import Any, Optional import ormar import pytest @@ -51,7 +51,7 @@ class Author(ormar.Model): test_text: str = ormar.Text(default="", **default_fernet) test_bool: bool = ormar.Boolean(nullable=False, **default_fernet) test_float: float = ormar.Float(**default_fernet) - test_float2: float = ormar.Float(nullable=True, **default_fernet) + test_float2: Optional[float] = ormar.Float(nullable=True, **default_fernet) test_datetime = ormar.DateTime(default=datetime.datetime.now, **default_fernet) test_date = ormar.Date(default=datetime.date.today, **default_fernet) test_time = ormar.Time(default=datetime.time, **default_fernet) diff --git a/tests/test_exclude_include_dict/test_dumping_model_to_dict.py b/tests/test_exclude_include_dict/test_dumping_model_to_dict.py index 9c062cf8b..f4158aec8 100644 --- a/tests/test_exclude_include_dict/test_dumping_model_to_dict.py +++ b/tests/test_exclude_include_dict/test_dumping_model_to_dict.py @@ -21,7 +21,7 @@ class User(ormar.Model): id: int = ormar.Integer(primary_key=True) email: str = ormar.String(max_length=255, nullable=False) - password: str = ormar.String(max_length=255, nullable=True) + password: Optional[str] = ormar.String(max_length=255, nullable=True) first_name: str = ormar.String(max_length=255, nullable=False) roles: List[Role] = ormar.ManyToMany(Role) diff --git a/tests/test_exclude_include_dict/test_excludable_items.py b/tests/test_exclude_include_dict/test_excludable_items.py index a4338f501..6cceecb27 100644 --- a/tests/test_exclude_include_dict/test_excludable_items.py +++ b/tests/test_exclude_include_dict/test_excludable_items.py @@ -14,7 +14,7 @@ class NickNames(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="hq_name") - is_lame: bool = ormar.Boolean(nullable=True) + is_lame: Optional[bool] = ormar.Boolean(nullable=True) class NicksHq(ormar.Model): @@ -34,7 +34,7 @@ class Company(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="company_name") - founded: int = ormar.Integer(nullable=True) + founded: Optional[int] = ormar.Integer(nullable=True) hq: HQ = ormar.ForeignKey(HQ) @@ -44,10 +44,10 @@ class Car(ormar.Model): id: int = ormar.Integer(primary_key=True) manufacturer: Optional[Company] = ormar.ForeignKey(Company) name: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - gearbox_type: str = ormar.String(max_length=20, nullable=True) - gears: int = ormar.Integer(nullable=True) - aircon_type: str = ormar.String(max_length=20, nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) + gearbox_type: Optional[str] = ormar.String(max_length=20, nullable=True) + gears: Optional[int] = ormar.Integer(nullable=True) + aircon_type: Optional[str] = ormar.String(max_length=20, nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_exclude_include_dict/test_excluding_fields_in_fastapi.py b/tests/test_exclude_include_dict/test_excluding_fields_in_fastapi.py index d7ab2490d..e13fba6f6 100644 --- a/tests/test_exclude_include_dict/test_excluding_fields_in_fastapi.py +++ b/tests/test_exclude_include_dict/test_excluding_fields_in_fastapi.py @@ -1,6 +1,7 @@ import datetime import random import string +from typing import Optional import ormar import pydantic @@ -64,10 +65,10 @@ class User(ormar.Model): id: int = ormar.Integer(primary_key=True) email: str = ormar.String(max_length=255) - password: str = ormar.String(max_length=255, nullable=True) + password: Optional[str] = ormar.String(max_length=255, nullable=True) first_name: str = ormar.String(max_length=255) last_name: str = ormar.String(max_length=255) - category: str = ormar.String(max_length=255, nullable=True) + category: Optional[str] = ormar.String(max_length=255, nullable=True) class User2(ormar.Model): @@ -78,7 +79,7 @@ class User2(ormar.Model): password: str = ormar.String(max_length=255) first_name: str = ormar.String(max_length=255) last_name: str = ormar.String(max_length=255) - category: str = ormar.String(max_length=255, nullable=True) + category: Optional[str] = ormar.String(max_length=255, nullable=True) timestamp: datetime.datetime = pydantic.Field(default_factory=datetime.datetime.now) diff --git a/tests/test_exclude_include_dict/test_excluding_fields_with_default.py b/tests/test_exclude_include_dict/test_excluding_fields_with_default.py index 92dff49e6..6fe2c1a57 100644 --- a/tests/test_exclude_include_dict/test_excluding_fields_with_default.py +++ b/tests/test_exclude_include_dict/test_excluding_fields_with_default.py @@ -19,7 +19,7 @@ class Album(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100) - is_best_seller: bool = ormar.Boolean(default=False, nullable=True) + is_best_seller: Optional[bool] = ormar.Boolean(default=False, nullable=True) class Track(ormar.Model): @@ -29,7 +29,7 @@ class Track(ormar.Model): album: Optional[Album] = ormar.ForeignKey(Album) title: str = ormar.String(max_length=100) position: int = ormar.Integer(default=get_position) - play_count: int = ormar.Integer(nullable=True, default=0) + play_count: Optional[int] = ormar.Integer(nullable=True, default=0) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_exclude_include_dict/test_excluding_subset_of_columns.py b/tests/test_exclude_include_dict/test_excluding_subset_of_columns.py index 71fe6a915..8940d071d 100644 --- a/tests/test_exclude_include_dict/test_excluding_subset_of_columns.py +++ b/tests/test_exclude_include_dict/test_excluding_subset_of_columns.py @@ -15,7 +15,7 @@ class Company(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False) - founded: int = ormar.Integer(nullable=True) + founded: Optional[int] = ormar.Integer(nullable=True) class Car(ormar.Model): @@ -24,10 +24,10 @@ class Car(ormar.Model): id: int = ormar.Integer(primary_key=True) manufacturer: Optional[Company] = ormar.ForeignKey(Company) name: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - gearbox_type: str = ormar.String(max_length=20, nullable=True) - gears: int = ormar.Integer(nullable=True, name="gears_number") - aircon_type: str = ormar.String(max_length=20, nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) + gearbox_type: Optional[str] = ormar.String(max_length=20, nullable=True) + gears: Optional[int] = ormar.Integer(nullable=True, name="gears_number") + aircon_type: Optional[str] = ormar.String(max_length=20, nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_exclude_include_dict/test_pydantic_dict_params.py b/tests/test_exclude_include_dict/test_pydantic_dict_params.py index ca23f281d..fdcc8b440 100644 --- a/tests/test_exclude_include_dict/test_pydantic_dict_params.py +++ b/tests/test_exclude_include_dict/test_pydantic_dict_params.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import ormar import pytest @@ -13,7 +13,7 @@ class Category(ormar.Model): ormar_config = base_ormar_config.copy(tablename="categories") id: int = ormar.Integer(primary_key=True) - name: str = ormar.String(max_length=100, default="Test", nullable=True) + name: Optional[str] = ormar.String(max_length=100, default="Test", nullable=True) visibility: bool = ormar.Boolean(default=True) diff --git a/tests/test_fastapi/test_json_field_fastapi.py b/tests/test_fastapi/test_json_field_fastapi.py index cde78ad55..cfe6a925a 100644 --- a/tests/test_fastapi/test_json_field_fastapi.py +++ b/tests/test_fastapi/test_json_field_fastapi.py @@ -1,6 +1,6 @@ # type: ignore import uuid -from typing import List +from typing import List, Optional import ormar import pydantic @@ -79,7 +79,7 @@ class Thing2(ormar.Model): id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4) name: str = ormar.Text(default="") - js: pydantic.Json = ormar.JSON(nullable=True) + js: Optional[pydantic.Json] = ormar.JSON(nullable=True) Thing2() diff --git a/tests/test_fastapi/test_recursion_error.py b/tests/test_fastapi/test_recursion_error.py index aa388f106..bc3fbf26a 100644 --- a/tests/test_fastapi/test_recursion_error.py +++ b/tests/test_fastapi/test_recursion_error.py @@ -1,6 +1,6 @@ import uuid from datetime import datetime -from typing import List +from typing import List, Optional import ormar import pytest @@ -27,7 +27,7 @@ class User(ormar.Model): username: str = ormar.String(unique=True, max_length=100) password: str = ormar.String(unique=True, max_length=100) verified: bool = ormar.Boolean(default=False) - verify_key: str = ormar.String(unique=True, max_length=100, nullable=True) + verify_key: Optional[str] = ormar.String(unique=True, max_length=100, nullable=True) created_at: datetime = ormar.DateTime(default=datetime.now()) ormar_config = base_ormar_config.copy(tablename="users") @@ -65,7 +65,7 @@ class QuizInput(BaseModel): class Quiz(ormar.Model): id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4) title: str = ormar.String(max_length=100) - description: str = ormar.String(max_length=300, nullable=True) + description: Optional[str] = ormar.String(max_length=300, nullable=True) created_at: datetime = ormar.DateTime(default=datetime.now()) updated_at: datetime = ormar.DateTime(default=datetime.now()) user_id: uuid.UUID = ormar.UUID(foreign_key=User.id) diff --git a/tests/test_fastapi/test_relations_with_nested_defaults.py b/tests/test_fastapi/test_relations_with_nested_defaults.py index 47b799881..7805d7fbc 100644 --- a/tests/test_fastapi/test_relations_with_nested_defaults.py +++ b/tests/test_fastapi/test_relations_with_nested_defaults.py @@ -36,7 +36,7 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) author: Optional[Author] = ormar.ForeignKey(Author) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_fastapi/test_wekref_exclusion.py b/tests/test_fastapi/test_wekref_exclusion.py index 87cb5321e..cf1d3febb 100644 --- a/tests/test_fastapi/test_wekref_exclusion.py +++ b/tests/test_fastapi/test_wekref_exclusion.py @@ -28,7 +28,7 @@ class Thing(ormar.Model): id: UUID = ormar.UUID(primary_key=True, default=uuid4) name: str = ormar.Text(default="") - js: pydantic.Json = ormar.JSON(nullable=True) + js: Optional[pydantic.Json] = ormar.JSON(nullable=True) other_thing: Optional[OtherThing] = ormar.ForeignKey(OtherThing, nullable=True) diff --git a/tests/test_inheritance_and_pydantic_generation/test_inherited_class_is_not_abstract_by_default.py b/tests/test_inheritance_and_pydantic_generation/test_inherited_class_is_not_abstract_by_default.py index 0f4352521..d6757ac33 100644 --- a/tests/test_inheritance_and_pydantic_generation/test_inherited_class_is_not_abstract_by_default.py +++ b/tests/test_inheritance_and_pydantic_generation/test_inherited_class_is_not_abstract_by_default.py @@ -1,4 +1,5 @@ import datetime +from typing import Optional import ormar import pytest @@ -17,8 +18,10 @@ class TableBase(ormar.Model): created_at: datetime.datetime = ormar.DateTime( timezone=True, default=datetime.datetime.now ) - last_modified_by: str = ormar.String(max_length=20, nullable=True) - last_modified_at: datetime.datetime = ormar.DateTime(timezone=True, nullable=True) + last_modified_by: Optional[str] = ormar.String(max_length=20, nullable=True) + last_modified_at: Optional[datetime.datetime] = ormar.DateTime( + timezone=True, nullable=True + ) class NationBase(ormar.Model): diff --git a/tests/test_model_definition/test_aliases.py b/tests/test_model_definition/test_aliases.py index 0bd3d1a81..a2219c583 100644 --- a/tests/test_model_definition/test_aliases.py +++ b/tests/test_model_definition/test_aliases.py @@ -15,7 +15,7 @@ class Child(ormar.Model): id: int = ormar.Integer(name="child_id", primary_key=True) first_name: str = ormar.String(name="fname", max_length=100) last_name: str = ormar.String(name="lname", max_length=100) - born_year: int = ormar.Integer(name="year_born", nullable=True) + born_year: Optional[int] = ormar.Integer(name="year_born", nullable=True) class Artist(ormar.Model): diff --git a/tests/test_model_definition/test_columns.py b/tests/test_model_definition/test_columns.py index 3fbf8f898..58ba55311 100644 --- a/tests/test_model_definition/test_columns.py +++ b/tests/test_model_definition/test_columns.py @@ -1,5 +1,6 @@ import datetime from enum import Enum +from typing import Optional import ormar import pydantic @@ -29,8 +30,8 @@ class Example(ormar.Model): created: datetime.datetime = ormar.DateTime(default=datetime.datetime.now) created_day: datetime.date = ormar.Date(default=datetime.date.today) created_time: datetime.time = ormar.Time(default=time) - description: str = ormar.Text(nullable=True) - value: float = ormar.Float(nullable=True) + description: Optional[str] = ormar.Text(nullable=True) + value: Optional[float] = ormar.Float(nullable=True) data: pydantic.Json = ormar.JSON(default={}) size: MyEnum = ormar.Enum(enum_class=MyEnum, default=MyEnum.SMALL) diff --git a/tests/test_model_definition/test_model_construct.py b/tests/test_model_definition/test_model_construct.py index b4f7ce385..44df5f703 100644 --- a/tests/test_model_definition/test_model_construct.py +++ b/tests/test_model_definition/test_model_construct.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import ormar import pytest @@ -33,7 +33,7 @@ class Company(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="company_name") - founded: int = ormar.Integer(nullable=True) + founded: Optional[int] = ormar.Integer(nullable=True) hq: HQ = ormar.ForeignKey(HQ) diff --git a/tests/test_model_definition/test_models.py b/tests/test_model_definition/test_models.py index c25ba965c..d994e5cc7 100644 --- a/tests/test_model_definition/test_models.py +++ b/tests/test_model_definition/test_models.py @@ -4,6 +4,7 @@ import os import uuid from enum import Enum +from typing import Optional import ormar import pydantic @@ -126,7 +127,9 @@ class NullableCountry(ormar.Model): ormar_config = base_ormar_config.copy(tablename="country2") id: int = ormar.Integer(primary_key=True) - name: CountryNameEnum = ormar.Enum(enum_class=CountryNameEnum, nullable=True) + name: Optional[CountryNameEnum] = ormar.Enum( + enum_class=CountryNameEnum, nullable=True + ) class NotNullableCountry(ormar.Model): diff --git a/tests/test_model_definition/test_pk_field_is_always_not_null.py b/tests/test_model_definition/test_pk_field_is_always_not_null.py index 4737a4b08..5d9c5e408 100644 --- a/tests/test_model_definition/test_pk_field_is_always_not_null.py +++ b/tests/test_model_definition/test_pk_field_is_always_not_null.py @@ -1,3 +1,5 @@ +from typing import Optional + import ormar from tests.lifespan import init_tests @@ -21,7 +23,7 @@ class NonAutoincrementModel(ormar.Model): class ExplicitNullableModel(ormar.Model): ormar_config = base_ormar_config.copy() - id: int = ormar.Integer(primary_key=True, nullable=True) + id: Optional[int] = ormar.Integer(primary_key=True, nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_model_definition/test_save_status.py b/tests/test_model_definition/test_save_status.py index a2bab8f9b..17e3254fb 100644 --- a/tests/test_model_definition/test_save_status.py +++ b/tests/test_model_definition/test_save_status.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import ormar import pytest @@ -15,7 +15,7 @@ class NickNames(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="hq_name") - is_lame: bool = ormar.Boolean(nullable=True) + is_lame: Optional[bool] = ormar.Boolean(nullable=True) class NicksHq(ormar.Model): @@ -35,7 +35,7 @@ class Company(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="company_name") - founded: int = ormar.Integer(nullable=True) + founded: Optional[int] = ormar.Integer(nullable=True) hq: HQ = ormar.ForeignKey(HQ) diff --git a/tests/test_model_methods/test_load_all.py b/tests/test_model_methods/test_load_all.py index 99d3ed2fb..5b3db7cbe 100644 --- a/tests/test_model_methods/test_load_all.py +++ b/tests/test_model_methods/test_load_all.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import ormar import pytest @@ -30,7 +30,7 @@ class NickName(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="hq_name") - is_lame: bool = ormar.Boolean(nullable=True) + is_lame: Optional[bool] = ormar.Boolean(nullable=True) level: CringeLevel = ormar.ForeignKey(CringeLevel) @@ -47,7 +47,7 @@ class Company(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="company_name") - founded: int = ormar.Integer(nullable=True) + founded: Optional[int] = ormar.Integer(nullable=True) hq: HQ = ormar.ForeignKey(HQ, related_name="companies") diff --git a/tests/test_model_methods/test_save_related.py b/tests/test_model_methods/test_save_related.py index f12d3d158..8dae177e4 100644 --- a/tests/test_model_methods/test_save_related.py +++ b/tests/test_model_methods/test_save_related.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import ormar import pytest @@ -21,7 +21,7 @@ class NickName(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="hq_name") - is_lame: bool = ormar.Boolean(nullable=True) + is_lame: Optional[bool] = ormar.Boolean(nullable=True) level: CringeLevel = ormar.ForeignKey(CringeLevel) @@ -42,7 +42,7 @@ class Company(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="company_name") - founded: int = ormar.Integer(nullable=True) + founded: Optional[int] = ormar.Integer(nullable=True) hq: HQ = ormar.ForeignKey(HQ, related_name="companies") diff --git a/tests/test_model_methods/test_save_related_from_dict.py b/tests/test_model_methods/test_save_related_from_dict.py index c6a4e7ec9..fa9433951 100644 --- a/tests/test_model_methods/test_save_related_from_dict.py +++ b/tests/test_model_methods/test_save_related_from_dict.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import ormar import pytest @@ -21,7 +21,7 @@ class NickName(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="hq_name") - is_lame: bool = ormar.Boolean(nullable=True) + is_lame: Optional[bool] = ormar.Boolean(nullable=True) level: CringeLevel = ormar.ForeignKey(CringeLevel) @@ -29,7 +29,7 @@ class NicksHq(ormar.Model): ormar_config = base_ormar_config.copy(tablename="nicks_x_hq") id: int = ormar.Integer(primary_key=True) - new_field: str = ormar.String(max_length=200, nullable=True) + new_field: Optional[str] = ormar.String(max_length=200, nullable=True) class HQ(ormar.Model): @@ -45,7 +45,7 @@ class Company(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="company_name") - founded: int = ormar.Integer(nullable=True) + founded: Optional[int] = ormar.Integer(nullable=True) hq: HQ = ormar.ForeignKey(HQ, related_name="companies") diff --git a/tests/test_ordering/test_default_model_order.py b/tests/test_ordering/test_default_model_order.py index 4a7f4aacc..f74ff4817 100644 --- a/tests/test_ordering/test_default_model_order.py +++ b/tests/test_ordering/test_default_model_order.py @@ -25,8 +25,8 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) author: Optional[Author] = ormar.ForeignKey(Author) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - ranking: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) + ranking: Optional[int] = ormar.Integer(nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_ordering/test_default_relation_order.py b/tests/test_ordering/test_default_relation_order.py index 6e395a018..83e8c7ca6 100644 --- a/tests/test_ordering/test_default_relation_order.py +++ b/tests/test_ordering/test_default_relation_order.py @@ -26,8 +26,8 @@ class Book(ormar.Model): Author, orders_by=["name"], related_orders_by=["-year"] ) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - ranking: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) + ranking: Optional[int] = ormar.Integer(nullable=True) class Animal(ormar.Model): diff --git a/tests/test_ordering/test_default_through_relation_order.py b/tests/test_ordering/test_default_through_relation_order.py index ed0889b76..2eaf30d2c 100644 --- a/tests/test_ordering/test_default_through_relation_order.py +++ b/tests/test_ordering/test_default_through_relation_order.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, List, Tuple, Type, cast +from typing import Any, Dict, List, Optional, Tuple, Type, cast from uuid import UUID, uuid4 import ormar @@ -30,8 +30,8 @@ class Link(ormar.Model): ormar_config = base_ormar_config.copy(tablename="link_table") id: UUID = ormar.UUID(primary_key=True, default=uuid4) - animal_order: int = ormar.Integer(nullable=True) - human_order: int = ormar.Integer(nullable=True) + animal_order: Optional[int] = ormar.Integer(nullable=True) + human_order: Optional[int] = ormar.Integer(nullable=True) class Human(ormar.Model): diff --git a/tests/test_ordering/test_proper_order_of_sorting_apply.py b/tests/test_ordering/test_proper_order_of_sorting_apply.py index f6f805192..4e6a83137 100644 --- a/tests/test_ordering/test_proper_order_of_sorting_apply.py +++ b/tests/test_ordering/test_proper_order_of_sorting_apply.py @@ -25,8 +25,8 @@ class Book(ormar.Model): Author, orders_by=["name"], related_orders_by=["-year"] ) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - ranking: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) + ranking: Optional[int] = ormar.Integer(nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_queries/test_aggr_functions.py b/tests/test_queries/test_aggr_functions.py index ba8199dff..f6b1963a6 100644 --- a/tests/test_queries/test_aggr_functions.py +++ b/tests/test_queries/test_aggr_functions.py @@ -26,8 +26,8 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) author: Optional[Author] = ormar.ForeignKey(Author) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - ranking: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) + ranking: Optional[int] = ormar.Integer(nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_queries/test_filter_groups.py b/tests/test_queries/test_filter_groups.py index 5fd8c396f..fe416c6f6 100644 --- a/tests/test_queries/test_filter_groups.py +++ b/tests/test_queries/test_filter_groups.py @@ -21,7 +21,7 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) author: Optional[Author] = ormar.ForeignKey(Author) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_queries/test_isnull_filter.py b/tests/test_queries/test_isnull_filter.py index 0f166caef..483943167 100644 --- a/tests/test_queries/test_isnull_filter.py +++ b/tests/test_queries/test_isnull_filter.py @@ -22,7 +22,7 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) author: Optional[Author] = ormar.ForeignKey(Author) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) class JsonModel(ormar.Model): diff --git a/tests/test_queries/test_or_filters.py b/tests/test_queries/test_or_filters.py index db61646b3..838b168d7 100644 --- a/tests/test_queries/test_or_filters.py +++ b/tests/test_queries/test_or_filters.py @@ -23,7 +23,7 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) author: Optional[Author] = ormar.ForeignKey(Author) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_queries/test_queryproxy_on_m2m_models.py b/tests/test_queries/test_queryproxy_on_m2m_models.py index 23de3d7b9..e46f4a982 100644 --- a/tests/test_queries/test_queryproxy_on_m2m_models.py +++ b/tests/test_queries/test_queryproxy_on_m2m_models.py @@ -30,7 +30,7 @@ class Category(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=40) - sort_order: int = ormar.Integer(nullable=True) + sort_order: Optional[int] = ormar.Integer(nullable=True) subject: Optional[Subject] = ormar.ForeignKey(Subject) diff --git a/tests/test_queries/test_reserved_sql_keywords_escaped.py b/tests/test_queries/test_reserved_sql_keywords_escaped.py index 0998233a7..e82034381 100644 --- a/tests/test_queries/test_reserved_sql_keywords_escaped.py +++ b/tests/test_queries/test_reserved_sql_keywords_escaped.py @@ -1,3 +1,5 @@ +from typing import Optional + import ormar import pytest @@ -20,14 +22,14 @@ class User(ormar.Model): display_name: str = ormar.String( unique=True, index=True, nullable=False, max_length=255 ) - pic_url: str = ormar.Text(nullable=True) + pic_url: Optional[str] = ormar.Text(nullable=True) class Task(ormar.Model): ormar_config = base_ormar_config.copy(tablename="task") id: int = ormar.Integer(primary_key=True, autoincrement=True, nullable=False) - from_: str = ormar.String(name="from", nullable=True, max_length=200) + from_: Optional[str] = ormar.String(name="from", nullable=True, max_length=200) user = ormar.ForeignKey(User) diff --git a/tests/test_queries/test_reverse_fk_queryset.py b/tests/test_queries/test_reverse_fk_queryset.py index 28937084b..aaaf2614b 100644 --- a/tests/test_queries/test_reverse_fk_queryset.py +++ b/tests/test_queries/test_reverse_fk_queryset.py @@ -32,7 +32,7 @@ class Track(ormar.Model): album: Optional[Album] = ormar.ForeignKey(Album, name="album_id") title: str = ormar.String(max_length=100) position: int = ormar.Integer() - play_count: int = ormar.Integer(nullable=True) + play_count: Optional[int] = ormar.Integer(nullable=True) written_by: Optional[Writer] = ormar.ForeignKey(Writer, name="writer_id") diff --git a/tests/test_queries/test_selecting_subset_of_columns.py b/tests/test_queries/test_selecting_subset_of_columns.py index 51d842ce3..90fbb9575 100644 --- a/tests/test_queries/test_selecting_subset_of_columns.py +++ b/tests/test_queries/test_selecting_subset_of_columns.py @@ -17,7 +17,7 @@ class NickNames(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="hq_name") - is_lame: bool = ormar.Boolean(nullable=True) + is_lame: Optional[bool] = ormar.Boolean(nullable=True) class NicksHq(ormar.Model): @@ -37,7 +37,7 @@ class Company(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=100, nullable=False, name="company_name") - founded: int = ormar.Integer(nullable=True) + founded: Optional[int] = ormar.Integer(nullable=True) hq: HQ = ormar.ForeignKey(HQ) @@ -47,10 +47,10 @@ class Car(ormar.Model): id: int = ormar.Integer(primary_key=True) manufacturer: Optional[Company] = ormar.ForeignKey(Company) name: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - gearbox_type: str = ormar.String(max_length=20, nullable=True) - gears: int = ormar.Integer(nullable=True) - aircon_type: str = ormar.String(max_length=20, nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) + gearbox_type: Optional[str] = ormar.String(max_length=20, nullable=True) + gears: Optional[int] = ormar.Integer(nullable=True) + aircon_type: Optional[str] = ormar.String(max_length=20, nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_queries/test_values_and_values_list.py b/tests/test_queries/test_values_and_values_list.py index 76eb4d3cc..e5f2ae2d8 100644 --- a/tests/test_queries/test_values_and_values_list.py +++ b/tests/test_queries/test_values_and_values_list.py @@ -31,7 +31,7 @@ class Category(ormar.Model): id: int = ormar.Integer(primary_key=True) name: str = ormar.String(max_length=40) - sort_order: int = ormar.Integer(nullable=True) + sort_order: Optional[int] = ormar.Integer(nullable=True) created_by: Optional[User] = ormar.ForeignKey(User, related_name="categories") diff --git a/tests/test_relations/test_foreign_keys.py b/tests/test_relations/test_foreign_keys.py index 49c1ae5b6..753118209 100644 --- a/tests/test_relations/test_foreign_keys.py +++ b/tests/test_relations/test_foreign_keys.py @@ -25,7 +25,7 @@ class Track(ormar.Model): album: Optional[Album] = ormar.ForeignKey(Album) title: str = ormar.String(max_length=100) position: int = ormar.Integer() - play_count: int = ormar.Integer(nullable=True, default=0) + play_count: Optional[int] = ormar.Integer(nullable=True, default=0) is_disabled: bool = ormar.Boolean(default=False) diff --git a/tests/test_relations/test_m2m_through_fields.py b/tests/test_relations/test_m2m_through_fields.py index 82b1d6262..ad49f64fe 100644 --- a/tests/test_relations/test_m2m_through_fields.py +++ b/tests/test_relations/test_m2m_through_fields.py @@ -1,4 +1,4 @@ -from typing import Any, ForwardRef +from typing import Any, ForwardRef, Optional import ormar import pytest @@ -20,7 +20,7 @@ class PostCategory(ormar.Model): ormar_config = base_ormar_config.copy(tablename="posts_x_categories") id: int = ormar.Integer(primary_key=True) - sort_order: int = ormar.Integer(nullable=True) + sort_order: Optional[int] = ormar.Integer(nullable=True) param_name: str = ormar.String(default="Name", max_length=200) @@ -47,7 +47,7 @@ class PostCategory2(ormar.Model): ormar_config = base_ormar_config.copy(tablename="posts_x_categories2") id: int = ormar.Integer(primary_key=True) - sort_order: int = ormar.Integer(nullable=True) + sort_order: Optional[int] = ormar.Integer(nullable=True) class Post2(ormar.Model): diff --git a/tests/test_relations/test_prefetch_related.py b/tests/test_relations/test_prefetch_related.py index cb7b17dce..bd85a631e 100644 --- a/tests/test_relations/test_prefetch_related.py +++ b/tests/test_relations/test_prefetch_related.py @@ -28,14 +28,14 @@ class Division(ormar.Model): ormar_config = base_ormar_config.copy(tablename="divisions") id: int = ormar.Integer(name="division_id", primary_key=True) - name: str = ormar.String(max_length=100, nullable=True) + name: Optional[str] = ormar.String(max_length=100, nullable=True) class Shop(ormar.Model): ormar_config = base_ormar_config.copy(tablename="shops") id: int = ormar.Integer(primary_key=True) - name: str = ormar.String(max_length=100, nullable=True) + name: Optional[str] = ormar.String(max_length=100, nullable=True) division: Optional[Division] = ormar.ForeignKey(Division) @@ -47,7 +47,7 @@ class Album(ormar.Model): ormar_config = base_ormar_config.copy(tablename="albums") id: int = ormar.Integer(primary_key=True) - name: str = ormar.String(max_length=100, nullable=True) + name: Optional[str] = ormar.String(max_length=100, nullable=True) shops: List[Shop] = ormar.ManyToMany(to=Shop, through=AlbumShops) @@ -69,7 +69,7 @@ class Cover(ormar.Model): Album, related_name="cover_pictures", name="album_id" ) title: str = ormar.String(max_length=100) - artist: str = ormar.String(max_length=200, nullable=True) + artist: Optional[str] = ormar.String(max_length=200, nullable=True) create_test_database = init_tests(base_ormar_config) diff --git a/tests/test_relations/test_replacing_models_with_copy.py b/tests/test_relations/test_replacing_models_with_copy.py index cf70e3388..11d8a2274 100644 --- a/tests/test_relations/test_replacing_models_with_copy.py +++ b/tests/test_relations/test_replacing_models_with_copy.py @@ -26,7 +26,7 @@ class Track(ormar.Model): album: Optional[Album] = ormar.ForeignKey(Album) title: str = ormar.String(max_length=100) position: int = ormar.Integer() - play_count: int = ormar.Integer(nullable=True, default=0) + play_count: Optional[int] = ormar.Integer(nullable=True, default=0) is_disabled: bool = ormar.Boolean(default=False) properties: Tuple[str, Any] diff --git a/tests/test_relations/test_through_relations_fail.py b/tests/test_relations/test_through_relations_fail.py index e6fcfefab..4abca195e 100644 --- a/tests/test_relations/test_through_relations_fail.py +++ b/tests/test_relations/test_through_relations_fail.py @@ -1,5 +1,6 @@ -# type: ignore +from typing import Optional +# type: ignore import ormar import pytest from ormar import ModelDefinitionError @@ -27,7 +28,7 @@ class PostCategory(ormar.Model): ormar_config = base_ormar_config.copy(tablename="posts_x_categories") id: int = ormar.Integer(primary_key=True) - sort_order: int = ormar.Integer(nullable=True) + sort_order: Optional[int] = ormar.Integer(nullable=True) param_name: str = ormar.String(default="Name", max_length=200) blog = ormar.ForeignKey(Blog) diff --git a/tests/test_types.py b/tests/test_types.py index f9bc3c68f..7c3377e70 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import ormar import pytest @@ -42,8 +44,8 @@ class Book(ormar.Model): id: int = ormar.Integer(primary_key=True) author = ormar.ForeignKey(Author) title: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - ranking: int = ormar.Integer(nullable=True) + year: Optional[int] = ormar.Integer(nullable=True) + ranking: Optional[int] = ormar.Integer(nullable=True) @pytest.fixture(autouse=True, scope="module") From 63193b026dd67195da49bbb8b2b7aadbd3f8055c Mon Sep 17 00:00:00 2001 From: Maxim Srour Date: Wed, 28 Jan 2026 02:52:56 +1100 Subject: [PATCH 5/6] fix: update the type overloads for boolean and enum fields --- ormar/fields/model_fields.py | 101 +++++++++++++++------------------- ormar/fields/model_fields.pyi | 20 ++++++- 2 files changed, 61 insertions(+), 60 deletions(-) diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 8df8c5bd5..8a7482748 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -327,33 +327,26 @@ def get_column_type(cls, **kwargs: Any) -> Any: return sqlalchemy.Float() -if TYPE_CHECKING: # pragma: nocover - - def Boolean(**kwargs: Any) -> bool: - pass +class Boolean(ModelFieldFactory, int): + """ + Boolean field factory that construct Field classes and populated their values. + """ -else: + _type = bool + _sample = True - class Boolean(ModelFieldFactory, int): - """ - Boolean field factory that construct Field classes and populated their values. + @classmethod + def get_column_type(cls, **kwargs: Any) -> Any: """ + Return proper type of db column for given field type. + Accepts required and optional parameters that each column type accepts. - _type = bool - _sample = True - - @classmethod - def get_column_type(cls, **kwargs: Any) -> Any: - """ - Return proper type of db column for given field type. - Accepts required and optional parameters that each column type accepts. - - :param kwargs: key, value pairs of sqlalchemy options - :type kwargs: Any - :return: initialized column with proper options - :rtype: sqlalchemy Column - """ - return sqlalchemy.Boolean() + :param kwargs: key, value pairs of sqlalchemy options + :type kwargs: Any + :return: initialized column with proper options + :rtype: sqlalchemy Column + """ + return sqlalchemy.Boolean() class DateTime(ModelFieldFactory, datetime.datetime): @@ -753,42 +746,34 @@ def get_column_type(cls, **kwargs: Any) -> Any: return sqlalchemy_uuid.UUID(uuid_format=uuid_format) -if TYPE_CHECKING: # pragma: nocover - T = TypeVar("T", bound=E) - - def Enum(enum_class: Type[T], **kwargs: Any) -> T: - pass - -else: - - class Enum(ModelFieldFactory): - """ - Enum field factory that construct Field classes and populated their values. - """ +class Enum(ModelFieldFactory): + """ + Enum field factory that construct Field classes and populated their values. + """ - _type = E - _sample = None + _type = E + _sample = None - def __new__( # type: ignore # noqa CFQ002 - cls, *, enum_class: Type[E], **kwargs: Any - ) -> Self: - kwargs = { - **kwargs, - **{ - k: v - for k, v in locals().items() - if k not in ["cls", "__class__", "kwargs"] - }, - } - return super().__new__(cls, **kwargs) + def __new__( # type: ignore # noqa CFQ002 + cls, *, enum_class: Type[E], **kwargs: Any + ) -> Self: + kwargs = { + **kwargs, + **{ + k: v + for k, v in locals().items() + if k not in ["cls", "__class__", "kwargs"] + }, + } + return super().__new__(cls, **kwargs) - @classmethod - def validate(cls, **kwargs: Any) -> None: - enum_class = kwargs.get("enum_class") - if enum_class is None or not isinstance(enum_class, EnumMeta): - raise ModelDefinitionError("Enum Field choices must be EnumType") + @classmethod + def validate(cls, **kwargs: Any) -> None: + enum_class = kwargs.get("enum_class") + if enum_class is None or not isinstance(enum_class, EnumMeta): + raise ModelDefinitionError("Enum Field choices must be EnumType") - @classmethod - def get_column_type(cls, **kwargs: Any) -> Any: - enum_cls = kwargs.get("enum_class") - return sqlalchemy.Enum(enum_cls) + @classmethod + def get_column_type(cls, **kwargs: Any) -> Any: + enum_cls = kwargs.get("enum_class") + return sqlalchemy.Enum(enum_cls) diff --git a/ormar/fields/model_fields.pyi b/ormar/fields/model_fields.pyi index dc73bd70e..054712df3 100644 --- a/ormar/fields/model_fields.pyi +++ b/ormar/fields/model_fields.pyi @@ -8,7 +8,10 @@ from uuid import UUID as UuidType T = TypeVar("T", bound=EnumBase) -def Boolean(**kwargs: Any) -> bool: ... +@overload +def Boolean(*, nullable: Literal[False] = False, **kwargs: Any) -> bool: ... +@overload +def Boolean(*, nullable: Literal[True], **kwargs: Any) -> bool | None: ... @overload def String( *, max_length: int, nullable: Literal[False] = False, **kwargs: Any @@ -81,4 +84,17 @@ def LargeBinary( def LargeBinary( max_length: int, represent_as_base64_str: Literal[False] = ..., **kwargs: Any ) -> bytes: ... -def Enum(enum_class: Type[T], **kwargs: Any) -> T: ... +@overload +def Enum( + enum_class: Type[T], + *, + nullable: Literal[False] = False, + **kwargs: Any, +) -> T: ... +@overload +def Enum( + enum_class: Type[T], + *, + nullable: Literal[True], + **kwargs: Any, +) -> T | None: ... From f71d08a71ad6c11c34c53fde044b765dfc15fd7f Mon Sep 17 00:00:00 2001 From: Maxim Srour Date: Wed, 28 Jan 2026 03:02:29 +1100 Subject: [PATCH 6/6] fix: consolidate superfluous LargeBinary type defs in stub file --- ormar/fields/model_fields.py | 129 +++++++++++++--------------------- ormar/fields/model_fields.pyi | 6 +- 2 files changed, 53 insertions(+), 82 deletions(-) diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 8a7482748..47acf0d3f 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -3,7 +3,7 @@ import uuid from enum import Enum as E from enum import EnumMeta -from typing import TYPE_CHECKING, Any, Optional, Type, TypeVar, Union, overload +from typing import Any, Optional, Type import pydantic import sqlalchemy @@ -14,11 +14,6 @@ from ormar.fields.base import BaseField # noqa I101 from ormar.fields.sqlalchemy_encrypted import EncryptBackends -try: - from typing import Literal # type: ignore -except ImportError: # pragma: no cover - from typing_extensions import Literal # type: ignore - try: from typing import Self # type: ignore except ImportError: # pragma: no cover @@ -463,81 +458,53 @@ def get_column_type(cls, **kwargs: Any) -> Any: return sqlalchemy.JSON(none_as_null=kwargs.get("sql_nullable", False)) -if TYPE_CHECKING: # pragma: nocover # noqa: C901 - - @overload - def LargeBinary( # type: ignore - max_length: int, *, represent_as_base64_str: Literal[True], **kwargs: Any - ) -> str: ... - - @overload - def LargeBinary( # type: ignore - max_length: int, *, represent_as_base64_str: Literal[False], **kwargs: Any - ) -> bytes: ... - - @overload - def LargeBinary( - max_length: int, represent_as_base64_str: Literal[False] = ..., **kwargs: Any - ) -> bytes: ... - - def LargeBinary( - max_length: int, represent_as_base64_str: bool = False, **kwargs: Any - ) -> Union[str, bytes]: - pass - -else: - - class LargeBinary(ModelFieldFactory, bytes): - """ - LargeBinary field factory that construct Field classes - and populated their values. - """ - - _type = bytes - _sample = "bytes" - - def __new__( # type: ignore # noqa CFQ002 - cls, - *, - max_length: int, - represent_as_base64_str: bool = False, - **kwargs: Any - ) -> Self: # type: ignore - kwargs = { - **kwargs, - **{ - k: v - for k, v in locals().items() - if k not in ["cls", "__class__", "kwargs"] - }, - } - return super().__new__(cls, **kwargs) - - @classmethod - def get_column_type(cls, **kwargs: Any) -> Any: - """ - Return proper type of db column for given field type. - Accepts required and optional parameters that each column type accepts. - - :param kwargs: key, value pairs of sqlalchemy options - :type kwargs: Any - :return: initialized column with proper options - :rtype: sqlalchemy Column - """ - return sqlalchemy.LargeBinary(length=kwargs.get("max_length")) - - @classmethod - def validate(cls, **kwargs: Any) -> None: - """ - Used to validate if all required parameters on a given field type are set. - :param kwargs: all params passed during construction - :type kwargs: Any - """ - max_length = kwargs.get("max_length", None) - if max_length <= 0: - raise ModelDefinitionError( - "Parameter max_length is required for field LargeBinary" - ) +class LargeBinary(ModelFieldFactory, bytes): + """ + LargeBinary field factory that construct Field classes + and populated their values. + """ + + _type = bytes + _sample = "bytes" + + def __new__( # type: ignore # noqa CFQ002 + cls, *, max_length: int, represent_as_base64_str: bool = False, **kwargs: Any + ) -> Self: # type: ignore + kwargs = { + **kwargs, + **{ + k: v + for k, v in locals().items() + if k not in ["cls", "__class__", "kwargs"] + }, + } + return super().__new__(cls, **kwargs) + + @classmethod + def get_column_type(cls, **kwargs: Any) -> Any: + """ + Return proper type of db column for given field type. + Accepts required and optional parameters that each column type accepts. + + :param kwargs: key, value pairs of sqlalchemy options + :type kwargs: Any + :return: initialized column with proper options + :rtype: sqlalchemy Column + """ + return sqlalchemy.LargeBinary(length=kwargs.get("max_length")) + + @classmethod + def validate(cls, **kwargs: Any) -> None: + """ + Used to validate if all required parameters on a given field type are set. + :param kwargs: all params passed during construction + :type kwargs: Any + """ + max_length = kwargs.get("max_length", None) + if max_length <= 0: + raise ModelDefinitionError( + "Parameter max_length is required for field LargeBinary" + ) class BigInteger(Integer, int): diff --git a/ormar/fields/model_fields.pyi b/ormar/fields/model_fields.pyi index 054712df3..3cd96a09e 100644 --- a/ormar/fields/model_fields.pyi +++ b/ormar/fields/model_fields.pyi @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import date, datetime, time from decimal import Decimal as DecimalType from enum import Enum as EnumBase -from typing import Any, Literal, Type, TypeVar, overload +from typing import Any, Literal, Type, TypeVar, Union, overload from uuid import UUID as UuidType T = TypeVar("T", bound=EnumBase) @@ -85,6 +85,10 @@ def LargeBinary( max_length: int, represent_as_base64_str: Literal[False] = ..., **kwargs: Any ) -> bytes: ... @overload +def LargeBinary( + max_length: int, represent_as_base64_str: bool = False, **kwargs: Any +) -> Union[str, bytes]: ... +@overload def Enum( enum_class: Type[T], *,