wip with m2m fields

This commit is contained in:
collerek
2021-02-28 08:19:02 +01:00
parent ad9d065c6d
commit fd38ae2a40
7 changed files with 231 additions and 304 deletions

View File

@ -12,6 +12,20 @@ class Excludable:
include: Set = field(default_factory=set) include: Set = field(default_factory=set)
exclude: Set = field(default_factory=set) exclude: Set = field(default_factory=set)
@property
def include_all(self):
return ... in self.include
@property
def exclude_all(self):
return ... in self.exclude
def get_copy(self) -> "Excludable":
_copy = self.__class__()
_copy.include = {x for x in self.include}
_copy.exclude = {x for x in self.exclude}
return _copy
def set_values(self, value: Set, is_exclude: bool) -> None: def set_values(self, value: Set, is_exclude: bool) -> None:
prop = "exclude" if is_exclude else "include" prop = "exclude" if is_exclude else "include"
if ... in getattr(self, prop) or ... in value: if ... in getattr(self, prop) or ... in value:
@ -38,15 +52,22 @@ class ExcludableItems:
def __init__(self) -> None: def __init__(self) -> None:
self.items: Dict[str, Excludable] = dict() self.items: Dict[str, Excludable] = dict()
@classmethod
def from_excludable(cls, other: "ExcludableItems") -> "ExcludableItems":
new_excludable = cls()
for key, value in other.items.items():
new_excludable.items[key] = value.get_copy()
return new_excludable
def get(self, model_cls: Type["Model"], alias: str = "") -> Excludable: def get(self, model_cls: Type["Model"], alias: str = "") -> Excludable:
key = f"{alias + '_' if alias else ''}{model_cls.get_name(lower=True)}" key = f"{alias + '_' if alias else ''}{model_cls.get_name(lower=True)}"
return self.items.get(key, Excludable()) return self.items.get(key, Excludable())
def build( def build(
self, self,
items: Union[List[str], str, Tuple[str], Set[str], Dict], items: Union[List[str], str, Tuple[str], Set[str], Dict],
model_cls: Type["Model"], model_cls: Type["Model"],
is_exclude: bool = False, is_exclude: bool = False,
) -> None: ) -> None:
if isinstance(items, str): if isinstance(items, str):
@ -75,7 +96,7 @@ class ExcludableItems:
) )
def _set_excludes( def _set_excludes(
self, items: Set, model_name: str, is_exclude: bool, alias: str = "" self, items: Set, model_name: str, is_exclude: bool, alias: str = ""
) -> None: ) -> None:
key = f"{alias + '_' if alias else ''}{model_name}" key = f"{alias + '_' if alias else ''}{model_name}"
@ -86,13 +107,13 @@ class ExcludableItems:
self.items[key] = excludable self.items[key] = excludable
def _traverse_dict( # noqa: CFQ002 def _traverse_dict( # noqa: CFQ002
self, self,
values: Dict, values: Dict,
source_model: Type["Model"], source_model: Type["Model"],
model_cls: Type["Model"], model_cls: Type["Model"],
is_exclude: bool, is_exclude: bool,
related_items: List = None, related_items: List = None,
alias: str = "", alias: str = "",
) -> None: ) -> None:
self_fields = set() self_fields = set()
@ -144,7 +165,7 @@ class ExcludableItems:
) )
def _traverse_list( def _traverse_list(
self, values: Set[str], model_cls: Type["Model"], is_exclude: bool self, values: Set[str], model_cls: Type["Model"], is_exclude: bool
) -> None: ) -> None:
# here we have only nested related keys # here we have only nested related keys

View File

