2929from typing import Any , Callable , Set
3030from warnings import WarningMessage , catch_warnings
3131
32+ from typing_extensions import deprecated
33+
3234
3335def fail (msg = None ):
3436 """Raise an AssertionError with the given message.
@@ -1354,8 +1356,8 @@ def assert_json_subset(first, second):
13541356 ...
13551357 json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
13561358
1357- In objects, the special name `Exists` can be used to check for the
1358- existence or non-existence of a specific key:
1359+ In objects, the special classes `Present` and `Absent` can be used to
1360+ check for the presence or absence of a specific key:
13591361
13601362 >>> assert_json_subset({Exists("foo"): True}, '{"foo": "bar"}')
13611363 >>> assert_json_subset({Exists("foo"): True}, '{}')
@@ -1401,6 +1403,8 @@ def assert_(self):
14011403 self ._assert_dicts_equal ()
14021404 elif isinstance (self ._expected , list ):
14031405 self ._assert_arrays_equal ()
1406+ elif _is_present (self ._expected ):
1407+ pass
14041408 else :
14051409 self ._assert_fundamental_values_equal ()
14061410
@@ -1413,6 +1417,8 @@ def _types_differ(self):
14131417 return self ._actual is not None
14141418 elif isinstance (self ._expected , (int , float )):
14151419 return not isinstance (self ._actual , (int , float ))
1420+ elif _is_present (self ._expected ):
1421+ return False
14161422 for type_ in [bool , str , _Str , list , dict ]:
14171423 if isinstance (self ._expected , type_ ):
14181424 return not isinstance (self ._actual , type_ )
@@ -1436,24 +1442,29 @@ def _assert_all_expected_keys_in_actual_dict(self) -> None:
14361442
14371443 def _assert_no_wrong_keys (self ) -> None :
14381444 for name in self ._expected :
1445+ if isinstance (name , str ) and _is_absent (self ._expected [name ]):
1446+ if name in self ._actual :
1447+ self ._raise_assertion_error (
1448+ f"spurious member '{ name } ' in object {{path}}"
1449+ )
14391450 if isinstance (name , Exists ) and not self ._expected [name ]:
14401451 if name .member_name in self ._actual :
14411452 self ._raise_assertion_error (
1442- f"spurious member '{ name .member_name } ' in "
1443- f"object {{path}}"
1453+ f"spurious member '{ name .member_name } ' in object {{path}}"
14441454 )
14451455
14461456 def _assert_dict_values_equal (self ) -> None :
14471457 for name in self ._expected :
1448- if isinstance (name , str ):
1458+ if isinstance (name , str ) and not _is_absent ( self . _expected [ name ]) :
14491459 self ._assert_json_value_equals_with_item (name )
14501460
14511461 @property
14521462 def _expected_key_names (self ) -> Set [str ]:
14531463 keys : Set [str ] = set ()
14541464 for k in self ._expected .keys ():
14551465 if isinstance (k , str ):
1456- keys .add (k )
1466+ if not _is_absent (self ._expected [k ]):
1467+ keys .add (k )
14571468 elif isinstance (k , Exists ) and self ._expected [k ]:
14581469 keys .add (k .member_name )
14591470 return keys
@@ -1523,6 +1534,23 @@ def append(self, item):
15231534 return _JSONPath ("{0}[{1}]" .format (self ._path , repr (item )))
15241535
15251536
1537+ class Present :
1538+ """Helper class for presence checks in assert_json_subset()."""
1539+
1540+
1541+ def _is_present (o : object ) -> bool :
1542+ return o is Present or isinstance (o , Present )
1543+
1544+
1545+ class Absent :
1546+ """Helper class for absence checks in assert_json_subset()."""
1547+
1548+
1549+ def _is_absent (o : object ) -> bool :
1550+ return o is Absent or isinstance (o , Absent )
1551+
1552+
1553+ @deprecated ("Use Present and Absent instead." )
15261554class Exists :
15271555 """Helper class for existence checks in assert_json_subset()."""
15281556
0 commit comments