Allow to configure models through class kwargs (#2356)

* add support for class kwargs config

* reformat tests

* add changes file and docs

* fix linting in 'inherit_config'

* tweak docs

Co-authored-by: Samuel Colvin <s@muelcolvin.com>
This commit is contained in:
Arseny Boykov
2021-02-22 20:10:04 +03:00
committed by GitHub
parent 5b147065a3
commit ce67660d2f
5 changed files with 61 additions and 10 deletions
+1
View File
@@ -0,0 +1 @@
Allow configuring models through class kwargs
@@ -0,0 +1,11 @@
from pydantic import BaseModel, ValidationError, Extra
class Model(BaseModel, extra=Extra.forbid):
a: str
try:
Model(a='spam', b='oh no')
except ValidationError as e:
print(e)
+6 -1
View File
@@ -89,8 +89,13 @@ not be included in the model schemas. **Note**: this means that attributes on th
```
_(This script is complete, it should run "as is")_
Similarly, if using the `@dataclass` decorator:
Also, you can specify config options as model class kwargs:
```py
{!.tmp_examples/model_config_class_kwargs.py!}
```
_(This script is complete, it should run "as is")_
Similarly, if using the `@dataclass` decorator:
```py
{!.tmp_examples/model_config_dataclass.py!}
```
+14 -9
View File
@@ -168,18 +168,18 @@ class BaseConfig:
pass
def inherit_config(self_config: 'ConfigType', parent_config: 'ConfigType') -> 'ConfigType':
namespace = {}
def inherit_config(self_config: 'ConfigType', parent_config: 'ConfigType', **namespace: Any) -> 'ConfigType':
if not self_config:
base_classes = (parent_config,)
base_classes: Tuple['ConfigType', ...] = (parent_config,)
elif self_config == parent_config:
base_classes = (self_config,)
else:
base_classes = self_config, parent_config # type: ignore
namespace['json_encoders'] = {
**getattr(parent_config, 'json_encoders', {}),
**getattr(self_config, 'json_encoders', {}),
}
base_classes = self_config, parent_config
namespace['json_encoders'] = {
**getattr(parent_config, 'json_encoders', {}),
**getattr(self_config, 'json_encoders', {}),
}
return type('Config', base_classes, namespace)
@@ -251,7 +251,12 @@ class ModelMetaclass(ABCMeta):
private_attributes.update(base.__private_attributes__)
class_vars.update(base.__class_vars__)
config = inherit_config(namespace.get('Config'), config)
config_kwargs = {key: kwargs.pop(key) for key in kwargs.keys() & BaseConfig.__dict__.keys()}
config_from_namespace = namespace.get('Config')
if config_kwargs and config_from_namespace:
raise TypeError('Specifying config in two places is ambiguous, use either Config attribute or class kwargs')
config = inherit_config(config_from_namespace, config, **config_kwargs)
validators = inherit_validators(extract_validators(namespace), validators)
vg = ValidatorGroup(validators)
+29
View File
@@ -1550,3 +1550,32 @@ def test_inherited_model_field_untouched():
assert id(image_1) == id(item.images[0])
assert id(image_2) == id(item.images[1])
def test_class_kwargs_config():
class Base(BaseModel, extra='forbid', alias_generator=str.upper):
a: int
assert Base.__config__.extra is Extra.forbid
assert Base.__config__.alias_generator is str.upper
assert Base.__fields__['a'].alias == 'A'
class Model(Base, extra='allow'):
b: int
assert Model.__config__.extra is Extra.allow # overwritten as intended
assert Model.__config__.alias_generator is str.upper # inherited as intended
assert Model.__fields__['b'].alias == 'B' # alias_generator still works
def test_class_kwargs_config_and_attr_conflict():
with pytest.raises(
TypeError, match='Specifying config in two places is ambiguous, use either Config attribute or class kwargs'
):
class Model(BaseModel, extra='allow'):
b: int
class Config:
extra = 'forbid'