Added query-string I/O support.

This commit is contained in:
Fabio Caccamo 2019-10-14 14:47:11 +02:00
parent fc9b2549ea
commit 8334993d96
7 changed files with 175 additions and 26 deletions

View File

@ -60,6 +60,11 @@ class benedict(IODict, KeypathDict, ParseDict):
def from_json(s, **kwargs):
return IODict.from_json(s, **kwargs)
@staticmethod
@benediction
def from_query_string(s, **kwargs):
return IODict.from_query_string(s, **kwargs)
@staticmethod
@benediction
def from_toml(s, **kwargs):

View File

@ -49,31 +49,21 @@ class IODict(dict):
@staticmethod
def _from_any_data_string(s, **kwargs):
try:
d = IODict.from_base64(s, **kwargs)
return d
except ValueError:
pass
try:
d = IODict.from_json(s, **kwargs)
return d
except ValueError:
pass
try:
d = IODict.from_toml(s, **kwargs)
return d
except ValueError:
pass
try:
d = IODict.from_xml(s, **kwargs)
return d
except ValueError:
pass
try:
d = IODict.from_yaml(s, **kwargs)
return d
except ValueError:
pass
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
@staticmethod
def from_base64(s, format='json', **kwargs):
@ -86,6 +76,11 @@ class IODict(dict):
return IODict._decode(s,
decoder=io_util.decode_json, **kwargs)
@staticmethod
def from_query_string(s, **kwargs):
return IODict._decode(s,
decoder=io_util.decode_query_string, **kwargs)
@staticmethod
def from_toml(s, **kwargs):
return IODict._decode(s,
@ -112,6 +107,11 @@ class IODict(dict):
encoder=io_util.encode_json,
filepath=filepath, **kwargs)
def to_query_string(self, filepath=None, **kwargs):
return IODict._encode(self,
encoder=io_util.encode_query_string,
filepath=filepath, **kwargs)
def to_toml(self, filepath=None, **kwargs):
return IODict._encode(self,
encoder=io_util.encode_toml,

View File

@ -6,6 +6,7 @@ import base64
import errno
import json
import os
import re
import requests
import xmltodict
import toml
@ -14,9 +15,13 @@ import yaml
try:
# python 3
from urllib.parse import unquote_plus as urldecode
from urllib.parse import urlencode
from urllib.parse import parse_qs
except ImportError:
# python 2
from urllib import unquote_plus as urldecode
from urllib import urlencode
from urlparse import parse_qs
def decode_base64(s, **kwargs):
@ -48,6 +53,19 @@ def decode_json(s, **kwargs):
return data
def decode_query_string(s, **kwargs):
flat = kwargs.pop('flat', True)
qs_re = r'^(([\w\-\%\+]+\=[\w\-\%\+]*)+([\&]{1})?)+'
qs_pattern = re.compile(qs_re)
if qs_pattern.match(s):
data = parse_qs(s)
if flat:
data = { key:value[0] for key, value in data.items() }
return data
else:
raise ValueError('Invalid query string: {}'.format(s))
def decode_xml(s, **kwargs):
kwargs.setdefault('dict_constructor', dict)
data = xmltodict.parse(s, **kwargs)
@ -89,6 +107,11 @@ def encode_json(d, **kwargs):
return data
def encode_query_string(d, **kwargs):
data = urlencode(d, **kwargs)
return data
def encode_toml(d, **kwargs):
data = toml.dumps(d, **kwargs)
return data

View File

@ -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.

View File

@ -0,0 +1 @@
ok=1&test=2&page=3&lib=python%20benedict&author=Fabio+Caccamo&author=Fabio%20Caccamo

View File

@ -483,6 +483,18 @@ class BenedictTestCase(unittest.TestCase):
self.assertTrue(isinstance(d, benedict))
self.assertEqual(d, { 'a': 1, 'b': 2, 'c': 3, })
def test_from_query_string_with_valid_data(self):
s = 'ok=1&test=2&page=3&lib=python%20benedict&author=Fabio+Caccamo&author=Fabio%20Caccamo'
r = { 'ok': '1', 'test': '2', 'page': '3', 'lib':'python benedict', 'author':'Fabio Caccamo' }
# static method
d = benedict.from_query_string(s)
self.assertTrue(isinstance(d, benedict))
self.assertEqual(d, r)
# constructor
d = benedict(s)
self.assertTrue(isinstance(d, benedict))
self.assertEqual(d, r)
def test_from_toml(self):
j = """
a = 1

View File

@ -263,7 +263,113 @@ class IODictTestCase(unittest.TestCase):
self.assertTrue(d, os.path.isfile(filepath))
self.assertEqual(d, IODict.from_json(filepath))
# YAML
# QUERY STRING
def test_from_query_string_with_valid_data(self):
s = 'ok=1&test=2&page=3&lib=python%20benedict&author=Fabio+Caccamo&author=Fabio%20Caccamo'
r = { 'ok': '1', 'test': '2', 'page': '3', 'lib':'python benedict', 'author':'Fabio Caccamo' }
# static method
d = IODict.from_query_string(s)
self.assertTrue(isinstance(d, dict))
self.assertEqual(d, r)
# constructor
d = IODict(s)
self.assertTrue(isinstance(d, dict))
self.assertEqual(d, r)
def test_from_query_string_with_invalid_data(self):
s = 'Lorem ipsum est in ea occaecat nisi officia.'
# static method
with self.assertRaises(ValueError):
IODict.from_query_string(s)
# constructor
with self.assertRaises(ValueError):
IODict(s)
def test_from_query_string_with_valid_file_valid_content(self):
filepath = self.input_path('valid-content.qs')
# static method
d = IODict.from_query_string(filepath)
self.assertTrue(isinstance(d, dict))
# constructor
d = IODict(filepath)
self.assertTrue(isinstance(d, dict))
def test_from_query_string_with_valid_file_valid_content_invalid_format(self):
filepath = self.input_path('valid-content.base64')
with self.assertRaises(ValueError):
IODict.from_query_string(filepath)
filepath = self.input_path('valid-content.json')
with self.assertRaises(ValueError):
IODict.from_query_string(filepath)
filepath = self.input_path('valid-content.toml')
with self.assertRaises(ValueError):
IODict.from_query_string(filepath)
filepath = self.input_path('valid-content.xml')
with self.assertRaises(ValueError):
IODict.from_query_string(filepath)
filepath = self.input_path('valid-content.yml')
with self.assertRaises(ValueError):
IODict.from_query_string(filepath)
def test_from_query_string_with_valid_file_invalid_content(self):
filepath = self.input_path('invalid-content.qs')
# static method
with self.assertRaises(ValueError):
IODict.from_query_string(filepath)
# constructor
with self.assertRaises(ValueError):
IODict(filepath)
def test_from_query_string_with_invalid_file(self):
filepath = self.input_path('invalid-file.qs')
# static method
with self.assertRaises(ValueError):
IODict.from_query_string(filepath)
# constructor
with self.assertRaises(ValueError):
IODict(filepath)
# 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'
# # static method
# d = IODict.from_query_string(url)
# self.assertTrue(isinstance(d, dict))
# # constructor
# d = IODict(url)
# self.assertTrue(isinstance(d, dict))
def test_from_query_string_with_valid_url_invalid_content(self):
url = 'https://github.com/fabiocaccamo/python-benedict'
# static method
with self.assertRaises(ValueError):
IODict.from_query_string(url)
# constructor
with self.assertRaises(ValueError):
IODict(url)
def test_from_query_string_with_invalid_url(self):
url = 'https://github.com/fabiocaccamo/python-benedict-invalid'
# static method
with self.assertRaises(ValueError):
IODict.from_query_string(url)
# constructor
with self.assertRaises(ValueError):
IODict(url)
def test_to_query_string(self):
data = { 'ok': '1', 'test': '2', 'page': '3', 'lib':'python benedict', 'author':'Fabio Caccamo' }
d = IODict({ 'ok': '1', 'test': '2', 'page': '3', 'lib':'python benedict', 'author':'Fabio Caccamo' })
s = d.to_query_string()
self.assertEqual(d, IODict.from_query_string(s))
def test_to_query_string_file(self):
d = IODict({ 'ok': '1', 'test': '2', 'page': '3', 'lib':'python benedict', 'author':'Fabio Caccamo' })
filepath = self.output_path('test_to_query_string_file.qs')
d.to_query_string(filepath=filepath)
self.assertTrue(d, os.path.isfile(filepath))
self.assertEqual(d, IODict.from_query_string(filepath))
# TOML
def test_from_toml_with_valid_data(self):