switch from class to instance fro fields

This commit is contained in:
collerek
2021-03-19 14:22:31 +01:00
parent 61c456a01f
commit 32695ffa1d
26 changed files with 329 additions and 411 deletions

View File

@ -1,7 +1,7 @@
from typing import Any, List, Optional, TYPE_CHECKING, Type, Union from typing import Any, Dict, List, Optional, TYPE_CHECKING, Type, Union
import sqlalchemy import sqlalchemy
from pydantic import Field, Json, typing from pydantic import Json, typing
from pydantic.fields import FieldInfo, Required, Undefined from pydantic.fields import FieldInfo, Required, Undefined
import ormar # noqa I101 import ormar # noqa I101
@ -28,44 +28,62 @@ class BaseField(FieldInfo):
to pydantic field types like ConstrainedStr to pydantic field types like ConstrainedStr
""" """
__type__ = None def __init__(self, **kwargs: Any) -> None:
related_name = None self.__type__: type = kwargs.pop("__type__", None)
self.related_name = kwargs.pop("related_name", None)
column_type: sqlalchemy.Column self.column_type: sqlalchemy.Column = kwargs.pop("column_type", None)
constraints: List = [] self.constraints: List = kwargs.pop("constraints", list())
name: str self.name: str = kwargs.pop("name", None)
alias: str self.db_alias: str = kwargs.pop("alias", None)
primary_key: bool self.primary_key: bool = kwargs.pop("primary_key", False)
autoincrement: bool self.autoincrement: bool = kwargs.pop("autoincrement", False)
nullable: bool self.nullable: bool = kwargs.pop("nullable", False)
index: bool self.index: bool = kwargs.pop("index", False)
unique: bool self.unique: bool = kwargs.pop("unique", False)
pydantic_only: bool self.pydantic_only: bool = kwargs.pop("pydantic_only", False)
choices: typing.Sequence self.choices: typing.Sequence = kwargs.pop("choices", False)
virtual: bool = False # ManyToManyFields and reverse ForeignKeyFields self.virtual: bool = kwargs.pop(
is_multi: bool = False # ManyToManyField "virtual", None
is_relation: bool = False # ForeignKeyField + subclasses ) # ManyToManyFields and reverse ForeignKeyFields
is_through: bool = False # ThroughFields self.is_multi: bool = kwargs.pop("is_multi", None) # ManyToManyField
self.is_relation: bool = kwargs.pop(
"is_relation", None
) # ForeignKeyField + subclasses
self.is_through: bool = kwargs.pop("is_through", False) # ThroughFields
owner: Type["Model"] self.owner: Type["Model"] = kwargs.pop("owner", None)
to: Type["Model"] self.to: Type["Model"] = kwargs.pop("to", None)
through: Type["Model"] self.through: Type["Model"] = kwargs.pop("through", None)
self_reference: bool = False self.self_reference: bool = kwargs.pop("self_reference", False)
self_reference_primary: Optional[str] = None self.self_reference_primary: Optional[str] = kwargs.pop(
orders_by: Optional[List[str]] = None "self_reference_primary", None
related_orders_by: Optional[List[str]] = None )
self.orders_by: Optional[List[str]] = kwargs.pop("orders_by", None)
self.related_orders_by: Optional[List[str]] = kwargs.pop(
"related_orders_by", None
)
encrypt_secret: str self.encrypt_secret: str = kwargs.pop("encrypt_secret", None)
encrypt_backend: EncryptBackends = EncryptBackends.NONE self.encrypt_backend: EncryptBackends = kwargs.pop(
encrypt_custom_backend: Optional[Type[EncryptBackend]] = None "encrypt_backend", EncryptBackends.NONE
)
self.encrypt_custom_backend: Optional[Type[EncryptBackend]] = kwargs.pop(
"encrypt_custom_backend", None
)
default: Any self.ormar_default: Any = kwargs.pop("default", None)
server_default: Any self.server_default: Any = kwargs.pop("server_default", None)
@classmethod for name, value in kwargs.items():
def is_valid_uni_relation(cls) -> bool: setattr(self, name, value)
kwargs.update(self.get_pydantic_default())
super().__init__(**kwargs)
def is_valid_uni_relation(self) -> bool:
""" """
Checks if field is a relation definition but only for ForeignKey relation, Checks if field is a relation definition but only for ForeignKey relation,
so excludes ManyToMany fields, as well as virtual ForeignKey so excludes ManyToMany fields, as well as virtual ForeignKey
@ -78,10 +96,9 @@ class BaseField(FieldInfo):
:return: result of the check :return: result of the check
:rtype: bool :rtype: bool
""" """
return not cls.is_multi and not cls.virtual return not self.is_multi and not self.virtual
@classmethod def get_alias(self) -> str:
def get_alias(cls) -> str:
""" """
Used to translate Model column names to database column names during db queries. Used to translate Model column names to database column names during db queries.
@ -89,73 +106,25 @@ class BaseField(FieldInfo):
otherwise field name in ormar/pydantic otherwise field name in ormar/pydantic
:rtype: str :rtype: str
""" """
return cls.alias if cls.alias else cls.name return self.db_alias if self.db_alias else self.name
@classmethod def get_pydantic_default(self) -> Dict:
def is_valid_field_info_field(cls, field_name: str) -> bool:
"""
Checks if field belongs to pydantic FieldInfo
- used during setting default pydantic values.
Excludes defaults and alias as they are populated separately
(defaults) or not at all (alias)
:param field_name: field name of BaseFIeld
:type field_name: str
:return: True if field is present on pydantic.FieldInfo
:rtype: bool
"""
return (
field_name not in ["default", "default_factory", "alias", "allow_mutation"]
and not field_name.startswith("__")
and hasattr(cls, field_name)
and not callable(getattr(cls, field_name))
)
@classmethod
def get_base_pydantic_field_info(cls, allow_null: bool) -> FieldInfo:
""" """
Generates base pydantic.FieldInfo with only default and optionally Generates base pydantic.FieldInfo with only default and optionally
required to fix pydantic Json field being set to required=False. required to fix pydantic Json field being set to required=False.
Used in an ormar Model Metaclass. Used in an ormar Model Metaclass.
:param allow_null: flag if the default value can be None
or if it should be populated by pydantic Undefined
:type allow_null: bool
:return: instance of base pydantic.FieldInfo :return: instance of base pydantic.FieldInfo
:rtype: pydantic.FieldInfo :rtype: pydantic.FieldInfo
""" """
base = cls.default_value() base = self.default_value()
if base is None: if base is None:
base = ( base = dict(default=None) if self.nullable else dict(default=Undefined)
FieldInfo(default=None) if self.__type__ == Json and base.get("default") is Undefined:
if (cls.nullable or allow_null) base["default"] = Required
else FieldInfo(default=Undefined)
)
if cls.__type__ == Json and base.default is Undefined:
base.default = Required
return base return base
@classmethod def default_value(self, use_server: bool = False) -> Optional[Dict]:
def convert_to_pydantic_field_info(cls, allow_null: bool = False) -> FieldInfo:
"""
Converts a BaseField into pydantic.FieldInfo
that is later easily processed by pydantic.
Used in an ormar Model Metaclass.
:param allow_null: flag if the default value can be None
or if it should be populated by pydantic Undefined
:type allow_null: bool
:return: actual instance of pydantic.FieldInfo with all needed fields populated
:rtype: pydantic.FieldInfo
"""
base = cls.get_base_pydantic_field_info(allow_null=allow_null)
for attr_name in FieldInfo.__dict__.keys():
if cls.is_valid_field_info_field(attr_name):
setattr(base, attr_name, cls.__dict__.get(attr_name))
return base
@classmethod
def default_value(cls, use_server: bool = False) -> Optional[FieldInfo]:
""" """
Returns a FieldInfo instance with populated default Returns a FieldInfo instance with populated default
(static) or default_factory (function). (static) or default_factory (function).
@ -173,17 +142,20 @@ class BaseField(FieldInfo):
which is returning a FieldInfo instance which is returning a FieldInfo instance
:rtype: Optional[pydantic.FieldInfo] :rtype: Optional[pydantic.FieldInfo]
""" """
if cls.is_auto_primary_key(): if self.is_auto_primary_key():
return Field(default=None) return dict(default=None)
if cls.has_default(use_server=use_server): if self.has_default(use_server=use_server):
default = cls.default if cls.default is not None else cls.server_default default = (
self.ormar_default
if self.ormar_default is not None
else self.server_default
)
if callable(default): if callable(default):
return Field(default_factory=default) return dict(default_factory=default)
return Field(default=default) return dict(default=default)
return None return None
@classmethod def get_default(self, use_server: bool = False) -> Any: # noqa CCR001
def get_default(cls, use_server: bool = False) -> Any: # noqa CCR001
""" """
Return default value for a field. Return default value for a field.
If the field is Callable the function is called and actual result is returned. If the field is Callable the function is called and actual result is returned.
@ -195,18 +167,17 @@ class BaseField(FieldInfo):
:return: default value for the field if set, otherwise implicit None :return: default value for the field if set, otherwise implicit None
:rtype: Any :rtype: Any
""" """
if cls.has_default(): if self.has_default():
default = ( default = (
cls.default self.ormar_default
if cls.default is not None if self.ormar_default is not None
else (cls.server_default if use_server else None) else (self.server_default if use_server else None)
) )
if callable(default): if callable(default):
default = default() default = default()
return default return default
@classmethod def has_default(self, use_server: bool = True) -> bool:
def has_default(cls, use_server: bool = True) -> bool:
""" """
Checks if the field has default value set. Checks if the field has default value set.
@ -216,12 +187,11 @@ class BaseField(FieldInfo):
:return: result of the check if default value is set :return: result of the check if default value is set
:rtype: bool :rtype: bool
""" """
return cls.default is not None or ( return self.ormar_default is not None or (
cls.server_default is not None and use_server self.server_default is not None and use_server
) )
@classmethod def is_auto_primary_key(self) -> bool:
def is_auto_primary_key(cls) -> bool:
""" """
Checks if field is first a primary key and if it, Checks if field is first a primary key and if it,
it's than check if it's set to autoincrement. it's than check if it's set to autoincrement.
@ -230,12 +200,11 @@ class BaseField(FieldInfo):
:return: result of the check for primary key and autoincrement :return: result of the check for primary key and autoincrement
:rtype: bool :rtype: bool
""" """
if cls.primary_key: if self.primary_key:
return cls.autoincrement return self.autoincrement
return False return False
@classmethod def construct_constraints(self) -> List:
def construct_constraints(cls) -> List:
""" """
Converts list of ormar constraints into sqlalchemy ForeignKeys. Converts list of ormar constraints into sqlalchemy ForeignKeys.
Has to be done dynamically as sqlalchemy binds ForeignKey to the table. Has to be done dynamically as sqlalchemy binds ForeignKey to the table.
@ -249,15 +218,14 @@ class BaseField(FieldInfo):
con.reference, con.reference,
ondelete=con.ondelete, ondelete=con.ondelete,
onupdate=con.onupdate, onupdate=con.onupdate,
name=f"fk_{cls.owner.Meta.tablename}_{cls.to.Meta.tablename}" name=f"fk_{self.owner.Meta.tablename}_{self.to.Meta.tablename}"
f"_{cls.to.get_column_alias(cls.to.Meta.pkname)}_{cls.name}", f"_{self.to.get_column_alias(self.to.Meta.pkname)}_{self.name}",
) )
for con in cls.constraints for con in self.constraints
] ]
return constraints return constraints
@classmethod def get_column(self, name: str) -> sqlalchemy.Column:
def get_column(cls, name: str) -> sqlalchemy.Column:
""" """
Returns definition of sqlalchemy.Column used in creation of sqlalchemy.Table. Returns definition of sqlalchemy.Column used in creation of sqlalchemy.Table.
Populates name, column type constraints, as well as a number of parameters like Populates name, column type constraints, as well as a number of parameters like
@ -268,24 +236,23 @@ class BaseField(FieldInfo):
:return: actual definition of the database column as sqlalchemy requires. :return: actual definition of the database column as sqlalchemy requires.
:rtype: sqlalchemy.Column :rtype: sqlalchemy.Column
""" """
if cls.encrypt_backend == EncryptBackends.NONE: if self.encrypt_backend == EncryptBackends.NONE:
column = sqlalchemy.Column( column = sqlalchemy.Column(
cls.alias or name, self.db_alias or name,
cls.column_type, self.column_type,
*cls.construct_constraints(), *self.construct_constraints(),
primary_key=cls.primary_key, primary_key=self.primary_key,
nullable=cls.nullable and not cls.primary_key, nullable=self.nullable and not self.primary_key,
index=cls.index, index=self.index,
unique=cls.unique, unique=self.unique,
default=cls.default, default=self.ormar_default,
server_default=cls.server_default, server_default=self.server_default,
) )
else: else:
column = cls._get_encrypted_column(name=name) column = self._get_encrypted_column(name=name)
return column return column
@classmethod def _get_encrypted_column(self, name: str) -> sqlalchemy.Column:
def _get_encrypted_column(cls, name: str) -> sqlalchemy.Column:
""" """
Returns EncryptedString column type instead of actual column. Returns EncryptedString column type instead of actual column.
@ -294,29 +261,28 @@ class BaseField(FieldInfo):
:return: newly defined column :return: newly defined column
:rtype: sqlalchemy.Column :rtype: sqlalchemy.Column
""" """
if cls.primary_key or cls.is_relation: if self.primary_key or self.is_relation:
raise ModelDefinitionError( raise ModelDefinitionError(
"Primary key field and relations fields" "cannot be encrypted!" "Primary key field and relations fields" "cannot be encrypted!"
) )
column = sqlalchemy.Column( column = sqlalchemy.Column(
cls.alias or name, self.db_alias or name,
EncryptedString( EncryptedString(
_field_type=cls, _field_type=self,
encrypt_secret=cls.encrypt_secret, encrypt_secret=self.encrypt_secret,
encrypt_backend=cls.encrypt_backend, encrypt_backend=self.encrypt_backend,
encrypt_custom_backend=cls.encrypt_custom_backend, encrypt_custom_backend=self.encrypt_custom_backend,
), ),
nullable=cls.nullable, nullable=self.nullable,
index=cls.index, index=self.index,
unique=cls.unique, unique=self.unique,
default=cls.default, default=self.ormar_default,
server_default=cls.server_default, server_default=self.server_default,
) )
return column return column
@classmethod
def expand_relationship( def expand_relationship(
cls, self,
value: Any, value: Any,
child: Union["Model", "NewBaseModel"], child: Union["Model", "NewBaseModel"],
to_register: bool = True, to_register: bool = True,
@ -339,21 +305,19 @@ class BaseField(FieldInfo):
""" """
return value return value
@classmethod def set_self_reference_flag(self) -> None:
def set_self_reference_flag(cls) -> None:
""" """
Sets `self_reference` to True if field to and owner are same model. Sets `self_reference` to True if field to and owner are same model.
:return: None :return: None
:rtype: None :rtype: None
""" """
if cls.owner is not None and ( if self.owner is not None and (
cls.owner == cls.to or cls.owner.Meta == cls.to.Meta self.owner == self.to or self.owner.Meta == self.to.Meta
): ):
cls.self_reference = True self.self_reference = True
cls.self_reference_primary = cls.name self.self_reference_primary = self.name
@classmethod def has_unresolved_forward_refs(self) -> bool:
def has_unresolved_forward_refs(cls) -> bool:
""" """
Verifies if the filed has any ForwardRefs that require updating before the Verifies if the filed has any ForwardRefs that require updating before the
model can be used. model can be used.
@ -363,8 +327,7 @@ class BaseField(FieldInfo):
""" """
return False return False
@classmethod def evaluate_forward_ref(self, globalns: Any, localns: Any) -> None:
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
""" """
Evaluates the ForwardRef to actual Field based on global and local namespaces Evaluates the ForwardRef to actual Field based on global and local namespaces
@ -376,8 +339,7 @@ class BaseField(FieldInfo):
:rtype: None :rtype: None
""" """
@classmethod def get_related_name(self) -> str:
def get_related_name(cls) -> str:
""" """
Returns name to use for reverse relation. Returns name to use for reverse relation.
It's either set as `related_name` or by default it's owner model. get_name + 's' It's either set as `related_name` or by default it's owner model. get_name + 's'

View File

@ -56,7 +56,7 @@ def create_dummy_instance(fk: Type["Model"], pk: Any = None) -> "Model":
def create_dummy_model( def create_dummy_model(
base_model: Type["Model"], base_model: Type["Model"],
pk_field: Type[Union[BaseField, "ForeignKeyField", "ManyToManyField"]], pk_field: Union[BaseField, "ForeignKeyField", "ManyToManyField"],
) -> Type["BaseModel"]: ) -> Type["BaseModel"]:
""" """
Used to construct a dummy pydantic model for type hints and pydantic validation. Used to construct a dummy pydantic model for type hints and pydantic validation.
@ -65,7 +65,7 @@ def create_dummy_model(
:param base_model: class of target dummy model :param base_model: class of target dummy model
:type base_model: Model class :type base_model: Model class
:param pk_field: ormar Field to be set on pydantic Model :param pk_field: ormar Field to be set on pydantic Model
:type pk_field: Type[Union[BaseField, "ForeignKeyField", "ManyToManyField"]] :type pk_field: Union[BaseField, "ForeignKeyField", "ManyToManyField"]
:return: constructed dummy model :return: constructed dummy model
:rtype: pydantic.BaseModel :rtype: pydantic.BaseModel
""" """
@ -256,7 +256,8 @@ def ForeignKey( # noqa CFQ002
related_orders_by=related_orders_by, related_orders_by=related_orders_by,
) )
return type("ForeignKey", (ForeignKeyField, BaseField), namespace) Field = type("ForeignKey", (ForeignKeyField, BaseField), {})
return Field(**namespace)
class ForeignKeyField(BaseField): class ForeignKeyField(BaseField):
@ -264,15 +265,15 @@ class ForeignKeyField(BaseField):
Actual class returned from ForeignKey function call and stored in model_fields. Actual class returned from ForeignKey function call and stored in model_fields.
""" """
to: Type["Model"] def __init__(self, **kwargs: Any) -> None:
name: str if TYPE_CHECKING: # pragma: no cover
related_name: str # type: ignore self.__type__: type
virtual: bool self.to: Type["Model"]
ondelete: str self.ondelete: str = kwargs.pop("ondelete", None)
onupdate: str self.onupdate: str = kwargs.pop("onupdate", None)
super().__init__(**kwargs)
@classmethod def get_source_related_name(self) -> str:
def get_source_related_name(cls) -> str:
""" """
Returns name to use for source relation name. Returns name to use for source relation name.
For FK it's the same, differs for m2m fields. For FK it's the same, differs for m2m fields.
@ -280,20 +281,18 @@ class ForeignKeyField(BaseField):
:return: name of the related_name or default related name. :return: name of the related_name or default related name.
:rtype: str :rtype: str
""" """
return cls.get_related_name() return self.get_related_name()
@classmethod def get_related_name(self) -> str:
def get_related_name(cls) -> str:
""" """
Returns name to use for reverse relation. Returns name to use for reverse relation.
It's either set as `related_name` or by default it's owner model. get_name + 's' It's either set as `related_name` or by default it's owner model. get_name + 's'
:return: name of the related_name or default related name. :return: name of the related_name or default related name.
:rtype: str :rtype: str
""" """
return cls.related_name or cls.owner.get_name() + "s" return self.related_name or self.owner.get_name() + "s"
@classmethod def evaluate_forward_ref(self, globalns: Any, localns: Any) -> None:
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
""" """
Evaluates the ForwardRef to actual Field based on global and local namespaces Evaluates the ForwardRef to actual Field based on global and local namespaces
@ -304,26 +303,25 @@ class ForeignKeyField(BaseField):
:return: None :return: None
:rtype: None :rtype: None
""" """
if cls.to.__class__ == ForwardRef: if self.to.__class__ == ForwardRef:
cls.to = evaluate_forwardref( self.to = evaluate_forwardref(
cls.to, # type: ignore self.to, # type: ignore
globalns, globalns,
localns or None, localns or None,
) )
( (
cls.__type__, self.__type__,
cls.constraints, self.constraints,
cls.column_type, self.column_type,
) = populate_fk_params_based_on_to_model( ) = populate_fk_params_based_on_to_model(
to=cls.to, to=self.to,
nullable=cls.nullable, nullable=self.nullable,
ondelete=cls.ondelete, ondelete=self.ondelete,
onupdate=cls.onupdate, onupdate=self.onupdate,
) )
@classmethod
def _extract_model_from_sequence( def _extract_model_from_sequence(
cls, value: List, child: "Model", to_register: bool, self, value: List, child: "Model", to_register: bool,
) -> List["Model"]: ) -> List["Model"]:
""" """
Takes a list of Models and registers them on parent. Takes a list of Models and registers them on parent.
@ -341,15 +339,14 @@ class ForeignKeyField(BaseField):
:rtype: List["Model"] :rtype: List["Model"]
""" """
return [ return [
cls.expand_relationship( # type: ignore self.expand_relationship( # type: ignore
value=val, child=child, to_register=to_register, value=val, child=child, to_register=to_register,
) )
for val in value for val in value
] ]
@classmethod
def _register_existing_model( def _register_existing_model(
cls, value: "Model", child: "Model", to_register: bool, self, value: "Model", child: "Model", to_register: bool,
) -> "Model": ) -> "Model":
""" """
Takes already created instance and registers it for parent. Takes already created instance and registers it for parent.
@ -367,12 +364,11 @@ class ForeignKeyField(BaseField):
:rtype: Model :rtype: Model
""" """
if to_register: if to_register:
cls.register_relation(model=value, child=child) self.register_relation(model=value, child=child)
return value return value
@classmethod
def _construct_model_from_dict( def _construct_model_from_dict(
cls, value: dict, child: "Model", to_register: bool self, value: dict, child: "Model", to_register: bool
) -> "Model": ) -> "Model":
""" """
Takes a dictionary, creates a instance and registers it for parent. Takes a dictionary, creates a instance and registers it for parent.
@ -390,16 +386,15 @@ class ForeignKeyField(BaseField):
:return: (if needed) registered Model :return: (if needed) registered Model
:rtype: Model :rtype: Model
""" """
if len(value.keys()) == 1 and list(value.keys())[0] == cls.to.Meta.pkname: if len(value.keys()) == 1 and list(value.keys())[0] == self.to.Meta.pkname:
value["__pk_only__"] = True value["__pk_only__"] = True
model = cls.to(**value) model = self.to(**value)
if to_register: if to_register:
cls.register_relation(model=model, child=child) self.register_relation(model=model, child=child)
return model return model
@classmethod
def _construct_model_from_pk( def _construct_model_from_pk(
cls, value: Any, child: "Model", to_register: bool self, value: Any, child: "Model", to_register: bool
) -> "Model": ) -> "Model":
""" """
Takes a pk value, creates a dummy instance and registers it for parent. Takes a pk value, creates a dummy instance and registers it for parent.
@ -416,21 +411,20 @@ class ForeignKeyField(BaseField):
:return: (if needed) registered Model :return: (if needed) registered Model
:rtype: Model :rtype: Model
""" """
if cls.to.pk_type() == uuid.UUID and isinstance(value, str): # pragma: nocover if self.to.pk_type() == uuid.UUID and isinstance(value, str): # pragma: nocover
value = uuid.UUID(value) value = uuid.UUID(value)
if not isinstance(value, cls.to.pk_type()): if not isinstance(value, self.to.pk_type()):
raise RelationshipInstanceError( raise RelationshipInstanceError(
f"Relationship error - ForeignKey {cls.to.__name__} " f"Relationship error - ForeignKey {self.to.__name__} "
f"is of type {cls.to.pk_type()} " f"is of type {self.to.pk_type()} "
f"while {type(value)} passed as a parameter." f"while {type(value)} passed as a parameter."
) )
model = create_dummy_instance(fk=cls.to, pk=value) model = create_dummy_instance(fk=self.to, pk=value)
if to_register: if to_register:
cls.register_relation(model=model, child=child) self.register_relation(model=model, child=child)
return model return model
@classmethod def register_relation(self, model: "Model", child: "Model") -> None:
def register_relation(cls, model: "Model", child: "Model") -> None:
""" """
Registers relation between parent and child in relation manager. Registers relation between parent and child in relation manager.
Relation manager is kep on each model (different instance). Relation manager is kep on each model (different instance).
@ -444,11 +438,10 @@ class ForeignKeyField(BaseField):
:type child: Model class :type child: Model class
""" """
model._orm.add( model._orm.add(
parent=model, child=child, field=cls, parent=model, child=child, field=self,
) )
@classmethod def has_unresolved_forward_refs(self) -> bool:
def has_unresolved_forward_refs(cls) -> bool:
""" """
Verifies if the filed has any ForwardRefs that require updating before the Verifies if the filed has any ForwardRefs that require updating before the
model can be used. model can be used.
@ -456,11 +449,10 @@ class ForeignKeyField(BaseField):
:return: result of the check :return: result of the check
:rtype: bool :rtype: bool
""" """
return cls.to.__class__ == ForwardRef return self.to.__class__ == ForwardRef
@classmethod
def expand_relationship( def expand_relationship(
cls, self,
value: Any, value: Any,
child: Union["Model", "NewBaseModel"], child: Union["Model", "NewBaseModel"],
to_register: bool = True, to_register: bool = True,
@ -483,20 +475,19 @@ class ForeignKeyField(BaseField):
:rtype: Optional[Union["Model", List["Model"]]] :rtype: Optional[Union["Model", List["Model"]]]
""" """
if value is None: if value is None:
return None if not cls.virtual else [] return None if not self.virtual else []
constructors = { constructors = {
f"{cls.to.__name__}": cls._register_existing_model, f"{self.to.__name__}": self._register_existing_model,
"dict": cls._construct_model_from_dict, "dict": self._construct_model_from_dict,
"list": cls._extract_model_from_sequence, "list": self._extract_model_from_sequence,
} }
model = constructors.get( # type: ignore model = constructors.get( # type: ignore
value.__class__.__name__, cls._construct_model_from_pk value.__class__.__name__, self._construct_model_from_pk
)(value, child, to_register) )(value, child, to_register)
return model return model
@classmethod def get_relation_name(self) -> str: # pragma: no cover
def get_relation_name(cls) -> str: # pragma: no cover
""" """
Returns name of the relation, which can be a own name or through model Returns name of the relation, which can be a own name or through model
names for m2m models names for m2m models
@ -504,14 +495,13 @@ class ForeignKeyField(BaseField):
:return: result of the check :return: result of the check
:rtype: bool :rtype: bool
""" """
return cls.name return self.name
@classmethod def get_source_model(self) -> Type["Model"]: # pragma: no cover
def get_source_model(cls) -> Type["Model"]: # pragma: no cover
""" """
Returns model from which the relation comes -> either owner or through model Returns model from which the relation comes -> either owner or through model
:return: source model :return: source model
:rtype: Type["Model"] :rtype: Type["Model"]
""" """
return cls.owner return self.owner

View File

@ -132,7 +132,8 @@ def ManyToMany(
related_orders_by=related_orders_by, related_orders_by=related_orders_by,
) )
return type("ManyToMany", (ManyToManyField, BaseField), namespace) Field = type("ManyToMany", (ManyToManyField, BaseField), {})
return Field(**namespace)
class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationProtocol): class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationProtocol):
@ -140,8 +141,14 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
Actual class returned from ManyToMany function call and stored in model_fields. Actual class returned from ManyToMany function call and stored in model_fields.
""" """
@classmethod def __init__(self, **kwargs: Any) -> None:
def get_source_related_name(cls) -> str: if TYPE_CHECKING: # pragma: no cover
self.__type__: type
self.to: Type["Model"]
self.through: Type["Model"]
super().__init__(**kwargs)
def get_source_related_name(self) -> str:
""" """
Returns name to use for source relation name. Returns name to use for source relation name.
For FK it's the same, differs for m2m fields. For FK it's the same, differs for m2m fields.
@ -150,32 +157,31 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
:rtype: str :rtype: str
""" """
return ( return (
cls.through.Meta.model_fields[cls.default_source_field_name()].related_name self.through.Meta.model_fields[
or cls.name self.default_source_field_name()
].related_name
or self.name
) )
@classmethod def default_target_field_name(self) -> str:
def default_target_field_name(cls) -> str:
""" """
Returns default target model name on through model. Returns default target model name on through model.
:return: name of the field :return: name of the field
:rtype: str :rtype: str
""" """
prefix = "from_" if cls.self_reference else "" prefix = "from_" if self.self_reference else ""
return f"{prefix}{cls.to.get_name()}" return f"{prefix}{self.to.get_name()}"
@classmethod def default_source_field_name(self) -> str:
def default_source_field_name(cls) -> str:
""" """
Returns default target model name on through model. Returns default target model name on through model.
:return: name of the field :return: name of the field
:rtype: str :rtype: str
""" """
prefix = "to_" if cls.self_reference else "" prefix = "to_" if self.self_reference else ""
return f"{prefix}{cls.owner.get_name()}" return f"{prefix}{self.owner.get_name()}"
@classmethod def has_unresolved_forward_refs(self) -> bool:
def has_unresolved_forward_refs(cls) -> bool:
""" """
Verifies if the filed has any ForwardRefs that require updating before the Verifies if the filed has any ForwardRefs that require updating before the
model can be used. model can be used.
@ -183,10 +189,9 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
:return: result of the check :return: result of the check
:rtype: bool :rtype: bool
""" """
return cls.to.__class__ == ForwardRef or cls.through.__class__ == ForwardRef return self.to.__class__ == ForwardRef or self.through.__class__ == ForwardRef
@classmethod def evaluate_forward_ref(self, globalns: Any, localns: Any) -> None:
def evaluate_forward_ref(cls, globalns: Any, localns: Any) -> None:
""" """
Evaluates the ForwardRef to actual Field based on global and local namespaces Evaluates the ForwardRef to actual Field based on global and local namespaces
@ -197,27 +202,26 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
:return: None :return: None
:rtype: None :rtype: None
""" """
if cls.to.__class__ == ForwardRef: if self.to.__class__ == ForwardRef:
cls.to = evaluate_forwardref( self.to = evaluate_forwardref(
cls.to, # type: ignore self.to, # type: ignore
globalns, globalns,
localns or None, localns or None,
) )
(cls.__type__, cls.column_type,) = populate_m2m_params_based_on_to_model( (self.__type__, self.column_type,) = populate_m2m_params_based_on_to_model(
to=cls.to, nullable=cls.nullable, to=self.to, nullable=self.nullable,
) )
if cls.through.__class__ == ForwardRef: if self.through.__class__ == ForwardRef:
cls.through = evaluate_forwardref( self.through = evaluate_forwardref(
cls.through, # type: ignore self.through, # type: ignore
globalns, globalns,
localns or None, localns or None,
) )
forbid_through_relations(cls.through) forbid_through_relations(self.through)
@classmethod def get_relation_name(self) -> str:
def get_relation_name(cls) -> str:
""" """
Returns name of the relation, which can be a own name or through model Returns name of the relation, which can be a own name or through model
names for m2m models names for m2m models
@ -225,34 +229,32 @@ class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationPro
:return: result of the check :return: result of the check
:rtype: bool :rtype: bool
""" """
if cls.self_reference and cls.name == cls.self_reference_primary: if self.self_reference and self.name == self.self_reference_primary:
return cls.default_source_field_name() return self.default_source_field_name()
return cls.default_target_field_name() return self.default_target_field_name()
@classmethod def get_source_model(self) -> Type["Model"]:
def get_source_model(cls) -> Type["Model"]:
""" """
Returns model from which the relation comes -> either owner or through model Returns model from which the relation comes -> either owner or through model
:return: source model :return: source model
:rtype: Type["Model"] :rtype: Type["Model"]
""" """
return cls.through return self.through
@classmethod def create_default_through_model(self) -> None:
def create_default_through_model(cls) -> None:
""" """
Creates default empty through model if no additional fields are required. Creates default empty through model if no additional fields are required.
""" """
owner_name = cls.owner.get_name(lower=False) owner_name = self.owner.get_name(lower=False)
to_name = cls.to.get_name(lower=False) to_name = self.to.get_name(lower=False)
class_name = f"{owner_name}{to_name}" class_name = f"{owner_name}{to_name}"
table_name = f"{owner_name.lower()}s_{to_name.lower()}s" table_name = f"{owner_name.lower()}s_{to_name.lower()}s"
new_meta_namespace = { new_meta_namespace = {
"tablename": table_name, "tablename": table_name,
"database": cls.owner.Meta.database, "database": self.owner.Meta.database,
"metadata": cls.owner.Meta.metadata, "metadata": self.owner.Meta.metadata,
} }
new_meta = type("Meta", (), new_meta_namespace) new_meta = type("Meta", (), new_meta_namespace)
through_model = type(class_name, (ormar.Model,), {"Meta": new_meta}) through_model = type(class_name, (ormar.Model,), {"Meta": new_meta})
cls.through = cast(Type["Model"], through_model) self.through = cast(Type["Model"], through_model)

