refactor and cleanup - drop of resolving relation names as not fully proper, extract mixins from modelproxy to be more maintainable, add some docstrings
This commit is contained in:
@ -1,12 +1,56 @@
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING, Type
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING, Tuple, Type
|
||||
|
||||
from ormar import ModelDefinitionError
|
||||
import ormar
|
||||
from ormar.fields.foreign_key import ForeignKeyField
|
||||
from ormar.models.helpers.pydantic import populate_pydantic_default_values
|
||||
|
||||
if TYPE_CHECKING: # pragma no cover
|
||||
from ormar import Model
|
||||
|
||||
|
||||
def populate_default_options_values(
|
||||
new_model: Type["Model"], model_fields: Dict
|
||||
) -> None:
|
||||
"""
|
||||
Sets all optional Meta values to it's defaults
|
||||
and set model_fields that were already previously extracted.
|
||||
|
||||
Here should live all options that are not overwritten/set for all models.
|
||||
|
||||
Current options are:
|
||||
* constraints = []
|
||||
* abstract = False
|
||||
|
||||
:param new_model: newly constructed Model
|
||||
:type new_model: Model class
|
||||
:param model_fields:
|
||||
:type model_fields: Union[Dict[str, type], Dict]
|
||||
"""
|
||||
if not hasattr(new_model.Meta, "constraints"):
|
||||
new_model.Meta.constraints = []
|
||||
if not hasattr(new_model.Meta, "model_fields"):
|
||||
new_model.Meta.model_fields = model_fields
|
||||
if not hasattr(new_model.Meta, "abstract"):
|
||||
new_model.Meta.abstract = False
|
||||
|
||||
|
||||
def extract_annotations_and_default_vals(attrs: Dict) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Extracts annotations from class namespace dict and triggers
|
||||
extraction of ormar model_fields.
|
||||
|
||||
:param attrs: namespace of the class created
|
||||
:type attrs: Dict
|
||||
:return: namespace of the class updated, dict of extracted model_fields
|
||||
:rtype: Tuple[Dict, Dict]
|
||||
"""
|
||||
key = "__annotations__"
|
||||
attrs[key] = attrs.get(key, {})
|
||||
attrs, model_fields = populate_pydantic_default_values(attrs)
|
||||
return attrs, model_fields
|
||||
|
||||
|
||||
# cannot be in relations helpers due to cyclical import
|
||||
def validate_related_names_in_relations(
|
||||
model_fields: Dict, new_model: Type["Model"]
|
||||
) -> None:
|
||||
@ -28,7 +72,7 @@ def validate_related_names_in_relations(
|
||||
if issubclass(field, ForeignKeyField):
|
||||
previous_related_names = already_registered.setdefault(field.to, [])
|
||||
if field.related_name in previous_related_names:
|
||||
raise ModelDefinitionError(
|
||||
raise ormar.ModelDefinitionError(
|
||||
f"Multiple fields declared on {new_model.get_name(lower=False)} "
|
||||
f"model leading to {field.to.get_name(lower=False)} model without "
|
||||
f"related_name property set. \nThere can be only one relation with "
|
||||
|
||||
@ -12,74 +12,6 @@ if TYPE_CHECKING: # pragma no cover
|
||||
from ormar import Model
|
||||
|
||||
|
||||
def verify_related_name_dont_duplicate(
|
||||
child: Type["Model"], parent_model: Type["Model"], related_name: str,
|
||||
) -> None:
|
||||
"""
|
||||
Verifies whether the used related_name (regardless of the fact if user defined or
|
||||
auto generated) is already used on related model, but is connected with other model
|
||||
than the one that we connect right now.
|
||||
|
||||
:raises: ModelDefinitionError if name is already used but lead to different related
|
||||
model
|
||||
:param child: related Model class
|
||||
:type child: ormar.models.metaclass.ModelMetaclass
|
||||
:param parent_model: parent Model class
|
||||
:type parent_model: ormar.models.metaclass.ModelMetaclass
|
||||
:param related_name:
|
||||
:type related_name:
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
if parent_model.Meta.model_fields.get(related_name):
|
||||
fk_field = parent_model.Meta.model_fields.get(related_name)
|
||||
if not fk_field: # pragma: no cover
|
||||
return
|
||||
if fk_field.to != child and fk_field.to.Meta != child.Meta:
|
||||
raise ormar.ModelDefinitionError(
|
||||
f"Relation with related_name "
|
||||
f"'{related_name}' "
|
||||
f"leading to model "
|
||||
f"{parent_model.get_name(lower=False)} "
|
||||
f"cannot be used on model "
|
||||
f"{child.get_name(lower=False)} "
|
||||
f"because it's already used by model "
|
||||
f"{fk_field.to.get_name(lower=False)}"
|
||||
)
|
||||
|
||||
|
||||
def reverse_field_not_already_registered(
|
||||
child: Type["Model"], child_model_name: str, parent_model: Type["Model"]
|
||||
) -> bool:
|
||||
"""
|
||||
Checks if child is already registered in parents pydantic fields.
|
||||
|
||||
:raises: ModelDefinitionError if related name is already used but lead to different
|
||||
related model
|
||||
:param child: related Model class
|
||||
:type child: ormar.models.metaclass.ModelMetaclass
|
||||
:param child_model_name: related_name of the child if provided
|
||||
:type child_model_name: str
|
||||
:param parent_model: parent Model class
|
||||
:type parent_model: ormar.models.metaclass.ModelMetaclass
|
||||
:return: result of the check
|
||||
:rtype: bool
|
||||
"""
|
||||
check_result = child_model_name not in parent_model.Meta.model_fields
|
||||
check_result2 = child.get_name() not in parent_model.Meta.model_fields
|
||||
|
||||
if not check_result:
|
||||
verify_related_name_dont_duplicate(
|
||||
child=child, parent_model=parent_model, related_name=child_model_name
|
||||
)
|
||||
if not check_result2:
|
||||
verify_related_name_dont_duplicate(
|
||||
child=child, parent_model=parent_model, related_name=child.get_name()
|
||||
)
|
||||
|
||||
return check_result and check_result2
|
||||
|
||||
|
||||
def create_pydantic_field(
|
||||
field_name: str, model: Type["Model"], model_field: Type[ManyToManyField]
|
||||
) -> None:
|
||||
@ -214,32 +146,6 @@ def get_pydantic_base_orm_config() -> Type[BaseConfig]:
|
||||
return Config
|
||||
|
||||
|
||||
def populate_default_options_values(
|
||||
new_model: Type["Model"], model_fields: Dict
|
||||
) -> None:
|
||||
"""
|
||||
Sets all optional Meta values to it's defaults
|
||||
and set model_fields that were already previously extracted.
|
||||
|
||||
Here should live all options that are not overwritten/set for all models.
|
||||
|
||||
Current options are:
|
||||
* constraints = []
|
||||
* abstract = False
|
||||
|
||||
:param new_model: newly constructed Model
|
||||
:type new_model: Model class
|
||||
:param model_fields:
|
||||
:type model_fields: Union[Dict[str, type], Dict]
|
||||
"""
|
||||
if not hasattr(new_model.Meta, "constraints"):
|
||||
new_model.Meta.constraints = []
|
||||
if not hasattr(new_model.Meta, "model_fields"):
|
||||
new_model.Meta.model_fields = model_fields
|
||||
if not hasattr(new_model.Meta, "abstract"):
|
||||
new_model.Meta.abstract = False
|
||||
|
||||
|
||||
def get_potential_fields(attrs: Dict) -> Dict:
|
||||
"""
|
||||
Gets all the fields in current class namespace that are Fields.
|
||||
@ -250,19 +156,3 @@ def get_potential_fields(attrs: Dict) -> Dict:
|
||||
:rtype: Dict
|
||||
"""
|
||||
return {k: v for k, v in attrs.items() if lenient_issubclass(v, BaseField)}
|
||||
|
||||
|
||||
def extract_annotations_and_default_vals(attrs: Dict) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Extracts annotations from class namespace dict and triggers
|
||||
extraction of ormar model_fields.
|
||||
|
||||
:param attrs: namespace of the class created
|
||||
:type attrs: Dict
|
||||
:return: namespace of the class updated, dict of extracted model_fields
|
||||
:rtype: Tuple[Dict, Dict]
|
||||
"""
|
||||
key = "__annotations__"
|
||||
attrs[key] = attrs.get(key, {})
|
||||
attrs, model_fields = populate_pydantic_default_values(attrs)
|
||||
return attrs, model_fields
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
from typing import TYPE_CHECKING, Type
|
||||
|
||||
import ormar
|
||||
from ormar import ForeignKey, ManyToMany
|
||||
from ormar.fields import ManyToManyField
|
||||
from ormar.fields.foreign_key import ForeignKeyField
|
||||
from ormar.models.helpers.pydantic import reverse_field_not_already_registered
|
||||
from ormar.models.helpers.sqlalchemy import adjust_through_many_to_many_model
|
||||
from ormar.relations import AliasManager
|
||||
|
||||
@ -31,7 +31,7 @@ def register_relation_on_build(new_model: Type["Model"], field_name: str) -> Non
|
||||
|
||||
|
||||
def register_many_to_many_relation_on_build(
|
||||
new_model: Type["Model"], field: Type[ManyToManyField]
|
||||
new_model: Type["Model"], field: Type[ManyToManyField], field_name: str
|
||||
) -> None:
|
||||
"""
|
||||
Registers connection between through model and both sides of the m2m relation.
|
||||
@ -43,13 +43,22 @@ def register_many_to_many_relation_on_build(
|
||||
|
||||
By default relation name is a model.name.lower().
|
||||
|
||||
:param field_name: name of the relation key
|
||||
:type field_name: str
|
||||
:param new_model: model on which m2m field is declared
|
||||
:type new_model: Model class
|
||||
:param field: relation field
|
||||
:type field: ManyToManyField class
|
||||
"""
|
||||
alias_manager.add_relation_type(field.through, new_model.get_name())
|
||||
alias_manager.add_relation_type(field.through, field.to.get_name())
|
||||
alias_manager.add_relation_type(
|
||||
field.through, new_model.get_name(), is_multi=True, reverse_name=field_name
|
||||
)
|
||||
alias_manager.add_relation_type(
|
||||
field.through,
|
||||
field.to.get_name(),
|
||||
is_multi=True,
|
||||
reverse_name=field.related_name or new_model.get_name() + "s",
|
||||
)
|
||||
|
||||
|
||||
def expand_reverse_relationships(model: Type["Model"]) -> None:
|
||||
@ -133,6 +142,76 @@ def register_relation_in_alias_manager(
|
||||
:type field_name: str
|
||||
"""
|
||||
if issubclass(field, ManyToManyField):
|
||||
register_many_to_many_relation_on_build(new_model=new_model, field=field)
|
||||
register_many_to_many_relation_on_build(
|
||||
new_model=new_model, field=field, field_name=field_name
|
||||
)
|
||||
elif issubclass(field, ForeignKeyField):
|
||||
register_relation_on_build(new_model=new_model, field_name=field_name)
|
||||
|
||||
|
||||
def verify_related_name_dont_duplicate(
|
||||
child: Type["Model"], parent_model: Type["Model"], related_name: str,
|
||||
) -> None:
|
||||
"""
|
||||
Verifies whether the used related_name (regardless of the fact if user defined or
|
||||
auto generated) is already used on related model, but is connected with other model
|
||||
than the one that we connect right now.
|
||||
|
||||
:raises: ModelDefinitionError if name is already used but lead to different related
|
||||
model
|
||||
:param child: related Model class
|
||||
:type child: ormar.models.metaclass.ModelMetaclass
|
||||
:param parent_model: parent Model class
|
||||
:type parent_model: ormar.models.metaclass.ModelMetaclass
|
||||
:param related_name:
|
||||
:type related_name:
|
||||
:return: None
|
||||
:rtype: None
|
||||
"""
|
||||
if parent_model.Meta.model_fields.get(related_name):
|
||||
fk_field = parent_model.Meta.model_fields.get(related_name)
|
||||
if not fk_field: # pragma: no cover
|
||||
return
|
||||
if fk_field.to != child and fk_field.to.Meta != child.Meta:
|
||||
raise ormar.ModelDefinitionError(
|
||||
f"Relation with related_name "
|
||||
f"'{related_name}' "
|
||||
f"leading to model "
|
||||
f"{parent_model.get_name(lower=False)} "
|
||||
f"cannot be used on model "
|
||||
f"{child.get_name(lower=False)} "
|
||||
f"because it's already used by model "
|
||||
f"{fk_field.to.get_name(lower=False)}"
|
||||
)
|
||||
|
||||
|
||||
def reverse_field_not_already_registered(
|
||||
child: Type["Model"], child_model_name: str, parent_model: Type["Model"]
|
||||
) -> bool:
|
||||
"""
|
||||
Checks if child is already registered in parents pydantic fields.
|
||||
|
||||
:raises: ModelDefinitionError if related name is already used but lead to different
|
||||
related model
|
||||
:param child: related Model class
|
||||
:type child: ormar.models.metaclass.ModelMetaclass
|
||||
:param child_model_name: related_name of the child if provided
|
||||
:type child_model_name: str
|
||||
:param parent_model: parent Model class
|
||||
:type parent_model: ormar.models.metaclass.ModelMetaclass
|
||||
:return: result of the check
|
||||
:rtype: bool
|
||||
"""
|
||||
check_result = child_model_name not in parent_model.Meta.model_fields
|
||||
check_result2 = child.get_name() not in parent_model.Meta.model_fields
|
||||
|
||||
if not check_result:
|
||||
verify_related_name_dont_duplicate(
|
||||
child=child, parent_model=parent_model, related_name=child_model_name
|
||||
)
|
||||
if not check_result2:
|
||||
verify_related_name_dont_duplicate(
|
||||
child=child, parent_model=parent_model, related_name=child.get_name()
|
||||
)
|
||||
|
||||
return check_result and check_result2
|
||||
|
||||
Reference in New Issue
Block a user