From b838fa1edf74e586be71d123194144a1da99a207 Mon Sep 17 00:00:00 2001 From: collerek Date: Thu, 3 Dec 2020 09:15:19 +0100 Subject: [PATCH] some cleanup and optimization --- ormar/fields/model_fields.py | 8 ++- ormar/models/metaclass.py | 55 +++++--------------- ormar/models/modelproxy.py | 6 +-- ormar/models/newbasemodel.py | 13 ++--- ormar/models/quick_access_views.py | 62 +++++++++++++++++++++++ tests/test_excluding_fields_in_fastapi.py | 8 +-- tests/test_pydantic_only_fields.py | 26 +++++----- 7 files changed, 106 insertions(+), 72 deletions(-) create mode 100644 ormar/models/quick_access_views.py diff --git a/ormar/fields/model_fields.py b/ormar/fields/model_fields.py index f90ed87..8cbbd5e 100644 --- a/ormar/fields/model_fields.py +++ b/ormar/fields/model_fields.py @@ -18,11 +18,15 @@ def is_field_nullable( pydantic_only: Optional[bool], ) -> bool: if nullable is None: - return default is not None or server_default is not None or pydantic_only + return ( + default is not None + or server_default is not None + or (pydantic_only is not None and pydantic_only) + ) return nullable -def is_auto_primary_key(primary_key: bool, autoincrement: bool): +def is_auto_primary_key(primary_key: bool, autoincrement: bool) -> bool: return primary_key and autoincrement diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index 5c569dd..4706029 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -1,7 +1,6 @@ -import inspect import logging import warnings -from typing import Any, Dict, List, Optional, Set, TYPE_CHECKING, Tuple, Type, Union +from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, Union import databases import pydantic @@ -12,11 +11,12 @@ from pydantic.utils import lenient_issubclass from sqlalchemy.sql.schema import ColumnCollectionConstraint import ormar # noqa I100 -from ormar import ForeignKey, ModelDefinitionError, Integer # noqa I100 +from ormar import ForeignKey, Integer, ModelDefinitionError # noqa I100 from ormar.fields import BaseField from ormar.fields.foreign_key import ForeignKeyField from ormar.fields.many_to_many import ManyToMany, ManyToManyField from ormar.fields.model_fields import ModelFieldFactory +from ormar.models.quick_access_views import quick_access_set from ormar.queryset import QuerySet from ormar.relations.alias_manager import AliasManager @@ -128,7 +128,7 @@ def create_pydantic_field( def get_pydantic_field(field_name: str, model: Type["Model"]) -> "ModelField": return ModelField( name=field_name, - type_=model.Meta.model_fields[field_name].__type__, + type_=model.Meta.model_fields[field_name].__type__, # type: ignore model_config=model.__config__, required=not model.Meta.model_fields[field_name].nullable, class_validators={}, @@ -316,10 +316,12 @@ def populate_choices_validators(model: Type["Model"]) -> None: # noqa CCR001 validators = getattr(model, "__pre_root_validators__", []) if choices_validator not in validators: validators.append(choices_validator) - setattr(model, "__pre_root_validators__", validators) + model.__pre_root_validators__ = validators -def populate_default_options_values(new_model: Type["Model"], model_fields: Dict): +def populate_default_options_values( + new_model: Type["Model"], model_fields: Dict +) -> None: if not hasattr(new_model.Meta, "constraints"): new_model.Meta.constraints = [] if not hasattr(new_model.Meta, "model_fields"): @@ -331,59 +333,28 @@ def populate_default_options_values(new_model: Type["Model"], model_fields: Dict new_model.Meta.include_props_in_fields = False -def add_cached_properties(new_model): +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") } - new_model._quick_access_fields = { - "_orm_id", - "_orm_saved", - "_orm", - "_convert_json", - "__fields__", - "_related_names", - "_props", - "__class__", - "__dict__", - "__config__", - "_iter", - "_get_value", - "_is_conversion_to_json_needed", - "__fields_set__", - "_skip_ellipsis", - "_calculate_keys", - "dict", - "_update_excluded_with_related_not_required", - "_extract_nested_models", - "_get_related_not_excluded_fields", - "get_properties", - "resolve_relation_name", - "resolve_relation_field", - "set_save_status", - "__pre_root_validators__", - "__post_root_validators__", - "_extract_nested_models_from_list", - "get_name", - "extract_related_names", - "Meta", - } + new_model._quick_access_fields = quick_access_set new_model._related_names = None new_model._pydantic_fields = {name for name in new_model.__fields__} -def add_property_fields(new_model): +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( + 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 else Any, + type_=Optional[field_type] if field_type is not None else Any, # type: ignore model_config=new_model.__config__, required=False, class_validators={}, diff --git a/ormar/models/modelproxy.py b/ormar/models/modelproxy.py index ca12672..0523752 100644 --- a/ormar/models/modelproxy.py +++ b/ormar/models/modelproxy.py @@ -20,10 +20,6 @@ from typing import ( from ormar.exceptions import ModelPersistenceError, RelationshipInstanceError from ormar.queryset.utils import translate_list_to_dict, update -try: - import orjson as json -except ImportError: # pragma: nocover - import json # type: ignore import ormar # noqa: I100 from ormar.fields import BaseField @@ -45,7 +41,7 @@ Field = TypeVar("Field", bound=BaseField) class ModelTableProxy: if TYPE_CHECKING: # pragma no cover Meta: ModelMeta - _related_names: Set + _related_names: Optional[Set] _related_names_hash: Union[str, bytes] pk: Any get_name: Callable diff --git a/ormar/models/newbasemodel.py b/ormar/models/newbasemodel.py index 64f703f..82adee9 100644 --- a/ormar/models/newbasemodel.py +++ b/ormar/models/newbasemodel.py @@ -1,6 +1,5 @@ import json import uuid -from collections import Counter from typing import ( AbstractSet, Any, @@ -66,9 +65,11 @@ class NewBaseModel( _orm_relationship_manager: AliasManager _orm: RelationsManager _orm_saved: bool - _related_names: Set + _related_names: Optional[Set] _related_names_hash: str _props: Set + _pydantic_fields: Set + _quick_access_fields: Set Meta: ModelMeta # noinspection PyMissingConstructor @@ -170,7 +171,7 @@ class NewBaseModel( value = object.__getattribute__(self, "__dict__").get(item, None) value = object.__getattribute__(self, "_convert_json")(item, value, "loads") return value - return object.__getattribute__(self, item) + return object.__getattribute__(self, item) # pragma: no cover def _extract_related_model_instead_of_field( self, item: str @@ -223,13 +224,13 @@ class NewBaseModel( @classmethod def get_properties( cls, include: Union[Set, Dict, None], exclude: Union[Set, Dict, None] - ) -> List[str]: + ) -> Set[str]: props = cls._props if include: - props = [prop for prop in props if prop in include] + props = {prop for prop in props if prop in include} if exclude: - props = [prop for prop in props if prop not in exclude] + props = {prop for prop in props if prop not in exclude} return props def _get_related_not_excluded_fields( diff --git a/ormar/models/quick_access_views.py b/ormar/models/quick_access_views.py new file mode 100644 index 0000000..bbab89e --- /dev/null +++ b/ormar/models/quick_access_views.py @@ -0,0 +1,62 @@ +quick_access_set = { + "Config", + "Meta", + "__class__", + "__config__", + "__custom_root_type__", + "__dict__", + "__fields__", + "__fields_set__", + "__json_encoder__", + "__post_root_validators__", + "__pre_root_validators__", + "__same__", + "_calculate_keys", + "_convert_json", + "_extract_db_related_names", + "_extract_model_db_fields", + "_extract_nested_models", + "_extract_nested_models_from_list", + "_extract_own_model_fields", + "_get_related_not_excluded_fields", + "_get_value", + "_is_conversion_to_json_needed", + "_iter", + "_orm", + "_orm_id", + "_orm_saved", + "_props", + "_related_names", + "_skip_ellipsis", + "_update_and_follow", + "_update_excluded_with_related_not_required", + "copy", + "delete", + "dict", + "extract_related_names", + "from_dict", + "get_column_alias", + "get_column_name_from_alias", + "get_filtered_names_to_extract", + "get_name", + "get_properties", + "get_related_field_name", + "get_relation_model_id", + "json", + "keys", + "load", + "pk_column", + "pk_type", + "populate_default_values", + "remove", + "resolve_relation_field", + "resolve_relation_name", + "save", + "save_related", + "saved", + "set_save_status", + "translate_aliases_to_columns", + "translate_columns_to_aliases", + "update", + "upsert", +} diff --git a/tests/test_excluding_fields_in_fastapi.py b/tests/test_excluding_fields_in_fastapi.py index c04ac94..819f3a7 100644 --- a/tests/test_excluding_fields_in_fastapi.py +++ b/tests/test_excluding_fields_in_fastapi.py @@ -76,7 +76,7 @@ class RandomModel(ormar.Model): @property def full_name(self): - return ' '.join([self.first_name, self.last_name]) + return " ".join([self.first_name, self.last_name]) class User(ormar.Model): @@ -199,7 +199,7 @@ def test_all_endpoints(): "first_name", "last_name", "created_date", - "full_name" + "full_name", ] assert response.json().get("full_name") == "John Test" @@ -212,7 +212,7 @@ def test_all_endpoints(): "first_name", "last_name", "created_date", - "full_name" + "full_name", ] RandomModel.Meta.include_props_in_dict = True @@ -224,5 +224,5 @@ def test_all_endpoints(): "first_name", "last_name", "created_date", - "full_name" + "full_name", ] diff --git a/tests/test_pydantic_only_fields.py b/tests/test_pydantic_only_fields.py index 19ac37f..74d6e38 100644 --- a/tests/test_pydantic_only_fields.py +++ b/tests/test_pydantic_only_fields.py @@ -26,9 +26,9 @@ class Album(ormar.Model): @property def name10(self) -> str: - return self.name + '_10' + return self.name + "_10" - @validator('name') + @validator("name") def test(cls, v): return v @@ -46,30 +46,30 @@ def create_test_database(): async def test_pydantic_only_fields(): async with database: async with database.transaction(force_rollback=True): - album = await Album.objects.create(name='Hitchcock') + album = await Album.objects.create(name="Hitchcock") assert album.pk is not None assert album.saved assert album.timestamp is None - album = await Album.objects.exclude_fields('timestamp').get() + album = await Album.objects.exclude_fields("timestamp").get() assert album.timestamp is None - album = await Album.objects.fields({'name', 'timestamp'}).get() + album = await Album.objects.fields({"name", "timestamp"}).get() assert album.timestamp is None test_dict = album.dict() - assert 'timestamp' in test_dict - assert test_dict['timestamp'] is None + assert "timestamp" in test_dict + assert test_dict["timestamp"] is None album.timestamp = datetime.datetime.now() test_dict = album.dict() - assert 'timestamp' in test_dict - assert test_dict['timestamp'] is not None - assert test_dict.get('name10') == 'Hitchcock_10' + assert "timestamp" in test_dict + assert test_dict["timestamp"] is not None + assert test_dict.get("name10") == "Hitchcock_10" Album.Meta.include_props_in_dict = False test_dict = album.dict() - assert 'timestamp' in test_dict - assert test_dict['timestamp'] is not None + 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["name10"] is None