View File

@ -1,7 +1,7 @@
import datetime import datetime
import decimal import decimal
import uuid import uuid
from typing import Any, Optional, TYPE_CHECKING, Type from typing import Any, Optional, TYPE_CHECKING
import pydantic import pydantic
import sqlalchemy import sqlalchemy
@ -63,7 +63,7 @@ class ModelFieldFactory:
_bases: Any = (BaseField,) _bases: Any = (BaseField,)
_type: Any = None _type: Any = None
def __new__(cls, *args: Any, **kwargs: Any) -> Type[BaseField]: # type: ignore def __new__(cls, *args: Any, **kwargs: Any) -> BaseField: # type: ignore
cls.validate(**kwargs) cls.validate(**kwargs)
default = kwargs.pop("default", None) default = kwargs.pop("default", None)
@ -77,7 +77,6 @@ class ModelFieldFactory:
encrypt_secret = kwargs.pop("encrypt_secret", None) encrypt_secret = kwargs.pop("encrypt_secret", None)
encrypt_backend = kwargs.pop("encrypt_backend", EncryptBackends.NONE) encrypt_backend = kwargs.pop("encrypt_backend", EncryptBackends.NONE)
encrypt_custom_backend = kwargs.pop("encrypt_custom_backend", None) encrypt_custom_backend = kwargs.pop("encrypt_custom_backend", None)
encrypt_max_length = kwargs.pop("encrypt_max_length", 5000)
namespace = dict( namespace = dict(
__type__=cls._type, __type__=cls._type,
@ -97,10 +96,10 @@ class ModelFieldFactory:
encrypt_secret=encrypt_secret, encrypt_secret=encrypt_secret,
encrypt_backend=encrypt_backend, encrypt_backend=encrypt_backend,
encrypt_custom_backend=encrypt_custom_backend, encrypt_custom_backend=encrypt_custom_backend,
encrypt_max_length=encrypt_max_length,
**kwargs **kwargs
) )
return type(cls.__name__, cls._bases, namespace) Field = type(cls.__name__, cls._bases, {})
return Field(**namespace)
@classmethod @classmethod
def get_column_type(cls, **kwargs: Any) -> Any: # pragma no cover def get_column_type(cls, **kwargs: Any) -> Any: # pragma no cover
@ -141,7 +140,7 @@ class String(ModelFieldFactory, str):
curtail_length: int = None, curtail_length: int = None,
regex: str = None, regex: str = None,
**kwargs: Any **kwargs: Any
) -> Type[BaseField]: # type: ignore ) -> BaseField: # type: ignore
kwargs = { kwargs = {
**kwargs, **kwargs,
**{ **{
@ -194,7 +193,7 @@ class Integer(ModelFieldFactory, int):
maximum: int = None, maximum: int = None,
multiple_of: int = None, multiple_of: int = None,
**kwargs: Any **kwargs: Any
) -> Type[BaseField]: ) -> BaseField:
autoincrement = kwargs.pop("autoincrement", None) autoincrement = kwargs.pop("autoincrement", None)
autoincrement = ( autoincrement = (
autoincrement autoincrement
@ -236,7 +235,7 @@ class Text(ModelFieldFactory, str):
def __new__( # type: ignore def __new__( # type: ignore
cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any
) -> Type[BaseField]: ) -> BaseField:
kwargs = { kwargs = {
**kwargs, **kwargs,
**{ **{
@ -276,7 +275,7 @@ class Float(ModelFieldFactory, float):
maximum: float = None, maximum: float = None,
multiple_of: int = None, multiple_of: int = None,
**kwargs: Any **kwargs: Any
) -> Type[BaseField]: ) -> BaseField:
kwargs = { kwargs = {
**kwargs, **kwargs,
**{ **{
@ -430,7 +429,7 @@ class BigInteger(Integer, int):
maximum: int = None, maximum: int = None,
multiple_of: int = None, multiple_of: int = None,
**kwargs: Any **kwargs: Any
) -> Type[BaseField]: ) -> BaseField:
autoincrement = kwargs.pop("autoincrement", None) autoincrement = kwargs.pop("autoincrement", None)
autoincrement = ( autoincrement = (
autoincrement autoincrement
@ -481,7 +480,7 @@ class Decimal(ModelFieldFactory, decimal.Decimal):
max_digits: int = None, max_digits: int = None,
decimal_places: int = None, decimal_places: int = None,
**kwargs: Any **kwargs: Any
) -> Type[BaseField]: ) -> BaseField:
kwargs = { kwargs = {
**kwargs, **kwargs,
**{ **{
@ -544,7 +543,7 @@ class UUID(ModelFieldFactory, uuid.UUID):
def __new__( # type: ignore # noqa CFQ002 def __new__( # type: ignore # noqa CFQ002
cls, *, uuid_format: str = "hex", **kwargs: Any cls, *, uuid_format: str = "hex", **kwargs: Any
) -> Type[BaseField]: ) -> BaseField:
kwargs = { kwargs = {
**kwargs, **kwargs,
**{ **{

View File

@ -133,7 +133,7 @@ class EncryptedString(types.TypeDecorator):
raise ModelDefinitionError("Wrong or no encrypt backend provided!") raise ModelDefinitionError("Wrong or no encrypt backend provided!")
self.backend: EncryptBackend = backend() self.backend: EncryptBackend = backend()
self._field_type: Type["BaseField"] = _field_type self._field_type: "BaseField" = _field_type
self._underlying_type: Any = _field_type.column_type self._underlying_type: Any = _field_type.column_type
self._key: Union[str, Callable] = encrypt_secret self._key: Union[str, Callable] = encrypt_secret
type_ = self._field_type.__type__ type_ = self._field_type.__type__

View File

@ -57,7 +57,8 @@ def Through( # noqa CFQ002
is_through=True, is_through=True,
) )
return type("Through", (ThroughField, BaseField), namespace) Field = type("Through", (ThroughField, BaseField), {})
return Field(**namespace)
class ThroughField(ForeignKeyField): class ThroughField(ForeignKeyField):

View File

@ -12,7 +12,7 @@ if TYPE_CHECKING: # pragma no cover
from ormar.fields import BaseField from ormar.fields import BaseField
def is_field_an_forward_ref(field: Type["BaseField"]) -> bool: def is_field_an_forward_ref(field: "BaseField") -> bool:
""" """
Checks if field is a relation field and whether any of the referenced models Checks if field is a relation field and whether any of the referenced models
are ForwardRefs that needs to be updated before proceeding. are ForwardRefs that needs to be updated before proceeding.

View File

@ -1,12 +1,10 @@
import warnings
from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type from typing import Dict, Optional, TYPE_CHECKING, Tuple, Type
import pydantic import pydantic
from pydantic.fields import ModelField from pydantic.fields import ModelField
from pydantic.utils import lenient_issubclass from pydantic.utils import lenient_issubclass
import ormar # noqa: I100, I202 from ormar.fields import BaseField # noqa: I100, I202
from ormar.fields import BaseField
if TYPE_CHECKING: # pragma no cover if TYPE_CHECKING: # pragma no cover
from ormar import Model from ormar import Model
@ -14,7 +12,7 @@ if TYPE_CHECKING: # pragma no cover
def create_pydantic_field( def create_pydantic_field(
field_name: str, model: Type["Model"], model_field: Type["ManyToManyField"] field_name: str, model: Type["Model"], model_field: "ManyToManyField"
) -> None: ) -> None:
""" """
Registers pydantic field on through model that leads to passed model Registers pydantic field on through model that leads to passed model
@ -59,38 +57,6 @@ def get_pydantic_field(field_name: str, model: Type["Model"]) -> "ModelField":
) )
def populate_default_pydantic_field_value(
ormar_field: Type["BaseField"], field_name: str, attrs: dict
) -> dict:
"""
Grabs current value of the ormar Field in class namespace
(so the default_value declared on ormar model if set)
and converts it to pydantic.FieldInfo
that pydantic is able to extract later.
On FieldInfo there are saved all needed params like max_length of the string
and other constraints that pydantic can use to build
it's own field validation used by ormar.
:param ormar_field: field to convert
:type ormar_field: ormar Field
:param field_name: field to convert name
:type field_name: str
:param attrs: current class namespace
:type attrs: Dict
:return: updated namespace dict
:rtype: Dict
"""
curr_def_value = attrs.get(field_name, ormar.Undefined)
if lenient_issubclass(curr_def_value, ormar.fields.BaseField):
curr_def_value = ormar.Undefined
if curr_def_value is None:
attrs[field_name] = ormar_field.convert_to_pydantic_field_info(allow_null=True)
else:
attrs[field_name] = ormar_field.convert_to_pydantic_field_info()
return attrs
def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]:
""" """
Extracts ormar fields from annotations (deprecated) and from namespace Extracts ormar fields from annotations (deprecated) and from namespace
@ -110,22 +76,11 @@ def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]:
:rtype: Tuple[Dict, Dict] :rtype: Tuple[Dict, Dict]
""" """
model_fields = {} model_fields = {}
potential_fields = { potential_fields = {}
k: v
for k, v in attrs["__annotations__"].items()
if lenient_issubclass(v, BaseField)
}
if potential_fields:
warnings.warn(
"Using ormar.Fields as type Model annotation has been deprecated,"
" check documentation of current version",
DeprecationWarning,
)
potential_fields.update(get_potential_fields(attrs)) potential_fields.update(get_potential_fields(attrs))
for field_name, field in potential_fields.items(): for field_name, field in potential_fields.items():
field.name = field_name field.name = field_name
attrs = populate_default_pydantic_field_value(field, field_name, attrs)
model_fields[field_name] = field model_fields[field_name] = field
attrs["__annotations__"][field_name] = ( attrs["__annotations__"][field_name] = (
field.__type__ if not field.nullable else Optional[field.__type__] field.__type__ if not field.nullable else Optional[field.__type__]
@ -156,4 +111,8 @@ def get_potential_fields(attrs: Dict) -> Dict:
:return: extracted fields that are ormar Fields :return: extracted fields that are ormar Fields
:rtype: Dict :rtype: Dict
""" """
return {k: v for k, v in attrs.items() if lenient_issubclass(v, BaseField)} return {
k: v
for k, v in attrs.items()
if (lenient_issubclass(v, BaseField) or isinstance(v, BaseField))
}

View File

@ -13,7 +13,7 @@ if TYPE_CHECKING: # pragma no cover
alias_manager = AliasManager() alias_manager = AliasManager()
def register_relation_on_build(field: Type["ForeignKeyField"]) -> None: def register_relation_on_build(field: "ForeignKeyField") -> None:
""" """
Registers ForeignKey relation in alias_manager to set a table_prefix. Registers ForeignKey relation in alias_manager to set a table_prefix.
Registration include also reverse relation side to be able to join both sides. Registration include also reverse relation side to be able to join both sides.
@ -32,7 +32,7 @@ def register_relation_on_build(field: Type["ForeignKeyField"]) -> None:
) )
def register_many_to_many_relation_on_build(field: Type["ManyToManyField"]) -> None: def register_many_to_many_relation_on_build(field: "ManyToManyField") -> None:
""" """
Registers connection between through model and both sides of the m2m relation. Registers connection between through model and both sides of the m2m relation.
Registration include also reverse relation side to be able to join both sides. Registration include also reverse relation side to be able to join both sides.
@ -58,7 +58,7 @@ def register_many_to_many_relation_on_build(field: Type["ManyToManyField"]) -> N
) )
def expand_reverse_relationship(model_field: Type["ForeignKeyField"]) -> None: def expand_reverse_relationship(model_field: "ForeignKeyField") -> None:
""" """
If the reverse relation has not been set before it's set here. If the reverse relation has not been set before it's set here.
@ -84,11 +84,11 @@ def expand_reverse_relationships(model: Type["Model"]) -> None:
model_fields = list(model.Meta.model_fields.values()) model_fields = list(model.Meta.model_fields.values())
for model_field in model_fields: for model_field in model_fields:
if model_field.is_relation and not model_field.has_unresolved_forward_refs(): if model_field.is_relation and not model_field.has_unresolved_forward_refs():
model_field = cast(Type["ForeignKeyField"], model_field) model_field = cast("ForeignKeyField", model_field)
expand_reverse_relationship(model_field=model_field) expand_reverse_relationship(model_field=model_field)
def register_reverse_model_fields(model_field: Type["ForeignKeyField"]) -> None: def register_reverse_model_fields(model_field: "ForeignKeyField") -> None:
""" """
Registers reverse ForeignKey field on related model. Registers reverse ForeignKey field on related model.
By default it's name.lower()+'s' of the model on which relation is defined. By default it's name.lower()+'s' of the model on which relation is defined.
@ -113,7 +113,7 @@ def register_reverse_model_fields(model_field: Type["ForeignKeyField"]) -> None:
orders_by=model_field.related_orders_by, orders_by=model_field.related_orders_by,
) )
# register foreign keys on through model # register foreign keys on through model
model_field = cast(Type["ManyToManyField"], model_field) model_field = cast("ManyToManyField", model_field)
register_through_shortcut_fields(model_field=model_field) register_through_shortcut_fields(model_field=model_field)
adjust_through_many_to_many_model(model_field=model_field) adjust_through_many_to_many_model(model_field=model_field)
else: else:
@ -128,7 +128,7 @@ def register_reverse_model_fields(model_field: Type["ForeignKeyField"]) -> None:
) )
def register_through_shortcut_fields(model_field: Type["ManyToManyField"]) -> None: def register_through_shortcut_fields(model_field: "ManyToManyField") -> None:
""" """
Registers m2m relation through shortcut on both ends of the relation. Registers m2m relation through shortcut on both ends of the relation.
@ -156,7 +156,7 @@ def register_through_shortcut_fields(model_field: Type["ManyToManyField"]) -> No
) )
def register_relation_in_alias_manager(field: Type["ForeignKeyField"]) -> None: def register_relation_in_alias_manager(field: "ForeignKeyField") -> None:
""" """
Registers the relation (and reverse relation) in alias manager. Registers the relation (and reverse relation) in alias manager.
The m2m relations require registration of through model between The m2m relations require registration of through model between
@ -172,7 +172,7 @@ def register_relation_in_alias_manager(field: Type["ForeignKeyField"]) -> None:
if field.is_multi: if field.is_multi:
if field.has_unresolved_forward_refs(): if field.has_unresolved_forward_refs():
return return
field = cast(Type["ManyToManyField"], field) field = cast("ManyToManyField", field)
register_many_to_many_relation_on_build(field=field) register_many_to_many_relation_on_build(field=field)
elif field.is_relation and not field.is_through: elif field.is_relation and not field.is_through:
if field.has_unresolved_forward_refs(): if field.has_unresolved_forward_refs():
@ -181,7 +181,7 @@ def register_relation_in_alias_manager(field: Type["ForeignKeyField"]) -> None:
def verify_related_name_dont_duplicate( def verify_related_name_dont_duplicate(
related_name: str, model_field: Type["ForeignKeyField"] related_name: str, model_field: "ForeignKeyField"
) -> None: ) -> None:
""" """
Verifies whether the used related_name (regardless of the fact if user defined or Verifies whether the used related_name (regardless of the fact if user defined or
@ -213,7 +213,7 @@ def verify_related_name_dont_duplicate(
) )
def reverse_field_not_already_registered(model_field: Type["ForeignKeyField"]) -> bool: def reverse_field_not_already_registered(model_field: "ForeignKeyField") -> bool:
""" """
Checks if child is already registered in parents pydantic fields. Checks if child is already registered in parents pydantic fields.

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING: # pragma no cover
from ormar.models import NewBaseModel from ormar.models import NewBaseModel
def adjust_through_many_to_many_model(model_field: Type["ManyToManyField"]) -> None: def adjust_through_many_to_many_model(model_field: "ManyToManyField") -> None:
""" """
Registers m2m relation on through model. Registers m2m relation on through model.
Sets ormar.ForeignKey from through model to both child and parent models. Sets ormar.ForeignKey from through model to both child and parent models.
@ -52,7 +52,7 @@ def adjust_through_many_to_many_model(model_field: Type["ManyToManyField"]) -> N
def create_and_append_m2m_fk( def create_and_append_m2m_fk(
model: Type["Model"], model_field: Type["ManyToManyField"], field_name: str model: Type["Model"], model_field: "ManyToManyField", field_name: str
) -> None: ) -> None:
""" """
Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model. Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model.
@ -190,22 +190,22 @@ def _process_fields(
return pkname, columns return pkname, columns
def _is_through_model_not_set(field: Type["BaseField"]) -> bool: def _is_through_model_not_set(field: "BaseField") -> bool:
""" """
Alias to if check that verifies if through model was created. Alias to if check that verifies if through model was created.
:param field: field to check :param field: field to check
:type field: Type["BaseField"] :type field: "BaseField"
:return: result of the check :return: result of the check
:rtype: bool :rtype: bool
""" """
return field.is_multi and not field.through return field.is_multi and not field.through
def _is_db_field(field: Type["BaseField"]) -> bool: def _is_db_field(field: "BaseField") -> bool:
""" """
Alias to if check that verifies if field should be included in database. Alias to if check that verifies if field should be included in database.
:param field: field to check :param field: field to check
:type field: Type["BaseField"] :type field: "BaseField"
:return: result of the check :return: result of the check
:rtype: bool :rtype: bool
""" """
@ -298,7 +298,7 @@ def populate_meta_sqlalchemy_table_if_required(meta: "ModelMeta") -> None:
def update_column_definition( def update_column_definition(
model: Union[Type["Model"], Type["NewBaseModel"]], field: Type["ForeignKeyField"] model: Union[Type["Model"], Type["NewBaseModel"]], field: "ForeignKeyField"
) -> None: ) -> None:
""" """
Updates a column with a new type column based on updated parameters in FK fields. Updates a column with a new type column based on updated parameters in FK fields.
@ -306,7 +306,7 @@ def update_column_definition(
:param model: model on which columns needs to be updated :param model: model on which columns needs to be updated
:type model: Type["Model"] :type model: Type["Model"]
:param field: field with column definition that requires update :param field: field with column definition that requires update
:type field: Type[ForeignKeyField] :type field: ForeignKeyField
:return: None :return: None
:rtype: None :rtype: None
""" """

View File

@ -20,7 +20,7 @@ if TYPE_CHECKING: # pragma no cover
from ormar import Model from ormar import Model
def check_if_field_has_choices(field: Type[BaseField]) -> bool: def check_if_field_has_choices(field: BaseField) -> bool:
""" """
Checks if given field has choices populated. Checks if given field has choices populated.
A if it has one, a validator for this field needs to be attached. A if it has one, a validator for this field needs to be attached.
@ -34,7 +34,7 @@ def check_if_field_has_choices(field: Type[BaseField]) -> bool:
def convert_choices_if_needed( # noqa: CCR001 def convert_choices_if_needed( # noqa: CCR001
field: Type["BaseField"], value: Any field: "BaseField", value: Any
) -> Tuple[Any, List]: ) -> Tuple[Any, List]:
""" """
Converts dates to isoformat as fastapi can check this condition in routes Converts dates to isoformat as fastapi can check this condition in routes
@ -47,7 +47,7 @@ def convert_choices_if_needed( # noqa: CCR001
Converts decimal to float with given scale. Converts decimal to float with given scale.
:param field: ormar field to check with choices :param field: ormar field to check with choices
:type field: Type[BaseField] :type field: BaseField
:param values: current values of the model to verify :param values: current values of the model to verify
:type values: Dict :type values: Dict
:return: value, choices list :return: value, choices list
@ -77,13 +77,13 @@ def convert_choices_if_needed( # noqa: CCR001
return value, choices return value, choices
def validate_choices(field: Type["BaseField"], value: Any) -> None: def validate_choices(field: "BaseField", value: Any) -> None:
""" """
Validates if given value is in provided choices. Validates if given value is in provided choices.
:raises ValueError: If value is not in choices. :raises ValueError: If value is not in choices.
:param field:field to validate :param field:field to validate
:type field: Type[BaseField] :type field: BaseField
:param value: value of the field :param value: value of the field
:type value: Any :type value: Any
""" """

View File

@ -63,9 +63,7 @@ class ModelMeta:
columns: List[sqlalchemy.Column] columns: List[sqlalchemy.Column]
constraints: List[ColumnCollectionConstraint] constraints: List[ColumnCollectionConstraint]
pkname: str pkname: str
model_fields: Dict[ model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]]
str, Union[Type[BaseField], Type[ForeignKeyField], Type[ManyToManyField]]
]
alias_manager: AliasManager alias_manager: AliasManager
property_fields: Set property_fields: Set
signals: SignalEmitter signals: SignalEmitter
@ -215,7 +213,7 @@ def update_attrs_from_base_meta( # noqa: CCR001
def copy_and_replace_m2m_through_model( # noqa: CFQ002 def copy_and_replace_m2m_through_model( # noqa: CFQ002
field: Type[ManyToManyField], field: ManyToManyField,
field_name: str, field_name: str,
table_name: str, table_name: str,
parent_fields: Dict, parent_fields: Dict,
@ -238,7 +236,7 @@ def copy_and_replace_m2m_through_model( # noqa: CFQ002
:param base_class: base class model :param base_class: base class model
:type base_class: Type["Model"] :type base_class: Type["Model"]
:param field: field with relations definition :param field: field with relations definition
:type field: Type[ManyToManyField] :type field: ManyToManyField
:param field_name: name of the relation field :param field_name: name of the relation field
:type field_name: str :type field_name: str
:param table_name: name of the table :param table_name: name of the table
@ -250,9 +248,10 @@ def copy_and_replace_m2m_through_model( # noqa: CFQ002
:param meta: metaclass of currently created model :param meta: metaclass of currently created model
:type meta: ModelMeta :type meta: ModelMeta
""" """
copy_field: Type[BaseField] = type( # type: ignore Field: Type[BaseField] = type( # type: ignore
field.__name__, (ManyToManyField, BaseField), dict(field.__dict__) field.__class__.__name__, (ManyToManyField, BaseField), {}
) )
copy_field = Field(**dict(field.__dict__))
related_name = field.related_name + "_" + table_name related_name = field.related_name + "_" + table_name
copy_field.related_name = related_name # type: ignore copy_field.related_name = related_name # type: ignore
@ -293,9 +292,7 @@ def copy_data_from_parent_model( # noqa: CCR001
base_class: Type["Model"], base_class: Type["Model"],
curr_class: type, curr_class: type,
attrs: Dict, attrs: Dict,
model_fields: Dict[ model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]],
str, Union[Type[BaseField], Type[ForeignKeyField], Type[ManyToManyField]]
],
) -> Tuple[Dict, Dict]: ) -> Tuple[Dict, Dict]:
""" """
Copy the key parameters [databse, metadata, property_fields and constraints] Copy the key parameters [databse, metadata, property_fields and constraints]
@ -342,7 +339,7 @@ def copy_data_from_parent_model( # noqa: CCR001
) )
for field_name, field in base_class.Meta.model_fields.items(): for field_name, field in base_class.Meta.model_fields.items():
if field.is_multi: if field.is_multi:
field = cast(Type["ManyToManyField"], field) field = cast(ManyToManyField, field)
copy_and_replace_m2m_through_model( copy_and_replace_m2m_through_model(
field=field, field=field,
field_name=field_name, field_name=field_name,
@ -354,9 +351,10 @@ def copy_data_from_parent_model( # noqa: CCR001
) )
elif field.is_relation and field.related_name: elif field.is_relation and field.related_name:
copy_field = type( # type: ignore Field = type( # type: ignore
field.__name__, (ForeignKeyField, BaseField), dict(field.__dict__) field.__class__.__name__, (ForeignKeyField, BaseField), {}
) )
copy_field = Field(**dict(field.__dict__))
related_name = field.related_name + "_" + table_name related_name = field.related_name + "_" + table_name
copy_field.related_name = related_name # type: ignore copy_field.related_name = related_name # type: ignore
parent_fields[field_name] = copy_field parent_fields[field_name] = copy_field
@ -372,9 +370,7 @@ def extract_from_parents_definition( # noqa: CCR001
base_class: type, base_class: type,
curr_class: type, curr_class: type,
attrs: Dict, attrs: Dict,
model_fields: Dict[ model_fields: Dict[str, Union[BaseField, ForeignKeyField, ManyToManyField]],
str, Union[Type[BaseField], Type[ForeignKeyField], Type[ManyToManyField]]
],
) -> Tuple[Dict, Dict]: ) -> Tuple[Dict, Dict]:
""" """
Extracts fields from base classes if they have valid oramr fields. Extracts fields from base classes if they have valid oramr fields.

View File

@ -67,6 +67,6 @@ class AliasMixin:
:rtype: Dict :rtype: Dict
""" """
for field_name, field in cls.Meta.model_fields.items(): for field_name, field in cls.Meta.model_fields.items():
if field.alias and field.alias in new_kwargs: if field.get_alias() and field.get_alias() in new_kwargs:
new_kwargs[field_name] = new_kwargs.pop(field.alias) new_kwargs[field_name] = new_kwargs.pop(field.get_alias())
return new_kwargs return new_kwargs

View File

@ -41,7 +41,7 @@ class PrefetchQueryMixin(RelationMixin):
field_name = parent_model.Meta.model_fields[related].get_related_name() field_name = parent_model.Meta.model_fields[related].get_related_name()
field = target_model.Meta.model_fields[field_name] field = target_model.Meta.model_fields[field_name]
if field.is_multi: if field.is_multi:
field = cast(Type["ManyToManyField"], field) field = cast("ManyToManyField", field)
field_name = field.default_target_field_name() field_name = field.default_target_field_name()
sub_field = field.through.Meta.model_fields[field_name] sub_field = field.through.Meta.model_fields[field_name]
return field.through, sub_field.get_alias() return field.through, sub_field.get_alias()
@ -78,7 +78,7 @@ class PrefetchQueryMixin(RelationMixin):
return column.get_alias() if use_raw else column.name return column.get_alias() if use_raw else column.name
@classmethod @classmethod
def get_related_field_name(cls, target_field: Type["ForeignKeyField"]) -> str: def get_related_field_name(cls, target_field: "ForeignKeyField") -> str:
""" """
Returns name of the relation field that should be used in prefetch query. Returns name of the relation field that should be used in prefetch query.
This field is later used to register relation in prefetch query, This field is later used to register relation in prefetch query,

View File

@ -1,4 +1,3 @@
import inspect
from typing import ( from typing import (
Callable, Callable,
List, List,
@ -9,6 +8,8 @@ from typing import (
Union, Union,
) )
from ormar import BaseField
class RelationMixin: class RelationMixin:
""" """
@ -85,7 +86,11 @@ class RelationMixin:
related_names = set() related_names = set()
for name, field in cls.Meta.model_fields.items(): for name, field in cls.Meta.model_fields.items():
if inspect.isclass(field) and field.is_relation and not field.is_through: if (
isinstance(field, BaseField)
and field.is_relation
and not field.is_through
):
related_names.add(name) related_names.add(name)
cls._related_names = related_names cls._related_names = related_names

View File

@ -29,7 +29,7 @@ class ModelRow(NewBaseModel):
source_model: Type["Model"], source_model: Type["Model"],
select_related: List = None, select_related: List = None,
related_models: Any = None, related_models: Any = None,
related_field: Type["ForeignKeyField"] = None, related_field: "ForeignKeyField" = None,
excludable: ExcludableItems = None, excludable: ExcludableItems = None,
current_relation_str: str = "", current_relation_str: str = "",
proxy_source_model: Optional[Type["Model"]] = None, proxy_source_model: Optional[Type["Model"]] = None,
@ -65,7 +65,7 @@ class ModelRow(NewBaseModel):
:param related_models: list or dict of related models :param related_models: list or dict of related models
:type related_models: Union[List, Dict] :type related_models: Union[List, Dict]
:param related_field: field with relation declaration :param related_field: field with relation declaration
:type related_field: Type[ForeignKeyField] :type related_field: ForeignKeyField
:return: returns model if model is populated from database :return: returns model if model is populated from database
:rtype: Optional[Model] :rtype: Optional[Model]
""" """
@ -116,7 +116,7 @@ class ModelRow(NewBaseModel):
cls, cls,
source_model: Type["Model"], source_model: Type["Model"],
current_relation_str: str, current_relation_str: str,
related_field: Type["ForeignKeyField"], related_field: "ForeignKeyField",
used_prefixes: List[str], used_prefixes: List[str],
) -> str: ) -> str:
""" """
@ -126,7 +126,7 @@ class ModelRow(NewBaseModel):
:param current_relation_str: current relation string :param current_relation_str: current relation string
:type current_relation_str: str :type current_relation_str: str
:param related_field: field with relation declaration :param related_field: field with relation declaration
:type related_field: Type["ForeignKeyField"] :type related_field: "ForeignKeyField"
:param used_prefixes: list of already extracted prefixes :param used_prefixes: list of already extracted prefixes
:type used_prefixes: List[str] :type used_prefixes: List[str]
:return: table_prefix to use :return: table_prefix to use
@ -193,7 +193,7 @@ class ModelRow(NewBaseModel):
for related in related_models: for related in related_models:
field = cls.Meta.model_fields[related] field = cls.Meta.model_fields[related]
field = cast(Type["ForeignKeyField"], field) field = cast("ForeignKeyField", field)
model_cls = field.to model_cls = field.to
model_excludable = excludable.get( model_excludable = excludable.get(
model_cls=cast(Type["Model"], cls), alias=table_prefix model_cls=cast(Type["Model"], cls), alias=table_prefix

View File

@ -67,7 +67,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
__slots__ = ("_orm_id", "_orm_saved", "_orm", "_pk_column") __slots__ = ("_orm_id", "_orm_saved", "_orm", "_pk_column")
if TYPE_CHECKING: # pragma no cover if TYPE_CHECKING: # pragma no cover
__model_fields__: Dict[str, Type[BaseField]] __model_fields__: Dict[str, BaseField]
__table__: sqlalchemy.Table __table__: sqlalchemy.Table
__fields__: Dict[str, pydantic.fields.ModelField] __fields__: Dict[str, pydantic.fields.ModelField]
__pydantic_model__: Type[BaseModel] __pydantic_model__: Type[BaseModel]
@ -455,7 +455,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
fields_to_check = cls.Meta.model_fields.copy() fields_to_check = cls.Meta.model_fields.copy()
for field in fields_to_check.values(): for field in fields_to_check.values():
if field.has_unresolved_forward_refs(): if field.has_unresolved_forward_refs():
field = cast(Type[ForeignKeyField], field) field = cast(ForeignKeyField, field)
field.evaluate_forward_ref(globalns=globalns, localns=localns) field.evaluate_forward_ref(globalns=globalns, localns=localns)
field.set_self_reference_flag() field.set_self_reference_flag()
expand_reverse_relationship(model_field=field) expand_reverse_relationship(model_field=field)
@ -747,12 +747,12 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
) )
return self_fields return self_fields
def get_relation_model_id(self, target_field: Type["BaseField"]) -> Optional[int]: def get_relation_model_id(self, target_field: "BaseField") -> Optional[int]:
""" """
Returns an id of the relation side model to use in prefetch query. Returns an id of the relation side model to use in prefetch query.
:param target_field: field with relation definition :param target_field: field with relation definition
:type target_field: Type["BaseField"] :type target_field: "BaseField"
:return: value of pk if set :return: value of pk if set
:rtype: Optional[int] :rtype: Optional[int]
""" """

