From 29761999e7882df56581cd92a56c1e9873875c6e Mon Sep 17 00:00:00 2001 From: Joshua Kifer Date: Sat, 24 Jul 2021 11:43:48 -0700 Subject: [PATCH 1/4] Allow custom model config --- ormar/models/metaclass.py | 8 +++++++- .../test_inheritance_concrete.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index 88f5681..c9ac863 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -552,7 +552,13 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): :param attrs: class namespace :type attrs: Dict """ - attrs["Config"] = get_pydantic_base_orm_config() + if "Config" in attrs: + class Config(attrs["Config"], get_pydantic_base_orm_config()): + pass + attrs["Config"] = Config + else: + attrs["Config"] = get_pydantic_base_orm_config() + attrs["__name__"] = name attrs, model_fields = extract_annotations_and_default_vals(attrs) for base in reversed(bases): diff --git a/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py b/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py index 41eac11..c655cf8 100644 --- a/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py +++ b/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py @@ -150,6 +150,12 @@ class Bus2(Car2): max_persons: int = ormar.Integer() +class ImmutablePerson(Person): + class Config: + allow_mutation = False + validate_assignment = False + + @pytest.fixture(autouse=True, scope="module") def create_test_database(): metadata.create_all(engine) @@ -495,3 +501,13 @@ async def test_inheritance_with_multi_relation(): assert len(unicorns) == 2 assert unicorns[1].name == "Unicorn 2" assert len(unicorns[1].co_owners) == 1 + + +def test_custom_config(): + # Custom config inherits defaults + assert getattr(ImmutablePerson.__config__, "orm_mode") == True + # Custom config can override defaults + assert getattr(ImmutablePerson.__config__, "validate_assignment") == False + sam = ImmutablePerson(name="Sam") + with pytest.raises(TypeError): + sam.name = "Not Sam" From 5dcbe8f0e5da9cc66e9ab78fc120f5f190e6e554 Mon Sep 17 00:00:00 2001 From: Joshua Kifer Date: Sat, 24 Jul 2021 11:48:11 -0700 Subject: [PATCH 2/4] Change comparisons to use 'is' --- .../test_inheritance_concrete.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py b/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py index c655cf8..488fdef 100644 --- a/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py +++ b/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py @@ -505,9 +505,9 @@ async def test_inheritance_with_multi_relation(): def test_custom_config(): # Custom config inherits defaults - assert getattr(ImmutablePerson.__config__, "orm_mode") == True + assert getattr(ImmutablePerson.__config__, "orm_mode") is True # Custom config can override defaults - assert getattr(ImmutablePerson.__config__, "validate_assignment") == False + assert getattr(ImmutablePerson.__config__, "validate_assignment") is False sam = ImmutablePerson(name="Sam") with pytest.raises(TypeError): sam.name = "Not Sam" From 3528d6effab5ff4312097d0629946a7a45f85c91 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 25 Jul 2021 12:11:50 +0200 Subject: [PATCH 3/4] add docs, provide check if Config is a class, ignore dynamic bases for mypy --- docs/models/index.md | 25 +++++++++++++++++++ docs_src/models/docs016.py | 20 +++++++++++++++ ormar/models/metaclass.py | 13 ++++++++-- .../test_inheritance_concrete.py | 7 ++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 docs_src/models/docs016.py diff --git a/docs/models/index.md b/docs/models/index.md index 9f4da22..3fdf2da 100644 --- a/docs/models/index.md +++ b/docs/models/index.md @@ -373,6 +373,31 @@ You can set this parameter by providing `Meta` class `constraints` argument. To set one column as unique use [`unique`](../fields/common-parameters.md#unique) common parameter. Of course you can set many columns as unique with this param but each of them will be checked separately. +### Pydantic configuration + +As each `ormar.Model` is also a `pydantic` model, you might want to tweak the settings of the pydantic configuration. + +The way to do this in pydantic is to adjust the settings on the `Config` class provided to your model, and it works exactly the same for ormer.Models. + +So in order to set your own preferences you need to provide not only the `Meta` class but also the `Config` class to your model. + +!!!note + To read more about available settings visit the [pydantic](https://pydantic-docs.helpmanual.io/usage/model_config/) config page. + +Note that if you do not provide your own configuration, ormar will do it for you. +The default config provided is as follows: + +```python +class Config(pydantic.BaseConfig): + orm_mode = True + validate_assignment = True +``` + +So to overwrite setting or provide your own a sample model can look like following: +```Python hl_lines="15-16" +--8<-- "../docs_src/models/docs016.py" +``` + ## Model sort order When querying the database with given model by default the Model is ordered by the `primary_key` diff --git a/docs_src/models/docs016.py b/docs_src/models/docs016.py new file mode 100644 index 0000000..5f9d71c --- /dev/null +++ b/docs_src/models/docs016.py @@ -0,0 +1,20 @@ +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 + + class Config: + allow_mutation = False + + id: int = ormar.Integer(primary_key=True) + name: str = ormar.String(max_length=100) + completed: bool = ormar.Boolean(default=False) diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index c9ac863..1b8c210 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -1,3 +1,4 @@ +import inspect from typing import ( Any, Dict, @@ -552,12 +553,20 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): :param attrs: class namespace :type attrs: Dict """ + DefaultConfig = get_pydantic_base_orm_config() if "Config" in attrs: - class Config(attrs["Config"], get_pydantic_base_orm_config()): + ProvidedConfig = attrs["Config"] + if not inspect.isclass(ProvidedConfig): + raise ModelDefinitionError( + f"Config provided for class {name} has to be a class." + ) + + class Config(ProvidedConfig, DefaultConfig): # type: ignore pass + attrs["Config"] = Config else: - attrs["Config"] = get_pydantic_base_orm_config() + attrs["Config"] = DefaultConfig attrs["__name__"] = name attrs, model_fields = extract_annotations_and_default_vals(attrs) diff --git a/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py b/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py index 488fdef..fda8e7c 100644 --- a/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py +++ b/tests/test_inheritance_and_pydantic_generation/test_inheritance_concrete.py @@ -179,6 +179,13 @@ def test_duplicated_related_name_on_different_model(): max_persons: int = ormar.Integer() +def test_config_is_not_a_class_raises_error(): + with pytest.raises(ModelDefinitionError): + + class ImmutablePerson2(Person): + Config = dict(allow_mutation=False, validate_assignment=False) + + def test_field_redefining_in_concrete_models(): class RedefinedField(DateFieldsModel): class Meta(ormar.ModelMeta): From 402998c90796d004cbcaad11b0cbea9ef7f9b5e9 Mon Sep 17 00:00:00 2001 From: collerek Date: Sun, 25 Jul 2021 12:15:13 +0200 Subject: [PATCH 4/4] fix for codefactor smells --- ormar/models/metaclass.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ormar/models/metaclass.py b/ormar/models/metaclass.py index 1b8c210..df15cac 100644 --- a/ormar/models/metaclass.py +++ b/ormar/models/metaclass.py @@ -595,9 +595,11 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): populate_meta_sqlalchemy_table_if_required(new_model.Meta) expand_reverse_relationships(new_model) # TODO: iterate only related fields - for name, field in new_model.Meta.model_fields.items(): + for field_name, field in new_model.Meta.model_fields.items(): register_relation_in_alias_manager(field=field) - add_field_descriptor(name=name, field=field, new_model=new_model) + add_field_descriptor( + name=field_name, field=field, new_model=new_model + ) if ( new_model.Meta.pkname @@ -655,10 +657,7 @@ class ModelMetaclass(pydantic.main.ModelMetaclass): model=field.to, access_chain=item, ) - else: - return FieldAccessor( - source_model=cast(Type["Model"], self), - field=field, - access_chain=item, - ) + return FieldAccessor( + source_model=cast(Type["Model"], self), field=field, access_chain=item, + ) return object.__getattribute__(self, item)