Skip to content

Commit 53c4ed4

Browse files
committed
Added yaml io support.
1 parent 4610108 commit 53c4ed4

7 files changed

Lines changed: 189 additions & 61 deletions

File tree

benedict/dicts/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ def fromkeys(cls, sequence, value=None):
5555
def from_json(s, **kwargs):
5656
return IODict.from_json(s, **kwargs)
5757

58+
@staticmethod
59+
@benediction
60+
def from_yaml(s, **kwargs):
61+
return IODict.from_yaml(s, **kwargs)
62+
5863
@benediction
5964
def __getitem__(self, key):
6065
return super(benedict, self).__getitem__(key)

benedict/dicts/io.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self, *args, **kwargs):
1313
# if first argument is string,
1414
# try to decode it using all decoders.
1515
if len(args) and isinstance(args[0], string_types):
16-
d = IODict._from_string(args[0])
16+
d = IODict._from_any_string(args[0])
1717
if d and isinstance(d, dict):
1818
args = list(args)
1919
args[0] = d
@@ -32,7 +32,16 @@ def _load_and_decode(s, decoder, **kwargs):
3232
content = io_util.read_file(s)
3333
else:
3434
content = s
35-
d = decoder(content, **kwargs)
35+
# decode content using the given decoder
36+
data = decoder(content, **kwargs)
37+
if isinstance(data, dict):
38+
d = data
39+
elif isinstance(data, list):
40+
# force list to dict
41+
d = { 'values':data }
42+
else:
43+
raise ValueError(
44+
'Invalid data type: {}, expected dict or list.'.format(type(data)))
3645
except Exception as e:
3746
raise ValueError(
3847
'Invalid data or url or filepath input argument: {}\n{}'.format(s, text_type(e)))
@@ -46,20 +55,33 @@ def _encode_and_save(d, encoder, filepath=None, **kwargs):
4655
return s
4756

4857
@staticmethod
49-
def _from_string(s, **kwargs):
58+
def _from_any_string(s, **kwargs):
5059
d = None
5160
try:
5261
d = IODict.from_json(s, **kwargs)
5362
except ValueError:
54-
d = None
63+
try:
64+
d = IODict.from_yaml(s, **kwargs)
65+
except ValueError:
66+
d = None
5567
return d
5668

5769
@staticmethod
5870
def from_json(s, **kwargs):
5971
return IODict._load_and_decode(s,
6072
io_util.decode_json, **kwargs)
6173

74+
@staticmethod
75+
def from_yaml(s, **kwargs):
76+
return IODict._load_and_decode(s,
77+
io_util.decode_yaml, **kwargs)
78+
6279
def to_json(self, filepath=None, **kwargs):
6380
return IODict._encode_and_save(self,
6481
encoder=io_util.encode_json,
6582
filepath=filepath, **kwargs)
83+
84+
def to_yaml(self, filepath=None, **kwargs):
85+
return IODict._encode_and_save(self,
86+
encoder=io_util.encode_yaml,
87+
filepath=filepath, **kwargs)

benedict/utils/io_util.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,28 @@
44
import json
55
import os
66
import requests
7+
import yaml
78

89

910
def decode_json(s, **kwargs):
1011
data = json.loads(s, **kwargs)
11-
return { 'values':data } if isinstance(data, list) else data
12+
return data
13+
14+
15+
def decode_yaml(s, **kwargs):
16+
kwargs.setdefault('Loader', yaml.Loader)
17+
data = yaml.load(s, **kwargs)
18+
return data
1219

1320

1421
def encode_json(d, **kwargs):
15-
return json.dumps(d, **kwargs)
22+
data = json.dumps(d, **kwargs)
23+
return data
24+
25+
26+
def encode_yaml(d, **kwargs):
27+
data = yaml.dump(d, **kwargs)
28+
return data
1629

1730

1831
def read_file(filepath):

tests/input/invalid-content.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Lorem ipsum consectetur sint id aute officia sed excepteur consectetur labore laboris dolore in labore consequat ut in eu ut deserunt.
2+
Elit aliqua velit aliquip voluptate consequat reprehenderit occaecat dolor ut esse aute laboris cillum fugiat esse est laborum.

tests/input/valid-content.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
invoice: 34843
2+
date : 2001-01-23
3+
bill-to: &id001
4+
given : Chris
5+
family : Dumars
6+
address:
7+
lines: |
8+
458 Walkman Dr.
9+
Suite #292
10+
city : Royal Oak
11+
state : MI
12+
postal : 48046
13+
ship-to: *id001
14+
product:
15+
- sku : BL394D
16+
quantity : 4
17+
description : Basketball
18+
price : 450.00
19+
- sku : BL4438H
20+
quantity : 1
21+
description : Super Hoop
22+
price : 2392.00
23+
tax : 251.42
24+
total: 4443.52
25+
comments: >
26+
Late afternoon is best.
27+
Backup contact is Nancy
28+
Billsmer @ 338-4338.

tests/test_benedict.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,22 @@ def test_from_json(self):
230230
self.assertTrue(isinstance(d, benedict))
231231
self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, })
232232

233+
def test_from_yaml(self):
234+
j = """
235+
a: 1
236+
b:
237+
c: 3
238+
d: 4
239+
"""
240+
# static method
241+
d = benedict.from_yaml(j)
242+
self.assertTrue(isinstance(d, benedict))
243+
self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },})
244+
# constructor
245+
d = benedict(j)
246+
self.assertTrue(isinstance(d, benedict))
247+
self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },})
248+
233249
def test_get(self):
234250
d = {
235251
'a': 1,

tests/test_io_dict.py

Lines changed: 97 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -122,70 +122,112 @@ def test_to_json_file(self):
122122
})
123123
filepath = self.output_path('test_to_json_file.json')
124124
s = d.to_json(filepath=filepath, sort_keys=True)
125+
self.assertTrue(d, os.path.isfile(filepath))
125126
self.assertEqual(d, IODict.from_json(filepath))
126127

