add docstrings, clean types test

This commit is contained in:
collerek
2021-03-02 19:10:59 +01:00
parent a99000d2c0
commit 9ad1528cc0
13 changed files with 190 additions and 98 deletions

View File

@ -73,7 +73,7 @@ def create_dummy_model(
"".join(choices(string.ascii_uppercase, k=2)) + uuid.uuid4().hex[:4] "".join(choices(string.ascii_uppercase, k=2)) + uuid.uuid4().hex[:4]
).lower() ).lower()
fields = {f"{pk_field.name}": (pk_field.__type__, None)} 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}", f"PkOnly{base_model.get_name(lower=False)}{alias}",
__module__=base_model.__module__, __module__=base_model.__module__,
**fields, # type: ignore **fields, # type: ignore

View File

@ -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.newbasemodel import NewBaseModel # noqa I100
from ormar.models.model_row import ModelRow # 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 from ormar.models.excludable import ExcludableItems # noqa I100
__all__ = ["T", "NewBaseModel", "Model", "ModelRow", "ExcludableItems"] __all__ = ["NewBaseModel", "Model", "ModelRow", "ExcludableItems"]

View File

@ -7,28 +7,58 @@ if TYPE_CHECKING: # pragma: no cover
from ormar import Model from ormar import Model
# TODO: Add docstrings
@dataclass @dataclass
class Excludable: class Excludable:
"""
Class that keeps sets of fields to exclude and include
"""
include: Set = field(default_factory=set) include: Set = field(default_factory=set)
exclude: Set = field(default_factory=set) exclude: Set = field(default_factory=set)
def get_copy(self) -> "Excludable": 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 = self.__class__()
_copy.include = {x for x in self.include} _copy.include = {x for x in self.include}
_copy.exclude = {x for x in self.exclude} _copy.exclude = {x for x in self.exclude}
return _copy return _copy
def set_values(self, value: Set, is_exclude: bool) -> None: 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" prop = "exclude" if is_exclude else "include"
current_value = getattr(self, prop) current_value = getattr(self, prop)
current_value.update(value) current_value.update(value)
setattr(self, prop, current_value) setattr(self, prop, current_value)
def is_included(self, key: str) -> bool: 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 return (... in self.include or key in self.include) if self.include else True
def is_excluded(self, key: str) -> bool: 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 return (... in self.exclude or key in self.exclude) if self.exclude else False
@ -44,12 +74,30 @@ class ExcludableItems:
@classmethod @classmethod
def from_excludable(cls, other: "ExcludableItems") -> "ExcludableItems": 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() new_excludable = cls()
for key, value in other.items.items(): for key, value in other.items.items():
new_excludable.items[key] = value.get_copy() new_excludable.items[key] = value.get_copy()
return new_excludable return new_excludable
def get(self, model_cls: Type["Model"], alias: str = "") -> 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)}" key = f"{alias + '_' if alias else ''}{model_cls.get_name(lower=True)}"
excludable = self.items.get(key) excludable = self.items.get(key)
if not excludable: if not excludable:
@ -63,7 +111,19 @@ class ExcludableItems:
model_cls: Type["Model"], model_cls: Type["Model"],
is_exclude: bool = False, is_exclude: bool = False,
) -> None: ) -> 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): if isinstance(items, str):
items = {items} items = {items}
@ -92,7 +152,18 @@ 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:
"""
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}" key = f"{alias + '_' if alias else ''}{model_name}"
excludable = self.items.get(key) excludable = self.items.get(key)
if not excludable: if not excludable:
@ -109,7 +180,22 @@ class ExcludableItems:
related_items: List = None, related_items: List = None,
alias: str = "", alias: str = "",
) -> None: ) -> 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() self_fields = set()
related_items = related_items[:] if related_items else [] related_items = related_items[:] if related_items else []
for key, value in values.items(): for key, value in values.items():
@ -160,7 +246,16 @@ 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:
"""
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 # here we have only nested related keys
for key in values: for key in values:
key_split = key.split("__") key_split = key.split("__")

View File

@ -3,7 +3,6 @@ from typing import (
Set, Set,
TYPE_CHECKING, TYPE_CHECKING,
Tuple, Tuple,
TypeVar,
) )
import ormar.queryset # noqa I100 import ormar.queryset # noqa I100
@ -15,20 +14,18 @@ from ormar.models.model_row import ModelRow
if TYPE_CHECKING: # pragma nocover if TYPE_CHECKING: # pragma nocover
from ormar import QuerySet from ormar import QuerySet
T = TypeVar("T", bound="Model")
class Model(ModelRow): class Model(ModelRow):
__abstract__ = False __abstract__ = False
if TYPE_CHECKING: # pragma nocover if TYPE_CHECKING: # pragma nocover
Meta: ModelMeta Meta: ModelMeta
objects: "QuerySet[Model]" objects: "QuerySet"
def __repr__(self) -> str: # pragma nocover def __repr__(self) -> str: # pragma nocover
_repr = {k: getattr(self, k) for k, v in self.Meta.model_fields.items()} _repr = {k: getattr(self, k) for k, v in self.Meta.model_fields.items()}
return f"{self.__class__.__name__}({str(_repr)})" 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. 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. 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.save()
return await self.update(**kwargs) return await self.update(**kwargs)
async def save(self: T) -> T: async def save(self) -> "Model":
""" """
Performs a save of given Model instance. Performs a save of given Model instance.
If primary key is already saved, db backend will throw integrity error. If primary key is already saved, db backend will throw integrity error.
@ -160,7 +157,7 @@ class Model(ModelRow):
@staticmethod @staticmethod
async def _update_and_follow( 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]: ) -> Tuple[int, Set]:
""" """
Internal method used in save_related to follow related models and update numbers Internal method used in save_related to follow related models and update numbers
@ -189,7 +186,7 @@ class Model(ModelRow):
update_count += 1 update_count += 1
return update_count, visited 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. Performs update of Model instance in the database.
Fields can be updated before or you can pass them as kwargs. 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) await self.signals.post_update.send(sender=self.__class__, instance=self)
return self return self
async def delete(self: T) -> int: async def delete(self) -> int:
""" """
Removes the Model instance from the database. 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) await self.signals.post_delete.send(sender=self.__class__, instance=self)
return result return result
async def load(self: T) -> T: async def load(self) -> "Model":
""" """
Allow to refresh existing Models fields from database. Allow to refresh existing Models fields from database.
Be careful as the related models can be overwritten by pk_only models in load. Be careful as the related models can be overwritten by pk_only models in load.

View File

@ -5,7 +5,6 @@ from typing import (
Optional, Optional,
TYPE_CHECKING, TYPE_CHECKING,
Type, Type,
TypeVar,
cast, cast,
) )
@ -17,24 +16,22 @@ from ormar.models.helpers.models import group_related_list
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from ormar.fields import ForeignKeyField from ormar.fields import ForeignKeyField
from ormar.models import T from ormar.models import Model
else:
T = TypeVar("T", bound="ModelRow")
class ModelRow(NewBaseModel): class ModelRow(NewBaseModel):
@classmethod @classmethod
def from_row( # noqa: CFQ002 def from_row( # noqa: CFQ002
cls: Type["ModelRow"], cls,
row: sqlalchemy.engine.ResultProxy, row: sqlalchemy.engine.ResultProxy,
source_model: Type[T], source_model: Type["Model"],
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,
excludable: ExcludableItems = None, excludable: ExcludableItems = None,
current_relation_str: str = "", current_relation_str: str = "",
proxy_source_model: Optional[Type["ModelRow"]] = None, proxy_source_model: Optional[Type["Model"]] = None,
) -> Optional[T]: ) -> Optional["Model"]:
""" """
Model method to convert raw sql row from database into ormar.Model instance. 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. 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 where rows are populated in a different way as they do not have
nested models in result. 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 :param excludable: structure of fields to include and exclude
:type excludable: ExcludableItems :type excludable: ExcludableItems
:param current_relation_str: name of the relation field :param current_relation_str: name of the relation field
@ -72,7 +71,6 @@ class ModelRow(NewBaseModel):
excludable = excludable or ExcludableItems() excludable = excludable or ExcludableItems()
if select_related: if select_related:
source_model = cast(Type[T], cls)
related_models = group_related_list(select_related) related_models = group_related_list(select_related)
if related_field: if related_field:
@ -88,19 +86,19 @@ class ModelRow(NewBaseModel):
related_models=related_models, related_models=related_models,
excludable=excludable, excludable=excludable,
current_relation_str=current_relation_str, current_relation_str=current_relation_str,
source_model=source_model, source_model=source_model, # type: ignore
proxy_source_model=proxy_source_model, # type: ignore proxy_source_model=proxy_source_model, # type: ignore
) )
item = cls.extract_prefixed_table_columns( item = cls.extract_prefixed_table_columns(
item=item, row=row, table_prefix=table_prefix, excludable=excludable 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: if item.get(cls.Meta.pkname, None) is not None:
item["__excluded__"] = cls.get_names_to_exclude( item["__excluded__"] = cls.get_names_to_exclude(
excludable=excludable, alias=table_prefix excludable=excludable, alias=table_prefix
) )
instance = cast(T, cls(**item)) instance = cast("Model", cls(**item))
instance.set_save_status(True) instance.set_save_status(True)
return instance return instance
@ -109,11 +107,11 @@ class ModelRow(NewBaseModel):
cls, cls,
item: dict, item: dict,
row: sqlalchemy.engine.ResultProxy, row: sqlalchemy.engine.ResultProxy,
source_model: Type[T], source_model: Type["Model"],
related_models: Any, related_models: Any,
excludable: ExcludableItems, excludable: ExcludableItems,
current_relation_str: str = None, current_relation_str: str = None,
proxy_source_model: Type[T] = None, proxy_source_model: Type["Model"] = None,
) -> dict: ) -> dict:
""" """
Traverses structure of related models and populates the nested models 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 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 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 :param excludable: structure of fields to include and exclude
:type excludable: ExcludableItems :type excludable: ExcludableItems
:param source_model: source model from which relation started :param source_model: source model from which relation started
@ -190,6 +190,21 @@ class ModelRow(NewBaseModel):
related: str, related: str,
excludable: ExcludableItems, excludable: ExcludableItems,
) -> "ModelRow": ) -> "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 model_cls = cls.Meta.model_fields[through_name].to
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

View File

