Last active
December 17, 2015 09:59
-
-
Save maizy/5591837 to your computer and use it in GitHub Desktop.
dict-like object for url query string building
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# _*_ coding: utf-8 _*_ | |
from copy import deepcopy | |
import json | |
from lxml import etree | |
from frontik import make_qs | |
class QueryStringDict(dict): | |
""" | |
dict-like object for url query string building. | |
See examples in `test_query.py`. | |
""" | |
def __init__(self, *args, **kwargs): # because internal dict.__init__ not called update | |
self.update(*args, **kwargs) | |
def update(self, *args, **kwargs): # because internal dict.update not called __setitem__ | |
for k, v in dict(*args, **kwargs).iteritems(): | |
self[k] = v | |
def copy(self): | |
copy = super(QueryStringDict, self).copy() | |
return type(self)(copy) | |
def unset(self, key): | |
self.pop(key, None) | |
def __setitem__(self, key, value): | |
# FIXME static | |
allowed_item_types = (str, unicode, int, float, bool, type(None)) | |
allowed_value_types = (list,) + allowed_item_types | |
if not isinstance(value, allowed_value_types): | |
raise ValueError('Not allowed value type. Given "{0.__name__}" but allowed "{1}"' | |
.format(type(value), '", "'.join(map(lambda x: x.__name__, allowed_value_types)))) | |
if isinstance(value, list): | |
for item in value: | |
if not isinstance(item, allowed_item_types): | |
raise ValueError('Not allowed list item type. Given "{0.__name__}" but allowed "{1}"' | |
.format(type(item), '", "'.join(map(lambda x: x.__name__, allowed_item_types)))) | |
return super(QueryStringDict, self).__setitem__(key, value) | |
def to_query(self): | |
return make_qs(dict((param, self._convert_values(values)) for param, values in self.items())) | |
def to_xml(self, root_tag_name='query'): | |
root_xml = etree.Element(root_tag_name) | |
for param, values in self.iteritems(): | |
values = self._convert_values(values) | |
param_xml = etree.SubElement(root_xml, 'param', name=param) | |
for val in values: | |
etree.SubElement(param_xml, 'value').text = unicode(val) | |
return root_xml | |
def to_json_query(self): | |
res = {} | |
for param, values in self.iteritems(): | |
res[param] = self._convert_values(values) | |
return res | |
def to_json_str(self): | |
return json.dumps(self) | |
def to_json(self): | |
return dict(deepcopy(self)) | |
def _convert_values(self, values): | |
converted_values = [] | |
if not isinstance(values, list): | |
values = [values] | |
elif isinstance(values, list) and len(values) == 0: | |
values = [u''] | |
for val in values: | |
if isinstance(val, type(None)): | |
val = u'' | |
elif isinstance(val, bool): | |
val = 'true' if val else 'false' | |
elif isinstance(val, str): | |
val = val.decode('utf-8') | |
elif not isinstance(val, unicode): | |
val = unicode(val) | |
converted_values.append(val) | |
return converted_values |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# _*_ coding: utf-8 _*_ | |
import json | |
import unittest | |
from urllib import quote | |
from frontik.testing.test_utils import XmlResponseTestCaseMixin | |
from query import QueryStringDict | |
class QueryDictTestCase(unittest.TestCase, XmlResponseTestCaseMixin): | |
def test_key_access(self): | |
d = QueryStringDict() | |
val = 777 | |
d['key'] = val | |
self.assertEqual(val, d['key']) | |
self.assertEqual(val, d.get('key')) | |
def test_costructor_params(self): | |
d = QueryStringDict(hello='world') | |
self.assertEqual('world', d['hello']) | |
def test_bad_value_type(self): | |
def _test(): | |
QueryStringDict(p=object()) | |
def _test2(): | |
d = QueryStringDict() | |
d['p'] = object() | |
def _test3(): | |
d = QueryStringDict() | |
d.update({'p': object()}) | |
self.assertRaises(ValueError, _test) | |
self.assertRaises(ValueError, _test2) | |
self.assertRaises(ValueError, _test3) | |
def test_to_json(self): | |
d = QueryStringDict(a=u'абв', b=4, c=['b', u'я', 'ю'], d=True) | |
d['я'] = 123 | |
d[u'ю'] = 321 | |
exected = { | |
'a': u'абв', | |
'b': 4, | |
'c': ['b', u'я', 'ю'], | |
'd': True, | |
'я': 123, | |
u'ю': 321, | |
} | |
json = d.to_json() | |
self.assertEqual(json, exected) | |
# json should be immutable | |
d['b'] = 8 | |
self.assertEqual(4, json['b']) | |
d['c'].append(89) | |
self.assertFalse(89 in json['c']) | |
def test_to_query(self): | |
d = QueryStringDict(a=u'абв', b=4, c=['b', u'я', 'ю'], d=True, e=None) | |
query_parts = d.to_query().split('&') | |
expected = ['a=' + quote('абв'), 'b=4', 'c=b', 'c=' + quote('я'), 'c=' + quote('ю'), | |
'd=true', 'e='] | |
self.assertEqual(query_parts.sort(), expected.sort()) | |
def test_to_json_query(self): | |
d = QueryStringDict(a=u'абв', b=4, c=['b', u'я', 'ю'], d=True, e=None, f=[]) | |
self.assertEqual(d.to_json_query(), | |
{'a': [u'абв'], 'b': [u'4'], 'c': [u'b', u'я', u'ю'], 'd': [u'true'], 'e': [u''], 'f': [u'']}) | |
def test_to_xml(self): | |
d = QueryStringDict(a=u'абв', b=4, c=['b', u'я', 'ю'], d=True, e=None, f=[]) | |
expected = u''' | |
<customName> | |
<param name="a"><value>абв</value></param> | |
<param name="b"><value>4</value></param> | |
<param name="c"><value>b</value><value>я</value><value>ю</value></param> | |
<param name="d"><value>true</value></param> | |
<param name="e"><value /></param> | |
<param name="f"><value /></param> | |
</customName> | |
'''.strip() | |
self.assertXmlAlmostEquals(expected, d.to_xml('customName')) | |
def test_cast_to_dict(self): | |
d = QueryStringDict(a=45, b='str') | |
self.assertEqual(dict(d), {'a': 45, 'b': 'str'}) | |
def test_eq_to_dict(self): | |
self.assertTrue(QueryStringDict(a=45, b='str') == {'a': 45, 'b': 'str'}) | |
def test_copy(self): | |
d = QueryStringDict(a=6) | |
d2 = d.copy() | |
self.assertEqual(type(d), type(d2)) | |
self.assertEqual(d.to_query(), d2.to_query()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment