Repository: pydantic/pydantic

Issue:
values include, exclude
make `values` a function and allow `include` and `exclude` arguments.


Discussion:

Resolving patch:
diff --git a/docs/examples/exotic.py b/docs/examples/exotic.py
--- a/docs/examples/exotic.py
+++ b/docs/examples/exotic.py
@@ -37,7 +37,7 @@ class Model(BaseModel):
     email_address='Samuel Colvin <s@muelcolvin.com >',
     email_and_name='Samuel Colvin <s@muelcolvin.com >',
 )
-print(m.values)
+print(m.values())
 """
 {
     'cos_function': <built-in function cos>,
diff --git a/docs/examples/recursive.py b/docs/examples/recursive.py
--- a/docs/examples/recursive.py
+++ b/docs/examples/recursive.py
@@ -17,5 +17,5 @@ class Spam(BaseModel):
 m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
 print(m)
 # > Spam foo=<Foo count=4 size=None> bars=[<Bar apple='x1' banana='y'>, <Bar apple='x2' banana='y'>]
-print(m.values)
+print(m.values())
 # {'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}
diff --git a/pydantic/main.py b/pydantic/main.py
--- a/pydantic/main.py
+++ b/pydantic/main.py
@@ -1,5 +1,6 @@
 from collections import OrderedDict
 from types import FunctionType
+from typing import Any, Dict, Set
 
 from .exceptions import Error, Extra, Missing, ValidationError
 from .fields import Field
@@ -96,7 +97,7 @@ class BaseModel(metaclass=MetaModel):
     Config = BaseConfig
 
     def __init__(self, **values):
-        self.__values__ = {}
+        self.__values__ = OrderedDict()
         self.__errors__ = OrderedDict()
         self._process_values(values)
 
@@ -111,9 +112,16 @@ def setattr(self, name, value):
         setattr(self, name, value)
         self.__values__[name] = value
 
-    @property
-    def values(self):
-        return dict(self)
+    def values(self, *, include: Set[str]=None, exclude: Set[str]=set()) -> Dict[str, Any]:
+        """
+        Get a dict of the values processed by the model, optionally specifying which fields to include or exclude.
+
+        This is NOT equivalent to the values() method on a dict.
+        """
+        return {
+            k: v for k, v in self
+            if k not in exclude and (not include or k in include)
+        }
 
     @property
     def fields(self):
@@ -175,7 +183,7 @@ def _process_value(self, name, alias, field, value):
     @classmethod
     def _get_value(cls, v):
         if isinstance(v, BaseModel):
-            return v.values
+            return v.values()
         elif isinstance(v, list):
             return [cls._get_value(v_) for v_ in v]
         elif isinstance(v, dict):
@@ -194,9 +202,9 @@ def __iter__(self):
 
     def __eq__(self, other):
         if isinstance(other, BaseModel):
-            return self.values == other.values
+            return self.values() == other.values()
         else:
-            return self.values == other
+            return self.values() == other
 
     def __repr__(self):
         return f'<{self}>'


Test patch:
diff --git a/tests/test_complex.py b/tests/test_complex.py
--- a/tests/test_complex.py
+++ b/tests/test_complex.py
@@ -180,7 +180,7 @@ class DictModel(BaseModel):
 
 
 def test_dict_values():
-    assert DictModel(v={'foo': 1}).values == {'v': {'foo': 1}}
+    assert DictModel(v={'foo': 1}).values() == {'v': {'foo': 1}}
 
 
 @pytest.mark.parametrize('value,result', [
@@ -269,7 +269,7 @@ def test_recursive_list():
     assert "<MasterListModel v=[<SubModel name='testing' count=4>]>" == repr(m)
     assert m.v[0].name == 'testing'
     assert m.v[0].count == 4
-    assert m.values == {'v': [{'count': 4, 'name': 'testing'}]}
+    assert m.values() == {'v': [{'count': 4, 'name': 'testing'}]}
 
     with pytest.raises(ValidationError) as exc_info:
         MasterListModel(v=['x'])
@@ -356,9 +356,9 @@ class Model(BaseModel):
 def test_any_dict():
     class Model(BaseModel):
         v: Dict[int, Any] = ...
-    assert Model(v={1: 'foobar'}).values == {'v': {1: 'foobar'}}
-    assert Model(v={123: 456}).values == {'v': {123: 456}}
-    assert Model(v={2: [1, 2, 3]}).values == {'v': {2: [1, 2, 3]}}
+    assert Model(v={1: 'foobar'}).values() == {'v': {1: 'foobar'}}
+    assert Model(v={123: 456}).values() == {'v': {123: 456}}
+    assert Model(v={2: [1, 2, 3]}).values() == {'v': {2: [1, 2, 3]}}
 
 
 def test_infer_alias():
@@ -402,3 +402,26 @@ class Config:
     assert list(Model.__fields__.keys()) == ['b', 'a']
     assert [f.alias for f in Model.__fields__.values()] == ['b', 'foobar']
     assert Model(foobar='123').a == 123.0
+
+
+def test_success_values_include():
+    class Model(BaseModel):
+        a: int = 1
+        b: int = 2
+        c: int = 3
+
+    m = Model()
+    assert m.values() == {'a': 1, 'b': 2, 'c': 3}
+    assert m.values(include={'a'}) == {'a': 1}
+    assert m.values(exclude={'a'}) == {'b': 2, 'c': 3}
+    assert m.values(include={'a', 'b'}, exclude={'a'}) == {'b': 2}
+
+
+def test_values_order():
+    class Model(BaseModel):
+        a: int = 1
+        b: int = 2
+        c: int = 3
+
+    m = Model(c=30, b=20, a=10)
+    assert list(m) == [('a', 10), ('b', 20), ('c', 30)]
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -224,19 +224,19 @@ class Model(BaseModel):
         class Config:
             allow_extra = True
 
-    assert Model(a='10.2', b=12).values == {'a': 10.2, 'b': 12}
+    assert Model(a='10.2', b=12).values() == {'a': 10.2, 'b': 12}
 
 
 def test_set_attr():
     m = UltraSimpleModel(a=10.2)
-    assert m.values == {'a': 10.2, 'b': 10}
+    assert m.values() == {'a': 10.2, 'b': 10}
     m.setattr('b', 20)
-    assert m.values == {'a': 10.2, 'b': 20}
+    assert m.values() == {'a': 10.2, 'b': 20}
 
 
 def test_set_attr_invalid():
     m = UltraSimpleModel(a=10.2)
-    assert m.values == {'a': 10.2, 'b': 10}
+    assert m.values() == {'a': 10.2, 'b': 10}
     with pytest.raises(ValueError) as exc_info:
         m.setattr('c', 20)
     assert '"UltraSimpleModel" object has no field "c"' in str(exc_info)
@@ -260,9 +260,9 @@ class Config:
             }
 
     assert Model().a == 'foobar'
-    assert Model().values == {'a': 'foobar'}
+    assert Model().values() == {'a': 'foobar'}
     assert Model(_a='different').a == 'different'
-    assert Model(_a='different').values == {'a': 'different'}
+    assert Model(_a='different').values() == {'a': 'different'}
 
 
 def test_field_order():
@@ -283,7 +283,7 @@ class Model(BaseModel):
         b: int = 10
 
     m = Model(a=10.2)
-    assert m.values == dict(a=10.2, b=10)
+    assert m.values() == dict(a=10.2, b=10)
 
     with pytest.raises(ValidationError) as exc_info:
         Model()
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -148,7 +148,7 @@ def test_default_validators(field, value, result):
         with pytest.raises(ValidationError):
             CheckModel(**kwargs)
     else:
-        assert CheckModel(**kwargs).values[field] == result
+        assert CheckModel(**kwargs).values()[field] == result
 
 
 class DatetimeModel(BaseModel):
@@ -348,7 +348,7 @@ def test_tuple():
     m = ListDictTupleModel(d=(1, 2, '3'))
     assert m.a is None
     assert m.d == (1, 2, '3')
-    assert m.values == {'a': None, 'b': None, 'c': None, 'd': (1, 2, '3')}
+    assert m.values() == {'a': None, 'b': None, 'c': None, 'd': (1, 2, '3')}
     assert ListDictTupleModel(d='xyz').d == ('x', 'y', 'z')
     assert ListDictTupleModel(d=(i**2 for i in range(5))).d == (0, 1, 4, 9, 16)
     with pytest.raises(ValidationError) as exc_info:
@@ -376,5 +376,5 @@ class SetModel(BaseModel):
 
     m = SetModel(v=[1, 2, 3])
     assert m.v == {1, 2, 3}
-    assert m.values == {'v': {1, 2, 3}}
+    assert m.values() == {'v': {1, 2, 3}}
     assert SetModel(v={'a', 'b', 'c'}).v == {'a', 'b', 'c'}