From d3af83b9342457d8b24476baeb799f7506ff04f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Langa?= Date: Mon, 26 Jun 2023 18:35:53 +0000 Subject: Revert "GH-96145: Add AttrDict to JSON module for use with object_hook (#96146)" (#105948) This reverts commit 1f0eafa844bf5a380603d55e8d4b42d8c2a3439d. --- Doc/library/json.rst | 43 ------ Doc/whatsnew/3.12.rst | 8 -- Lib/json/__init__.py | 52 +------- Lib/test/test_json/__init__.py | 1 - Lib/test/test_json/test_attrdict.py | 145 --------------------- .../2023-06-20-23-18-45.gh-issue-96145.o5dTRM.rst | 1 + 6 files changed, 2 insertions(+), 248 deletions(-) delete mode 100644 Lib/test/test_json/test_attrdict.py create mode 100644 Misc/NEWS.d/next/Library/2023-06-20-23-18-45.gh-issue-96145.o5dTRM.rst diff --git a/Doc/library/json.rst b/Doc/library/json.rst index ef58dd0..5383614 100644 --- a/Doc/library/json.rst +++ b/Doc/library/json.rst @@ -9,11 +9,6 @@ **Source code:** :source:`Lib/json/__init__.py` -.. testsetup:: * - - import json - from json import AttrDict - -------------- `JSON (JavaScript Object Notation) `_, specified by @@ -548,44 +543,6 @@ Exceptions .. versionadded:: 3.5 -.. class:: AttrDict(**kwargs) - AttrDict(mapping, **kwargs) - AttrDict(iterable, **kwargs) - - Subclass of :class:`dict` that also supports attribute style dotted access. - - This class is intended for use with the :attr:`object_hook` in - :func:`json.load` and :func:`json.loads`: - - .. doctest:: - - >>> json_string = '{"mercury": 88, "venus": 225, "earth": 365, "mars": 687}' - >>> orbital_period = json.loads(json_string, object_hook=AttrDict) - >>> orbital_period['earth'] # Dict style lookup - 365 - >>> orbital_period.earth # Attribute style lookup - 365 - >>> orbital_period.keys() # All dict methods are present - dict_keys(['mercury', 'venus', 'earth', 'mars']) - - Attribute style access only works for keys that are valid attribute - names. In contrast, dictionary style access works for all keys. For - example, ``d.two words`` contains a space and is not syntactically - valid Python, so ``d["two words"]`` should be used instead. - - If a key has the same name as a dictionary method, then a dictionary - lookup finds the key and an attribute lookup finds the method: - - .. doctest:: - - >>> d = AttrDict(items=50) - >>> d['items'] # Lookup the key - 50 - >>> d.items() # Call the method - dict_items([('items', 50)]) - - .. versionadded:: 3.12 - Standard Compliance and Interoperability ---------------------------------------- diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index d8a2360..b6382e7 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -593,14 +593,6 @@ itertools tuples where the last batch may be shorter than the rest. (Contributed by Raymond Hettinger in :gh:`98363`.) -json ----- - -* Added :class:`json.AttrDict` for use with ``object_hook`` in :func:`json.load` - or :func:`json.loads`. This is a subclass of :class:`dict` that also supports - attribute style dotted access. - (Contributed by Raymond Hettinger in :gh:`96145`.) - math ---- diff --git a/Lib/json/__init__.py b/Lib/json/__init__.py index 256e76a..ed2c747 100644 --- a/Lib/json/__init__.py +++ b/Lib/json/__init__.py @@ -97,7 +97,7 @@ Using json.tool from the shell to validate and pretty-print:: """ __version__ = '2.0.9' __all__ = [ - 'dump', 'dumps', 'load', 'loads', 'AttrDict', + 'dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder', ] @@ -357,53 +357,3 @@ def loads(s, *, cls=None, object_hook=None, parse_float=None, if parse_constant is not None: kw['parse_constant'] = parse_constant return cls(**kw).decode(s) - -class AttrDict(dict): - """Dict like object that supports attribute style dotted access. - - This class is intended for use with the *object_hook* in json.loads(): - - >>> from json import loads, AttrDict - >>> json_string = '{"mercury": 88, "venus": 225, "earth": 365, "mars": 687}' - >>> orbital_period = loads(json_string, object_hook=AttrDict) - >>> orbital_period['earth'] # Dict style lookup - 365 - >>> orbital_period.earth # Attribute style lookup - 365 - >>> orbital_period.keys() # All dict methods are present - dict_keys(['mercury', 'venus', 'earth', 'mars']) - - Attribute style access only works for keys that are valid attribute names. - In contrast, dictionary style access works for all keys. - For example, ``d.two words`` contains a space and is not syntactically - valid Python, so ``d["two words"]`` should be used instead. - - If a key has the same name as dictionary method, then a dictionary - lookup finds the key and an attribute lookup finds the method: - - >>> d = AttrDict(items=50) - >>> d['items'] # Lookup the key - 50 - >>> d.items() # Call the method - dict_items([('items', 50)]) - - """ - __slots__ = () - - def __getattr__(self, attr): - try: - return self[attr] - except KeyError: - raise AttributeError(attr) from None - - def __setattr__(self, attr, value): - self[attr] = value - - def __delattr__(self, attr): - try: - del self[attr] - except KeyError: - raise AttributeError(attr) from None - - def __dir__(self): - return list(self) + dir(type(self)) diff --git a/Lib/test/test_json/__init__.py b/Lib/test/test_json/__init__.py index 37b2e0d..74b64ed 100644 --- a/Lib/test/test_json/__init__.py +++ b/Lib/test/test_json/__init__.py @@ -18,7 +18,6 @@ class PyTest(unittest.TestCase): json = pyjson loads = staticmethod(pyjson.loads) dumps = staticmethod(pyjson.dumps) - AttrDict = pyjson.AttrDict JSONDecodeError = staticmethod(pyjson.JSONDecodeError) @unittest.skipUnless(cjson, 'requires _json') diff --git a/Lib/test/test_json/test_attrdict.py b/Lib/test/test_json/test_attrdict.py deleted file mode 100644 index 143ea46..0000000 --- a/Lib/test/test_json/test_attrdict.py +++ /dev/null @@ -1,145 +0,0 @@ -from test.test_json import PyTest -import pickle -import sys -import unittest - -kepler_dict = { - "orbital_period": { - "mercury": 88, - "venus": 225, - "earth": 365, - "mars": 687, - "jupiter": 4331, - "saturn": 10_756, - "uranus": 30_687, - "neptune": 60_190, - }, - "dist_from_sun": { - "mercury": 58, - "venus": 108, - "earth": 150, - "mars": 228, - "jupiter": 778, - "saturn": 1_400, - "uranus": 2_900, - "neptune": 4_500, - } -} - -class TestAttrDict(PyTest): - - def test_dict_subclass(self): - self.assertTrue(issubclass(self.AttrDict, dict)) - - def test_slots(self): - d = self.AttrDict(x=1, y=2) - with self.assertRaises(TypeError): - vars(d) - - def test_constructor_signatures(self): - AttrDict = self.AttrDict - target = dict(x=1, y=2) - self.assertEqual(AttrDict(x=1, y=2), target) # kwargs - self.assertEqual(AttrDict(dict(x=1, y=2)), target) # mapping - self.assertEqual(AttrDict(dict(x=1, y=0), y=2), target) # mapping, kwargs - self.assertEqual(AttrDict([('x', 1), ('y', 2)]), target) # iterable - self.assertEqual(AttrDict([('x', 1), ('y', 0)], y=2), target) # iterable, kwargs - - def test_getattr(self): - d = self.AttrDict(x=1, y=2) - self.assertEqual(d.x, 1) - with self.assertRaises(AttributeError): - d.z - - def test_setattr(self): - d = self.AttrDict(x=1, y=2) - d.x = 3 - d.z = 5 - self.assertEqual(d, dict(x=3, y=2, z=5)) - - def test_delattr(self): - d = self.AttrDict(x=1, y=2) - del d.x - self.assertEqual(d, dict(y=2)) - with self.assertRaises(AttributeError): - del d.z - - def test_dir(self): - d = self.AttrDict(x=1, y=2) - self.assertTrue(set(dir(d)), set(dir(dict)).union({'x', 'y'})) - - def test_repr(self): - # This repr is doesn't round-trip. It matches a regular dict. - # That seems to be the norm for AttrDict recipes being used - # in the wild. Also it supports the design concept that an - # AttrDict is just like a regular dict but has optional - # attribute style lookup. - self.assertEqual(repr(self.AttrDict(x=1, y=2)), - repr(dict(x=1, y=2))) - - def test_overlapping_keys_and_methods(self): - d = self.AttrDict(items=50) - self.assertEqual(d['items'], 50) - self.assertEqual(d.items(), dict(d).items()) - - def test_invalid_attribute_names(self): - d = self.AttrDict({ - 'control': 'normal case', - 'class': 'keyword', - 'two words': 'contains space', - 'hypen-ate': 'contains a hyphen' - }) - self.assertEqual(d.control, dict(d)['control']) - self.assertEqual(d['class'], dict(d)['class']) - self.assertEqual(d['two words'], dict(d)['two words']) - self.assertEqual(d['hypen-ate'], dict(d)['hypen-ate']) - - def test_object_hook_use_case(self): - AttrDict = self.AttrDict - json_string = self.dumps(kepler_dict) - kepler_ad = self.loads(json_string, object_hook=AttrDict) - - self.assertEqual(kepler_ad, kepler_dict) # Match regular dict - self.assertIsInstance(kepler_ad, AttrDict) # Verify conversion - self.assertIsInstance(kepler_ad.orbital_period, AttrDict) # Nested - - # Exercise dotted lookups - self.assertEqual(kepler_ad.orbital_period, kepler_dict['orbital_period']) - self.assertEqual(kepler_ad.orbital_period.earth, - kepler_dict['orbital_period']['earth']) - self.assertEqual(kepler_ad['orbital_period'].earth, - kepler_dict['orbital_period']['earth']) - - # Dict style error handling and Attribute style error handling - with self.assertRaises(KeyError): - kepler_ad.orbital_period['pluto'] - with self.assertRaises(AttributeError): - kepler_ad.orbital_period.Pluto - - # Order preservation - self.assertEqual(list(kepler_ad.items()), list(kepler_dict.items())) - self.assertEqual(list(kepler_ad.orbital_period.items()), - list(kepler_dict['orbital_period'].items())) - - # Round trip - self.assertEqual(self.dumps(kepler_ad), json_string) - - def test_pickle(self): - AttrDict = self.AttrDict - json_string = self.dumps(kepler_dict) - kepler_ad = self.loads(json_string, object_hook=AttrDict) - - # Pickling requires the cached module to be the real module - cached_module = sys.modules.get('json') - sys.modules['json'] = self.json - try: - for protocol in range(pickle.HIGHEST_PROTOCOL + 1): - kepler_ad2 = pickle.loads(pickle.dumps(kepler_ad, protocol)) - self.assertEqual(kepler_ad2, kepler_ad) - self.assertEqual(type(kepler_ad2), AttrDict) - finally: - sys.modules['json'] = cached_module - - -if __name__ == "__main__": - unittest.main() diff --git a/Misc/NEWS.d/next/Library/2023-06-20-23-18-45.gh-issue-96145.o5dTRM.rst b/Misc/NEWS.d/next/Library/2023-06-20-23-18-45.gh-issue-96145.o5dTRM.rst new file mode 100644 index 0000000..f4fb0e4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-20-23-18-45.gh-issue-96145.o5dTRM.rst @@ -0,0 +1 @@ +Reverted addition of ``json.AttrDict``. -- cgit v0.12