Skip to content

Instantly share code, notes, and snippets.

@Ceasar
Last active August 29, 2015 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ceasar/c545b5a97deecd1d065b to your computer and use it in GitHub Desktop.
Save Ceasar/c545b5a97deecd1d065b to your computer and use it in GitHub Desktop.
Implements a Level 3 expand function for RFC 6570 URI Templates.
from urllib import quote
_OP_LEVEL_2 = {'+', '#'}
_OP_LEVEL_3 = {'.', '/', ';', '?', '&'}
OPERATORS = _OP_LEVEL_2 | _OP_LEVEL_3
_RESERVED = ':/?#[]@' + '!$&\'()*+,;='
class Token(object):
def expand(self, **kwargs):
raise NotImplementedError()
class Literal(Token):
def __init__(self, val):
self.val = val
def __str__(self):
return self.val
def expand(self, **kwargs):
return self.val
_START = {
'': '',
'+': '',
'#': '#',
'.': '.',
'/': '/',
';': ';',
'?': '?',
'&': '&',
}
_SEP = {
'': ',',
'+': ',',
'#': ',',
'.': '.',
'/': '/',
';': ';',
'?': '&',
'&': '&',
}
class Expression(Token):
"""
>>> token = Expression(['var'])
>>> str(token)
'{var}'
>>> token.expand(var='value')
'value'
>>> hello = 'Hello World!'
>>> Expression(['hello']).expand(hello=hello)
'Hello%20World%21'
>>> Expression(['x', 'hello', 'y']).expand(x='1024', y='768', hello=hello)
'1024,Hello%20World%21,768'
"""
def __init__(self, variables, operator=''):
self.variables = variables
assert operator == '' or operator in OPERATORS, operator
self.operator = operator
@classmethod
def from_string(cls, string):
if string.startswith('{') and string.endswith('}'):
expression = string[1:-1]
if expression[0] in OPERATORS:
return cls(expression[1:].split(','), operator=expression[0])
else:
return cls(expression.split(','))
else:
raise ValueError('invalid expression')
def __str__(self):
return return '{{{}{}}}'.format(self.operator, ','.join(self.variables))
def expand(self, **kwargs):
"""
>>> var = 'value'
>>> hello = 'Hello World!'
>>> empty = ''
>>> path = '/foo/bar'
>>> x = '1024'
>>> y = '768'
String expansion with multiple variables:
>>> Expression.from_string('{x,y}').expand(x=x, y=y)
'1024,768'
>>> Expression.from_string('{x,hello,y}').expand(x=x, hello=hello, y=y)
'1024,Hello%20World%21,768'
Reserved expansion with value modifiers:
>>> Expression.from_string('{+x,hello,y}').expand(x=x, hello=hello, y=y)
'1024,Hello%20World!,768'
>>> Expression.from_string('{+path,x}').expand(path=path, x=x)
'/foo/bar,1024'
Fragment expansion with multiple variables:
>>> Expression.from_string('{#x,hello,y}').expand(x=x, hello=hello, y=y)
'#1024,Hello%20World!,768'
Label expansion, dot-prefixed:
>>> Expression.from_string('{.var}').expand(var=var)
'.value'
>>> Expression.from_string('{.x,y}').expand(x=x, y=y)
'.1024.768'
Path segments, slash-prefixed:
>>> Expression.from_string('{/var}').expand(var=var)
'/value'
>>> Expression.from_string('{/var,x}').expand(var=var, x=x)
'/value/1024'
Path-style parameters, semicolon-prefixed:
>>> Expression.from_string('{;x,y}').expand(x=x, y=y)
';x=1024;y=768'
>>> Expression.from_string('{;x,y,empty}').expand(x=x, y=y, empty=empty)
';x=1024;y=768;empty'
Form-style query, ampersand-separated:
>>> Expression.from_string('{?x,y}').expand(x=x, y=y)
'?x=1024&y=768'
>>> Expression.from_string('{?x,y,empty}').expand(x=x, y=y, empty=empty)
'?x=1024&y=768&empty='
Form-style query continuation:
>>> Expression.from_string('{&x,y,empty}').expand(x=x, y=y, empty=empty)
'&x=1024&y=768&empty='
"""
safe = _RESERVED if self.operator in _OP_LEVEL_2 else ''
varlist = ([
'{}={}'.format(quote(var), quote(kwargs[var]))
if kwargs[var] else var
for var in self.variables if var in kwargs
] if self.operator == ';' else [
'{}={}'.format(quote(var), quote(kwargs[var]))
for var in self.variables if var in kwargs
] if self.operator in {'?', '&'} else [
quote(kwargs[var], safe)
for var in self.variables if var in kwargs
])
start, sep = _START[self.operator], _SEP[self.operator]
return start + sep.join(varlist) if varlist else ''
class URITemplate(object):
"""
Level 3 URI Template.
>>> template = URITemplate([
... Literal('http://www.example.com/foo'),
... Expression(['query', 'number'], operator='?')
... ])
>>> str(template)
'http://www.example.com/foo{?query,number}'
>>> template.expand(query='mycelium', number='100')
'http://www.example.com/foo?query=mycelium&number=100'
>>> template.expand(number='100')
'http://www.example.com/foo?number=100'
>>> template.expand()
'http://www.example.com/foo'
:param tokens:
A list of :class:`Token` objects.
"""
def __init__(self, tokens):
self.tokens = tokens
def __str__(self):
return ''.join([str(token) for token in self.tokens])
def expand(self, **kwargs):
return ''.join([token.expand(**kwargs) for token in self.tokens])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment