add docstrings, clean types test
This commit is contained in:
@ -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"]
|
||||
|
||||
@ -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("__")
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user