127-
# def test_from_query_string(self):
128-
# pass
128+
# YAML
129129

130-
# def test_from_query_string_file(self):
131-
# pass
132-
133-
# def test_from_query_string_url(self):
134-
# pass
135-
136-
# def test_from_toml_string(self):
137-
# pass
138-
139-
# def test_from_toml_file(self):
140-
# pass
141-
142-
# def test_from_toml_url(self):
143-
# pass
144-
145-
# def test_from_xml_string(self):
146-
# pass
147-
148-
# def test_from_xml_file(self):
149-
# pass
150-
151-
# def test_from_xml_url(self):
152-
# pass
153-
154-
# def test_from_yaml_string(self):
155-
# pass
156-
157-
# def test_from_yaml_file(self):
158-
# pass
159-
160-
# def test_from_yaml_url(self):
161-
# pass
162-
163-
# def test_to_base64(self):
164-
# pass
130+
def test_from_yaml_with_valid_data(self):
131+
j = """
132+
a: 1
133+
b:
134+
c: 3
135+
d: 4
136+
"""
137+
# static method
138+
d = IODict.from_yaml(j)
139+
self.assertTrue(isinstance(d, dict))
140+
self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },})
141+
# constructor
142+
d = IODict(j)
143+
self.assertTrue(isinstance(d, dict))
144+
self.assertEqual(d, { 'a':1, 'b':{ 'c':3, 'd':4 },})
165145

166-
# def test_to_base64_file(self):
167-
# pass
146+
def test_from_yaml_with_invalid_data(self):
147+
j = 'Lorem ipsum est in ea occaecat nisi officia.'
148+
# static method
149+
with self.assertRaises(ValueError):
150+
d = IODict.from_yaml(j)
151+
# constructor
152+
with self.assertRaises(ValueError):
153+
d = IODict(j)
168154

169-
# def test_to_query_string(self):
170-
# pass
155+
def test_from_yaml_with_valid_file_valid_content(self):
156+
filepath = self.input_path('valid-content.yml')
157+
# static method
158+
d = IODict.from_yaml(filepath)
159+
self.assertTrue(isinstance(d, dict))
160+
# constructor
161+
d = IODict(filepath)
162+
self.assertTrue(isinstance(d, dict))
171163

172-
# def test_to_query_string_file(self):
173-
# pass
164+
def test_from_yaml_with_valid_file_invalid_content(self):
165+
filepath = self.input_path('invalid-content.yml')
166+
# static method
167+
with self.assertRaises(ValueError):
168+
d = IODict.from_yaml(filepath)
169+
# constructor
170+
with self.assertRaises(ValueError):
171+
d = IODict(filepath)
174172

175-
# def test_to_toml(self):
176-
# pass
173+
def test_from_yaml_with_invalid_file(self):
174+
filepath = self.input_path('invalid-file.yml')
175+
# static method
176+
with self.assertRaises(ValueError):
177+
d = IODict.from_yaml(filepath)
178+
# constructor
179+
with self.assertRaises(ValueError):
180+
d = IODict(filepath)
177181

178-
# def test_to_toml_file(self):
179-
# pass
182+
# def test_from_yaml_with_valid_url_valid_content(self):
183+
# url = 'https://github.com/fabiocaccamo/python-benedict/tests/input/valid-content.yml'
184+
# # static method
185+
# d = IODict.from_yaml(url)
186+
# self.assertTrue(isinstance(d, dict))
187+
# # constructor
188+
# d = IODict(url)
189+
# self.assertTrue(isinstance(d, dict))
180190

181-
# def test_to_xml(self):
182-
# pass
191+
def test_from_yaml_with_valid_url_invalid_content(self):
192+
url = 'https://github.com/fabiocaccamo/python-benedict'
193+
# static method
194+
with self.assertRaises(ValueError):
195+
d = IODict.from_yaml(url)
196+
# constructor
197+
with self.assertRaises(ValueError):
198+
d = IODict(url)
183199

184-
# def test_to_xml_file(self):
185-
# pass
200+
def test_from_yaml_with_invalid_url(self):
201+
url = 'https://github.com/fabiocaccamo/python-benedict-invalid'
202+
# static method
203+
with self.assertRaises(ValueError):
204+
d = IODict.from_yaml(url)
205+
# constructor
206+
with self.assertRaises(ValueError):
207+
d = IODict(url)
186208

187-
# def test_to_yaml(self):
188-
# pass
209+
def test_to_yaml(self):
210+
d = IODict({
211+
'x': 7,
212+
'y': 8,
213+
'z': 9,
214+
'a': 1,
215+
'b': 2,
216+
'c': 3,
217+
})
218+
s = d.to_yaml()
219+
self.assertEqual(d, IODict.from_yaml(s))
189220

190-
# def test_to_yaml_file(self):
191-
# pass
221+
def test_to_yaml_file(self):
222+
d = IODict({
223+
'x': 7,
224+
'y': 8,
225+
'z': 9,
226+
'a': 1,
227+
'b': 2,
228+
'c': 3,
229+
})
230+
filepath = self.output_path('test_to_yaml_file.yml')
231+
s = d.to_yaml(filepath=filepath)
232+
self.assertTrue(d, os.path.isfile(filepath))
233+
self.assertEqual(d, IODict.from_yaml(filepath))

0 commit comments

Comments
 (0)