Skip to content

Commit

Permalink
Allow to configure models through class kwargs (#2356)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Bobronium and samuelcolvin committed Feb 22, 2021
1 parent 5b14706 commit ce67660
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 10 deletions.
1 change: 1 addition & 0 deletions changes/2356-MrMrRobat.md
@@ -0,0 +1 @@
Allow configuring models through class kwargs
11 changes: 11 additions & 0 deletions docs/examples/model_config_class_kwargs.py
@@ -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)
7 changes: 6 additions & 1 deletion docs/usage/model_config.md
Expand Up @@ -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!}
```
Expand Down
23 changes: 14 additions & 9 deletions pydantic/main.py
Expand Up @@ -168,18 +168,18 @@ def prepare_field(cls, field: 'ModelField') -> None:
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)

Expand Down Expand Up @@ -251,7 +251,12 @@ def __new__(mcs, name, bases, namespace, **kwargs): # noqa C901
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)

Expand Down
29 changes: 29 additions & 0 deletions tests/test_main.py
Expand Up @@ -1550,3 +1550,32 @@ class Item(BaseModel):

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'

0 comments on commit ce67660

Please sign in to comment.