From 3b164c76ded88a02d322d404768bde412bebbb52 Mon Sep 17 00:00:00 2001 From: collerek Date: Thu, 3 Dec 2020 16:39:14 +0100 Subject: [PATCH] revert adding props to fields --- ormar/models/metaclass.py | 85 ++++++++++++----------- ormar/models/modelproxy.py | 7 +- ormar/models/newbasemodel.py | 63 +++++++++-------- tests/test_excluding_fields_in_fastapi.py | 65 ++++++++++------- tests/test_pydantic_only_fields.py | 7 +- 5 files changed, 121 insertions(+), 106 deletions(-) diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index 4706029..55b0bbb 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -1,3 +1,4 @@ +import inspect import logging import warnings from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, Union @@ -47,7 +48,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( @@ -56,11 +57,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__ ) @@ -71,7 +72,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 @@ -79,10 +80,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( @@ -97,7 +98,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, real_name=model.get_name(), ondelete="CASCADE" @@ -114,7 +115,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, @@ -136,7 +137,7 @@ def get_pydantic_field(field_name: str, model: Type["Model"]) -> "ModelField": 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(), @@ -152,7 +153,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.") @@ -162,7 +163,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 @@ -176,9 +177,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.get_alias())) register_relation_in_alias_manager(table_name, field) @@ -186,7 +187,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) @@ -195,7 +196,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): @@ -242,7 +243,7 @@ def extract_annotations_and_default_vals(attrs: dict) -> Tuple[Dict, Dict]: 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 = ( @@ -268,7 +269,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( @@ -320,7 +321,7 @@ def populate_choices_validators(model: Type["Model"]) -> None: # noqa CCR001 def populate_default_options_values( - new_model: Type["Model"], model_fields: Dict + new_model: Type["Model"], model_fields: Dict ) -> None: if not hasattr(new_model.Meta, "constraints"): new_model.Meta.constraints = [] @@ -335,10 +336,9 @@ def populate_default_options_values( def add_cached_properties(new_model: Type["Model"]) -> None: new_model._props = { - prop - for prop in vars(new_model) - if isinstance(getattr(new_model, prop), property) - and prop not in ("__values__", "__fields__", "fields", "pk_column", "saved") + prop[0] + for prop in inspect.getmembers(new_model, lambda o: isinstance(o, property)) + if prop[0] not in ("__values__", "__fields__", "fields", "pk_column", "saved") } new_model._quick_access_fields = quick_access_set new_model._related_names = None @@ -346,24 +346,27 @@ def add_cached_properties(new_model: Type["Model"]) -> None: def add_property_fields(new_model: Type["Model"]) -> None: - if new_model.Meta.include_props_in_fields: - for prop in new_model._props: - field_type = getattr(new_model, prop).fget.__annotations__.get("return") - new_model.Meta.model_fields[prop] = ModelFieldFactory( # type: ignore - nullable=True, pydantic_only=True - ) - new_model.__fields__[prop] = ModelField( - name=prop, - type_=Optional[field_type] if field_type is not None else Any, # type: ignore - model_config=new_model.__config__, - required=False, - class_validators={}, - ) + pass + + +# if new_model.Meta.include_props_in_fields: +# for prop in new_model._props: +# field_type = getattr(new_model, prop).fget.__annotations__.get("return") +# new_model.Meta.model_fields[prop] = ModelFieldFactory( # type: ignore +# nullable=True, pydantic_only=True +# ) +# new_model.__fields__[prop] = ModelField( +# name=prop, +# type_=Optional[field_type] if field_type is not None else Any, # type: ignore +# model_config=new_model.__config__, +# required=False, +# class_validators={}, +# ) 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 @@ -371,6 +374,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): new_model = super().__new__( # type: ignore mcs, name, bases, attrs ) + add_cached_properties(new_model) if hasattr(new_model, "Meta"): populate_default_options_values(new_model, model_fields) @@ -387,7 +391,6 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): ) new_model.Meta.alias_manager = alias_manager new_model.objects = QuerySet(new_model) - add_cached_properties(new_model) add_property_fields(new_model) return new_model diff --git a/ormar/models/modelproxy.py b/ormar/models/modelproxy.py index 0523752..eee9ae9 100644 --- a/ormar/models/modelproxy.py +++ b/ormar/models/modelproxy.py @@ -20,7 +20,6 @@ from typing import ( from ormar.exceptions import ModelPersistenceError, RelationshipInstanceError from ormar.queryset.utils import translate_list_to_dict, update - import ormar # noqa: I100 from ormar.fields import BaseField from ormar.fields.foreign_key import ForeignKeyField @@ -46,13 +45,11 @@ class ModelTableProxy: pk: Any get_name: Callable _props: Set - - def dict(self): # noqa A003 - raise NotImplementedError # pragma no cover + dict: Callable def _extract_own_model_fields(self) -> Dict: related_names = self.extract_related_names() - self_fields = {k: v for k, v in self.dict().items() if k not in related_names} + self_fields = self.dict(exclude=related_names) return self_fields @classmethod diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index 82adee9..7a7ee59 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -1,3 +1,4 @@ +import inspect import json import uuid from typing import ( @@ -74,7 +75,7 @@ class NewBaseModel( # noinspection PyMissingConstructor def __init__(self, *args: Any, **kwargs: Any) -> None: # type: ignore - + caller_name = inspect.currentframe().f_back.f_code.co_name object.__setattr__(self, "_orm_id", uuid.uuid4().hex) object.__setattr__(self, "_orm_saved", False) object.__setattr__( @@ -96,6 +97,8 @@ class NewBaseModel( if "pk" in kwargs: kwargs[self.Meta.pkname] = kwargs.pop("pk") # build the models to set them and validate but don't register + if self.Meta.include_props_in_dict: + kwargs = {k: v for k, v in kwargs.items() if k not in object.__getattribute__(self, '_props')} try: new_kwargs: Dict[str, Any] = { k: self._convert_json( @@ -174,7 +177,7 @@ class NewBaseModel( return object.__getattribute__(self, item) # pragma: no cover def _extract_related_model_instead_of_field( - self, item: str + self, item: str ) -> Optional[Union["T", Sequence["T"]]]: if item in self._orm: return self._orm.get(item) @@ -187,9 +190,9 @@ class NewBaseModel( def __same__(self, other: "NewBaseModel") -> bool: return ( - self._orm_id == other._orm_id - or (self.pk == other.pk and self.pk is not None) - or self.dict() == other.dict() + self._orm_id == other._orm_id + or (self.pk == other.pk and self.pk is not None) + or self.dict() == other.dict() ) @classmethod @@ -223,7 +226,7 @@ class NewBaseModel( @classmethod def get_properties( - cls, include: Union[Set, Dict, None], exclude: Union[Set, Dict, None] + cls, include: Union[Set, Dict, None], exclude: Union[Set, Dict, None] ) -> Set[str]: props = cls._props @@ -234,7 +237,7 @@ class NewBaseModel( return props def _get_related_not_excluded_fields( - self, include: Optional[Dict], exclude: Optional[Dict], + self, include: Optional[Dict], exclude: Optional[Dict], ) -> List: fields = [field for field in self.extract_related_names()] if include: @@ -249,15 +252,15 @@ class NewBaseModel( @staticmethod def _extract_nested_models_from_list( - models: MutableSequence, - include: Union[Set, Dict, None], - exclude: Union[Set, Dict, None], + models: MutableSequence, + include: Union[Set, Dict, None], + exclude: Union[Set, Dict, None], ) -> List: result = [] for model in models: try: result.append( - model.dict(nested=True, include=include, exclude=exclude,) + model.dict(nested=True, include=include, exclude=exclude, ) ) except ReferenceError: # pragma no cover continue @@ -265,17 +268,17 @@ class NewBaseModel( @staticmethod def _skip_ellipsis( - items: Union[Set, Dict, None], key: str + items: Union[Set, Dict, None], key: str ) -> Union[Set, Dict, None]: result = Excludable.get_child(items, key) return result if result is not Ellipsis else None def _extract_nested_models( # noqa: CCR001 - self, - nested: bool, - dict_instance: Dict, - include: Optional[Dict], - exclude: Optional[Dict], + self, + nested: bool, + dict_instance: Dict, + include: Optional[Dict], + exclude: Optional[Dict], ) -> Dict: fields = self._get_related_not_excluded_fields(include=include, exclude=exclude) @@ -301,17 +304,19 @@ class NewBaseModel( return dict_instance def dict( # type: ignore # noqa A003 - self, - *, - include: Union[Set, Dict] = None, - exclude: Union[Set, Dict] = None, - by_alias: bool = False, - skip_defaults: bool = None, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, - nested: bool = False, + self, + *, + include: Union[Set, Dict] = None, + exclude: Union[Set, Dict] = None, + by_alias: bool = False, + skip_defaults: bool = None, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + nested: bool = False, ) -> "DictStrAny": # noqa: A003' + # callable_name = inspect.currentframe().f_back.f_code.co_name + # print('dict', callable_name) dict_instance = super().dict( include=include, exclude=self._update_excluded_with_related_not_required(exclude, nested), @@ -367,6 +372,6 @@ class NewBaseModel( def _is_conversion_to_json_needed(self, column_name: str) -> bool: return ( - column_name in self.Meta.model_fields - and self.Meta.model_fields[column_name].__type__ == pydantic.Json + column_name in self.Meta.model_fields + and self.Meta.model_fields[column_name].__type__ == pydantic.Json ) diff --git a/tests/test_excluding_fields_in_fastapi.py b/tests/test_excluding_fields_in_fastapi.py index 819f3a7..cb801ce 100644 --- a/tests/test_excluding_fields_in_fastapi.py +++ b/tests/test_excluding_fields_in_fastapi.py @@ -64,7 +64,7 @@ class RandomModel(ormar.Model): metadata = metadata database = database - include_props_in_fields = True + include_props_in_dict = True id: int = ormar.Integer(primary_key=True) password: str = ormar.String(max_length=255, default=gen_pass) @@ -139,7 +139,9 @@ async def create_user4(user: User2): @app.post("/random/", response_model=RandomModel) async def create_user5(user: RandomModel): - return await user.save() + user = await user.save() + print('returning') + return user @app.post("/random2/", response_model=RandomModel) @@ -148,7 +150,7 @@ async def create_user6(user: RandomModel): return user.dict() -def test_all_endpoints(): +def test_excluding_fields_in_endpoints(): client = TestClient(app) with client as client: user = { @@ -168,20 +170,24 @@ def test_all_endpoints(): "last_name": "Doe", } + print('before call') response = client.post("/users/", json=user2) created_user = User(**response.json()) assert created_user.pk is not None assert created_user.password is None + print('before call') response = client.post("/users2/", json=user) created_user2 = User(**response.json()) assert created_user2.pk is not None assert created_user2.password is None # response has only 3 fields from UserBase + print('before call') response = client.post("/users3/", json=user) assert list(response.json().keys()) == ["email", "first_name", "last_name"] + print('before call') response = client.post("/users4/", json=user) assert list(response.json().keys()) == [ "id", @@ -191,32 +197,40 @@ def test_all_endpoints(): "category", ] - user3 = {"last_name": "Test"} - response = client.post("/random/", json=user3) - assert list(response.json().keys()) == [ - "id", - "password", - "first_name", - "last_name", - "created_date", - "full_name", - ] - assert response.json().get("full_name") == "John Test" + # user3 = {"last_name": "Test"} + # print('before call') + # response = client.post("/random/", json=user3) + # assert list(response.json().keys()) == [ + # "id", + # "password", + # "first_name", + # "last_name", + # "created_date", + # "full_name", + # ] + # assert response.json().get("full_name") == "John Test" + # + # RandomModel.Meta.include_props_in_fields = False + # user3 = {"last_name": "Test"} + # print('before call') + # response = client.post("/random/", json=user3) + # assert list(response.json().keys()) == [ + # "id", + # "password", + # "first_name", + # "last_name", + # "created_date", + # "full_name", + # ] + # assert response.json().get("full_name") == "John Test" - RandomModel.Meta.include_props_in_fields = False - user3 = {"last_name": "Test"} - response = client.post("/random/", json=user3) - assert list(response.json().keys()) == [ - "id", - "password", - "first_name", - "last_name", - "created_date", - "full_name", - ] +def test_adding_fields_in_endpoints(): + client = TestClient(app) + with client as client: RandomModel.Meta.include_props_in_dict = True user3 = {"last_name": "Test"} + print('before call') response = client.post("/random2/", json=user3) assert list(response.json().keys()) == [ "id", @@ -226,3 +240,4 @@ def test_all_endpoints(): "created_date", "full_name", ] + assert response.json().get("full_name") == "John Test" diff --git a/tests/test_pydantic_only_fields.py b/tests/test_pydantic_only_fields.py index 74d6e38..6c6b14e 100644 --- a/tests/test_pydantic_only_fields.py +++ b/tests/test_pydantic_only_fields.py @@ -28,10 +28,6 @@ class Album(ormar.Model): def name10(self) -> str: return self.name + "_10" - @validator("name") - def test(cls, v): - return v - @pytest.fixture(autouse=True, scope="module") def create_test_database(): @@ -71,5 +67,4 @@ async def test_pydantic_only_fields(): test_dict = album.dict() assert "timestamp" in test_dict assert test_dict["timestamp"] is not None - # key is still there as now it's a field - assert test_dict["name10"] is None + assert test_dict.get("name10", 'aa') == 'aa'