diff --git a/benedict/dicts/__init__.py b/benedict/dicts/__init__.py index a00a5c3..0804eab 100644 --- a/benedict/dicts/__init__.py +++ b/benedict/dicts/__init__.py @@ -52,8 +52,13 @@ class benedict(IODict, KeypathDict, ParseDict): @staticmethod @benediction - def from_base64(s, format='json', **kwargs): - return IODict.from_base64(s, format, **kwargs) + def from_base64(s, subformat='json', encoding='utf-8', **kwargs): + return IODict.from_base64(s, subformat=subformat, encoding=encoding, **kwargs) + + @staticmethod + @benediction + def from_csv(s, columns=None, columns_row=True, **kwargs): + return IODict.from_csv(s, columns=columns, columns_row=columns_row, **kwargs) @staticmethod @benediction diff --git a/benedict/dicts/io.py b/benedict/dicts/io.py index ade8b31..73877e8 100644 --- a/benedict/dicts/io.py +++ b/benedict/dicts/io.py @@ -8,30 +8,34 @@ from six import string_types, text_type class IODict(dict): def __init__(self, *args, **kwargs): - # if first argument is data-string, - # try to decode it using all decoders. + # if first argument is data-string try to decode it. + # use 'format' kwarg to specify the decoder to use, default 'json'. if len(args) and isinstance(args[0], string_types): - d = IODict._from_any_data_string(args[0], **kwargs) + s = args[0] + format = kwargs.pop('format', 'json').lower() + if format in ['b64', 'base64']: + kwargs.setdefault('subformat', 'json') + # decode data-string and initialize with dict data. + d = IODict._decode(s, format, **kwargs) if d and isinstance(d, dict): - args = list(args) - args[0] = d - args = tuple(args) + super(IODict, self).__init__(d) else: raise ValueError('Invalid string data input.') - super(IODict, self).__init__(*args, **kwargs) + else: + super(IODict, self).__init__(*args, **kwargs) @staticmethod - def _decode(s, decoder, **kwargs): + def _decode(s, format, **kwargs): d = None try: content = io_util.read_content(s) - # decode content using the given decoder - data = decoder(content, **kwargs) + # decode content using the given format + data = io_util.decode(content, format, **kwargs) if isinstance(data, dict): d = data elif isinstance(data, list): # force list to dict - d = { 'values':data } + d = { 'values': data } else: raise ValueError( 'Invalid data type: {}, expected dict or list.'.format(type(data))) @@ -41,90 +45,66 @@ class IODict(dict): return d @staticmethod - def _encode(d, encoder, filepath=None, **kwargs): - s = encoder(d, **kwargs) + def _encode(d, format, **kwargs): + filepath = kwargs.pop('filepath', None) + s = io_util.encode(d, format, **kwargs) if filepath: io_util.write_file(filepath, s) return s @staticmethod - def _from_any_data_string(s, **kwargs): - funcs = [ - IODict.from_base64, - IODict.from_json, - IODict.from_query_string, - IODict.from_toml, - IODict.from_xml, - IODict.from_yaml, - ] - for f in funcs: - try: - options = kwargs.copy() - d = f(s, **options) - return d - except ValueError: - pass + def from_base64(s, subformat='json', encoding='utf-8', **kwargs): + kwargs['subformat'] = subformat + kwargs['encoding'] = encoding + return IODict._decode(s, 'base64', **kwargs) @staticmethod - def from_base64(s, format='json', encoding='utf-8', **kwargs): - kwargs['format'] = format - kwargs['encoding'] = encoding - return IODict._decode(s, - decoder=io_util.decode_base64, **kwargs) + def from_csv(s, columns=None, columns_row=True, **kwargs): + kwargs['columns'] = columns + kwargs['columns_row'] = columns_row + return IODict._decode(s, 'csv', **kwargs) @staticmethod def from_json(s, **kwargs): - return IODict._decode(s, - decoder=io_util.decode_json, **kwargs) + return IODict._decode(s, 'json', **kwargs) @staticmethod def from_query_string(s, **kwargs): - return IODict._decode(s, - decoder=io_util.decode_query_string, **kwargs) + return IODict._decode(s, 'query_string', **kwargs) @staticmethod def from_toml(s, **kwargs): - return IODict._decode(s, - decoder=io_util.decode_toml, **kwargs) + return IODict._decode(s, 'toml', **kwargs) @staticmethod def from_xml(s, **kwargs): - return IODict._decode(s, - decoder=io_util.decode_xml, **kwargs) + return IODict._decode(s, 'xml', **kwargs) @staticmethod def from_yaml(s, **kwargs): - return IODict._decode(s, - decoder=io_util.decode_yaml, **kwargs) + return IODict._decode(s, 'yaml', **kwargs) - def to_base64(self, filepath=None, format='json', encoding='utf-8', **kwargs): - kwargs['format'] = format + def to_base64(self, subformat='json', encoding='utf-8', **kwargs): + kwargs['subformat'] = subformat kwargs['encoding'] = encoding - return IODict._encode(self, - encoder=io_util.encode_base64, - filepath=filepath, **kwargs) + return IODict._encode(self, 'base64', **kwargs) - def to_json(self, filepath=None, **kwargs): - return IODict._encode(self, - encoder=io_util.encode_json, - filepath=filepath, **kwargs) + def to_csv(self, key='values', columns=None, columns_row=True, **kwargs): + kwargs['columns'] = columns + kwargs['columns_row'] = columns_row + return IODict._encode(self[key], 'csv', **kwargs) - def to_query_string(self, filepath=None, **kwargs): - return IODict._encode(self, - encoder=io_util.encode_query_string, - filepath=filepath, **kwargs) + def to_json(self, **kwargs): + return IODict._encode(self, 'json', **kwargs) - def to_toml(self, filepath=None, **kwargs): - return IODict._encode(self, - encoder=io_util.encode_toml, - filepath=filepath, **kwargs) + def to_query_string(self, **kwargs): + return IODict._encode(self, 'query_string', **kwargs) - def to_xml(self, filepath=None, **kwargs): - return IODict._encode(self, - encoder=io_util.encode_xml, - filepath=filepath, **kwargs) + def to_toml(self, **kwargs): + return IODict._encode(self, 'toml', **kwargs) - def to_yaml(self, filepath=None, **kwargs): - return IODict._encode(self, - encoder=io_util.encode_yaml, - filepath=filepath, **kwargs) + def to_xml(self, **kwargs): + return IODict._encode(self, 'xml', **kwargs) + + def to_yaml(self, **kwargs): + return IODict._encode(self, 'yaml', **kwargs) diff --git a/benedict/utils/io_util.py b/benedict/utils/io_util.py index cf69164..fc6027c 100644 --- a/benedict/utils/io_util.py +++ b/benedict/utils/io_util.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from six import binary_type, string_types, StringIO +from slugify import slugify import base64 import csv @@ -27,6 +28,16 @@ except ImportError: from urlparse import parse_qs +def decode(s, format, **kwargs): + decode_func = _get_format_decoder(format) + if decode_func: + decode_opts = kwargs.copy() + data = decode_func(s.strip(), **decode_opts) + return data + else: + raise ValueError('Invalid format: {}.'.format(format)) + + def decode_base64(s, **kwargs): # fix urlencoded chars s = unquote(s) @@ -35,18 +46,12 @@ def decode_base64(s, **kwargs): if m != 0: s += '=' * (4 - m) data = base64.b64decode(s) - format = kwargs.pop('format', None) - encoding = kwargs.pop('encoding', 'utf-8' if format else None) + subformat = kwargs.pop('subformat', None) + encoding = kwargs.pop('encoding', 'utf-8' if subformat else None) if encoding: data = data.decode(encoding) - if format: - decoders = { - 'json': decode_json, - 'toml': decode_toml, - 'yaml': decode_yaml, - 'xml': decode_xml, - } - decode_func = decoders.get(format.lower(), '') + if subformat: + decode_func = _get_format_decoder(subformat) if decode_func: data = decode_func(data, **kwargs) return data @@ -109,18 +114,21 @@ def decode_yaml(s, **kwargs): return data +def encode(d, format, **kwargs): + encode_func = _get_format_encoder(format) + if encode_func: + s = encode_func(d, **kwargs) + return s + else: + raise ValueError('Invalid format: {}.'.format(format)) + + def encode_base64(d, **kwargs): data = d - format = kwargs.pop('format', None) - encoding = kwargs.pop('encoding', 'utf-8' if format else None) - if not isinstance(data, string_types) and format: - encoders = { - 'json': encode_json, - 'toml': encode_toml, - 'yaml': encode_yaml, - 'xml': encode_xml, - } - encode_func = encoders.get(format.lower(), '') + subformat = kwargs.pop('subformat', None) + encoding = kwargs.pop('encoding', 'utf-8' if subformat else None) + if not isinstance(data, string_types) and subformat: + encode_func = _get_format_encoder(subformat) if encode_func: data = encode_func(data, **kwargs) if isinstance(data, string_types) and encoding: @@ -225,3 +233,60 @@ def write_file(filepath, content): handler.write(content) handler.close() return True + + +_formats = { + 'b64': { + 'decoder': decode_base64, + 'encoder': encode_base64, + }, + 'base64': { + 'decoder': decode_base64, + 'encoder': encode_base64, + }, + 'csv': { + 'decoder': decode_csv, + 'encoder': encode_csv, + }, + 'json': { + 'decoder': decode_json, + 'encoder': encode_json, + }, + 'qs': { + 'decoder': decode_query_string, + 'encoder': encode_query_string, + }, + 'query_string': { + 'decoder': decode_query_string, + 'encoder': encode_query_string, + }, + 'toml': { + 'decoder': decode_toml, + 'encoder': encode_toml, + }, + 'yaml': { + 'decoder': decode_yaml, + 'encoder': encode_yaml, + }, + 'yml': { + 'decoder': decode_yaml, + 'encoder': encode_yaml, + }, + 'xml': { + 'decoder': decode_xml, + 'encoder': encode_xml, + }, +} + + +def _get_format(format): + return _formats.get( + slugify(format, separator='_'), {}) + + +def _get_format_decoder(format): + return _get_format(format).get('decoder', None) + + +def _get_format_encoder(format): + return _get_format(format).get('encoder', None) diff --git a/tests/input/invalid-content.csv b/tests/input/invalid-content.csv new file mode 100644 index 0000000..7728a3a --- /dev/null +++ b/tests/input/invalid-content.csv @@ -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/input/valid-content.csv b/tests/input/valid-content.csv new file mode 100644 index 0000000..f03ec76 --- /dev/null +++ b/tests/input/valid-content.csv @@ -0,0 +1,5 @@ +id,name,age,height,weight +1,Alice,20,62,120.6 +2,Freddie,21,74,190.6 +3,Bob,17,68,120.0 +4,François,32,75,110.05 \ No newline at end of file diff --git a/tests/test_benedict.py b/tests/test_benedict.py index ae78cd7..46f6cc7 100644 --- a/tests/test_benedict.py +++ b/tests/test_benedict.py @@ -494,10 +494,42 @@ class BenedictTestCase(unittest.TestCase): d = benedict.from_base64(j) self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) - # constructor - d = benedict(j) + # static method with subformat + d = benedict.from_base64(j, subformat='json') self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) + # constructor + d = benedict(j, format='base64') + self.assertTrue(isinstance(d, benedict)) + self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) + # constructor with subformat + d = benedict(j, format='base64', subformat='json') + self.assertTrue(isinstance(d, benedict)) + self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) + + def test_from_csv_with_valid_data(self): + s = """id,name,age,height,weight +1,Alice,20,62,120.6 +2,Freddie,21,74,190.6 +3,Bob,17,68,120.0 +4,François,32,75,110.05 +""" + r = { + 'values': [ + { 'id':'1', 'name':'Alice', 'age':'20', 'height':'62', 'weight':'120.6', }, + { 'id':'2', 'name':'Freddie', 'age':'21', 'height':'74', 'weight':'190.6', }, + { 'id':'3', 'name':'Bob', 'age':'17', 'height':'68', 'weight':'120.0', }, + { 'id':'4', 'name':'François', 'age':'32', 'height':'75', 'weight':'110.05', }, + ], + } + # static method + d = benedict.from_csv(s) + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, r) + # constructor + d = benedict(s, format='csv') + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, r) def test_from_json(self): j = '{"a": 1, "b": 2, "c": 3}' @@ -506,7 +538,7 @@ class BenedictTestCase(unittest.TestCase): self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) # constructor - d = benedict(j) + d = benedict(j, format='json') self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) @@ -518,7 +550,7 @@ class BenedictTestCase(unittest.TestCase): self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, r) # constructor - d = benedict(s) + d = benedict(s, format='query-string') self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, r) @@ -535,42 +567,43 @@ class BenedictTestCase(unittest.TestCase): self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },}) # constructor - d = benedict(j) + d = benedict(j, format='toml') self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },}) def test_from_xml(self): - j = """ - - 1 - - 3 - 4 - - - """ + j = """ + + + 1 + + 3 + 4 + + +""" # static method d = benedict.from_xml(j) self.assertTrue(isinstance(d, benedict)) self.assertEqual(d.get('root'), { 'a':'1', 'b':{ 'c':'3', 'd':'4' },}) # constructor - d = benedict(j) + d = benedict(j, format='xml') self.assertTrue(isinstance(d, benedict)) self.assertEqual(d.get('root'), { 'a':'1', 'b':{ 'c':'3', 'd':'4' },}) def test_from_yaml(self): j = """ - a: 1 - b: - c: 3 - d: 4 - """ +a: 1 +b: + c: 3 + d: 4 +""" # static method d = benedict.from_yaml(j) self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },}) # constructor - d = benedict(j) + d = benedict(j, format='yaml') self.assertTrue(isinstance(d, benedict)) self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },}) diff --git a/tests/test_io_dict.py b/tests/test_io_dict.py index 89cd34f..23be4a8 100644 --- a/tests/test_io_dict.py +++ b/tests/test_io_dict.py @@ -33,7 +33,11 @@ class IODictTestCase(unittest.TestCase): self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) # constructor - d = IODict(j) + d = IODict(j, format='base64') + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) + # constructor with subformat + d = IODict(j, format='base64', subformat='json') self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) @@ -46,7 +50,7 @@ class IODictTestCase(unittest.TestCase): self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, 'd': 4}) # constructor - d = IODict(j) + d = IODict(j, format='base64') self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, 'd': 4}) @@ -57,7 +61,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_base64(j) # constructor with self.assertRaises(ValueError): - IODict(j) + IODict(j, format='base64') def test_from_base64_with_valid_file_valid_content(self): filepath = self.input_path('valid-content.base64') @@ -65,7 +69,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_base64(filepath) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(filepath) + d = IODict(filepath, format='base64') self.assertTrue(isinstance(d, dict)) def test_from_base64_with_valid_file_valid_content_invalid_format(self): @@ -92,7 +96,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_base64(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='base64') def test_from_base64_with_invalid_file(self): filepath = self.input_path('invalid-file.base64') @@ -101,7 +105,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_base64(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='base64') def test_from_base64_with_valid_url_valid_content(self): url = 'https://raw.githubusercontent.com/fabiocaccamo/python-benedict/master/tests/input/valid-content.base64' @@ -109,7 +113,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_base64(url) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(url) + d = IODict(url, format='base64') self.assertTrue(isinstance(d, dict)) def test_from_base64_with_valid_url_invalid_content(self): @@ -119,7 +123,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_base64(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='base64') def test_from_base64_with_invalid_url(self): url = 'https://github.com/fabiocaccamo/python-benedict-invalid' @@ -128,7 +132,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_base64(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='base64') def test_to_base64(self): d = IODict({ @@ -150,6 +154,210 @@ class IODictTestCase(unittest.TestCase): self.assertTrue(d, os.path.isfile(filepath)) self.assertEqual(d, IODict.from_base64(filepath)) +# CSV + + def test_from_csv_with_valid_data(self): + s = """id,name,age,height,weight +1,Alice,20,62,120.6 +2,Freddie,21,74,190.6 +3,Bob,17,68,120.0 +4,François,32,75,110.05 +""" + r = { + 'values': [ + { 'id':'1', 'name':'Alice', 'age':'20', 'height':'62', 'weight':'120.6', }, + { 'id':'2', 'name':'Freddie', 'age':'21', 'height':'74', 'weight':'190.6', }, + { 'id':'3', 'name':'Bob', 'age':'17', 'height':'68', 'weight':'120.0', }, + { 'id':'4', 'name':'François', 'age':'32', 'height':'75', 'weight':'110.05', }, + ], + } + # static method + d = IODict.from_csv(s) + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, r) + # constructor + d = IODict(s, format='csv') + self.assertTrue(isinstance(d, dict)) + self.assertEqual(d, r) + + # def test_from_csv_with_invalid_data(self): + # s = 'Lorem ipsum est in ea occaecat nisi officia.' + # # static method + # with self.assertRaises(ValueError): + # print(IODict.from_csv(s)) + # # constructor + # with self.assertRaises(ValueError): + # IODict(s, format='csv') + + def test_from_csv_with_valid_file_valid_content(self): + filepath = self.input_path('valid-content.csv') + # static method + d = IODict.from_csv(filepath) + self.assertTrue(isinstance(d, dict)) + # constructor + d = IODict(filepath, format='csv') + self.assertTrue(isinstance(d, dict)) + + # def test_from_csv_with_valid_file_valid_content_invalid_format(self): + # filepath = self.input_path('valid-content.base64') + # with self.assertRaises(ValueError): + # IODict.from_csv(filepath) + # filepath = self.input_path('valid-content.qs') + # with self.assertRaises(ValueError): + # IODict.from_csv(filepath) + # filepath = self.input_path('valid-content.toml') + # with self.assertRaises(ValueError): + # IODict.from_csv(filepath) + # filepath = self.input_path('valid-content.xml') + # with self.assertRaises(ValueError): + # IODict.from_csv(filepath) + # filepath = self.input_path('valid-content.yml') + # with self.assertRaises(ValueError): + # IODict.from_csv(filepath) + + # def test_from_csv_with_valid_file_invalid_content(self): + # filepath = self.input_path('invalid-content.csv') + # # static method + # with self.assertRaises(ValueError): + # IODict.from_csv(filepath) + # # constructor + # with self.assertRaises(ValueError): + # IODict(filepath, format='csv') + + # def test_from_csv_with_invalid_file(self): + # filepath = self.input_path('invalid-file.csv') + # # static method + # with self.assertRaises(ValueError): + # IODict.from_csv(filepath) + # # constructor + # with self.assertRaises(ValueError): + # IODict(filepath, format='csv') + + # def test_from_csv_with_valid_url_valid_content(self): + # url = 'https://raw.githubusercontent.com/fabiocaccamo/python-benedict/master/tests/input/valid-content.csv' + # # static method + # d = IODict.from_csv(url) + # self.assertTrue(isinstance(d, dict)) + # # constructor + # d = IODict(url, format='csv') + # self.assertTrue(isinstance(d, dict)) + + # def test_from_csv_with_valid_url_invalid_content(self): + # url = 'https://github.com/fabiocaccamo/python-benedict' + # # static method + # with self.assertRaises(ValueError): + # IODict.from_csv(url) + # # constructor + # with self.assertRaises(ValueError): + # IODict(url, format='csv') + + def test_from_csv_with_invalid_url(self): + url = 'https://github.com/fabiocaccamo/python-benedict-invalid' + # static method + with self.assertRaises(ValueError): + IODict.from_csv(url) + # constructor + with self.assertRaises(ValueError): + IODict(url, format='csv') + + def test_to_csv(self): + d = IODict({ + 'values': [ + { 'id':'1', 'name':'Alice', 'age':'20', 'height':'62', 'weight':'120.6', }, + { 'id':'2', 'name':'Freddie', 'age':'21', 'height':'74', 'weight':'190.6', }, + { 'id':'3', 'name':'Bob', 'age':'17', 'height':'68', 'weight':'120.0', }, + { 'id':'4', 'name':'François', 'age':'32', 'height':'75', 'weight':'110.05', }, + ], + }) + s = d.to_csv() + r = """age,height,id,name,weight +20,62,1,Alice,120.6 +21,74,2,Freddie,190.6 +17,68,3,Bob,120.0 +32,75,4,François,110.05 +""" + self.assertEqual(s, r) + + def test_to_csv_with_custom_columns(self): + d = IODict({ + 'values': [ + { 'id':'1', 'name':'Alice', 'age':'20', 'height':'62', 'weight':'120.6', }, + { 'id':'2', 'name':'Freddie', 'age':'21', 'height':'74', 'weight':'190.6', }, + { 'id':'3', 'name':'Bob', 'age':'17', 'height':'68', 'weight':'120.0', }, + { 'id':'4', 'name':'François', 'age':'32', 'height':'75', 'weight':'110.05', }, + ], + }) + s = d.to_csv(key='values', columns=['id', 'name', 'family_name', 'age', 'height', 'gender', 'weight']) + r = """id,name,family_name,age,height,gender,weight +1,Alice,,20,62,,120.6 +2,Freddie,,21,74,,190.6 +3,Bob,,17,68,,120.0 +4,François,,32,75,,110.05 +""" + self.assertEqual(s, r) + + def test_to_csv_with_custom_delimiter_and_quotes(self): + d = IODict({ + 'values': [ + { 'id':'1', 'name':'Alice', 'age':'20', 'height':'62', 'weight':'120.6', }, + { 'id':'2', 'name':'Freddie', 'age':'21', 'height':'74', 'weight':'190.6', }, + { 'id':'3', 'name':'Bob', 'age':'17', 'height':'68', 'weight':'120.0', }, + { 'id':'4', 'name':'François', 'age':'32', 'height':'75', 'weight':'110.05', }, + ], + }) + s = d.to_csv(columns=['id', 'name', 'age', 'height', 'weight'], delimiter=";", quote=True) + r = """"id";"name";"age";"height";"weight" +"1";"Alice";"20";"62";"120.6" +"2";"Freddie";"21";"74";"190.6" +"3";"Bob";"17";"68";"120.0" +"4";"François";"32";"75";"110.05" +""" + self.assertEqual(s, r) + + def test_to_csv_with_custom_key_valid(self): + d = IODict({ + 'results': [ + { 'id':'1', 'name':'Alice', 'age':'20', 'height':'62', 'weight':'120.6', }, + { 'id':'2', 'name':'Freddie', 'age':'21', 'height':'74', 'weight':'190.6', }, + { 'id':'3', 'name':'Bob', 'age':'17', 'height':'68', 'weight':'120.0', }, + { 'id':'4', 'name':'François', 'age':'32', 'height':'75', 'weight':'110.05', }, + ], + }) + s = d.to_csv('results', columns=['id', 'name', 'age', 'height', 'weight']) + r = """id,name,age,height,weight +1,Alice,20,62,120.6 +2,Freddie,21,74,190.6 +3,Bob,17,68,120.0 +4,François,32,75,110.05 +""" + self.assertEqual(s, r) + + def test_to_csv_with_custom_key_invalid(self): + d = IODict({ + 'values': [ + { 'id':'1', 'name':'Alice', 'age':'20', 'height':'62', 'weight':'120.6', }, + { 'id':'2', 'name':'Freddie', 'age':'21', 'height':'74', 'weight':'190.6', }, + { 'id':'3', 'name':'Bob', 'age':'17', 'height':'68', 'weight':'120.0', }, + { 'id':'4', 'name':'François', 'age':'32', 'height':'75', 'weight':'110.05', }, + ], + }) + with self.assertRaises(KeyError): + s = d.to_csv('invalid_values', columns=['id', 'name', 'age', 'height', 'weight']) + + def test_to_csv_file(self): + d = IODict({ + 'values': [ + { 'id':'1', 'name':'Alice', 'age':'20', 'height':'62', 'weight':'120.6', }, + { 'id':'2', 'name':'Freddie', 'age':'21', 'height':'74', 'weight':'190.6', }, + { 'id':'3', 'name':'Bob', 'age':'17', 'height':'68', 'weight':'120.0', }, + { 'id':'4', 'name':'François', 'age':'32', 'height':'75', 'weight':'110.05', }, + ], + }) + filepath = self.output_path('test_to_csv_file.csv') + d.to_csv(filepath=filepath) + self.assertTrue(d, os.path.isfile(filepath)) + self.assertEqual(d, IODict.from_csv(filepath)) + # JSON def test_from_json_with_valid_data(self): @@ -159,7 +367,7 @@ class IODictTestCase(unittest.TestCase): self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) # constructor - d = IODict(j) + d = IODict(j, format='json') self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, }) @@ -170,7 +378,7 @@ class IODictTestCase(unittest.TestCase): self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'values': [0, 1, 2, 3, 4, 5] }) # constructor - d = IODict(j) + d = IODict(j, format='json') self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'values': [0, 1, 2, 3, 4, 5] }) @@ -202,7 +410,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_json(j) # constructor with self.assertRaises(ValueError): - IODict(j) + IODict(j, format='json') def test_from_json_with_valid_file_valid_content(self): filepath = self.input_path('valid-content.json') @@ -210,7 +418,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_json(filepath) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(filepath) + d = IODict(filepath, format='json') self.assertTrue(isinstance(d, dict)) def test_from_json_with_valid_file_valid_content_invalid_format(self): @@ -237,7 +445,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_json(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='json') def test_from_json_with_invalid_file(self): filepath = self.input_path('invalid-file.json') @@ -246,7 +454,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_json(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='json') def test_from_json_with_valid_url_valid_content(self): url = 'https://raw.githubusercontent.com/fabiocaccamo/python-benedict/master/tests/input/valid-content.json' @@ -254,7 +462,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_json(url) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(url) + d = IODict(url, format='json') self.assertTrue(isinstance(d, dict)) def test_from_json_with_valid_url_invalid_content(self): @@ -264,7 +472,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_json(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='json') def test_from_json_with_invalid_url(self): url = 'https://github.com/fabiocaccamo/python-benedict-invalid' @@ -273,7 +481,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_json(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='json') def test_to_json(self): d = IODict({ @@ -311,7 +519,7 @@ class IODictTestCase(unittest.TestCase): self.assertTrue(isinstance(d, dict)) self.assertEqual(d, r) # constructor - d = IODict(s) + d = IODict(s, format='query_string') self.assertTrue(isinstance(d, dict)) self.assertEqual(d, r) @@ -322,7 +530,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_query_string(s) # constructor with self.assertRaises(ValueError): - IODict(s) + IODict(s, format='query_string') def test_from_query_string_with_valid_file_valid_content(self): filepath = self.input_path('valid-content.qs') @@ -330,7 +538,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_query_string(filepath) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(filepath) + d = IODict(filepath, format='query_string') self.assertTrue(isinstance(d, dict)) def test_from_query_string_with_valid_file_valid_content_invalid_format(self): @@ -357,7 +565,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_query_string(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='query_string') def test_from_query_string_with_invalid_file(self): filepath = self.input_path('invalid-file.qs') @@ -366,7 +574,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_query_string(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='query_string') def test_from_query_string_with_valid_url_valid_content(self): url = 'https://raw.githubusercontent.com/fabiocaccamo/python-benedict/master/tests/input/valid-content.qs' @@ -374,7 +582,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_query_string(url) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(url) + d = IODict(url, format='query_string') self.assertTrue(isinstance(d, dict)) def test_from_query_string_with_valid_url_invalid_content(self): @@ -384,7 +592,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_query_string(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='query_string') def test_from_query_string_with_invalid_url(self): url = 'https://github.com/fabiocaccamo/python-benedict-invalid' @@ -393,7 +601,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_query_string(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='query_string') def test_to_query_string(self): data = { 'ok': '1', 'test': '2', 'page': '3', 'lib':'python benedict', 'author':'Fabio Caccamo' } @@ -412,18 +620,18 @@ class IODictTestCase(unittest.TestCase): def test_from_toml_with_valid_data(self): j = """ - a = 1 +a = 1 - [b] - c = 3 - d = 4 - """ +[b] +c = 3 +d = 4 +""" # static method d = IODict.from_toml(j) self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },}) # constructor - d = IODict(j) + d = IODict(j, format='toml') self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },}) @@ -434,7 +642,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_toml(j) # constructor with self.assertRaises(ValueError): - IODict(j) + IODict(j, format='toml') def test_from_toml_with_valid_file_valid_content(self): filepath = self.input_path('valid-content.toml') @@ -442,7 +650,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_toml(filepath) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(filepath) + d = IODict(filepath, format='toml') self.assertTrue(isinstance(d, dict)) def test_from_toml_with_valid_file_valid_content_invalid_format(self): @@ -469,7 +677,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_toml(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='toml') def test_from_toml_with_invalid_file(self): filepath = self.input_path('invalid-file.toml') @@ -478,7 +686,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_toml(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='toml') def test_from_toml_with_valid_url_valid_content(self): url = 'https://raw.githubusercontent.com/fabiocaccamo/python-benedict/master/tests/input/valid-content.toml' @@ -486,7 +694,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_toml(url) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(url) + d = IODict(url, format='toml') self.assertTrue(isinstance(d, dict)) def test_from_toml_with_valid_url_invalid_content(self): @@ -496,7 +704,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_toml(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='toml') def test_from_toml_with_invalid_url(self): url = 'https://github.com/fabiocaccamo/python-benedict-invalid' @@ -505,7 +713,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_toml(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='toml') def test_to_toml(self): d = IODict({ @@ -536,21 +744,22 @@ class IODictTestCase(unittest.TestCase): # XML def test_from_xml_with_valid_data(self): - j = """ - - 1 - - 3 - 4 - - - """ + j = """ + + + 1 + + 3 + 4 + + +""" # static method d = IODict.from_xml(j) self.assertTrue(isinstance(d, dict)) self.assertEqual(d.get('root'), { 'a':'1', 'b':{ 'c':'3', 'd':'4' },}) # constructor - d = IODict(j) + d = IODict(j, format='xml') self.assertTrue(isinstance(d, dict)) self.assertEqual(d.get('root'), { 'a':'1', 'b':{ 'c':'3', 'd':'4' },}) @@ -561,7 +770,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_xml(j) # constructor with self.assertRaises(ValueError): - IODict(j) + IODict(j, format='xml') def test_from_xml_with_valid_file_valid_content(self): filepath = self.input_path('valid-content.xml') @@ -569,7 +778,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_xml(filepath) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(filepath) + d = IODict(filepath, format='xml') self.assertTrue(isinstance(d, dict)) def test_from_xml_with_valid_file_valid_content_invalid_format(self): @@ -596,7 +805,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_xml(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='xml') def test_from_xml_with_invalid_file(self): filepath = self.input_path('invalid-file.xml') @@ -605,7 +814,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_xml(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='xml') def test_from_xml_with_valid_url_valid_content(self): url = 'https://raw.githubusercontent.com/fabiocaccamo/python-benedict/master/tests/input/valid-content.xml' @@ -613,7 +822,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_xml(url) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(url) + d = IODict(url, format='xml') self.assertTrue(isinstance(d, dict)) def test_from_xml_with_valid_url_invalid_content(self): @@ -623,7 +832,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_xml(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='xml') def test_from_xml_with_invalid_url(self): url = 'https://github.com/fabiocaccamo/python-benedict-invalid' @@ -632,7 +841,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_xml(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='xml') def test_to_xml(self): d = IODict({ @@ -668,17 +877,17 @@ class IODictTestCase(unittest.TestCase): def test_from_yaml_with_valid_data(self): j = """ - a: 1 - b: - c: 3 - d: 4 - """ +a: 1 +b: + c: 3 + d: 4 +""" # static method d = IODict.from_yaml(j) self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },}) # constructor - d = IODict(j) + d = IODict(j, format='yaml') self.assertTrue(isinstance(d, dict)) self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },}) @@ -689,7 +898,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_yaml(j) # constructor with self.assertRaises(ValueError): - IODict(j) + IODict(j, format='yaml') def test_from_yaml_with_valid_file_valid_content(self): filepath = self.input_path('valid-content.yml') @@ -697,7 +906,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_yaml(filepath) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(filepath) + d = IODict(filepath, format='yaml') self.assertTrue(isinstance(d, dict)) def test_from_yaml_with_valid_file_valid_content_invalid_format(self): @@ -724,7 +933,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_yaml(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='yaml') def test_from_yaml_with_invalid_file(self): filepath = self.input_path('invalid-file.yml') @@ -733,7 +942,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_yaml(filepath) # constructor with self.assertRaises(ValueError): - IODict(filepath) + IODict(filepath, format='yaml') def test_from_yaml_with_valid_url_valid_content(self): url = 'https://raw.githubusercontent.com/fabiocaccamo/python-benedict/master/tests/input/valid-content.yml' @@ -741,7 +950,7 @@ class IODictTestCase(unittest.TestCase): d = IODict.from_yaml(url) self.assertTrue(isinstance(d, dict)) # constructor - d = IODict(url) + d = IODict(url, format='yaml') self.assertTrue(isinstance(d, dict)) def test_from_yaml_with_valid_url_invalid_content(self): @@ -751,7 +960,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_yaml(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='yaml') def test_from_yaml_with_invalid_url(self): url = 'https://github.com/fabiocaccamo/python-benedict-invalid' @@ -760,7 +969,7 @@ class IODictTestCase(unittest.TestCase): IODict.from_yaml(url) # constructor with self.assertRaises(ValueError): - IODict(url) + IODict(url, format='yaml') def test_to_yaml(self): d = IODict({