diff --git a/benedict/dicts/io/io_dict.py b/benedict/dicts/io/io_dict.py index 3ff52ee..7064cdc 100644 --- a/benedict/dicts/io/io_dict.py +++ b/benedict/dicts/io/io_dict.py @@ -83,6 +83,16 @@ class IODict(dict): kwargs['columns_row'] = columns_row return cls(IODict._decode(s, 'csv', **kwargs)) + @classmethod + def from_pickle(cls, s, **kwargs): + """ + Load and decode a pickle encoded in Base64 format data from url, filepath or data-string. + Decoder specific options can be passed using kwargs: + https://docs.python.org/3/library/pickle.html + Return a new dict instance. A ValueError is raised in case of failure. + """ + return cls(IODict._decode(s, 'pickle', **kwargs)) + @classmethod def from_json(cls, s, **kwargs): """ @@ -155,6 +165,17 @@ class IODict(dict): kwargs['columns_row'] = columns_row return IODict._encode(self[key], 'csv', **kwargs) + def to_pickle(self, **kwargs): + """ + Encode the current dict instance as pickle (encoded in Base64). + The pickle highest protocol is used by default: protocol=pickle.HIGHEST_PROTOCOL + Encoder specific options can be passed using kwargs: + https://docs.python.org/3/library/pickle.html + Return the encoded string and optionally save it at 'filepath'. + A ValueError is raised in case of failure. + """ + return IODict._encode(self, 'pickle', **kwargs) + def to_json(self, **kwargs): """ Encode the current dict instance in JSON format. diff --git a/tests/dicts/io/input/invalid-content.pickle b/tests/dicts/io/input/invalid-content.pickle new file mode 100644 index 0000000..7728a3a --- /dev/null +++ b/tests/dicts/io/input/invalid-content.pickle @@ -0,0 +1,2 @@ +Lorem ipsum consectetur sint id aute officia sed excepteur consectetur labore laboris dolore in labore consequat ut in eu ut deserunt. +Elit aliqua velit aliquip voluptate consequat reprehenderit occaecat dolor ut esse aute laboris cillum fugiat esse est laborum. \ No newline at end of file diff --git a/tests/dicts/io/input/valid-content.pickle b/tests/dicts/io/input/valid-content.pickle new file mode 100644 index 0000000..177f84a --- /dev/null +++ b/tests/dicts/io/input/valid-content.pickle @@ -0,0 +1 @@ +gASVNAAAAAAAAAB9lIwEZGF0ZZSMCGRhdGV0aW1llIwIZGF0ZXRpbWWUk5RDCgfBBAMAAAAAAACUhZRSlHMu \ No newline at end of file diff --git a/tests/dicts/io/test_io_dict_pickle.py b/tests/dicts/io/test_io_dict_pickle.py new file mode 100644 index 0000000..96cc23d --- /dev/null +++ b/tests/dicts/io/test_io_dict_pickle.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +from benedict.dicts.io import IODict + +from .test_io_dict import io_dict_test_case + +import datetime as dt + + +class io_dict_pickle_test_case(io_dict_test_case): + + @staticmethod + def _get_pickle_decoded(): + return { + 'date': dt.datetime(year=1985, month=4, day=3), + } + + @staticmethod + def _get_pickle_encoded(): + return 'gASVNAAAAAAAAAB9lIwEZGF0ZZSMCGRhdGV0aW1llIwIZGF0ZXRpbWWUk5RDCgfBBAMAAAAAAACUhZRSlHMu' + + def test_from_pickle_with_valid_data(self): + j = self._get_pickle_encoded() + r = self._get_pickle_decoded() + # static method + d = IODict.from_pickle(j) + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, r) + # constructor + d = IODict(j, format='pickle') + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, r) + + def test_from_pickle_with_valid_data_without_padding(self): + j = self._get_pickle_encoded() + r = self._get_pickle_decoded() + # static method + d = IODict.from_pickle(j) + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, r) + # constructor + d = IODict(j, format='pickle') + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, r) + + def test_from_pickle_with_invalid_data(self): + j = 'Lorem ipsum est in ea occaecat nisi officia.' + # static method + with self.assertRaises(ValueError): + IODict.from_pickle(j) + # constructor + with self.assertRaises(ValueError): + IODict(j, format='pickle') + + def test_from_pickle_with_valid_file_valid_content(self): + filepath = self.input_path('valid-content.pickle') + # static method + d = IODict.from_pickle(filepath) + self.assertTrue(isinstance(d, dict)) + # constructor + d = IODict(filepath, format='pickle') + self.assertTrue(isinstance(d, dict)) + + def test_from_pickle_with_valid_file_valid_content_invalid_format(self): + filepath = self.input_path('valid-content.json') + with self.assertRaises(ValueError): + IODict.from_pickle(filepath) + filepath = self.input_path('valid-content.qs') + with self.assertRaises(ValueError): + IODict.from_pickle(filepath) + filepath = self.input_path('valid-content.toml') + with self.assertRaises(ValueError): + IODict.from_pickle(filepath) + filepath = self.input_path('valid-content.xml') + with self.assertRaises(ValueError): + IODict.from_pickle(filepath) + filepath = self.input_path('valid-content.yml') + with self.assertRaises(ValueError): + IODict.from_pickle(filepath) + + def test_from_pickle_with_valid_file_invalid_content(self): + filepath = self.input_path('invalid-content.pickle') + # static method + with self.assertRaises(ValueError): + IODict.from_pickle(filepath) + # constructor + with self.assertRaises(ValueError): + IODict(filepath, format='pickle') + + def test_from_pickle_with_invalid_file(self): + filepath = self.input_path('invalid-file.pickle') + # static method + with self.assertRaises(ValueError): + IODict.from_pickle(filepath) + # constructor + with self.assertRaises(ValueError): + IODict(filepath, format='pickle') + + def test_from_pickle_with_valid_url_valid_content(self): + url = self.input_url('valid-content.pickle') + # static method + d = IODict.from_pickle(url) + self.assertTrue(isinstance(d, dict)) + # constructor + d = IODict(url, format='pickle') + self.assertTrue(isinstance(d, dict)) + + def test_from_pickle_with_valid_url_invalid_content(self): + url = 'https://github.com/fabiocaccamo/python-benedict' + # static method + with self.assertRaises(ValueError): + IODict.from_pickle(url) + # constructor + with self.assertRaises(ValueError): + IODict(url, format='pickle') + + def test_from_pickle_with_invalid_url(self): + url = 'https://github.com/fabiocaccamo/python-benedict-invalid' + # static method + with self.assertRaises(ValueError): + IODict.from_pickle(url) + # constructor + with self.assertRaises(ValueError): + IODict(url, format='pickle') + + def test_to_pickle(self): + d = IODict(self._get_pickle_decoded()) + s = d.to_pickle() + self.assertEqual(IODict.from_pickle(s), self._get_pickle_decoded()) + + def test_to_pickle_file(self): + d = IODict({ + 'date': self._get_pickle_decoded(), + }) + filepath = self.output_path('test_to_pickle_file.pickle') + d.to_pickle(filepath=filepath) + self.assertFileExists(filepath) + self.assertEqual(d, IODict.from_pickle(filepath))