@ -9,9 +9,10 @@ from typing import (
TYPE_CHECKING, TYPE_CHECKING,
Type, Type,
TypeVar, TypeVar,
Union, Union, cast,
) )
from ormar.models.excludable import ExcludableItems
from ormar.models.mixins.relation_mixin import RelationMixin from ormar.models.mixins.relation_mixin import RelationMixin
from ormar.queryset.utils import translate_list_to_dict, update from ormar.queryset.utils import translate_list_to_dict, update
@ -35,7 +36,7 @@ class ExcludableMixin(RelationMixin):
@staticmethod @staticmethod
def get_child( def get_child(
items: Union[Set, Dict, None], key: str = None items: Union[Set, Dict, None], key: str = None
) -> Union[Set, Dict, None]: ) -> Union[Set, Dict, None]:
""" """
Used to get nested dictionaries keys if they exists otherwise returns Used to get nested dictionaries keys if they exists otherwise returns
@ -53,7 +54,7 @@ class ExcludableMixin(RelationMixin):
@staticmethod @staticmethod
def get_excluded( def get_excluded(
exclude: Union[Set, Dict, None], key: str = None exclude: Union[Set, Dict, None], key: str = None
) -> Union[Set, Dict, None]: ) -> Union[Set, Dict, None]:
""" """
Proxy to ExcludableMixin.get_child for exclusions. Proxy to ExcludableMixin.get_child for exclusions.
@ -69,7 +70,7 @@ class ExcludableMixin(RelationMixin):
@staticmethod @staticmethod
def get_included( def get_included(
include: Union[Set, Dict, None], key: str = None include: Union[Set, Dict, None], key: str = None
) -> Union[Set, Dict, None]: ) -> Union[Set, Dict, None]:
""" """
Proxy to ExcludableMixin.get_child for inclusions. Proxy to ExcludableMixin.get_child for inclusions.
@ -131,9 +132,9 @@ class ExcludableMixin(RelationMixin):
@staticmethod @staticmethod
def _populate_pk_column( def _populate_pk_column(
model: Union[Type["Model"], Type["ModelRow"]], model: Union[Type["Model"], Type["ModelRow"]],
columns: List[str], columns: List[str],
use_alias: bool = False, use_alias: bool = False,
) -> List[str]: ) -> List[str]:
""" """
Adds primary key column/alias (depends on use_alias flag) to list of Adds primary key column/alias (depends on use_alias flag) to list of
@ -159,12 +160,13 @@ class ExcludableMixin(RelationMixin):
@classmethod @classmethod
def own_table_columns( def own_table_columns(
cls, cls,
model: Union[Type["Model"], Type["ModelRow"]], model: Union[Type["Model"], Type["ModelRow"]],
fields: Optional[Union[Set, Dict]], excludable: ExcludableItems,
exclude_fields: Optional[Union[Set, Dict]], alias: str = '',
use_alias: bool = False, use_alias: bool = False,
) -> List[str]: ) -> List[str]:
# TODO update docstring
""" """
Returns list of aliases or field names for given model. Returns list of aliases or field names for given model.
Aliases/names switch is use_alias flag. Aliases/names switch is use_alias flag.
@ -176,15 +178,12 @@ class ExcludableMixin(RelationMixin):
:param model: model on columns are selected :param model: model on columns are selected
:type model: Type["Model"] :type model: Type["Model"]
:param fields: set/dict of fields to include
:type fields: Optional[Union[Set, Dict]]
:param exclude_fields: set/dict of fields to exclude
:type exclude_fields: Optional[Union[Set, Dict]]
:param use_alias: flag if aliases or field names should be used :param use_alias: flag if aliases or field names should be used
:type use_alias: bool :type use_alias: bool
:return: list of column field names or aliases :return: list of column field names or aliases
:rtype: List[str] :rtype: List[str]
""" """
model_excludable = excludable.get(model_cls=model, alias=alias)
columns = [ columns = [
model.get_column_name_from_alias(col.name) if not use_alias else col.name model.get_column_name_from_alias(col.name) if not use_alias else col.name
for col in model.Meta.table.columns for col in model.Meta.table.columns
@ -193,17 +192,17 @@ class ExcludableMixin(RelationMixin):
model.get_column_name_from_alias(col.name) model.get_column_name_from_alias(col.name)
for col in model.Meta.table.columns for col in model.Meta.table.columns
] ]
if fields: if model_excludable.include:
columns = [ columns = [
col col
for col, name in zip(columns, field_names) for col, name in zip(columns, field_names)
if model.is_included(fields, name) if model_excludable.is_included(name)
] ]
if exclude_fields: if model_excludable.exclude:
columns = [ columns = [
col col
for col, name in zip(columns, field_names) for col, name in zip(columns, field_names)
if not model.is_excluded(exclude_fields, name) if not model_excludable.is_excluded(name)
] ]
# always has to return pk column for ormar to work # always has to return pk column for ormar to work
@ -215,9 +214,9 @@ class ExcludableMixin(RelationMixin):
@classmethod @classmethod
def _update_excluded_with_related_not_required( def _update_excluded_with_related_not_required(
cls, cls,
exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None], exclude: Union["AbstractSetIntStr", "MappingIntStrAny", None],
nested: bool = False, nested: bool = False,
) -> Union[Set, Dict]: ) -> Union[Set, Dict]:
""" """
Used during generation of the dict(). Used during generation of the dict().
@ -245,9 +244,9 @@ class ExcludableMixin(RelationMixin):
@classmethod @classmethod
def get_names_to_exclude( def get_names_to_exclude(
cls, cls,
fields: Optional[Union[Dict, Set]] = None, excludable: ExcludableItems,
exclude_fields: Optional[Union[Dict, Set]] = None, alias: str
) -> Set: ) -> Set:
""" """
Returns a set of models field names that should be explicitly excluded Returns a set of models field names that should be explicitly excluded
@ -259,33 +258,27 @@ class ExcludableMixin(RelationMixin):
Used in parsing data from database rows that construct Models by initializing Used in parsing data from database rows that construct Models by initializing
them with dicts constructed from those db rows. them with dicts constructed from those db rows.
:param fields: set/dict of fields to include :param alias: alias of current relation
:type fields: Optional[Union[Set, Dict]] :type alias: str
:param exclude_fields: set/dict of fields to exclude :param excludable: structure of fields to include and exclude
:type exclude_fields: Optional[Union[Set, Dict]] :type excludable: ExcludableItems
:return: set of field names that should be excluded :return: set of field names that should be excluded
:rtype: Set :rtype: Set
""" """
model = cast(Type["Model"], cls)
model_excludable = excludable.get(model_cls=model, alias=alias)
fields_names = cls.extract_db_own_fields() fields_names = cls.extract_db_own_fields()
if fields and fields is not Ellipsis: if model_excludable.include and model_excludable.include_all:
fields_to_keep = {name for name in fields if name in fields_names} fields_to_keep = model_excludable.include.intersection(fields_names)
else: else:
fields_to_keep = fields_names fields_to_keep = fields_names
fields_to_exclude = fields_names - fields_to_keep fields_to_exclude = fields_names - fields_to_keep
if isinstance(exclude_fields, Set): if model_excludable.exclude:
fields_to_exclude = fields_to_exclude.union( fields_to_exclude = fields_to_exclude.union(
{name for name in exclude_fields if name in fields_names} model_excludable.exclude.intersection(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} fields_to_exclude = fields_to_exclude - {cls.Meta.pkname}
return fields_to_exclude return fields_to_exclude

View File

@ -14,6 +14,7 @@ from typing import (
import sqlalchemy import sqlalchemy
from ormar.models import NewBaseModel # noqa: I202 from ormar.models import NewBaseModel # noqa: I202
from ormar.models.excludable import ExcludableItems
from ormar.models.helpers.models import group_related_list from ormar.models.helpers.models import group_related_list
@ -33,8 +34,7 @@ class ModelRow(NewBaseModel):
select_related: List = None, select_related: List = None,
related_models: Any = None, related_models: Any = None,
related_field: Type["ForeignKeyField"] = None, related_field: Type["ForeignKeyField"] = None,
fields: Optional[Union[Dict, Set]] = None, excludable: ExcludableItems = None,
exclude_fields: Optional[Union[Dict, Set]] = None,
current_relation_str: str = "", current_relation_str: str = "",
) -> Optional[T]: ) -> Optional[T]:
""" """
@ -50,6 +50,8 @@ class ModelRow(NewBaseModel):
where rows are populated in a different way as they do not have where rows are populated in a different way as they do not have
nested models in result. nested models in result.
:param excludable: structure of fields to include and exclude
:type excludable: ExcludableItems
:param current_relation_str: name of the relation field :param current_relation_str: name of the relation field
:type current_relation_str: str :type current_relation_str: str
:param source_model: model on which relation was defined :param source_model: model on which relation was defined
@ -62,12 +64,6 @@ class ModelRow(NewBaseModel):
: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: Type[ForeignKeyField]
:param fields: fields and related model fields to include
if provided only those are included
:type fields: Optional[Union[Dict, Set]]
:param exclude_fields: fields and related model fields to exclude
excludes the fields even if they are provided in fields
:type exclude_fields: Optional[Union[Dict, Set]]
:return: returns model if model is populated from database :return: returns model if model is populated from database
:rtype: Optional[Model] :rtype: Optional[Model]
""" """
@ -75,6 +71,7 @@ class ModelRow(NewBaseModel):
select_related = select_related or [] select_related = select_related or []
related_models = related_models or [] related_models = related_models or []
table_prefix = "" table_prefix = ""
excludable = excludable or ExcludableItems()
if select_related: if select_related:
source_model = cast(Type[T], cls) source_model = cast(Type[T], cls)
@ -87,12 +84,11 @@ class ModelRow(NewBaseModel):
relation_field=related_field, relation_field=related_field,
) )
item = cls.populate_nested_models_from_row( item = cls._populate_nested_models_from_row(
item=item, item=item,
row=row, row=row,
related_models=related_models, related_models=related_models,
fields=fields, excludable=excludable,
exclude_fields=exclude_fields,
current_relation_str=current_relation_str, current_relation_str=current_relation_str,
source_model=source_model, source_model=source_model,
) )
@ -100,28 +96,26 @@ class ModelRow(NewBaseModel):
item=item, item=item,
row=row, row=row,
table_prefix=table_prefix, table_prefix=table_prefix,
fields=fields, excludable=excludable
exclude_fields=exclude_fields,
) )
instance: Optional[T] = None instance: Optional[T] = None
if item.get(cls.Meta.pkname, None) is not None: if item.get(cls.Meta.pkname, None) is not None:
item["__excluded__"] = cls.get_names_to_exclude( item["__excluded__"] = cls.get_names_to_exclude(
fields=fields, exclude_fields=exclude_fields excludable=excludable, alias=table_prefix
) )
instance = cast(T, cls(**item)) instance = cast(T, cls(**item))
instance.set_save_status(True) instance.set_save_status(True)
return instance return instance
@classmethod @classmethod
def populate_nested_models_from_row( # noqa: CFQ002 def _populate_nested_models_from_row( # noqa: CFQ002
cls, cls,
item: dict, item: dict,
row: sqlalchemy.engine.ResultProxy, row: sqlalchemy.engine.ResultProxy,
source_model: Type[T], source_model: Type[T],
related_models: Any, related_models: Any,
fields: Optional[Union[Dict, Set]] = None, excludable: ExcludableItems,
exclude_fields: Optional[Union[Dict, Set]] = None,
current_relation_str: str = None, current_relation_str: str = None,
) -> dict: ) -> dict:
""" """
@ -134,6 +128,8 @@ class ModelRow(NewBaseModel):
Recurrently calls from_row method on nested instances and create nested Recurrently calls from_row method on nested instances and create nested
instances. In the end those instances are added to the final model dictionary. instances. In the end those instances are added to the final model dictionary.
:param excludable: structure of fields to include and exclude
:type excludable: ExcludableItems
:param source_model: source model from which relation started :param source_model: source model from which relation started
:type source_model: Type[Model] :type source_model: Type[Model]
:param current_relation_str: joined related parts into one string :param current_relation_str: joined related parts into one string
@ -144,12 +140,6 @@ class ModelRow(NewBaseModel):
:type row: sqlalchemy.engine.result.ResultProxy :type row: sqlalchemy.engine.result.ResultProxy
:param related_models: list or dict of related models :param related_models: list or dict of related models
:type related_models: Union[Dict, List] :type related_models: Union[Dict, List]
:param fields: fields and related model fields to include -
if provided only those are included
:type fields: Optional[Union[Dict, Set]]
:param exclude_fields: fields and related model fields to exclude
excludes the fields even if they are provided in fields
:type exclude_fields: Optional[Union[Dict, Set]]
:return: dictionary with keys corresponding to model fields names :return: dictionary with keys corresponding to model fields names
and values are database values and values are database values
:rtype: Dict :rtype: Dict
@ -163,8 +153,6 @@ class ModelRow(NewBaseModel):
) )
field = cls.Meta.model_fields[related] field = cls.Meta.model_fields[related]
field = cast(Type["ForeignKeyField"], field) field = cast(Type["ForeignKeyField"], field)
fields = cls.get_included(fields, related)
exclude_fields = cls.get_excluded(exclude_fields, related)
model_cls = field.to model_cls = field.to
remainder = None remainder = None
@ -174,8 +162,7 @@ class ModelRow(NewBaseModel):
row, row,
related_models=remainder, related_models=remainder,
related_field=field, related_field=field,
fields=fields, excludable=excludable,
exclude_fields=exclude_fields,
current_relation_str=relation_str, current_relation_str=relation_str,
source_model=source_model, source_model=source_model,
) )
@ -188,8 +175,7 @@ class ModelRow(NewBaseModel):
row=row, row=row,
related=related, related=related,
through_name=through_name, through_name=through_name,
fields=fields, excludable=excludable
exclude_fields=exclude_fields,
) )
item[through_name] = through_child item[through_name] = through_child
setattr(child, through_name, through_child) setattr(child, through_name, through_child)
@ -203,35 +189,29 @@ class ModelRow(NewBaseModel):
row: sqlalchemy.engine.ResultProxy, row: sqlalchemy.engine.ResultProxy,
through_name: str, through_name: str,
related: str, related: str,
fields: Optional[Union[Dict, Set]] = None, excludable: ExcludableItems
exclude_fields: Optional[Union[Dict, Set]] = None,
) -> Dict: ) -> Dict:
# TODO: fix excludes and includes # TODO: fix excludes and includes and docstring
fields = cls.get_included(fields, through_name)
# exclude_fields = cls.get_excluded(exclude_fields, through_name)
model_cls = cls.Meta.model_fields[through_name].to model_cls = cls.Meta.model_fields[through_name].to
exclude_fields = model_cls.extract_related_names()
table_prefix = cls.Meta.alias_manager.resolve_relation_alias( table_prefix = cls.Meta.alias_manager.resolve_relation_alias(
from_model=cls, relation_name=related from_model=cls, relation_name=related
) )
child = model_cls.extract_prefixed_table_columns( child = model_cls.extract_prefixed_table_columns(
item={}, item={},
row=row, row=row,
table_prefix=table_prefix, excludable=excludable,
fields=fields, table_prefix=table_prefix
exclude_fields=exclude_fields,
) )
return child return child
@classmethod @classmethod
def extract_prefixed_table_columns( # noqa CCR001 def extract_prefixed_table_columns(
cls, cls,
item: dict, item: dict,
row: sqlalchemy.engine.result.ResultProxy, row: sqlalchemy.engine.result.ResultProxy,
table_prefix: str, table_prefix: str,
fields: Optional[Union[Dict, Set]] = None, excludable: ExcludableItems
exclude_fields: Optional[Union[Dict, Set]] = None, ) -> Dict:
) -> dict:
""" """
Extracts own fields from raw sql result, using a given prefix. Extracts own fields from raw sql result, using a given prefix.
Prefix changes depending on the table's position in a join. Prefix changes depending on the table's position in a join.
@ -244,6 +224,8 @@ class ModelRow(NewBaseModel):
Used in Model.from_row and PrefetchQuery._populate_rows methods. Used in Model.from_row and PrefetchQuery._populate_rows methods.
:param excludable: structure of fields to include and exclude
:type excludable: ExcludableItems
:param item: dictionary of already populated nested models, otherwise empty dict :param item: dictionary of already populated nested models, otherwise empty dict
:type item: Dict :type item: Dict
:param row: raw result row from the database :param row: raw result row from the database
@ -252,12 +234,6 @@ class ModelRow(NewBaseModel):
each pair of tables have own prefix (two of them depending on direction) - each pair of tables have own prefix (two of them depending on direction) -
used in joins to allow multiple joins to the same table. used in joins to allow multiple joins to the same table.
:type table_prefix: str :type table_prefix: str
:param fields: fields and related model fields to include -
if provided only those are included
:type fields: Optional[Union[Dict, Set]]
:param exclude_fields: fields and related model fields to exclude
excludes the fields even if they are provided in fields
:type exclude_fields: Optional[Union[Dict, Set]]
:return: dictionary with keys corresponding to model fields names :return: dictionary with keys corresponding to model fields names
and values are database values and values are database values
:rtype: Dict :rtype: Dict
@ -267,8 +243,8 @@ class ModelRow(NewBaseModel):
selected_columns = cls.own_table_columns( selected_columns = cls.own_table_columns(
model=cls, model=cls,
fields=fields or {}, excludable=excludable,
exclude_fields=exclude_fields or {}, alias=table_prefix,
use_alias=False, use_alias=False,
) )

