Skip to content

Instantly share code, notes, and snippets.

@maizy
Last active December 17, 2015 09:59
Show Gist options
  • Save maizy/5591837 to your computer and use it in GitHub Desktop.
Save maizy/5591837 to your computer and use it in GitHub Desktop.
dict-like object for url query string building
# _*_ 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
# _*_ 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