From 320588a3c1afa269034d4c7610fb6b112cd1b866 Mon Sep 17 00:00:00 2001 From: collerek Date: Sat, 31 Oct 2020 09:23:24 +0100 Subject: [PATCH 01/19] some typos --- mypy.ini | 4 +++- tests/test_fastapi_docs.py | 2 +- tests/test_unique_constraints.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mypy.ini b/mypy.ini index d9b0283..6347ae1 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,7 @@ [mypy] python_version = 3.8 +plugins = pydantic.mypy [mypy-sqlalchemy.*] -ignore_missing_imports = True \ No newline at end of file +ignore_missing_imports = True + diff --git a/tests/test_fastapi_docs.py b/tests/test_fastapi_docs.py index 030815f..ed956c6 100644 --- a/tests/test_fastapi_docs.py +++ b/tests/test_fastapi_docs.py @@ -77,7 +77,7 @@ async def create_item(item: Item): @app.post("/items/add_category/", response_model=Item) -async def create_item(item: Item, category: Category): +async def add_item_category(item: Item, category: Category): await item.categories.add(category) return item diff --git a/tests/test_unique_constraints.py b/tests/test_unique_constraints.py index 7908ae4..ffa561c 100644 --- a/tests/test_unique_constraints.py +++ b/tests/test_unique_constraints.py @@ -1,7 +1,7 @@ import asyncio import sqlite3 -import asyncpg +import asyncpg # type: ignore import databases import pymysql import pytest From 8fba94efa1d8524eef27a149e23e6b932ef78179 Mon Sep 17 00:00:00 2001 From: collerek Date: Sat, 31 Oct 2020 15:43:34 +0100 Subject: [PATCH 02/19] allow change to build in type hints --- ormar/fields/base.py | 26 +- ormar/fields/foreign_key.py | 22 +- ormar/fields/many_to_many.py | 27 +- ormar/fields/model_fields.py | 28 ++- ormar/models/metaclass.py | 82 +++--- ormar/models/model.py | 31 ++- ormar/models/modelproxy.py | 8 +- ormar/models/newbasemodel.py | 19 +- ormar/queryset/queryset.py | 8 +- ormar/relations/querysetproxy.py | 15 +- ormar/relations/relation.py | 18 +- ormar/relations/relation_manager.py | 6 +- ormar/relations/relation_proxy.py | 2 +- tests/test_fastapi_docs.py | 4 +- tests/test_models.py | 4 +- tests/test_new_annotation_style.py | 374 ++++++++++++++++++++++++++++ tests/test_server_default.py | 30 +-- tests/test_unique_constraints.py | 2 +- 18 files changed, 575 insertions(+), 131 deletions(-) create mode 100644 tests/test_new_annotation_style.py diff --git a/ormar/fields/base.py b/ormar/fields/base.py index 0a47ae4..e57dc64 100644 --- a/ormar/fields/base.py +++ b/ormar/fields/base.py @@ -1,5 +1,6 @@ from typing import Any, List, Optional, TYPE_CHECKING, Type, Union +import pydantic import sqlalchemy from pydantic import Field, typing from pydantic.fields import FieldInfo @@ -11,8 +12,9 @@ if TYPE_CHECKING: # pragma no cover from ormar.models import NewBaseModel -class BaseField: +class BaseField(FieldInfo): __type__ = None + __pydantic_type__ = None column_type: sqlalchemy.Column constraints: List = [] @@ -32,6 +34,28 @@ class BaseField: default: Any server_default: Any + @classmethod + def is_valid_field_info_field(cls, field_name: str) -> bool: + return ( + field_name not in ["default", "default_factory"] + and not field_name.startswith("__") + and hasattr(cls, field_name) + ) + + @classmethod + def convert_to_pydantic_field_info(cls, allow_null: bool = False) -> FieldInfo: + base = cls.default_value() + if base is None: + base = ( + FieldInfo(default=None) + if (cls.nullable or allow_null) + else FieldInfo(default=pydantic.fields.Undefined) + ) + for attr_name in FieldInfo.__dict__.keys(): + if cls.is_valid_field_info_field(attr_name): + setattr(base, attr_name, cls.__dict__.get(attr_name)) + return base + @classmethod def default_value(cls, use_server: bool = False) -> Optional[FieldInfo]: if cls.is_auto_primary_key(): diff --git a/ormar/fields/foreign_key.py b/ormar/fields/foreign_key.py index 854f83d..c2f9911 100644 --- a/ormar/fields/foreign_key.py +++ b/ormar/fields/foreign_key.py @@ -1,4 +1,4 @@ -from typing import Any, Generator, List, Optional, TYPE_CHECKING, Type, Union +from typing import Any, List, Optional, TYPE_CHECKING, Type, Union import sqlalchemy from sqlalchemy import UniqueConstraint @@ -39,8 +39,15 @@ def ForeignKey( # noqa CFQ002 ondelete: str = None, ) -> Type["ForeignKeyField"]: fk_string = to.Meta.tablename + "." + to.get_column_alias(to.Meta.pkname) - to_field = to.__fields__[to.Meta.pkname] + to_field = to.Meta.model_fields[to.Meta.pkname] + __type__ = ( + Union[to_field.__type__, to] + if not nullable + else Optional[Union[to_field.__type__, to]] + ) namespace = dict( + __type__=__type__, + __pydantic_type__=__type__, to=to, name=name, nullable=nullable, @@ -50,7 +57,7 @@ def ForeignKey( # noqa CFQ002 ) ], unique=unique, - column_type=to_field.type_.column_type, + column_type=to_field.column_type, related_name=related_name, virtual=virtual, primary_key=False, @@ -58,7 +65,6 @@ def ForeignKey( # noqa CFQ002 pydantic_only=False, default=None, server_default=None, - __pydantic_model__=to, ) return type("ForeignKey", (ForeignKeyField, BaseField), namespace) @@ -70,14 +76,6 @@ class ForeignKeyField(BaseField): related_name: str virtual: bool - @classmethod - def __get_validators__(cls) -> Generator: - yield cls.validate - - @classmethod - def validate(cls, value: Any) -> Any: - return value - @classmethod def _extract_model_from_sequence( cls, value: List, child: "Model", to_register: bool diff --git a/ormar/fields/many_to_many.py b/ormar/fields/many_to_many.py index 1f73a0d..5c81182 100644 --- a/ormar/fields/many_to_many.py +++ b/ormar/fields/many_to_many.py @@ -1,4 +1,4 @@ -from typing import Dict, TYPE_CHECKING, Type +from typing import Any, List, Optional, TYPE_CHECKING, Type, Union from ormar.fields import BaseField from ormar.fields.foreign_key import ForeignKeyField @@ -15,17 +15,26 @@ def ManyToMany( *, name: str = None, unique: bool = False, - related_name: str = None, virtual: bool = False, + **kwargs: Any ) -> Type["ManyToManyField"]: - to_field = to.__fields__[to.Meta.pkname] + to_field = to.Meta.model_fields[to.Meta.pkname] + related_name = kwargs.pop("related_name", None) + nullable = kwargs.pop("nullable", True) + __type__ = ( + Union[to_field.__type__, to, List[to]] # type: ignore + if not nullable + else Optional[Union[to_field.__type__, to, List[to]]] # type: ignore + ) namespace = dict( + __type__=__type__, + __pydantic_type__=__type__, to=to, through=through, name=name, nullable=True, unique=unique, - column_type=to_field.type_.column_type, + column_type=to_field.column_type, related_name=related_name, virtual=virtual, primary_key=False, @@ -33,9 +42,6 @@ def ManyToMany( pydantic_only=False, default=None, server_default=None, - __pydantic_model__=to, - # __origin__=List, - # __args__=[Optional[to]] ) return type("ManyToMany", (ManyToManyField, BaseField), namespace) @@ -43,10 +49,3 @@ def ManyToMany( class ManyToManyField(ForeignKeyField): through: Type["Model"] - - @classmethod - def __modify_schema__(cls, field_schema: Dict) -> None: - field_schema["type"] = "array" - field_schema["title"] = cls.name.title() - field_schema["definitions"] = {f"{cls.to.__name__}": cls.to.schema()} - field_schema["items"] = {"$ref": f"{REF_PREFIX}{cls.to.__name__}"} diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 5462865..2e98018 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -20,8 +20,9 @@ def is_field_nullable( class ModelFieldFactory: - _bases: Any = BaseField + _bases: Any = (BaseField,) _type: Any = None + _pydantic_type: Any = None def __new__(cls, *args: Any, **kwargs: Any) -> Type[BaseField]: # type: ignore cls.validate(**kwargs) @@ -32,6 +33,7 @@ class ModelFieldFactory: namespace = dict( __type__=cls._type, + __pydantic_type__=cls._pydantic_type, name=kwargs.pop("name", None), primary_key=kwargs.pop("primary_key", False), default=default, @@ -57,8 +59,8 @@ class ModelFieldFactory: class String(ModelFieldFactory): - _bases = (pydantic.ConstrainedStr, BaseField) _type = str + _pydantic_type = pydantic.ConstrainedStr def __new__( # type: ignore # noqa CFQ002 cls, @@ -96,8 +98,8 @@ class String(ModelFieldFactory): class Integer(ModelFieldFactory): - _bases = (pydantic.ConstrainedInt, BaseField) _type = int + _pydantic_type = pydantic.ConstrainedInt def __new__( # type: ignore cls, @@ -131,8 +133,8 @@ class Integer(ModelFieldFactory): class Text(ModelFieldFactory): - _bases = (pydantic.ConstrainedStr, BaseField) _type = str + _pydantic_type = pydantic.ConstrainedStr def __new__( # type: ignore cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any @@ -154,8 +156,8 @@ class Text(ModelFieldFactory): class Float(ModelFieldFactory): - _bases = (pydantic.ConstrainedFloat, BaseField) _type = float + _pydantic_type = pydantic.ConstrainedFloat def __new__( # type: ignore cls, @@ -183,8 +185,8 @@ class Float(ModelFieldFactory): class Boolean(ModelFieldFactory): - _bases = (int, BaseField) _type = bool + _pydantic_type = bool @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -192,8 +194,8 @@ class Boolean(ModelFieldFactory): class DateTime(ModelFieldFactory): - _bases = (datetime.datetime, BaseField) _type = datetime.datetime + _pydantic_type = datetime.datetime @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -201,8 +203,8 @@ class DateTime(ModelFieldFactory): class Date(ModelFieldFactory): - _bases = (datetime.date, BaseField) _type = datetime.date + _pydantic_type = datetime.date @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -210,8 +212,8 @@ class Date(ModelFieldFactory): class Time(ModelFieldFactory): - _bases = (datetime.time, BaseField) _type = datetime.time + _pydantic_type = datetime.time @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -219,8 +221,8 @@ class Time(ModelFieldFactory): class JSON(ModelFieldFactory): - _bases = (pydantic.Json, BaseField) _type = pydantic.Json + _pydantic_type = pydantic.Json @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -228,8 +230,8 @@ class JSON(ModelFieldFactory): class BigInteger(Integer): - _bases = (pydantic.ConstrainedInt, BaseField) _type = int + _pydantic_type = pydantic.ConstrainedInt def __new__( # type: ignore cls, @@ -263,8 +265,8 @@ class BigInteger(Integer): class Decimal(ModelFieldFactory): - _bases = (pydantic.ConstrainedDecimal, BaseField) _type = decimal.Decimal + _pydantic_type = pydantic.ConstrainedDecimal def __new__( # type: ignore # noqa CFQ002 cls, @@ -318,8 +320,8 @@ class Decimal(ModelFieldFactory): class UUID(ModelFieldFactory): - _bases = (uuid.UUID, BaseField) _type = uuid.UUID + _pydantic_type = uuid.UUID @classmethod def get_column_type(cls, **kwargs: Any) -> Any: diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index d7c57b9..798424b 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -5,7 +5,8 @@ import databases import pydantic import sqlalchemy from pydantic import BaseConfig -from pydantic.fields import FieldInfo, ModelField +from pydantic.fields import ModelField +from pydantic.utils import lenient_issubclass from sqlalchemy.sql.schema import ColumnCollectionConstraint import ormar # noqa I100 @@ -179,44 +180,58 @@ def register_relation_in_alias_manager( def populate_default_pydantic_field_value( - type_: Type[BaseField], field: str, attrs: dict + ormar_field: Type[BaseField], field_name: str, attrs: dict ) -> dict: - def_value = type_.default_value() - curr_def_value = attrs.get(field, "NONE") - if curr_def_value == "NONE" and isinstance(def_value, FieldInfo): - attrs[field] = def_value - elif curr_def_value == "NONE" and type_.nullable: - attrs[field] = FieldInfo(default=None) + curr_def_value = attrs.get(field_name, ormar.Undefined) + if lenient_issubclass(curr_def_value, ormar.fields.BaseField): + curr_def_value = ormar.Undefined + if curr_def_value is None: + attrs[field_name] = ormar_field.convert_to_pydantic_field_info(allow_null=True) + else: + attrs[field_name] = ormar_field.convert_to_pydantic_field_info() return attrs -def populate_pydantic_default_values(attrs: Dict) -> Dict: - for field, type_ in attrs["__annotations__"].items(): - if issubclass(type_, BaseField): - if type_.name is None: - type_.name = field - attrs = populate_default_pydantic_field_value(type_, field, attrs) - return attrs +def check_if_field_annotation_or_value_is_ormar( + field: Any, field_name: str, attrs: Dict +) -> bool: + return lenient_issubclass(field, BaseField) or issubclass( + attrs.get(field_name, type), BaseField + ) -def extract_annotations_and_default_vals(attrs: dict, bases: Tuple) -> dict: +def extract_field_from_annotation_or_value( + field: Any, field_name: str, attrs: Dict +) -> Type[ormar.fields.BaseField]: + return field if lenient_issubclass(field, BaseField) else attrs.get(field_name) + + +def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: + model_fields = {} + for field_name, field in attrs["__annotations__"].items(): + # ormar fields can be used as annotation or as default value + if check_if_field_annotation_or_value_is_ormar(field, field_name, attrs): + ormar_field = extract_field_from_annotation_or_value( + field, field_name, attrs + ) + if ormar_field.name is None: + ormar_field.name = field_name + attrs = populate_default_pydantic_field_value( + ormar_field, field_name, attrs + ) + model_fields[field_name] = ormar_field + attrs["__annotations__"][field_name] = ormar_field.__type__ + return attrs, model_fields + + +def extract_annotations_and_default_vals( + attrs: dict, bases: Tuple +) -> Tuple[Dict, Dict]: attrs["__annotations__"] = attrs.get("__annotations__") or bases[0].__dict__.get( "__annotations__", {} ) - attrs = populate_pydantic_default_values(attrs) - return attrs - - -def populate_meta_orm_model_fields( - attrs: dict, new_model: Type["Model"] -) -> Type["Model"]: - model_fields = { - field_name: field - for field_name, field in attrs["__annotations__"].items() - if issubclass(field, BaseField) - } - new_model.Meta.model_fields = model_fields - return new_model + attrs, model_fields = populate_pydantic_default_values(attrs) + return attrs, model_fields def populate_meta_tablename_columns_and_pk( @@ -305,7 +320,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): ) -> "ModelMetaclass": attrs["Config"] = get_pydantic_base_orm_config() attrs["__name__"] = name - attrs = extract_annotations_and_default_vals(attrs, bases) + attrs, model_fields = extract_annotations_and_default_vals(attrs, bases) new_model = super().__new__( # type: ignore mcs, name, bases, attrs ) @@ -313,7 +328,8 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): if hasattr(new_model, "Meta"): if not hasattr(new_model.Meta, "constraints"): new_model.Meta.constraints = [] - new_model = populate_meta_orm_model_fields(attrs, new_model) + if not hasattr(new_model.Meta, "model_fields"): + new_model.Meta.model_fields = model_fields new_model = populate_meta_tablename_columns_and_pk(name, new_model) new_model = populate_meta_sqlalchemy_table_if_required(new_model) expand_reverse_relationships(new_model) @@ -322,7 +338,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): if new_model.Meta.pkname not in attrs["__annotations__"]: field_name = new_model.Meta.pkname field = Integer(name=field_name, primary_key=True) - attrs["__annotations__"][field_name] = field + attrs["__annotations__"][field_name] = Optional[int] # type: ignore populate_default_pydantic_field_value( field, field_name, attrs # type: ignore ) diff --git a/ormar/models/model.py b/ormar/models/model.py index d0b07e4..f8884b1 100644 --- a/ormar/models/model.py +++ b/ormar/models/model.py @@ -1,11 +1,12 @@ import itertools -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Type, TypeVar import sqlalchemy import ormar.queryset # noqa I100 from ormar.fields.many_to_many import ManyToManyField from ormar.models import NewBaseModel # noqa I100 +from ormar.models.metaclass import ModelMeta def group_related_list(list_: List) -> Dict: @@ -23,18 +24,30 @@ def group_related_list(list_: List) -> Dict: return test_dict +T = TypeVar("T", bound="Model") + + class Model(NewBaseModel): __abstract__ = False + if TYPE_CHECKING: # pragma nocover + Meta: ModelMeta + + def __repr__(self) -> str: # pragma nocover + attrs_to_include = ["tablename", "columns", "pkname"] + _repr = {k: v for k, v in self.Meta.model_fields.items()} + for atr in attrs_to_include: + _repr[atr] = getattr(self.Meta, atr) + return f"{self.__class__.__name__}({str(_repr)})" @classmethod def from_row( # noqa CCR001 - cls, + cls: Type[T], row: sqlalchemy.engine.ResultProxy, select_related: List = None, related_models: Any = None, previous_table: str = None, fields: List = None, - ) -> Optional["Model"]: + ) -> Optional[T]: item: Dict[str, Any] = {} select_related = select_related or [] @@ -66,7 +79,9 @@ class Model(NewBaseModel): item, row, table_prefix, fields, nested=table_prefix != "" ) - instance = cls(**item) if item.get(cls.Meta.pkname, None) is not None else None + instance: Optional[T] = cls(**item) if item.get( + cls.Meta.pkname, None + ) is not None else None return instance @classmethod @@ -124,7 +139,7 @@ class Model(NewBaseModel): return item - async def save(self) -> "Model": + async def save(self: T) -> T: self_fields = self._extract_model_db_fields() if not self.pk and self.Meta.model_fields[self.Meta.pkname].autoincrement: @@ -137,7 +152,7 @@ class Model(NewBaseModel): setattr(self, self.Meta.pkname, item_id) return self - async def update(self, **kwargs: Any) -> "Model": + async def update(self: T, **kwargs: Any) -> T: if kwargs: new_values = {**self.dict(), **kwargs} self.from_dict(new_values) @@ -151,13 +166,13 @@ class Model(NewBaseModel): await self.Meta.database.execute(expr) return self - async def delete(self) -> int: + async def delete(self: T) -> int: expr = self.Meta.table.delete() expr = expr.where(self.pk_column == (getattr(self, self.Meta.pkname))) result = await self.Meta.database.execute(expr) return result - async def load(self) -> "Model": + async def load(self: T) -> T: expr = self.Meta.table.select().where(self.pk_column == self.pk) row = await self.Meta.database.fetch_one(expr) if not row: # pragma nocover diff --git a/ormar/models/modelproxy.py b/ormar/models/modelproxy.py index 56ee930..fb0e789 100644 --- a/ormar/models/modelproxy.py +++ b/ormar/models/modelproxy.py @@ -1,5 +1,5 @@ import inspect -from typing import Dict, List, Set, TYPE_CHECKING, Type, TypeVar, Union +from typing import Dict, List, Sequence, Set, TYPE_CHECKING, Type, TypeVar, Union import ormar from ormar.exceptions import RelationshipInstanceError @@ -11,6 +11,8 @@ if TYPE_CHECKING: # pragma no cover from ormar import Model from ormar.models import NewBaseModel + T = TypeVar("T", bound=Model) + Field = TypeVar("Field", bound=BaseField) @@ -135,7 +137,7 @@ class ModelTableProxy: if field.to == related.__class__ or field.to.Meta == related.Meta: return name # fallback for not registered relation - if register_missing: + if register_missing: # pragma nocover expand_reverse_relationships(related.__class__) # type: ignore return ModelTableProxy.resolve_relation_name( item, related, register_missing=False @@ -177,7 +179,7 @@ class ModelTableProxy: return new_kwargs @classmethod - def merge_instances_list(cls, result_rows: List["Model"]) -> List["Model"]: + def merge_instances_list(cls, result_rows: Sequence["Model"]) -> Sequence["Model"]: merged_rows: List["Model"] = [] for index, model in enumerate(result_rows): if index > 0 and model is not None and model.pk == merged_rows[-1].pk: diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index faab1c1..72083d3 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -5,11 +5,12 @@ from typing import ( Any, Callable, Dict, - List, Mapping, Optional, + Sequence, TYPE_CHECKING, Type, + TypeVar, Union, ) @@ -27,7 +28,9 @@ from ormar.relations.alias_manager import AliasManager from ormar.relations.relation_manager import RelationsManager if TYPE_CHECKING: # pragma no cover - from ormar.models.model import Model + from ormar import Model + + T = TypeVar("T", bound=Model) IntStr = Union[int, str] DictStrAny = Dict[str, Any] @@ -52,7 +55,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass Meta: ModelMeta # noinspection PyMissingConstructor - def __init__(self, *args: Any, **kwargs: Any) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: # type: ignore object.__setattr__(self, "_orm_id", uuid.uuid4().hex) object.__setattr__(self, "_orm_saved", False) @@ -73,7 +76,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass if "pk" in kwargs: kwargs[self.Meta.pkname] = kwargs.pop("pk") # build the models to set them and validate but don't register - kwargs = { + new_kwargs = { k: self._convert_json( k, self.Meta.model_fields[k].expand_relationship( @@ -85,7 +88,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass } values, fields_set, validation_error = pydantic.validate_model( - self, kwargs # type: ignore + self, new_kwargs # type: ignore ) if validation_error and not pk_only: raise validation_error @@ -96,7 +99,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass # register the columns models after initialization for related in self.extract_related_names(): self.Meta.model_fields[related].expand_relationship( - kwargs.get(related), self, to_register=True + new_kwargs.get(related), self, to_register=True ) def __setattr__(self, name: str, value: Any) -> None: # noqa CCR001 @@ -133,7 +136,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass def _extract_related_model_instead_of_field( self, item: str - ) -> Optional[Union["Model", List["Model"]]]: + ) -> Optional[Union[T, Sequence[T]]]: alias = self.get_column_alias(item) if alias in self._orm: return self._orm.get(alias) @@ -170,7 +173,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass def db_backend_name(cls) -> str: return cls.Meta.database._backend._dialect.name - def remove(self, name: "Model") -> None: + def remove(self, name: T) -> None: self._orm.remove_parent(self, name) def dict( # noqa A003 diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 1decda1..5b58c55 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, TYPE_CHECKING, Type, Union +from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Type, Union import databases import sqlalchemy @@ -59,7 +59,7 @@ class QuerySet: raise ValueError("Model class of QuerySet is not initialized") return self.model_cls - def _process_query_result_rows(self, rows: List) -> List[Optional["Model"]]: + def _process_query_result_rows(self, rows: List) -> Sequence[Optional["Model"]]: result_rows = [ self.model.from_row( row, select_related=self._select_related, fields=self._columns @@ -87,7 +87,7 @@ class QuerySet: return new_kwargs @staticmethod - def check_single_result_rows_count(rows: List[Optional["Model"]]) -> None: + def check_single_result_rows_count(rows: Sequence[Optional["Model"]]) -> None: if not rows or rows[0] is None: raise NoMatch() if len(rows) > 1: @@ -267,7 +267,7 @@ class QuerySet: model = await self.get(pk=kwargs[pk_name]) return await model.update(**kwargs) - async def all(self, **kwargs: Any) -> List[Optional["Model"]]: # noqa: A003 + async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003 if kwargs: return await self.filter(**kwargs).all() diff --git a/ormar/relations/querysetproxy.py b/ormar/relations/querysetproxy.py index f71de87..b9fb250 100644 --- a/ormar/relations/querysetproxy.py +++ b/ormar/relations/querysetproxy.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, TYPE_CHECKING, Union +from typing import Any, List, Optional, Sequence, TYPE_CHECKING, TypeVar, Union import ormar @@ -7,6 +7,8 @@ if TYPE_CHECKING: # pragma no cover from ormar.models import Model from ormar.queryset import QuerySet + T = TypeVar("T", bound=Model) + class QuerysetProxy: if TYPE_CHECKING: # pragma no cover @@ -26,27 +28,28 @@ class QuerysetProxy: def queryset(self, value: "QuerySet") -> None: self._queryset = value - def _assign_child_to_parent(self, child: Optional["Model"]) -> None: + def _assign_child_to_parent(self, child: Optional[T]) -> None: if child: owner = self.relation._owner rel_name = owner.resolve_relation_name(owner, child) setattr(owner, rel_name, child) - def _register_related(self, child: Union["Model", List[Optional["Model"]]]) -> None: + def _register_related(self, child: Union[T, Sequence[Optional[T]]]) -> None: if isinstance(child, list): for subchild in child: self._assign_child_to_parent(subchild) else: + assert isinstance(child, Model) self._assign_child_to_parent(child) - async def create_through_instance(self, child: "Model") -> None: + async def create_through_instance(self, child: T) -> None: queryset = ormar.QuerySet(model_cls=self.relation.through) owner_column = self.relation._owner.get_name() child_column = child.get_name() kwargs = {owner_column: self.relation._owner, child_column: child} await queryset.create(**kwargs) - async def delete_through_instance(self, child: "Model") -> None: + async def delete_through_instance(self, child: T) -> None: queryset = ormar.QuerySet(model_cls=self.relation.through) owner_column = self.relation._owner.get_name() child_column = child.get_name() @@ -88,7 +91,7 @@ class QuerysetProxy: self._register_related(get) return get - async def all(self, **kwargs: Any) -> List[Optional["Model"]]: # noqa: A003 + async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003 all_items = await self.queryset.all(**kwargs) self._register_related(all_items) return all_items diff --git a/ormar/relations/relation.py b/ormar/relations/relation.py index 91e00df..6520702 100644 --- a/ormar/relations/relation.py +++ b/ormar/relations/relation.py @@ -1,5 +1,5 @@ from enum import Enum -from typing import List, Optional, TYPE_CHECKING, Type, Union +from typing import List, Optional, TYPE_CHECKING, Type, TypeVar, Union import ormar # noqa I100 from ormar.exceptions import RelationshipInstanceError # noqa I100 @@ -11,6 +11,8 @@ if TYPE_CHECKING: # pragma no cover from ormar.relations import RelationsManager from ormar.models import NewBaseModel + T = TypeVar("T", bound=Model) + class RelationType(Enum): PRIMARY = 1 @@ -23,15 +25,15 @@ class Relation: self, manager: "RelationsManager", type_: RelationType, - to: Type["Model"], - through: Type["Model"] = None, + to: Type[T], + through: Type[T] = None, ) -> None: self.manager = manager self._owner: "Model" = manager.owner self._type: RelationType = type_ - self.to: Type["Model"] = to - self.through: Optional[Type["Model"]] = through - self.related_models: Optional[Union[RelationProxy, "Model"]] = ( + self.to: Type[T] = to + self.through: Optional[Type[T]] = through + self.related_models: Optional[Union[RelationProxy, T]] = ( RelationProxy(relation=self) if type_ in (RelationType.REVERSE, RelationType.MULTIPLE) else None @@ -50,7 +52,7 @@ class Relation: self.related_models.pop(ind) return None - def add(self, child: "Model") -> None: + def add(self, child: T) -> None: relation_name = self._owner.resolve_relation_name(self._owner, child) if self._type == RelationType.PRIMARY: self.related_models = child @@ -77,7 +79,7 @@ class Relation: self.related_models.pop(position) # type: ignore del self._owner.__dict__[relation_name][position] - def get(self) -> Optional[Union[List["Model"], "Model"]]: + def get(self) -> Optional[Union[List[T], T]]: return self.related_models def __repr__(self) -> str: # pragma no cover diff --git a/ormar/relations/relation_manager.py b/ormar/relations/relation_manager.py index 6e7eb24..82a6887 100644 --- a/ormar/relations/relation_manager.py +++ b/ormar/relations/relation_manager.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional, TYPE_CHECKING, Type, Union +from typing import Dict, List, Optional, Sequence, TYPE_CHECKING, Type, TypeVar, Union from weakref import proxy from ormar.fields import BaseField @@ -14,6 +14,8 @@ if TYPE_CHECKING: # pragma no cover from ormar import Model from ormar.models import NewBaseModel + T = TypeVar("T", bound=Model) + class RelationsManager: def __init__( @@ -46,7 +48,7 @@ class RelationsManager: def __contains__(self, item: str) -> bool: return item in self._related_names - def get(self, name: str) -> Optional[Union[List["Model"], "Model"]]: + def get(self, name: str) -> Optional[Union[T, Sequence[T]]]: relation = self._relations.get(name, None) if relation is not None: return relation.get() diff --git a/ormar/relations/relation_proxy.py b/ormar/relations/relation_proxy.py index 29e4b97..806a07d 100644 --- a/ormar/relations/relation_proxy.py +++ b/ormar/relations/relation_proxy.py @@ -72,6 +72,6 @@ class RelationProxy(list): if self.relation._type == ormar.RelationType.MULTIPLE: await self.queryset_proxy.create_through_instance(item) rel_name = item.resolve_relation_name(item, self._owner) - if rel_name not in item._orm: + if rel_name not in item._orm: # pragma nocover item._orm._add_relation(item.Meta.model_fields[rel_name]) setattr(item, rel_name, self._owner) diff --git a/tests/test_fastapi_docs.py b/tests/test_fastapi_docs.py index ed956c6..4cb1df3 100644 --- a/tests/test_fastapi_docs.py +++ b/tests/test_fastapi_docs.py @@ -125,7 +125,9 @@ def test_all_endpoints(): def test_schema_modification(): schema = Item.schema() - assert schema["properties"]["categories"]["type"] == "array" + assert any( + x.get("type") == "array" for x in schema["properties"]["categories"]["anyOf"] + ) assert schema["properties"]["categories"]["title"] == "Categories" diff --git a/tests/test_models.py b/tests/test_models.py index 47b5d50..cc783e3 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -98,9 +98,9 @@ async def create_test_database(): def test_model_class(): assert list(User.Meta.model_fields.keys()) == ["id", "name"] - assert issubclass(User.Meta.model_fields["id"], pydantic.ConstrainedInt) + assert issubclass(User.Meta.model_fields["id"], pydantic.fields.FieldInfo) assert User.Meta.model_fields["id"].primary_key is True - assert issubclass(User.Meta.model_fields["name"], pydantic.ConstrainedStr) + assert issubclass(User.Meta.model_fields["name"], pydantic.fields.FieldInfo) assert User.Meta.model_fields["name"].max_length == 100 assert isinstance(User.Meta.table, sqlalchemy.Table) diff --git a/tests/test_new_annotation_style.py b/tests/test_new_annotation_style.py new file mode 100644 index 0000000..9a665cc --- /dev/null +++ b/tests/test_new_annotation_style.py @@ -0,0 +1,374 @@ +from typing import Optional + +import databases +import pytest +import sqlalchemy + +import ormar +from ormar.exceptions import NoMatch, MultipleMatches, RelationshipInstanceError +from tests.settings import DATABASE_URL + +database = databases.Database(DATABASE_URL, force_rollback=True) +metadata = sqlalchemy.MetaData() + + +class Album(ormar.Model): + class Meta: + tablename = "albums" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + + +class Track(ormar.Model): + class Meta: + tablename = "tracks" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + album: Optional[Album] = ormar.ForeignKey(Album) + title: str = ormar.String(max_length=100) + position: int = ormar.Integer() + + +class Cover(ormar.Model): + class Meta: + tablename = "covers" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + album: Album = ormar.ForeignKey(Album, related_name="cover_pictures") + title: str = ormar.String(max_length=100) + + +class Organisation(ormar.Model): + class Meta: + tablename = "org" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + ident: str = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) + + +class Team(ormar.Model): + class Meta: + tablename = "teams" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + org: Optional[Organisation] = ormar.ForeignKey(Organisation) + name: str = ormar.String(max_length=100) + + +class Member(ormar.Model): + class Meta: + tablename = "members" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + team: Optional[Team] = ormar.ForeignKey(Team) + email: str = ormar.String(max_length=100) + + +@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) + + +@pytest.mark.asyncio +async def test_wrong_query_foreign_key_type(): + async with database: + with pytest.raises(RelationshipInstanceError): + Track(title="The Error", album="wrong_pk_type") + + +@pytest.mark.asyncio +async def test_setting_explicitly_empty_relation(): + async with database: + track = Track(album=None, title="The Bird", position=1) + assert track.album is None + + +@pytest.mark.asyncio +async def test_related_name(): + async with database: + async with database.transaction(force_rollback=True): + album = await Album.objects.create(name="Vanilla") + await Cover.objects.create(album=album, title="The cover file") + assert len(album.cover_pictures) == 1 + + +@pytest.mark.asyncio +async def test_model_crud(): + async with database: + async with database.transaction(force_rollback=True): + album = Album(name="Jamaica") + await album.save() + track1 = Track(album=album, title="The Bird", position=1) + track2 = Track(album=album, title="Heart don't stand a chance", position=2) + track3 = Track(album=album, title="The Waters", position=3) + await track1.save() + await track2.save() + await track3.save() + + track = await Track.objects.get(title="The Bird") + assert track.album.pk == album.pk + assert isinstance(track.album, ormar.Model) + assert track.album.name is None + await track.album.load() + assert track.album.name == "Jamaica" + + assert len(album.tracks) == 3 + assert album.tracks[1].title == "Heart don't stand a chance" + + album1 = await Album.objects.get(name="Jamaica") + assert album1.pk == album.pk + assert album1.tracks == [] + + await Track.objects.create( + album={"id": track.album.pk}, title="The Bird2", position=4 + ) + + +@pytest.mark.asyncio +async def test_select_related(): + async with database: + async with database.transaction(force_rollback=True): + album = Album(name="Malibu") + await album.save() + track1 = Track(album=album, title="The Bird", position=1) + track2 = Track(album=album, title="Heart don't stand a chance", position=2) + track3 = Track(album=album, title="The Waters", position=3) + await track1.save() + await track2.save() + await track3.save() + + fantasies = Album(name="Fantasies") + await fantasies.save() + track4 = Track(album=fantasies, title="Help I'm Alive", position=1) + track5 = Track(album=fantasies, title="Sick Muse", position=2) + track6 = Track(album=fantasies, title="Satellite Mind", position=3) + await track4.save() + await track5.save() + await track6.save() + + track = await Track.objects.select_related("album").get(title="The Bird") + assert track.album.name == "Malibu" + + tracks = await Track.objects.select_related("album").all() + assert len(tracks) == 6 + + +@pytest.mark.asyncio +async def test_model_removal_from_relations(): + async with database: + async with database.transaction(force_rollback=True): + album = Album(name="Chichi") + await album.save() + track1 = Track(album=album, title="The Birdman", position=1) + track2 = Track(album=album, title="Superman", position=2) + track3 = Track(album=album, title="Wonder Woman", position=3) + await track1.save() + await track2.save() + await track3.save() + + assert len(album.tracks) == 3 + await album.tracks.remove(track1) + assert len(album.tracks) == 2 + assert track1.album is None + + await track1.update() + track1 = await Track.objects.get(title="The Birdman") + assert track1.album is None + + await album.tracks.add(track1) + assert len(album.tracks) == 3 + assert track1.album == album + + await track1.update() + track1 = await Track.objects.select_related("album__tracks").get( + title="The Birdman" + ) + album = await Album.objects.select_related("tracks").get(name="Chichi") + assert track1.album == album + + track1.remove(album) + assert track1.album is None + assert len(album.tracks) == 2 + + track2.remove(album) + assert track2.album is None + assert len(album.tracks) == 1 + + +@pytest.mark.asyncio +async def test_fk_filter(): + async with database: + async with database.transaction(force_rollback=True): + malibu = Album(name="Malibu%") + await malibu.save() + await Track.objects.create(album=malibu, title="The Bird", position=1) + await Track.objects.create( + album=malibu, title="Heart don't stand a chance", position=2 + ) + await Track.objects.create(album=malibu, title="The Waters", position=3) + + fantasies = await Album.objects.create(name="Fantasies") + await Track.objects.create( + album=fantasies, title="Help I'm Alive", position=1 + ) + await Track.objects.create(album=fantasies, title="Sick Muse", position=2) + await Track.objects.create( + album=fantasies, title="Satellite Mind", position=3 + ) + + tracks = ( + await Track.objects.select_related("album") + .filter(album__name="Fantasies") + .all() + ) + assert len(tracks) == 3 + for track in tracks: + assert track.album.name == "Fantasies" + + tracks = ( + await Track.objects.select_related("album") + .filter(album__name__icontains="fan") + .all() + ) + assert len(tracks) == 3 + for track in tracks: + assert track.album.name == "Fantasies" + + tracks = await Track.objects.filter(album__name__contains="Fan").all() + assert len(tracks) == 3 + for track in tracks: + assert track.album.name == "Fantasies" + + tracks = await Track.objects.filter(album__name__contains="Malibu%").all() + assert len(tracks) == 3 + + tracks = ( + await Track.objects.filter(album=malibu).select_related("album").all() + ) + assert len(tracks) == 3 + for track in tracks: + assert track.album.name == "Malibu%" + + tracks = await Track.objects.select_related("album").all(album=malibu) + assert len(tracks) == 3 + for track in tracks: + assert track.album.name == "Malibu%" + + +@pytest.mark.asyncio +async def test_multiple_fk(): + async with database: + async with database.transaction(force_rollback=True): + acme = await Organisation.objects.create(ident="ACME Ltd") + red_team = await Team.objects.create(org=acme, name="Red Team") + blue_team = await Team.objects.create(org=acme, name="Blue Team") + await Member.objects.create(team=red_team, email="a@example.org") + await Member.objects.create(team=red_team, email="b@example.org") + await Member.objects.create(team=blue_team, email="c@example.org") + await Member.objects.create(team=blue_team, email="d@example.org") + + other = await Organisation.objects.create(ident="Other ltd") + team = await Team.objects.create(org=other, name="Green Team") + await Member.objects.create(team=team, email="e@example.org") + + members = ( + await Member.objects.select_related("team__org") + .filter(team__org__ident="ACME Ltd") + .all() + ) + assert len(members) == 4 + for member in members: + assert member.team.org.ident == "ACME Ltd" + + +@pytest.mark.asyncio +async def test_wrong_choices(): + async with database: + async with database.transaction(force_rollback=True): + with pytest.raises(ValueError): + await Organisation.objects.create(ident="Test 1") + + +@pytest.mark.asyncio +async def test_pk_filter(): + async with database: + async with database.transaction(force_rollback=True): + fantasies = await Album.objects.create(name="Test") + track = await Track.objects.create( + album=fantasies, title="Test1", position=1 + ) + await Track.objects.create(album=fantasies, title="Test2", position=2) + await Track.objects.create(album=fantasies, title="Test3", position=3) + tracks = ( + await Track.objects.select_related("album").filter(pk=track.pk).all() + ) + assert len(tracks) == 1 + + tracks = ( + await Track.objects.select_related("album") + .filter(position=2, album__name="Test") + .all() + ) + assert len(tracks) == 1 + + +@pytest.mark.asyncio +async def test_limit_and_offset(): + async with database: + async with database.transaction(force_rollback=True): + fantasies = await Album.objects.create(name="Limitless") + await Track.objects.create( + id=None, album=fantasies, title="Sample", position=1 + ) + await Track.objects.create(album=fantasies, title="Sample2", position=2) + await Track.objects.create(album=fantasies, title="Sample3", position=3) + + tracks = await Track.objects.limit(1).all() + assert len(tracks) == 1 + assert tracks[0].title == "Sample" + + tracks = await Track.objects.limit(1).offset(1).all() + assert len(tracks) == 1 + assert tracks[0].title == "Sample2" + + +@pytest.mark.asyncio +async def test_get_exceptions(): + async with database: + async with database.transaction(force_rollback=True): + fantasies = await Album.objects.create(name="Test") + + with pytest.raises(NoMatch): + await Album.objects.get(name="Test2") + + await Track.objects.create(album=fantasies, title="Test1", position=1) + await Track.objects.create(album=fantasies, title="Test2", position=2) + await Track.objects.create(album=fantasies, title="Test3", position=3) + with pytest.raises(MultipleMatches): + await Track.objects.select_related("album").get(album=fantasies) + + +@pytest.mark.asyncio +async def test_wrong_model_passed_as_fk(): + async with database: + async with database.transaction(force_rollback=True): + with pytest.raises(RelationshipInstanceError): + org = await Organisation.objects.create(ident="ACME Ltd") + await Track.objects.create(album=org, title="Test1", position=1) diff --git a/tests/test_server_default.py b/tests/test_server_default.py index b9106d0..4c4db66 100644 --- a/tests/test_server_default.py +++ b/tests/test_server_default.py @@ -22,7 +22,7 @@ class Product(ormar.Model): id: ormar.Integer(primary_key=True) name: ormar.String(max_length=100) - company: ormar.String(max_length=200, server_default='Acme') + company: ormar.String(max_length=200, server_default="Acme") sort_order: ormar.Integer(server_default=text("10")) created: ormar.DateTime(server_default=func.now()) @@ -44,42 +44,44 @@ async def create_test_database(): def test_table_defined_properly(): - assert Product.Meta.model_fields['created'].nullable - assert not Product.__fields__['created'].required - assert Product.Meta.table.columns['created'].server_default.arg.name == 'now' + assert Product.Meta.model_fields["created"].nullable + assert not Product.__fields__["created"].required + assert Product.Meta.table.columns["created"].server_default.arg.name == "now" @pytest.mark.asyncio async def test_model_creation(): async with database: async with database.transaction(force_rollback=True): - p1 = Product(name='Test') + p1 = Product(name="Test") assert p1.created is None await p1.save() await p1.load() assert p1.created is not None - assert p1.company == 'Acme' + assert p1.company == "Acme" assert p1.sort_order == 10 - date = datetime.strptime('2020-10-27 11:30', '%Y-%m-%d %H:%M') - p3 = await Product.objects.create(name='Test2', created=date, company='Roadrunner', sort_order=1) + date = datetime.strptime("2020-10-27 11:30", "%Y-%m-%d %H:%M") + p3 = await Product.objects.create( + name="Test2", created=date, company="Roadrunner", sort_order=1 + ) assert p3.created is not None assert p3.created == date assert p1.created != p3.created - assert p3.company == 'Roadrunner' + assert p3.company == "Roadrunner" assert p3.sort_order == 1 - p3 = await Product.objects.get(name='Test2') - assert p3.company == 'Roadrunner' + p3 = await Product.objects.get(name="Test2") + assert p3.company == "Roadrunner" assert p3.sort_order == 1 time.sleep(1) - p2 = await Product.objects.create(name='Test3') + p2 = await Product.objects.create(name="Test3") assert p2.created is not None - assert p2.company == 'Acme' + assert p2.company == "Acme" assert p2.sort_order == 10 - if Product.db_backend_name() != 'postgresql': + if Product.db_backend_name() != "postgresql": # postgres use transaction timestamp so it will remain the same assert p1.created != p2.created # pragma nocover diff --git a/tests/test_unique_constraints.py b/tests/test_unique_constraints.py index ffa561c..93dd9ba 100644 --- a/tests/test_unique_constraints.py +++ b/tests/test_unique_constraints.py @@ -1,7 +1,7 @@ import asyncio import sqlite3 -import asyncpg # type: ignore +import asyncpg # type: ignore import databases import pymysql import pytest From 7d5e291a19e2ed21f264a17970e6ffd31ef1c73c Mon Sep 17 00:00:00 2001 From: collerek Date: Sat, 31 Oct 2020 18:11:48 +0100 Subject: [PATCH 03/19] switch to equals in most of the code, fix dependencies, clean tests, make all not relation fields work with type hints --- README.md | 12 +-- docs/index.md | 12 +-- docs/models.md | 2 +- docs/queries.md | 50 +++++------ docs_src/fastapi/docs001.py | 12 +-- docs_src/fields/docs001.py | 14 +-- docs_src/fields/docs002.py | 14 +-- docs_src/fields/docs003.py | 14 +-- docs_src/fields/docs004.py | 12 +-- docs_src/models/docs001.py | 6 +- docs_src/models/docs002.py | 6 +- docs_src/models/docs003.py | 6 +- docs_src/models/docs004.py | 6 +- docs_src/models/docs005.py | 6 +- docs_src/models/docs008.py | 8 +- docs_src/models/docs009.py | 6 +- docs_src/models/docs010.py | 10 +-- docs_src/queries/docs001.py | 14 +-- docs_src/relations/docs001.py | 14 +-- docs_src/relations/docs002.py | 20 +++-- mypy.ini | 3 + ormar/fields/many_to_many.py | 71 +++++++++++++-- ormar/fields/model_fields.py | 102 +++++++++++----------- ormar/models/metaclass.py | 65 +++++++------- ormar/models/model.py | 46 +++++----- ormar/models/newbasemodel.py | 4 +- ormar/queryset/queryset.py | 2 +- ormar/relations/querysetproxy.py | 10 +-- ormar/relations/relation.py | 14 +-- ormar/relations/relation_manager.py | 2 +- tests/test_aliases.py | 26 +++--- tests/test_columns.py | 17 ++-- tests/test_fastapi_docs.py | 12 +-- tests/test_fastapi_usage.py | 12 +-- tests/test_foreign_keys.py | 56 ++++++------ tests/test_many_to_many.py | 21 +++-- tests/test_model_definition.py | 83 +++++++++++------- tests/test_models.py | 30 +++---- tests/test_more_reallife_fastapi.py | 12 +-- tests/test_more_same_table_joins.py | 31 +++---- tests/test_new_annotation_style.py | 34 ++++---- tests/test_non_integer_pkey.py | 4 +- tests/test_queryset_level_methods.py | 26 +++--- tests/test_same_table_joins.py | 31 +++---- tests/test_selecting_subset_of_columns.py | 22 ++--- tests/test_server_default.py | 10 +-- tests/test_unique_constraints.py | 6 +- 47 files changed, 558 insertions(+), 438 deletions(-) diff --git a/README.md b/README.md index d6ff26c..2a5988b 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ class Album(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(length=100) class Track(ormar.Model): @@ -85,10 +85,10 @@ class Track(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - album: ormar.ForeignKey(Album) - title: ormar.String(length=100) - position: ormar.Integer() + id = ormar.Integer(primary_key=True) + album= ormar.ForeignKey(Album) + title = ormar.String(length=100) + position = ormar.Integer() # Create some records to work with. diff --git a/docs/index.md b/docs/index.md index ca77b54..3d0f507 100644 --- a/docs/index.md +++ b/docs/index.md @@ -75,8 +75,8 @@ class Album(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(length=100) class Track(ormar.Model): @@ -85,10 +85,10 @@ class Track(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - album: ormar.ForeignKey(Album) - title: ormar.String(length=100) - position: ormar.Integer() + id = ormar.Integer(primary_key=True) + album: Optional[Album] =ormar.ForeignKey(Album) + title = ormar.String(length=100) + position = ormar.Integer() # Create some records to work with. diff --git a/docs/models.md b/docs/models.md index 3562a7b..cb063fb 100644 --- a/docs/models.md +++ b/docs/models.md @@ -34,7 +34,7 @@ By default if you assign primary key to `Integer` field, the `autoincrement` opt You can disable by passing `autoincremant=False`. ```Python -id: ormar.Integer(primary_key=True, autoincrement=False) +id = ormar.Integer(primary_key=True, autoincrement=False) ``` ### Fields names vs Column names diff --git a/docs/queries.md b/docs/queries.md index c3f0268..dd6e249 100644 --- a/docs/queries.md +++ b/docs/queries.md @@ -109,10 +109,10 @@ class Book(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - title: ormar.String(max_length=200) - author: ormar.String(max_length=100) - genre: ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + id = ormar.Integer(primary_key=True) + title = ormar.String(max_length=200) + author = ormar.String(max_length=100) + genre = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') @@ -146,10 +146,10 @@ class Book(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - title: ormar.String(max_length=200) - author: ormar.String(max_length=100) - genre: ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + id = ormar.Integer(primary_key=True) + title = ormar.String(max_length=200) + author = ormar.String(max_length=100) + genre = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') @@ -192,9 +192,9 @@ class ToDo(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - text: ormar.String(max_length=500) - completed: ormar.Boolean(default=False) + id = ormar.Integer(primary_key=True) + text = ormar.String(max_length=500) + completed= ormar.Boolean(default=False) # create multiple instances at once with bulk_create await ToDo.objects.bulk_create( @@ -259,10 +259,10 @@ class Book(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - title: ormar.String(max_length=200) - author: ormar.String(max_length=100) - genre: ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + id = ormar.Integer(primary_key=True) + title = ormar.String(max_length=200) + author = ormar.String(max_length=100) + genre = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy') @@ -470,9 +470,9 @@ class Company(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - founded: ormar.Integer(nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + founded = ormar.Integer(nullable=True) class Car(ormar.Model): @@ -481,13 +481,13 @@ class Car(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - manufacturer: ormar.ForeignKey(Company) - name: ormar.String(max_length=100) - year: ormar.Integer(nullable=True) - gearbox_type: ormar.String(max_length=20, nullable=True) - gears: ormar.Integer(nullable=True) - aircon_type: ormar.String(max_length=20, nullable=True) + id = ormar.Integer(primary_key=True) + manufacturer= ormar.ForeignKey(Company) + name = ormar.String(max_length=100) + year = ormar.Integer(nullable=True) + gearbox_type = ormar.String(max_length=20, nullable=True) + gears = ormar.Integer(nullable=True) + aircon_type = ormar.String(max_length=20, nullable=True) diff --git a/docs_src/fastapi/docs001.py b/docs_src/fastapi/docs001.py index 475756a..7987bef 100644 --- a/docs_src/fastapi/docs001.py +++ b/docs_src/fastapi/docs001.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import databases import sqlalchemy @@ -32,8 +32,8 @@ class Category(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Item(ormar.Model): @@ -42,9 +42,9 @@ class Item(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - category: ormar.ForeignKey(Category, nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + category= ormar.ForeignKey(Category, nullable=True) @app.get("/items/", response_model=List[Item]) diff --git a/docs_src/fields/docs001.py b/docs_src/fields/docs001.py index 7cb26f4..304b92d 100644 --- a/docs_src/fields/docs001.py +++ b/docs_src/fields/docs001.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import sqlalchemy @@ -12,8 +14,8 @@ class Department(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Course(ormar.Model): @@ -21,10 +23,10 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) - department: ormar.ForeignKey(Department) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) + department= ormar.ForeignKey(Department) department = Department(name='Science') diff --git a/docs_src/fields/docs002.py b/docs_src/fields/docs002.py index 4a0c61f..13e4544 100644 --- a/docs_src/fields/docs002.py +++ b/docs_src/fields/docs002.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import sqlalchemy @@ -12,8 +14,8 @@ class Department(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Course(ormar.Model): @@ -21,10 +23,10 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) - department: ormar.ForeignKey(Department, related_name="my_courses") + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) + department= ormar.ForeignKey(Department, related_name="my_courses") department = Department(name='Science') diff --git a/docs_src/fields/docs003.py b/docs_src/fields/docs003.py index a0cb2c4..7a7f8b9 100644 --- a/docs_src/fields/docs003.py +++ b/docs_src/fields/docs003.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import sqlalchemy @@ -12,8 +14,8 @@ class Department(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Course(ormar.Model): @@ -21,7 +23,7 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) - department: ormar.ForeignKey(Department) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) + department= ormar.ForeignKey(Department) diff --git a/docs_src/fields/docs004.py b/docs_src/fields/docs004.py index b04cdb2..793dc93 100644 --- a/docs_src/fields/docs004.py +++ b/docs_src/fields/docs004.py @@ -1,3 +1,5 @@ +from datetime import datetime + import databases import sqlalchemy from sqlalchemy import func, text @@ -14,8 +16,8 @@ class Product(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - company: ormar.String(max_length=200, server_default='Acme') - sort_order: ormar.Integer(server_default=text("10")) - created: ormar.DateTime(server_default=func.now()) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + company = ormar.String(max_length=200, server_default='Acme') + sort_order = ormar.Integer(server_default=text("10")) + created= ormar.DateTime(server_default=func.now()) diff --git a/docs_src/models/docs001.py b/docs_src/models/docs001.py index 47b3581..78e2238 100644 --- a/docs_src/models/docs001.py +++ b/docs_src/models/docs001.py @@ -12,6 +12,6 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) diff --git a/docs_src/models/docs002.py b/docs_src/models/docs002.py index 96ee368..b8f206e 100644 --- a/docs_src/models/docs002.py +++ b/docs_src/models/docs002.py @@ -15,6 +15,6 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) diff --git a/docs_src/models/docs003.py b/docs_src/models/docs003.py index 91a83cf..92a7d49 100644 --- a/docs_src/models/docs003.py +++ b/docs_src/models/docs003.py @@ -12,9 +12,9 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) print(Course.__fields__) diff --git a/docs_src/models/docs004.py b/docs_src/models/docs004.py index a1db655..f40c1f1 100644 --- a/docs_src/models/docs004.py +++ b/docs_src/models/docs004.py @@ -12,9 +12,9 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) print(Course.Meta.table.columns) diff --git a/docs_src/models/docs005.py b/docs_src/models/docs005.py index ce81887..50e3515 100644 --- a/docs_src/models/docs005.py +++ b/docs_src/models/docs005.py @@ -12,9 +12,9 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) print({x:v.__dict__ for x,v in Course.Meta.model_fields.items()}) """ diff --git a/docs_src/models/docs008.py b/docs_src/models/docs008.py index 9a3d063..b7ab424 100644 --- a/docs_src/models/docs008.py +++ b/docs_src/models/docs008.py @@ -13,7 +13,7 @@ class Child(ormar.Model): metadata = metadata database = database - id: ormar.Integer(name='child_id', primary_key=True) - first_name: ormar.String(name='fname', max_length=100) - last_name: ormar.String(name='lname', max_length=100) - born_year: ormar.Integer(name='year_born', nullable=True) + id = ormar.Integer(name='child_id', primary_key=True) + first_name = ormar.String(name='fname', max_length=100) + last_name = ormar.String(name='lname', max_length=100) + born_year = ormar.Integer(name='year_born', nullable=True) diff --git a/docs_src/models/docs009.py b/docs_src/models/docs009.py index 0204feb..71b1bb3 100644 --- a/docs_src/models/docs009.py +++ b/docs_src/models/docs009.py @@ -4,6 +4,6 @@ class Album(ormar.Model): metadata = metadata database = database - id: ormar.Integer(name='album_id', primary_key=True) - name: ormar.String(name='album_name', max_length=100) - artist: ormar.ForeignKey(Artist, name='artist_id') + id = ormar.Integer(name='album_id', primary_key=True) + name = ormar.String(name='album_name', max_length=100) + artist= ormar.ForeignKey(Artist, name='artist_id') diff --git a/docs_src/models/docs010.py b/docs_src/models/docs010.py index 57febef..dd52267 100644 --- a/docs_src/models/docs010.py +++ b/docs_src/models/docs010.py @@ -11,8 +11,8 @@ class Artist(ormar.Model): metadata = metadata database = database - id: ormar.Integer(name='artist_id', primary_key=True) - first_name: ormar.String(name='fname', max_length=100) - last_name: ormar.String(name='lname', max_length=100) - born_year: ormar.Integer(name='year') - children: ormar.ManyToMany(Child, through=ArtistChildren) + id = ormar.Integer(name='artist_id', primary_key=True) + first_name = ormar.String(name='fname', max_length=100) + last_name = ormar.String(name='lname', max_length=100) + born_year = ormar.Integer(name='year') + children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany(Child, through=ArtistChildren) diff --git a/docs_src/queries/docs001.py b/docs_src/queries/docs001.py index 2e4fe43..6edaa53 100644 --- a/docs_src/queries/docs001.py +++ b/docs_src/queries/docs001.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import ormar import sqlalchemy @@ -12,8 +14,8 @@ class Album(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Track(ormar.Model): @@ -22,7 +24,7 @@ class Track(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - album: ormar.ForeignKey(Album) - title: ormar.String(max_length=100) - position: ormar.Integer() \ No newline at end of file + id = ormar.Integer(primary_key=True) + album= ormar.ForeignKey(Album) + title = ormar.String(max_length=100) + position = ormar.Integer() \ No newline at end of file diff --git a/docs_src/relations/docs001.py b/docs_src/relations/docs001.py index 930e8d3..6425f39 100644 --- a/docs_src/relations/docs001.py +++ b/docs_src/relations/docs001.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import sqlalchemy @@ -12,8 +14,8 @@ class Department(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Course(ormar.Model): @@ -21,10 +23,10 @@ class Course(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - completed: ormar.Boolean(default=False) - department: ormar.ForeignKey(Department) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed= ormar.Boolean(default=False) + department= ormar.ForeignKey(Department) department = Department(name='Science') diff --git a/docs_src/relations/docs002.py b/docs_src/relations/docs002.py index f78083a..470d9b1 100644 --- a/docs_src/relations/docs002.py +++ b/docs_src/relations/docs002.py @@ -1,3 +1,5 @@ +from typing import Optional, Union, List + import databases import ormar import sqlalchemy @@ -12,9 +14,9 @@ class Author(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - first_name: ormar.String(max_length=80) - last_name: ormar.String(max_length=80) + id = ormar.Integer(primary_key=True) + first_name = ormar.String(max_length=80) + last_name = ormar.String(max_length=80) class Category(ormar.Model): @@ -23,8 +25,8 @@ class Category(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=40) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=40) class PostCategory(ormar.Model): @@ -42,7 +44,7 @@ class Post(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - title: ormar.String(max_length=200) - categories: ormar.ManyToMany(Category, through=PostCategory) - author: ormar.ForeignKey(Author) + id = ormar.Integer(primary_key=True) + title = ormar.String(max_length=200) + categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(Category, through=PostCategory) + author= ormar.ForeignKey(Author) diff --git a/mypy.ini b/mypy.ini index 6347ae1..108c485 100644 --- a/mypy.ini +++ b/mypy.ini @@ -5,3 +5,6 @@ plugins = pydantic.mypy [mypy-sqlalchemy.*] ignore_missing_imports = True +[mypy-tests.test_model_definition.*] +ignore_errors = True + diff --git a/ormar/fields/many_to_many.py b/ormar/fields/many_to_many.py index 5c81182..5160a26 100644 --- a/ormar/fields/many_to_many.py +++ b/ormar/fields/many_to_many.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, TYPE_CHECKING, Type, Union +from typing import Any, List, Optional, TYPE_CHECKING, Type, Union, Sequence from ormar.fields import BaseField from ormar.fields.foreign_key import ForeignKeyField @@ -10,13 +10,13 @@ REF_PREFIX = "#/components/schemas/" def ManyToMany( - to: Type["Model"], - through: Type["Model"], - *, - name: str = None, - unique: bool = False, - virtual: bool = False, - **kwargs: Any + to: Type["Model"], + through: Type["Model"], + *, + name: str = None, + unique: bool = False, + virtual: bool = False, + **kwargs: Any ) -> Type["ManyToManyField"]: to_field = to.Meta.model_fields[to.Meta.pkname] related_name = kwargs.pop("related_name", None) @@ -49,3 +49,58 @@ def ManyToMany( class ManyToManyField(ForeignKeyField): through: Type["Model"] + + if TYPE_CHECKING: # pragma nocover + @staticmethod + async def add(item: "Model") -> None: + pass + + @staticmethod + async def remove(item: "Model") -> None: + pass + + from ormar import QuerySet + + @staticmethod + def filter(**kwargs: Any) -> "QuerySet": # noqa: A003 + pass + + @staticmethod + def select_related(related: Union[List, str]) -> "QuerySet": + pass + + @staticmethod + async def exists(self) -> bool: + return await self.queryset.exists() + + @staticmethod + async def count(self) -> int: + return await self.queryset.count() + + @staticmethod + async def clear(self) -> int: + pass + + @staticmethod + def limit(limit_count: int) -> "QuerySet": + pass + + @staticmethod + def offset(self, offset: int) -> "QuerySet": + pass + + @staticmethod + async def first(self, **kwargs: Any) -> "Model": + pass + + @staticmethod + async def get(self, **kwargs: Any) -> "Model": + pass + + @staticmethod + async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003 + pass + + @staticmethod + async def create(self, **kwargs: Any) -> "Model": + pass diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 2e98018..9c53574 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -12,7 +12,7 @@ from ormar.fields.base import BaseField # noqa I101 def is_field_nullable( - nullable: Optional[bool], default: Any, server_default: Any + nullable: Optional[bool], default: Any, server_default: Any ) -> bool: if nullable is None: return default is not None or server_default is not None @@ -58,20 +58,20 @@ class ModelFieldFactory: pass -class String(ModelFieldFactory): +class String(ModelFieldFactory, str): _type = str _pydantic_type = pydantic.ConstrainedStr def __new__( # type: ignore # noqa CFQ002 - cls, - *, - allow_blank: bool = True, - strip_whitespace: bool = False, - min_length: int = None, - max_length: int = None, - curtail_length: int = None, - regex: str = None, - **kwargs: Any + cls, + *, + allow_blank: bool = True, + strip_whitespace: bool = False, + min_length: int = None, + max_length: int = None, + curtail_length: int = None, + regex: str = None, + **kwargs: Any ) -> Type[BaseField]: # type: ignore kwargs = { **kwargs, @@ -97,17 +97,17 @@ class String(ModelFieldFactory): ) -class Integer(ModelFieldFactory): +class Integer(ModelFieldFactory, int): _type = int _pydantic_type = pydantic.ConstrainedInt def __new__( # type: ignore - cls, - *, - minimum: int = None, - maximum: int = None, - multiple_of: int = None, - **kwargs: Any + cls, + *, + minimum: int = None, + maximum: int = None, + multiple_of: int = None, + **kwargs: Any ) -> Type[BaseField]: autoincrement = kwargs.pop("autoincrement", None) autoincrement = ( @@ -132,12 +132,12 @@ class Integer(ModelFieldFactory): return sqlalchemy.Integer() -class Text(ModelFieldFactory): +class Text(ModelFieldFactory, str): _type = str _pydantic_type = pydantic.ConstrainedStr def __new__( # type: ignore - cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any + cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any ) -> Type[BaseField]: kwargs = { **kwargs, @@ -155,17 +155,17 @@ class Text(ModelFieldFactory): return sqlalchemy.Text() -class Float(ModelFieldFactory): +class Float(ModelFieldFactory, float): _type = float _pydantic_type = pydantic.ConstrainedFloat def __new__( # type: ignore - cls, - *, - minimum: float = None, - maximum: float = None, - multiple_of: int = None, - **kwargs: Any + cls, + *, + minimum: float = None, + maximum: float = None, + multiple_of: int = None, + **kwargs: Any ) -> Type[BaseField]: kwargs = { **kwargs, @@ -184,7 +184,7 @@ class Float(ModelFieldFactory): return sqlalchemy.Float() -class Boolean(ModelFieldFactory): +class Boolean(ModelFieldFactory, int): _type = bool _pydantic_type = bool @@ -193,7 +193,7 @@ class Boolean(ModelFieldFactory): return sqlalchemy.Boolean() -class DateTime(ModelFieldFactory): +class DateTime(ModelFieldFactory, datetime.datetime): _type = datetime.datetime _pydantic_type = datetime.datetime @@ -202,7 +202,7 @@ class DateTime(ModelFieldFactory): return sqlalchemy.DateTime() -class Date(ModelFieldFactory): +class Date(ModelFieldFactory, datetime.date): _type = datetime.date _pydantic_type = datetime.date @@ -211,7 +211,7 @@ class Date(ModelFieldFactory): return sqlalchemy.Date() -class Time(ModelFieldFactory): +class Time(ModelFieldFactory, datetime.time): _type = datetime.time _pydantic_type = datetime.time @@ -220,7 +220,7 @@ class Time(ModelFieldFactory): return sqlalchemy.Time() -class JSON(ModelFieldFactory): +class JSON(ModelFieldFactory, pydantic.Json): _type = pydantic.Json _pydantic_type = pydantic.Json @@ -229,17 +229,17 @@ class JSON(ModelFieldFactory): return sqlalchemy.JSON() -class BigInteger(Integer): +class BigInteger(Integer, int): _type = int _pydantic_type = pydantic.ConstrainedInt def __new__( # type: ignore - cls, - *, - minimum: int = None, - maximum: int = None, - multiple_of: int = None, - **kwargs: Any + cls, + *, + minimum: int = None, + maximum: int = None, + multiple_of: int = None, + **kwargs: Any ) -> Type[BaseField]: autoincrement = kwargs.pop("autoincrement", None) autoincrement = ( @@ -264,21 +264,21 @@ class BigInteger(Integer): return sqlalchemy.BigInteger() -class Decimal(ModelFieldFactory): +class Decimal(ModelFieldFactory, decimal.Decimal): _type = decimal.Decimal _pydantic_type = pydantic.ConstrainedDecimal def __new__( # type: ignore # noqa CFQ002 - cls, - *, - minimum: float = None, - maximum: float = None, - multiple_of: int = None, - precision: int = None, - scale: int = None, - max_digits: int = None, - decimal_places: int = None, - **kwargs: Any + cls, + *, + minimum: float = None, + maximum: float = None, + multiple_of: int = None, + precision: int = None, + scale: int = None, + max_digits: int = None, + decimal_places: int = None, + **kwargs: Any ) -> Type[BaseField]: kwargs = { **kwargs, @@ -319,7 +319,7 @@ class Decimal(ModelFieldFactory): ) -class UUID(ModelFieldFactory): +class UUID(ModelFieldFactory, uuid.UUID): _type = uuid.UUID _pydantic_type = uuid.UUID diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index 798424b..8d264a5 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -42,7 +42,7 @@ def register_relation_on_build(table_name: str, field: Type[ForeignKeyField]) -> def register_many_to_many_relation_on_build( - table_name: str, field: Type[ManyToManyField] + table_name: str, field: Type[ManyToManyField] ) -> None: alias_manager.add_relation_type(field.through.Meta.tablename, table_name) alias_manager.add_relation_type( @@ -51,11 +51,11 @@ def register_many_to_many_relation_on_build( def reverse_field_not_already_registered( - child: Type["Model"], child_model_name: str, parent_model: Type["Model"] + child: Type["Model"], child_model_name: str, parent_model: Type["Model"] ) -> bool: return ( - child_model_name not in parent_model.__fields__ - and child.get_name() not in parent_model.__fields__ + child_model_name not in parent_model.__fields__ + and child.get_name() not in parent_model.__fields__ ) @@ -66,7 +66,7 @@ def expand_reverse_relationships(model: Type["Model"]) -> None: parent_model = model_field.to child = model if reverse_field_not_already_registered( - child, child_model_name, parent_model + child, child_model_name, parent_model ): register_reverse_model_fields( parent_model, child, child_model_name, model_field @@ -74,10 +74,10 @@ def expand_reverse_relationships(model: Type["Model"]) -> None: def register_reverse_model_fields( - model: Type["Model"], - child: Type["Model"], - child_model_name: str, - model_field: Type["ForeignKeyField"], + model: Type["Model"], + child: Type["Model"], + child_model_name: str, + model_field: Type["ForeignKeyField"], ) -> None: if issubclass(model_field, ManyToManyField): model.Meta.model_fields[child_model_name] = ManyToMany( @@ -92,7 +92,7 @@ def register_reverse_model_fields( def adjust_through_many_to_many_model( - model: Type["Model"], child: Type["Model"], model_field: Type[ManyToManyField] + model: Type["Model"], child: Type["Model"], model_field: Type[ManyToManyField] ) -> None: model_field.through.Meta.model_fields[model.get_name()] = ForeignKey( model, name=model.get_name(), ondelete="CASCADE" @@ -109,7 +109,7 @@ def adjust_through_many_to_many_model( def create_pydantic_field( - field_name: str, model: Type["Model"], model_field: Type[ManyToManyField] + field_name: str, model: Type["Model"], model_field: Type[ManyToManyField] ) -> None: model_field.through.__fields__[field_name] = ModelField( name=field_name, @@ -121,7 +121,7 @@ def create_pydantic_field( def create_and_append_m2m_fk( - model: Type["Model"], model_field: Type[ManyToManyField] + model: Type["Model"], model_field: Type[ManyToManyField] ) -> None: column = sqlalchemy.Column( model.get_name(), @@ -137,7 +137,7 @@ def create_and_append_m2m_fk( def check_pk_column_validity( - field_name: str, field: BaseField, pkname: Optional[str] + field_name: str, field: BaseField, pkname: Optional[str] ) -> Optional[str]: if pkname is not None: raise ModelDefinitionError("Only one primary key column is allowed.") @@ -147,7 +147,7 @@ def check_pk_column_validity( def sqlalchemy_columns_from_model_fields( - model_fields: Dict, table_name: str + model_fields: Dict, table_name: str ) -> Tuple[Optional[str], List[sqlalchemy.Column]]: columns = [] pkname = None @@ -161,9 +161,9 @@ def sqlalchemy_columns_from_model_fields( if field.primary_key: pkname = check_pk_column_validity(field_name, field, pkname) if ( - not field.pydantic_only - and not field.virtual - and not issubclass(field, ManyToManyField) + not field.pydantic_only + and not field.virtual + and not issubclass(field, ManyToManyField) ): columns.append(field.get_column(field_name)) register_relation_in_alias_manager(table_name, field) @@ -171,7 +171,7 @@ def sqlalchemy_columns_from_model_fields( def register_relation_in_alias_manager( - table_name: str, field: Type[ForeignKeyField] + table_name: str, field: Type[ForeignKeyField] ) -> None: if issubclass(field, ManyToManyField): register_many_to_many_relation_on_build(table_name, field) @@ -180,7 +180,7 @@ def register_relation_in_alias_manager( def populate_default_pydantic_field_value( - ormar_field: Type[BaseField], field_name: str, attrs: dict + ormar_field: Type[BaseField], field_name: str, attrs: dict ) -> dict: curr_def_value = attrs.get(field_name, ormar.Undefined) if lenient_issubclass(curr_def_value, ormar.fields.BaseField): @@ -193,7 +193,7 @@ def populate_default_pydantic_field_value( def check_if_field_annotation_or_value_is_ormar( - field: Any, field_name: str, attrs: Dict + field: Any, field_name: str, attrs: Dict ) -> bool: return lenient_issubclass(field, BaseField) or issubclass( attrs.get(field_name, type), BaseField @@ -201,14 +201,16 @@ def check_if_field_annotation_or_value_is_ormar( def extract_field_from_annotation_or_value( - field: Any, field_name: str, attrs: Dict + field: Any, field_name: str, attrs: Dict ) -> Type[ormar.fields.BaseField]: return field if lenient_issubclass(field, BaseField) else attrs.get(field_name) def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: model_fields = {} - for field_name, field in attrs["__annotations__"].items(): + potential_fields = {k: v for k, v in attrs["__annotations__"].items() if lenient_issubclass(v, BaseField)} + potential_fields.update({k: v for k, v in attrs.items() if lenient_issubclass(v, BaseField)}) + for field_name, field in potential_fields.items(): # ormar fields can be used as annotation or as default value if check_if_field_annotation_or_value_is_ormar(field, field_name, attrs): ormar_field = extract_field_from_annotation_or_value( @@ -225,17 +227,16 @@ def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: def extract_annotations_and_default_vals( - attrs: dict, bases: Tuple + attrs: dict ) -> Tuple[Dict, Dict]: - attrs["__annotations__"] = attrs.get("__annotations__") or bases[0].__dict__.get( - "__annotations__", {} - ) + key = '__annotations__' + attrs[key] = attrs.get(key, {}) attrs, model_fields = populate_pydantic_default_values(attrs) return attrs, model_fields def populate_meta_tablename_columns_and_pk( - name: str, new_model: Type["Model"] + name: str, new_model: Type["Model"] ) -> Type["Model"]: tablename = name.lower() + "s" new_model.Meta.tablename = ( @@ -261,7 +262,7 @@ def populate_meta_tablename_columns_and_pk( def populate_meta_sqlalchemy_table_if_required( - new_model: Type["Model"], + new_model: Type["Model"], ) -> Type["Model"]: if not hasattr(new_model.Meta, "table"): new_model.Meta.table = sqlalchemy.Table( @@ -276,7 +277,7 @@ def populate_meta_sqlalchemy_table_if_required( def get_pydantic_base_orm_config() -> Type[BaseConfig]: class Config(BaseConfig): orm_mode = True - arbitrary_types_allowed = True + # arbitrary_types_allowed = True return Config @@ -303,7 +304,7 @@ def choices_validator(cls: Type["Model"], values: Dict[str, Any]) -> Dict[str, A def populate_choices_validators( # noqa CCR001 - model: Type["Model"], attrs: Dict + model: Type["Model"], attrs: Dict ) -> None: if model_initialized_and_has_model_fields(model): for _, field in model.Meta.model_fields.items(): @@ -316,11 +317,11 @@ def populate_choices_validators( # noqa CCR001 class ModelMetaclass(pydantic.main.ModelMetaclass): def __new__( # type: ignore - mcs: "ModelMetaclass", name: str, bases: Any, attrs: dict + mcs: "ModelMetaclass", name: str, bases: Any, attrs: dict ) -> "ModelMetaclass": attrs["Config"] = get_pydantic_base_orm_config() attrs["__name__"] = name - attrs, model_fields = extract_annotations_and_default_vals(attrs, bases) + attrs, model_fields = extract_annotations_and_default_vals(attrs) new_model = super().__new__( # type: ignore mcs, name, bases, attrs ) diff --git a/ormar/models/model.py b/ormar/models/model.py index f8884b1..f8b6ea0 100644 --- a/ormar/models/model.py +++ b/ormar/models/model.py @@ -24,6 +24,9 @@ def group_related_list(list_: List) -> Dict: return test_dict +if TYPE_CHECKING: # pragma nocover + from ormar import QuerySet + T = TypeVar("T", bound="Model") @@ -31,6 +34,7 @@ class Model(NewBaseModel): __abstract__ = False if TYPE_CHECKING: # pragma nocover Meta: ModelMeta + objects: "QuerySet" def __repr__(self) -> str: # pragma nocover attrs_to_include = ["tablename", "columns", "pkname"] @@ -41,12 +45,12 @@ class Model(NewBaseModel): @classmethod def from_row( # noqa CCR001 - cls: Type[T], - row: sqlalchemy.engine.ResultProxy, - select_related: List = None, - related_models: Any = None, - previous_table: str = None, - fields: List = None, + cls: Type[T], + row: sqlalchemy.engine.ResultProxy, + select_related: List = None, + related_models: Any = None, + previous_table: str = None, + fields: List = None, ) -> Optional[T]: item: Dict[str, Any] = {} @@ -56,9 +60,9 @@ class Model(NewBaseModel): related_models = group_related_list(select_related) if ( - previous_table - and previous_table in cls.Meta.model_fields - and issubclass(cls.Meta.model_fields[previous_table], ManyToManyField) + previous_table + and previous_table in cls.Meta.model_fields + and issubclass(cls.Meta.model_fields[previous_table], ManyToManyField) ): previous_table = cls.Meta.model_fields[ previous_table @@ -86,12 +90,12 @@ class Model(NewBaseModel): @classmethod def populate_nested_models_from_row( - cls, - item: dict, - row: sqlalchemy.engine.ResultProxy, - related_models: Any, - previous_table: sqlalchemy.Table, - fields: List = None, + cls, + item: dict, + row: sqlalchemy.engine.ResultProxy, + related_models: Any, + previous_table: sqlalchemy.Table, + fields: List = None, ) -> dict: for related in related_models: if isinstance(related_models, dict) and related_models[related]: @@ -115,12 +119,12 @@ class Model(NewBaseModel): @classmethod def extract_prefixed_table_columns( # noqa CCR001 - cls, - item: dict, - row: sqlalchemy.engine.result.ResultProxy, - table_prefix: str, - fields: List = None, - nested: bool = False, + cls, + item: dict, + row: sqlalchemy.engine.result.ResultProxy, + table_prefix: str, + fields: List = None, + nested: bool = False, ) -> dict: # databases does not keep aliases in Record for postgres, change to raw row diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index 72083d3..3f48ddf 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -136,7 +136,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass def _extract_related_model_instead_of_field( self, item: str - ) -> Optional[Union[T, Sequence[T]]]: + ) -> Optional[Union["T", Sequence["T"]]]: alias = self.get_column_alias(item) if alias in self._orm: return self._orm.get(alias) @@ -173,7 +173,7 @@ class NewBaseModel(pydantic.BaseModel, ModelTableProxy, metaclass=ModelMetaclass def db_backend_name(cls) -> str: return cls.Meta.database._backend._dialect.name - def remove(self, name: T) -> None: + def remove(self, name: "T") -> None: self._orm.remove_parent(self, name) def dict( # noqa A003 diff --git a/ormar/queryset/queryset.py b/ormar/queryset/queryset.py index 5b58c55..749eb75 100644 --- a/ormar/queryset/queryset.py +++ b/ormar/queryset/queryset.py @@ -39,7 +39,7 @@ class QuerySet: def __get__( self, - instance: Union["QuerySet", "QuerysetProxy"], + instance: Optional[Union["QuerySet", "QuerysetProxy"]], owner: Union[Type["Model"], Type["QuerysetProxy"]], ) -> "QuerySet": if issubclass(owner, ormar.Model): diff --git a/ormar/relations/querysetproxy.py b/ormar/relations/querysetproxy.py index b9fb250..88c8dc1 100644 --- a/ormar/relations/querysetproxy.py +++ b/ormar/relations/querysetproxy.py @@ -28,28 +28,28 @@ class QuerysetProxy: def queryset(self, value: "QuerySet") -> None: self._queryset = value - def _assign_child_to_parent(self, child: Optional[T]) -> None: + def _assign_child_to_parent(self, child: Optional["T"]) -> None: if child: owner = self.relation._owner rel_name = owner.resolve_relation_name(owner, child) setattr(owner, rel_name, child) - def _register_related(self, child: Union[T, Sequence[Optional[T]]]) -> None: + def _register_related(self, child: Union["T", Sequence[Optional["T"]]]) -> None: if isinstance(child, list): for subchild in child: self._assign_child_to_parent(subchild) else: - assert isinstance(child, Model) + assert isinstance(child, ormar.Model) self._assign_child_to_parent(child) - async def create_through_instance(self, child: T) -> None: + async def create_through_instance(self, child: "T") -> None: queryset = ormar.QuerySet(model_cls=self.relation.through) owner_column = self.relation._owner.get_name() child_column = child.get_name() kwargs = {owner_column: self.relation._owner, child_column: child} await queryset.create(**kwargs) - async def delete_through_instance(self, child: T) -> None: + async def delete_through_instance(self, child: "T") -> None: queryset = ormar.QuerySet(model_cls=self.relation.through) owner_column = self.relation._owner.get_name() child_column = child.get_name() diff --git a/ormar/relations/relation.py b/ormar/relations/relation.py index 6520702..e09f00c 100644 --- a/ormar/relations/relation.py +++ b/ormar/relations/relation.py @@ -25,15 +25,15 @@ class Relation: self, manager: "RelationsManager", type_: RelationType, - to: Type[T], - through: Type[T] = None, + to: Type["T"], + through: Type["T"] = None, ) -> None: self.manager = manager self._owner: "Model" = manager.owner self._type: RelationType = type_ - self.to: Type[T] = to - self.through: Optional[Type[T]] = through - self.related_models: Optional[Union[RelationProxy, T]] = ( + self.to: Type["T"] = to + self.through: Optional[Type["T"]] = through + self.related_models: Optional[Union[RelationProxy, "T"]] = ( RelationProxy(relation=self) if type_ in (RelationType.REVERSE, RelationType.MULTIPLE) else None @@ -52,7 +52,7 @@ class Relation: self.related_models.pop(ind) return None - def add(self, child: T) -> None: + def add(self, child: "T") -> None: relation_name = self._owner.resolve_relation_name(self._owner, child) if self._type == RelationType.PRIMARY: self.related_models = child @@ -79,7 +79,7 @@ class Relation: self.related_models.pop(position) # type: ignore del self._owner.__dict__[relation_name][position] - def get(self) -> Optional[Union[List[T], T]]: + def get(self) -> Optional[Union[List["T"], "T"]]: return self.related_models def __repr__(self) -> str: # pragma no cover diff --git a/ormar/relations/relation_manager.py b/ormar/relations/relation_manager.py index 82a6887..0e36844 100644 --- a/ormar/relations/relation_manager.py +++ b/ormar/relations/relation_manager.py @@ -48,7 +48,7 @@ class RelationsManager: def __contains__(self, item: str) -> bool: return item in self._related_names - def get(self, name: str) -> Optional[Union[T, Sequence[T]]]: + def get(self, name: str) -> Optional[Union["T", Sequence["T"]]]: relation = self._relations.get(name, None) if relation is not None: return relation.get() diff --git a/tests/test_aliases.py b/tests/test_aliases.py index b48bb3f..b07e3af 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -15,10 +15,10 @@ class Child(ormar.Model): metadata = metadata database = database - id: ormar.Integer(name="child_id", primary_key=True) - first_name: ormar.String(name="fname", max_length=100) - last_name: ormar.String(name="lname", max_length=100) - born_year: ormar.Integer(name="year_born", nullable=True) + id = ormar.Integer(name="child_id", primary_key=True) + first_name = ormar.String(name="fname", max_length=100) + last_name = ormar.String(name="lname", max_length=100) + born_year = ormar.Integer(name="year_born", nullable=True) class ArtistChildren(ormar.Model): @@ -34,11 +34,13 @@ class Artist(ormar.Model): metadata = metadata database = database - id: ormar.Integer(name="artist_id", primary_key=True) - first_name: ormar.String(name="fname", max_length=100) - last_name: ormar.String(name="lname", max_length=100) - born_year: ormar.Integer(name="year") - children: ormar.ManyToMany(Child, through=ArtistChildren) + id = ormar.Integer(name="artist_id", primary_key=True) + first_name = ormar.String(name="fname", max_length=100) + last_name = ormar.String(name="lname", max_length=100) + born_year = ormar.Integer(name="year") + children = ormar.ManyToMany( + Child, through=ArtistChildren + ) class Album(ormar.Model): @@ -47,9 +49,9 @@ class Album(ormar.Model): metadata = metadata database = database - id: ormar.Integer(name="album_id", primary_key=True) - name: ormar.String(name="album_name", max_length=100) - artist: ormar.ForeignKey(Artist, name="artist_id") + id = ormar.Integer(name="album_id", primary_key=True) + name = ormar.String(name="album_name", max_length=100) + artist = ormar.ForeignKey(Artist, name="artist_id") @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_columns.py b/tests/test_columns.py index b2a7bd0..d0eb326 100644 --- a/tests/test_columns.py +++ b/tests/test_columns.py @@ -2,6 +2,7 @@ import datetime import os import databases +import pydantic import pytest import sqlalchemy @@ -22,14 +23,14 @@ class Example(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=200, default="aaa") - created: ormar.DateTime(default=datetime.datetime.now) - created_day: ormar.Date(default=datetime.date.today) - created_time: ormar.Time(default=time) - description: ormar.Text(nullable=True) - value: ormar.Float(nullable=True) - data: ormar.JSON(default={}) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=200, default="aaa") + created = ormar.DateTime(default=datetime.datetime.now) + created_day = ormar.Date(default=datetime.date.today) + created_time = ormar.Time(default=time) + description = ormar.Text(nullable=True) + value = ormar.Float(nullable=True) + data = ormar.JSON(default={}) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_fastapi_docs.py b/tests/test_fastapi_docs.py index 4cb1df3..8ad0e56 100644 --- a/tests/test_fastapi_docs.py +++ b/tests/test_fastapi_docs.py @@ -38,8 +38,8 @@ class Category(ormar.Model): class Meta(LocalMeta): tablename = "categories" - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class ItemsXCategories(ormar.Model): @@ -51,9 +51,11 @@ class Item(ormar.Model): class Meta(LocalMeta): pass - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - categories: ormar.ManyToMany(Category, through=ItemsXCategories) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + categories = ormar.ManyToMany( + Category, through=ItemsXCategories + ) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_fastapi_usage.py b/tests/test_fastapi_usage.py index fcbf479..ecf1a5f 100644 --- a/tests/test_fastapi_usage.py +++ b/tests/test_fastapi_usage.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import sqlalchemy from fastapi import FastAPI @@ -18,8 +20,8 @@ class Category(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Item(ormar.Model): @@ -28,9 +30,9 @@ class Item(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - category: ormar.ForeignKey(Category, nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + category = ormar.ForeignKey(Category, nullable=True) @app.post("/items/", response_model=Item) diff --git a/tests/test_foreign_keys.py b/tests/test_foreign_keys.py index b85493d..4fe836c 100644 --- a/tests/test_foreign_keys.py +++ b/tests/test_foreign_keys.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import pytest import sqlalchemy @@ -16,8 +18,8 @@ class Album(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Track(ormar.Model): @@ -26,10 +28,10 @@ class Track(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - album: ormar.ForeignKey(Album) - title: ormar.String(max_length=100) - position: ormar.Integer() + id: int = ormar.Integer(primary_key=True) + album: Optional[Album] = ormar.ForeignKey(Album) + title: str = ormar.String(max_length=100) + position: int = ormar.Integer() class Cover(ormar.Model): @@ -38,9 +40,9 @@ class Cover(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - album: ormar.ForeignKey(Album, related_name="cover_pictures") - title: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + album = ormar.ForeignKey(Album, related_name="cover_pictures") + title = ormar.String(max_length=100) class Organisation(ormar.Model): @@ -49,8 +51,12 @@ class Organisation(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - ident: ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) + id = ormar.Integer(primary_key=True) + ident = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) + + +class Organization(object): + pass class Team(ormar.Model): @@ -59,9 +65,9 @@ class Team(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - org: ormar.ForeignKey(Organisation) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + org = ormar.ForeignKey(Organisation) + name = ormar.String(max_length=100) class Member(ormar.Model): @@ -70,9 +76,9 @@ class Member(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - team: ormar.ForeignKey(Team) - email: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + team = ormar.ForeignKey(Team) + email = ormar.String(max_length=100) @pytest.fixture(autouse=True, scope="module") @@ -233,8 +239,8 @@ async def test_fk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(album__name="Fantasies") - .all() + .filter(album__name="Fantasies") + .all() ) assert len(tracks) == 3 for track in tracks: @@ -242,8 +248,8 @@ async def test_fk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(album__name__icontains="fan") - .all() + .filter(album__name__icontains="fan") + .all() ) assert len(tracks) == 3 for track in tracks: @@ -288,8 +294,8 @@ async def test_multiple_fk(): members = ( await Member.objects.select_related("team__org") - .filter(team__org__ident="ACME Ltd") - .all() + .filter(team__org__ident="ACME Ltd") + .all() ) assert len(members) == 4 for member in members: @@ -321,8 +327,8 @@ async def test_pk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(position=2, album__name="Test") - .all() + .filter(position=2, album__name="Test") + .all() ) assert len(tracks) == 1 diff --git a/tests/test_many_to_many.py b/tests/test_many_to_many.py index fc97b0d..d4e78ad 100644 --- a/tests/test_many_to_many.py +++ b/tests/test_many_to_many.py @@ -1,4 +1,5 @@ import asyncio +from typing import List, Union, Optional import databases import pytest @@ -18,9 +19,9 @@ class Author(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - first_name: ormar.String(max_length=80) - last_name: ormar.String(max_length=80) + id = ormar.Integer(primary_key=True) + first_name = ormar.String(max_length=80) + last_name = ormar.String(max_length=80) class Category(ormar.Model): @@ -29,8 +30,8 @@ class Category(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=40) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=40) class PostCategory(ormar.Model): @@ -46,10 +47,12 @@ class Post(ormar.Model): database = database metadata = metadata - id: ormar.Integer(primary_key=True) - title: ormar.String(max_length=200) - categories: ormar.ManyToMany(Category, through=PostCategory) - author: ormar.ForeignKey(Author) + id = ormar.Integer(primary_key=True) + title = ormar.String(max_length=200) + categories = ormar.ManyToMany( + Category, through=PostCategory + ) + author= ormar.ForeignKey(Author) @pytest.fixture(scope="module") diff --git a/tests/test_model_definition.py b/tests/test_model_definition.py index 2f64259..dd3b717 100644 --- a/tests/test_model_definition.py +++ b/tests/test_model_definition.py @@ -1,13 +1,17 @@ +# type: ignore +import asyncio import datetime import decimal import pydantic import pytest import sqlalchemy +import typing -import ormar.fields as fields +import ormar from ormar.exceptions import ModelDefinitionError from ormar.models import Model +from tests.settings import DATABASE_URL metadata = sqlalchemy.MetaData() @@ -17,18 +21,18 @@ class ExampleModel(Model): tablename = "example" metadata = metadata - test: fields.Integer(primary_key=True) - test_string: fields.String(max_length=250) - test_text: fields.Text(default="") - test_bool: fields.Boolean(nullable=False) - test_float: fields.Float() = None - test_datetime: fields.DateTime(default=datetime.datetime.now) - test_date: fields.Date(default=datetime.date.today) - test_time: fields.Time(default=datetime.time) - test_json: fields.JSON(default={}) - test_bigint: fields.BigInteger(default=0) - test_decimal: fields.Decimal(scale=10, precision=2) - test_decimal2: fields.Decimal(max_digits=10, decimal_places=2) + test = ormar.Integer(primary_key=True) + test_string = ormar.String(max_length=250) + test_text = ormar.Text(default="") + test_bool = ormar.Boolean(nullable=False) + test_float: ormar.Float() = None # type: ignore + test_datetime = ormar.DateTime(default=datetime.datetime.now) + test_date = ormar.Date(default=datetime.date.today) + test_time = ormar.Time(default=datetime.time) + test_json = ormar.JSON(default={}) + test_bigint = ormar.BigInteger(default=0) + test_decimal = ormar.Decimal(scale=10, precision=2) + test_decimal2 = ormar.Decimal(max_digits=10, decimal_places=2) fields_to_check = [ @@ -46,11 +50,26 @@ fields_to_check = [ class ExampleModel2(Model): class Meta: - tablename = "example2" + tablename = "examples" metadata = metadata - test: fields.Integer(primary_key=True) - test_string: fields.String(max_length=250) + test = ormar.Integer(primary_key=True) + test_string = ormar.String(max_length=250) + + +@pytest.fixture(scope="module") +def event_loop(): + loop = asyncio.get_event_loop() + yield loop + loop.close() + + +@pytest.fixture(autouse=True, scope="module") +async def create_test_database(): + engine = sqlalchemy.create_engine(DATABASE_URL) + metadata.create_all(engine) + yield + metadata.drop_all(engine) @pytest.fixture() @@ -117,62 +136,64 @@ def test_sqlalchemy_table_is_created(example): assert all([field in example.Meta.table.columns for field in fields_to_check]) -def test_no_pk_in_model_definition(): - with pytest.raises(ModelDefinitionError): - - class ExampleModel2(Model): +@typing.no_type_check +def test_no_pk_in_model_definition(): # type: ignore + with pytest.raises(ModelDefinitionError): # type: ignore + class ExampleModel2(Model): # type: ignore class Meta: - tablename = "example3" + tablename = "example2" metadata = metadata - test_string: fields.String(max_length=250) + test_string = ormar.String(max_length=250) # type: ignore +@typing.no_type_check def test_two_pks_in_model_definition(): with pytest.raises(ModelDefinitionError): - + @typing.no_type_check class ExampleModel2(Model): class Meta: tablename = "example3" metadata = metadata - id: fields.Integer(primary_key=True) - test_string: fields.String(max_length=250, primary_key=True) + id = ormar.Integer(primary_key=True) + test_string = ormar.String(max_length=250, primary_key=True) +@typing.no_type_check def test_setting_pk_column_as_pydantic_only_in_model_definition(): with pytest.raises(ModelDefinitionError): - class ExampleModel2(Model): class Meta: tablename = "example4" metadata = metadata - test: fields.Integer(primary_key=True, pydantic_only=True) + test = ormar.Integer(primary_key=True, pydantic_only=True) +@typing.no_type_check def test_decimal_error_in_model_definition(): with pytest.raises(ModelDefinitionError): - class ExampleModel2(Model): class Meta: tablename = "example5" metadata = metadata - test: fields.Decimal(primary_key=True) + test = ormar.Decimal(primary_key=True) +@typing.no_type_check def test_string_error_in_model_definition(): with pytest.raises(ModelDefinitionError): - class ExampleModel2(Model): class Meta: tablename = "example6" metadata = metadata - test: fields.String(primary_key=True) + test = ormar.String(primary_key=True) +@typing.no_type_check def test_json_conversion_in_model(): with pytest.raises(pydantic.ValidationError): ExampleModel( diff --git a/tests/test_models.py b/tests/test_models.py index cc783e3..067e0b0 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -22,8 +22,8 @@ class JsonSample(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - test_json: ormar.JSON(nullable=True) + id = ormar.Integer(primary_key=True) + test_json= ormar.JSON(nullable=True) class UUIDSample(ormar.Model): @@ -32,8 +32,8 @@ class UUIDSample(ormar.Model): metadata = metadata database = database - id: ormar.UUID(primary_key=True, default=uuid.uuid4) - test_text: ormar.Text() + id= ormar.UUID(primary_key=True, default=uuid.uuid4) + test_text = ormar.Text() class User(ormar.Model): @@ -42,8 +42,8 @@ class User(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100, default="") + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100, default="") class Product(ormar.Model): @@ -52,11 +52,11 @@ class Product(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - rating: ormar.Integer(minimum=1, maximum=5) - in_stock: ormar.Boolean(default=False) - last_delivery: ormar.Date(default=datetime.now) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + rating = ormar.Integer(minimum=1, maximum=5) + in_stock= ormar.Boolean(default=False) + last_delivery= ormar.Date(default=datetime.now) country_name_choices = ("Canada", "Algeria", "United States") @@ -70,12 +70,12 @@ class Country(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String( + id = ormar.Integer(primary_key=True) + name = ormar.String( max_length=9, choices=country_name_choices, default="Canada", ) - taxed: ormar.Boolean(choices=country_taxed_choices, default=True) - country_code: ormar.Integer( + taxed= ormar.Boolean(choices=country_taxed_choices, default=True) + country_code = ormar.Integer( minimum=0, maximum=1000, choices=country_country_code_choices, default=1 ) diff --git a/tests/test_more_reallife_fastapi.py b/tests/test_more_reallife_fastapi.py index b538b5c..1ad7425 100644 --- a/tests/test_more_reallife_fastapi.py +++ b/tests/test_more_reallife_fastapi.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional import databases import pytest @@ -35,8 +35,8 @@ class Category(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Item(ormar.Model): @@ -45,9 +45,9 @@ class Item(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - category: ormar.ForeignKey(Category, nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + category = ormar.ForeignKey(Category, nullable=True) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_more_same_table_joins.py b/tests/test_more_same_table_joins.py index 0492d91..d0b9859 100644 --- a/tests/test_more_same_table_joins.py +++ b/tests/test_more_same_table_joins.py @@ -1,4 +1,5 @@ import asyncio +from typing import Optional import databases import pytest @@ -17,8 +18,8 @@ class Department(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True, autoincrement=False) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True, autoincrement=False) + name = ormar.String(max_length=100) class SchoolClass(ormar.Model): @@ -27,8 +28,8 @@ class SchoolClass(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Category(ormar.Model): @@ -37,9 +38,9 @@ class Category(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - department: ormar.ForeignKey(Department, nullable=False) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + department= ormar.ForeignKey(Department, nullable=False) class Student(ormar.Model): @@ -48,10 +49,10 @@ class Student(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - schoolclass: ormar.ForeignKey(SchoolClass) - category: ormar.ForeignKey(Category, nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + schoolclass= ormar.ForeignKey(SchoolClass) + category= ormar.ForeignKey(Category, nullable=True) class Teacher(ormar.Model): @@ -60,10 +61,10 @@ class Teacher(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - schoolclass: ormar.ForeignKey(SchoolClass) - category: ormar.ForeignKey(Category, nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + schoolclass= ormar.ForeignKey(SchoolClass) + category= ormar.ForeignKey(Category, nullable=True) @pytest.fixture(scope="module") diff --git a/tests/test_new_annotation_style.py b/tests/test_new_annotation_style.py index 9a665cc..54a20cf 100644 --- a/tests/test_new_annotation_style.py +++ b/tests/test_new_annotation_style.py @@ -18,8 +18,8 @@ class Album(ormar.Model): metadata = metadata database = database - id: int = ormar.Integer(primary_key=True) - name: str = ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Track(ormar.Model): @@ -28,10 +28,10 @@ class Track(ormar.Model): metadata = metadata database = database - id: int = ormar.Integer(primary_key=True) - album: Optional[Album] = ormar.ForeignKey(Album) - title: str = ormar.String(max_length=100) - position: int = ormar.Integer() + id = ormar.Integer(primary_key=True) + album= ormar.ForeignKey(Album) + title = ormar.String(max_length=100) + position = ormar.Integer() class Cover(ormar.Model): @@ -40,9 +40,9 @@ class Cover(ormar.Model): metadata = metadata database = database - id: int = ormar.Integer(primary_key=True) - album: Album = ormar.ForeignKey(Album, related_name="cover_pictures") - title: str = ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + album= ormar.ForeignKey(Album, related_name="cover_pictures") + title = ormar.String(max_length=100) class Organisation(ormar.Model): @@ -51,8 +51,8 @@ class Organisation(ormar.Model): metadata = metadata database = database - id: int = ormar.Integer(primary_key=True) - ident: str = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) + id = ormar.Integer(primary_key=True) + ident = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) class Team(ormar.Model): @@ -61,9 +61,9 @@ class Team(ormar.Model): metadata = metadata database = database - id: int = ormar.Integer(primary_key=True) - org: Optional[Organisation] = ormar.ForeignKey(Organisation) - name: str = ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + org= ormar.ForeignKey(Organisation) + name = ormar.String(max_length=100) class Member(ormar.Model): @@ -72,9 +72,9 @@ class Member(ormar.Model): metadata = metadata database = database - id: int = ormar.Integer(primary_key=True) - team: Optional[Team] = ormar.ForeignKey(Team) - email: str = ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + team= ormar.ForeignKey(Team) + email = ormar.String(max_length=100) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_non_integer_pkey.py b/tests/test_non_integer_pkey.py index d601e10..0b3d8d1 100644 --- a/tests/test_non_integer_pkey.py +++ b/tests/test_non_integer_pkey.py @@ -21,8 +21,8 @@ class Model(ormar.Model): metadata = metadata database = database - id: ormar.String(primary_key=True, default=key, max_length=8) - name: ormar.String(max_length=32) + id = ormar.String(primary_key=True, default=key, max_length=8) + name = ormar.String(max_length=32) @pytest.fixture(autouse=True, scope="function") diff --git a/tests/test_queryset_level_methods.py b/tests/test_queryset_level_methods.py index af8385e..1e986b5 100644 --- a/tests/test_queryset_level_methods.py +++ b/tests/test_queryset_level_methods.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import pytest import sqlalchemy @@ -16,10 +18,10 @@ class Book(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - title: ormar.String(max_length=200) - author: ormar.String(max_length=100) - genre: ormar.String( + id = ormar.Integer(primary_key=True) + title = ormar.String(max_length=200) + author = ormar.String(max_length=100) + genre = ormar.String( max_length=100, default="Fiction", choices=["Fiction", "Adventure", "Historic", "Fantasy"], @@ -32,9 +34,9 @@ class ToDo(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - text: ormar.String(max_length=500) - completed: ormar.Boolean(default=False) + id = ormar.Integer(primary_key=True) + text = ormar.String(max_length=500) + completed= ormar.Boolean(default=False) class Category(ormar.Model): @@ -43,8 +45,8 @@ class Category(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=500) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=500) class Note(ormar.Model): @@ -53,9 +55,9 @@ class Note(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - text: ormar.String(max_length=500) - category: ormar.ForeignKey(Category) + id = ormar.Integer(primary_key=True) + text = ormar.String(max_length=500) + category= ormar.ForeignKey(Category) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_same_table_joins.py b/tests/test_same_table_joins.py index 33d2677..5d3169b 100644 --- a/tests/test_same_table_joins.py +++ b/tests/test_same_table_joins.py @@ -1,4 +1,5 @@ import asyncio +from typing import Optional import databases import pytest @@ -17,8 +18,8 @@ class Department(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True, autoincrement=False) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True, autoincrement=False) + name = ormar.String(max_length=100) class SchoolClass(ormar.Model): @@ -27,9 +28,9 @@ class SchoolClass(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - department: ormar.ForeignKey(Department, nullable=False) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + department= ormar.ForeignKey(Department, nullable=False) class Category(ormar.Model): @@ -38,8 +39,8 @@ class Category(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) class Student(ormar.Model): @@ -48,10 +49,10 @@ class Student(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - schoolclass: ormar.ForeignKey(SchoolClass) - category: ormar.ForeignKey(Category, nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + schoolclass= ormar.ForeignKey(SchoolClass) + category= ormar.ForeignKey(Category, nullable=True) class Teacher(ormar.Model): @@ -60,10 +61,10 @@ class Teacher(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - schoolclass: ormar.ForeignKey(SchoolClass) - category: ormar.ForeignKey(Category, nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + schoolclass= ormar.ForeignKey(SchoolClass) + category= ormar.ForeignKey(Category, nullable=True) @pytest.fixture(scope="module") diff --git a/tests/test_selecting_subset_of_columns.py b/tests/test_selecting_subset_of_columns.py index dee9785..fc73109 100644 --- a/tests/test_selecting_subset_of_columns.py +++ b/tests/test_selecting_subset_of_columns.py @@ -1,3 +1,5 @@ +from typing import Optional + import databases import pydantic import pytest @@ -16,9 +18,9 @@ class Company(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100, nullable=False) - founded: ormar.Integer(nullable=True) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100, nullable=False) + founded = ormar.Integer(nullable=True) class Car(ormar.Model): @@ -27,13 +29,13 @@ class Car(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - manufacturer: ormar.ForeignKey(Company) - name: ormar.String(max_length=100) - year: ormar.Integer(nullable=True) - gearbox_type: ormar.String(max_length=20, nullable=True) - gears: ormar.Integer(nullable=True) - aircon_type: ormar.String(max_length=20, nullable=True) + id = ormar.Integer(primary_key=True) + manufacturer= ormar.ForeignKey(Company) + name = ormar.String(max_length=100) + year = ormar.Integer(nullable=True) + gearbox_type = ormar.String(max_length=20, nullable=True) + gears = ormar.Integer(nullable=True) + aircon_type = ormar.String(max_length=20, nullable=True) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_server_default.py b/tests/test_server_default.py index 4c4db66..dbee490 100644 --- a/tests/test_server_default.py +++ b/tests/test_server_default.py @@ -20,11 +20,11 @@ class Product(ormar.Model): metadata = metadata database = database - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - company: ormar.String(max_length=200, server_default="Acme") - sort_order: ormar.Integer(server_default=text("10")) - created: ormar.DateTime(server_default=func.now()) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + company = ormar.String(max_length=200, server_default="Acme") + sort_order = ormar.Integer(server_default=text("10")) + created= ormar.DateTime(server_default=func.now()) @pytest.fixture(scope="module") diff --git a/tests/test_unique_constraints.py b/tests/test_unique_constraints.py index 93dd9ba..e39cbbc 100644 --- a/tests/test_unique_constraints.py +++ b/tests/test_unique_constraints.py @@ -21,9 +21,9 @@ class Product(ormar.Model): database = database constraints = [ormar.UniqueColumns("name", "company")] - id: ormar.Integer(primary_key=True) - name: ormar.String(max_length=100) - company: ormar.String(max_length=200) + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + company = ormar.String(max_length=200) @pytest.fixture(scope="module") From 3c10892db771a12b5508d17907314ae8104d8879 Mon Sep 17 00:00:00 2001 From: collerek Date: Sat, 31 Oct 2020 18:22:15 +0100 Subject: [PATCH 04/19] liniting, black, mypy fixes --- ormar/fields/many_to_many.py | 41 ++++++------ ormar/fields/model_fields.py | 78 +++++++++++------------ ormar/models/metaclass.py | 66 ++++++++++--------- ormar/models/model.py | 42 ++++++------ tests/test_aliases.py | 4 +- tests/test_fastapi_docs.py | 4 +- tests/test_foreign_keys.py | 16 ++--- tests/test_many_to_many.py | 6 +- tests/test_model_definition.py | 5 ++ tests/test_models.py | 14 ++-- tests/test_more_same_table_joins.py | 10 +-- tests/test_new_annotation_style.py | 8 +-- tests/test_queryset_level_methods.py | 4 +- tests/test_same_table_joins.py | 10 +-- tests/test_selecting_subset_of_columns.py | 2 +- tests/test_server_default.py | 2 +- 16 files changed, 157 insertions(+), 155 deletions(-) diff --git a/ormar/fields/many_to_many.py b/ormar/fields/many_to_many.py index 5160a26..e9fbb68 100644 --- a/ormar/fields/many_to_many.py +++ b/ormar/fields/many_to_many.py @@ -1,4 +1,4 @@ -from typing import Any, List, Optional, TYPE_CHECKING, Type, Union, Sequence +from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Type, Union from ormar.fields import BaseField from ormar.fields.foreign_key import ForeignKeyField @@ -10,13 +10,13 @@ REF_PREFIX = "#/components/schemas/" def ManyToMany( - to: Type["Model"], - through: Type["Model"], - *, - name: str = None, - unique: bool = False, - virtual: bool = False, - **kwargs: Any + to: Type["Model"], + through: Type["Model"], + *, + name: str = None, + unique: bool = False, + virtual: bool = False, + **kwargs: Any ) -> Type["ManyToManyField"]: to_field = to.Meta.model_fields[to.Meta.pkname] related_name = kwargs.pop("related_name", None) @@ -50,7 +50,8 @@ def ManyToMany( class ManyToManyField(ForeignKeyField): through: Type["Model"] - if TYPE_CHECKING: # pragma nocover + if TYPE_CHECKING: # noqa: C901; pragma nocover + @staticmethod async def add(item: "Model") -> None: pass @@ -62,7 +63,7 @@ class ManyToManyField(ForeignKeyField): from ormar import QuerySet @staticmethod - def filter(**kwargs: Any) -> "QuerySet": # noqa: A003 + def filter(**kwargs: Any) -> "QuerySet": # noqa: A003, A001 pass @staticmethod @@ -70,15 +71,15 @@ class ManyToManyField(ForeignKeyField): pass @staticmethod - async def exists(self) -> bool: - return await self.queryset.exists() + async def exists() -> bool: + pass @staticmethod - async def count(self) -> int: - return await self.queryset.count() + async def count() -> int: + pass @staticmethod - async def clear(self) -> int: + async def clear() -> int: pass @staticmethod @@ -86,21 +87,21 @@ class ManyToManyField(ForeignKeyField): pass @staticmethod - def offset(self, offset: int) -> "QuerySet": + def offset(offset: int) -> "QuerySet": pass @staticmethod - async def first(self, **kwargs: Any) -> "Model": + async def first(**kwargs: Any) -> "Model": pass @staticmethod - async def get(self, **kwargs: Any) -> "Model": + async def get(**kwargs: Any) -> "Model": pass @staticmethod - async def all(self, **kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003 + async def all(**kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003, A001 pass @staticmethod - async def create(self, **kwargs: Any) -> "Model": + async def create(**kwargs: Any) -> "Model": pass diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 9c53574..780d2dd 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -12,7 +12,7 @@ from ormar.fields.base import BaseField # noqa I101 def is_field_nullable( - nullable: Optional[bool], default: Any, server_default: Any + nullable: Optional[bool], default: Any, server_default: Any ) -> bool: if nullable is None: return default is not None or server_default is not None @@ -63,15 +63,15 @@ class String(ModelFieldFactory, str): _pydantic_type = pydantic.ConstrainedStr def __new__( # type: ignore # noqa CFQ002 - cls, - *, - allow_blank: bool = True, - strip_whitespace: bool = False, - min_length: int = None, - max_length: int = None, - curtail_length: int = None, - regex: str = None, - **kwargs: Any + cls, + *, + allow_blank: bool = True, + strip_whitespace: bool = False, + min_length: int = None, + max_length: int = None, + curtail_length: int = None, + regex: str = None, + **kwargs: Any ) -> Type[BaseField]: # type: ignore kwargs = { **kwargs, @@ -102,12 +102,12 @@ class Integer(ModelFieldFactory, int): _pydantic_type = pydantic.ConstrainedInt def __new__( # type: ignore - cls, - *, - minimum: int = None, - maximum: int = None, - multiple_of: int = None, - **kwargs: Any + cls, + *, + minimum: int = None, + maximum: int = None, + multiple_of: int = None, + **kwargs: Any ) -> Type[BaseField]: autoincrement = kwargs.pop("autoincrement", None) autoincrement = ( @@ -137,7 +137,7 @@ class Text(ModelFieldFactory, str): _pydantic_type = pydantic.ConstrainedStr def __new__( # type: ignore - cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any + cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any ) -> Type[BaseField]: kwargs = { **kwargs, @@ -160,12 +160,12 @@ class Float(ModelFieldFactory, float): _pydantic_type = pydantic.ConstrainedFloat def __new__( # type: ignore - cls, - *, - minimum: float = None, - maximum: float = None, - multiple_of: int = None, - **kwargs: Any + cls, + *, + minimum: float = None, + maximum: float = None, + multiple_of: int = None, + **kwargs: Any ) -> Type[BaseField]: kwargs = { **kwargs, @@ -234,12 +234,12 @@ class BigInteger(Integer, int): _pydantic_type = pydantic.ConstrainedInt def __new__( # type: ignore - cls, - *, - minimum: int = None, - maximum: int = None, - multiple_of: int = None, - **kwargs: Any + cls, + *, + minimum: int = None, + maximum: int = None, + multiple_of: int = None, + **kwargs: Any ) -> Type[BaseField]: autoincrement = kwargs.pop("autoincrement", None) autoincrement = ( @@ -269,16 +269,16 @@ class Decimal(ModelFieldFactory, decimal.Decimal): _pydantic_type = pydantic.ConstrainedDecimal def __new__( # type: ignore # noqa CFQ002 - cls, - *, - minimum: float = None, - maximum: float = None, - multiple_of: int = None, - precision: int = None, - scale: int = None, - max_digits: int = None, - decimal_places: int = None, - **kwargs: Any + cls, + *, + minimum: float = None, + maximum: float = None, + multiple_of: int = None, + precision: int = None, + scale: int = None, + max_digits: int = None, + decimal_places: int = None, + **kwargs: Any ) -> Type[BaseField]: kwargs = { **kwargs, diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index 8d264a5..4e09af5 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -42,7 +42,7 @@ def register_relation_on_build(table_name: str, field: Type[ForeignKeyField]) -> def register_many_to_many_relation_on_build( - table_name: str, field: Type[ManyToManyField] + table_name: str, field: Type[ManyToManyField] ) -> None: alias_manager.add_relation_type(field.through.Meta.tablename, table_name) alias_manager.add_relation_type( @@ -51,11 +51,11 @@ def register_many_to_many_relation_on_build( def reverse_field_not_already_registered( - child: Type["Model"], child_model_name: str, parent_model: Type["Model"] + child: Type["Model"], child_model_name: str, parent_model: Type["Model"] ) -> bool: return ( - child_model_name not in parent_model.__fields__ - and child.get_name() not in parent_model.__fields__ + child_model_name not in parent_model.__fields__ + and child.get_name() not in parent_model.__fields__ ) @@ -66,7 +66,7 @@ def expand_reverse_relationships(model: Type["Model"]) -> None: parent_model = model_field.to child = model if reverse_field_not_already_registered( - child, child_model_name, parent_model + child, child_model_name, parent_model ): register_reverse_model_fields( parent_model, child, child_model_name, model_field @@ -74,10 +74,10 @@ def expand_reverse_relationships(model: Type["Model"]) -> None: def register_reverse_model_fields( - model: Type["Model"], - child: Type["Model"], - child_model_name: str, - model_field: Type["ForeignKeyField"], + model: Type["Model"], + child: Type["Model"], + child_model_name: str, + model_field: Type["ForeignKeyField"], ) -> None: if issubclass(model_field, ManyToManyField): model.Meta.model_fields[child_model_name] = ManyToMany( @@ -92,7 +92,7 @@ def register_reverse_model_fields( def adjust_through_many_to_many_model( - model: Type["Model"], child: Type["Model"], model_field: Type[ManyToManyField] + model: Type["Model"], child: Type["Model"], model_field: Type[ManyToManyField] ) -> None: model_field.through.Meta.model_fields[model.get_name()] = ForeignKey( model, name=model.get_name(), ondelete="CASCADE" @@ -109,7 +109,7 @@ def adjust_through_many_to_many_model( def create_pydantic_field( - field_name: str, model: Type["Model"], model_field: Type[ManyToManyField] + field_name: str, model: Type["Model"], model_field: Type[ManyToManyField] ) -> None: model_field.through.__fields__[field_name] = ModelField( name=field_name, @@ -121,7 +121,7 @@ def create_pydantic_field( def create_and_append_m2m_fk( - model: Type["Model"], model_field: Type[ManyToManyField] + model: Type["Model"], model_field: Type[ManyToManyField] ) -> None: column = sqlalchemy.Column( model.get_name(), @@ -137,7 +137,7 @@ def create_and_append_m2m_fk( def check_pk_column_validity( - field_name: str, field: BaseField, pkname: Optional[str] + field_name: str, field: BaseField, pkname: Optional[str] ) -> Optional[str]: if pkname is not None: raise ModelDefinitionError("Only one primary key column is allowed.") @@ -147,7 +147,7 @@ def check_pk_column_validity( def sqlalchemy_columns_from_model_fields( - model_fields: Dict, table_name: str + model_fields: Dict, table_name: str ) -> Tuple[Optional[str], List[sqlalchemy.Column]]: columns = [] pkname = None @@ -161,9 +161,9 @@ def sqlalchemy_columns_from_model_fields( if field.primary_key: pkname = check_pk_column_validity(field_name, field, pkname) if ( - not field.pydantic_only - and not field.virtual - and not issubclass(field, ManyToManyField) + not field.pydantic_only + and not field.virtual + and not issubclass(field, ManyToManyField) ): columns.append(field.get_column(field_name)) register_relation_in_alias_manager(table_name, field) @@ -171,7 +171,7 @@ def sqlalchemy_columns_from_model_fields( def register_relation_in_alias_manager( - table_name: str, field: Type[ForeignKeyField] + table_name: str, field: Type[ForeignKeyField] ) -> None: if issubclass(field, ManyToManyField): register_many_to_many_relation_on_build(table_name, field) @@ -180,7 +180,7 @@ def register_relation_in_alias_manager( def populate_default_pydantic_field_value( - ormar_field: Type[BaseField], field_name: str, attrs: dict + ormar_field: Type[BaseField], field_name: str, attrs: dict ) -> dict: curr_def_value = attrs.get(field_name, ormar.Undefined) if lenient_issubclass(curr_def_value, ormar.fields.BaseField): @@ -193,7 +193,7 @@ def populate_default_pydantic_field_value( def check_if_field_annotation_or_value_is_ormar( - field: Any, field_name: str, attrs: Dict + field: Any, field_name: str, attrs: Dict ) -> bool: return lenient_issubclass(field, BaseField) or issubclass( attrs.get(field_name, type), BaseField @@ -201,15 +201,21 @@ def check_if_field_annotation_or_value_is_ormar( def extract_field_from_annotation_or_value( - field: Any, field_name: str, attrs: Dict + field: Any, field_name: str, attrs: Dict ) -> Type[ormar.fields.BaseField]: return field if lenient_issubclass(field, BaseField) else attrs.get(field_name) def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: model_fields = {} - potential_fields = {k: v for k, v in attrs["__annotations__"].items() if lenient_issubclass(v, BaseField)} - potential_fields.update({k: v for k, v in attrs.items() if lenient_issubclass(v, BaseField)}) + potential_fields = { + k: v + for k, v in attrs["__annotations__"].items() + if lenient_issubclass(v, BaseField) + } + potential_fields.update( + {k: v for k, v in attrs.items() if lenient_issubclass(v, BaseField)} + ) for field_name, field in potential_fields.items(): # ormar fields can be used as annotation or as default value if check_if_field_annotation_or_value_is_ormar(field, field_name, attrs): @@ -226,17 +232,15 @@ def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: return attrs, model_fields -def extract_annotations_and_default_vals( - attrs: dict -) -> Tuple[Dict, Dict]: - key = '__annotations__' +def extract_annotations_and_default_vals(attrs: dict) -> Tuple[Dict, Dict]: + key = "__annotations__" attrs[key] = attrs.get(key, {}) attrs, model_fields = populate_pydantic_default_values(attrs) return attrs, model_fields def populate_meta_tablename_columns_and_pk( - name: str, new_model: Type["Model"] + name: str, new_model: Type["Model"] ) -> Type["Model"]: tablename = name.lower() + "s" new_model.Meta.tablename = ( @@ -262,7 +266,7 @@ def populate_meta_tablename_columns_and_pk( def populate_meta_sqlalchemy_table_if_required( - new_model: Type["Model"], + new_model: Type["Model"], ) -> Type["Model"]: if not hasattr(new_model.Meta, "table"): new_model.Meta.table = sqlalchemy.Table( @@ -304,7 +308,7 @@ def choices_validator(cls: Type["Model"], values: Dict[str, Any]) -> Dict[str, A def populate_choices_validators( # noqa CCR001 - model: Type["Model"], attrs: Dict + model: Type["Model"], attrs: Dict ) -> None: if model_initialized_and_has_model_fields(model): for _, field in model.Meta.model_fields.items(): @@ -317,7 +321,7 @@ def populate_choices_validators( # noqa CCR001 class ModelMetaclass(pydantic.main.ModelMetaclass): def __new__( # type: ignore - mcs: "ModelMetaclass", name: str, bases: Any, attrs: dict + mcs: "ModelMetaclass", name: str, bases: Any, attrs: dict ) -> "ModelMetaclass": attrs["Config"] = get_pydantic_base_orm_config() attrs["__name__"] = name diff --git a/ormar/models/model.py b/ormar/models/model.py index f8b6ea0..a086a3b 100644 --- a/ormar/models/model.py +++ b/ormar/models/model.py @@ -45,12 +45,12 @@ class Model(NewBaseModel): @classmethod def from_row( # noqa CCR001 - cls: Type[T], - row: sqlalchemy.engine.ResultProxy, - select_related: List = None, - related_models: Any = None, - previous_table: str = None, - fields: List = None, + cls: Type[T], + row: sqlalchemy.engine.ResultProxy, + select_related: List = None, + related_models: Any = None, + previous_table: str = None, + fields: List = None, ) -> Optional[T]: item: Dict[str, Any] = {} @@ -60,9 +60,9 @@ class Model(NewBaseModel): related_models = group_related_list(select_related) if ( - previous_table - and previous_table in cls.Meta.model_fields - and issubclass(cls.Meta.model_fields[previous_table], ManyToManyField) + previous_table + and previous_table in cls.Meta.model_fields + and issubclass(cls.Meta.model_fields[previous_table], ManyToManyField) ): previous_table = cls.Meta.model_fields[ previous_table @@ -90,12 +90,12 @@ class Model(NewBaseModel): @classmethod def populate_nested_models_from_row( - cls, - item: dict, - row: sqlalchemy.engine.ResultProxy, - related_models: Any, - previous_table: sqlalchemy.Table, - fields: List = None, + cls, + item: dict, + row: sqlalchemy.engine.ResultProxy, + related_models: Any, + previous_table: sqlalchemy.Table, + fields: List = None, ) -> dict: for related in related_models: if isinstance(related_models, dict) and related_models[related]: @@ -119,12 +119,12 @@ class Model(NewBaseModel): @classmethod def extract_prefixed_table_columns( # noqa CCR001 - cls, - item: dict, - row: sqlalchemy.engine.result.ResultProxy, - table_prefix: str, - fields: List = None, - nested: bool = False, + cls, + item: dict, + row: sqlalchemy.engine.result.ResultProxy, + table_prefix: str, + fields: List = None, + nested: bool = False, ) -> dict: # databases does not keep aliases in Record for postgres, change to raw row diff --git a/tests/test_aliases.py b/tests/test_aliases.py index b07e3af..1d84bff 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -38,9 +38,7 @@ class Artist(ormar.Model): first_name = ormar.String(name="fname", max_length=100) last_name = ormar.String(name="lname", max_length=100) born_year = ormar.Integer(name="year") - children = ormar.ManyToMany( - Child, through=ArtistChildren - ) + children = ormar.ManyToMany(Child, through=ArtistChildren) class Album(ormar.Model): diff --git a/tests/test_fastapi_docs.py b/tests/test_fastapi_docs.py index 8ad0e56..c66cbb4 100644 --- a/tests/test_fastapi_docs.py +++ b/tests/test_fastapi_docs.py @@ -53,9 +53,7 @@ class Item(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - categories = ormar.ManyToMany( - Category, through=ItemsXCategories - ) + categories = ormar.ManyToMany(Category, through=ItemsXCategories) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_foreign_keys.py b/tests/test_foreign_keys.py index 4fe836c..fc3f3e5 100644 --- a/tests/test_foreign_keys.py +++ b/tests/test_foreign_keys.py @@ -239,8 +239,8 @@ async def test_fk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(album__name="Fantasies") - .all() + .filter(album__name="Fantasies") + .all() ) assert len(tracks) == 3 for track in tracks: @@ -248,8 +248,8 @@ async def test_fk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(album__name__icontains="fan") - .all() + .filter(album__name__icontains="fan") + .all() ) assert len(tracks) == 3 for track in tracks: @@ -294,8 +294,8 @@ async def test_multiple_fk(): members = ( await Member.objects.select_related("team__org") - .filter(team__org__ident="ACME Ltd") - .all() + .filter(team__org__ident="ACME Ltd") + .all() ) assert len(members) == 4 for member in members: @@ -327,8 +327,8 @@ async def test_pk_filter(): tracks = ( await Track.objects.select_related("album") - .filter(position=2, album__name="Test") - .all() + .filter(position=2, album__name="Test") + .all() ) assert len(tracks) == 1 diff --git a/tests/test_many_to_many.py b/tests/test_many_to_many.py index d4e78ad..bd20b1d 100644 --- a/tests/test_many_to_many.py +++ b/tests/test_many_to_many.py @@ -49,10 +49,8 @@ class Post(ormar.Model): id = ormar.Integer(primary_key=True) title = ormar.String(max_length=200) - categories = ormar.ManyToMany( - Category, through=PostCategory - ) - author= ormar.ForeignKey(Author) + categories = ormar.ManyToMany(Category, through=PostCategory) + author = ormar.ForeignKey(Author) @pytest.fixture(scope="module") diff --git a/tests/test_model_definition.py b/tests/test_model_definition.py index dd3b717..840405d 100644 --- a/tests/test_model_definition.py +++ b/tests/test_model_definition.py @@ -139,6 +139,7 @@ def test_sqlalchemy_table_is_created(example): @typing.no_type_check def test_no_pk_in_model_definition(): # type: ignore with pytest.raises(ModelDefinitionError): # type: ignore + class ExampleModel2(Model): # type: ignore class Meta: tablename = "example2" @@ -150,6 +151,7 @@ def test_no_pk_in_model_definition(): # type: ignore @typing.no_type_check def test_two_pks_in_model_definition(): with pytest.raises(ModelDefinitionError): + @typing.no_type_check class ExampleModel2(Model): class Meta: @@ -163,6 +165,7 @@ def test_two_pks_in_model_definition(): @typing.no_type_check def test_setting_pk_column_as_pydantic_only_in_model_definition(): with pytest.raises(ModelDefinitionError): + class ExampleModel2(Model): class Meta: tablename = "example4" @@ -174,6 +177,7 @@ def test_setting_pk_column_as_pydantic_only_in_model_definition(): @typing.no_type_check def test_decimal_error_in_model_definition(): with pytest.raises(ModelDefinitionError): + class ExampleModel2(Model): class Meta: tablename = "example5" @@ -185,6 +189,7 @@ def test_decimal_error_in_model_definition(): @typing.no_type_check def test_string_error_in_model_definition(): with pytest.raises(ModelDefinitionError): + class ExampleModel2(Model): class Meta: tablename = "example6" diff --git a/tests/test_models.py b/tests/test_models.py index 067e0b0..84eed89 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -23,7 +23,7 @@ class JsonSample(ormar.Model): database = database id = ormar.Integer(primary_key=True) - test_json= ormar.JSON(nullable=True) + test_json = ormar.JSON(nullable=True) class UUIDSample(ormar.Model): @@ -32,7 +32,7 @@ class UUIDSample(ormar.Model): metadata = metadata database = database - id= ormar.UUID(primary_key=True, default=uuid.uuid4) + id = ormar.UUID(primary_key=True, default=uuid.uuid4) test_text = ormar.Text() @@ -55,8 +55,8 @@ class Product(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) rating = ormar.Integer(minimum=1, maximum=5) - in_stock= ormar.Boolean(default=False) - last_delivery= ormar.Date(default=datetime.now) + in_stock = ormar.Boolean(default=False) + last_delivery = ormar.Date(default=datetime.now) country_name_choices = ("Canada", "Algeria", "United States") @@ -71,10 +71,8 @@ class Country(ormar.Model): database = database id = ormar.Integer(primary_key=True) - name = ormar.String( - max_length=9, choices=country_name_choices, default="Canada", - ) - taxed= ormar.Boolean(choices=country_taxed_choices, default=True) + name = ormar.String(max_length=9, choices=country_name_choices, default="Canada",) + taxed = ormar.Boolean(choices=country_taxed_choices, default=True) country_code = ormar.Integer( minimum=0, maximum=1000, choices=country_country_code_choices, default=1 ) diff --git a/tests/test_more_same_table_joins.py b/tests/test_more_same_table_joins.py index d0b9859..a0f712f 100644 --- a/tests/test_more_same_table_joins.py +++ b/tests/test_more_same_table_joins.py @@ -40,7 +40,7 @@ class Category(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - department= ormar.ForeignKey(Department, nullable=False) + department = ormar.ForeignKey(Department, nullable=False) class Student(ormar.Model): @@ -51,8 +51,8 @@ class Student(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - schoolclass= ormar.ForeignKey(SchoolClass) - category= ormar.ForeignKey(Category, nullable=True) + schoolclass = ormar.ForeignKey(SchoolClass) + category = ormar.ForeignKey(Category, nullable=True) class Teacher(ormar.Model): @@ -63,8 +63,8 @@ class Teacher(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - schoolclass= ormar.ForeignKey(SchoolClass) - category= ormar.ForeignKey(Category, nullable=True) + schoolclass = ormar.ForeignKey(SchoolClass) + category = ormar.ForeignKey(Category, nullable=True) @pytest.fixture(scope="module") diff --git a/tests/test_new_annotation_style.py b/tests/test_new_annotation_style.py index 54a20cf..6f141dc 100644 --- a/tests/test_new_annotation_style.py +++ b/tests/test_new_annotation_style.py @@ -29,7 +29,7 @@ class Track(ormar.Model): database = database id = ormar.Integer(primary_key=True) - album= ormar.ForeignKey(Album) + album = ormar.ForeignKey(Album) title = ormar.String(max_length=100) position = ormar.Integer() @@ -41,7 +41,7 @@ class Cover(ormar.Model): database = database id = ormar.Integer(primary_key=True) - album= ormar.ForeignKey(Album, related_name="cover_pictures") + album = ormar.ForeignKey(Album, related_name="cover_pictures") title = ormar.String(max_length=100) @@ -62,7 +62,7 @@ class Team(ormar.Model): database = database id = ormar.Integer(primary_key=True) - org= ormar.ForeignKey(Organisation) + org = ormar.ForeignKey(Organisation) name = ormar.String(max_length=100) @@ -73,7 +73,7 @@ class Member(ormar.Model): database = database id = ormar.Integer(primary_key=True) - team= ormar.ForeignKey(Team) + team = ormar.ForeignKey(Team) email = ormar.String(max_length=100) diff --git a/tests/test_queryset_level_methods.py b/tests/test_queryset_level_methods.py index 1e986b5..86e6a1e 100644 --- a/tests/test_queryset_level_methods.py +++ b/tests/test_queryset_level_methods.py @@ -36,7 +36,7 @@ class ToDo(ormar.Model): id = ormar.Integer(primary_key=True) text = ormar.String(max_length=500) - completed= ormar.Boolean(default=False) + completed = ormar.Boolean(default=False) class Category(ormar.Model): @@ -57,7 +57,7 @@ class Note(ormar.Model): id = ormar.Integer(primary_key=True) text = ormar.String(max_length=500) - category= ormar.ForeignKey(Category) + category = ormar.ForeignKey(Category) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_same_table_joins.py b/tests/test_same_table_joins.py index 5d3169b..9ca0654 100644 --- a/tests/test_same_table_joins.py +++ b/tests/test_same_table_joins.py @@ -30,7 +30,7 @@ class SchoolClass(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - department= ormar.ForeignKey(Department, nullable=False) + department = ormar.ForeignKey(Department, nullable=False) class Category(ormar.Model): @@ -51,8 +51,8 @@ class Student(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - schoolclass= ormar.ForeignKey(SchoolClass) - category= ormar.ForeignKey(Category, nullable=True) + schoolclass = ormar.ForeignKey(SchoolClass) + category = ormar.ForeignKey(Category, nullable=True) class Teacher(ormar.Model): @@ -63,8 +63,8 @@ class Teacher(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - schoolclass= ormar.ForeignKey(SchoolClass) - category= ormar.ForeignKey(Category, nullable=True) + schoolclass = ormar.ForeignKey(SchoolClass) + category = ormar.ForeignKey(Category, nullable=True) @pytest.fixture(scope="module") diff --git a/tests/test_selecting_subset_of_columns.py b/tests/test_selecting_subset_of_columns.py index fc73109..7cf0173 100644 --- a/tests/test_selecting_subset_of_columns.py +++ b/tests/test_selecting_subset_of_columns.py @@ -30,7 +30,7 @@ class Car(ormar.Model): database = database id = ormar.Integer(primary_key=True) - manufacturer= ormar.ForeignKey(Company) + manufacturer = ormar.ForeignKey(Company) name = ormar.String(max_length=100) year = ormar.Integer(nullable=True) gearbox_type = ormar.String(max_length=20, nullable=True) diff --git a/tests/test_server_default.py b/tests/test_server_default.py index dbee490..f88a062 100644 --- a/tests/test_server_default.py +++ b/tests/test_server_default.py @@ -24,7 +24,7 @@ class Product(ormar.Model): name = ormar.String(max_length=100) company = ormar.String(max_length=200, server_default="Acme") sort_order = ormar.Integer(server_default=text("10")) - created= ormar.DateTime(server_default=func.now()) + created = ormar.DateTime(server_default=func.now()) @pytest.fixture(scope="module") From 79cf225ddc430a9a7b9835d4cb4cee1c2709076f Mon Sep 17 00:00:00 2001 From: collerek Date: Sat, 31 Oct 2020 18:42:13 +0100 Subject: [PATCH 05/19] fix scale and precision in decimal --- docs_src/fastapi/docs001.py | 2 +- docs_src/fields/docs001.py | 12 ++++++------ ormar/fields/foreign_key.py | 2 +- ormar/fields/many_to_many.py | 4 ++-- ormar/fields/model_fields.py | 12 ++++++------ tests/test_model_definition.py | 2 +- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs_src/fastapi/docs001.py b/docs_src/fastapi/docs001.py index 7987bef..efcc959 100644 --- a/docs_src/fastapi/docs001.py +++ b/docs_src/fastapi/docs001.py @@ -44,7 +44,7 @@ class Item(ormar.Model): id = ormar.Integer(primary_key=True) name = ormar.String(max_length=100) - category= ormar.ForeignKey(Category, nullable=True) + category = ormar.ForeignKey(Category, nullable=True) @app.get("/items/", response_model=List[Item]) diff --git a/docs_src/fields/docs001.py b/docs_src/fields/docs001.py index 304b92d..a757525 100644 --- a/docs_src/fields/docs001.py +++ b/docs_src/fields/docs001.py @@ -14,8 +14,8 @@ class Department(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Course(ormar.Model): @@ -23,10 +23,10 @@ class Course(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) - department= ormar.ForeignKey(Department) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) + department: Optional[Department] = ormar.ForeignKey(Department) department = Department(name='Science') diff --git a/ormar/fields/foreign_key.py b/ormar/fields/foreign_key.py index c2f9911..0974746 100644 --- a/ormar/fields/foreign_key.py +++ b/ormar/fields/foreign_key.py @@ -37,7 +37,7 @@ def ForeignKey( # noqa CFQ002 virtual: bool = False, onupdate: str = None, ondelete: str = None, -) -> Type["ForeignKeyField"]: +) -> Any: fk_string = to.Meta.tablename + "." + to.get_column_alias(to.Meta.pkname) to_field = to.Meta.model_fields[to.Meta.pkname] __type__ = ( diff --git a/ormar/fields/many_to_many.py b/ormar/fields/many_to_many.py index e9fbb68..79fb7fa 100644 --- a/ormar/fields/many_to_many.py +++ b/ormar/fields/many_to_many.py @@ -17,7 +17,7 @@ def ManyToMany( unique: bool = False, virtual: bool = False, **kwargs: Any -) -> Type["ManyToManyField"]: +) -> Any: to_field = to.Meta.model_fields[to.Meta.pkname] related_name = kwargs.pop("related_name", None) nullable = kwargs.pop("nullable", True) @@ -50,7 +50,7 @@ def ManyToMany( class ManyToManyField(ForeignKeyField): through: Type["Model"] - if TYPE_CHECKING: # noqa: C901; pragma nocover + if TYPE_CHECKING: # noqa: C901; #pragma nocover @staticmethod async def add(item: "Model") -> None: diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 780d2dd..29755a5 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -292,14 +292,14 @@ class Decimal(ModelFieldFactory, decimal.Decimal): kwargs["le"] = kwargs["maximum"] if kwargs.get("max_digits"): - kwargs["scale"] = kwargs["max_digits"] - elif kwargs.get("scale"): - kwargs["max_digits"] = kwargs["scale"] + kwargs["precision"] = kwargs["max_digits"] + elif kwargs.get("precision"): + kwargs["max_digits"] = kwargs["precision"] if kwargs.get("decimal_places"): - kwargs["precision"] = kwargs["decimal_places"] - elif kwargs.get("precision"): - kwargs["decimal_places"] = kwargs["precision"] + kwargs["scale"] = kwargs["decimal_places"] + elif kwargs.get("scale"): + kwargs["decimal_places"] = kwargs["scale"] return super().__new__(cls, **kwargs) diff --git a/tests/test_model_definition.py b/tests/test_model_definition.py index 840405d..66ced8a 100644 --- a/tests/test_model_definition.py +++ b/tests/test_model_definition.py @@ -31,7 +31,7 @@ class ExampleModel(Model): test_time = ormar.Time(default=datetime.time) test_json = ormar.JSON(default={}) test_bigint = ormar.BigInteger(default=0) - test_decimal = ormar.Decimal(scale=10, precision=2) + test_decimal = ormar.Decimal(scale=2, precision=10) test_decimal2 = ormar.Decimal(max_digits=10, decimal_places=2) From 9175f6004cf1eeed4b3feec8ed21b90727aaa211 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 08:29:06 +0100 Subject: [PATCH 06/19] refactor many2many typehints into protocols --- ormar/__init__.py | 8 ++-- ormar/fields/many_to_many.py | 61 ++-------------------------- ormar/protocols/__init__.py | 4 ++ ormar/protocols/queryset_protocol.py | 41 +++++++++++++++++++ ormar/protocols/relation_protocol.py | 12 ++++++ ormar/relations/querysetproxy.py | 2 +- 6 files changed, 66 insertions(+), 62 deletions(-) create mode 100644 ormar/protocols/__init__.py create mode 100644 ormar/protocols/queryset_protocol.py create mode 100644 ormar/protocols/relation_protocol.py diff --git a/ormar/__init__.py b/ormar/__init__.py index ecead7c..17324ad 100644 --- a/ormar/__init__.py +++ b/ormar/__init__.py @@ -1,5 +1,6 @@ from ormar.exceptions import ModelDefinitionError, ModelNotSet, MultipleMatches, NoMatch -from ormar.fields import ( +from ormar.protocols import QuerySetProtocol, RelationProtocol # noqa: I100 +from ormar.fields import ( # noqa: I100 BigInteger, Boolean, Date, @@ -28,8 +29,7 @@ class UndefinedType: # pragma no cover Undefined = UndefinedType() - -__version__ = "0.3.11" +__version__ = "0.4.0" __all__ = [ "Integer", "BigInteger", @@ -54,4 +54,6 @@ __all__ = [ "Undefined", "UUID", "UniqueColumns", + "QuerySetProtocol", + "RelationProtocol", ] diff --git a/ormar/fields/many_to_many.py b/ormar/fields/many_to_many.py index 79fb7fa..0573a86 100644 --- a/ormar/fields/many_to_many.py +++ b/ormar/fields/many_to_many.py @@ -1,5 +1,6 @@ -from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Type, Union +from typing import Any, List, Optional, TYPE_CHECKING, Type, Union +import ormar from ormar.fields import BaseField from ormar.fields.foreign_key import ForeignKeyField @@ -47,61 +48,5 @@ def ManyToMany( return type("ManyToMany", (ManyToManyField, BaseField), namespace) -class ManyToManyField(ForeignKeyField): +class ManyToManyField(ForeignKeyField, ormar.QuerySetProtocol, ormar.RelationProtocol): through: Type["Model"] - - if TYPE_CHECKING: # noqa: C901; #pragma nocover - - @staticmethod - async def add(item: "Model") -> None: - pass - - @staticmethod - async def remove(item: "Model") -> None: - pass - - from ormar import QuerySet - - @staticmethod - def filter(**kwargs: Any) -> "QuerySet": # noqa: A003, A001 - pass - - @staticmethod - def select_related(related: Union[List, str]) -> "QuerySet": - pass - - @staticmethod - async def exists() -> bool: - pass - - @staticmethod - async def count() -> int: - pass - - @staticmethod - async def clear() -> int: - pass - - @staticmethod - def limit(limit_count: int) -> "QuerySet": - pass - - @staticmethod - def offset(offset: int) -> "QuerySet": - pass - - @staticmethod - async def first(**kwargs: Any) -> "Model": - pass - - @staticmethod - async def get(**kwargs: Any) -> "Model": - pass - - @staticmethod - async def all(**kwargs: Any) -> Sequence[Optional["Model"]]: # noqa: A003, A001 - pass - - @staticmethod - async def create(**kwargs: Any) -> "Model": - pass diff --git a/ormar/protocols/__init__.py b/ormar/protocols/__init__.py new file mode 100644 index 0000000..214cc10 --- /dev/null +++ b/ormar/protocols/__init__.py @@ -0,0 +1,4 @@ +from ormar.protocols.queryset_protocol import QuerySetProtocol +from ormar.protocols.relation_protocol import RelationProtocol + +__all__ = ["QuerySetProtocol", "RelationProtocol"] diff --git a/ormar/protocols/queryset_protocol.py b/ormar/protocols/queryset_protocol.py new file mode 100644 index 0000000..e1c1284 --- /dev/null +++ b/ormar/protocols/queryset_protocol.py @@ -0,0 +1,41 @@ +from typing import Any, List, Optional, Protocol, Sequence, TYPE_CHECKING, Union + +if TYPE_CHECKING: # noqa: C901; #pragma nocover + from ormar import QuerySet, Model + + +class QuerySetProtocol(Protocol): # pragma: nocover + def filter(self, **kwargs: Any) -> "QuerySet": # noqa: A003, A001 + ... + + def select_related(self, related: Union[List, str]) -> "QuerySet": + ... + + async def exists(self) -> bool: + ... + + async def count(self) -> int: + ... + + async def clear(self) -> int: + ... + + def limit(self, limit_count: int) -> "QuerySet": + ... + + def offset(self, offset: int) -> "QuerySet": + ... + + async def first(self, **kwargs: Any) -> "Model": + ... + + async def get(self, **kwargs: Any) -> "Model": + ... + + async def all( # noqa: A003, A001 + self, **kwargs: Any + ) -> Sequence[Optional["Model"]]: + ... + + async def create(self, **kwargs: Any) -> "Model": + ... diff --git a/ormar/protocols/relation_protocol.py b/ormar/protocols/relation_protocol.py new file mode 100644 index 0000000..ec0fe98 --- /dev/null +++ b/ormar/protocols/relation_protocol.py @@ -0,0 +1,12 @@ +from typing import Protocol, TYPE_CHECKING, Type, Union + +if TYPE_CHECKING: # pragma: nocover + from ormar import Model + + +class RelationProtocol(Protocol): # pragma: nocover + def add(self, child: "Model") -> None: + ... + + def remove(self, child: Union["Model", Type["Model"]]) -> None: + ... diff --git a/ormar/relations/querysetproxy.py b/ormar/relations/querysetproxy.py index 88c8dc1..8e2d6b4 100644 --- a/ormar/relations/querysetproxy.py +++ b/ormar/relations/querysetproxy.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: # pragma no cover T = TypeVar("T", bound=Model) -class QuerysetProxy: +class QuerysetProxy(ormar.QuerySetProtocol): if TYPE_CHECKING: # pragma no cover relation: "Relation" From be35c804127870c4c900d349965ceb7bd5020ce9 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 08:32:12 +0100 Subject: [PATCH 07/19] remove unused pydantic_types vars --- ormar/fields/base.py | 1 - ormar/fields/foreign_key.py | 1 - ormar/fields/many_to_many.py | 1 - ormar/fields/model_fields.py | 14 -------------- 4 files changed, 17 deletions(-) diff --git a/ormar/fields/base.py b/ormar/fields/base.py index e57dc64..7b86277 100644 --- a/ormar/fields/base.py +++ b/ormar/fields/base.py @@ -14,7 +14,6 @@ if TYPE_CHECKING: # pragma no cover class BaseField(FieldInfo): __type__ = None - __pydantic_type__ = None column_type: sqlalchemy.Column constraints: List = [] diff --git a/ormar/fields/foreign_key.py b/ormar/fields/foreign_key.py index 0974746..26334b4 100644 --- a/ormar/fields/foreign_key.py +++ b/ormar/fields/foreign_key.py @@ -47,7 +47,6 @@ def ForeignKey( # noqa CFQ002 ) namespace = dict( __type__=__type__, - __pydantic_type__=__type__, to=to, name=name, nullable=nullable, diff --git a/ormar/fields/many_to_many.py b/ormar/fields/many_to_many.py index 0573a86..d53106a 100644 --- a/ormar/fields/many_to_many.py +++ b/ormar/fields/many_to_many.py @@ -29,7 +29,6 @@ def ManyToMany( ) namespace = dict( __type__=__type__, - __pydantic_type__=__type__, to=to, through=through, name=name, diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index 29755a5..e766aa7 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -22,7 +22,6 @@ def is_field_nullable( class ModelFieldFactory: _bases: Any = (BaseField,) _type: Any = None - _pydantic_type: Any = None def __new__(cls, *args: Any, **kwargs: Any) -> Type[BaseField]: # type: ignore cls.validate(**kwargs) @@ -33,7 +32,6 @@ class ModelFieldFactory: namespace = dict( __type__=cls._type, - __pydantic_type__=cls._pydantic_type, name=kwargs.pop("name", None), primary_key=kwargs.pop("primary_key", False), default=default, @@ -60,7 +58,6 @@ class ModelFieldFactory: class String(ModelFieldFactory, str): _type = str - _pydantic_type = pydantic.ConstrainedStr def __new__( # type: ignore # noqa CFQ002 cls, @@ -99,7 +96,6 @@ class String(ModelFieldFactory, str): class Integer(ModelFieldFactory, int): _type = int - _pydantic_type = pydantic.ConstrainedInt def __new__( # type: ignore cls, @@ -134,7 +130,6 @@ class Integer(ModelFieldFactory, int): class Text(ModelFieldFactory, str): _type = str - _pydantic_type = pydantic.ConstrainedStr def __new__( # type: ignore cls, *, allow_blank: bool = True, strip_whitespace: bool = False, **kwargs: Any @@ -157,7 +152,6 @@ class Text(ModelFieldFactory, str): class Float(ModelFieldFactory, float): _type = float - _pydantic_type = pydantic.ConstrainedFloat def __new__( # type: ignore cls, @@ -186,7 +180,6 @@ class Float(ModelFieldFactory, float): class Boolean(ModelFieldFactory, int): _type = bool - _pydantic_type = bool @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -195,7 +188,6 @@ class Boolean(ModelFieldFactory, int): class DateTime(ModelFieldFactory, datetime.datetime): _type = datetime.datetime - _pydantic_type = datetime.datetime @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -204,7 +196,6 @@ class DateTime(ModelFieldFactory, datetime.datetime): class Date(ModelFieldFactory, datetime.date): _type = datetime.date - _pydantic_type = datetime.date @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -213,7 +204,6 @@ class Date(ModelFieldFactory, datetime.date): class Time(ModelFieldFactory, datetime.time): _type = datetime.time - _pydantic_type = datetime.time @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -222,7 +212,6 @@ class Time(ModelFieldFactory, datetime.time): class JSON(ModelFieldFactory, pydantic.Json): _type = pydantic.Json - _pydantic_type = pydantic.Json @classmethod def get_column_type(cls, **kwargs: Any) -> Any: @@ -231,7 +220,6 @@ class JSON(ModelFieldFactory, pydantic.Json): class BigInteger(Integer, int): _type = int - _pydantic_type = pydantic.ConstrainedInt def __new__( # type: ignore cls, @@ -266,7 +254,6 @@ class BigInteger(Integer, int): class Decimal(ModelFieldFactory, decimal.Decimal): _type = decimal.Decimal - _pydantic_type = pydantic.ConstrainedDecimal def __new__( # type: ignore # noqa CFQ002 cls, @@ -321,7 +308,6 @@ class Decimal(ModelFieldFactory, decimal.Decimal): class UUID(ModelFieldFactory, uuid.UUID): _type = uuid.UUID - _pydantic_type = uuid.UUID @classmethod def get_column_type(cls, **kwargs: Any) -> Any: From 358b5c2e5258b184b2f60613e8e5afb7e33e76a9 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 10:11:25 +0100 Subject: [PATCH 08/19] restore typing in tests and docs, remove unused metaclass code --- README.md | 10 ++--- docs/index.md | 15 +++++--- docs/models.md | 2 +- docs/queries.md | 46 +++++++++++------------ docs_src/__init__.py | 0 docs_src/fastapi/__init__.py | 0 docs_src/fastapi/docs001.py | 10 ++--- docs_src/fields/__init__.py | 0 docs_src/fields/docs001.py | 4 +- docs_src/fields/docs002.py | 16 ++++---- docs_src/fields/docs003.py | 12 +++--- docs_src/fields/docs004.py | 10 ++--- docs_src/models/__init__.py | 0 docs_src/models/docs001.py | 6 +-- docs_src/models/docs002.py | 6 +-- docs_src/models/docs003.py | 6 +-- docs_src/models/docs004.py | 8 ++-- docs_src/models/docs005.py | 13 ++++--- docs_src/models/docs006.py | 8 ++-- docs_src/models/docs007.py | 11 +++--- docs_src/models/docs008.py | 8 ++-- docs_src/models/docs009.py | 18 +++++++-- docs_src/models/docs010.py | 24 +++++++++--- docs_src/queries/__init__.py | 0 docs_src/queries/docs001.py | 12 +++--- docs_src/relations/__init__.py | 0 docs_src/relations/docs001.py | 24 ++++++------ docs_src/relations/docs002.py | 20 +++++----- ormar/__init__.py | 2 + ormar/fields/model_fields.py | 20 +++++++--- ormar/models/metaclass.py | 39 +++++++------------ tests/test_aliases.py | 26 +++++++------ tests/test_columns.py | 16 ++++---- tests/test_docs/__init__.py | 0 tests/test_fastapi_docs.py | 10 ++--- tests/test_fastapi_usage.py | 10 ++--- tests/test_foreign_keys.py | 22 +++++------ tests/test_many_to_many.py | 18 ++++----- tests/test_model_definition.py | 26 ++++++------- tests/test_models.py | 32 ++++++++-------- tests/test_more_reallife_fastapi.py | 10 ++--- tests/test_more_same_table_joins.py | 30 +++++++-------- tests/test_new_annotation_style.py | 34 ++++++++--------- tests/test_non_integer_pkey.py | 4 +- tests/test_queryset_level_methods.py | 24 ++++++------ tests/test_same_table_joins.py | 30 +++++++-------- tests/test_selecting_subset_of_columns.py | 20 +++++----- tests/test_server_default.py | 10 ++--- tests/test_unique_constraints.py | 6 +-- 49 files changed, 354 insertions(+), 324 deletions(-) create mode 100644 docs_src/__init__.py create mode 100644 docs_src/fastapi/__init__.py create mode 100644 docs_src/fields/__init__.py create mode 100644 docs_src/models/__init__.py create mode 100644 docs_src/queries/__init__.py create mode 100644 docs_src/relations/__init__.py create mode 100644 tests/test_docs/__init__.py diff --git a/README.md b/README.md index 2a5988b..c2b0daa 100644 --- a/README.md +++ b/README.md @@ -75,8 +75,8 @@ class Album(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(length=100) class Track(ormar.Model): @@ -85,10 +85,10 @@ class Track(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) + id: int = ormar.Integer(primary_key=True) album= ormar.ForeignKey(Album) - title = ormar.String(length=100) - position = ormar.Integer() + title: str = ormar.String(length=100) + position: int = ormar.Integer() # Create some records to work with. diff --git a/docs/index.md b/docs/index.md index 3d0f507..0b691dd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -74,9 +74,12 @@ class Album(ormar.Model): tablename = "album" metadata = metadata database = database - - id = ormar.Integer(primary_key=True) - name = ormar.String(length=100) + + # note that type hints are optional so + # id = ormar.Integer(primary_key=True) + # is also valid + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(length=100) class Track(ormar.Model): @@ -85,10 +88,10 @@ class Track(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) + id: int = ormar.Integer(primary_key=True) album: Optional[Album] =ormar.ForeignKey(Album) - title = ormar.String(length=100) - position = ormar.Integer() + title: str = ormar.String(length=100) + position: int = ormar.Integer() # Create some records to work with. diff --git a/docs/models.md b/docs/models.md index cb063fb..495b839 100644 --- a/docs/models.md +++ b/docs/models.md @@ -34,7 +34,7 @@ By default if you assign primary key to `Integer` field, the `autoincrement` opt You can disable by passing `autoincremant=False`. ```Python -id = ormar.Integer(primary_key=True, autoincrement=False) +id: int = ormar.Integer(primary_key=True, autoincrement=False) ``` ### Fields names vs Column names diff --git a/docs/queries.md b/docs/queries.md index dd6e249..18c6479 100644 --- a/docs/queries.md +++ b/docs/queries.md @@ -109,10 +109,10 @@ class Book(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - title = ormar.String(max_length=200) - author = ormar.String(max_length=100) - genre = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + author: str = ormar.String(max_length=100) + genre: str = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') @@ -146,10 +146,10 @@ class Book(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - title = ormar.String(max_length=200) - author = ormar.String(max_length=100) - genre = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + author: str = ormar.String(max_length=100) + genre: str = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') @@ -192,8 +192,8 @@ class ToDo(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - text = ormar.String(max_length=500) + id: int = ormar.Integer(primary_key=True) + text: str = ormar.String(max_length=500) completed= ormar.Boolean(default=False) # create multiple instances at once with bulk_create @@ -259,10 +259,10 @@ class Book(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - title = ormar.String(max_length=200) - author = ormar.String(max_length=100) - genre = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + author: str = ormar.String(max_length=100) + genre: str = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy') @@ -470,9 +470,9 @@ class Company(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - founded = ormar.Integer(nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + founded: int = ormar.Integer(nullable=True) class Car(ormar.Model): @@ -481,13 +481,13 @@ class Car(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) + id: int = ormar.Integer(primary_key=True) manufacturer= ormar.ForeignKey(Company) - name = ormar.String(max_length=100) - year = ormar.Integer(nullable=True) - gearbox_type = ormar.String(max_length=20, nullable=True) - gears = ormar.Integer(nullable=True) - aircon_type = ormar.String(max_length=20, nullable=True) + name: str = ormar.String(max_length=100) + year: int = ormar.Integer(nullable=True) + gearbox_type: str = ormar.String(max_length=20, nullable=True) + gears: int = ormar.Integer(nullable=True) + aircon_type: str = ormar.String(max_length=20, nullable=True) diff --git a/docs_src/__init__.py b/docs_src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs_src/fastapi/__init__.py b/docs_src/fastapi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs_src/fastapi/docs001.py b/docs_src/fastapi/docs001.py index efcc959..4d2087c 100644 --- a/docs_src/fastapi/docs001.py +++ b/docs_src/fastapi/docs001.py @@ -32,8 +32,8 @@ class Category(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Item(ormar.Model): @@ -42,9 +42,9 @@ class Item(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - category = ormar.ForeignKey(Category, nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + category: Optional[Category] = ormar.ForeignKey(Category, nullable=True) @app.get("/items/", response_model=List[Item]) diff --git a/docs_src/fields/__init__.py b/docs_src/fields/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs_src/fields/docs001.py b/docs_src/fields/docs001.py index a757525..dd79791 100644 --- a/docs_src/fields/docs001.py +++ b/docs_src/fields/docs001.py @@ -29,8 +29,8 @@ class Course(ormar.Model): department: Optional[Department] = ormar.ForeignKey(Department) -department = Department(name='Science') -course = Course(name='Math', completed=False, department=department) +department = Department(name="Science") +course = Course(name="Math", completed=False, department=department) print(department.courses[0]) # Will produce: diff --git a/docs_src/fields/docs002.py b/docs_src/fields/docs002.py index 13e4544..2432856 100644 --- a/docs_src/fields/docs002.py +++ b/docs_src/fields/docs002.py @@ -14,8 +14,8 @@ class Department(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Course(ormar.Model): @@ -23,14 +23,14 @@ class Course(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) - department= ormar.ForeignKey(Department, related_name="my_courses") + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) + department: Optional[Department] = ormar.ForeignKey(Department, related_name="my_courses") -department = Department(name='Science') -course = Course(name='Math', completed=False, department=department) +department = Department(name="Science") +course = Course(name="Math", completed=False, department=department) print(department.my_courses[0]) # Will produce: diff --git a/docs_src/fields/docs003.py b/docs_src/fields/docs003.py index 7a7f8b9..5066f60 100644 --- a/docs_src/fields/docs003.py +++ b/docs_src/fields/docs003.py @@ -14,8 +14,8 @@ class Department(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Course(ormar.Model): @@ -23,7 +23,7 @@ class Course(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) - department= ormar.ForeignKey(Department) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) + department: Optional[Department] = ormar.ForeignKey(Department) diff --git a/docs_src/fields/docs004.py b/docs_src/fields/docs004.py index 793dc93..8a4e1ab 100644 --- a/docs_src/fields/docs004.py +++ b/docs_src/fields/docs004.py @@ -16,8 +16,8 @@ class Product(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - company = ormar.String(max_length=200, server_default='Acme') - sort_order = ormar.Integer(server_default=text("10")) - created= ormar.DateTime(server_default=func.now()) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + company: str = ormar.String(max_length=200, server_default="Acme") + sort_order: int = ormar.Integer(server_default=text("10")) + created: datetime = ormar.DateTime(server_default=func.now()) diff --git a/docs_src/models/__init__.py b/docs_src/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs_src/models/docs001.py b/docs_src/models/docs001.py index 78e2238..36d7fd9 100644 --- a/docs_src/models/docs001.py +++ b/docs_src/models/docs001.py @@ -12,6 +12,6 @@ class Course(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) diff --git a/docs_src/models/docs002.py b/docs_src/models/docs002.py index b8f206e..3450dae 100644 --- a/docs_src/models/docs002.py +++ b/docs_src/models/docs002.py @@ -15,6 +15,6 @@ class Course(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) diff --git a/docs_src/models/docs003.py b/docs_src/models/docs003.py index 92a7d49..e79bfba 100644 --- a/docs_src/models/docs003.py +++ b/docs_src/models/docs003.py @@ -12,9 +12,9 @@ class Course(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) print(Course.__fields__) diff --git a/docs_src/models/docs004.py b/docs_src/models/docs004.py index f40c1f1..cc8bce8 100644 --- a/docs_src/models/docs004.py +++ b/docs_src/models/docs004.py @@ -8,13 +8,13 @@ metadata = sqlalchemy.MetaData() class Course(ormar.Model): - class Meta: + class Meta(ormar.ModelMeta): # note you don't have to subclass - but it's recommended for ide completion and mypy database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) print(Course.Meta.table.columns) diff --git a/docs_src/models/docs005.py b/docs_src/models/docs005.py index 50e3515..cd0fa9d 100644 --- a/docs_src/models/docs005.py +++ b/docs_src/models/docs005.py @@ -8,15 +8,16 @@ metadata = sqlalchemy.MetaData() class Course(ormar.Model): - class Meta: + class Meta(ormar.ModelMeta): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) -print({x:v.__dict__ for x,v in Course.Meta.model_fields.items()}) + +print({x: v.__dict__ for x, v in Course.Meta.model_fields.items()}) """ Will produce: {'completed': mappingproxy({'autoincrement': False, @@ -63,4 +64,4 @@ Will produce: 'server_default': None, 'strip_whitespace': False, 'unique': False})} -""" \ No newline at end of file +""" diff --git a/docs_src/models/docs006.py b/docs_src/models/docs006.py index 7926cfb..7649c2c 100644 --- a/docs_src/models/docs006.py +++ b/docs_src/models/docs006.py @@ -14,8 +14,8 @@ class Course(ormar.Model): # define your constraints in Meta class of the model # it's a list that can contain multiple constraints # hera a combination of name and column will have to be unique in db - constraints = [ormar.UniqueColumns('name', 'completed')] + constraints = [ormar.UniqueColumns("name", "completed")] - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed = ormar.Boolean(default=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) diff --git a/docs_src/models/docs007.py b/docs_src/models/docs007.py index 751cace..2f4b7b5 100644 --- a/docs_src/models/docs007.py +++ b/docs_src/models/docs007.py @@ -12,12 +12,11 @@ class Course(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed = ormar.Boolean(default=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) course = Course(name="Painting for dummies", completed=False) -await course.save() - -await Course.objects.create(name="Painting for dummies", completed=False) +await course.save() # type: ignore +await Course.objects.create(name="Painting for dummies", completed=False) # type: ignore diff --git a/docs_src/models/docs008.py b/docs_src/models/docs008.py index b7ab424..da2444c 100644 --- a/docs_src/models/docs008.py +++ b/docs_src/models/docs008.py @@ -13,7 +13,7 @@ class Child(ormar.Model): metadata = metadata database = database - id = ormar.Integer(name='child_id', primary_key=True) - first_name = ormar.String(name='fname', max_length=100) - last_name = ormar.String(name='lname', max_length=100) - born_year = ormar.Integer(name='year_born', nullable=True) + id: int = ormar.Integer(name="child_id", primary_key=True) + first_name: str = ormar.String(name="fname", max_length=100) + last_name: str = ormar.String(name="lname", max_length=100) + born_year: int = ormar.Integer(name="year_born", nullable=True) diff --git a/docs_src/models/docs009.py b/docs_src/models/docs009.py index 71b1bb3..747f612 100644 --- a/docs_src/models/docs009.py +++ b/docs_src/models/docs009.py @@ -1,9 +1,21 @@ +from typing import Optional + +import databases +import sqlalchemy + +import ormar +from .docs010 import Artist # previous example + +database = databases.Database("sqlite:///test.db", force_rollback=True) +metadata = sqlalchemy.MetaData() + + class Album(ormar.Model): class Meta: tablename = "music_albums" metadata = metadata database = database - id = ormar.Integer(name='album_id', primary_key=True) - name = ormar.String(name='album_name', max_length=100) - artist= ormar.ForeignKey(Artist, name='artist_id') + id: int = ormar.Integer(name="album_id", primary_key=True) + name: str = ormar.String(name="album_name", max_length=100) + artist: Optional[Artist] = ormar.ForeignKey(Artist, name="artist_id") diff --git a/docs_src/models/docs010.py b/docs_src/models/docs010.py index dd52267..af2c9dc 100644 --- a/docs_src/models/docs010.py +++ b/docs_src/models/docs010.py @@ -1,3 +1,15 @@ +from typing import Optional, Union, List + +import databases +import sqlalchemy + +import ormar +from .docs008 import Child + +database = databases.Database("sqlite:///test.db", force_rollback=True) +metadata = sqlalchemy.MetaData() + + class ArtistChildren(ormar.Model): class Meta: tablename = "children_x_artists" @@ -11,8 +23,10 @@ class Artist(ormar.Model): metadata = metadata database = database - id = ormar.Integer(name='artist_id', primary_key=True) - first_name = ormar.String(name='fname', max_length=100) - last_name = ormar.String(name='lname', max_length=100) - born_year = ormar.Integer(name='year') - children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany(Child, through=ArtistChildren) + id: int = ormar.Integer(name="artist_id", primary_key=True) + first_name: str = ormar.String(name="fname", max_length=100) + last_name: str = ormar.String(name="lname", max_length=100) + born_year: int = ormar.Integer(name="year") + children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany( + Child, through=ArtistChildren + ) diff --git a/docs_src/queries/__init__.py b/docs_src/queries/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs_src/queries/docs001.py b/docs_src/queries/docs001.py index 6edaa53..850030e 100644 --- a/docs_src/queries/docs001.py +++ b/docs_src/queries/docs001.py @@ -14,8 +14,8 @@ class Album(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Track(ormar.Model): @@ -24,7 +24,7 @@ class Track(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - album= ormar.ForeignKey(Album) - title = ormar.String(max_length=100) - position = ormar.Integer() \ No newline at end of file + id: int = ormar.Integer(primary_key=True) + album: Optional[Album] = ormar.ForeignKey(Album) + title: str = ormar.String(max_length=100) + position: int = ormar.Integer() diff --git a/docs_src/relations/__init__.py b/docs_src/relations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs_src/relations/docs001.py b/docs_src/relations/docs001.py index 6425f39..48307d2 100644 --- a/docs_src/relations/docs001.py +++ b/docs_src/relations/docs001.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, Dict, Union import databases import sqlalchemy @@ -14,8 +14,8 @@ class Department(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Course(ormar.Model): @@ -23,22 +23,22 @@ class Course(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - completed= ormar.Boolean(default=False) - department= ormar.ForeignKey(Department) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) + department: Optional[Union[Department, Dict]] = ormar.ForeignKey(Department) -department = Department(name='Science') +department = Department(name="Science") # set up a relation with actual Model instance -course = Course(name='Math', completed=False, department=department) +course = Course(name="Math", completed=False, department=department) # set up relation with only related model pk value -course2 = Course(name='Math II', completed=False, department=department.pk) +course2 = Course(name="Math II", completed=False, department=department.pk) # set up a relation with dictionary corresponding to related model -course3 = Course(name='Math III', completed=False, department=department.dict()) +course3 = Course(name="Math III", completed=False, department=department.dict()) # explicitly set up None -course4 = Course(name='Math III', completed=False, department=None) +course4 = Course(name="Math III", completed=False, department=None) diff --git a/docs_src/relations/docs002.py b/docs_src/relations/docs002.py index 470d9b1..8dd0566 100644 --- a/docs_src/relations/docs002.py +++ b/docs_src/relations/docs002.py @@ -14,9 +14,9 @@ class Author(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - first_name = ormar.String(max_length=80) - last_name = ormar.String(max_length=80) + id: int = ormar.Integer(primary_key=True) + first_name: str = ormar.String(max_length=80) + last_name: str = ormar.String(max_length=80) class Category(ormar.Model): @@ -25,8 +25,8 @@ class Category(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=40) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=40) class PostCategory(ormar.Model): @@ -44,7 +44,9 @@ class Post(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - title = ormar.String(max_length=200) - categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(Category, through=PostCategory) - author= ormar.ForeignKey(Author) + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany( + Category, through=PostCategory + ) + author: Optional[Author] = ormar.ForeignKey(Author) diff --git a/ormar/__init__.py b/ormar/__init__.py index 17324ad..b121bb1 100644 --- a/ormar/__init__.py +++ b/ormar/__init__.py @@ -18,6 +18,7 @@ from ormar.fields import ( # noqa: I100 UniqueColumns, ) from ormar.models import Model +from ormar.models.metaclass import ModelMeta from ormar.queryset import QuerySet from ormar.relations import RelationType @@ -56,4 +57,5 @@ __all__ = [ "UniqueColumns", "QuerySetProtocol", "RelationProtocol", + "ModelMeta", ] diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index e766aa7..f32b5d4 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -1,7 +1,7 @@ import datetime import decimal import uuid -from typing import Any, Optional, Type +from typing import Any, Optional, TYPE_CHECKING, Type import pydantic import sqlalchemy @@ -178,12 +178,20 @@ class Float(ModelFieldFactory, float): return sqlalchemy.Float() -class Boolean(ModelFieldFactory, int): - _type = bool +if TYPE_CHECKING: # pragma: nocover - @classmethod - def get_column_type(cls, **kwargs: Any) -> Any: - return sqlalchemy.Boolean() + def Boolean(**kwargs: Any) -> bool: + pass + + +else: + + class Boolean(ModelFieldFactory, int): + _type = bool + + @classmethod + def get_column_type(cls, **kwargs: Any) -> Any: + return sqlalchemy.Boolean() class DateTime(ModelFieldFactory, datetime.datetime): diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index 4e09af5..0190a6b 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -1,4 +1,5 @@ import logging +import warnings from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, Union import databases @@ -192,20 +193,6 @@ def populate_default_pydantic_field_value( return attrs -def check_if_field_annotation_or_value_is_ormar( - field: Any, field_name: str, attrs: Dict -) -> bool: - return lenient_issubclass(field, BaseField) or issubclass( - attrs.get(field_name, type), BaseField - ) - - -def extract_field_from_annotation_or_value( - field: Any, field_name: str, attrs: Dict -) -> Type[ormar.fields.BaseField]: - return field if lenient_issubclass(field, BaseField) else attrs.get(field_name) - - def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: model_fields = {} potential_fields = { @@ -213,22 +200,22 @@ def populate_pydantic_default_values(attrs: Dict) -> Tuple[Dict, Dict]: for k, v in attrs["__annotations__"].items() if lenient_issubclass(v, BaseField) } + if potential_fields: + warnings.warn( + "Using ormar.Fields as type Model annotation has been deprecated," + " check documentation of current version", + DeprecationWarning, + ) + potential_fields.update( {k: v for k, v in attrs.items() if lenient_issubclass(v, BaseField)} ) for field_name, field in potential_fields.items(): - # ormar fields can be used as annotation or as default value - if check_if_field_annotation_or_value_is_ormar(field, field_name, attrs): - ormar_field = extract_field_from_annotation_or_value( - field, field_name, attrs - ) - if ormar_field.name is None: - ormar_field.name = field_name - attrs = populate_default_pydantic_field_value( - ormar_field, field_name, attrs - ) - model_fields[field_name] = ormar_field - attrs["__annotations__"][field_name] = ormar_field.__type__ + if field.name is None: + field.name = field_name + attrs = populate_default_pydantic_field_value(field, field_name, attrs) + model_fields[field_name] = field + attrs["__annotations__"][field_name] = field.__type__ return attrs, model_fields diff --git a/tests/test_aliases.py b/tests/test_aliases.py index 1d84bff..581b59c 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -1,3 +1,5 @@ +from typing import Optional, Union, List + import databases import pytest import sqlalchemy @@ -15,10 +17,10 @@ class Child(ormar.Model): metadata = metadata database = database - id = ormar.Integer(name="child_id", primary_key=True) - first_name = ormar.String(name="fname", max_length=100) - last_name = ormar.String(name="lname", max_length=100) - born_year = ormar.Integer(name="year_born", nullable=True) + id: int = ormar.Integer(name="child_id", primary_key=True) + first_name: str = ormar.String(name="fname", max_length=100) + last_name: str = ormar.String(name="lname", max_length=100) + born_year: int = ormar.Integer(name="year_born", nullable=True) class ArtistChildren(ormar.Model): @@ -34,11 +36,11 @@ class Artist(ormar.Model): metadata = metadata database = database - id = ormar.Integer(name="artist_id", primary_key=True) - first_name = ormar.String(name="fname", max_length=100) - last_name = ormar.String(name="lname", max_length=100) - born_year = ormar.Integer(name="year") - children = ormar.ManyToMany(Child, through=ArtistChildren) + id: int = ormar.Integer(name="artist_id", primary_key=True) + first_name: str = ormar.String(name="fname", max_length=100) + last_name: str = ormar.String(name="lname", max_length=100) + born_year: int = ormar.Integer(name="year") + children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany(Child, through=ArtistChildren) class Album(ormar.Model): @@ -47,9 +49,9 @@ class Album(ormar.Model): metadata = metadata database = database - id = ormar.Integer(name="album_id", primary_key=True) - name = ormar.String(name="album_name", max_length=100) - artist = ormar.ForeignKey(Artist, name="artist_id") + id: int = ormar.Integer(name="album_id", primary_key=True) + name: str = ormar.String(name="album_name", max_length=100) + artist: Optional[Artist] = ormar.ForeignKey(Artist, name="artist_id") @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_columns.py b/tests/test_columns.py index d0eb326..73cf84a 100644 --- a/tests/test_columns.py +++ b/tests/test_columns.py @@ -23,14 +23,14 @@ class Example(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=200, default="aaa") - created = ormar.DateTime(default=datetime.datetime.now) - created_day = ormar.Date(default=datetime.date.today) - created_time = ormar.Time(default=time) - description = ormar.Text(nullable=True) - value = ormar.Float(nullable=True) - data = ormar.JSON(default={}) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=200, default="aaa") + created: datetime.datetime = ormar.DateTime(default=datetime.datetime.now) + created_day: datetime.date = ormar.Date(default=datetime.date.today) + created_time: datetime.time = ormar.Time(default=time) + description: str = ormar.Text(nullable=True) + value: float = ormar.Float(nullable=True) + data: pydantic.Json = ormar.JSON(default={}) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_docs/__init__.py b/tests/test_docs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_fastapi_docs.py b/tests/test_fastapi_docs.py index c66cbb4..9fa503f 100644 --- a/tests/test_fastapi_docs.py +++ b/tests/test_fastapi_docs.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Union, Optional import databases import pytest @@ -38,8 +38,8 @@ class Category(ormar.Model): class Meta(LocalMeta): tablename = "categories" - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class ItemsXCategories(ormar.Model): @@ -51,8 +51,8 @@ class Item(ormar.Model): class Meta(LocalMeta): pass - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) categories = ormar.ManyToMany(Category, through=ItemsXCategories) diff --git a/tests/test_fastapi_usage.py b/tests/test_fastapi_usage.py index ecf1a5f..503e582 100644 --- a/tests/test_fastapi_usage.py +++ b/tests/test_fastapi_usage.py @@ -20,8 +20,8 @@ class Category(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Item(ormar.Model): @@ -30,9 +30,9 @@ class Item(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - category = ormar.ForeignKey(Category, nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + category: Optional[Category] = ormar.ForeignKey(Category, nullable=True) @app.post("/items/", response_model=Item) diff --git a/tests/test_foreign_keys.py b/tests/test_foreign_keys.py index fc3f3e5..e7bf4e5 100644 --- a/tests/test_foreign_keys.py +++ b/tests/test_foreign_keys.py @@ -40,9 +40,9 @@ class Cover(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - album = ormar.ForeignKey(Album, related_name="cover_pictures") - title = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + album: Optional[Album] = ormar.ForeignKey(Album, related_name="cover_pictures") + title: str = ormar.String(max_length=100) class Organisation(ormar.Model): @@ -51,8 +51,8 @@ class Organisation(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - ident = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) + id: int = ormar.Integer(primary_key=True) + ident: str = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) class Organization(object): @@ -65,9 +65,9 @@ class Team(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - org = ormar.ForeignKey(Organisation) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + org: Optional[Organisation] = ormar.ForeignKey(Organisation) + name: str = ormar.String(max_length=100) class Member(ormar.Model): @@ -76,9 +76,9 @@ class Member(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - team = ormar.ForeignKey(Team) - email = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + team: Optional[Team] = ormar.ForeignKey(Team) + email: str = ormar.String(max_length=100) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_many_to_many.py b/tests/test_many_to_many.py index bd20b1d..a414cae 100644 --- a/tests/test_many_to_many.py +++ b/tests/test_many_to_many.py @@ -19,9 +19,9 @@ class Author(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - first_name = ormar.String(max_length=80) - last_name = ormar.String(max_length=80) + id: int = ormar.Integer(primary_key=True) + first_name: str = ormar.String(max_length=80) + last_name: str = ormar.String(max_length=80) class Category(ormar.Model): @@ -30,8 +30,8 @@ class Category(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=40) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=40) class PostCategory(ormar.Model): @@ -47,10 +47,10 @@ class Post(ormar.Model): database = database metadata = metadata - id = ormar.Integer(primary_key=True) - title = ormar.String(max_length=200) - categories = ormar.ManyToMany(Category, through=PostCategory) - author = ormar.ForeignKey(Author) + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(Category, through=PostCategory) + author: Optional[Author] = ormar.ForeignKey(Author) @pytest.fixture(scope="module") diff --git a/tests/test_model_definition.py b/tests/test_model_definition.py index 66ced8a..5f6d360 100644 --- a/tests/test_model_definition.py +++ b/tests/test_model_definition.py @@ -21,16 +21,16 @@ class ExampleModel(Model): tablename = "example" metadata = metadata - test = ormar.Integer(primary_key=True) - test_string = ormar.String(max_length=250) - test_text = ormar.Text(default="") - test_bool = ormar.Boolean(nullable=False) + test: int = ormar.Integer(primary_key=True) + test_string: str = ormar.String(max_length=250) + test_text: str = ormar.Text(default="") + test_bool: bool = ormar.Boolean(nullable=False) test_float: ormar.Float() = None # type: ignore test_datetime = ormar.DateTime(default=datetime.datetime.now) test_date = ormar.Date(default=datetime.date.today) test_time = ormar.Time(default=datetime.time) test_json = ormar.JSON(default={}) - test_bigint = ormar.BigInteger(default=0) + test_bigint: int = ormar.BigInteger(default=0) test_decimal = ormar.Decimal(scale=2, precision=10) test_decimal2 = ormar.Decimal(max_digits=10, decimal_places=2) @@ -53,8 +53,8 @@ class ExampleModel2(Model): tablename = "examples" metadata = metadata - test = ormar.Integer(primary_key=True) - test_string = ormar.String(max_length=250) + test: int = ormar.Integer(primary_key=True) + test_string: str = ormar.String(max_length=250) @pytest.fixture(scope="module") @@ -145,7 +145,7 @@ def test_no_pk_in_model_definition(): # type: ignore tablename = "example2" metadata = metadata - test_string = ormar.String(max_length=250) # type: ignore + test_string: str = ormar.String(max_length=250) # type: ignore @typing.no_type_check @@ -158,8 +158,8 @@ def test_two_pks_in_model_definition(): tablename = "example3" metadata = metadata - id = ormar.Integer(primary_key=True) - test_string = ormar.String(max_length=250, primary_key=True) + id: int = ormar.Integer(primary_key=True) + test_string: str = ormar.String(max_length=250, primary_key=True) @typing.no_type_check @@ -171,7 +171,7 @@ def test_setting_pk_column_as_pydantic_only_in_model_definition(): tablename = "example4" metadata = metadata - test = ormar.Integer(primary_key=True, pydantic_only=True) + test: int = ormar.Integer(primary_key=True, pydantic_only=True) @typing.no_type_check @@ -183,7 +183,7 @@ def test_decimal_error_in_model_definition(): tablename = "example5" metadata = metadata - test = ormar.Decimal(primary_key=True) + test: decimal.Decimal = ormar.Decimal(primary_key=True) @typing.no_type_check @@ -195,7 +195,7 @@ def test_string_error_in_model_definition(): tablename = "example6" metadata = metadata - test = ormar.String(primary_key=True) + test: str = ormar.String(primary_key=True) @typing.no_type_check diff --git a/tests/test_models.py b/tests/test_models.py index 84eed89..2bac6d7 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,6 +1,6 @@ import asyncio import uuid -from datetime import datetime +import datetime from typing import List import databases @@ -22,7 +22,7 @@ class JsonSample(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) + id: int = ormar.Integer(primary_key=True) test_json = ormar.JSON(nullable=True) @@ -32,8 +32,8 @@ class UUIDSample(ormar.Model): metadata = metadata database = database - id = ormar.UUID(primary_key=True, default=uuid.uuid4) - test_text = ormar.Text() + id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4) + test_text: str = ormar.Text() class User(ormar.Model): @@ -42,8 +42,8 @@ class User(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100, default="") + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100, default="") class Product(ormar.Model): @@ -52,11 +52,11 @@ class Product(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - rating = ormar.Integer(minimum=1, maximum=5) - in_stock = ormar.Boolean(default=False) - last_delivery = ormar.Date(default=datetime.now) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + rating: int = ormar.Integer(minimum=1, maximum=5) + in_stock: bool = ormar.Boolean(default=False) + last_delivery: datetime.date = ormar.Date(default=datetime.datetime.now) country_name_choices = ("Canada", "Algeria", "United States") @@ -70,10 +70,10 @@ class Country(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=9, choices=country_name_choices, default="Canada",) - taxed = ormar.Boolean(choices=country_taxed_choices, default=True) - country_code = ormar.Integer( + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=9, choices=country_name_choices, default="Canada",) + taxed: bool = ormar.Boolean(choices=country_taxed_choices, default=True) + country_code: int = ormar.Integer( minimum=0, maximum=1000, choices=country_country_code_choices, default=1 ) @@ -213,7 +213,7 @@ async def test_model_filter(): assert product.pk is not None assert product.name == "T-Shirt" assert product.rating == 5 - assert product.last_delivery == datetime.now().date() + assert product.last_delivery == datetime.datetime.now().date() products = await Product.objects.all(rating__gte=2, in_stock=True) assert len(products) == 2 diff --git a/tests/test_more_reallife_fastapi.py b/tests/test_more_reallife_fastapi.py index 1ad7425..fc3b1f7 100644 --- a/tests/test_more_reallife_fastapi.py +++ b/tests/test_more_reallife_fastapi.py @@ -35,8 +35,8 @@ class Category(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Item(ormar.Model): @@ -45,9 +45,9 @@ class Item(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - category = ormar.ForeignKey(Category, nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + category: Optional[Category] = ormar.ForeignKey(Category, nullable=True) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_more_same_table_joins.py b/tests/test_more_same_table_joins.py index a0f712f..3fe22fe 100644 --- a/tests/test_more_same_table_joins.py +++ b/tests/test_more_same_table_joins.py @@ -18,8 +18,8 @@ class Department(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True, autoincrement=False) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True, autoincrement=False) + name: str = ormar.String(max_length=100) class SchoolClass(ormar.Model): @@ -28,8 +28,8 @@ class SchoolClass(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Category(ormar.Model): @@ -38,9 +38,9 @@ class Category(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - department = ormar.ForeignKey(Department, nullable=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + department: Optional[Department] = ormar.ForeignKey(Department, nullable=False) class Student(ormar.Model): @@ -49,10 +49,10 @@ class Student(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - schoolclass = ormar.ForeignKey(SchoolClass) - category = ormar.ForeignKey(Category, nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass) + category: Optional[Category] = ormar.ForeignKey(Category, nullable=True) class Teacher(ormar.Model): @@ -61,10 +61,10 @@ class Teacher(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - schoolclass = ormar.ForeignKey(SchoolClass) - category = ormar.ForeignKey(Category, nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass) + category: Optional[Category] = ormar.ForeignKey(Category, nullable=True) @pytest.fixture(scope="module") diff --git a/tests/test_new_annotation_style.py b/tests/test_new_annotation_style.py index 6f141dc..28665a5 100644 --- a/tests/test_new_annotation_style.py +++ b/tests/test_new_annotation_style.py @@ -18,8 +18,8 @@ class Album(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Track(ormar.Model): @@ -28,10 +28,10 @@ class Track(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - album = ormar.ForeignKey(Album) - title = ormar.String(max_length=100) - position = ormar.Integer() + id: int = ormar.Integer(primary_key=True) + album: Optional[Album] = ormar.ForeignKey(Album) + title: str = ormar.String(max_length=100) + position: int = ormar.Integer() class Cover(ormar.Model): @@ -40,9 +40,9 @@ class Cover(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - album = ormar.ForeignKey(Album, related_name="cover_pictures") - title = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + album: Optional[Album] = ormar.ForeignKey(Album, related_name="cover_pictures") + title: str = ormar.String(max_length=100) class Organisation(ormar.Model): @@ -51,8 +51,8 @@ class Organisation(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - ident = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) + id: int = ormar.Integer(primary_key=True) + ident: str = ormar.String(max_length=100, choices=["ACME Ltd", "Other ltd"]) class Team(ormar.Model): @@ -61,9 +61,9 @@ class Team(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - org = ormar.ForeignKey(Organisation) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + org: Optional[Organisation] = ormar.ForeignKey(Organisation) + name: str = ormar.String(max_length=100) class Member(ormar.Model): @@ -72,9 +72,9 @@ class Member(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - team = ormar.ForeignKey(Team) - email = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + team: Optional[Team] = ormar.ForeignKey(Team) + email: str = ormar.String(max_length=100) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_non_integer_pkey.py b/tests/test_non_integer_pkey.py index 0b3d8d1..78fb179 100644 --- a/tests/test_non_integer_pkey.py +++ b/tests/test_non_integer_pkey.py @@ -21,8 +21,8 @@ class Model(ormar.Model): metadata = metadata database = database - id = ormar.String(primary_key=True, default=key, max_length=8) - name = ormar.String(max_length=32) + id: str = ormar.String(primary_key=True, default=key, max_length=8) + name: str = ormar.String(max_length=32) @pytest.fixture(autouse=True, scope="function") diff --git a/tests/test_queryset_level_methods.py b/tests/test_queryset_level_methods.py index 86e6a1e..5ea5443 100644 --- a/tests/test_queryset_level_methods.py +++ b/tests/test_queryset_level_methods.py @@ -18,10 +18,10 @@ class Book(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - title = ormar.String(max_length=200) - author = ormar.String(max_length=100) - genre = ormar.String( + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + author: str = ormar.String(max_length=100) + genre: str = ormar.String( max_length=100, default="Fiction", choices=["Fiction", "Adventure", "Historic", "Fantasy"], @@ -34,9 +34,9 @@ class ToDo(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - text = ormar.String(max_length=500) - completed = ormar.Boolean(default=False) + id: int = ormar.Integer(primary_key=True) + text: str = ormar.String(max_length=500) + completed: bool = ormar.Boolean(default=False) class Category(ormar.Model): @@ -45,8 +45,8 @@ class Category(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=500) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=500) class Note(ormar.Model): @@ -55,9 +55,9 @@ class Note(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - text = ormar.String(max_length=500) - category = ormar.ForeignKey(Category) + id: int = ormar.Integer(primary_key=True) + text: str = ormar.String(max_length=500) + category: Optional[Category] = ormar.ForeignKey(Category) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_same_table_joins.py b/tests/test_same_table_joins.py index 9ca0654..2375613 100644 --- a/tests/test_same_table_joins.py +++ b/tests/test_same_table_joins.py @@ -18,8 +18,8 @@ class Department(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True, autoincrement=False) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True, autoincrement=False) + name: str = ormar.String(max_length=100) class SchoolClass(ormar.Model): @@ -28,9 +28,9 @@ class SchoolClass(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - department = ormar.ForeignKey(Department, nullable=False) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + department: Optional[Department] = ormar.ForeignKey(Department, nullable=False) class Category(ormar.Model): @@ -39,8 +39,8 @@ class Category(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) class Student(ormar.Model): @@ -49,10 +49,10 @@ class Student(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - schoolclass = ormar.ForeignKey(SchoolClass) - category = ormar.ForeignKey(Category, nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass) + category: Optional[Category] = ormar.ForeignKey(Category, nullable=True) class Teacher(ormar.Model): @@ -61,10 +61,10 @@ class Teacher(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - schoolclass = ormar.ForeignKey(SchoolClass) - category = ormar.ForeignKey(Category, nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + schoolclass: Optional[SchoolClass] = ormar.ForeignKey(SchoolClass) + category: Optional[Category] = ormar.ForeignKey(Category, nullable=True) @pytest.fixture(scope="module") diff --git a/tests/test_selecting_subset_of_columns.py b/tests/test_selecting_subset_of_columns.py index 7cf0173..389648b 100644 --- a/tests/test_selecting_subset_of_columns.py +++ b/tests/test_selecting_subset_of_columns.py @@ -18,9 +18,9 @@ class Company(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100, nullable=False) - founded = ormar.Integer(nullable=True) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100, nullable=False) + founded: int = ormar.Integer(nullable=True) class Car(ormar.Model): @@ -29,13 +29,13 @@ class Car(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - manufacturer = ormar.ForeignKey(Company) - name = ormar.String(max_length=100) - year = ormar.Integer(nullable=True) - gearbox_type = ormar.String(max_length=20, nullable=True) - gears = ormar.Integer(nullable=True) - aircon_type = ormar.String(max_length=20, nullable=True) + id: int = ormar.Integer(primary_key=True) + manufacturer: Optional[Company] = ormar.ForeignKey(Company) + name: str = ormar.String(max_length=100) + year: int = ormar.Integer(nullable=True) + gearbox_type: str = ormar.String(max_length=20, nullable=True) + gears: int = ormar.Integer(nullable=True) + aircon_type: str = ormar.String(max_length=20, nullable=True) @pytest.fixture(autouse=True, scope="module") diff --git a/tests/test_server_default.py b/tests/test_server_default.py index f88a062..d01fc40 100644 --- a/tests/test_server_default.py +++ b/tests/test_server_default.py @@ -20,11 +20,11 @@ class Product(ormar.Model): metadata = metadata database = database - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - company = ormar.String(max_length=200, server_default="Acme") - sort_order = ormar.Integer(server_default=text("10")) - created = ormar.DateTime(server_default=func.now()) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + company: str = ormar.String(max_length=200, server_default="Acme") + sort_order: int = ormar.Integer(server_default=text("10")) + created: datetime = ormar.DateTime(server_default=func.now()) @pytest.fixture(scope="module") diff --git a/tests/test_unique_constraints.py b/tests/test_unique_constraints.py index e39cbbc..3126d2a 100644 --- a/tests/test_unique_constraints.py +++ b/tests/test_unique_constraints.py @@ -21,9 +21,9 @@ class Product(ormar.Model): database = database constraints = [ormar.UniqueColumns("name", "company")] - id = ormar.Integer(primary_key=True) - name = ormar.String(max_length=100) - company = ormar.String(max_length=200) + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + company: str = ormar.String(max_length=200) @pytest.fixture(scope="module") From cce59acd993f6a439a8a78351649dc8298c13c01 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 11:41:18 +0100 Subject: [PATCH 09/19] update docs, cleaning --- .travis.yml | 2 +- docs/contributing.md | 4 +- docs/fields.md | 2 +- docs/models.md | 50 ++++++- docs/mypy.md | 28 ++++ docs/plugin.md | 18 +++ docs/queries.md | 212 ++++-------------------------- docs/relations.md | 51 +++++-- docs/releases.md | 18 ++- docs_src/fastapi/mypy/__init__.py | 0 docs_src/fastapi/mypy/docs001.py | 17 +++ docs_src/models/docs007.py | 5 +- docs_src/models/docs010.py | 6 +- docs_src/models/docs011.py | 19 +++ docs_src/models/docs012.py | 17 +++ docs_src/models/docs013.py | 38 ++++++ docs_src/queries/docs002.py | 28 ++++ docs_src/queries/docs003.py | 32 +++++ docs_src/queries/docs004.py | 30 +++++ docs_src/queries/docs005.py | 30 +++++ docs_src/queries/docs006.py | 67 ++++++++++ mkdocs.yml | 2 + tests/test_aliases.py | 4 +- tests/test_many_to_many.py | 4 +- tests/test_models.py | 4 +- 25 files changed, 468 insertions(+), 220 deletions(-) create mode 100644 docs/mypy.md create mode 100644 docs/plugin.md create mode 100644 docs_src/fastapi/mypy/__init__.py create mode 100644 docs_src/fastapi/mypy/docs001.py create mode 100644 docs_src/models/docs011.py create mode 100644 docs_src/models/docs012.py create mode 100644 docs_src/models/docs013.py create mode 100644 docs_src/queries/docs002.py create mode 100644 docs_src/queries/docs003.py create mode 100644 docs_src/queries/docs004.py create mode 100644 docs_src/queries/docs005.py create mode 100644 docs_src/queries/docs006.py diff --git a/.travis.yml b/.travis.yml index 6996e4f..f3c6d41 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,7 @@ script: - DATABASE_URL=postgresql://localhost/test_database scripts/test.sh - DATABASE_URL=mysql://localhost/test_database scripts/test.sh - DATABASE_URL=sqlite:///test.db scripts/test.sh - - mypy --config-file mypy.ini ormar + - mypy --config-file mypy.ini ormar tests after_script: - codecov \ No newline at end of file diff --git a/docs/contributing.md b/docs/contributing.md index 690a2cf..90c957a 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -44,9 +44,9 @@ git checkout -b my-new-feature-branch # 5. Formatting and linting # ormar uses black for formatting, flake8 for linting and mypy for type hints check # run all of the following as all those calls will be run on travis after every push -black ormar +black ormar tests flake8 ormar -mypy --config-file mypy.ini ormar +mypy --config-file mypy.ini ormar tests # 6. Run tests # on localhost all tests are run against sglite backend diff --git a/docs/fields.md b/docs/fields.md index 6dc0a17..67bdc74 100644 --- a/docs/fields.md +++ b/docs/fields.md @@ -78,7 +78,7 @@ Used in sql only. Sample usage: -```Python hl_lines="19-21" +```Python hl_lines="21-23" --8<-- "../docs_src/fields/docs004.py" ``` diff --git a/docs/models.md b/docs/models.md index 495b839..5fdc950 100644 --- a/docs/models.md +++ b/docs/models.md @@ -50,14 +50,44 @@ Here you have a sample model with changed names ``` Note that you can also change the ForeignKey column name -```Python hl_lines="9" +```Python hl_lines="21" --8<-- "../docs_src/models/docs009.py" ``` But for now you cannot change the ManyToMany column names as they go through other Model anyway. -```Python hl_lines="18" +```Python hl_lines="28" --8<-- "../docs_src/models/docs010.py" ``` + +### Type Hints & Legacy + +Before version 0.4.0 `ormar` supported only one way of defining `Fields` on a `Model` using python type hints as pydantic. + +```Python hl_lines="15-17" +--8<-- "../docs_src/models/docs011.py" +``` + +But that didn't play well with static type checkers like `mypy` and `pydantic` PyCharm plugin. + +Therefore from version >=0.4.0 `ormar` switched to new notation. + +```Python hl_lines="15-17" +--8<-- "../docs_src/models/docs001.py" +``` + +Note that type hints are **optional** so perfectly valid `ormar` code can look like this: + +```Python hl_lines="15-17" +--8<-- "../docs_src/models/docs001.py" +``` + +!!!warning + Even if you use type hints **`ormar` does not use them to construct `pydantic` fields!** + + Type hints are there only to support static checkers and linting, + `ormar` construct annotations used by `pydantic` from own fields. + + ### Database initialization/ migrations Note that all examples assume that you already have a database. @@ -133,6 +163,20 @@ Created instance needs to be passed to every `Model` with `Meta` class `metadata You need to create the `MetaData` instance **only once** and use it for all models. You can create several ones if you want to use multiple databases. +#### Best practice + +Only thing that `ormar` expects is a class with name `Meta` and two class variables: `metadata` and `databases`. + +So instead of providing the same parameters over and over again for all models you should creata a class and subclass it in all models. + +```Python hl_lines="14 20 33" +--8<-- "../docs_src/models/docs013.py" +``` + +!!!warning + You need to subclass your `MainMeta` class in each `Model` class as those classes store configuration variables + that otherwise would be overwritten by each `Model`. + ### Table Names By default table name is created from Model class name as lowercase name plus 's'. @@ -278,7 +322,7 @@ To access ormar `Fields` you can use `Model.Meta.model_fields` parameter For example to list table model fields you can: -```Python hl_lines="19" +```Python hl_lines="20" --8<-- "../docs_src/models/docs005.py" ``` diff --git a/docs/mypy.md b/docs/mypy.md new file mode 100644 index 0000000..4066e3d --- /dev/null +++ b/docs/mypy.md @@ -0,0 +1,28 @@ +To provide better errors check you should use mypy with pydantic [plugin][plugin] + +Note that legacy model declaration type will raise static type analyzers errors. + +So you **cannot use the old notation** like this: + +```Python hl_lines="15-17" +--8<-- "../docs_src/models/docs011.py" +``` + +Instead switch to notation introduced in version 0.4.0. + +```Python hl_lines="15-17" +--8<-- "../docs_src/models/docs012.py" +``` + +Note that above example is not using the type hints, so further operations with mypy might fail, depending on the context. + +Preferred notation should look liked this: + +```Python hl_lines="15-17" +--8<-- "../docs_src/models/docs001.py" +``` + + + + +[plugin]: https://pydantic-docs.helpmanual.io/mypy_plugin/ \ No newline at end of file diff --git a/docs/plugin.md b/docs/plugin.md new file mode 100644 index 0000000..fff0590 --- /dev/null +++ b/docs/plugin.md @@ -0,0 +1,18 @@ +While `ormar` will work with any IDE there is a PyCharm `pydantic` plugin that enhances the user experience for this IDE. + +Plugin is available on the JetBrains Plugins Repository for PyCharm: [plugin page][plugin page]. + +You can install the plugin for free from the plugin marketplace +(PyCharm's Preferences -> Plugin -> Marketplace -> search "pydantic"). + +!!!note + For plugin to work properly you need to provide valid type hints for model fields. + +!!!info + Plugin supports type hints, argument inspection and more but mainly only for __init__ methods + +More information can be found on the +[official plugin page](https://plugins.jetbrains.com/plugin/12861-pydantic) +and [github repository](https://github.com/koxudaxi/pydantic-pycharm-plugin). + +[plugin page]: https://plugins.jetbrains.com/plugin/12861-pydantic \ No newline at end of file diff --git a/docs/queries.md b/docs/queries.md index 18c6479..77c6229 100644 --- a/docs/queries.md +++ b/docs/queries.md @@ -2,10 +2,13 @@ ## QuerySet -Each Model is auto registered with a QuerySet that represents the underlaying query and it's options. +Each Model is auto registered with a `QuerySet` that represents the underlaying query and it's options. Most of the methods are also available through many to many relation interface. +!!!info + To see which one are supported and how to construct relations visit [relations][relations]. + Given the Models like this ```Python @@ -95,74 +98,24 @@ If you do not provide this flag or a filter a `QueryDefinitionError` will be rai Return number of rows updated. -```python hl_lines="24-28" -import databases -import ormar -import sqlalchemy - -database = databases.Database("sqlite:///db.sqlite") -metadata = sqlalchemy.MetaData() - -class Book(ormar.Model): - class Meta: - tablename = "books" - metadata = metadata - database = database - - id: int = ormar.Integer(primary_key=True) - title: str = ormar.String(max_length=200) - author: str = ormar.String(max_length=100) - genre: str = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) - -await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') -await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') -await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') - - -# queryset needs to be filtered before deleting to prevent accidental overwrite -# to update whole database table each=True needs to be provided as a safety switch -await Book.objects.update(each=True, genre='Fiction') -all_books = await Book.objects.filter(genre='Fiction').all() -assert len(all_books) == 3 +```Python hl_lines="26-28" +--8<-- "../docs_src/queries/docs002.py" ``` +!!!warning + Queryset needs to be filtered before updating to prevent accidental overwrite. + + To update whole database table `each=True` needs to be provided as a safety switch + + ### update_or_create `update_or_create(**kwargs) -> Model` Updates the model, or in case there is no match in database creates a new one. -```python hl_lines="24-30" -import databases -import ormar -import sqlalchemy - -database = databases.Database("sqlite:///db.sqlite") -metadata = sqlalchemy.MetaData() - -class Book(ormar.Model): - class Meta: - tablename = "books" - metadata = metadata - database = database - - id: int = ormar.Integer(primary_key=True) - title: str = ormar.String(max_length=200) - author: str = ormar.String(max_length=100) - genre: str = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) - -await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') -await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') -await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') - - -# if not exist the instance will be persisted in db -vol2 = await Book.objects.update_or_create(title="Volume II", author='Anonymous', genre='Fiction') -assert await Book.objects.count() == 1 - -# if pk or pkname passed in kwargs (like id here) the object will be updated -assert await Book.objects.update_or_create(id=vol2.id, genre='Historic') -assert await Book.objects.count() == 1 +```Python hl_lines="26-32" +--8<-- "../docs_src/queries/docs003.py" ``` !!!note @@ -177,36 +130,8 @@ Allows you to create multiple objects at once. A valid list of `Model` objects needs to be passed. -```python hl_lines="20-26" -import databases -import ormar -import sqlalchemy - -database = databases.Database("sqlite:///db.sqlite") -metadata = sqlalchemy.MetaData() - - -class ToDo(ormar.Model): - class Meta: - tablename = "todos" - metadata = metadata - database = database - - id: int = ormar.Integer(primary_key=True) - text: str = ormar.String(max_length=500) - completed= ormar.Boolean(default=False) - -# create multiple instances at once with bulk_create -await ToDo.objects.bulk_create( - [ - ToDo(text="Buy the groceries."), - ToDo(text="Call Mum.", completed=True), - ToDo(text="Send invoices.", completed=True), - ] - ) - -todoes = await ToDo.objects.all() -assert len(todoes) == 3 +```python hl_lines="21-27" +--8<-- "../docs_src/queries/docs004.py" ``` ### bulk_update @@ -245,34 +170,8 @@ If you do not provide this flag or a filter a `QueryDefinitionError` will be rai Return number of rows deleted. -```python hl_lines="23-27" -import databases -import ormar -import sqlalchemy - -database = databases.Database("sqlite:///db.sqlite") -metadata = sqlalchemy.MetaData() - -class Book(ormar.Model): - class Meta: - tablename = "books" - metadata = metadata - database = database - - id: int = ormar.Integer(primary_key=True) - title: str = ormar.String(max_length=200) - author: str = ormar.String(max_length=100) - genre: str = ormar.String(max_length=100, default='Fiction', choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) - -await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') -await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy') -await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') - -# delete accepts kwargs that will be used in filter -# acting in same way as queryset.filter(**kwargs).delete() -await Book.objects.delete(genre='Fantasy') # delete all fantasy books -all_books = await Book.objects.all() -assert len(all_books) == 2 +```python hl_lines="26-30" +--8<-- "../docs_src/queries/docs005.py" ``` ### all @@ -453,76 +352,8 @@ has_sample = await Book.objects.filter(title='Sample').exists() With `fields()` you can select subset of model columns to limit the data load. -```python hl_lines="48 60 61 67" -import databases -import sqlalchemy - -import ormar -from tests.settings import DATABASE_URL - -database = databases.Database(DATABASE_URL, force_rollback=True) -metadata = sqlalchemy.MetaData() - - -class Company(ormar.Model): - class Meta: - tablename = "companies" - metadata = metadata - database = database - - id: int = ormar.Integer(primary_key=True) - name: str = ormar.String(max_length=100) - founded: int = ormar.Integer(nullable=True) - - -class Car(ormar.Model): - class Meta: - tablename = "cars" - metadata = metadata - database = database - - id: int = ormar.Integer(primary_key=True) - manufacturer= ormar.ForeignKey(Company) - name: str = ormar.String(max_length=100) - year: int = ormar.Integer(nullable=True) - gearbox_type: str = ormar.String(max_length=20, nullable=True) - gears: int = ormar.Integer(nullable=True) - aircon_type: str = ormar.String(max_length=20, nullable=True) - - - -# build some sample data -toyota = await Company.objects.create(name="Toyota", founded=1937) -await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, - aircon_type='Manual') -await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, - aircon_type='Manual') -await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, - aircon_type='Auto') - -# select manufacturer but only name - to include related models use notation {model_name}__{column} -all_cars = await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__name']).all() -for car in all_cars: - # excluded columns will yield None - assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) - # included column on related models will be available, pk column is always included - # even if you do not include it in fields list - assert car.manufacturer.name == 'Toyota' - # also in the nested related models - you cannot exclude pk - it's always auto added - assert car.manufacturer.founded is None - -# fields() can be called several times, building up the columns to select -# models selected in select_related but with no columns in fields list implies all fields -all_cars = await Car.objects.select_related('manufacturer').fields('id').fields( - ['name']).all() -# all fiels from company model are selected -assert all_cars[0].manufacturer.name == 'Toyota' -assert all_cars[0].manufacturer.founded == 1937 - -# cannot exclude mandatory model columns - company__name in this example -await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__founded']).all() -# will raise pydantic ValidationError as company.name is required - +```python hl_lines="47 59 60 66" +--8<-- "../docs_src/queries/docs006.py" ``` !!!warning @@ -539,4 +370,5 @@ await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company_ Something like `Track.object.select_related("album").filter(album__name="Malibu").offset(1).limit(1).all()` -[models]: ./models.md \ No newline at end of file +[models]: ./models.md +[relations]: ./relations.md \ No newline at end of file diff --git a/docs/relations.md b/docs/relations.md index fd6a3e1..5bda020 100644 --- a/docs/relations.md +++ b/docs/relations.md @@ -15,7 +15,7 @@ Sqlalchemy column and Type are automatically taken from target `Model`. To define a relation add `ForeignKey` field that points to related `Model`. -```Python hl_lines="27" +```Python hl_lines="29" --8<-- "../docs_src/fields/docs003.py" ``` @@ -25,7 +25,7 @@ To define a relation add `ForeignKey` field that points to related `Model`. By default it's child (source) `Model` name + s, like courses in snippet below: -```Python hl_lines="27 33" +```Python hl_lines="29 35" --8<-- "../docs_src/fields/docs001.py" ``` @@ -33,7 +33,7 @@ By default it's child (source) `Model` name + s, like courses in snippet below: But you can overwrite this name by providing `related_name` parameter like below: -```Python hl_lines="27 33" +```Python hl_lines="29 35" --8<-- "../docs_src/fields/docs002.py" ``` @@ -49,7 +49,7 @@ You have several ways to set-up a relationship connection. The most obvious one is to pass a related `Model` instance to the constructor. -```Python hl_lines="32-33" +```Python hl_lines="34-35" --8<-- "../docs_src/relations/docs001.py" ``` @@ -57,7 +57,7 @@ The most obvious one is to pass a related `Model` instance to the constructor. You can setup the relation also with just the pk column value of the related model. -```Python hl_lines="35-36" +```Python hl_lines="37-38" --8<-- "../docs_src/relations/docs001.py" ``` @@ -67,7 +67,7 @@ Next option is with a dictionary of key-values of the related model. You can build the dictionary yourself or get it from existing model with `dict()` method. -```Python hl_lines="38-39" +```Python hl_lines="40-41" --8<-- "../docs_src/relations/docs001.py" ``` @@ -75,7 +75,7 @@ You can build the dictionary yourself or get it from existing model with `dict() Finally you can explicitly set it to None (default behavior if no value passed). -```Python hl_lines="41-42" +```Python hl_lines="43-44" --8<-- "../docs_src/relations/docs001.py" ``` @@ -121,7 +121,11 @@ await news.posts.add(post) Otherwise an IntegrityError will be raised by your database driver library. -#### Creating new related `Model` instances +#### create() + +Create related `Model` directly from parent `Model`. + +The link table is automatically populated, as well as relation ids in the database. ```python # Creating columns object from instance: @@ -136,15 +140,27 @@ assert len(await post.categories.all()) == 2 To learn more about available QuerySet methods visit [queries][queries] -#### Removing related models +#### remove() + +Removal of the related model one by one. + +Removes also the relation in the database. + ```python -# Removal of the relationship by one await news.posts.remove(post) -# or all at once +``` + +#### clear() + +Removal all related models in one call. + +Removes also the relation in the database. + +```python await news.posts.clear() ``` -#### All other queryset methods +#### Other queryset methods When access directly the related `ManyToMany` field returns the list of related models. @@ -164,7 +180,18 @@ news_posts = await news.posts.select_related("author").all() assert news_posts[0].author == guido ``` +Currently supported methods are: + !!!tip To learn more about available QuerySet methods visit [queries][queries] +##### get() +##### all() +##### filter() +##### select_related() +##### limit() +##### offset() +##### count() +##### exists() + [queries]: ./queries.md \ No newline at end of file diff --git a/docs/releases.md b/docs/releases.md index ebc41e0..c24ec54 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1,6 +1,22 @@ +# 0.4.0 + +* Changed notation in Model definition -> now use name = ormar.Field() not name: ormar.Field() + * Note that old notation is still supported but deprecated and will not play nice with static checkers like mypy and pydantic pycharm plugin +* Type hint docs and test +* Use mypy for tests also not, only ormar package +* Fix scale and precision translation with max_digits and decimal_places pydantic Decimal field +* Update docs - add best practices for dependencies +* Refactor metaclass and model_fields to play nice with type hints +* Add mypy and pydantic plugin to docs +* Expand the docs on ManyToMany relation + +# 0.3.11 + +* Fix setting server_default as default field value in python + # 0.3.10 -* Fix +* Fix postgresql check to avoid exceptions with drivers not installed if using different backend # 0.3.9 diff --git a/docs_src/fastapi/mypy/__init__.py b/docs_src/fastapi/mypy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs_src/fastapi/mypy/docs001.py b/docs_src/fastapi/mypy/docs001.py new file mode 100644 index 0000000..2c74a77 --- /dev/null +++ b/docs_src/fastapi/mypy/docs001.py @@ -0,0 +1,17 @@ +import databases +import sqlalchemy + +import ormar + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + + +class Course(ormar.Model): + class Meta: + database = database + metadata = metadata + + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed = ormar.Boolean(default=False) diff --git a/docs_src/models/docs007.py b/docs_src/models/docs007.py index 2f4b7b5..9dfe28a 100644 --- a/docs_src/models/docs007.py +++ b/docs_src/models/docs007.py @@ -18,5 +18,6 @@ class Course(ormar.Model): course = Course(name="Painting for dummies", completed=False) -await course.save() # type: ignore -await Course.objects.create(name="Painting for dummies", completed=False) # type: ignore +await course.save() + +await Course.objects.create(name="Painting for dummies", completed=False) diff --git a/docs_src/models/docs010.py b/docs_src/models/docs010.py index af2c9dc..d63101e 100644 --- a/docs_src/models/docs010.py +++ b/docs_src/models/docs010.py @@ -1,5 +1,3 @@ -from typing import Optional, Union, List - import databases import sqlalchemy @@ -27,6 +25,4 @@ class Artist(ormar.Model): first_name: str = ormar.String(name="fname", max_length=100) last_name: str = ormar.String(name="lname", max_length=100) born_year: int = ormar.Integer(name="year") - children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany( - Child, through=ArtistChildren - ) + children = ormar.ManyToMany(Child, through=ArtistChildren) diff --git a/docs_src/models/docs011.py b/docs_src/models/docs011.py new file mode 100644 index 0000000..962de1d --- /dev/null +++ b/docs_src/models/docs011.py @@ -0,0 +1,19 @@ +import databases +import sqlalchemy + +import ormar + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + + +class Course(ormar.Model): + class Meta: + database = database + metadata = metadata + + id: ormar.Integer(primary_key=True) + name: ormar.String(max_length=100) + completed: ormar.Boolean(default=False) + +c1 = Course() \ No newline at end of file diff --git a/docs_src/models/docs012.py b/docs_src/models/docs012.py new file mode 100644 index 0000000..68e75b9 --- /dev/null +++ b/docs_src/models/docs012.py @@ -0,0 +1,17 @@ +import databases +import sqlalchemy + +import ormar + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + + +class Course(ormar.Model): + class Meta: + database = database + metadata = metadata + + id = ormar.Integer(primary_key=True) + name = ormar.String(max_length=100) + completed = ormar.Boolean(default=False) \ No newline at end of file diff --git a/docs_src/models/docs013.py b/docs_src/models/docs013.py new file mode 100644 index 0000000..8d584f3 --- /dev/null +++ b/docs_src/models/docs013.py @@ -0,0 +1,38 @@ +from typing import Optional + +import databases +import sqlalchemy + +import ormar + +database = databases.Database("sqlite:///test.db", force_rollback=True) +metadata = sqlalchemy.MetaData() + + +# note that you do not have to subclass ModelMeta, +# it's useful for type hints and code completion +class MainMeta(ormar.ModelMeta): + metadata = metadata + database = database + + +class Artist(ormar.Model): + class Meta(MainMeta): + # note that tablename is optional + # if not provided ormar will user class.__name__.lower()+'s' + # -> artists in this example + pass + + id: int = ormar.Integer(primary_key=True) + first_name: str = ormar.String(max_length=100) + last_name: str = ormar.String(max_length=100) + born_year: int = ormar.Integer(name="year") + + +class Album(ormar.Model): + class Meta(MainMeta): + pass + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + artist: Optional[Artist] = ormar.ForeignKey(Artist) diff --git a/docs_src/queries/docs002.py b/docs_src/queries/docs002.py new file mode 100644 index 0000000..d9cdeff --- /dev/null +++ b/docs_src/queries/docs002.py @@ -0,0 +1,28 @@ +import databases +import ormar +import sqlalchemy + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + + +class Book(ormar.Model): + class Meta: + tablename = "books" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + author: str = ormar.String(max_length=100) + genre: str = ormar.String(max_length=100, default='Fiction', + choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + + +await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') +await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') + +await Book.objects.update(each=True, genre='Fiction') +all_books = await Book.objects.filter(genre='Fiction').all() +assert len(all_books) == 3 diff --git a/docs_src/queries/docs003.py b/docs_src/queries/docs003.py new file mode 100644 index 0000000..58e4f1b --- /dev/null +++ b/docs_src/queries/docs003.py @@ -0,0 +1,32 @@ +import databases +import ormar +import sqlalchemy + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + + +class Book(ormar.Model): + class Meta: + tablename = "books" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + author: str = ormar.String(max_length=100) + genre: str = ormar.String(max_length=100, default='Fiction', + choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + + +await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') +await Book.objects.create(title='War and Peace', author="Tolstoy, Leo", genre='Fiction') +await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') + +# if not exist the instance will be persisted in db +vol2 = await Book.objects.update_or_create(title="Volume II", author='Anonymous', genre='Fiction') +assert await Book.objects.count() == 1 + +# if pk or pkname passed in kwargs (like id here) the object will be updated +assert await Book.objects.update_or_create(id=vol2.id, genre='Historic') +assert await Book.objects.count() == 1 diff --git a/docs_src/queries/docs004.py b/docs_src/queries/docs004.py new file mode 100644 index 0000000..6aaf01f --- /dev/null +++ b/docs_src/queries/docs004.py @@ -0,0 +1,30 @@ +import databases +import ormar +import sqlalchemy + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + + +class ToDo(ormar.Model): + class Meta: + tablename = "todos" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + text: str = ormar.String(max_length=500) + completed = ormar.Boolean(default=False) + + +# create multiple instances at once with bulk_create +await ToDo.objects.bulk_create( + [ + ToDo(text="Buy the groceries."), + ToDo(text="Call Mum.", completed=True), + ToDo(text="Send invoices.", completed=True), + ] +) + +todoes = await ToDo.objects.all() +assert len(todoes) == 3 diff --git a/docs_src/queries/docs005.py b/docs_src/queries/docs005.py new file mode 100644 index 0000000..dca0757 --- /dev/null +++ b/docs_src/queries/docs005.py @@ -0,0 +1,30 @@ +import databases +import ormar +import sqlalchemy + +database = databases.Database("sqlite:///db.sqlite") +metadata = sqlalchemy.MetaData() + + +class Book(ormar.Model): + class Meta: + tablename = "books" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + title: str = ormar.String(max_length=200) + author: str = ormar.String(max_length=100) + genre: str = ormar.String(max_length=100, default='Fiction', + choices=['Fiction', 'Adventure', 'Historic', 'Fantasy']) + + +await Book.objects.create(title='Tom Sawyer', author="Twain, Mark", genre='Adventure') +await Book.objects.create(title='War and Peace in Space', author="Tolstoy, Leo", genre='Fantasy') +await Book.objects.create(title='Anna Karenina', author="Tolstoy, Leo", genre='Fiction') + +# delete accepts kwargs that will be used in filter +# acting in same way as queryset.filter(**kwargs).delete() +await Book.objects.delete(genre='Fantasy') # delete all fantasy books +all_books = await Book.objects.all() +assert len(all_books) == 2 diff --git a/docs_src/queries/docs006.py b/docs_src/queries/docs006.py new file mode 100644 index 0000000..936c79f --- /dev/null +++ b/docs_src/queries/docs006.py @@ -0,0 +1,67 @@ +import databases +import sqlalchemy + +import ormar +from tests.settings import DATABASE_URL + +database = databases.Database(DATABASE_URL, force_rollback=True) +metadata = sqlalchemy.MetaData() + + +class Company(ormar.Model): + class Meta: + tablename = "companies" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + founded: int = ormar.Integer(nullable=True) + + +class Car(ormar.Model): + class Meta: + tablename = "cars" + metadata = metadata + database = database + + id: int = ormar.Integer(primary_key=True) + manufacturer = ormar.ForeignKey(Company) + name: str = ormar.String(max_length=100) + year: int = ormar.Integer(nullable=True) + gearbox_type: str = ormar.String(max_length=20, nullable=True) + gears: int = ormar.Integer(nullable=True) + aircon_type: str = ormar.String(max_length=20, nullable=True) + + +# build some sample data +toyota = await Company.objects.create(name="Toyota", founded=1937) +await Car.objects.create(manufacturer=toyota, name="Corolla", year=2020, gearbox_type='Manual', gears=5, + aircon_type='Manual') +await Car.objects.create(manufacturer=toyota, name="Yaris", year=2019, gearbox_type='Manual', gears=5, + aircon_type='Manual') +await Car.objects.create(manufacturer=toyota, name="Supreme", year=2020, gearbox_type='Auto', gears=6, + aircon_type='Auto') + +# select manufacturer but only name - to include related models use notation {model_name}__{column} +all_cars = await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__name']).all() +for car in all_cars: + # excluded columns will yield None + assert all(getattr(car, x) is None for x in ['year', 'gearbox_type', 'gears', 'aircon_type']) + # included column on related models will be available, pk column is always included + # even if you do not include it in fields list + assert car.manufacturer.name == 'Toyota' + # also in the nested related models - you cannot exclude pk - it's always auto added + assert car.manufacturer.founded is None + +# fields() can be called several times, building up the columns to select +# models selected in select_related but with no columns in fields list implies all fields +all_cars = await Car.objects.select_related('manufacturer').fields('id').fields( + ['name']).all() +# all fiels from company model are selected +assert all_cars[0].manufacturer.name == 'Toyota' +assert all_cars[0].manufacturer.founded == 1937 + +# cannot exclude mandatory model columns - company__name in this example +await Car.objects.select_related('manufacturer').fields(['id', 'name', 'company__founded']).all() +# will raise pydantic ValidationError as company.name is required diff --git a/mkdocs.yml b/mkdocs.yml index a7b99e3..9c3dec6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -8,6 +8,8 @@ nav: - Relations: relations.md - Queries: queries.md - Use with Fastapi: fastapi.md + - Use with mypy: mypy.md + - PyCharm plugin: plugin.md - Contributing: contributing.md - Release Notes: releases.md repo_name: collerek/ormar diff --git a/tests/test_aliases.py b/tests/test_aliases.py index 581b59c..81c4a8c 100644 --- a/tests/test_aliases.py +++ b/tests/test_aliases.py @@ -40,7 +40,9 @@ class Artist(ormar.Model): first_name: str = ormar.String(name="fname", max_length=100) last_name: str = ormar.String(name="lname", max_length=100) born_year: int = ormar.Integer(name="year") - children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany(Child, through=ArtistChildren) + children: Optional[Union[Child, List[Child]]] = ormar.ManyToMany( + Child, through=ArtistChildren + ) class Album(ormar.Model): diff --git a/tests/test_many_to_many.py b/tests/test_many_to_many.py index a414cae..3ddeb61 100644 --- a/tests/test_many_to_many.py +++ b/tests/test_many_to_many.py @@ -49,7 +49,9 @@ class Post(ormar.Model): id: int = ormar.Integer(primary_key=True) title: str = ormar.String(max_length=200) - categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany(Category, through=PostCategory) + categories: Optional[Union[Category, List[Category]]] = ormar.ManyToMany( + Category, through=PostCategory + ) author: Optional[Author] = ormar.ForeignKey(Author) diff --git a/tests/test_models.py b/tests/test_models.py index 2bac6d7..490dedd 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -71,7 +71,9 @@ class Country(ormar.Model): database = database id: int = ormar.Integer(primary_key=True) - name: str = ormar.String(max_length=9, choices=country_name_choices, default="Canada",) + name: str = ormar.String( + max_length=9, choices=country_name_choices, default="Canada", + ) taxed: bool = ormar.Boolean(choices=country_taxed_choices, default=True) country_code: int = ormar.Integer( minimum=0, maximum=1000, choices=country_country_code_choices, default=1 From 9bcf6a346adf55be4c7c139c38ed1ca0038c1015 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:02:25 +0700 Subject: [PATCH 10/19] Update queryset_protocol.py --- ormar/protocols/queryset_protocol.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ormar/protocols/queryset_protocol.py b/ormar/protocols/queryset_protocol.py index e1c1284..be771b8 100644 --- a/ormar/protocols/queryset_protocol.py +++ b/ormar/protocols/queryset_protocol.py @@ -1,4 +1,9 @@ -from typing import Any, List, Optional, Protocol, Sequence, TYPE_CHECKING, Union +from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Union + +try: + from typing import Protocol +except ImportError: + from typing_extentions import Protocol if TYPE_CHECKING: # noqa: C901; #pragma nocover from ormar import QuerySet, Model From 551aabf0a8ff61a2c28c889719c8e92c9c2c6276 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:03:18 +0700 Subject: [PATCH 11/19] Update relation_protocol.py --- ormar/protocols/relation_protocol.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ormar/protocols/relation_protocol.py b/ormar/protocols/relation_protocol.py index ec0fe98..bc06093 100644 --- a/ormar/protocols/relation_protocol.py +++ b/ormar/protocols/relation_protocol.py @@ -1,4 +1,9 @@ -from typing import Protocol, TYPE_CHECKING, Type, Union +from typing import TYPE_CHECKING, Type, Union + +try: + from typing import Protocol +except ImportError: + from typing_extentions import Protocol if TYPE_CHECKING: # pragma: nocover from ormar import Model From e22f3ef27df1c6d34a0c97ad48f5818f372466df Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:04:46 +0700 Subject: [PATCH 12/19] Update requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e6b1752..e665778 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ databases[postgresql] databases[mysql] pydantic sqlalchemy +typing_extensions # Async database drivers aiomysql @@ -32,4 +33,4 @@ flake8-builtins flake8-variables-names flake8-cognitive-complexity flake8-functions -flake8-expression-complexity \ No newline at end of file +flake8-expression-complexity From 531defdf41efe27e294852389aee514a6a8e2185 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:05:41 +0700 Subject: [PATCH 13/19] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 97a3145..02c016c 100644 --- a/setup.py +++ b/setup.py @@ -51,7 +51,7 @@ setup( packages=get_packages(PACKAGE), package_data={PACKAGE: ["py.typed"]}, data_files=[("", ["LICENSE.md"])], - install_requires=["databases", "pydantic>=1.5", "sqlalchemy"], + install_requires=["databases", "pydantic>=1.5", "sqlalchemy", "typing_extensions"], extras_require={ "postgresql": ["asyncpg", "psycopg2"], "mysql": ["aiomysql", "pymysql"], From be150950976ae495870eba9c97f14a4864fa5022 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:06:50 +0700 Subject: [PATCH 14/19] Update queryset_protocol.py --- ormar/protocols/queryset_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ormar/protocols/queryset_protocol.py b/ormar/protocols/queryset_protocol.py index be771b8..72f0343 100644 --- a/ormar/protocols/queryset_protocol.py +++ b/ormar/protocols/queryset_protocol.py @@ -3,7 +3,7 @@ from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Union try: from typing import Protocol except ImportError: - from typing_extentions import Protocol + from typing_extensions import Protocol if TYPE_CHECKING: # noqa: C901; #pragma nocover from ormar import QuerySet, Model From 496445f121338a82c5609e2fff53a1ba749a2bf9 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:07:14 +0700 Subject: [PATCH 15/19] Update relation_protocol.py --- ormar/protocols/relation_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ormar/protocols/relation_protocol.py b/ormar/protocols/relation_protocol.py index bc06093..4b342e7 100644 --- a/ormar/protocols/relation_protocol.py +++ b/ormar/protocols/relation_protocol.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Type, Union try: from typing import Protocol except ImportError: - from typing_extentions import Protocol + from typing_extensions import Protocol if TYPE_CHECKING: # pragma: nocover from ormar import Model From 110541d74ff4236e324318c9b84c53b6330da6cc Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:14:07 +0700 Subject: [PATCH 16/19] Update queryset_protocol.py --- ormar/protocols/queryset_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ormar/protocols/queryset_protocol.py b/ormar/protocols/queryset_protocol.py index 72f0343..3a3d7e4 100644 --- a/ormar/protocols/queryset_protocol.py +++ b/ormar/protocols/queryset_protocol.py @@ -3,7 +3,7 @@ from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Union try: from typing import Protocol except ImportError: - from typing_extensions import Protocol + from typing_extensions import Protocol # type: ignore if TYPE_CHECKING: # noqa: C901; #pragma nocover from ormar import QuerySet, Model From 5eb2c9d27216b5a249bae6baee1f659f29a7ad5f Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:14:39 +0700 Subject: [PATCH 17/19] Update relation_protocol.py --- ormar/protocols/relation_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ormar/protocols/relation_protocol.py b/ormar/protocols/relation_protocol.py index 4b342e7..ac6ed6c 100644 --- a/ormar/protocols/relation_protocol.py +++ b/ormar/protocols/relation_protocol.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Type, Union try: from typing import Protocol except ImportError: - from typing_extensions import Protocol + from typing_extensions import Protocol # type: ignore if TYPE_CHECKING: # pragma: nocover from ormar import Model From 2577d572b5aacb69e3a74d13c6b01a4b35f545dc Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:26:01 +0700 Subject: [PATCH 18/19] Update relation_protocol.py --- ormar/protocols/relation_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ormar/protocols/relation_protocol.py b/ormar/protocols/relation_protocol.py index ac6ed6c..b551b8d 100644 --- a/ormar/protocols/relation_protocol.py +++ b/ormar/protocols/relation_protocol.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Type, Union try: from typing import Protocol -except ImportError: +except ImportError: # pragma: nocover from typing_extensions import Protocol # type: ignore if TYPE_CHECKING: # pragma: nocover From c005da58c1b830883454ad2382891bdc29fff7c6 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 1 Nov 2020 18:26:35 +0700 Subject: [PATCH 19/19] Update queryset_protocol.py --- ormar/protocols/queryset_protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ormar/protocols/queryset_protocol.py b/ormar/protocols/queryset_protocol.py index 3a3d7e4..1320c2a 100644 --- a/ormar/protocols/queryset_protocol.py +++ b/ormar/protocols/queryset_protocol.py @@ -2,7 +2,7 @@ from typing import Any, List, Optional, Sequence, TYPE_CHECKING, Union try: from typing import Protocol -except ImportError: +except ImportError: # pragma: nocover from typing_extensions import Protocol # type: ignore if TYPE_CHECKING: # noqa: C901; #pragma nocover