Skip to content

Commit

Permalink
allow abstract sets in include and exclude arguments (#921)
Browse files Browse the repository at this point in the history
* allow abstract sets in include and exclude arguments

* add change

* correct type hints
  • Loading branch information
samuelcolvin committed Oct 23, 2019
1 parent 80c2fb1 commit bab6970
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 26 deletions.
1 change: 1 addition & 0 deletions changes/921-samuelcolvin.md
@@ -0,0 +1 @@
Allow abstracts sets (eg. dict keys) in the `include` and `exclude` arguments of `dict()`
26 changes: 13 additions & 13 deletions pydantic/main.py
Expand Up @@ -24,7 +24,7 @@
from .class_validators import ValidatorListDict
from .types import ModelOrDc
from .typing import CallableGenerator, TupleGenerator, DictStrAny, DictAny, SetStr
from .typing import SetIntStr, DictIntStrAny, ReprArgs # noqa: F401
from .typing import AbstractSetIntStr, DictIntStrAny, ReprArgs # noqa: F401

ConfigType = Type['BaseConfig']
Model = TypeVar('Model', bound='BaseModel')
Expand Down Expand Up @@ -302,8 +302,8 @@ def __setstate__(self, state: 'DictAny') -> None:
def dict(
self,
*,
include: Union['SetIntStr', 'DictIntStrAny'] = None,
exclude: Union['SetIntStr', 'DictIntStrAny'] = None,
include: Union['AbstractSetIntStr', 'DictIntStrAny'] = None,
exclude: Union['AbstractSetIntStr', 'DictIntStrAny'] = None,
by_alias: bool = False,
skip_defaults: bool = None,
exclude_unset: bool = False,
Expand Down Expand Up @@ -344,8 +344,8 @@ def _get_key_factory(self, by_alias: bool) -> Callable[..., str]:
def json(
self,
*,
include: Union['SetIntStr', 'DictIntStrAny'] = None,
exclude: Union['SetIntStr', 'DictIntStrAny'] = None,
include: Union['AbstractSetIntStr', 'DictIntStrAny'] = None,
exclude: Union['AbstractSetIntStr', 'DictIntStrAny'] = None,
by_alias: bool = False,
skip_defaults: bool = None,
exclude_unset: bool = False,
Expand Down Expand Up @@ -454,8 +454,8 @@ def construct(cls: Type['Model'], _fields_set: Optional['SetStr'] = None, **valu
def copy(
self: 'Model',
*,
include: Union['SetIntStr', 'DictIntStrAny'] = None,
exclude: Union['SetIntStr', 'DictIntStrAny'] = None,
include: Union['AbstractSetIntStr', 'DictIntStrAny'] = None,
exclude: Union['AbstractSetIntStr', 'DictIntStrAny'] = None,
update: 'DictStrAny' = None,
deep: bool = False,
) -> 'Model':
Expand Down Expand Up @@ -545,8 +545,8 @@ def _get_value(
v: Any,
to_dict: bool,
by_alias: bool,
include: Optional[Union['SetIntStr', 'DictIntStrAny']],
exclude: Optional[Union['SetIntStr', 'DictIntStrAny']],
include: Optional[Union['AbstractSetIntStr', 'DictIntStrAny']],
exclude: Optional[Union['AbstractSetIntStr', 'DictIntStrAny']],
exclude_unset: bool,
exclude_defaults: bool,
) -> Any:
Expand Down Expand Up @@ -622,8 +622,8 @@ def _iter(
to_dict: bool = False,
by_alias: bool = False,
allowed_keys: Optional['SetStr'] = None,
include: Union['SetIntStr', 'DictIntStrAny'] = None,
exclude: Union['SetIntStr', 'DictIntStrAny'] = None,
include: Union['AbstractSetIntStr', 'DictIntStrAny'] = None,
exclude: Union['AbstractSetIntStr', 'DictIntStrAny'] = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
) -> 'TupleGenerator':
Expand Down Expand Up @@ -652,8 +652,8 @@ def _iter(

def _calculate_keys(
self,
include: Optional[Union['SetIntStr', 'DictIntStrAny']],
exclude: Optional[Union['SetIntStr', 'DictIntStrAny']],
include: Optional[Union['AbstractSetIntStr', 'DictIntStrAny']],
exclude: Optional[Union['AbstractSetIntStr', 'DictIntStrAny']],
exclude_unset: bool,
update: Optional['DictStrAny'] = None,
) -> Optional['SetStr']:
Expand Down
6 changes: 4 additions & 2 deletions pydantic/typing.py
Expand Up @@ -2,6 +2,7 @@
from enum import Enum
from typing import ( # type: ignore
TYPE_CHECKING,
AbstractSet,
Any,
ClassVar,
Dict,
Expand Down Expand Up @@ -62,7 +63,7 @@ def evaluate_forwardref(type_, globalns, localns): # type: ignore
SetStr = Set[str]
ListStr = List[str]
IntStr = Union[int, str]
SetIntStr = Set[IntStr]
AbstractSetIntStr = AbstractSet[IntStr]
DictIntStrAny = Dict[IntStr, Any]
CallableGenerator = Generator[AnyCallable, None, None]
ReprArgs = Sequence[Tuple[Optional[str], Any]]
Expand All @@ -89,10 +90,11 @@ def evaluate_forwardref(type_, globalns, localns): # type: ignore
'SetStr',
'ListStr',
'IntStr',
'SetIntStr',
'AbstractSetIntStr',
'DictIntStrAny',
'CallableGenerator',
'ReprArgs',
'CallableGenerator',
)


Expand Down
15 changes: 8 additions & 7 deletions pydantic/utils.py
Expand Up @@ -3,6 +3,7 @@
from importlib import import_module
from typing import (
TYPE_CHECKING,
AbstractSet,
Any,
Callable,
Dict,
Expand All @@ -27,7 +28,7 @@

if TYPE_CHECKING:
from .main import BaseModel # noqa: F401
from .typing import SetIntStr, DictIntStrAny, IntStr, ReprArgs # noqa: F401
from .typing import AbstractSetIntStr, DictIntStrAny, IntStr, ReprArgs # noqa: F401

KeyType = TypeVar('KeyType')

Expand Down Expand Up @@ -247,15 +248,15 @@ class ValueItems(Representation):

__slots__ = ('_items', '_type')

def __init__(self, value: Any, items: Union['SetIntStr', 'DictIntStrAny']) -> None:
def __init__(self, value: Any, items: Union['AbstractSetIntStr', 'DictIntStrAny']) -> None:
if TYPE_CHECKING:
self._items: Union['SetIntStr', 'DictIntStrAny']
self._items: Union['AbstractSetIntStr', 'DictIntStrAny']
self._type: Type[Union[set, dict]] # type: ignore

# For further type checks speed-up
if isinstance(items, dict):
self._type = dict
elif isinstance(items, set):
elif isinstance(items, AbstractSet):
self._type = set
else:
raise TypeError(f'Unexpected type of exclude value {type(items)}')
Expand Down Expand Up @@ -288,7 +289,7 @@ def is_included(self, item: Any) -> bool:
return item in self._items

@no_type_check
def for_element(self, e: 'IntStr') -> Optional[Union['SetIntStr', 'DictIntStrAny']]:
def for_element(self, e: 'IntStr') -> Optional[Union['AbstractSetIntStr', 'DictIntStrAny']]:
"""
:param e: key or index of element on value
:return: raw values for elemet if self._items is dict and contain needed element
Expand All @@ -301,8 +302,8 @@ def for_element(self, e: 'IntStr') -> Optional[Union['SetIntStr', 'DictIntStrAny

@no_type_check
def _normalize_indexes(
self, items: Union['SetIntStr', 'DictIntStrAny'], v_length: int
) -> Union['SetIntStr', 'DictIntStrAny']:
self, items: Union['AbstractSetIntStr', 'DictIntStrAny'], v_length: int
) -> Union['AbstractSetIntStr', 'DictIntStrAny']:
"""
:param items: dict or set of indexes which will be normalized
:param v_length: length of sequence indexes of which will be
Expand Down
16 changes: 12 additions & 4 deletions tests/test_edge_cases.py
Expand Up @@ -420,23 +420,31 @@ class Model(BaseModel):
assert m.dict(include={'a', 'b', 'c'}, exclude={'b'}, exclude_defaults=True) == {'a': 1}
assert m.dict(include={'a', 'b', 'c'}, exclude={'a', 'c'}, exclude_defaults=True) == {'b': 2}

# abstract set
assert m.dict(include={'a': 1}.keys()) == {'a': 1}
assert m.dict(exclude={'a': 1}.keys()) == {'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 7}

assert m.dict(include={'a': 1}.keys(), exclude_unset=True) == {'a': 1}
assert m.dict(exclude={'a': 1}.keys(), exclude_unset=True) == {'b': 2, 'e': 5, 'f': 7}


def test_skip_defaults_deprecated():
class Model(BaseModel):
x: int
b: int = 2

m = Model(x=1)
match = r'Model.dict\(\): "skip_defaults" is deprecated and replaced by "exclude_unset"'
with pytest.warns(DeprecationWarning, match=match):
assert m.dict(skip_defaults=True)
assert m.dict(skip_defaults=True) == m.dict(exclude_unset=True)
with pytest.warns(DeprecationWarning, match=match):
assert m.dict(skip_defaults=False)
assert m.dict(skip_defaults=False) == m.dict(exclude_unset=False)

match = r'Model.json\(\): "skip_defaults" is deprecated and replaced by "exclude_unset"'
with pytest.warns(DeprecationWarning, match=match):
assert m.json(skip_defaults=True)
assert m.json(skip_defaults=True) == m.json(exclude_unset=True)
with pytest.warns(DeprecationWarning, match=match):
assert m.json(skip_defaults=False)
assert m.json(skip_defaults=False) == m.json(exclude_unset=False)


def test_advanced_exclude():
Expand Down

0 comments on commit bab6970

Please sign in to comment.