intorduce relation flags on basefield and simplify imports
This commit is contained in:
@ -6,6 +6,6 @@ ass well as vast number of helper functions for pydantic, sqlalchemy and relatio
|
||||
|
||||
from ormar.models.newbasemodel import NewBaseModel # noqa I100
|
||||
from ormar.models.model_row import ModelRow # noqa I100
|
||||
from ormar.models.model import Model # noqa I100
|
||||
from ormar.models.model import Model, T # noqa I100
|
||||
|
||||
__all__ = ["NewBaseModel", "Model", "ModelRow"]
|
||||
__all__ = ["T", "NewBaseModel", "Model", "ModelRow"]
|
||||
|
||||
@ -21,7 +21,7 @@ def is_field_an_forward_ref(field: Type["BaseField"]) -> bool:
|
||||
:return: result of the check
|
||||
:rtype: bool
|
||||
"""
|
||||
return issubclass(field, ormar.ForeignKeyField) and (
|
||||
return field.is_relation and (
|
||||
field.to.__class__ == ForwardRef or field.through.__class__ == ForwardRef
|
||||
)
|
||||
|
||||
|
||||
@ -6,14 +6,15 @@ from pydantic.fields import ModelField
|
||||
from pydantic.utils import lenient_issubclass
|
||||
|
||||
import ormar # noqa: I100, I202
|
||||
from ormar.fields import BaseField, ManyToManyField
|
||||
from ormar.fields import BaseField
|
||||
|
||||
if TYPE_CHECKING: # pragma no cover
|
||||
from ormar import Model
|
||||
from ormar.fields import ManyToManyField
|
||||
|
||||
|
||||
def create_pydantic_field(
|
||||
field_name: str, model: Type["Model"], model_field: Type[ManyToManyField]
|
||||
field_name: str, model: Type["Model"], model_field: Type["ManyToManyField"]
|
||||
) -> None:
|
||||
"""
|
||||
Registers pydantic field on through model that leads to passed model
|
||||
@ -59,7 +60,7 @@ 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
|
||||
ormar_field: Type["BaseField"], field_name: str, attrs: dict
|
||||
) -> dict:
|
||||
"""
|
||||
Grabs current value of the ormar Field in class namespace
|
||||
|
||||
@ -25,7 +25,7 @@ def validate_related_names_in_relations( # noqa CCR001
|
||||
"""
|
||||
already_registered: Dict[str, List[Optional[str]]] = dict()
|
||||
for field in model_fields.values():
|
||||
if issubclass(field, ormar.ForeignKeyField):
|
||||
if field.is_relation:
|
||||
to_name = (
|
||||
field.to.get_name()
|
||||
if not field.to.__class__ == ForwardRef
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
from typing import TYPE_CHECKING, Type
|
||||
from typing import TYPE_CHECKING, Type, cast
|
||||
|
||||
import ormar
|
||||
from ormar import ForeignKey, ManyToMany
|
||||
from ormar.fields import ManyToManyField, Through, ThroughField
|
||||
from ormar.fields.foreign_key import ForeignKeyField
|
||||
from ormar.fields import Through
|
||||
from ormar.models.helpers.sqlalchemy import adjust_through_many_to_many_model
|
||||
from ormar.relations import AliasManager
|
||||
|
||||
if TYPE_CHECKING: # pragma no cover
|
||||
from ormar import Model
|
||||
from ormar.fields import ManyToManyField, ForeignKeyField
|
||||
|
||||
alias_manager = AliasManager()
|
||||
|
||||
@ -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: Type["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.
|
||||
@ -83,10 +83,8 @@ def expand_reverse_relationships(model: Type["Model"]) -> None:
|
||||
"""
|
||||
model_fields = list(model.Meta.model_fields.values())
|
||||
for model_field in model_fields:
|
||||
if (
|
||||
issubclass(model_field, ForeignKeyField)
|
||||
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)
|
||||
expand_reverse_relationship(model_field=model_field)
|
||||
|
||||
|
||||
@ -102,7 +100,7 @@ def register_reverse_model_fields(model_field: Type["ForeignKeyField"]) -> None:
|
||||
:type model_field: relation Field
|
||||
"""
|
||||
related_name = model_field.get_related_name()
|
||||
if issubclass(model_field, ManyToManyField):
|
||||
if model_field.is_multi:
|
||||
model_field.to.Meta.model_fields[related_name] = ManyToMany(
|
||||
model_field.owner,
|
||||
through=model_field.through,
|
||||
@ -114,6 +112,7 @@ def register_reverse_model_fields(model_field: Type["ForeignKeyField"]) -> None:
|
||||
self_reference_primary=model_field.self_reference_primary,
|
||||
)
|
||||
# register foreign keys on through model
|
||||
model_field = cast(Type["ManyToManyField"], model_field)
|
||||
register_through_shortcut_fields(model_field=model_field)
|
||||
adjust_through_many_to_many_model(model_field=model_field)
|
||||
else:
|
||||
@ -155,7 +154,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: Type["ForeignKeyField"]) -> None:
|
||||
"""
|
||||
Registers the relation (and reverse relation) in alias manager.
|
||||
The m2m relations require registration of through model between
|
||||
@ -168,11 +167,12 @@ def register_relation_in_alias_manager(field: Type[ForeignKeyField]) -> None:
|
||||
:param field: relation field
|
||||
:type field: ForeignKey or ManyToManyField class
|
||||
"""
|
||||
if issubclass(field, ManyToManyField):
|
||||
if field.is_multi:
|
||||
if field.has_unresolved_forward_refs():
|
||||
return
|
||||
field = cast(Type["ManyToManyField"], field)
|
||||
register_many_to_many_relation_on_build(field=field)
|
||||
elif issubclass(field, ForeignKeyField) and not issubclass(field, ThroughField):
|
||||
elif field.is_relation and not field.is_through:
|
||||
if field.has_unresolved_forward_refs():
|
||||
return
|
||||
register_relation_on_build(field=field)
|
||||
|
||||
@ -156,11 +156,7 @@ def sqlalchemy_columns_from_model_fields(
|
||||
field.owner = new_model
|
||||
if field.primary_key:
|
||||
pkname = check_pk_column_validity(field_name, field, pkname)
|
||||
if (
|
||||
not field.pydantic_only
|
||||
and not field.virtual
|
||||
and not issubclass(field, ormar.ManyToManyField)
|
||||
):
|
||||
if not field.pydantic_only and not field.virtual and not field.is_multi:
|
||||
columns.append(field.get_column(field.get_alias()))
|
||||
return pkname, columns
|
||||
|
||||
|
||||
@ -262,7 +262,7 @@ def copy_and_replace_m2m_through_model(
|
||||
new_meta.model_fields = {
|
||||
name: field
|
||||
for name, field in new_meta.model_fields.items()
|
||||
if not issubclass(field, ForeignKeyField)
|
||||
if not field.is_relation
|
||||
}
|
||||
_, columns = sqlalchemy_columns_from_model_fields(
|
||||
new_meta.model_fields, copy_through
|
||||
@ -329,7 +329,8 @@ def copy_data_from_parent_model( # noqa: CCR001
|
||||
else attrs.get("__name__", "").lower() + "s"
|
||||
)
|
||||
for field_name, field in base_class.Meta.model_fields.items():
|
||||
if issubclass(field, ManyToManyField):
|
||||
if field.is_multi:
|
||||
field = cast(Type["ManyToManyField"], field)
|
||||
copy_and_replace_m2m_through_model(
|
||||
field=field,
|
||||
field_name=field_name,
|
||||
@ -339,7 +340,7 @@ def copy_data_from_parent_model( # noqa: CCR001
|
||||
meta=meta,
|
||||
)
|
||||
|
||||
elif issubclass(field, ForeignKeyField) and field.related_name:
|
||||
elif field.is_relation and field.related_name:
|
||||
copy_field = type( # type: ignore
|
||||
field.__name__, (ForeignKeyField, BaseField), dict(field.__dict__)
|
||||
)
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
from typing import Callable, Dict, List, TYPE_CHECKING, Tuple, Type
|
||||
from typing import Callable, Dict, List, TYPE_CHECKING, Tuple, Type, cast
|
||||
|
||||
import ormar
|
||||
from ormar.fields.foreign_key import ForeignKeyField
|
||||
from ormar.models.mixins.relation_mixin import RelationMixin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ormar.fields import ForeignKeyField, ManyToManyField
|
||||
|
||||
|
||||
class PrefetchQueryMixin(RelationMixin):
|
||||
"""
|
||||
@ -39,7 +40,8 @@ class PrefetchQueryMixin(RelationMixin):
|
||||
if reverse:
|
||||
field_name = parent_model.Meta.model_fields[related].get_related_name()
|
||||
field = target_model.Meta.model_fields[field_name]
|
||||
if issubclass(field, ormar.fields.ManyToManyField):
|
||||
if field.is_multi:
|
||||
field = cast(Type["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()
|
||||
@ -87,7 +89,7 @@ class PrefetchQueryMixin(RelationMixin):
|
||||
:return: name of the field
|
||||
:rtype: str
|
||||
"""
|
||||
if issubclass(target_field, ormar.fields.ManyToManyField):
|
||||
if target_field.is_multi:
|
||||
return cls.get_name()
|
||||
if target_field.virtual:
|
||||
return target_field.get_related_name()
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import inspect
|
||||
from typing import List, Optional, Set, TYPE_CHECKING
|
||||
|
||||
from ormar import ManyToManyField
|
||||
from ormar.fields import ThroughField
|
||||
from ormar.fields.foreign_key import ForeignKeyField
|
||||
|
||||
|
||||
class RelationMixin:
|
||||
"""
|
||||
@ -62,7 +58,7 @@ class RelationMixin:
|
||||
related_fields = set()
|
||||
for name in cls.extract_related_names():
|
||||
field = cls.Meta.model_fields[name]
|
||||
if issubclass(field, ManyToManyField):
|
||||
if field.is_multi:
|
||||
related_fields.add(field.through.get_name(lower=True))
|
||||
return related_fields
|
||||
|
||||
@ -80,11 +76,7 @@ class RelationMixin:
|
||||
|
||||
related_names = set()
|
||||
for name, field in cls.Meta.model_fields.items():
|
||||
if (
|
||||
inspect.isclass(field)
|
||||
and issubclass(field, ForeignKeyField)
|
||||
and not issubclass(field, ThroughField)
|
||||
):
|
||||
if inspect.isclass(field) and field.is_relation and not field.is_through:
|
||||
related_names.add(name)
|
||||
cls._related_names = related_names
|
||||
|
||||
|
||||
@ -8,7 +8,6 @@ from typing import (
|
||||
|
||||
import ormar.queryset # noqa I100
|
||||
from ormar.exceptions import ModelPersistenceError, NoMatch
|
||||
from ormar.fields.many_to_many import ManyToManyField
|
||||
from ormar.models import NewBaseModel # noqa I100
|
||||
from ormar.models.metaclass import ModelMeta
|
||||
from ormar.models.model_row import ModelRow
|
||||
@ -139,8 +138,9 @@ class Model(ModelRow):
|
||||
visited.add(self.__class__)
|
||||
|
||||
for related in self.extract_related_names():
|
||||
if self.Meta.model_fields[related].virtual or issubclass(
|
||||
self.Meta.model_fields[related], ManyToManyField
|
||||
if (
|
||||
self.Meta.model_fields[related].virtual
|
||||
or self.Meta.model_fields[related].is_multi
|
||||
):
|
||||
for rel in getattr(self, related):
|
||||
update_count, visited = await self._update_and_follow(
|
||||
|
||||
@ -8,24 +8,26 @@ from typing import (
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
import sqlalchemy
|
||||
|
||||
from ormar import ManyToManyField # noqa: I202
|
||||
from ormar.models import NewBaseModel
|
||||
from ormar.models import NewBaseModel # noqa: I202
|
||||
from ormar.models.helpers.models import group_related_list
|
||||
|
||||
T = TypeVar("T", bound="ModelRow")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ormar.fields import ForeignKeyField
|
||||
from ormar.models import T
|
||||
else:
|
||||
T = TypeVar("T", bound="ModelRow")
|
||||
|
||||
|
||||
class ModelRow(NewBaseModel):
|
||||
@classmethod
|
||||
def from_row(
|
||||
cls: Type[T],
|
||||
def from_row( # noqa: CFQ002
|
||||
cls: Type["ModelRow"],
|
||||
row: sqlalchemy.engine.ResultProxy,
|
||||
source_model: Type[T],
|
||||
select_related: List = None,
|
||||
@ -75,7 +77,7 @@ class ModelRow(NewBaseModel):
|
||||
table_prefix = ""
|
||||
|
||||
if select_related:
|
||||
source_model = cls
|
||||
source_model = cast(Type[T], cls)
|
||||
related_models = group_related_list(select_related)
|
||||
|
||||
if related_field:
|
||||
@ -107,7 +109,7 @@ class ModelRow(NewBaseModel):
|
||||
item["__excluded__"] = cls.get_names_to_exclude(
|
||||
fields=fields, exclude_fields=exclude_fields
|
||||
)
|
||||
instance = cls(**item)
|
||||
instance = cast(T, cls(**item))
|
||||
instance.set_save_status(True)
|
||||
return instance
|
||||
|
||||
@ -160,6 +162,7 @@ class ModelRow(NewBaseModel):
|
||||
else related
|
||||
)
|
||||
field = cls.Meta.model_fields[related]
|
||||
field = cast(Type["ForeignKeyField"], field)
|
||||
fields = cls.get_included(fields, related)
|
||||
exclude_fields = cls.get_excluded(exclude_fields, related)
|
||||
model_cls = field.to
|
||||
@ -177,7 +180,7 @@ class ModelRow(NewBaseModel):
|
||||
source_model=source_model,
|
||||
)
|
||||
item[model_cls.get_column_name_from_alias(related)] = child
|
||||
if issubclass(field, ManyToManyField) and child:
|
||||
if field.is_multi and child:
|
||||
# TODO: way to figure out which side should be populated?
|
||||
through_name = cls.Meta.model_fields[related].through.get_name()
|
||||
# for now it's nested dict, should be instance?
|
||||
|
||||
@ -46,15 +46,15 @@ from ormar.relations.alias_manager import AliasManager
|
||||
from ormar.relations.relation_manager import RelationsManager
|
||||
|
||||
if TYPE_CHECKING: # pragma no cover
|
||||
from ormar import Model
|
||||
from ormar.models import Model, T
|
||||
from ormar.signals import SignalEmitter
|
||||
|
||||
T = TypeVar("T", bound=Model)
|
||||
|
||||
IntStr = Union[int, str]
|
||||
DictStrAny = Dict[str, Any]
|
||||
AbstractSetIntStr = AbstractSet[IntStr]
|
||||
MappingIntStrAny = Mapping[IntStr, Any]
|
||||
else:
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass):
|
||||
@ -89,7 +89,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
||||
Meta: ModelMeta
|
||||
|
||||
# noinspection PyMissingConstructor
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None: # type: ignore
|
||||
def __init__(self: T, *args: Any, **kwargs: Any) -> None: # type: ignore
|
||||
"""
|
||||
Initializer that creates a new ormar Model that is also pydantic Model at the
|
||||
same time.
|
||||
@ -129,7 +129,9 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
||||
object.__setattr__(
|
||||
self,
|
||||
"_orm",
|
||||
RelationsManager(related_fields=self.extract_related_fields(), owner=self,),
|
||||
RelationsManager(
|
||||
related_fields=self.extract_related_fields(), owner=cast(T, self),
|
||||
),
|
||||
)
|
||||
|
||||
pk_only = kwargs.pop("__pk_only__", False)
|
||||
@ -298,7 +300,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
||||
|
||||
def _extract_related_model_instead_of_field(
|
||||
self, item: str
|
||||
) -> Optional[Union["T", Sequence["T"]]]:
|
||||
) -> Optional[Union["Model", Sequence["Model"]]]:
|
||||
"""
|
||||
Retrieves the related model/models from RelationshipManager.
|
||||
|
||||
@ -755,9 +757,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
|
||||
:return: value of pk if set
|
||||
:rtype: Optional[int]
|
||||
"""
|
||||
if target_field.virtual or issubclass(
|
||||
target_field, ormar.fields.ManyToManyField
|
||||
):
|
||||
if target_field.virtual or target_field.is_multi:
|
||||
return self.pk
|
||||
related_name = target_field.name
|
||||
related_model = getattr(self, related_name)
|
||||
|
||||
Reference in New Issue
Block a user