Skip to content

Commit

Permalink
prevent setting fields on custom config
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin committed Sep 6, 2020
1 parent d920e8f commit ff1ed7e
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 19 deletions.
32 changes: 20 additions & 12 deletions pydantic/decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def wrapper_function(*args: Any, **kwargs: Any) -> Any:


class ValidatedFunction:
def __init__(self, function: 'Callable', config: 'ConfigType'):
def __init__(self, function: 'Callable', config: 'ConfigType'): # noqa C901
from inspect import Parameter, signature

parameters: Mapping[str, Parameter] = signature(function).parameters
Expand All @@ -55,6 +55,21 @@ def __init__(self, function: 'Callable', config: 'ConfigType'):
f'names when using the "{validate_arguments.__name__}" decorator'
)

class CustomConfig:
pass

if not TYPE_CHECKING: # pragma: no branch
if isinstance(config, dict):
CustomConfig = type('Config', (), config) # noqa: F811
elif config is not None:
CustomConfig = config # noqa: F811

if hasattr(CustomConfig, 'fields'):
raise ConfigError(
'Setting the "fields" property on custom Config for '
'@validate_arguments is not yet supported, please remove.'
)

self.raw_function = function
self.arg_mapping: Dict[int, str] = {}
self.positional_only_args = set()
Expand Down Expand Up @@ -108,7 +123,7 @@ def __init__(self, function: 'Callable', config: 'ConfigType'):
# same with kwargs
fields[self.v_kwargs_name] = Dict[Any, Any], None

self.create_model(fields, takes_args, takes_kwargs, config)
self.create_model(fields, takes_args, takes_kwargs, CustomConfig)

def call(self, *args: Any, **kwargs: Any) -> Any:
values = self.build_values(args, kwargs)
Expand Down Expand Up @@ -178,18 +193,11 @@ def execute(self, m: BaseModel) -> Any:
else:
return self.raw_function(**d)

def create_model(self, fields: Dict[str, Any], takes_args: bool, takes_kwargs: bool, config: 'ConfigType') -> None:
def create_model(
self, fields: Dict[str, Any], takes_args: bool, takes_kwargs: bool, CustomConfig: Type[Any]
) -> None:
pos_args = len(self.arg_mapping)

class CustomConfig:
pass

if not TYPE_CHECKING:
if isinstance(config, dict):
CustomConfig = type('Config', (), config) # noqa: F811
elif config is not None:
CustomConfig = config # noqa: F811

class DecoratorBaseModel(BaseModel):
@validator(self.v_args_name, check_fields=False, allow_reuse=True)
def check_args(cls, v: List[Any]) -> List[Any]:
Expand Down
1 change: 0 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[tool:pytest]
testpaths = tests
timeout = 10
filterwarnings =
error
ignore::DeprecationWarning:distutils
Expand Down
52 changes: 46 additions & 6 deletions tests/test_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,15 +265,55 @@ def foo(cls, a: int, b: int):
]


def test_config():
@validate_arguments(config=dict(title='testing', fields={'b': 'bang'}))
def test_config_title():
@validate_arguments(config=dict(title='Testing'))
def foo(a: int, b: int):
return f'{a}, {b}'

assert foo(1, 2) == '1, 2'
assert foo(1, bang=2) == '1, 2'
assert foo.model.schema()['title'] == 'testing'
assert foo(1, b=2) == '1, 2'
assert foo.model.schema()['title'] == 'Testing'


def test_config_title_cls():
class Config:
title = 'Testing'

@validate_arguments(config=Config)
def foo(a: int, b: int):
return f'{a}, {b}'

assert foo(1, 2) == '1, 2'
assert foo(1, b=2) == '1, 2'
assert foo.model.schema()['title'] == 'Testing'


def test_config_fields():
with pytest.raises(ConfigError, match='Setting the "fields" property on custom Config for'):

@validate_arguments(config=dict(fields={'b': 'bang'}))
def foo(a: int, b: int):
return f'{a}, {b}'


def test_config_arbitrary_types_allowed():
class EggBox:
def __str__(self) -> str:
return 'EggBox()'

@validate_arguments(config=dict(arbitrary_types_allowed=True))
def foo(a: int, b: EggBox):
return f'{a}, {b}'

assert foo(1, EggBox()) == '1, EggBox()'
with pytest.raises(ValidationError) as exc_info:
foo(1, b=2)
assert foo(1, 2) == '1, 2'

assert exc_info.value.errors() == ...
assert exc_info.value.errors() == [
{
'loc': ('b',),
'msg': 'instance of EggBox expected',
'type': 'type_error.arbitrary_type',
'ctx': {'expected_arbitrary_type': 'EggBox'},
},
]

0 comments on commit ff1ed7e

Please sign in to comment.