From 9ad1528cc0ce233e70607f349731a80a4507eb8e Mon Sep 17 00:00:00 2001 From: collerek Date: Tue, 2 Mar 2021 19:10:59 +0100 Subject: [PATCH] add docstrings, clean types test --- ormar/fields/foreign_key.py | 2 +- ormar/models/__init__.py | 4 +- ormar/models/excludable.py | 97 ++++++++++++++++++++++- ormar/models/model.py | 17 ++-- ormar/models/model_row.py | 43 ++++++---- ormar/models/newbasemodel.py | 11 +-- ormar/queryset/__init__.py | 3 +- ormar/queryset/prefetch_query.py | 1 - ormar/queryset/queryset.py | 44 +++++----- ormar/relations/querysetproxy.py | 39 +++++---- ormar/relations/relation.py | 8 +- ormar/relations/relation_manager.py | 4 +- tests/test_excluding_fields_in_fastapi.py | 15 ++-- 13 files changed, 190 insertions(+), 98 deletions(-) diff --git a/ormar/fields/foreign_key.py b/ormar/fields/foreign_key.py index 8dda14f..643b88d 100644 --- a/ormar/fields/foreign_key.py +++ b/ormar/fields/foreign_key.py @@ -73,7 +73,7 @@ def create_dummy_model( "".join(choices(string.ascii_uppercase, k=2)) + uuid.uuid4().hex[:4] ).lower() fields = {f"{pk_field.name}": (pk_field.__type__, None)} - dummy_model = create_model( + dummy_model = create_model( # type: ignore f"PkOnly{base_model.get_name(lower=False)}{alias}", __module__=base_model.__module__, **fields, # type: ignore diff --git a/ormar/models/__init__.py b/ormar/models/__init__.py index 0ecd9cc..eb6bdd7 100644 --- a/ormar/models/__init__.py +++ b/ormar/models/__init__.py @@ -6,7 +6,7 @@ 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, T # noqa I100 +from ormar.models.model import Model # noqa I100 from ormar.models.excludable import ExcludableItems # noqa I100 -__all__ = ["T", "NewBaseModel", "Model", "ModelRow", "ExcludableItems"] +__all__ = ["NewBaseModel", "Model", "ModelRow", "ExcludableItems"] diff --git a/ormar/models/excludable.py b/ormar/models/excludable.py index 203d8a0..9b888b0 100644 --- a/ormar/models/excludable.py +++ b/ormar/models/excludable.py @@ -7,28 +7,58 @@ if TYPE_CHECKING: # pragma: no cover from ormar import Model -# TODO: Add docstrings @dataclass class Excludable: + """ + Class that keeps sets of fields to exclude and include + """ + include: Set = field(default_factory=set) exclude: Set = field(default_factory=set) def get_copy(self) -> "Excludable": + """ + Return copy of self to avoid in place modifications + :return: copy of self with copied sets + :rtype: ormar.models.excludable.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: + """ + Appends the data to include/exclude sets. + + :param value: set of values to add + :type value: set + :param is_exclude: flag if values are to be excluded or included + :type is_exclude: bool + """ prop = "exclude" if is_exclude else "include" current_value = getattr(self, prop) current_value.update(value) setattr(self, prop, current_value) def is_included(self, key: str) -> bool: + """ + Check if field in included (in set or set is {...}) + :param key: key to check + :type key: str + :return: result of the check + :rtype: bool + """ return (... in self.include or key in self.include) if self.include else True def is_excluded(self, key: str) -> bool: + """ + Check if field in excluded (in set or set is {...}) + :param key: key to check + :type key: str + :return: result of the check + :rtype: bool + """ return (... in self.exclude or key in self.exclude) if self.exclude else False @@ -44,12 +74,30 @@ class ExcludableItems: @classmethod def from_excludable(cls, other: "ExcludableItems") -> "ExcludableItems": + """ + Copy passed ExcludableItems to avoid inplace modifications. + + :param other: other excludable items to be copied + :type other: ormar.models.excludable.ExcludableItems + :return: copy of other + :rtype: ormar.models.excludable.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: + """ + Return Excludable for given model and alias. + + :param model_cls: target model to check + :type model_cls: ormar.models.metaclass.ModelMetaclass + :param alias: table alias from relation manager + :type alias: str + :return: Excludable for given model and alias + :rtype: ormar.models.excludable.Excludable + """ key = f"{alias + '_' if alias else ''}{model_cls.get_name(lower=True)}" excludable = self.items.get(key) if not excludable: @@ -63,7 +111,19 @@ class ExcludableItems: model_cls: Type["Model"], is_exclude: bool = False, ) -> None: + """ + Receives the one of the types of items and parses them as to achieve + a end situation with one excludable per alias/model in relation. + Each excludable has two sets of values - one to include, one to exclude. + + :param items: values to be included or excluded + :type items: Union[List[str], str, Tuple[str], Set[str], Dict] + :param model_cls: source model from which relations are constructed + :type model_cls: ormar.models.metaclass.ModelMetaclass + :param is_exclude: flag if items should be included or excluded + :type is_exclude: bool + """ if isinstance(items, str): items = {items} @@ -92,7 +152,18 @@ class ExcludableItems: def _set_excludes( self, items: Set, model_name: str, is_exclude: bool, alias: str = "" ) -> None: + """ + Sets set of values to be included or excluded for given key and model. + :param items: items to include/exclude + :type items: set + :param model_name: name of model to construct key + :type model_name: str + :param is_exclude: flag if values should be included or excluded + :type is_exclude: bool + :param alias: + :type alias: str + """ key = f"{alias + '_' if alias else ''}{model_name}" excludable = self.items.get(key) if not excludable: @@ -109,7 +180,22 @@ class ExcludableItems: related_items: List = None, alias: str = "", ) -> None: + """ + Goes through dict of nested values and construct/update Excludables. + :param values: items to include/exclude + :type values: Dict + :param source_model: source model from which relations are constructed + :type source_model: ormar.models.metaclass.ModelMetaclass + :param model_cls: model from which current relation is constructed + :type model_cls: ormar.models.metaclass.ModelMetaclass + :param is_exclude: flag if values should be included or excluded + :type is_exclude: bool + :param related_items: list of names of related fields chain + :type related_items: List + :param alias: alias of relation + :type alias: str + """ self_fields = set() related_items = related_items[:] if related_items else [] for key, value in values.items(): @@ -160,7 +246,16 @@ class ExcludableItems: def _traverse_list( self, values: Set[str], model_cls: Type["Model"], is_exclude: bool ) -> None: + """ + Goes through list of values and construct/update Excludables. + :param values: items to include/exclude + :type values: set + :param model_cls: model from which current relation is constructed + :type model_cls: ormar.models.metaclass.ModelMetaclass + :param is_exclude: flag if values should be included or excluded + :type is_exclude: bool + """ # here we have only nested related keys for key in values: key_split = key.split("__") diff --git a/ormar/models/model.py b/ormar/models/model.py index 1535113..2c14888 100644 --- a/ormar/models/model.py +++ b/ormar/models/model.py @@ -3,7 +3,6 @@ from typing import ( Set, TYPE_CHECKING, Tuple, - TypeVar, ) import ormar.queryset # noqa I100 @@ -15,20 +14,18 @@ from ormar.models.model_row import ModelRow if TYPE_CHECKING: # pragma nocover from ormar import QuerySet -T = TypeVar("T", bound="Model") - class Model(ModelRow): __abstract__ = False if TYPE_CHECKING: # pragma nocover Meta: ModelMeta - objects: "QuerySet[Model]" + objects: "QuerySet" def __repr__(self) -> str: # pragma nocover _repr = {k: getattr(self, k) for k, v in self.Meta.model_fields.items()} return f"{self.__class__.__name__}({str(_repr)})" - async def upsert(self: T, **kwargs: Any) -> T: + async def upsert(self, **kwargs: Any) -> "Model": """ Performs either a save or an update depending on the presence of the pk. If the pk field is filled it's an update, otherwise the save is performed. @@ -43,7 +40,7 @@ class Model(ModelRow): return await self.save() return await self.update(**kwargs) - async def save(self: T) -> T: + async def save(self) -> "Model": """ Performs a save of given Model instance. If primary key is already saved, db backend will throw integrity error. @@ -160,7 +157,7 @@ class Model(ModelRow): @staticmethod async def _update_and_follow( - rel: T, follow: bool, visited: Set, update_count: int + rel: "Model", follow: bool, visited: Set, update_count: int ) -> Tuple[int, Set]: """ Internal method used in save_related to follow related models and update numbers @@ -189,7 +186,7 @@ class Model(ModelRow): update_count += 1 return update_count, visited - async def update(self: T, **kwargs: Any) -> T: + async def update(self, **kwargs: Any) -> "Model": """ Performs update of Model instance in the database. Fields can be updated before or you can pass them as kwargs. @@ -225,7 +222,7 @@ class Model(ModelRow): await self.signals.post_update.send(sender=self.__class__, instance=self) return self - async def delete(self: T) -> int: + async def delete(self) -> int: """ Removes the Model instance from the database. @@ -248,7 +245,7 @@ class Model(ModelRow): await self.signals.post_delete.send(sender=self.__class__, instance=self) return result - async def load(self: T) -> T: + async def load(self) -> "Model": """ Allow to refresh existing Models fields from database. Be careful as the related models can be overwritten by pk_only models in load. diff --git a/ormar/models/model_row.py b/ormar/models/model_row.py index 1b15e6d..c4ea9d0 100644 --- a/ormar/models/model_row.py +++ b/ormar/models/model_row.py @@ -5,7 +5,6 @@ from typing import ( Optional, TYPE_CHECKING, Type, - TypeVar, cast, ) @@ -17,24 +16,22 @@ from ormar.models.helpers.models import group_related_list if TYPE_CHECKING: # pragma: no cover from ormar.fields import ForeignKeyField - from ormar.models import T -else: - T = TypeVar("T", bound="ModelRow") + from ormar.models import Model class ModelRow(NewBaseModel): @classmethod def from_row( # noqa: CFQ002 - cls: Type["ModelRow"], + cls, row: sqlalchemy.engine.ResultProxy, - source_model: Type[T], + source_model: Type["Model"], select_related: List = None, related_models: Any = None, related_field: Type["ForeignKeyField"] = None, excludable: ExcludableItems = None, current_relation_str: str = "", - proxy_source_model: Optional[Type["ModelRow"]] = None, - ) -> Optional[T]: + proxy_source_model: Optional[Type["Model"]] = None, + ) -> Optional["Model"]: """ Model method to convert raw sql row from database into ormar.Model instance. Traverses nested models if they were specified in select_related for query. @@ -48,6 +45,8 @@ class ModelRow(NewBaseModel): where rows are populated in a different way as they do not have nested models in result. + :param proxy_source_model: source model from which querysetproxy is constructed + :type proxy_source_model: Optional[Type["ModelRow"]] :param excludable: structure of fields to include and exclude :type excludable: ExcludableItems :param current_relation_str: name of the relation field @@ -72,7 +71,6 @@ class ModelRow(NewBaseModel): excludable = excludable or ExcludableItems() if select_related: - source_model = cast(Type[T], cls) related_models = group_related_list(select_related) if related_field: @@ -88,19 +86,19 @@ class ModelRow(NewBaseModel): related_models=related_models, excludable=excludable, current_relation_str=current_relation_str, - source_model=source_model, + source_model=source_model, # type: ignore proxy_source_model=proxy_source_model, # type: ignore ) item = cls.extract_prefixed_table_columns( item=item, row=row, table_prefix=table_prefix, excludable=excludable ) - instance: Optional[T] = None + instance: Optional["Model"] = None if item.get(cls.Meta.pkname, None) is not None: item["__excluded__"] = cls.get_names_to_exclude( excludable=excludable, alias=table_prefix ) - instance = cast(T, cls(**item)) + instance = cast("Model", cls(**item)) instance.set_save_status(True) return instance @@ -109,11 +107,11 @@ class ModelRow(NewBaseModel): cls, item: dict, row: sqlalchemy.engine.ResultProxy, - source_model: Type[T], + source_model: Type["Model"], related_models: Any, excludable: ExcludableItems, current_relation_str: str = None, - proxy_source_model: Type[T] = None, + proxy_source_model: Type["Model"] = None, ) -> dict: """ Traverses structure of related models and populates the nested models @@ -125,6 +123,8 @@ class ModelRow(NewBaseModel): Recurrently calls from_row method on nested instances and create nested instances. In the end those instances are added to the final model dictionary. + :param proxy_source_model: source model from which querysetproxy is constructed + :type proxy_source_model: Optional[Type["ModelRow"]] :param excludable: structure of fields to include and exclude :type excludable: ExcludableItems :param source_model: source model from which relation started @@ -190,6 +190,21 @@ class ModelRow(NewBaseModel): related: str, excludable: ExcludableItems, ) -> "ModelRow": + """ + Initialize the through model from db row. + Excluded all relation fields and other exclude/include set in excludable. + + :param row: loaded row from database + :type row: sqlalchemy.engine.ResultProxy + :param through_name: name of the through field + :type through_name: str + :param related: name of the relation + :type related: str + :param excludable: structure of fields to include and exclude + :type excludable: ExcludableItems + :return: initialized through model without relation + :rtype: "ModelRow" + """ model_cls = cls.Meta.model_fields[through_name].to table_prefix = cls.Meta.alias_manager.resolve_relation_alias( from_model=cls, relation_name=related diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index d400c0f..9308d83 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -13,7 +13,6 @@ from typing import ( Set, TYPE_CHECKING, Type, - TypeVar, Union, cast, ) @@ -46,15 +45,13 @@ from ormar.relations.alias_manager import AliasManager from ormar.relations.relation_manager import RelationsManager if TYPE_CHECKING: # pragma no cover - from ormar.models import Model, T + from ormar.models import Model from ormar.signals import SignalEmitter 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 +86,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass Meta: ModelMeta # noinspection PyMissingConstructor - def __init__(self: T, *args: Any, **kwargs: Any) -> None: # type: ignore + def __init__(self, *args: Any, **kwargs: Any) -> None: # type: ignore """ Initializer that creates a new ormar Model that is also pydantic Model at the same time. @@ -130,7 +127,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass self, "_orm", RelationsManager( - related_fields=self.extract_related_fields(), owner=cast(T, self), + related_fields=self.extract_related_fields(), owner=cast("Model", self), ), ) @@ -397,7 +394,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass cause some dialect require different treatment""" return cls.Meta.database._backend._dialect.name - def remove(self, parent: "T", name: str) -> None: + def remove(self, parent: "Model", name: str) -> None: """Removes child from relation with given name in RelationshipManager""" self._orm.remove_parent(self, parent, name) diff --git a/ormar/queryset/__init__.py b/ormar/queryset/__init__.py index 11b072e..161d9bb 100644 --- a/ormar/queryset/__init__.py +++ b/ormar/queryset/__init__.py @@ -6,10 +6,9 @@ from ormar.queryset.filter_query import FilterQuery from ormar.queryset.limit_query import LimitQuery from ormar.queryset.offset_query import OffsetQuery from ormar.queryset.order_query import OrderQuery -from ormar.queryset.queryset import QuerySet, T +from ormar.queryset.queryset import QuerySet __all__ = [ - "T", "QuerySet", "FilterQuery", "LimitQuery", diff --git a/ormar/queryset/prefetch_query.py b/ormar/queryset/prefetch_query.py index a661b73..d224c22 100644 --- a/ormar/queryset/prefetch_query.py +++ b/ormar/queryset/prefetch_query.py @@ -596,7 +596,6 @@ class PrefetchQuery: """ target_model = target_field.to for row in rows: - # TODO Fix fields field_name = parent_model.get_related_field_name(target_field=target_field) item = target_model.extract_prefixed_table_columns( item={}, row=row, table_prefix=table_prefix, excludable=excludable, diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index f4f9fd0..7fadcb8 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -1,14 +1,12 @@ from typing import ( Any, Dict, - Generic, List, Optional, Sequence, Set, TYPE_CHECKING, Type, - TypeVar, Union, cast, ) @@ -27,22 +25,20 @@ from ormar.queryset.prefetch_query import PrefetchQuery from ormar.queryset.query import Query if TYPE_CHECKING: # pragma no cover - from ormar.models import T + from ormar import Model from ormar.models.metaclass import ModelMeta from ormar.relations.querysetproxy import QuerysetProxy from ormar.models.excludable import ExcludableItems -else: - T = TypeVar("T") -class QuerySet(Generic[T]): +class QuerySet: """ Main class to perform database queries, exposed on each model as objects attribute. """ def __init__( # noqa CFQ002 self, - model_cls: Optional[Type[T]] = None, + model_cls: Optional[Type["Model"]] = None, filter_clauses: List = None, exclude_clauses: List = None, select_related: List = None, @@ -52,7 +48,7 @@ class QuerySet(Generic[T]): order_bys: List = None, prefetch_related: List = None, limit_raw_sql: bool = False, - proxy_source_model: Optional[Type[T]] = None, + proxy_source_model: Optional[Type["Model"]] = None, ) -> None: self.proxy_source_model = proxy_source_model self.model_cls = model_cls @@ -69,7 +65,7 @@ class QuerySet(Generic[T]): def __get__( self, instance: Optional[Union["QuerySet", "QuerysetProxy"]], - owner: Union[Type[T], Type["QuerysetProxy"]], + owner: Union[Type["Model"], Type["QuerysetProxy"]], ) -> "QuerySet": if issubclass(owner, ormar.Model): if owner.Meta.requires_ref_update: @@ -78,7 +74,7 @@ class QuerySet(Generic[T]): f"ForwardRefs. \nBefore using the model you " f"need to call update_forward_refs()." ) - owner = cast(Type[T], owner) + owner = cast(Type["Model"], owner) return self.__class__(model_cls=owner) return self.__class__() # pragma: no cover @@ -95,7 +91,7 @@ class QuerySet(Generic[T]): return self.model_cls.Meta @property - def model(self) -> Type[T]: + def model(self) -> Type["Model"]: """ Shortcut to model class set on QuerySet. @@ -117,7 +113,7 @@ class QuerySet(Generic[T]): order_bys: List = None, prefetch_related: List = None, limit_raw_sql: bool = None, - proxy_source_model: Optional[Type[T]] = None, + proxy_source_model: Optional[Type["Model"]] = None, ) -> "QuerySet": """ Method that returns new instance of queryset based on passed params, @@ -152,8 +148,8 @@ class QuerySet(Generic[T]): ) async def _prefetch_related_models( - self, models: Sequence[Optional["T"]], rows: List - ) -> Sequence[Optional["T"]]: + self, models: Sequence[Optional["Model"]], rows: List + ) -> Sequence[Optional["Model"]]: """ Performs prefetch query for selected models names. @@ -173,7 +169,7 @@ class QuerySet(Generic[T]): ) return await query.prefetch_related(models=models, rows=rows) # type: ignore - def _process_query_result_rows(self, rows: List) -> Sequence[Optional[T]]: + def _process_query_result_rows(self, rows: List) -> Sequence[Optional["Model"]]: """ Process database rows and initialize ormar Model from each of the rows. @@ -197,7 +193,7 @@ class QuerySet(Generic[T]): return result_rows @staticmethod - def check_single_result_rows_count(rows: Sequence[Optional[T]]) -> None: + def check_single_result_rows_count(rows: Sequence[Optional["Model"]]) -> None: """ Verifies if the result has one and only one row. @@ -638,7 +634,7 @@ class QuerySet(Generic[T]): limit_raw_sql = self.limit_sql_raw if limit_raw_sql is None else limit_raw_sql return self.rebuild_self(offset=offset, limit_raw_sql=limit_raw_sql,) - async def first(self, **kwargs: Any) -> T: + async def first(self, **kwargs: Any) -> "Model": """ Gets the first row from the db ordered by primary key column ascending. @@ -669,7 +665,7 @@ class QuerySet(Generic[T]): self.check_single_result_rows_count(processed_rows) return processed_rows[0] # type: ignore - async def get(self, **kwargs: Any) -> T: + async def get(self, **kwargs: Any) -> "Model": """ Get's the first row from the db meeting the criteria set by kwargs. @@ -708,7 +704,7 @@ class QuerySet(Generic[T]): self.check_single_result_rows_count(processed_rows) return processed_rows[0] # type: ignore - async def get_or_create(self, **kwargs: Any) -> T: + async def get_or_create(self, **kwargs: Any) -> "Model": """ Combination of create and get methods. @@ -726,7 +722,7 @@ class QuerySet(Generic[T]): except NoMatch: return await self.create(**kwargs) - async def update_or_create(self, **kwargs: Any) -> T: + async def update_or_create(self, **kwargs: Any) -> "Model": """ Updates the model, or in case there is no match in database creates a new one. @@ -743,7 +739,7 @@ class QuerySet(Generic[T]): model = await self.get(pk=kwargs[pk_name]) return await model.update(**kwargs) - async def all(self, **kwargs: Any) -> Sequence[Optional[T]]: # noqa: A003 + async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003 """ Returns all rows from a database for given model for set filter options. @@ -767,7 +763,7 @@ class QuerySet(Generic[T]): return result_rows - async def create(self, **kwargs: Any) -> T: + async def create(self, **kwargs: Any) -> "Model": """ Creates the model instance, saves it in a database and returns the updates model (with pk populated if not passed and autoincrement is set). @@ -810,7 +806,7 @@ class QuerySet(Generic[T]): ) return instance - async def bulk_create(self, objects: List[T]) -> None: + async def bulk_create(self, objects: List["Model"]) -> None: """ Performs a bulk update in one database session to speed up the process. @@ -836,7 +832,7 @@ class QuerySet(Generic[T]): objt.set_save_status(True) async def bulk_update( # noqa: CCR001 - self, objects: List[T], columns: List[str] = None + self, objects: List["Model"], columns: List[str] = None ) -> None: """ Performs bulk update in one database session to speed up the process. diff --git a/ormar/relations/querysetproxy.py b/ormar/relations/querysetproxy.py index 952a6c7..190dd7b 100644 --- a/ormar/relations/querysetproxy.py +++ b/ormar/relations/querysetproxy.py @@ -1,14 +1,13 @@ -from typing import ( +from _weakref import CallableProxyType +from typing import ( # noqa: I100, I201 Any, Dict, - Generic, List, MutableSequence, Optional, Sequence, Set, TYPE_CHECKING, - TypeVar, Union, cast, ) @@ -18,14 +17,12 @@ from ormar.exceptions import ModelPersistenceError, QueryDefinitionError if TYPE_CHECKING: # pragma no cover from ormar.relations import Relation - from ormar.models import Model, T + from ormar.models import Model from ormar.queryset import QuerySet from ormar import RelationType -else: - T = TypeVar("T") -class QuerysetProxy(Generic[T]): +class QuerysetProxy: """ Exposes QuerySet methods on relations, but also handles creating and removing of through Models for m2m relations. @@ -40,7 +37,7 @@ class QuerysetProxy(Generic[T]): self.relation: Relation = relation self._queryset: Optional["QuerySet"] = qryset self.type_: "RelationType" = type_ - self._owner: "Model" = self.relation.manager.owner + self._owner: Union[CallableProxyType, "Model"] = self.relation.manager.owner self.related_field_name = self._owner.Meta.model_fields[ self.relation.field_name ].get_related_name() @@ -72,7 +69,7 @@ class QuerysetProxy(Generic[T]): """ self._queryset = value - def _assign_child_to_parent(self, child: Optional["T"]) -> None: + def _assign_child_to_parent(self, child: Optional["Model"]) -> None: """ Registers child in parents RelationManager. @@ -84,7 +81,9 @@ class QuerysetProxy(Generic[T]): rel_name = self.relation.field_name setattr(owner, rel_name, child) - def _register_related(self, child: Union["T", Sequence[Optional["T"]]]) -> None: + def _register_related( + self, child: Union["Model", Sequence[Optional["Model"]]] + ) -> None: """ Registers child/ children in parents RelationManager. @@ -96,7 +95,7 @@ class QuerysetProxy(Generic[T]): self._assign_child_to_parent(subchild) else: assert isinstance(child, ormar.Model) - child = cast(T, child) + child = cast("Model", child) self._assign_child_to_parent(child) def _clean_items_on_load(self) -> None: @@ -107,7 +106,7 @@ class QuerysetProxy(Generic[T]): for item in self.relation.related_models[:]: self.relation.remove(item) - async def create_through_instance(self, child: "T", **kwargs: Any) -> None: + async def create_through_instance(self, child: "Model", **kwargs: Any) -> None: """ Crete a through model instance in the database for m2m relations. @@ -132,7 +131,7 @@ class QuerysetProxy(Generic[T]): # print("\n", expr.compile(compile_kwargs={"literal_binds": True})) await model_cls.Meta.database.execute(expr) - async def update_through_instance(self, child: "T", **kwargs: Any) -> None: + async def update_through_instance(self, child: "Model", **kwargs: Any) -> None: """ Updates a through model instance in the database for m2m relations. @@ -148,7 +147,7 @@ class QuerysetProxy(Generic[T]): through_model = await model_cls.objects.get(**rel_kwargs) await through_model.update(**kwargs) - async def delete_through_instance(self, child: "T") -> None: + async def delete_through_instance(self, child: "Model") -> None: """ Removes through model instance from the database for m2m relations. @@ -217,7 +216,7 @@ class QuerysetProxy(Generic[T]): ) return await queryset.delete(**kwargs) # type: ignore - async def first(self, **kwargs: Any) -> T: + async def first(self, **kwargs: Any) -> "Model": """ Gets the first row from the db ordered by primary key column ascending. @@ -235,7 +234,7 @@ class QuerysetProxy(Generic[T]): self._register_related(first) return first - async def get(self, **kwargs: Any) -> "T": + async def get(self, **kwargs: Any) -> "Model": """ Get's the first row from the db meeting the criteria set by kwargs. @@ -259,7 +258,7 @@ class QuerysetProxy(Generic[T]): self._register_related(get) return get - async def all(self, **kwargs: Any) -> Sequence[Optional["T"]]: # noqa: A003 + async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003 """ Returns all rows from a database for given model for set filter options. @@ -281,7 +280,7 @@ class QuerysetProxy(Generic[T]): self._register_related(all_items) return all_items - async def create(self, **kwargs: Any) -> "T": + async def create(self, **kwargs: Any) -> "Model": """ Creates the model instance, saves it in a database and returns the updates model (with pk populated if not passed and autoincrement is set). @@ -338,7 +337,7 @@ class QuerysetProxy(Generic[T]): ) return len(children) - async def get_or_create(self, **kwargs: Any) -> "T": + async def get_or_create(self, **kwargs: Any) -> "Model": """ Combination of create and get methods. @@ -356,7 +355,7 @@ class QuerysetProxy(Generic[T]): except ormar.NoMatch: return await self.create(**kwargs) - async def update_or_create(self, **kwargs: Any) -> "T": + async def update_or_create(self, **kwargs: Any) -> "Model": """ Updates the model, or in case there is no match in database creates a new one. diff --git a/ormar/relations/relation.py b/ormar/relations/relation.py index 6d4da36..0bdc671 100644 --- a/ormar/relations/relation.py +++ b/ormar/relations/relation.py @@ -7,7 +7,7 @@ from ormar.relations.relation_proxy import RelationProxy if TYPE_CHECKING: # pragma no cover from ormar.relations import RelationsManager - from ormar.models import Model, NewBaseModel, T + from ormar.models import Model, NewBaseModel class RelationType(Enum): @@ -36,7 +36,7 @@ class Relation: type_: RelationType, field_name: str, to: Type["Model"], - through: Type["T"] = None, + through: Type["Model"] = None, ) -> None: """ Initialize the Relation and keep the related models either as instances of @@ -69,7 +69,7 @@ class Relation: ) @property - def through(self) -> Type["T"]: + def through(self) -> Type["Model"]: if not self._through: # pragma: no cover raise RelationshipInstanceError("Relation does not have through model!") return self._through @@ -116,7 +116,7 @@ class Relation: self._to_remove.add(ind) return None - def add(self, child: "T") -> None: + def add(self, child: "Model") -> None: """ Adds child Model to relation, either sets child as related model or adds it to the list in RelationProxy depending on relation type. diff --git a/ormar/relations/relation_manager.py b/ormar/relations/relation_manager.py index a718b09..0719550 100644 --- a/ormar/relations/relation_manager.py +++ b/ormar/relations/relation_manager.py @@ -5,7 +5,7 @@ from ormar.relations.relation import Relation, RelationType from ormar.relations.utils import get_relations_sides_and_names if TYPE_CHECKING: # pragma no cover - from ormar.models import NewBaseModel, T, Model + from ormar.models import NewBaseModel, Model from ormar.fields import ForeignKeyField, BaseField @@ -17,7 +17,7 @@ class RelationsManager: def __init__( self, related_fields: List[Type["ForeignKeyField"]] = None, - owner: Optional["T"] = None, + owner: Optional["Model"] = None, ) -> None: self.owner = proxy(owner) self._related_fields = related_fields or [] diff --git a/tests/test_excluding_fields_in_fastapi.py b/tests/test_excluding_fields_in_fastapi.py index 6568d9b..dd4dd84 100644 --- a/tests/test_excluding_fields_in_fastapi.py +++ b/tests/test_excluding_fields_in_fastapi.py @@ -124,8 +124,7 @@ async def create_user(user: User): @app.post("/users2/", response_model=User) async def create_user2(user: User): - user = await user.save() - return user.dict(exclude={"password"}) + return (await user.save()).dict(exclude={"password"}) @app.post("/users3/", response_model=UserBase) @@ -135,26 +134,22 @@ async def create_user3(user: User2): @app.post("/users4/") async def create_user4(user: User2): - user = await user.save() - return user.dict(exclude={"password"}) + return (await user.save()).dict(exclude={"password"}) @app.post("/random/", response_model=RandomModel) async def create_user5(user: RandomModel): - user = await user.save() - return user + return await user.save() @app.post("/random2/", response_model=RandomModel) async def create_user6(user: RandomModel): - user = await user.save() - return user.dict() + return await user.save() @app.post("/random3/", response_model=RandomModel, response_model_exclude={"full_name"}) async def create_user7(user: RandomModel): - user = await user.save() - return user.dict() + return await user.save() def test_excluding_fields_in_endpoints():