| #!/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 |
| try: |
| from urllib.parse import quote |
| except ImportError: |
| from urllib import quote |
| |
| |
| |
| __version__ = "0.6" |
| |
| RESERVED = ":/?#[]@!$&'()*+,;=" |
| OPERATOR = "+#./;?&|!@" |
| MODIFIER = ":^" |
| TEMPLATE = re.compile(r"{([^\}]+)}") |
| |
| |
| 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 _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 _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 _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: |
| 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, |
| ";": _tostring_semi, |
| "?": _tostring_query, |
| "&": _tostring_query, |
| "/": _tostring_path, |
| ".": _tostring_path, |
| } |
| |
| |
| 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 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 |
| 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) |