Merge pull request #286 from naturalethic/allow-custom-model-config
Allow custom model config
This commit is contained in:
@ -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`
|
||||
|
||||
20
docs_src/models/docs016.py
Normal file
20
docs_src/models/docs016.py
Normal file
@ -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)
|
||||
@ -1,3 +1,4 @@
|
||||
import inspect
|
||||
from typing import (
|
||||
Any,
|
||||
Dict,
|
||||
@ -552,7 +553,21 @@ class ModelMetaclass(pydantic.main.ModelMetaclass):
|
||||
:param attrs: class namespace
|
||||
:type attrs: Dict
|
||||
"""
|
||||
attrs["Config"] = get_pydantic_base_orm_config()
|
||||
DefaultConfig = get_pydantic_base_orm_config()
|
||||
if "Config" in attrs:
|
||||
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"] = DefaultConfig
|
||||
|
||||
attrs["__name__"] = name
|
||||
attrs, model_fields = extract_annotations_and_default_vals(attrs)
|
||||
for base in reversed(bases):
|
||||
@ -580,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
|
||||
@ -640,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)
|
||||
|
||||
@ -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)
|
||||
@ -173,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):
|
||||
@ -495,3 +508,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") is True
|
||||
# Custom config can override defaults
|
||||
assert getattr(ImmutablePerson.__config__, "validate_assignment") is False
|
||||
sam = ImmutablePerson(name="Sam")
|
||||
with pytest.raises(TypeError):
|
||||
sam.name = "Not Sam"
|
||||
|
||||
Reference in New Issue
Block a user