@ -13,7 +13,6 @@ from typing import (
Set, Set,
TYPE_CHECKING, TYPE_CHECKING,
Type, Type,
TypeVar,
Union, Union,
cast, cast,
) )
@ -46,15 +45,13 @@ from ormar.relations.alias_manager import AliasManager
from ormar.relations.relation_manager import RelationsManager from ormar.relations.relation_manager import RelationsManager
if TYPE_CHECKING: # pragma no cover if TYPE_CHECKING: # pragma no cover
from ormar.models import Model, T from ormar.models import Model
from ormar.signals import SignalEmitter from ormar.signals import SignalEmitter
IntStr = Union[int, str] IntStr = Union[int, str]
DictStrAny = Dict[str, Any] DictStrAny = Dict[str, Any]
AbstractSetIntStr = AbstractSet[IntStr] AbstractSetIntStr = AbstractSet[IntStr]
MappingIntStrAny = Mapping[IntStr, Any] MappingIntStrAny = Mapping[IntStr, Any]
else:
T = TypeVar("T")
class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass): class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass):
@ -89,7 +86,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
Meta: ModelMeta Meta: ModelMeta
# noinspection PyMissingConstructor # 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 Initializer that creates a new ormar Model that is also pydantic Model at the
same time. same time.
@ -130,7 +127,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass
self, self,
"_orm", "_orm",
RelationsManager( 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""" cause some dialect require different treatment"""
return cls.Meta.database._backend._dialect.name 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""" """Removes child from relation with given name in RelationshipManager"""
self._orm.remove_parent(self, parent, name) self._orm.remove_parent(self, parent, name)

View File

@ -6,10 +6,9 @@ from ormar.queryset.filter_query import FilterQuery
from ormar.queryset.limit_query import LimitQuery from ormar.queryset.limit_query import LimitQuery
from ormar.queryset.offset_query import OffsetQuery from ormar.queryset.offset_query import OffsetQuery
from ormar.queryset.order_query import OrderQuery from ormar.queryset.order_query import OrderQuery
from ormar.queryset.queryset import QuerySet, T from ormar.queryset.queryset import QuerySet
__all__ = [ __all__ = [
"T",
"QuerySet", "QuerySet",
"FilterQuery", "FilterQuery",
"LimitQuery", "LimitQuery",

View File

@ -596,7 +596,6 @@ 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={}, row=row, table_prefix=table_prefix, excludable=excludable, item={}, row=row, table_prefix=table_prefix, excludable=excludable,

View File

@ -1,14 +1,12 @@
from typing import ( from typing import (
Any, Any,
Dict, Dict,
Generic,
List, List,
Optional, Optional,
Sequence, Sequence,
Set, Set,
TYPE_CHECKING, TYPE_CHECKING,
Type, Type,
TypeVar,
Union, Union,
cast, cast,
) )
@ -27,22 +25,20 @@ from ormar.queryset.prefetch_query import PrefetchQuery
from ormar.queryset.query import Query from ormar.queryset.query import Query
if TYPE_CHECKING: # pragma no cover if TYPE_CHECKING: # pragma no cover
from ormar.models import T from ormar import Model
from ormar.models.metaclass import ModelMeta from ormar.models.metaclass import ModelMeta
from ormar.relations.querysetproxy import QuerysetProxy from ormar.relations.querysetproxy import QuerysetProxy
from ormar.models.excludable import ExcludableItems 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. Main class to perform database queries, exposed on each model as objects attribute.
""" """
def __init__( # noqa CFQ002 def __init__( # noqa CFQ002
self, self,
model_cls: Optional[Type[T]] = None, model_cls: Optional[Type["Model"]] = 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,
@ -52,7 +48,7 @@ class QuerySet(Generic[T]):
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,
proxy_source_model: Optional[Type[T]] = None, proxy_source_model: Optional[Type["Model"]] = None,
) -> None: ) -> None:
self.proxy_source_model = proxy_source_model self.proxy_source_model = proxy_source_model
self.model_cls = model_cls self.model_cls = model_cls
@ -69,7 +65,7 @@ class QuerySet(Generic[T]):
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["Model"], 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:
@ -78,7 +74,7 @@ class QuerySet(Generic[T]):
f"ForwardRefs. \nBefore using the model you " f"ForwardRefs. \nBefore using the model you "
f"need to call update_forward_refs()." 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__(model_cls=owner)
return self.__class__() # pragma: no cover return self.__class__() # pragma: no cover
@ -95,7 +91,7 @@ class QuerySet(Generic[T]):
return self.model_cls.Meta return self.model_cls.Meta
@property @property
def model(self) -> Type[T]: def model(self) -> Type["Model"]:
""" """
Shortcut to model class set on QuerySet. Shortcut to model class set on QuerySet.
@ -117,7 +113,7 @@ class QuerySet(Generic[T]):
order_bys: List = None, order_bys: List = None,
prefetch_related: List = None, prefetch_related: List = None,
limit_raw_sql: bool = None, limit_raw_sql: bool = None,
proxy_source_model: Optional[Type[T]] = None, proxy_source_model: Optional[Type["Model"]] = None,
) -> "QuerySet": ) -> "QuerySet":
""" """
Method that returns new instance of queryset based on passed params, Method that returns new instance of queryset based on passed params,
@ -152,8 +148,8 @@ class QuerySet(Generic[T]):
) )
async def _prefetch_related_models( async def _prefetch_related_models(
self, models: Sequence[Optional["T"]], rows: List self, models: Sequence[Optional["Model"]], rows: List
) -> Sequence[Optional["T"]]: ) -> Sequence[Optional["Model"]]:
""" """
Performs prefetch query for selected models names. 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 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. Process database rows and initialize ormar Model from each of the rows.
@ -197,7 +193,7 @@ class QuerySet(Generic[T]):
return result_rows return result_rows
@staticmethod @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. 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 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,) 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. 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) self.check_single_result_rows_count(processed_rows)
return processed_rows[0] # type: ignore 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. 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) self.check_single_result_rows_count(processed_rows)
return processed_rows[0] # type: ignore 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. Combination of create and get methods.
@ -726,7 +722,7 @@ class QuerySet(Generic[T]):
except NoMatch: except NoMatch:
return await self.create(**kwargs) 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. 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]) model = await self.get(pk=kwargs[pk_name])
return await model.update(**kwargs) 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. Returns all rows from a database for given model for set filter options.
@ -767,7 +763,7 @@ class QuerySet(Generic[T]):
return result_rows 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 Creates the model instance, saves it in a database and returns the updates model
(with pk populated if not passed and autoincrement is set). (with pk populated if not passed and autoincrement is set).
@ -810,7 +806,7 @@ class QuerySet(Generic[T]):
) )
return instance 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. 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) 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["Model"], 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.

View File

@ -1,14 +1,13 @@
from typing import ( from _weakref import CallableProxyType
from typing import ( # noqa: I100, I201
Any, Any,
Dict, Dict,
Generic,
List, List,
MutableSequence, MutableSequence,
Optional, Optional,
Sequence, Sequence,
Set, Set,
TYPE_CHECKING, TYPE_CHECKING,
TypeVar,
Union, Union,
cast, cast,
) )
@ -18,14 +17,12 @@ from ormar.exceptions import ModelPersistenceError, QueryDefinitionError
if TYPE_CHECKING: # pragma no cover if TYPE_CHECKING: # pragma no cover
from ormar.relations import Relation from ormar.relations import Relation
from ormar.models import Model, T from ormar.models import Model
from ormar.queryset import QuerySet from ormar.queryset import QuerySet
from ormar import RelationType 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 Exposes QuerySet methods on relations, but also handles creating and removing
of through Models for m2m relations. of through Models for m2m relations.
@ -40,7 +37,7 @@ class QuerysetProxy(Generic[T]):
self.relation: Relation = relation self.relation: Relation = relation
self._queryset: Optional["QuerySet"] = qryset self._queryset: Optional["QuerySet"] = qryset
self.type_: "RelationType" = type_ 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.related_field_name = self._owner.Meta.model_fields[
self.relation.field_name self.relation.field_name
].get_related_name() ].get_related_name()
@ -72,7 +69,7 @@ class QuerysetProxy(Generic[T]):
""" """
self._queryset = value 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. Registers child in parents RelationManager.
@ -84,7 +81,9 @@ class QuerysetProxy(Generic[T]):
rel_name = self.relation.field_name rel_name = self.relation.field_name
setattr(owner, rel_name, child) 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. Registers child/ children in parents RelationManager.
@ -96,7 +95,7 @@ class QuerysetProxy(Generic[T]):
self._assign_child_to_parent(subchild) self._assign_child_to_parent(subchild)
else: else:
assert isinstance(child, ormar.Model) assert isinstance(child, ormar.Model)
child = cast(T, child) child = cast("Model", child)
self._assign_child_to_parent(child) self._assign_child_to_parent(child)
def _clean_items_on_load(self) -> None: def _clean_items_on_load(self) -> None:
@ -107,7 +106,7 @@ class QuerysetProxy(Generic[T]):
for item in self.relation.related_models[:]: for item in self.relation.related_models[:]:
self.relation.remove(item) 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. 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})) # print("\n", expr.compile(compile_kwargs={"literal_binds": True}))
await model_cls.Meta.database.execute(expr) 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. 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) through_model = await model_cls.objects.get(**rel_kwargs)
await through_model.update(**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. 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 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. 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) self._register_related(first)
return 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. 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) self._register_related(get)
return 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. 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) self._register_related(all_items)
return 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 Creates the model instance, saves it in a database and returns the updates model
(with pk populated if not passed and autoincrement is set). (with pk populated if not passed and autoincrement is set).
@ -338,7 +337,7 @@ class QuerysetProxy(Generic[T]):
) )
return len(children) 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. Combination of create and get methods.
@ -356,7 +355,7 @@ class QuerysetProxy(Generic[T]):
except ormar.NoMatch: except ormar.NoMatch:
return await self.create(**kwargs) 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. Updates the model, or in case there is no match in database creates a new one.

View File

@ -7,7 +7,7 @@ from ormar.relations.relation_proxy import RelationProxy
if TYPE_CHECKING: # pragma no cover if TYPE_CHECKING: # pragma no cover
from ormar.relations import RelationsManager from ormar.relations import RelationsManager
from ormar.models import Model, NewBaseModel, T from ormar.models import Model, NewBaseModel
class RelationType(Enum): class RelationType(Enum):
@ -36,7 +36,7 @@ class Relation:
type_: RelationType, type_: RelationType,
field_name: str, field_name: str,
to: Type["Model"], to: Type["Model"],
through: Type["T"] = None, through: Type["Model"] = None,
) -> None: ) -> None:
""" """
Initialize the Relation and keep the related models either as instances of Initialize the Relation and keep the related models either as instances of
@ -69,7 +69,7 @@ class Relation:
) )
@property @property
def through(self) -> Type["T"]: def through(self) -> Type["Model"]:
if not self._through: # pragma: no cover if not self._through: # pragma: no cover
raise RelationshipInstanceError("Relation does not have through model!") raise RelationshipInstanceError("Relation does not have through model!")
return self._through return self._through
@ -116,7 +116,7 @@ class Relation:
self._to_remove.add(ind) self._to_remove.add(ind)
return None 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 Adds child Model to relation, either sets child as related model or adds
it to the list in RelationProxy depending on relation type. it to the list in RelationProxy depending on relation type.

View File

@ -5,7 +5,7 @@ from ormar.relations.relation import Relation, RelationType
from ormar.relations.utils import get_relations_sides_and_names from ormar.relations.utils import get_relations_sides_and_names
if TYPE_CHECKING: # pragma no cover 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 from ormar.fields import ForeignKeyField, BaseField
@ -17,7 +17,7 @@ class RelationsManager:
def __init__( def __init__(
self, self,
related_fields: List[Type["ForeignKeyField"]] = None, related_fields: List[Type["ForeignKeyField"]] = None,
owner: Optional["T"] = None, owner: Optional["Model"] = None,
) -> None: ) -> None:
self.owner = proxy(owner) self.owner = proxy(owner)
self._related_fields = related_fields or [] self._related_fields = related_fields or []

View File

@ -124,8 +124,7 @@ async def create_user(user: User):
@app.post("/users2/", response_model=User) @app.post("/users2/", response_model=User)
async def create_user2(user: User): async def create_user2(user: User):
user = await user.save() return (await user.save()).dict(exclude={"password"})
return user.dict(exclude={"password"})
@app.post("/users3/", response_model=UserBase) @app.post("/users3/", response_model=UserBase)
@ -135,26 +134,22 @@ async def create_user3(user: User2):
@app.post("/users4/") @app.post("/users4/")
async def create_user4(user: User2): async def create_user4(user: User2):
user = await user.save() return (await user.save()).dict(exclude={"password"})
return user.dict(exclude={"password"})
@app.post("/random/", response_model=RandomModel) @app.post("/random/", response_model=RandomModel)
async def create_user5(user: RandomModel): async def create_user5(user: RandomModel):
user = await user.save() return await user.save()
return user
@app.post("/random2/", response_model=RandomModel) @app.post("/random2/", response_model=RandomModel)
async def create_user6(user: RandomModel): async def create_user6(user: RandomModel):
user = await user.save() return await user.save()
return user.dict()
@app.post("/random3/", response_model=RandomModel, response_model_exclude={"full_name"}) @app.post("/random3/", response_model=RandomModel, response_model_exclude={"full_name"})
async def create_user7(user: RandomModel): async def create_user7(user: RandomModel):
user = await user.save() return await user.save()
return user.dict()
def test_excluding_fields_in_endpoints(): def test_excluding_fields_in_endpoints():