improve types -> make queryset generic
This commit is contained in:
@ -15,7 +15,7 @@ from ormar.exceptions import ModelDefinitionError, RelationshipInstanceError
|
|||||||
from ormar.fields.base import BaseField
|
from ormar.fields.base import BaseField
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma no cover
|
if TYPE_CHECKING: # pragma no cover
|
||||||
from ormar.models import Model, NewBaseModel
|
from ormar.models import Model, NewBaseModel, T
|
||||||
from ormar.fields import ManyToManyField
|
from ormar.fields import ManyToManyField
|
||||||
|
|
||||||
if sys.version_info < (3, 7):
|
if sys.version_info < (3, 7):
|
||||||
@ -24,7 +24,7 @@ if TYPE_CHECKING: # pragma no cover
|
|||||||
ToType = Union[Type["Model"], "ForwardRef"]
|
ToType = Union[Type["Model"], "ForwardRef"]
|
||||||
|
|
||||||
|
|
||||||
def create_dummy_instance(fk: Type["Model"], pk: Any = None) -> "Model":
|
def create_dummy_instance(fk: Type["T"], pk: Any = None) -> "T":
|
||||||
"""
|
"""
|
||||||
Ormar never returns you a raw data.
|
Ormar never returns you a raw data.
|
||||||
So if you have a related field that has a value populated
|
So if you have a related field that has a value populated
|
||||||
@ -55,7 +55,7 @@ def create_dummy_instance(fk: Type["Model"], pk: Any = None) -> "Model":
|
|||||||
|
|
||||||
|
|
||||||
def create_dummy_model(
|
def create_dummy_model(
|
||||||
base_model: Type["Model"],
|
base_model: Type["T"],
|
||||||
pk_field: Union[BaseField, "ForeignKeyField", "ManyToManyField"],
|
pk_field: Union[BaseField, "ForeignKeyField", "ManyToManyField"],
|
||||||
) -> Type["BaseModel"]:
|
) -> Type["BaseModel"]:
|
||||||
"""
|
"""
|
||||||
@ -83,7 +83,7 @@ def create_dummy_model(
|
|||||||
|
|
||||||
|
|
||||||
def populate_fk_params_based_on_to_model(
|
def populate_fk_params_based_on_to_model(
|
||||||
to: Type["Model"], nullable: bool, onupdate: str = None, ondelete: str = None,
|
to: Type["T"], nullable: bool, onupdate: str = None, ondelete: str = None,
|
||||||
) -> Tuple[Any, List, Any]:
|
) -> Tuple[Any, List, Any]:
|
||||||
"""
|
"""
|
||||||
Based on target to model to which relation leads to populates the type of the
|
Based on target to model to which relation leads to populates the type of the
|
||||||
@ -169,7 +169,7 @@ class ForeignKeyConstraint:
|
|||||||
|
|
||||||
|
|
||||||
def ForeignKey( # noqa CFQ002
|
def ForeignKey( # noqa CFQ002
|
||||||
to: "ToType",
|
to: Type["T"],
|
||||||
*,
|
*,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
unique: bool = False,
|
unique: bool = False,
|
||||||
@ -179,7 +179,7 @@ def ForeignKey( # noqa CFQ002
|
|||||||
onupdate: str = None,
|
onupdate: str = None,
|
||||||
ondelete: str = None,
|
ondelete: str = None,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Any:
|
) -> "T":
|
||||||
"""
|
"""
|
||||||
Despite a name it's a function that returns constructed ForeignKeyField.
|
Despite a name it's a function that returns constructed ForeignKeyField.
|
||||||
This function is actually used in model declaration (as ormar.ForeignKey(ToModel)).
|
This function is actually used in model declaration (as ormar.ForeignKey(ToModel)).
|
||||||
|
|||||||
@ -65,7 +65,7 @@ def ManyToMany(
|
|||||||
unique: bool = False,
|
unique: bool = False,
|
||||||
virtual: bool = False,
|
virtual: bool = False,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Any:
|
):
|
||||||
"""
|
"""
|
||||||
Despite a name it's a function that returns constructed ManyToManyField.
|
Despite a name it's a function that returns constructed ManyToManyField.
|
||||||
This function is actually used in model declaration
|
This function is actually used in model declaration
|
||||||
|
|||||||
@ -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 # noqa I100
|
from ormar.models.model import Model, T # noqa I100
|
||||||
from ormar.models.excludable import ExcludableItems # noqa I100
|
from ormar.models.excludable import ExcludableItems # noqa I100
|
||||||
|
|
||||||
__all__ = ["NewBaseModel", "Model", "ModelRow", "ExcludableItems"]
|
__all__ = ["NewBaseModel", "Model", "ModelRow", "ExcludableItems", "T"]
|
||||||
|
|||||||
@ -117,7 +117,7 @@ def register_reverse_model_fields(model_field: "ForeignKeyField") -> None:
|
|||||||
register_through_shortcut_fields(model_field=model_field)
|
register_through_shortcut_fields(model_field=model_field)
|
||||||
adjust_through_many_to_many_model(model_field=model_field)
|
adjust_through_many_to_many_model(model_field=model_field)
|
||||||
else:
|
else:
|
||||||
model_field.to.Meta.model_fields[related_name] = ForeignKey(
|
model_field.to.Meta.model_fields[related_name] = ForeignKey( # type: ignore
|
||||||
model_field.owner,
|
model_field.owner,
|
||||||
real_name=related_name,
|
real_name=related_name,
|
||||||
virtual=True,
|
virtual=True,
|
||||||
|
|||||||
@ -26,14 +26,14 @@ def adjust_through_many_to_many_model(model_field: "ManyToManyField") -> None:
|
|||||||
"""
|
"""
|
||||||
parent_name = model_field.default_target_field_name()
|
parent_name = model_field.default_target_field_name()
|
||||||
child_name = model_field.default_source_field_name()
|
child_name = model_field.default_source_field_name()
|
||||||
|
model_fields = model_field.through.Meta.model_fields
|
||||||
model_field.through.Meta.model_fields[parent_name] = ormar.ForeignKey(
|
model_fields[parent_name] = ormar.ForeignKey( # type: ignore
|
||||||
model_field.to,
|
model_field.to,
|
||||||
real_name=parent_name,
|
real_name=parent_name,
|
||||||
ondelete="CASCADE",
|
ondelete="CASCADE",
|
||||||
owner=model_field.through,
|
owner=model_field.through,
|
||||||
)
|
)
|
||||||
model_field.through.Meta.model_fields[child_name] = ormar.ForeignKey(
|
model_fields[child_name] = ormar.ForeignKey( # type: ignore
|
||||||
model_field.owner,
|
model_field.owner,
|
||||||
real_name=child_name,
|
real_name=child_name,
|
||||||
ondelete="CASCADE",
|
ondelete="CASCADE",
|
||||||
|
|||||||
@ -18,6 +18,7 @@ from sqlalchemy.sql.schema import ColumnCollectionConstraint
|
|||||||
|
|
||||||
import ormar # noqa I100
|
import ormar # noqa I100
|
||||||
from ormar import ModelDefinitionError # noqa I100
|
from ormar import ModelDefinitionError # noqa I100
|
||||||
|
from ormar.exceptions import ModelError
|
||||||
from ormar.fields import BaseField
|
from ormar.fields import BaseField
|
||||||
from ormar.fields.foreign_key import ForeignKeyField
|
from ormar.fields.foreign_key import ForeignKeyField
|
||||||
from ormar.fields.many_to_many import ManyToManyField
|
from ormar.fields.many_to_many import ManyToManyField
|
||||||
@ -44,6 +45,7 @@ from ormar.signals import Signal, SignalEmitter
|
|||||||
|
|
||||||
if TYPE_CHECKING: # pragma no cover
|
if TYPE_CHECKING: # pragma no cover
|
||||||
from ormar import Model
|
from ormar import Model
|
||||||
|
from ormar.models import T
|
||||||
|
|
||||||
CONFIG_KEY = "Config"
|
CONFIG_KEY = "Config"
|
||||||
PARSED_FIELDS_KEY = "__parsed_fields__"
|
PARSED_FIELDS_KEY = "__parsed_fields__"
|
||||||
@ -545,6 +547,15 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
|
|||||||
field_name=field_name, model=new_model
|
field_name=field_name, model=new_model
|
||||||
)
|
)
|
||||||
new_model.Meta.alias_manager = alias_manager
|
new_model.Meta.alias_manager = alias_manager
|
||||||
new_model.objects = QuerySet(new_model)
|
|
||||||
|
|
||||||
return new_model
|
return new_model
|
||||||
|
|
||||||
|
@property
|
||||||
|
def objects(cls: Type["T"]) -> "QuerySet[T]": # type: ignore
|
||||||
|
if cls.Meta.requires_ref_update:
|
||||||
|
raise ModelError(
|
||||||
|
f"Model {cls.get_name()} has not updated "
|
||||||
|
f"ForwardRefs. \nBefore using the model you "
|
||||||
|
f"need to call update_forward_refs()."
|
||||||
|
)
|
||||||
|
return QuerySet(model_cls=cls)
|
||||||
|
|||||||
@ -15,8 +15,6 @@ from ormar.models import NewBaseModel # noqa I100
|
|||||||
from ormar.models.metaclass import ModelMeta
|
from ormar.models.metaclass import ModelMeta
|
||||||
from ormar.models.model_row import ModelRow
|
from ormar.models.model_row import ModelRow
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma nocover
|
|
||||||
from ormar import QuerySet
|
|
||||||
|
|
||||||
T = TypeVar("T", bound="Model")
|
T = TypeVar("T", bound="Model")
|
||||||
|
|
||||||
@ -25,7 +23,6 @@ class Model(ModelRow):
|
|||||||
__abstract__ = False
|
__abstract__ = False
|
||||||
if TYPE_CHECKING: # pragma nocover
|
if TYPE_CHECKING: # pragma nocover
|
||||||
Meta: ModelMeta
|
Meta: ModelMeta
|
||||||
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()}
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Dict,
|
Dict,
|
||||||
List,
|
Generic, List,
|
||||||
Optional,
|
Optional,
|
||||||
Sequence,
|
Sequence,
|
||||||
Set,
|
Set,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Type,
|
Type,
|
||||||
Union,
|
TypeVar, Union,
|
||||||
cast,
|
cast,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,19 +26,22 @@ from ormar.queryset.query import Query
|
|||||||
|
|
||||||
if TYPE_CHECKING: # pragma no cover
|
if TYPE_CHECKING: # pragma no cover
|
||||||
from ormar import Model
|
from ormar import Model
|
||||||
|
from ormar.models import T
|
||||||
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", bound="Model")
|
||||||
|
|
||||||
|
|
||||||
class QuerySet:
|
class QuerySet(Generic[T]):
|
||||||
"""
|
"""
|
||||||
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["Model"]] = 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,
|
||||||
@ -62,21 +65,6 @@ class QuerySet:
|
|||||||
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__(
|
|
||||||
self,
|
|
||||||
instance: Optional[Union["QuerySet", "QuerysetProxy"]],
|
|
||||||
owner: Union[Type["Model"], Type["QuerysetProxy"]],
|
|
||||||
) -> "QuerySet":
|
|
||||||
if issubclass(owner, ormar.Model):
|
|
||||||
if owner.Meta.requires_ref_update:
|
|
||||||
raise ModelError(
|
|
||||||
f"Model {owner.get_name()} has not updated "
|
|
||||||
f"ForwardRefs. \nBefore using the model you "
|
|
||||||
f"need to call update_forward_refs()."
|
|
||||||
)
|
|
||||||
owner = cast(Type["Model"], owner)
|
|
||||||
return self.__class__(model_cls=owner)
|
|
||||||
return self.__class__() # pragma: no cover
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model_meta(self) -> "ModelMeta":
|
def model_meta(self) -> "ModelMeta":
|
||||||
@ -91,7 +79,7 @@ class QuerySet:
|
|||||||
return self.model_cls.Meta
|
return self.model_cls.Meta
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self) -> Type["Model"]:
|
def model(self) -> Type["T"]:
|
||||||
"""
|
"""
|
||||||
Shortcut to model class set on QuerySet.
|
Shortcut to model class set on QuerySet.
|
||||||
|
|
||||||
@ -148,8 +136,8 @@ class QuerySet:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _prefetch_related_models(
|
async def _prefetch_related_models(
|
||||||
self, models: List[Optional["Model"]], rows: List
|
self, models: List[Optional["T"]], rows: List
|
||||||
) -> List[Optional["Model"]]:
|
) -> List[Optional["T"]]:
|
||||||
"""
|
"""
|
||||||
Performs prefetch query for selected models names.
|
Performs prefetch query for selected models names.
|
||||||
|
|
||||||
@ -169,7 +157,7 @@ class QuerySet:
|
|||||||
)
|
)
|
||||||
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) -> List[Optional["Model"]]:
|
def _process_query_result_rows(self, rows: List) -> List[Optional["T"]]:
|
||||||
"""
|
"""
|
||||||
Process database rows and initialize ormar Model from each of the rows.
|
Process database rows and initialize ormar Model from each of the rows.
|
||||||
|
|
||||||
@ -190,7 +178,7 @@ class QuerySet:
|
|||||||
]
|
]
|
||||||
if result_rows:
|
if result_rows:
|
||||||
return self.model.merge_instances_list(result_rows) # type: ignore
|
return self.model.merge_instances_list(result_rows) # type: ignore
|
||||||
return result_rows
|
return cast(List[Optional["T"]], result_rows)
|
||||||
|
|
||||||
def _resolve_filter_groups(self, groups: Any) -> List[FilterGroup]:
|
def _resolve_filter_groups(self, groups: Any) -> List[FilterGroup]:
|
||||||
"""
|
"""
|
||||||
@ -221,7 +209,7 @@ class QuerySet:
|
|||||||
return filter_groups
|
return filter_groups
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_single_result_rows_count(rows: Sequence[Optional["Model"]]) -> None:
|
def check_single_result_rows_count(rows: Sequence[Optional["T"]]) -> None:
|
||||||
"""
|
"""
|
||||||
Verifies if the result has one and only one row.
|
Verifies if the result has one and only one row.
|
||||||
|
|
||||||
@ -286,7 +274,7 @@ class QuerySet:
|
|||||||
|
|
||||||
def filter( # noqa: A003
|
def filter( # noqa: A003
|
||||||
self, *args: Any, _exclude: bool = False, **kwargs: Any
|
self, *args: Any, _exclude: bool = False, **kwargs: Any
|
||||||
) -> "QuerySet":
|
) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
Allows you to filter by any `Model` attribute/field
|
Allows you to filter by any `Model` attribute/field
|
||||||
as well as to fetch instances, with a filter across an FK relationship.
|
as well as to fetch instances, with a filter across an FK relationship.
|
||||||
@ -337,7 +325,7 @@ class QuerySet:
|
|||||||
select_related=select_related,
|
select_related=select_related,
|
||||||
)
|
)
|
||||||
|
|
||||||
def exclude(self, *args: Any, **kwargs: Any) -> "QuerySet": # noqa: A003
|
def exclude(self, *args: Any, **kwargs: Any) -> "QuerySet[T]": # noqa: A003
|
||||||
"""
|
"""
|
||||||
Works exactly the same as filter and all modifiers (suffixes) are the same,
|
Works exactly the same as filter and all modifiers (suffixes) are the same,
|
||||||
but returns a *not* condition.
|
but returns a *not* condition.
|
||||||
@ -358,7 +346,7 @@ class QuerySet:
|
|||||||
"""
|
"""
|
||||||
return self.filter(_exclude=True, *args, **kwargs)
|
return self.filter(_exclude=True, *args, **kwargs)
|
||||||
|
|
||||||
def select_related(self, related: Union[List, str]) -> "QuerySet":
|
def select_related(self, related: Union[List, str]) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
Allows to prefetch related models during the same query.
|
Allows to prefetch related models during the same query.
|
||||||
|
|
||||||
@ -381,7 +369,7 @@ class QuerySet:
|
|||||||
related = sorted(list(set(list(self._select_related) + related)))
|
related = sorted(list(set(list(self._select_related) + related)))
|
||||||
return self.rebuild_self(select_related=related,)
|
return self.rebuild_self(select_related=related,)
|
||||||
|
|
||||||
def prefetch_related(self, related: Union[List, str]) -> "QuerySet":
|
def prefetch_related(self, related: Union[List, str]) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
Allows to prefetch related models during query - but opposite to
|
Allows to prefetch related models during query - but opposite to
|
||||||
`select_related` each subsequent model is fetched in a separate database query.
|
`select_related` each subsequent model is fetched in a separate database query.
|
||||||
@ -407,7 +395,7 @@ class QuerySet:
|
|||||||
|
|
||||||
def fields(
|
def fields(
|
||||||
self, columns: Union[List, str, Set, Dict], _is_exclude: bool = False
|
self, columns: Union[List, str, Set, Dict], _is_exclude: bool = False
|
||||||
) -> "QuerySet":
|
) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
|
||||||
@ -461,7 +449,7 @@ class QuerySet:
|
|||||||
|
|
||||||
return self.rebuild_self(excludable=excludable,)
|
return self.rebuild_self(excludable=excludable,)
|
||||||
|
|
||||||
def exclude_fields(self, columns: Union[List, str, Set, Dict]) -> "QuerySet":
|
def exclude_fields(self, columns: Union[List, str, Set, Dict]) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
With `exclude_fields()` you can select subset of model columns that will
|
With `exclude_fields()` you can select subset of model columns that will
|
||||||
be excluded to limit the data load.
|
be excluded to limit the data load.
|
||||||
@ -490,7 +478,7 @@ class QuerySet:
|
|||||||
"""
|
"""
|
||||||
return self.fields(columns=columns, _is_exclude=True)
|
return self.fields(columns=columns, _is_exclude=True)
|
||||||
|
|
||||||
def order_by(self, columns: Union[List, str]) -> "QuerySet":
|
def order_by(self, columns: Union[List, str]) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
With `order_by()` you can order the results from database based on your
|
With `order_by()` you can order the results from database based on your
|
||||||
choice of fields.
|
choice of fields.
|
||||||
@ -680,7 +668,7 @@ class QuerySet:
|
|||||||
)
|
)
|
||||||
return await self.database.execute(expr)
|
return await self.database.execute(expr)
|
||||||
|
|
||||||
def paginate(self, page: int, page_size: int = 20) -> "QuerySet":
|
def paginate(self, page: int, page_size: int = 20) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
You can paginate the result which is a combination of offset and limit clauses.
|
You can paginate the result which is a combination of offset and limit clauses.
|
||||||
Limit is set to page size and offset is set to (page-1) * page_size.
|
Limit is set to page size and offset is set to (page-1) * page_size.
|
||||||
@ -699,7 +687,7 @@ class QuerySet:
|
|||||||
query_offset = (page - 1) * page_size
|
query_offset = (page - 1) * page_size
|
||||||
return self.rebuild_self(limit_count=limit_count, offset=query_offset,)
|
return self.rebuild_self(limit_count=limit_count, offset=query_offset,)
|
||||||
|
|
||||||
def limit(self, limit_count: int, limit_raw_sql: bool = None) -> "QuerySet":
|
def limit(self, limit_count: int, limit_raw_sql: bool = None) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
You can limit the results to desired number of parent models.
|
You can limit the results to desired number of parent models.
|
||||||
|
|
||||||
@ -716,7 +704,7 @@ class QuerySet:
|
|||||||
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(limit_count=limit_count, limit_raw_sql=limit_raw_sql,)
|
return self.rebuild_self(limit_count=limit_count, limit_raw_sql=limit_raw_sql,)
|
||||||
|
|
||||||
def offset(self, offset: int, limit_raw_sql: bool = None) -> "QuerySet":
|
def offset(self, offset: int, limit_raw_sql: bool = None) -> "QuerySet[T]":
|
||||||
"""
|
"""
|
||||||
You can also offset the results by desired number of main models.
|
You can also offset the results by desired number of main models.
|
||||||
|
|
||||||
@ -733,7 +721,7 @@ class QuerySet:
|
|||||||
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) -> "Model":
|
async def first(self, **kwargs: Any) -> "T":
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
|
||||||
@ -764,7 +752,7 @@ class QuerySet:
|
|||||||
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) -> "Model":
|
async def get(self, **kwargs: Any) -> "T":
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
|
||||||
@ -803,7 +791,7 @@ class QuerySet:
|
|||||||
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) -> "Model":
|
async def get_or_create(self, **kwargs: Any) -> "T":
|
||||||
"""
|
"""
|
||||||
Combination of create and get methods.
|
Combination of create and get methods.
|
||||||
|
|
||||||
@ -821,7 +809,7 @@ class QuerySet:
|
|||||||
except NoMatch:
|
except NoMatch:
|
||||||
return await self.create(**kwargs)
|
return await self.create(**kwargs)
|
||||||
|
|
||||||
async def update_or_create(self, **kwargs: Any) -> "Model":
|
async def update_or_create(self, **kwargs: Any) -> "T":
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
|
||||||
@ -838,7 +826,7 @@ class QuerySet:
|
|||||||
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) -> List[Optional["Model"]]: # noqa: A003
|
async def all(self, **kwargs: Any) -> List[Optional["T"]]: # 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.
|
||||||
|
|
||||||
@ -862,7 +850,7 @@ class QuerySet:
|
|||||||
|
|
||||||
return result_rows
|
return result_rows
|
||||||
|
|
||||||
async def create(self, **kwargs: Any) -> "Model":
|
async def create(self, **kwargs: Any) -> "T":
|
||||||
"""
|
"""
|
||||||
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).
|
||||||
@ -905,7 +893,7 @@ class QuerySet:
|
|||||||
)
|
)
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
async def bulk_create(self, objects: List["Model"]) -> None:
|
async def bulk_create(self, objects: List["T"]) -> 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.
|
||||||
|
|
||||||
@ -931,7 +919,7 @@ class QuerySet:
|
|||||||
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["Model"], 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.
|
||||||
|
|||||||
@ -50,7 +50,7 @@ class AliasTest(ormar.Model):
|
|||||||
|
|
||||||
id: int = ormar.Integer(name="alias_id", primary_key=True)
|
id: int = ormar.Integer(name="alias_id", primary_key=True)
|
||||||
name: str = ormar.String(name="alias_name", max_length=100)
|
name: str = ormar.String(name="alias_name", max_length=100)
|
||||||
nested: str = ormar.ForeignKey(AliasNested, name="nested_alias")
|
nested = ormar.ForeignKey(AliasNested, name="nested_alias")
|
||||||
|
|
||||||
|
|
||||||
class Toy(ormar.Model):
|
class Toy(ormar.Model):
|
||||||
|
|||||||
108
tests/test_types.py
Normal file
108
tests/test_types.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
from typing import Any, Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
import databases
|
||||||
|
import pytest
|
||||||
|
import sqlalchemy
|
||||||
|
|
||||||
|
import ormar
|
||||||
|
from ormar.relations.querysetproxy import QuerysetProxy
|
||||||
|
from tests.settings import DATABASE_URL
|
||||||
|
|
||||||
|
database = databases.Database(DATABASE_URL)
|
||||||
|
metadata = sqlalchemy.MetaData()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMeta(ormar.ModelMeta):
|
||||||
|
metadata = metadata
|
||||||
|
database = database
|
||||||
|
|
||||||
|
|
||||||
|
class Publisher(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "publishers"
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class Author(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "authors"
|
||||||
|
order_by = ["-name"]
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
name: str = ormar.String(max_length=100)
|
||||||
|
publishers = ormar.ManyToMany(Publisher)
|
||||||
|
|
||||||
|
|
||||||
|
class Book(ormar.Model):
|
||||||
|
class Meta(BaseMeta):
|
||||||
|
tablename = "books"
|
||||||
|
order_by = ["year", "-ranking"]
|
||||||
|
|
||||||
|
id: int = ormar.Integer(primary_key=True)
|
||||||
|
author = ormar.ForeignKey(Author)
|
||||||
|
title: str = ormar.String(max_length=100)
|
||||||
|
year: int = ormar.Integer(nullable=True)
|
||||||
|
ranking: int = ormar.Integer(nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, scope="module")
|
||||||
|
def create_test_database():
|
||||||
|
engine = sqlalchemy.create_engine(DATABASE_URL)
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
metadata.create_all(engine)
|
||||||
|
yield
|
||||||
|
metadata.drop_all(engine)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_type(book: Book):
|
||||||
|
print(book)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_types() -> None:
|
||||||
|
async with database:
|
||||||
|
query = Book.objects
|
||||||
|
publisher = await Publisher(name="Test publisher").save()
|
||||||
|
author = await Author.objects.create(name="Test Author")
|
||||||
|
await author.publishers.add(publisher)
|
||||||
|
author2 = await Author.objects.select_related("publishers").get()
|
||||||
|
publishers = author2.publishers
|
||||||
|
publisher2 = await Publisher.objects.select_related("authors").get()
|
||||||
|
authors = publisher2.authors
|
||||||
|
assert authors[0] == author
|
||||||
|
for author in authors:
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
reveal_type(author) # iter of relation proxy
|
||||||
|
book = await Book.objects.create(title="Test", author=author)
|
||||||
|
book2 = await Book.objects.select_related("author").get()
|
||||||
|
books = await Book.objects.select_related("author").all()
|
||||||
|
author_books = await author.books.all()
|
||||||
|
assert book.author.name == "Test Author"
|
||||||
|
assert book2.author.name == "Test Author"
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
reveal_type(publisher) # model method
|
||||||
|
reveal_type(publishers) # many to many
|
||||||
|
reveal_type(publishers[0]) # item in m2m list
|
||||||
|
# getting relation without __getattribute__
|
||||||
|
to_model = Publisher.Meta.model_fields['authors'].to
|
||||||
|
reveal_type(
|
||||||
|
publisher2._extract_related_model_instead_of_field("authors")
|
||||||
|
) # TODO: wrong
|
||||||
|
reveal_type(publisher.Meta.model_fields['authors'].to) # TODO: wrong
|
||||||
|
reveal_type(authors) # reverse many to many # TODO: wrong
|
||||||
|
reveal_type(book2) # queryset get
|
||||||
|
reveal_type(books) # queryset all
|
||||||
|
reveal_type(book) # queryset - create
|
||||||
|
reveal_type(query) # queryset itself
|
||||||
|
reveal_type(book.author) # fk
|
||||||
|
reveal_type(
|
||||||
|
author.books.queryset_proxy
|
||||||
|
) # queryset in querysetproxy # TODO: wrong
|
||||||
|
reveal_type(author.books) # reverse fk # TODO: wrong
|
||||||
|
reveal_type(author) # another test for queryset get different model
|
||||||
|
reveal_type(book.author.name) # field on related model
|
||||||
|
reveal_type(author_books) # querysetproxy for fk # TODO: wrong
|
||||||
|
reveal_type(author_books[0]) # item i qs proxy for fk # TODO: wrong
|
||||||
|
assert_type(book)
|
||||||
Reference in New Issue
Block a user