further refactor into mixins
This commit is contained in:
@ -1,49 +0,0 @@
|
|||||||
from typing import Dict, Set, Union
|
|
||||||
|
|
||||||
|
|
||||||
class Excludable:
|
|
||||||
@staticmethod
|
|
||||||
def get_child(
|
|
||||||
items: Union[Set, Dict, None], key: str = None
|
|
||||||
) -> Union[Set, Dict, None]:
|
|
||||||
if isinstance(items, dict):
|
|
||||||
return items.get(key, {})
|
|
||||||
return items
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_excluded(
|
|
||||||
exclude: Union[Set, Dict, None], key: str = None
|
|
||||||
) -> Union[Set, Dict, None]:
|
|
||||||
return Excludable.get_child(items=exclude, key=key)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_included(
|
|
||||||
include: Union[Set, Dict, None], key: str = None
|
|
||||||
) -> Union[Set, Dict, None]:
|
|
||||||
return Excludable.get_child(items=include, key=key)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_excluded(exclude: Union[Set, Dict, None], key: str = None) -> bool:
|
|
||||||
if exclude is None:
|
|
||||||
return False
|
|
||||||
if exclude is Ellipsis: # pragma: nocover
|
|
||||||
return True
|
|
||||||
to_exclude = Excludable.get_excluded(exclude=exclude, key=key)
|
|
||||||
if isinstance(to_exclude, Set):
|
|
||||||
return key in to_exclude
|
|
||||||
if to_exclude is ...:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_included(include: Union[Set, Dict, None], key: str = None) -> bool:
|
|
||||||
if include is None:
|
|
||||||
return True
|
|
||||||
if include is Ellipsis:
|
|
||||||
return True
|
|
||||||
to_include = Excludable.get_included(include=include, key=key)
|
|
||||||
if isinstance(to_include, Set):
|
|
||||||
return key in to_include
|
|
||||||
if to_include is ...:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
from ormar.models.helpers.models import (
|
||||||
|
extract_annotations_and_default_vals,
|
||||||
|
populate_default_options_values,
|
||||||
|
)
|
||||||
|
from ormar.models.helpers.pydantic import (
|
||||||
|
get_potential_fields,
|
||||||
|
get_pydantic_base_orm_config,
|
||||||
|
get_pydantic_field,
|
||||||
|
)
|
||||||
|
from ormar.models.helpers.relations import (
|
||||||
|
alias_manager,
|
||||||
|
register_relation_in_alias_manager,
|
||||||
|
)
|
||||||
|
from ormar.models.helpers.relations import expand_reverse_relationships
|
||||||
|
from ormar.models.helpers.sqlalchemy import (
|
||||||
|
populate_meta_sqlalchemy_table_if_required,
|
||||||
|
populate_meta_tablename_columns_and_pk,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"expand_reverse_relationships",
|
||||||
|
"extract_annotations_and_default_vals",
|
||||||
|
"populate_meta_tablename_columns_and_pk",
|
||||||
|
"populate_meta_sqlalchemy_table_if_required",
|
||||||
|
"populate_default_options_values",
|
||||||
|
"alias_manager",
|
||||||
|
"register_relation_in_alias_manager",
|
||||||
|
"get_pydantic_field",
|
||||||
|
"get_potential_fields",
|
||||||
|
"get_pydantic_base_orm_config",
|
||||||
|
]
|
||||||
|
|||||||
@ -21,23 +21,17 @@ from ormar import ForeignKey, Integer, ModelDefinitionError # noqa I100
|
|||||||
from ormar.fields import BaseField
|
from ormar.fields import BaseField
|
||||||
from ormar.fields.foreign_key import ForeignKeyField
|
from ormar.fields.foreign_key import ForeignKeyField
|
||||||
from ormar.fields.many_to_many import ManyToManyField
|
from ormar.fields.many_to_many import ManyToManyField
|
||||||
from ormar.models.helpers.models import (
|
from ormar.models.helpers import (
|
||||||
|
alias_manager,
|
||||||
|
expand_reverse_relationships,
|
||||||
extract_annotations_and_default_vals,
|
extract_annotations_and_default_vals,
|
||||||
populate_default_options_values,
|
|
||||||
)
|
|
||||||
from ormar.models.helpers.pydantic import (
|
|
||||||
get_potential_fields,
|
get_potential_fields,
|
||||||
get_pydantic_base_orm_config,
|
get_pydantic_base_orm_config,
|
||||||
get_pydantic_field,
|
get_pydantic_field,
|
||||||
)
|
populate_default_options_values,
|
||||||
from ormar.models.helpers.relations import (
|
|
||||||
alias_manager,
|
|
||||||
register_relation_in_alias_manager,
|
|
||||||
)
|
|
||||||
from ormar.models.helpers.relations import expand_reverse_relationships
|
|
||||||
from ormar.models.helpers.sqlalchemy import (
|
|
||||||
populate_meta_sqlalchemy_table_if_required,
|
populate_meta_sqlalchemy_table_if_required,
|
||||||
populate_meta_tablename_columns_and_pk,
|
populate_meta_tablename_columns_and_pk,
|
||||||
|
register_relation_in_alias_manager,
|
||||||
)
|
)
|
||||||
from ormar.models.quick_access_views import quick_access_set
|
from ormar.models.quick_access_views import quick_access_set
|
||||||
from ormar.queryset import QuerySet
|
from ormar.queryset import QuerySet
|
||||||
@ -387,7 +381,6 @@ def copy_data_from_parent_model( # noqa: CCR001
|
|||||||
}
|
}
|
||||||
populate_meta_sqlalchemy_table_if_required(new_meta)
|
populate_meta_sqlalchemy_table_if_required(new_meta)
|
||||||
copy_name = through_class.__name__ + attrs.get("__name__", "")
|
copy_name = through_class.__name__ + attrs.get("__name__", "")
|
||||||
# TODO: when adding additional fields they need to be copied here
|
|
||||||
copy_through = type(copy_name, (ormar.Model,), {"Meta": new_meta})
|
copy_through = type(copy_name, (ormar.Model,), {"Meta": new_meta})
|
||||||
copy_field.through = copy_through
|
copy_field.through = copy_through
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,19 @@
|
|||||||
|
"""
|
||||||
|
Package contains functionalities divided by features.
|
||||||
|
All mixins are combined into ModelTableProxy which is one of the parents of Model.
|
||||||
|
The split into mixins was done to ease the maintainability of the proxy class, as
|
||||||
|
it became quite complicated over time.
|
||||||
|
"""
|
||||||
from ormar.models.mixins.alias_mixin import AliasMixin
|
from ormar.models.mixins.alias_mixin import AliasMixin
|
||||||
|
from ormar.models.mixins.excludable_mixin import ExcludableMixin
|
||||||
from ormar.models.mixins.merge_mixin import MergeModelMixin
|
from ormar.models.mixins.merge_mixin import MergeModelMixin
|
||||||
from ormar.models.mixins.prefetch_mixin import PrefetchQueryMixin
|
from ormar.models.mixins.prefetch_mixin import PrefetchQueryMixin
|
||||||
|
from ormar.models.mixins.save_mixin import SavePrepareMixin
|
||||||
|
|
||||||
__all__ = ["MergeModelMixin", "AliasMixin", "PrefetchQueryMixin"]
|
__all__ = [
|
||||||
|
"MergeModelMixin",
|
||||||
|
"AliasMixin",
|
||||||
|
"PrefetchQueryMixin",
|
||||||
|
"SavePrepareMixin",
|
||||||
|
"ExcludableMixin",
|
||||||
|
]
|
||||||
|
|||||||
@ -1,19 +1,35 @@
|
|||||||
from typing import Dict, List, Optional, Set, TYPE_CHECKING, Type, Union
|
from typing import Dict, TYPE_CHECKING
|
||||||
|
|
||||||
|
|
||||||
class AliasMixin:
|
class AliasMixin:
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from ormar import Model, ModelMeta
|
from ormar import ModelMeta
|
||||||
|
|
||||||
Meta: ModelMeta
|
Meta: ModelMeta
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_column_alias(cls, field_name: str) -> str:
|
def get_column_alias(cls, field_name: str) -> str:
|
||||||
|
"""
|
||||||
|
Returns db alias (column name in db) for given ormar field.
|
||||||
|
For fields without alias field name is returned.
|
||||||
|
:param field_name: name of the field to get alias from
|
||||||
|
:type field_name: str
|
||||||
|
:return: alias (db name) if set, otherwise passed name
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
field = cls.Meta.model_fields.get(field_name)
|
field = cls.Meta.model_fields.get(field_name)
|
||||||
return field.get_alias() if field is not None else field_name
|
return field.get_alias() if field is not None else field_name
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_column_name_from_alias(cls, alias: str) -> str:
|
def get_column_name_from_alias(cls, alias: str) -> str:
|
||||||
|
"""
|
||||||
|
Returns ormar field name for given db alias (column name in db).
|
||||||
|
If field do not have alias it's returned as is.
|
||||||
|
:param alias:
|
||||||
|
:type alias: str
|
||||||
|
:return: field name if set, otherwise passed alias (db name)
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
for field_name, field in cls.Meta.model_fields.items():
|
for field_name, field in cls.Meta.model_fields.items():
|
||||||
if field.get_alias() == alias:
|
if field.get_alias() == alias:
|
||||||
return field_name
|
return field_name
|
||||||
@ -21,6 +37,15 @@ class AliasMixin:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def translate_columns_to_aliases(cls, new_kwargs: Dict) -> Dict:
|
def translate_columns_to_aliases(cls, new_kwargs: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
Translates dictionary of model fields changing field names into aliases.
|
||||||
|
If field has no alias the field name remains intact.
|
||||||
|
Only fields present in the dictionary are translated.
|
||||||
|
:param new_kwargs: dict with fields names and their values
|
||||||
|
:type new_kwargs: Dict
|
||||||
|
:return: dict with aliases and their values
|
||||||
|
:rtype: Dict
|
||||||
|
"""
|
||||||
for field_name, field in cls.Meta.model_fields.items():
|
for field_name, field in cls.Meta.model_fields.items():
|
||||||
if field_name in new_kwargs:
|
if field_name in new_kwargs:
|
||||||
new_kwargs[field.get_alias()] = new_kwargs.pop(field_name)
|
new_kwargs[field.get_alias()] = new_kwargs.pop(field_name)
|
||||||
@ -28,56 +53,16 @@ class AliasMixin:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def translate_aliases_to_columns(cls, new_kwargs: Dict) -> Dict:
|
def translate_aliases_to_columns(cls, new_kwargs: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
Translates dictionary of model fields changing aliases into field names.
|
||||||
|
If field has no alias the alias is already a field name.
|
||||||
|
Only fields present in the dictionary are translated.
|
||||||
|
:param new_kwargs: dict with aliases and their values
|
||||||
|
:type new_kwargs: Dict
|
||||||
|
:return: dict with fields names and their values
|
||||||
|
: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.alias and field.alias in new_kwargs:
|
||||||
new_kwargs[field_name] = new_kwargs.pop(field.alias)
|
new_kwargs[field_name] = new_kwargs.pop(field.alias)
|
||||||
return new_kwargs
|
return new_kwargs
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _populate_pk_column(
|
|
||||||
model: Type["Model"], columns: List[str], use_alias: bool = False,
|
|
||||||
) -> List[str]:
|
|
||||||
pk_alias = (
|
|
||||||
model.get_column_alias(model.Meta.pkname)
|
|
||||||
if use_alias
|
|
||||||
else model.Meta.pkname
|
|
||||||
)
|
|
||||||
if pk_alias not in columns:
|
|
||||||
columns.append(pk_alias)
|
|
||||||
return columns
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def own_table_columns(
|
|
||||||
cls,
|
|
||||||
model: Type["Model"],
|
|
||||||
fields: Optional[Union[Set, Dict]],
|
|
||||||
exclude_fields: Optional[Union[Set, Dict]],
|
|
||||||
use_alias: bool = False,
|
|
||||||
) -> List[str]:
|
|
||||||
columns = [
|
|
||||||
model.get_column_name_from_alias(col.name) if not use_alias else col.name
|
|
||||||
for col in model.Meta.table.columns
|
|
||||||
]
|
|
||||||
field_names = [
|
|
||||||
model.get_column_name_from_alias(col.name)
|
|
||||||
for col in model.Meta.table.columns
|
|
||||||
]
|
|
||||||
if fields:
|
|
||||||
columns = [
|
|
||||||
col
|
|
||||||
for col, name in zip(columns, field_names)
|
|
||||||
if model.is_included(fields, name)
|
|
||||||
]
|
|
||||||
if exclude_fields:
|
|
||||||
columns = [
|
|
||||||
col
|
|
||||||
for col, name in zip(columns, field_names)
|
|
||||||
if not model.is_excluded(exclude_fields, name)
|
|
||||||
]
|
|
||||||
|
|
||||||
# always has to return pk column for ormar to work
|
|
||||||
columns = cls._populate_pk_column(
|
|
||||||
model=model, columns=columns, use_alias=use_alias
|
|
||||||
)
|
|
||||||
|
|
||||||
return columns
|
|
||||||
|
|||||||
169
ormar/models/mixins/excludable_mixin.py
Normal file
169
ormar/models/mixins/excludable_mixin.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
from typing import (
|
||||||
|
AbstractSet,
|
||||||
|
Any,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Mapping,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
TYPE_CHECKING,
|
||||||
|
Type,
|
||||||
|
TypeVar,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ormar.models.mixins.relation_mixin import RelationMixin
|
||||||
|
from ormar.queryset.utils import translate_list_to_dict, update
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma no cover
|
||||||
|
from ormar import Model
|
||||||
|
|
||||||
|
T = TypeVar("T", bound=Model)
|
||||||
|
IntStr = Union[int, str]
|
||||||
|
AbstractSetIntStr = AbstractSet[IntStr]
|
||||||
|
MappingIntStrAny = Mapping[IntStr, Any]
|
||||||
|
|
||||||
|
|
||||||
|
class ExcludableMixin(RelationMixin):
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from ormar import Model
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_child(
|
||||||
|
items: Union[Set, Dict, None], key: str = None
|
||||||
|
) -> Union[Set, Dict, None]:
|
||||||
|
if isinstance(items, dict):
|
||||||
|
return items.get(key, {})
|
||||||
|
return items
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_excluded(
|
||||||
|
exclude: Union[Set, Dict, None], key: str = None
|
||||||
|
) -> Union[Set, Dict, None]:
|
||||||
|
return ExcludableMixin.get_child(items=exclude, key=key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_included(
|
||||||
|
include: Union[Set, Dict, None], key: str = None
|
||||||
|
) -> Union[Set, Dict, None]:
|
||||||
|
return ExcludableMixin.get_child(items=include, key=key)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_excluded(exclude: Union[Set, Dict, None], key: str = None) -> bool:
|
||||||
|
if exclude is None:
|
||||||
|
return False
|
||||||
|
if exclude is Ellipsis: # pragma: nocover
|
||||||
|
return True
|
||||||
|
to_exclude = ExcludableMixin.get_excluded(exclude=exclude, key=key)
|
||||||
|
if isinstance(to_exclude, Set):
|
||||||
|
return key in to_exclude
|
||||||
|
if to_exclude is ...:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_included(include: Union[Set, Dict, None], key: str = None) -> bool:
|
||||||
|
if include is None:
|
||||||
|
return True
|
||||||
|
if include is Ellipsis:
|
||||||
|
return True
|
||||||
|
to_include = ExcludableMixin.get_included(include=include, key=key)
|
||||||
|
if isinstance(to_include, Set):
|
||||||
|
return key in to_include
|
||||||
|
if to_include is ...:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _populate_pk_column(
|
||||||
|
model: Type["Model"], columns: List[str], use_alias: bool = False,
|
||||||
|
) -> List[str]:
|
||||||
|
pk_alias = (
|
||||||
|
model.get_column_alias(model.Meta.pkname)
|
||||||
|
if use_alias
|
||||||
|
else model.Meta.pkname
|
||||||
|
)
|
||||||
|
if pk_alias not in columns:
|
||||||
|
columns.append(pk_alias)
|
||||||
|
return columns
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def own_table_columns(
|
||||||
|
cls,
|
||||||
|
model: Type["Model"],
|
||||||
|
fields: Optional[Union[Set, Dict]],
|
||||||
|
exclude_fields: Optional[Union[Set, Dict]],
|
||||||
|
use_alias: bool = False,
|
||||||
|
) -> List[str]:
|
||||||
|
columns = [
|
||||||
|
model.get_column_name_from_alias(col.name) if not use_alias else col.name
|
||||||
|
for col in model.Meta.table.columns
|
||||||
|
]
|
||||||
|
field_names = [
|
||||||
|
model.get_column_name_from_alias(col.name)
|
||||||
|
for col in model.Meta.table.columns
|
||||||
|
]
|
||||||
|
if fields:
|
||||||
|
columns = [
|
||||||
|
col
|
||||||
|
for col, name in zip(columns, field_names)
|
||||||
|
if model.is_included(fields, name)
|
||||||
|
]
|
||||||
|
if exclude_fields:
|
||||||
|
columns = [
|
||||||
|
col
|
||||||
|
for col, name in zip(columns, field_names)
|
||||||
|
if not model.is_excluded(exclude_fields, name)
|
||||||
|
]
|
||||||
|
|
||||||
|
# always has to return pk column for ormar to work
|
||||||
|
columns = cls._populate_pk_column(
|
||||||
|
model=model, columns=columns, use_alias=use_alias
|
||||||
|
)
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _update_excluded_with_related_not_required(
|
||||||
|
cls,
|
||||||
|
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None],
|
||||||
|
nested: bool = False,
|
||||||
|
) -> Union[Set, Dict]:
|
||||||
|
exclude = exclude or {}
|
||||||
|
related_set = cls._exclude_related_names_not_required(nested=nested)
|
||||||
|
if isinstance(exclude, set):
|
||||||
|
exclude.union(related_set)
|
||||||
|
else:
|
||||||
|
related_dict = translate_list_to_dict(related_set)
|
||||||
|
exclude = update(related_dict, exclude)
|
||||||
|
return exclude
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_names_to_exclude(
|
||||||
|
cls,
|
||||||
|
fields: Optional[Union[Dict, Set]] = None,
|
||||||
|
exclude_fields: Optional[Union[Dict, Set]] = None,
|
||||||
|
) -> Set:
|
||||||
|
fields_names = cls.extract_db_own_fields()
|
||||||
|
if fields and fields is not Ellipsis:
|
||||||
|
fields_to_keep = {name for name in fields if name in fields_names}
|
||||||
|
else:
|
||||||
|
fields_to_keep = fields_names
|
||||||
|
|
||||||
|
fields_to_exclude = fields_names - fields_to_keep
|
||||||
|
|
||||||
|
if isinstance(exclude_fields, Set):
|
||||||
|
fields_to_exclude = fields_to_exclude.union(
|
||||||
|
{name for name in exclude_fields if name in fields_names}
|
||||||
|
)
|
||||||
|
elif isinstance(exclude_fields, Dict):
|
||||||
|
new_to_exclude = {
|
||||||
|
name
|
||||||
|
for name in exclude_fields
|
||||||
|
if name in fields_names and exclude_fields[name] is Ellipsis
|
||||||
|
}
|
||||||
|
fields_to_exclude = fields_to_exclude.union(new_to_exclude)
|
||||||
|
|
||||||
|
fields_to_exclude = fields_to_exclude - {cls.Meta.pkname}
|
||||||
|
|
||||||
|
return fields_to_exclude
|
||||||
@ -2,14 +2,14 @@ from typing import Callable, Dict, List, TYPE_CHECKING, Tuple, Type
|
|||||||
|
|
||||||
import ormar
|
import ormar
|
||||||
from ormar.fields import BaseField
|
from ormar.fields import BaseField
|
||||||
|
from ormar.models.mixins.relation_mixin import RelationMixin
|
||||||
|
|
||||||
|
|
||||||
class PrefetchQueryMixin:
|
class PrefetchQueryMixin(RelationMixin):
|
||||||
if TYPE_CHECKING: # pragma no cover
|
if TYPE_CHECKING: # pragma no cover
|
||||||
from ormar import Model
|
from ormar import Model
|
||||||
|
|
||||||
get_name: Callable # defined in NewBaseModel
|
get_name: Callable # defined in NewBaseModel
|
||||||
extract_related_names: Callable # defined in ModelTableProxy
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_clause_target_and_filter_column_name(
|
def get_clause_target_and_filter_column_name(
|
||||||
|
|||||||
68
ormar/models/mixins/relation_mixin.py
Normal file
68
ormar/models/mixins/relation_mixin.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import inspect
|
||||||
|
from typing import List, Optional, Set, TYPE_CHECKING
|
||||||
|
|
||||||
|
from ormar.fields.foreign_key import ForeignKeyField
|
||||||
|
|
||||||
|
|
||||||
|
class RelationMixin:
|
||||||
|
if TYPE_CHECKING: # pragma no cover
|
||||||
|
from ormar import ModelMeta
|
||||||
|
|
||||||
|
Meta: ModelMeta
|
||||||
|
_related_names: Optional[Set]
|
||||||
|
_related_fields: Optional[List]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def extract_db_own_fields(cls) -> Set:
|
||||||
|
related_names = cls.extract_related_names()
|
||||||
|
self_fields = {
|
||||||
|
name for name in cls.Meta.model_fields.keys() if name not in related_names
|
||||||
|
}
|
||||||
|
return self_fields
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def extract_related_fields(cls) -> List:
|
||||||
|
|
||||||
|
if isinstance(cls._related_fields, List):
|
||||||
|
return cls._related_fields
|
||||||
|
|
||||||
|
related_fields = []
|
||||||
|
for name in cls.extract_related_names():
|
||||||
|
related_fields.append(cls.Meta.model_fields[name])
|
||||||
|
cls._related_fields = related_fields
|
||||||
|
|
||||||
|
return related_fields
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def extract_related_names(cls) -> Set:
|
||||||
|
|
||||||
|
if isinstance(cls._related_names, Set):
|
||||||
|
return cls._related_names
|
||||||
|
|
||||||
|
related_names = set()
|
||||||
|
for name, field in cls.Meta.model_fields.items():
|
||||||
|
if inspect.isclass(field) and issubclass(field, ForeignKeyField):
|
||||||
|
related_names.add(name)
|
||||||
|
cls._related_names = related_names
|
||||||
|
|
||||||
|
return related_names
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _extract_db_related_names(cls) -> Set:
|
||||||
|
related_names = cls.extract_related_names()
|
||||||
|
related_names = {
|
||||||
|
name
|
||||||
|
for name in related_names
|
||||||
|
if cls.Meta.model_fields[name].is_valid_uni_relation()
|
||||||
|
}
|
||||||
|
return related_names
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _exclude_related_names_not_required(cls, nested: bool = False) -> Set:
|
||||||
|
if nested:
|
||||||
|
return cls.extract_related_names()
|
||||||
|
related_names = cls.extract_related_names()
|
||||||
|
related_names = {
|
||||||
|
name for name in related_names if cls.Meta.model_fields[name].nullable
|
||||||
|
}
|
||||||
|
return related_names
|
||||||
47
ormar/models/mixins/save_mixin.py
Normal file
47
ormar/models/mixins/save_mixin.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
from ormar.exceptions import ModelPersistenceError
|
||||||
|
from ormar.models.mixins.relation_mixin import RelationMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SavePrepareMixin(RelationMixin):
|
||||||
|
@classmethod
|
||||||
|
def substitute_models_with_pks(cls, model_dict: Dict) -> Dict: # noqa CCR001
|
||||||
|
for field in cls.extract_related_names():
|
||||||
|
field_value = model_dict.get(field, None)
|
||||||
|
if field_value is not None:
|
||||||
|
target_field = cls.Meta.model_fields[field]
|
||||||
|
target_pkname = target_field.to.Meta.pkname
|
||||||
|
if isinstance(field_value, ormar.Model):
|
||||||
|
pk_value = getattr(field_value, target_pkname)
|
||||||
|
if not pk_value:
|
||||||
|
raise ModelPersistenceError(
|
||||||
|
f"You cannot save {field_value.get_name()} "
|
||||||
|
f"model without pk set!"
|
||||||
|
)
|
||||||
|
model_dict[field] = pk_value
|
||||||
|
elif field_value: # nested dict
|
||||||
|
if isinstance(field_value, list):
|
||||||
|
model_dict[field] = [
|
||||||
|
target.get(target_pkname) for target in field_value
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
model_dict[field] = field_value.get(target_pkname)
|
||||||
|
else:
|
||||||
|
model_dict.pop(field, None)
|
||||||
|
return model_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def populate_default_values(cls, new_kwargs: Dict) -> Dict:
|
||||||
|
for field_name, field in cls.Meta.model_fields.items():
|
||||||
|
if (
|
||||||
|
field_name not in new_kwargs
|
||||||
|
and field.has_default(use_server=False)
|
||||||
|
and not field.pydantic_only
|
||||||
|
):
|
||||||
|
new_kwargs[field_name] = field.get_default()
|
||||||
|
# clear fields with server_default set as None
|
||||||
|
if field.server_default is not None and not new_kwargs.get(field_name):
|
||||||
|
new_kwargs.pop(field_name, None)
|
||||||
|
return new_kwargs
|
||||||
@ -1,183 +1,14 @@
|
|||||||
import inspect
|
|
||||||
from typing import (
|
|
||||||
AbstractSet,
|
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
List,
|
|
||||||
Mapping,
|
|
||||||
Optional,
|
|
||||||
Set,
|
|
||||||
TYPE_CHECKING,
|
|
||||||
TypeVar,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
import ormar # noqa: I100
|
import ormar # noqa: I100
|
||||||
from ormar.exceptions import ModelPersistenceError
|
from ormar.models.mixins import (
|
||||||
from ormar.fields import BaseField
|
AliasMixin,
|
||||||
from ormar.fields.foreign_key import ForeignKeyField
|
ExcludableMixin,
|
||||||
from ormar.models.metaclass import ModelMeta
|
MergeModelMixin,
|
||||||
from ormar.models.mixins import AliasMixin, MergeModelMixin, PrefetchQueryMixin
|
PrefetchQueryMixin,
|
||||||
from ormar.queryset.utils import translate_list_to_dict, update
|
SavePrepareMixin,
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma no cover
|
|
||||||
from ormar import Model
|
|
||||||
|
|
||||||
T = TypeVar("T", bound=Model)
|
|
||||||
IntStr = Union[int, str]
|
|
||||||
AbstractSetIntStr = AbstractSet[IntStr]
|
|
||||||
MappingIntStrAny = Mapping[IntStr, Any]
|
|
||||||
|
|
||||||
Field = TypeVar("Field", bound=BaseField)
|
|
||||||
|
|
||||||
|
|
||||||
class ModelTableProxy(PrefetchQueryMixin, MergeModelMixin, AliasMixin):
|
|
||||||
if TYPE_CHECKING: # pragma no cover
|
|
||||||
Meta: ModelMeta
|
|
||||||
_related_names: Optional[Set]
|
|
||||||
_related_fields: Optional[List]
|
|
||||||
pk: Any
|
|
||||||
get_name: Callable
|
|
||||||
_props: Set
|
|
||||||
dict: Callable # noqa: A001, VNE003
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def extract_db_own_fields(cls) -> Set:
|
|
||||||
related_names = cls.extract_related_names()
|
|
||||||
self_fields = {
|
|
||||||
name for name in cls.Meta.model_fields.keys() if name not in related_names
|
|
||||||
}
|
|
||||||
return self_fields
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def substitute_models_with_pks(cls, model_dict: Dict) -> Dict: # noqa CCR001
|
|
||||||
for field in cls.extract_related_names():
|
|
||||||
field_value = model_dict.get(field, None)
|
|
||||||
if field_value is not None:
|
|
||||||
target_field = cls.Meta.model_fields[field]
|
|
||||||
target_pkname = target_field.to.Meta.pkname
|
|
||||||
if isinstance(field_value, ormar.Model):
|
|
||||||
pk_value = getattr(field_value, target_pkname)
|
|
||||||
if not pk_value:
|
|
||||||
raise ModelPersistenceError(
|
|
||||||
f"You cannot save {field_value.get_name()} "
|
|
||||||
f"model without pk set!"
|
|
||||||
)
|
)
|
||||||
model_dict[field] = pk_value
|
|
||||||
elif field_value: # nested dict
|
|
||||||
if isinstance(field_value, list):
|
|
||||||
model_dict[field] = [
|
|
||||||
target.get(target_pkname) for target in field_value
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
model_dict[field] = field_value.get(target_pkname)
|
|
||||||
else:
|
|
||||||
model_dict.pop(field, None)
|
|
||||||
return model_dict
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def populate_default_values(cls, new_kwargs: Dict) -> Dict:
|
class ModelTableProxy(
|
||||||
for field_name, field in cls.Meta.model_fields.items():
|
PrefetchQueryMixin, MergeModelMixin, AliasMixin, SavePrepareMixin, ExcludableMixin
|
||||||
if (
|
|
||||||
field_name not in new_kwargs
|
|
||||||
and field.has_default(use_server=False)
|
|
||||||
and not field.pydantic_only
|
|
||||||
):
|
):
|
||||||
new_kwargs[field_name] = field.get_default()
|
pass
|
||||||
# clear fields with server_default set as None
|
|
||||||
if field.server_default is not None and not new_kwargs.get(field_name):
|
|
||||||
new_kwargs.pop(field_name, None)
|
|
||||||
return new_kwargs
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def extract_related_fields(cls) -> List:
|
|
||||||
|
|
||||||
if isinstance(cls._related_fields, List):
|
|
||||||
return cls._related_fields
|
|
||||||
|
|
||||||
related_fields = []
|
|
||||||
for name in cls.extract_related_names():
|
|
||||||
related_fields.append(cls.Meta.model_fields[name])
|
|
||||||
cls._related_fields = related_fields
|
|
||||||
|
|
||||||
return related_fields
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def extract_related_names(cls) -> Set:
|
|
||||||
|
|
||||||
if isinstance(cls._related_names, Set):
|
|
||||||
return cls._related_names
|
|
||||||
|
|
||||||
related_names = set()
|
|
||||||
for name, field in cls.Meta.model_fields.items():
|
|
||||||
if inspect.isclass(field) and issubclass(field, ForeignKeyField):
|
|
||||||
related_names.add(name)
|
|
||||||
cls._related_names = related_names
|
|
||||||
|
|
||||||
return related_names
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _extract_db_related_names(cls) -> Set:
|
|
||||||
related_names = cls.extract_related_names()
|
|
||||||
related_names = {
|
|
||||||
name
|
|
||||||
for name in related_names
|
|
||||||
if cls.Meta.model_fields[name].is_valid_uni_relation()
|
|
||||||
}
|
|
||||||
return related_names
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _exclude_related_names_not_required(cls, nested: bool = False) -> Set:
|
|
||||||
if nested:
|
|
||||||
return cls.extract_related_names()
|
|
||||||
related_names = cls.extract_related_names()
|
|
||||||
related_names = {
|
|
||||||
name for name in related_names if cls.Meta.model_fields[name].nullable
|
|
||||||
}
|
|
||||||
return related_names
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _update_excluded_with_related_not_required(
|
|
||||||
cls,
|
|
||||||
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None],
|
|
||||||
nested: bool = False,
|
|
||||||
) -> Union[Set, Dict]:
|
|
||||||
exclude = exclude or {}
|
|
||||||
related_set = cls._exclude_related_names_not_required(nested=nested)
|
|
||||||
if isinstance(exclude, set):
|
|
||||||
exclude.union(related_set)
|
|
||||||
else:
|
|
||||||
related_dict = translate_list_to_dict(related_set)
|
|
||||||
exclude = update(related_dict, exclude)
|
|
||||||
return exclude
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_names_to_exclude(
|
|
||||||
cls,
|
|
||||||
fields: Optional[Union[Dict, Set]] = None,
|
|
||||||
exclude_fields: Optional[Union[Dict, Set]] = None,
|
|
||||||
) -> Set:
|
|
||||||
fields_names = cls.extract_db_own_fields()
|
|
||||||
if fields and fields is not Ellipsis:
|
|
||||||
fields_to_keep = {name for name in fields if name in fields_names}
|
|
||||||
else:
|
|
||||||
fields_to_keep = fields_names
|
|
||||||
|
|
||||||
fields_to_exclude = fields_names - fields_to_keep
|
|
||||||
|
|
||||||
if isinstance(exclude_fields, Set):
|
|
||||||
fields_to_exclude = fields_to_exclude.union(
|
|
||||||
{name for name in exclude_fields if name in fields_names}
|
|
||||||
)
|
|
||||||
elif isinstance(exclude_fields, Dict):
|
|
||||||
new_to_exclude = {
|
|
||||||
name
|
|
||||||
for name in exclude_fields
|
|
||||||
if name in fields_names and exclude_fields[name] is Ellipsis
|
|
||||||
}
|
|
||||||
fields_to_exclude = fields_to_exclude.union(new_to_exclude)
|
|
||||||
|
|
||||||
fields_to_exclude = fields_to_exclude - {cls.Meta.pkname}
|
|
||||||
|
|
||||||
return fields_to_exclude
|
|
||||||
|
|||||||
@ -28,7 +28,6 @@ from pydantic import BaseModel
|
|||||||
import ormar # noqa I100
|
import ormar # noqa I100
|
||||||
from ormar.exceptions import ModelError
|
from ormar.exceptions import ModelError
|
||||||
from ormar.fields import BaseField
|
from ormar.fields import BaseField
|
||||||
from ormar.models.excludable import Excludable
|
|
||||||
from ormar.models.metaclass import ModelMeta, ModelMetaclass
|
from ormar.models.metaclass import ModelMeta, ModelMetaclass
|
||||||
from ormar.models.modelproxy import ModelTableProxy
|
from ormar.models.modelproxy import ModelTableProxy
|
||||||
from ormar.queryset.utils import translate_list_to_dict
|
from ormar.queryset.utils import translate_list_to_dict
|
||||||
@ -47,9 +46,7 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
MappingIntStrAny = Mapping[IntStr, Any]
|
MappingIntStrAny = Mapping[IntStr, Any]
|
||||||
|
|
||||||
|
|
||||||
class NewBaseModel(
|
class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass):
|
||||||
pydantic.BaseModel, ModelTableProxy, Excludable, 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
|
||||||
@ -272,11 +269,10 @@ class NewBaseModel(
|
|||||||
continue
|
continue
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _skip_ellipsis(
|
def _skip_ellipsis(
|
||||||
items: Union[Set, Dict, None], key: str
|
self, items: Union[Set, Dict, None], key: str
|
||||||
) -> Union[Set, Dict, None]:
|
) -> Union[Set, Dict, None]:
|
||||||
result = Excludable.get_child(items, key)
|
result = self.get_child(items, key)
|
||||||
return result if result is not Ellipsis else None
|
return result if result is not Ellipsis else None
|
||||||
|
|
||||||
def _extract_nested_models( # noqa: CCR001
|
def _extract_nested_models( # noqa: CCR001
|
||||||
|
|||||||
@ -2,15 +2,15 @@ import databases
|
|||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
|
|
||||||
import ormar
|
import ormar
|
||||||
from ormar.models.excludable import Excludable
|
from ormar.models.mixins import ExcludableMixin
|
||||||
from ormar.queryset.prefetch_query import sort_models
|
from ormar.queryset.prefetch_query import sort_models
|
||||||
from ormar.queryset.utils import translate_list_to_dict, update_dict_from_list, update
|
from ormar.queryset.utils import translate_list_to_dict, update_dict_from_list, update
|
||||||
from tests.settings import DATABASE_URL
|
from tests.settings import DATABASE_URL
|
||||||
|
|
||||||
|
|
||||||
def test_empty_excludable():
|
def test_empty_excludable():
|
||||||
assert Excludable.is_included(None, "key") # all fields included if empty
|
assert ExcludableMixin.is_included(None, "key") # all fields included if empty
|
||||||
assert not Excludable.is_excluded(None, "key") # none field excluded if empty
|
assert not ExcludableMixin.is_excluded(None, "key") # none field excluded if empty
|
||||||
|
|
||||||
|
|
||||||
def test_list_to_dict_translation():
|
def test_list_to_dict_translation():
|
||||||
|
|||||||
Reference in New Issue
Block a user