from unittest import TestCase, main from uritemplate import URITemplate, expand, partial, variables from uritemplate import variable def merge_dicts(*args): d = {} for arg in args: d.update(arg) return d class RFCTemplateExamples(type): var = {'var': 'value'} hello = {'hello': 'Hello World!'} path = {'path': '/foo/bar'} x = {'x': '1024'} y = {'y': '768'} empty = {'empty': ''} merged_x_y = merge_dicts(x, y) list_ex = {'list': ['red', 'green', 'blue']} keys = {'keys': [('semi', ';'), ('dot', '.'), ('comma', ',')]} # # Level 1 # Simple string expansion level1_examples = { '{var}': { 'expansion': var, 'expected': 'value', }, '{hello}': { 'expansion': hello, 'expected': 'Hello%20World%21', }, } # # Level 2 # Reserved string expansion level2_reserved_examples = { '{+var}': { 'expansion': var, 'expected': 'value', }, '{+hello}': { 'expansion': hello, 'expected': 'Hello%20World!', }, '{+path}/here': { 'expansion': path, 'expected': '/foo/bar/here', }, 'here?ref={+path}': { 'expansion': path, 'expected': 'here?ref=/foo/bar', }, } # Fragment expansion, crosshatch-prefixed level2_fragment_examples = { 'X{#var}': { 'expansion': var, 'expected': 'X#value', }, 'X{#hello}': { 'expansion': hello, 'expected': 'X#Hello%20World!' }, } # # Level 3 # String expansion with multiple variables level3_multiple_variable_examples = { 'map?{x,y}': { 'expansion': merged_x_y, 'expected': 'map?1024,768', }, '{x,hello,y}': { 'expansion': merge_dicts(x, y, hello), 'expected': '1024,Hello%20World%21,768', }, } # Reserved expansion with multiple variables level3_reserved_examples = { '{+x,hello,y}': { 'expansion': merge_dicts(x, y, hello), 'expected': '1024,Hello%20World!,768', }, '{+path,x}/here': { 'expansion': merge_dicts(path, x), 'expected': '/foo/bar,1024/here', }, } # Fragment expansion with multiple variables level3_fragment_examples = { '{#x,hello,y}': { 'expansion': merge_dicts(x, y, hello), 'expected': '#1024,Hello%20World!,768', }, '{#path,x}/here': { 'expansion': merge_dicts(path, x), 'expected': '#/foo/bar,1024/here' }, } # Label expansion, dot-prefixed level3_label_examples = { 'X{.var}': { 'expansion': var, 'expected': 'X.value', }, 'X{.x,y}': { 'expansion': merged_x_y, 'expected': 'X.1024.768', } } # Path segments, slash-prefixed level3_path_segment_examples = { '{/var}': { 'expansion': var, 'expected': '/value', }, '{/var,x}/here': { 'expansion': merge_dicts(var, x), 'expected': '/value/1024/here', }, } # Path-style parameters, semicolon-prefixed level3_path_semi_examples = { '{;x,y}': { 'expansion': merged_x_y, 'expected': ';x=1024;y=768', }, '{;x,y,empty}': { 'expansion': merge_dicts(x, y, empty), 'expected': ';x=1024;y=768;empty', }, } # Form-style query, ampersand-separated level3_form_amp_examples = { '{?x,y}': { 'expansion': merged_x_y, 'expected': '?x=1024&y=768', }, '{?x,y,empty}': { 'expansion': merge_dicts(x, y, empty), 'expected': '?x=1024&y=768&empty=', }, } # Form-style query continuation level3_form_cont_examples = { '?fixed=yes{&x}': { 'expansion': x, 'expected': '?fixed=yes&x=1024', }, '{&x,y,empty}': { 'expansion': merge_dicts(x, y, empty), 'expected': '&x=1024&y=768&empty=', } } # # Level 4 # String expansion with value modifiers level4_value_modifier_examples = { '{var:3}': { 'expansion': var, 'expected': 'val', }, '{var:30}': { 'expansion': var, 'expected': 'value', }, '{list}': { 'expansion': list_ex, 'expected': 'red,green,blue', }, '{list*}': { 'expansion': list_ex, 'expected': 'red,green,blue', }, '{keys}': { 'expansion': keys, 'expected': 'semi,%3B,dot,.,comma,%2C', }, '{keys*}': { 'expansion': keys, 'expected': 'semi=%3B,dot=.,comma=%2C', }, } # Reserved expansion with value modifiers level4_reserved_examples = { '{+path:6}/here': { 'expansion': path, 'expected': '/foo/b/here', }, '{+list}': { 'expansion': list_ex, 'expected': 'red,green,blue', }, '{+list*}': { 'expansion': list_ex, 'expected': 'red,green,blue', }, '{+keys}': { 'expansion': keys, 'expected': 'semi,;,dot,.,comma,,', }, '{+keys*}': { 'expansion': keys, 'expected': 'semi=;,dot=.,comma=,', }, } # Fragment expansion with value modifiers level4_fragment_examples = { '{#path:6}/here': { 'expansion': path, 'expected': '#/foo/b/here', }, '{#list}': { 'expansion': list_ex, 'expected': '#red,green,blue', }, '{#list*}': { 'expansion': list_ex, 'expected': '#red,green,blue', }, '{#keys}': { 'expansion': keys, 'expected': '#semi,;,dot,.,comma,,' }, '{#keys*}': { 'expansion': keys, 'expected': '#semi=;,dot=.,comma=,' }, } # Label expansion, dot-prefixed level4_label_examples = { 'X{.var:3}': { 'expansion': var, 'expected': 'X.val', }, 'X{.list}': { 'expansion': list_ex, 'expected': 'X.red,green,blue', }, 'X{.list*}': { 'expansion': list_ex, 'expected': 'X.red.green.blue', }, 'X{.keys}': { 'expansion': keys, 'expected': 'X.semi,%3B,dot,.,comma,%2C', }, 'X{.keys*}': { 'expansion': keys, 'expected': 'X.semi=%3B.dot=..comma=%2C', }, } # Path segments, slash-prefixed level4_path_slash_examples = { '{/var:1,var}': { 'expansion': var, 'expected': '/v/value', }, '{/list}': { 'expansion': list_ex, 'expected': '/red,green,blue', }, '{/list*}': { 'expansion': list_ex, 'expected': '/red/green/blue', }, '{/list*,path:4}': { 'expansion': merge_dicts(list_ex, path), 'expected': '/red/green/blue/%2Ffoo', }, '{/keys}': { 'expansion': keys, 'expected': '/semi,%3B,dot,.,comma,%2C', }, '{/keys*}': { 'expansion': keys, 'expected': '/semi=%3B/dot=./comma=%2C', }, } # Path-style parameters, semicolon-prefixed level4_path_semi_examples = { '{;hello:5}': { 'expansion': hello, 'expected': ';hello=Hello', }, '{;list}': { 'expansion': list_ex, 'expected': ';list=red,green,blue', }, '{;list*}': { 'expansion': list_ex, 'expected': ';list=red;list=green;list=blue', }, '{;keys}': { 'expansion': keys, 'expected': ';keys=semi,%3B,dot,.,comma,%2C', }, '{;keys*}': { 'expansion': keys, 'expected': ';semi=%3B;dot=.;comma=%2C', }, } # Form-style query, ampersand-separated level4_form_amp_examples = { '{?var:3}': { 'expansion': var, 'expected': '?var=val', }, '{?list}': { 'expansion': list_ex, 'expected': '?list=red,green,blue', }, '{?list*}': { 'expansion': list_ex, 'expected': '?list=red&list=green&list=blue', }, '{?keys}': { 'expansion': keys, 'expected': '?keys=semi,%3B,dot,.,comma,%2C', }, '{?keys*}': { 'expansion': keys, 'expected': '?semi=%3B&dot=.&comma=%2C', }, } # Form-style query continuation level4_form_query_examples = { '{&var:3}': { 'expansion': var, 'expected': '&var=val', }, '{&list}': { 'expansion': list_ex, 'expected': '&list=red,green,blue', }, '{&list*}': { 'expansion': list_ex, 'expected': '&list=red&list=green&list=blue', }, '{&keys}': { 'expansion': keys, 'expected': '&keys=semi,%3B,dot,.,comma,%2C', }, '{&keys*}': { 'expansion': keys, 'expected': '&semi=%3B&dot=.&comma=%2C', }, } def __new__(cls, name, bases, attrs): def make_test(d): def _test_(self): for k, v in d.items(): t = URITemplate(k) self.assertEqual(t.expand(v['expansion']), v['expected']) return _test_ examples = [ ( n, getattr(RFCTemplateExamples, n) ) for n in dir(RFCTemplateExamples) if n.startswith('level') ] for name, value in examples: testname = 'test_%s' % name attrs[testname] = make_test(value) return type.__new__(cls, name, bases, attrs) class TestURITemplate(RFCTemplateExamples('RFCMeta', (TestCase,), {})): def test_no_variables_in_uri(self): """ This test ensures that if there are no variables present, the template evaluates to itself. """ uri = 'https://api.github.com/users' t = URITemplate(uri) self.assertEqual(t.expand(), uri) self.assertEqual(t.expand(users='foo'), uri) def test_all_variables_parsed(self): """ This test ensures that all variables are parsed. """ uris = [ 'https://api.github.com', 'https://api.github.com/users{/user}', 'https://api.github.com/repos{/user}{/repo}', 'https://api.github.com/repos{/user}{/repo}/issues{/issue}' ] for i, uri in enumerate(uris): t = URITemplate(uri) self.assertEqual(len(t.variables), i) def test_expand(self): """ This test ensures that expansion works as expected. """ # Single t = URITemplate('https://api.github.com/users{/user}') expanded = 'https://api.github.com/users/sigmavirus24' self.assertEqual(t.expand(user='sigmavirus24'), expanded) v = t.variables[0] self.assertEqual(v.expand({'user': None}), {'/user': ''}) # Multiple t = URITemplate('https://api.github.com/users{/user}{/repo}') expanded = 'https://api.github.com/users/sigmavirus24/github3.py' self.assertEqual( t.expand({'repo': 'github3.py'}, user='sigmavirus24'), expanded ) def test_str_repr(self): uri = 'https://api.github.com{/endpoint}' t = URITemplate(uri) self.assertEqual(str(t), uri) self.assertEqual(str(t.variables[0]), '/endpoint') self.assertEqual(repr(t), 'URITemplate("%s")' % uri) self.assertEqual(repr(t.variables[0]), 'URIVariable(/endpoint)') def test_hash(self): uri = 'https://api.github.com{/endpoint}' self.assertEqual(hash(URITemplate(uri)), hash(uri)) def test_default_value(self): uri = 'https://api.github.com/user{/user=sigmavirus24}' t = URITemplate(uri) self.assertEqual(t.expand(), 'https://api.github.com/user/sigmavirus24') self.assertEqual(t.expand(user='lukasa'), 'https://api.github.com/user/lukasa') def test_query_expansion(self): t = URITemplate('{foo}') self.assertEqual( t.variables[0]._query_expansion('foo', None, False, False), None ) def test_label_path_expansion(self): t = URITemplate('{foo}') self.assertEqual( t.variables[0]._label_path_expansion('foo', None, False, False), None ) def test_label_path_expansion_explode_slash(self): t = URITemplate('{/foo*}') self.assertEqual(t.variables[0]._label_path_expansion( 'foo', [], True, '/'), None ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', [None], True, '/'), None ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', [None, None], True, '/'), None ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', ['one'], True, '/'), 'one' ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', ['one', 'two'], True, '/'), 'one/two' ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', ['one', None, 'two'], True, '/'), 'one/two' ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', [''], True, '/'), '' ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', ['', ''], True, '/'), '/' ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', {}, True, '/'), None ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', {'one': ''}, True, '/'), 'one=' ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', {'one': '', 'two': ''}, True, '/'), 'one=/two=' ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', {'one': None}, True, '/'), None ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', {'one': None, 'two': 'two'}, True, '/'), 'two=two' ) self.assertEqual(t.variables[0]._label_path_expansion( 'foo', {'one': None, 'two': None}, True, '/'), None ) def test_semi_path_expansion(self): t = URITemplate('{foo}') v = t.variables[0] self.assertEqual( v._semi_path_expansion('foo', None, False, False), None ) t.variables[0].operator = '?' self.assertEqual( v._semi_path_expansion('foo', ['bar', 'bogus'], True, False), 'foo=bar&foo=bogus' ) def test_string_expansion(self): t = URITemplate('{foo}') self.assertEqual( t.variables[0]._string_expansion('foo', None, False, False), None ) def test_hashability(self): t = URITemplate('{foo}') u = URITemplate('{foo}') d = {t: 1} d[u] += 1 self.assertEqual(d, {t: 2}) def test_no_mutate(self): args = {} t = URITemplate('') t.expand(args, key=1) self.assertEqual(args, {}) class TestURIVariable(TestCase): def setUp(self): self.v = variable.URIVariable('{foo}') def test_post_parse(self): v = self.v self.assertEqual(v.join_str, ',') self.assertEqual(v.operator, '') self.assertEqual(v.safe, '') self.assertEqual(v.start, '') def test_post_parse_plus(self): v = self.v v.operator = '+' v.post_parse() self.assertEqual(v.join_str, ',') self.assertEqual(v.safe, variable.URIVariable.reserved) self.assertEqual(v.start, '') def test_post_parse_octothorpe(self): v = self.v v.operator = '#' v.post_parse() self.assertEqual(v.join_str, ',') self.assertEqual(v.safe, variable.URIVariable.reserved) self.assertEqual(v.start, '#') def test_post_parse_question(self): v = self.v v.operator = '?' v.post_parse() self.assertEqual(v.join_str, '&') self.assertEqual(v.safe, '') self.assertEqual(v.start, '?') def test_post_parse_ampersand(self): v = self.v v.operator = '&' v.post_parse() self.assertEqual(v.join_str, '&') self.assertEqual(v.safe, '') self.assertEqual(v.start, '&') class TestVariableModule(TestCase): def test_is_list_of_tuples(self): a_list = [(1, 2), (3, 4)] self.assertEqual(variable.is_list_of_tuples(a_list), (True, a_list)) a_list = [1, 2, 3, 4] self.assertEqual(variable.is_list_of_tuples(a_list), (False, None)) def test_list_test(self): a_list = [1, 2, 3, 4] self.assertEqual(variable.list_test(a_list), True) a_list = str([1, 2, 3, 4]) self.assertEqual(variable.list_test(a_list), False) def test_list_of_tuples_test(self): a_list = [(1, 2), (3, 4)] self.assertEqual(variable.dict_test(a_list), False) d = dict(a_list) self.assertEqual(variable.dict_test(d), True) class TestAPI(TestCase): uri = 'https://api.github.com{/endpoint}' def test_expand(self): self.assertEqual(expand(self.uri, {'endpoint': 'users'}), 'https://api.github.com/users') def test_partial(self): self.assertEqual(partial(self.uri), URITemplate(self.uri)) uri = self.uri + '/sigmavirus24{/other}' self.assertEqual( partial(uri, endpoint='users'), URITemplate('https://api.github.com/users/sigmavirus24{/other}') ) def test_variables(self): self.assertEqual(variables(self.uri), URITemplate(self.uri).variable_names) if __name__ == '__main__': main()