From 42bc4c92920a4073ffb02ec503ea3889d5c6768d Mon Sep 17 00:00:00 2001 From: Dave Shawley Date: Fri, 5 Nov 2021 07:20:10 -0400 Subject: [PATCH] Add support for encoding namedtuples as tuples --- docs/history.rst | 1 + sprockets/mixins/mediatype/transcoders.py | 3 +++ tests.py | 21 +++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/docs/history.rst b/docs/history.rst index 9e28945..6495ae7 100644 --- a/docs/history.rst +++ b/docs/history.rst @@ -9,6 +9,7 @@ Version History - Add support for encoding :class:`ipaddress.IPv4Address` and :class:`ipaddress.IPv6Address` - Add support for encoding :class:`pathlib.Path` - Add support for encoding :class:`array.array` +- Add support for encoding :class:`collections.namedtuple` instances as tuples - Add type annotations (see :ref:`type-info`) - Return a "406 Not Acceptable" if the :http:header:`Accept` header values cannot be matched and there is no default content type configured diff --git a/sprockets/mixins/mediatype/transcoders.py b/sprockets/mixins/mediatype/transcoders.py index 0f8f0a7..e7d0717 100644 --- a/sprockets/mixins/mediatype/transcoders.py +++ b/sprockets/mixins/mediatype/transcoders.py @@ -506,6 +506,9 @@ class FormUrlEncodedTranscoder: return ''.join(char_map[c] for c in datum) elif isinstance(datum, type_info.SupportsIsoFormat): str_repr = datum.isoformat() + # the following check is for namedtuples + elif isinstance(datum, tuple) and datum.__class__ is not tuple: + str_repr = str(tuple(datum)) else: str_repr = str(datum) diff --git a/tests.py b/tests.py index c467de3..086aebc 100644 --- a/tests.py +++ b/tests.py @@ -1,5 +1,6 @@ import array import base64 +import collections import dataclasses import datetime import decimal @@ -403,6 +404,13 @@ class JSONTranscoderTests(unittest.TestCase): self.assertEqual(dumped, '{"array":[%s]}' % (','.join(str(x) for x in a), )) + def test_that_namedtuples_are_handled_as_tuples(self): + Point = collections.namedtuple('Point', ['x', 'y']) + datum = Point(3, 4) + dumped = self.transcoder.dumps({'point': datum}) + expected = json.dumps({'point': [x for x in datum]}) + self.assertDictEqual(json.loads(expected), json.loads(dumped)) + class ContentSettingsTests(unittest.TestCase): def test_that_handler_listed_in_available_content_types(self): @@ -667,6 +675,12 @@ class MsgPackTranscoderTests(unittest.TestCase): expected += pack_string(ch) self.assertEqual(expected, self.transcoder.packb(a)) + def test_that_namedtuples_are_handled_as_tuples(self): + Point = collections.namedtuple('Point', ['x', 'y']) + datum = Point(3, 4) + expected = struct.pack('>BBB', 0x90 | 2, 3, 4) + self.assertEqual(expected, self.transcoder.packb(datum)) + class FormUrlEncodingTranscoderTests(unittest.TestCase): transcoder: type_info.Transcoder @@ -851,3 +865,10 @@ class FormUrlEncodingTranscoderTests(unittest.TestCase): _, expected = self.transcoder.to_bytes({'array': a.tolist()}) _, result = self.transcoder.to_bytes({'array': a}) self.assertEqual(expected, result) + + def test_that_namedtuples_are_handled_as_tuples(self): + Point = collections.namedtuple('Point', ['x', 'y']) + datum = Point(3, 4) + _, expected = self.transcoder.to_bytes({'point': (3, 4)}) + _, result = self.transcoder.to_bytes({'point': datum}) + self.assertEqual(expected, result)