From 0f111e6eaf0f83e3a44bfbf8f21ab840e04f6dd9 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Fri, 12 Sep 2014 07:20:11 -0400 Subject: [PATCH] update uritemplate --- uritemplate/__init__.py | 366 ++++++++++++++++++++++++++-------------- 1 file changed, 242 insertions(+), 124 deletions(-) diff --git a/uritemplate/__init__.py b/uritemplate/__init__.py index 5d0ebced..712405d4 100644 --- a/uritemplate/__init__.py +++ b/uritemplate/__init__.py @@ -1,147 +1,265 @@ -# Early, and incomplete implementation of -04. -# +#!/usr/bin/env python + +""" +URI Template (RFC6570) Processor +""" + +__copyright__ = """\ +Copyright 2011-2013 Joe Gregorio + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + import re -import urllib +try: + from urllib.parse import quote +except ImportError: + from urllib import quote + + + +__version__ = "0.6" RESERVED = ":/?#[]@!$&'()*+,;=" -OPERATOR = "+./;?|!@" -EXPLODE = "*+" +OPERATOR = "+#./;?&|!@" MODIFIER = ":^" -TEMPLATE = re.compile(r"{(?P[\+\./;\?|!@])?(?P[^}]+)}", re.UNICODE) -VAR = re.compile(r"^(?P[^=\+\*:\^]+)((?P[\+\*])|(?P[:\^]-?[0-9]+))?(=(?P.*))?$", re.UNICODE) - -def _tostring(varname, value, explode, operator, safe=""): - if type(value) == type([]): - if explode == "+": - return ",".join([varname + "." + urllib.quote(x, safe) for x in value]) - else: - return ",".join([urllib.quote(x, safe) for x in value]) - if type(value) == type({}): - keys = value.keys() - keys.sort() - if explode == "+": - return ",".join([varname + "." + urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) - else: - return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) - else: - return urllib.quote(value, safe) +TEMPLATE = re.compile("{([^\}]+)}") -def _tostring_path(varname, value, explode, operator, safe=""): - joiner = operator - if type(value) == type([]): - if explode == "+": - return joiner.join([varname + "." + urllib.quote(x, safe) for x in value]) - elif explode == "*": - return joiner.join([urllib.quote(x, safe) for x in value]) - else: - return ",".join([urllib.quote(x, safe) for x in value]) - elif type(value) == type({}): - keys = value.keys() - keys.sort() - if explode == "+": - return joiner.join([varname + "." + urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) - elif explode == "*": - return joiner.join([urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) - else: - return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) - else: - if value: - return urllib.quote(value, safe) - else: - return "" +def variables(template): + '''Returns the set of keywords in a uri template''' + vars = set() + for varlist in TEMPLATE.findall(template): + if varlist[0] in OPERATOR: + varlist = varlist[1:] + varspecs = varlist.split(',') + for var in varspecs: + # handle prefix values + var = var.split(':')[0] + # handle composite values + if var.endswith('*'): + var = var[:-1] + vars.add(var) + return vars -def _tostring_query(varname, value, explode, operator, safe=""): - joiner = operator - varprefix = "" - if operator == "?": - joiner = "&" - varprefix = varname + "=" - if type(value) == type([]): - if 0 == len(value): - return "" - if explode == "+": - return joiner.join([varname + "=" + urllib.quote(x, safe) for x in value]) - elif explode == "*": - return joiner.join([urllib.quote(x, safe) for x in value]) + +def _quote(value, safe, prefix=None): + if prefix is not None: + return quote(str(value)[:prefix], safe) + return quote(str(value), safe) + + +def _tostring(varname, value, explode, prefix, operator, safe=""): + if isinstance(value, list): + return ",".join([_quote(x, safe) for x in value]) + if isinstance(value, dict): + keys = sorted(value.keys()) + if explode: + return ",".join([_quote(key, safe) + "=" + \ + _quote(value[key], safe) for key in keys]) + else: + return ",".join([_quote(key, safe) + "," + \ + _quote(value[key], safe) for key in keys]) + elif value is None: + return else: - return varprefix + ",".join([urllib.quote(x, safe) for x in value]) - elif type(value) == type({}): - if 0 == len(value): - return "" - keys = value.keys() - keys.sort() - if explode == "+": - return joiner.join([varname + "." + urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) - elif explode == "*": - return joiner.join([urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) + return _quote(value, safe, prefix) + + +def _tostring_path(varname, value, explode, prefix, operator, safe=""): + joiner = operator + if isinstance(value, list): + if explode: + out = [_quote(x, safe) for x in value if value is not None] + else: + joiner = "," + out = [_quote(x, safe) for x in value if value is not None] + if out: + return joiner.join(out) + else: + return + elif isinstance(value, dict): + keys = sorted(value.keys()) + if explode: + out = [_quote(key, safe) + "=" + \ + _quote(value[key], safe) for key in keys \ + if value[key] is not None] + else: + joiner = "," + out = [_quote(key, safe) + "," + \ + _quote(value[key], safe) \ + for key in keys if value[key] is not None] + if out: + return joiner.join(out) + else: + return + elif value is None: + return else: - return varprefix + ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) - else: - if value: - return varname + "=" + urllib.quote(value, safe) + return _quote(value, safe, prefix) + + +def _tostring_semi(varname, value, explode, prefix, operator, safe=""): + joiner = operator + if operator == "?": + joiner = "&" + if isinstance(value, list): + if explode: + out = [varname + "=" + _quote(x, safe) \ + for x in value if x is not None] + if out: + return joiner.join(out) + else: + return + else: + return varname + "=" + ",".join([_quote(x, safe) \ + for x in value]) + elif isinstance(value, dict): + keys = sorted(value.keys()) + if explode: + return joiner.join([_quote(key, safe) + "=" + \ + _quote(value[key], safe) \ + for key in keys if key is not None]) + else: + return varname + "=" + ",".join([_quote(key, safe) + "," + \ + _quote(value[key], safe) for key in keys \ + if key is not None]) else: - return varname + if value is None: + return + elif value: + return (varname + "=" + _quote(value, safe, prefix)) + else: + return varname + + +def _tostring_query(varname, value, explode, prefix, operator, safe=""): + joiner = operator + if operator in ["?", "&"]: + joiner = "&" + if isinstance(value, list): + if 0 == len(value): + return None + if explode: + return joiner.join([varname + "=" + _quote(x, safe) \ + for x in value]) + else: + return (varname + "=" + ",".join([_quote(x, safe) \ + for x in value])) + elif isinstance(value, dict): + if 0 == len(value): + return None + keys = sorted(value.keys()) + if explode: + return joiner.join([_quote(key, safe) + "=" + \ + _quote(value[key], safe) \ + for key in keys]) + else: + return varname + "=" + \ + ",".join([_quote(key, safe) + "," + \ + _quote(value[key], safe) for key in keys]) + else: + if value is None: + return + elif value: + return (varname + "=" + _quote(value, safe, prefix)) + else: + return (varname + "=") + TOSTRING = { "" : _tostring, "+": _tostring, - ";": _tostring_query, + "#": _tostring, + ";": _tostring_semi, "?": _tostring_query, + "&": _tostring_query, "/": _tostring_path, ".": _tostring_path, } -def expand(template, vars): - def _sub(match): - groupdict = match.groupdict() - operator = groupdict.get('operator') - if operator is None: - operator = '' - varlist = groupdict.get('varlist') +def expand(template, variables): + """ + Expand template as a URI Template using variables. + """ + def _sub(match): + expression = match.group(1) + operator = "" + if expression[0] in OPERATOR: + operator = expression[0] + varlist = expression[1:] + else: + varlist = expression - safe = "@" - if operator == '+': - safe = RESERVED - varspecs = varlist.split(",") - varnames = [] - defaults = {} - for varspec in varspecs: - m = VAR.search(varspec) - groupdict = m.groupdict() - varname = groupdict.get('varname') - explode = groupdict.get('explode') - partial = groupdict.get('partial') - default = groupdict.get('default') - if default: - defaults[varname] = default - varnames.append((varname, explode, partial)) + safe = "" + if operator in ["+", "#"]: + safe = RESERVED + varspecs = varlist.split(",") + varnames = [] + defaults = {} + for varspec in varspecs: + default = None + explode = False + prefix = None + if "=" in varspec: + varname, default = tuple(varspec.split("=", 1)) + else: + varname = varspec + if varname[-1] == "*": + explode = True + varname = varname[:-1] + elif ":" in varname: + try: + prefix = int(varname[varname.index(":")+1:]) + except ValueError: + raise ValueError("non-integer prefix '{0}'".format( + varname[varname.index(":")+1:])) + varname = varname[:varname.index(":")] + if default: + defaults[varname] = default + varnames.append((varname, explode, prefix)) - retval = [] - joiner = operator - prefix = operator - if operator == "+": - prefix = "" - joiner = "," - if operator == "?": - joiner = "&" - if operator == "": - joiner = "," - for varname, explode, partial in varnames: - if varname in vars: - value = vars[varname] - #if not value and (type(value) == type({}) or type(value) == type([])) and varname in defaults: - if not value and value != "" and varname in defaults: - value = defaults[varname] - elif varname in defaults: - value = defaults[varname] - else: - continue - retval.append(TOSTRING[operator](varname, value, explode, operator, safe=safe)) - if "".join(retval): - return prefix + joiner.join(retval) - else: - return "" + retval = [] + joiner = operator + start = operator + if operator == "+": + start = "" + joiner = "," + if operator == "#": + joiner = "," + if operator == "?": + joiner = "&" + if operator == "&": + start = "&" + if operator == "": + joiner = "," + for varname, explode, prefix in varnames: + if varname in variables: + value = variables[varname] + if not value and value != "" and varname in defaults: + value = defaults[varname] + elif varname in defaults: + value = defaults[varname] + else: + continue + expanded = TOSTRING[operator]( + varname, value, explode, prefix, operator, safe=safe) + if expanded is not None: + retval.append(expanded) + if len(retval) > 0: + return start + joiner.join(retval) + else: + return "" - return TEMPLATE.sub(_sub, template) + return TEMPLATE.sub(_sub, template)