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

@ -12,7 +12,7 @@ if TYPE_CHECKING: # pragma no cover
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
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
import pydantic
from pydantic.fields import ModelField
from pydantic.utils import lenient_issubclass
import ormar # noqa: I100, I202
from ormar.fields import BaseField
from ormar.fields import BaseField # noqa: I100, I202
if TYPE_CHECKING: # pragma no cover
from ormar import Model
@ -14,7 +12,7 @@ if TYPE_CHECKING: # pragma no cover
def create_pydantic_field(
field_name: str, model: Type["Model"], model_field: Type["ManyToManyField"]
field_name: str, model: Type["Model"], model_field: "ManyToManyField"
) -> None:
"""
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]:
"""
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]
"""
model_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 = {}
potential_fields.update(get_potential_fields(attrs))
for field_name, field in potential_fields.items():
field.name = field_name
attrs = populate_default_pydantic_field_value(field, field_name, attrs)
model_fields[field_name] = field
attrs["__annotations__"][field_name] = (
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
: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()
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.
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.
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.
@ -84,11 +84,11 @@ def expand_reverse_relationships(model: Type["Model"]) -> None:
model_fields = list(model.Meta.model_fields.values())
for model_field in model_fields:
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)
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.
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,
)
# 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)
adjust_through_many_to_many_model(model_field=model_field)
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.
@ -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.
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.has_unresolved_forward_refs():
return
field = cast(Type["ManyToManyField"], field)
field = cast("ManyToManyField", field)
register_many_to_many_relation_on_build(field=field)
elif field.is_relation and not field.is_through:
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(
related_name: str, model_field: Type["ForeignKeyField"]
related_name: str, model_field: "ForeignKeyField"
) -> None:
"""
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.

View File

@ -14,7 +14,7 @@ if TYPE_CHECKING: # pragma no cover
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.
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(
model: Type["Model"], model_field: Type["ManyToManyField"], field_name: str
model: Type["Model"], model_field: "ManyToManyField", field_name: str
) -> None:
"""
Registers sqlalchemy Column with sqlalchemy.ForeignKey leading to the model.
@ -190,22 +190,22 @@ def _process_fields(
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.
:param field: field to check
:type field: Type["BaseField"]
:type field: "BaseField"
:return: result of the check
:rtype: bool
"""
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.
:param field: field to check
:type field: Type["BaseField"]
:type field: "BaseField"
:return: result of the check
:rtype: bool
"""
@ -298,7 +298,7 @@ def populate_meta_sqlalchemy_table_if_required(meta: "ModelMeta") -> None:
def update_column_definition(
model: Union[Type["Model"], Type["NewBaseModel"]], field: Type["ForeignKeyField"]
model: Union[Type["Model"], Type["NewBaseModel"]], field: "ForeignKeyField"
) -> None:
"""
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
:type model: Type["Model"]
:param field: field with column definition that requires update
:type field: Type[ForeignKeyField]
:type field: ForeignKeyField
:return: None
:rtype: None
"""

View File

@ -20,7 +20,7 @@ if TYPE_CHECKING: # pragma no cover
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.
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
field: Type["BaseField"], value: Any
field: "BaseField", value: Any
) -> Tuple[Any, List]:
"""
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.
:param field: ormar field to check with choices
:type field: Type[BaseField]
:type field: BaseField
:param values: current values of the model to verify
:type values: Dict
:return: value, choices list
@ -77,13 +77,13 @@ def convert_choices_if_needed( # noqa: CCR001
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.
:raises ValueError: If value is not in choices.
:param field:field to validate
:type field: Type[BaseField]
:type field: BaseField
:param value: value of the field
:type value: Any
"""

View File

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

View File

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

View File

@ -41,7 +41,7 @@ class PrefetchQueryMixin(RelationMixin):
field_name = parent_model.Meta.model_fields[related].get_related_name()
field = target_model.Meta.model_fields[field_name]
if field.is_multi:
field = cast(Type["ManyToManyField"], field)
field = cast("ManyToManyField", field)
field_name = field.default_target_field_name()
sub_field = field.through.Meta.model_fields[field_name]
return field.through, sub_field.get_alias()
@ -78,7 +78,7 @@ class PrefetchQueryMixin(RelationMixin):
return column.get_alias() if use_raw else column.name
@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.
This field is later used to register relation in prefetch query,

View File

@ -1,4 +1,3 @@
import inspect
from typing import (
Callable,
List,
@ -9,6 +8,8 @@ from typing import (
Union,
)
from ormar import BaseField
class RelationMixin:
"""
@ -85,7 +86,11 @@ class RelationMixin:
related_names = set()
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)
cls._related_names = related_names

View File

@ -29,7 +29,7 @@ class ModelRow(NewBaseModel):
source_model: Type["Model"],
select_related: List = None,
related_models: Any = None,
related_field: Type["ForeignKeyField"] = None,
related_field: "ForeignKeyField" = None,
excludable: ExcludableItems = None,
current_relation_str: str = "",
proxy_source_model: Optional[Type["Model"]] = None,
@ -65,7 +65,7 @@ class ModelRow(NewBaseModel):
:param related_models: list or dict of related models
:type related_models: Union[List, Dict]
: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
:rtype: Optional[Model]
"""
@ -116,7 +116,7 @@ class ModelRow(NewBaseModel):
cls,
source_model: Type["Model"],
current_relation_str: str,
related_field: Type["ForeignKeyField"],
related_field: "ForeignKeyField",
used_prefixes: List[str],
) -> str:
"""
@ -126,7 +126,7 @@ class ModelRow(NewBaseModel):
:param current_relation_str: current relation string
:type current_relation_str: str
:param related_field: field with relation declaration
:type related_field: Type["ForeignKeyField"]
:type related_field: "ForeignKeyField"
:param used_prefixes: list of already extracted prefixes
:type used_prefixes: List[str]
:return: table_prefix to use
@ -193,7 +193,7 @@ class ModelRow(NewBaseModel):
for related in related_models:
field = cls.Meta.model_fields[related]
field = cast(Type["ForeignKeyField"], field)
field = cast("ForeignKeyField", field)
model_cls = field.to
model_excludable = excludable.get(
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")
if TYPE_CHECKING: # pragma no cover
__model_fields__: Dict[str, Type[BaseField]]
__model_fields__: Dict[str, BaseField]
__table__: sqlalchemy.Table
__fields__: Dict[str, pydantic.fields.ModelField]
__pydantic_model__: Type[BaseModel]
@ -455,7 +455,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
fields_to_check = cls.Meta.model_fields.copy()
for field in fields_to_check.values():
if field.has_unresolved_forward_refs():
field = cast(Type[ForeignKeyField], field)
field = cast(ForeignKeyField, field)
field.evaluate_forward_ref(globalns=globalns, localns=localns)
field.set_self_reference_flag()
expand_reverse_relationship(model_field=field)
@ -747,12 +747,12 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
)
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.
:param target_field: field with relation definition
:type target_field: Type["BaseField"]
:type target_field: "BaseField"
:return: value of pk if set
:rtype: Optional[int]
"""