View File

@ -16,6 +16,7 @@ from sqlalchemy import text
import ormar # noqa I100 import ormar # noqa I100
from ormar.exceptions import RelationshipInstanceError from ormar.exceptions import RelationshipInstanceError
from ormar.models.excludable import ExcludableItems
from ormar.relations import AliasManager from ormar.relations import AliasManager
if TYPE_CHECKING: # pragma no cover if TYPE_CHECKING: # pragma no cover
@ -29,8 +30,7 @@ class SqlJoin:
used_aliases: List, used_aliases: List,
select_from: sqlalchemy.sql.select, select_from: sqlalchemy.sql.select,
columns: List[sqlalchemy.Column], columns: List[sqlalchemy.Column],
fields: Optional[Union[Set, Dict]], excludable: ExcludableItems,
exclude_fields: Optional[Union[Set, Dict]],
order_columns: Optional[List["OrderAction"]], order_columns: Optional[List["OrderAction"]],
sorted_orders: OrderedDict, sorted_orders: OrderedDict,
main_model: Type["Model"], main_model: Type["Model"],
@ -44,8 +44,7 @@ class SqlJoin:
self.related_models = related_models or [] self.related_models = related_models or []
self.select_from = select_from self.select_from = select_from
self.columns = columns self.columns = columns
self.fields = fields self.excludable=excludable
self.exclude_fields = exclude_fields
self.order_columns = order_columns self.order_columns = order_columns
self.sorted_orders = sorted_orders self.sorted_orders = sorted_orders
self.main_model = main_model self.main_model = main_model
@ -200,10 +199,7 @@ class SqlJoin:
used_aliases=self.used_aliases, used_aliases=self.used_aliases,
select_from=self.select_from, select_from=self.select_from,
columns=self.columns, columns=self.columns,
fields=self.main_model.get_excluded(self.fields, related_name), excludable=self.excludable,
exclude_fields=self.main_model.get_excluded(
self.exclude_fields, related_name
),
order_columns=self.order_columns, order_columns=self.order_columns,
sorted_orders=self.sorted_orders, sorted_orders=self.sorted_orders,
main_model=self.next_model, main_model=self.next_model,
@ -303,8 +299,8 @@ class SqlJoin:
# TODO: fix fields and exclusions for through model? # TODO: fix fields and exclusions for through model?
self_related_fields = self.next_model.own_table_columns( self_related_fields = self.next_model.own_table_columns(
model=self.next_model, model=self.next_model,
fields=self.fields, excludable=self.excludable,
exclude_fields=self.exclude_fields, alias=self.next_alias,
use_alias=True, use_alias=True,
) )
self.columns.extend( self.columns.extend(

View File

@ -13,6 +13,7 @@ from typing import (
) )
import ormar import ormar
from ormar.models.excludable import ExcludableItems
from ormar.queryset.clause import QueryClause from ormar.queryset.clause import QueryClause
from ormar.queryset.query import Query from ormar.queryset.query import Query
from ormar.queryset.utils import extract_models_to_dict_of_lists, translate_list_to_dict from ormar.queryset.utils import extract_models_to_dict_of_lists, translate_list_to_dict
@ -24,7 +25,7 @@ if TYPE_CHECKING: # pragma: no cover
def add_relation_field_to_fields( def add_relation_field_to_fields(
fields: Union[Set[Any], Dict[Any, Any], None], related_field_name: str fields: Union[Set[Any], Dict[Any, Any], None], related_field_name: str
) -> Union[Set[Any], Dict[Any, Any], None]: ) -> Union[Set[Any], Dict[Any, Any], None]:
""" """
Adds related field into fields to include as otherwise it would be skipped. Adds related field into fields to include as otherwise it would be skipped.
@ -73,12 +74,12 @@ def sort_models(models: List["Model"], orders_by: Dict) -> List["Model"]:
def set_children_on_model( # noqa: CCR001 def set_children_on_model( # noqa: CCR001
model: "Model", model: "Model",
related: str, related: str,
children: Dict, children: Dict,
model_id: int, model_id: int,
models: Dict, models: Dict,
orders_by: Dict, orders_by: Dict,
) -> None: ) -> None:
""" """
Extract ids of child models by given relation id key value. Extract ids of child models by given relation id key value.
@ -123,21 +124,19 @@ class PrefetchQuery:
""" """
def __init__( # noqa: CFQ002 def __init__( # noqa: CFQ002
self, self,
model_cls: Type["Model"], model_cls: Type["Model"],
fields: Optional[Union[Dict, Set]], excludable: ExcludableItems,
exclude_fields: Optional[Union[Dict, Set]], prefetch_related: List,
prefetch_related: List, select_related: List,
select_related: List, orders_by: List["OrderAction"],
orders_by: List["OrderAction"],
) -> None: ) -> None:
self.model = model_cls self.model = model_cls
self.database = self.model.Meta.database self.database = self.model.Meta.database
self._prefetch_related = prefetch_related self._prefetch_related = prefetch_related
self._select_related = select_related self._select_related = select_related
self._exclude_columns = exclude_fields self.excludable = excludable
self._columns = fields
self.already_extracted: Dict = dict() self.already_extracted: Dict = dict()
self.models: Dict = {} self.models: Dict = {}
self.select_dict = translate_list_to_dict(self._select_related) self.select_dict = translate_list_to_dict(self._select_related)
@ -148,7 +147,7 @@ class PrefetchQuery:
) )
async def prefetch_related( async def prefetch_related(
self, models: Sequence["Model"], rows: List self, models: Sequence["Model"], rows: List
) -> Sequence["Model"]: ) -> Sequence["Model"]:
""" """
Main entry point for prefetch_query. Main entry point for prefetch_query.
@ -173,7 +172,7 @@ class PrefetchQuery:
return await self._prefetch_related_models(models=models, rows=rows) return await self._prefetch_related_models(models=models, rows=rows)
def _extract_ids_from_raw_data( def _extract_ids_from_raw_data(
self, parent_model: Type["Model"], column_name: str self, parent_model: Type["Model"], column_name: str
) -> Set: ) -> Set:
""" """
Iterates over raw rows and extract id values of relation columns by using Iterates over raw rows and extract id values of relation columns by using
@ -196,7 +195,7 @@ class PrefetchQuery:
return list_of_ids return list_of_ids
def _extract_ids_from_preloaded_models( def _extract_ids_from_preloaded_models(
self, parent_model: Type["Model"], column_name: str self, parent_model: Type["Model"], column_name: str
) -> Set: ) -> Set:
""" """
Extracts relation ids from already populated models if they were included Extracts relation ids from already populated models if they were included
@ -219,7 +218,7 @@ class PrefetchQuery:
return list_of_ids return list_of_ids
def _extract_required_ids( def _extract_required_ids(
self, parent_model: Type["Model"], reverse: bool, related: str, self, parent_model: Type["Model"], reverse: bool, related: str,
) -> Set: ) -> Set:
""" """
Delegates extraction of the fields to either get ids from raw sql response Delegates extraction of the fields to either get ids from raw sql response
@ -253,11 +252,11 @@ class PrefetchQuery:
) )
def _get_filter_for_prefetch( def _get_filter_for_prefetch(
self, self,
parent_model: Type["Model"], parent_model: Type["Model"],
target_model: Type["Model"], target_model: Type["Model"],
reverse: bool, reverse: bool,
related: str, related: str,
) -> List: ) -> List:
""" """
Populates where clause with condition to return only models within the Populates where clause with condition to return only models within the
@ -298,7 +297,7 @@ class PrefetchQuery:
return [] return []
def _populate_nested_related( def _populate_nested_related(
self, model: "Model", prefetch_dict: Dict, orders_by: Dict, self, model: "Model", prefetch_dict: Dict, orders_by: Dict,
) -> "Model": ) -> "Model":
""" """
Populates all related models children of parent model that are Populates all related models children of parent model that are
@ -342,7 +341,7 @@ class PrefetchQuery:
return model return model
async def _prefetch_related_models( async def _prefetch_related_models(
self, models: Sequence["Model"], rows: List self, models: Sequence["Model"], rows: List
) -> Sequence["Model"]: ) -> Sequence["Model"]:
""" """
Main method of the query. Main method of the query.
@ -366,8 +365,6 @@ class PrefetchQuery:
select_dict = translate_list_to_dict(self._select_related) select_dict = translate_list_to_dict(self._select_related)
prefetch_dict = translate_list_to_dict(self._prefetch_related) prefetch_dict = translate_list_to_dict(self._prefetch_related)
target_model = self.model target_model = self.model
fields = self._columns
exclude_fields = self._exclude_columns
orders_by = self.order_dict orders_by = self.order_dict
for related in prefetch_dict.keys(): for related in prefetch_dict.keys():
await self._extract_related_models( await self._extract_related_models(
@ -375,8 +372,7 @@ class PrefetchQuery:
target_model=target_model, target_model=target_model,
prefetch_dict=prefetch_dict.get(related, {}), prefetch_dict=prefetch_dict.get(related, {}),
select_dict=select_dict.get(related, {}), select_dict=select_dict.get(related, {}),
fields=fields, excludable=self.excludable,
exclude_fields=exclude_fields,
orders_by=orders_by.get(related, {}), orders_by=orders_by.get(related, {}),
) )
final_models = [] final_models = []
@ -389,14 +385,13 @@ class PrefetchQuery:
return models return models
async def _extract_related_models( # noqa: CFQ002, CCR001 async def _extract_related_models( # noqa: CFQ002, CCR001
self, self,
related: str, related: str,
target_model: Type["Model"], target_model: Type["Model"],
prefetch_dict: Dict, prefetch_dict: Dict,
select_dict: Dict, select_dict: Dict,
fields: Union[Set[Any], Dict[Any, Any], None], excludable: ExcludableItems,
exclude_fields: Union[Set[Any], Dict[Any, Any], None], orders_by: Dict,
orders_by: Dict,
) -> None: ) -> None:
""" """
Constructs queries with required ids and extracts data with fields that should Constructs queries with required ids and extracts data with fields that should
@ -424,8 +419,6 @@ class PrefetchQuery:
:return: None :return: None
:rtype: None :rtype: None
""" """
fields = target_model.get_included(fields, related)
exclude_fields = target_model.get_excluded(exclude_fields, related)
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(Type["ForeignKeyField"], target_field)
reverse = False reverse = False
@ -450,14 +443,11 @@ class PrefetchQuery:
related_field_name = parent_model.get_related_field_name( related_field_name = parent_model.get_related_field_name(
target_field=target_field target_field=target_field
) )
fields = add_relation_field_to_fields(
fields=fields, related_field_name=related_field_name
)
table_prefix, rows = await self._run_prefetch_query( table_prefix, rows = await self._run_prefetch_query(
target_field=target_field, target_field=target_field,
fields=fields, excludable=excludable,
exclude_fields=exclude_fields,
filter_clauses=filter_clauses, filter_clauses=filter_clauses,
related_field_name=related_field_name
) )
else: else:
rows = [] rows = []
@ -472,8 +462,7 @@ class PrefetchQuery:
select_dict=self._get_select_related_if_apply( select_dict=self._get_select_related_if_apply(
subrelated, select_dict subrelated, select_dict
), ),
fields=fields, excludable=excludable,
exclude_fields=exclude_fields,
orders_by=self._get_select_related_if_apply(subrelated, orders_by), orders_by=self._get_select_related_if_apply(subrelated, orders_by),
) )
@ -483,8 +472,7 @@ class PrefetchQuery:
parent_model=parent_model, parent_model=parent_model,
target_field=target_field, target_field=target_field,
table_prefix=table_prefix, table_prefix=table_prefix,
fields=fields, excludable=excludable,
exclude_fields=exclude_fields,
prefetch_dict=prefetch_dict, prefetch_dict=prefetch_dict,
orders_by=orders_by, orders_by=orders_by,
) )
@ -496,11 +484,11 @@ class PrefetchQuery:
) )
async def _run_prefetch_query( async def _run_prefetch_query(
self, self,
target_field: Type["BaseField"], target_field: Type["BaseField"],
fields: Union[Set[Any], Dict[Any, Any], None], excludable: ExcludableItems,
exclude_fields: Union[Set[Any], Dict[Any, Any], None], filter_clauses: List,
filter_clauses: List, related_field_name: str
) -> Tuple[str, List]: ) -> Tuple[str, List]:
""" """
Actually runs the queries against the database and populates the raw response Actually runs the queries against the database and populates the raw response
@ -511,10 +499,6 @@ class PrefetchQuery:
:param target_field: ormar field with relation definition :param target_field: ormar field with relation definition
:type target_field: Type["BaseField"] :type target_field: Type["BaseField"]
:param fields: fields to include
:type fields: Union[Set[Any], Dict[Any, Any], None]
:param exclude_fields: fields to exclude
:type exclude_fields: Union[Set[Any], Dict[Any, Any], None]
: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
@ -533,6 +517,11 @@ class PrefetchQuery:
) )
self.already_extracted.setdefault(target_name, {})["prefix"] = table_prefix self.already_extracted.setdefault(target_name, {})["prefix"] = table_prefix
model_excludable = excludable.get(model_cls=target_model, alias=table_prefix)
if model_excludable.include and not model_excludable.is_included(
related_field_name):
model_excludable.set_values({related_field_name}, is_exclude=False)
qry = Query( qry = Query(
model_cls=query_target, model_cls=query_target,
select_related=select_related, select_related=select_related,
@ -540,8 +529,7 @@ class PrefetchQuery:
exclude_clauses=[], exclude_clauses=[],
offset=None, offset=None,
limit_count=None, limit_count=None,
fields=fields, excludable=excludable,
exclude_fields=exclude_fields,
order_bys=None, order_bys=None,
limit_raw_sql=False, limit_raw_sql=False,
) )
@ -571,7 +559,7 @@ 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: Type["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.
@ -590,15 +578,14 @@ class PrefetchQuery:
) )
def _populate_rows( # noqa: CFQ002 def _populate_rows( # noqa: CFQ002
self, self,
rows: List, rows: List,
target_field: Type["ForeignKeyField"], target_field: Type["ForeignKeyField"],
parent_model: Type["Model"], parent_model: Type["Model"],
table_prefix: str, table_prefix: str,
fields: Union[Set[Any], Dict[Any, Any], None], excludable: ExcludableItems,
exclude_fields: Union[Set[Any], Dict[Any, Any], None], prefetch_dict: Dict,
prefetch_dict: Dict, orders_by: Dict,
orders_by: Dict,
) -> None: ) -> None:
""" """
Instantiates children models extracted from given relation. Instantiates children models extracted from given relation.
@ -610,6 +597,8 @@ class PrefetchQuery:
already_extracted dictionary. Later those instances will be fetched by ids already_extracted dictionary. Later those instances will be fetched by ids
and set on the parent model after sorting if needed. and set on the parent model after sorting if needed.
:param excludable: structure of fields to include and exclude
:type excludable: ExcludableItems
: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
@ -618,10 +607,6 @@ class PrefetchQuery:
: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
:type table_prefix: str :type table_prefix: str
:param fields: fields to include
:type fields: Union[Set[Any], Dict[Any, Any], None]
:param exclude_fields: fields to exclude
:type exclude_fields: Union[Set[Any], Dict[Any, Any], None]
: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
@ -629,16 +614,16 @@ class PrefetchQuery:
""" """
target_model = target_field.to target_model = target_field.to
for row in rows: for row in rows:
# TODO Fix fields
field_name = parent_model.get_related_field_name(target_field=target_field) field_name = parent_model.get_related_field_name(target_field=target_field)
item = target_model.extract_prefixed_table_columns( item = target_model.extract_prefixed_table_columns(
item={}, item={},
row=row, row=row,
table_prefix=table_prefix, table_prefix=table_prefix,
fields=fields, excludable=excludable,
exclude_fields=exclude_fields,
) )
item["__excluded__"] = target_model.get_names_to_exclude( item["__excluded__"] = target_model.get_names_to_exclude(
fields=fields, exclude_fields=exclude_fields excludable=excludable, alias=table_prefix
) )
instance = target_model(**item) instance = target_model(**item)
instance = self._populate_nested_related( instance = self._populate_nested_related(

View File

@ -6,6 +6,7 @@ import sqlalchemy
from sqlalchemy import text from sqlalchemy import text
import ormar # noqa I100 import ormar # noqa I100
from ormar.models.excludable import ExcludableItems
from ormar.models.helpers.models import group_related_list from ormar.models.helpers.models import group_related_list
from ormar.queryset import FilterQuery, LimitQuery, OffsetQuery, OrderQuery from ormar.queryset import FilterQuery, LimitQuery, OffsetQuery, OrderQuery
from ormar.queryset.actions.filter_action import FilterAction from ormar.queryset.actions.filter_action import FilterAction
@ -18,25 +19,23 @@ if TYPE_CHECKING: # pragma no cover
class Query: class Query:
def __init__( # noqa CFQ002 def __init__( # noqa CFQ002
self, self,
model_cls: Type["Model"], model_cls: Type["Model"],
filter_clauses: List[FilterAction], filter_clauses: List[FilterAction],
exclude_clauses: List[FilterAction], exclude_clauses: List[FilterAction],
select_related: List, select_related: List,
limit_count: Optional[int], limit_count: Optional[int],
offset: Optional[int], offset: Optional[int],
fields: Optional[Union[Dict, Set]], excludable: ExcludableItems,
exclude_fields: Optional[Union[Dict, Set]], order_bys: Optional[List["OrderAction"]],
order_bys: Optional[List["OrderAction"]], limit_raw_sql: bool,
limit_raw_sql: bool,
) -> None: ) -> None:
self.query_offset = offset self.query_offset = offset
self.limit_count = limit_count self.limit_count = limit_count
self._select_related = select_related[:] self._select_related = select_related[:]
self.filter_clauses = filter_clauses[:] self.filter_clauses = filter_clauses[:]
self.exclude_clauses = exclude_clauses[:] self.exclude_clauses = exclude_clauses[:]
self.fields = copy.deepcopy(fields) if fields else {} self.excludable = excludable
self.exclude_fields = copy.deepcopy(exclude_fields) if exclude_fields else {}
self.model_cls = model_cls self.model_cls = model_cls
self.table = self.model_cls.Meta.table self.table = self.model_cls.Meta.table
@ -105,8 +104,7 @@ class Query:
""" """
self_related_fields = self.model_cls.own_table_columns( self_related_fields = self.model_cls.own_table_columns(
model=self.model_cls, model=self.model_cls,
fields=self.fields, excludable=self.excludable,
exclude_fields=self.exclude_fields,
use_alias=True, use_alias=True,
) )
self.columns = self.model_cls.Meta.alias_manager.prefixed_columns( self.columns = self.model_cls.Meta.alias_manager.prefixed_columns(
@ -121,8 +119,6 @@ class Query:
related_models = group_related_list(self._select_related) related_models = group_related_list(self._select_related)
for related in related_models: for related in related_models:
fields = self.model_cls.get_included(self.fields, related)
exclude_fields = self.model_cls.get_excluded(self.exclude_fields, related)
remainder = None remainder = None
if isinstance(related_models, dict) and related_models[related]: if isinstance(related_models, dict) and related_models[related]:
remainder = related_models[related] remainder = related_models[related]
@ -130,8 +126,7 @@ class Query:
used_aliases=self.used_aliases, used_aliases=self.used_aliases,
select_from=self.select_from, select_from=self.select_from,
columns=self.columns, columns=self.columns,
fields=fields, excludable=self.excludable,
exclude_fields=exclude_fields,
order_columns=self.order_columns, order_columns=self.order_columns,
sorted_orders=self.sorted_orders, sorted_orders=self.sorted_orders,
main_model=self.model_cls, main_model=self.model_cls,
@ -196,7 +191,7 @@ class Query:
return expr return expr
def _apply_expression_modifiers( def _apply_expression_modifiers(
self, expr: sqlalchemy.sql.select self, expr: sqlalchemy.sql.select
) -> sqlalchemy.sql.select: ) -> sqlalchemy.sql.select:
""" """
Receives the select query (might be join) and applies: Receives the select query (might be join) and applies:
@ -231,5 +226,3 @@ class Query:
self.select_from = [] self.select_from = []
self.columns = [] self.columns = []
self.used_aliases = [] self.used_aliases = []
self.fields = {}
self.exclude_fields = {}

View File

@ -20,6 +20,7 @@ from sqlalchemy import bindparam
import ormar # noqa I100 import ormar # noqa I100
from ormar import MultipleMatches, NoMatch from ormar import MultipleMatches, NoMatch
from ormar.exceptions import ModelError, ModelPersistenceError, QueryDefinitionError from ormar.exceptions import ModelError, ModelPersistenceError, QueryDefinitionError
from ormar.models.excludable import ExcludableItems
from ormar.queryset import FilterQuery from ormar.queryset import FilterQuery
from ormar.queryset.actions.order_action import OrderAction from ormar.queryset.actions.order_action import OrderAction
from ormar.queryset.clause import QueryClause from ormar.queryset.clause import QueryClause
@ -41,18 +42,17 @@ class QuerySet(Generic[T]):
""" """
def __init__( # noqa CFQ002 def __init__( # noqa CFQ002
self, self,
model_cls: Optional[Type[T]] = None, model_cls: Optional[Type[T]] = None,
filter_clauses: List = None, filter_clauses: List = None,
exclude_clauses: List = None, exclude_clauses: List = None,
select_related: List = None, select_related: List = None,
limit_count: int = None, limit_count: int = None,
offset: int = None, offset: int = None,
columns: Dict = None, excludable: ExcludableItems = None,
exclude_columns: Dict = None, order_bys: List = None,
order_bys: List = None, prefetch_related: List = None,
prefetch_related: List = None, limit_raw_sql: bool = False,
limit_raw_sql: bool = False,
) -> None: ) -> None:
self.model_cls = model_cls self.model_cls = model_cls
self.filter_clauses = [] if filter_clauses is None else filter_clauses self.filter_clauses = [] if filter_clauses is None else filter_clauses
@ -61,15 +61,14 @@ class QuerySet(Generic[T]):
self._prefetch_related = [] if prefetch_related is None else prefetch_related self._prefetch_related = [] if prefetch_related is None else prefetch_related
self.limit_count = limit_count self.limit_count = limit_count
self.query_offset = offset self.query_offset = offset
self._columns = columns or {} self._excludable = excludable or ExcludableItems()
self._exclude_columns = exclude_columns or {}
self.order_bys = order_bys or [] self.order_bys = order_bys or []
self.limit_sql_raw = limit_raw_sql self.limit_sql_raw = limit_raw_sql
def __get__( def __get__(
self, self,
instance: Optional[Union["QuerySet", "QuerysetProxy"]], instance: Optional[Union["QuerySet", "QuerysetProxy"]],
owner: Union[Type[T], Type["QuerysetProxy"]], owner: Union[Type[T], Type["QuerysetProxy"]],
) -> "QuerySet": ) -> "QuerySet":
if issubclass(owner, ormar.Model): if issubclass(owner, ormar.Model):
if owner.Meta.requires_ref_update: if owner.Meta.requires_ref_update:
@ -107,7 +106,7 @@ class QuerySet(Generic[T]):
return self.model_cls return self.model_cls
async def _prefetch_related_models( async def _prefetch_related_models(
self, models: Sequence[Optional["T"]], rows: List self, models: Sequence[Optional["T"]], rows: List
) -> Sequence[Optional["T"]]: ) -> Sequence[Optional["T"]]:
""" """
Performs prefetch query for selected models names. Performs prefetch query for selected models names.
@ -121,8 +120,7 @@ class QuerySet(Generic[T]):
""" """
query = PrefetchQuery( query = PrefetchQuery(
model_cls=self.model, model_cls=self.model,
fields=self._columns, excludable=self._excludable,
exclude_fields=self._exclude_columns,
prefetch_related=self._prefetch_related, prefetch_related=self._prefetch_related,
select_related=self._select_related, select_related=self._select_related,
orders_by=self.order_bys, orders_by=self.order_bys,
@ -142,8 +140,7 @@ class QuerySet(Generic[T]):
self.model.from_row( self.model.from_row(
row=row, row=row,
select_related=self._select_related, select_related=self._select_related,
fields=self._columns, excludable=self._excludable,
exclude_fields=self._exclude_columns,
source_model=self.model, source_model=self.model,
) )
for row in rows for row in rows
@ -186,7 +183,7 @@ class QuerySet(Generic[T]):
return self.model_meta.table return self.model_meta.table
def build_select_expression( def build_select_expression(
self, limit: int = None, offset: int = None, order_bys: List = None, self, limit: int = None, offset: int = None, order_bys: List = None,
) -> sqlalchemy.sql.select: ) -> sqlalchemy.sql.select:
""" """
Constructs the actual database query used in the QuerySet. Constructs the actual database query used in the QuerySet.
@ -208,8 +205,7 @@ class QuerySet(Generic[T]):
exclude_clauses=self.exclude_clauses, exclude_clauses=self.exclude_clauses,
offset=offset or self.query_offset, offset=offset or self.query_offset,
limit_count=limit or self.limit_count, limit_count=limit or self.limit_count,
fields=self._columns, excludable=self._excludable,
exclude_fields=self._exclude_columns,
order_bys=order_bys or self.order_bys, order_bys=order_bys or self.order_bys,
limit_raw_sql=self.limit_sql_raw, limit_raw_sql=self.limit_sql_raw,
) )
@ -265,8 +261,7 @@ class QuerySet(Generic[T]):
select_related=select_related, select_related=select_related,
limit_count=self.limit_count, limit_count=self.limit_count,
offset=self.query_offset, offset=self.query_offset,
columns=self._columns, excludable=self._excludable,
exclude_columns=self._exclude_columns,
order_bys=self.order_bys, order_bys=self.order_bys,
prefetch_related=self._prefetch_related, prefetch_related=self._prefetch_related,
limit_raw_sql=self.limit_sql_raw, limit_raw_sql=self.limit_sql_raw,
@ -321,8 +316,7 @@ class QuerySet(Generic[T]):
select_related=related, select_related=related,
limit_count=self.limit_count, limit_count=self.limit_count,
offset=self.query_offset, offset=self.query_offset,
columns=self._columns, excludable=self._excludable,
exclude_columns=self._exclude_columns,
order_bys=self.order_bys, order_bys=self.order_bys,
prefetch_related=self._prefetch_related, prefetch_related=self._prefetch_related,
limit_raw_sql=self.limit_sql_raw, limit_raw_sql=self.limit_sql_raw,
@ -357,14 +351,14 @@ class QuerySet(Generic[T]):
select_related=self._select_related, select_related=self._select_related,
limit_count=self.limit_count, limit_count=self.limit_count,
offset=self.query_offset, offset=self.query_offset,
columns=self._columns, excludable=self._excludable,
exclude_columns=self._exclude_columns,
order_bys=self.order_bys, order_bys=self.order_bys,
prefetch_related=related, prefetch_related=related,
limit_raw_sql=self.limit_sql_raw, limit_raw_sql=self.limit_sql_raw,
) )
def fields(self, columns: Union[List, str, Set, Dict]) -> "QuerySet": def fields(self, columns: Union[List, str, Set, Dict],
_is_exclude: bool = False) -> "QuerySet":
""" """
With `fields()` you can select subset of model columns to limit the data load. With `fields()` you can select subset of model columns to limit the data load.
@ -407,15 +401,10 @@ class QuerySet(Generic[T]):
:return: QuerySet :return: QuerySet
:rtype: QuerySet :rtype: QuerySet
""" """
if isinstance(columns, str): excludable = ExcludableItems.from_excludable(self._excludable)
columns = [columns] excludable.build(items=columns,
model_cls=self.model_cls,
# TODO: Flatten all excludes into one dict-like structure with alias + model key is_exclude=_is_exclude)
current_included = self._columns
if not isinstance(columns, dict):
current_included = update_dict_from_list(current_included, columns)
else:
current_included = update(current_included, columns)
return self.__class__( return self.__class__(
model_cls=self.model, model_cls=self.model,
@ -424,8 +413,7 @@ class QuerySet(Generic[T]):
select_related=self._select_related, select_related=self._select_related,
limit_count=self.limit_count, limit_count=self.limit_count,
offset=self.query_offset, offset=self.query_offset,
columns=current_included, excludable=excludable,
exclude_columns=self._exclude_columns,
order_bys=self.order_bys, order_bys=self.order_bys,
prefetch_related=self._prefetch_related, prefetch_related=self._prefetch_related,
limit_raw_sql=self.limit_sql_raw, limit_raw_sql=self.limit_sql_raw,
@ -458,28 +446,7 @@ class QuerySet(Generic[T]):
:return: QuerySet :return: QuerySet
:rtype: QuerySet :rtype: QuerySet
""" """
if isinstance(columns, str): return self.fields(columns=columns, _is_exclude=True)
columns = [columns]
current_excluded = self._exclude_columns
if not isinstance(columns, dict):
current_excluded = update_dict_from_list(current_excluded, columns)
else:
current_excluded = update(current_excluded, columns)
return self.__class__(
model_cls=self.model,
filter_clauses=self.filter_clauses,
exclude_clauses=self.exclude_clauses,
select_related=self._select_related,
limit_count=self.limit_count,
offset=self.query_offset,
columns=self._columns,
exclude_columns=current_excluded,
order_bys=self.order_bys,
prefetch_related=self._prefetch_related,
limit_raw_sql=self.limit_sql_raw,
)
def order_by(self, columns: Union[List, str]) -> "QuerySet": def order_by(self, columns: Union[List, str]) -> "QuerySet":
""" """
@ -529,8 +496,7 @@ class QuerySet(Generic[T]):
select_related=self._select_related, select_related=self._select_related,
limit_count=self.limit_count, limit_count=self.limit_count,
offset=self.query_offset, offset=self.query_offset,
columns=self._columns, excludable=self._excludable,
exclude_columns=self._exclude_columns,
order_bys=order_bys, order_bys=order_bys,
prefetch_related=self._prefetch_related, prefetch_related=self._prefetch_related,
limit_raw_sql=self.limit_sql_raw, limit_raw_sql=self.limit_sql_raw,
@ -642,8 +608,7 @@ class QuerySet(Generic[T]):
select_related=self._select_related, select_related=self._select_related,
limit_count=limit_count, limit_count=limit_count,
offset=query_offset, offset=query_offset,
columns=self._columns, excludable=self._excludable,
exclude_columns=self._exclude_columns,
order_bys=self.order_bys, order_bys=self.order_bys,
prefetch_related=self._prefetch_related, prefetch_related=self._prefetch_related,
limit_raw_sql=self.limit_sql_raw, limit_raw_sql=self.limit_sql_raw,
@ -671,8 +636,7 @@ class QuerySet(Generic[T]):
select_related=self._select_related, select_related=self._select_related,
limit_count=limit_count, limit_count=limit_count,
offset=self.query_offset, offset=self.query_offset,
columns=self._columns, excludable=self._excludable,
exclude_columns=self._exclude_columns,
order_bys=self.order_bys, order_bys=self.order_bys,
prefetch_related=self._prefetch_related, prefetch_related=self._prefetch_related,
limit_raw_sql=limit_raw_sql, limit_raw_sql=limit_raw_sql,
@ -700,8 +664,7 @@ class QuerySet(Generic[T]):
select_related=self._select_related, select_related=self._select_related,
limit_count=self.limit_count, limit_count=self.limit_count,
offset=offset, offset=offset,
columns=self._columns, excludable=self._excludable,
exclude_columns=self._exclude_columns,
order_bys=self.order_bys, order_bys=self.order_bys,
prefetch_related=self._prefetch_related, prefetch_related=self._prefetch_related,
limit_raw_sql=limit_raw_sql, limit_raw_sql=limit_raw_sql,
@ -724,12 +687,12 @@ class QuerySet(Generic[T]):
expr = self.build_select_expression( expr = self.build_select_expression(
limit=1, limit=1,
order_bys=[ order_bys=[
OrderAction( OrderAction(
order_str=f"{self.model.Meta.pkname}", order_str=f"{self.model.Meta.pkname}",
model_cls=self.model_cls, # type: ignore model_cls=self.model_cls, # type: ignore
) )
] ]
+ self.order_bys, + self.order_bys,
) )
rows = await self.database.fetch_all(expr) rows = await self.database.fetch_all(expr)
processed_rows = self._process_query_result_rows(rows) processed_rows = self._process_query_result_rows(rows)
@ -760,12 +723,12 @@ class QuerySet(Generic[T]):
expr = self.build_select_expression( expr = self.build_select_expression(
limit=1, limit=1,
order_bys=[ order_bys=[
OrderAction( OrderAction(
order_str=f"-{self.model.Meta.pkname}", order_str=f"-{self.model.Meta.pkname}",
model_cls=self.model_cls, # type: ignore model_cls=self.model_cls, # type: ignore
) )
] ]
+ self.order_bys, + self.order_bys,
) )
else: else:
expr = self.build_select_expression() expr = self.build_select_expression()
@ -868,9 +831,9 @@ class QuerySet(Generic[T]):
# refresh server side defaults # refresh server side defaults
if any( if any(
field.server_default is not None field.server_default is not None
for name, field in self.model.Meta.model_fields.items() for name, field in self.model.Meta.model_fields.items()
if name not in kwargs if name not in kwargs
): ):
instance = await instance.load() instance = await instance.load()
instance.set_save_status(True) instance.set_save_status(True)
@ -905,7 +868,7 @@ class QuerySet(Generic[T]):
objt.set_save_status(True) objt.set_save_status(True)
async def bulk_update( # noqa: CCR001 async def bulk_update( # noqa: CCR001
self, objects: List[T], columns: List[str] = None self, objects: List[T], columns: List[str] = None
) -> None: ) -> None:
""" """
Performs bulk update in one database session to speed up the process. Performs bulk update in one database session to speed up the process.