View File

@ -292,7 +292,7 @@ class PrefetchQuery:
for related in related_to_extract: for related in related_to_extract:
target_field = model.Meta.model_fields[related] target_field = model.Meta.model_fields[related]
target_field = cast(Type["ForeignKeyField"], target_field) target_field = cast("ForeignKeyField", target_field)
target_model = target_field.to.get_name() target_model = target_field.to.get_name()
model_id = model.get_relation_model_id(target_field=target_field) model_id = model.get_relation_model_id(target_field=target_field)
@ -394,7 +394,7 @@ class PrefetchQuery:
:rtype: None :rtype: None
""" """
target_field = target_model.Meta.model_fields[related] target_field = target_model.Meta.model_fields[related]
target_field = cast(Type["ForeignKeyField"], target_field) target_field = cast("ForeignKeyField", target_field)
reverse = False reverse = False
if target_field.virtual or target_field.is_multi: if target_field.virtual or target_field.is_multi:
reverse = True reverse = True
@ -461,7 +461,7 @@ class PrefetchQuery:
async def _run_prefetch_query( async def _run_prefetch_query(
self, self,
target_field: Type["BaseField"], target_field: "BaseField",
excludable: "ExcludableItems", excludable: "ExcludableItems",
filter_clauses: List, filter_clauses: List,
related_field_name: str, related_field_name: str,
@ -474,7 +474,7 @@ class PrefetchQuery:
models. models.
:param target_field: ormar field with relation definition :param target_field: ormar field with relation definition
:type target_field: Type["BaseField"] :type target_field: "BaseField"
:param filter_clauses: list of clauses, actually one clause with ids of relation :param filter_clauses: list of clauses, actually one clause with ids of relation
:type filter_clauses: List[sqlalchemy.sql.elements.TextClause] :type filter_clauses: List[sqlalchemy.sql.elements.TextClause]
:return: table prefix and raw rows from sql response :return: table prefix and raw rows from sql response
@ -540,13 +540,13 @@ class PrefetchQuery:
) )
def _update_already_loaded_rows( # noqa: CFQ002 def _update_already_loaded_rows( # noqa: CFQ002
self, target_field: Type["BaseField"], prefetch_dict: Dict, orders_by: Dict, self, target_field: "BaseField", prefetch_dict: Dict, orders_by: Dict,
) -> None: ) -> None:
""" """
Updates models that are already loaded, usually children of children. Updates models that are already loaded, usually children of children.
:param target_field: ormar field with relation definition :param target_field: ormar field with relation definition
:type target_field: Type["BaseField"] :type target_field: "BaseField"
:param prefetch_dict: dictionaries of related models to prefetch :param prefetch_dict: dictionaries of related models to prefetch
:type prefetch_dict: Dict :type prefetch_dict: Dict
:param orders_by: dictionary of order by clauses by model :param orders_by: dictionary of order by clauses by model
@ -561,7 +561,7 @@ class PrefetchQuery:
def _populate_rows( # noqa: CFQ002 def _populate_rows( # noqa: CFQ002
self, self,
rows: List, rows: List,
target_field: Type["ForeignKeyField"], target_field: "ForeignKeyField",
parent_model: Type["Model"], parent_model: Type["Model"],
table_prefix: str, table_prefix: str,
exclude_prefix: str, exclude_prefix: str,
@ -584,7 +584,7 @@ class PrefetchQuery:
:param rows: raw sql response from the prefetch query :param rows: raw sql response from the prefetch query
:type rows: List[sqlalchemy.engine.result.RowProxy] :type rows: List[sqlalchemy.engine.result.RowProxy]
:param target_field: field with relation definition from parent model :param target_field: field with relation definition from parent model
:type target_field: Type["BaseField"] :type target_field: "BaseField"
:param parent_model: model with relation definition :param parent_model: model with relation definition
:type parent_model: Type[Model] :type parent_model: Type[Model]
:param table_prefix: prefix of the target table from current relation :param table_prefix: prefix of the target table from current relation

View File

@ -264,7 +264,7 @@ def get_relationship_alias_model_and_str(
def _process_through_field( def _process_through_field(
related_parts: List, related_parts: List,
relation: Optional[str], relation: Optional[str],
related_field: Type["BaseField"], related_field: "BaseField",
previous_model: Type["Model"], previous_model: Type["Model"],
previous_models: List[Type["Model"]], previous_models: List[Type["Model"]],
) -> Tuple[Type["Model"], Optional[str], bool]: ) -> Tuple[Type["Model"], Optional[str], bool]:
@ -276,7 +276,7 @@ def _process_through_field(
:param relation: relation name :param relation: relation name
:type relation: str :type relation: str
:param related_field: field with relation declaration :param related_field: field with relation declaration
:type related_field: Type["ForeignKeyField"] :type related_field: "ForeignKeyField"
:param previous_model: model from which relation is coming :param previous_model: model from which relation is coming
:type previous_model: Type["Model"] :type previous_model: Type["Model"]
:param previous_models: list of already visited models in relation chain :param previous_models: list of already visited models in relation chain

View File

@ -151,7 +151,7 @@ class AliasManager:
self, self,
source_model: Union[Type["Model"], Type["ModelRow"]], source_model: Union[Type["Model"], Type["ModelRow"]],
relation_str: str, relation_str: str,
relation_field: Type["ForeignKeyField"], relation_field: "ForeignKeyField",
) -> str: ) -> str:
""" """
Given source model and relation string returns the alias for this complex Given source model and relation string returns the alias for this complex
@ -159,7 +159,7 @@ class AliasManager:
field definition. field definition.
:param relation_field: field with direct relation definition :param relation_field: field with direct relation definition
:type relation_field: Type["ForeignKeyField"] :type relation_field: "ForeignKeyField"
:param source_model: model with query starts :param source_model: model with query starts
:type source_model: source Model :type source_model: source Model
:param relation_str: string with relation joins defined :param relation_str: string with relation joins defined

View File

@ -16,7 +16,7 @@ class RelationsManager:
def __init__( def __init__(
self, self,
related_fields: List[Type["ForeignKeyField"]] = None, related_fields: List["ForeignKeyField"] = None,
owner: Optional["Model"] = None, owner: Optional["Model"] = None,
) -> None: ) -> None:
self.owner = proxy(owner) self.owner = proxy(owner)
@ -57,7 +57,7 @@ class RelationsManager:
return None # pragma nocover return None # pragma nocover
@staticmethod @staticmethod
def add(parent: "Model", child: "Model", field: Type["ForeignKeyField"],) -> None: def add(parent: "Model", child: "Model", field: "ForeignKeyField",) -> None:
""" """
Adds relation on both sides -> meaning on both child and parent models. Adds relation on both sides -> meaning on both child and parent models.
One side of the relation is always weakref proxy to avoid circular refs. One side of the relation is always weakref proxy to avoid circular refs.
@ -138,12 +138,12 @@ class RelationsManager:
return relation return relation
return None return None
def _get_relation_type(self, field: Type["BaseField"]) -> RelationType: def _get_relation_type(self, field: "BaseField") -> RelationType:
""" """
Returns type of the relation declared on a field. Returns type of the relation declared on a field.
:param field: field with relation declaration :param field: field with relation declaration
:type field: Type[BaseField] :type field: BaseField
:return: type of the relation defined on field :return: type of the relation defined on field
:rtype: RelationType :rtype: RelationType
""" """
@ -153,13 +153,13 @@ class RelationsManager:
return RelationType.THROUGH return RelationType.THROUGH
return RelationType.PRIMARY if not field.virtual else RelationType.REVERSE return RelationType.PRIMARY if not field.virtual else RelationType.REVERSE
def _add_relation(self, field: Type["BaseField"]) -> None: def _add_relation(self, field: "BaseField") -> None:
""" """
Registers relation in the manager. Registers relation in the manager.
Adds Relation instance under field.name. Adds Relation instance under field.name.
:param field: field with relation declaration :param field: field with relation declaration
:type field: Type[BaseField] :type field: BaseField
""" """
self._relations[field.name] = Relation( self._relations[field.name] = Relation(
manager=self, manager=self,

View File

@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Tuple, Type from typing import TYPE_CHECKING, Tuple
from weakref import proxy from weakref import proxy
from ormar.fields.foreign_key import ForeignKeyField from ormar.fields.foreign_key import ForeignKeyField
@ -8,7 +8,7 @@ if TYPE_CHECKING: # pragma no cover
def get_relations_sides_and_names( def get_relations_sides_and_names(
to_field: Type[ForeignKeyField], parent: "Model", child: "Model", to_field: ForeignKeyField, parent: "Model", child: "Model",
) -> Tuple["Model", "Model", str, str]: ) -> Tuple["Model", "Model", str, str]:
""" """
Determines the names of child and parent relations names, as well as Determines the names of child and parent relations names, as well as

View File

@ -193,8 +193,8 @@ def test_field_redefining_in_concrete_models():
created_date: str = ormar.String(max_length=200, name="creation_date") created_date: str = ormar.String(max_length=200, name="creation_date")
changed_field = RedefinedField.Meta.model_fields["created_date"] changed_field = RedefinedField.Meta.model_fields["created_date"]
assert changed_field.default is None assert changed_field.ormar_default is None
assert changed_field.alias == "creation_date" assert changed_field.get_alias() == "creation_date"
assert any(x.name == "creation_date" for x in RedefinedField.Meta.table.columns) assert any(x.name == "creation_date" for x in RedefinedField.Meta.table.columns)
assert isinstance( assert isinstance(
RedefinedField.Meta.table.columns["creation_date"].type, sa.sql.sqltypes.String, RedefinedField.Meta.table.columns["creation_date"].type, sa.sql.sqltypes.String,

View File

@ -64,8 +64,10 @@ def test_field_redefining():
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
created_date: datetime.datetime = ormar.DateTime(name="creation_date") created_date: datetime.datetime = ormar.DateTime(name="creation_date")
assert RedefinedField.Meta.model_fields["created_date"].default is None assert RedefinedField.Meta.model_fields["created_date"].ormar_default is None
assert RedefinedField.Meta.model_fields["created_date"].alias == "creation_date" assert (
RedefinedField.Meta.model_fields["created_date"].get_alias() == "creation_date"
)
assert any(x.name == "creation_date" for x in RedefinedField.Meta.table.columns) assert any(x.name == "creation_date" for x in RedefinedField.Meta.table.columns)
@ -87,8 +89,10 @@ def test_field_redefining_in_second_raises_error():
id: int = ormar.Integer(primary_key=True) id: int = ormar.Integer(primary_key=True)
created_date: str = ormar.String(max_length=200, name="creation_date") created_date: str = ormar.String(max_length=200, name="creation_date")
assert RedefinedField2.Meta.model_fields["created_date"].default is None assert RedefinedField2.Meta.model_fields["created_date"].ormar_default is None
assert RedefinedField2.Meta.model_fields["created_date"].alias == "creation_date" assert (
RedefinedField2.Meta.model_fields["created_date"].get_alias() == "creation_date"
)
assert any(x.name == "creation_date" for x in RedefinedField2.Meta.table.columns) assert any(x.name == "creation_date" for x in RedefinedField2.Meta.table.columns)
assert isinstance( assert isinstance(
RedefinedField2.Meta.table.columns["creation_date"].type, RedefinedField2.Meta.table.columns["creation_date"].type,

View File

@ -29,7 +29,7 @@ class ExampleModel(Model):
test_string: str = ormar.String(max_length=250) test_string: str = ormar.String(max_length=250)
test_text: str = ormar.Text(default="") test_text: str = ormar.Text(default="")
test_bool: bool = ormar.Boolean(nullable=False) test_bool: bool = ormar.Boolean(nullable=False)
test_float: ormar.Float() = None # type: ignore test_float = ormar.Float(nullable=True)
test_datetime = ormar.DateTime(default=datetime.datetime.now) test_datetime = ormar.DateTime(default=datetime.datetime.now)
test_date = ormar.Date(default=datetime.date.today) test_date = ormar.Date(default=datetime.date.today)
test_time = ormar.Time(default=datetime.time) test_time = ormar.Time(default=datetime.time)

View File

@ -120,9 +120,9 @@ async def create_test_database():
def test_model_class(): def test_model_class():
assert list(User.Meta.model_fields.keys()) == ["id", "name"] assert list(User.Meta.model_fields.keys()) == ["id", "name"]
assert issubclass(User.Meta.model_fields["id"], pydantic.fields.FieldInfo) assert issubclass(User.Meta.model_fields["id"].__class__, pydantic.fields.FieldInfo)
assert User.Meta.model_fields["id"].primary_key is True assert User.Meta.model_fields["id"].primary_key is True
assert issubclass(User.Meta.model_fields["name"], pydantic.fields.FieldInfo) assert isinstance(User.Meta.model_fields["name"], pydantic.fields.FieldInfo)
assert User.Meta.model_fields["name"].max_length == 100 assert User.Meta.model_fields["name"].max_length == 100
assert isinstance(User.Meta.table, sqlalchemy.Table) assert isinstance(User.Meta.table, sqlalchemy.Table)