OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 2 |
| 3 """ |
| 4 URI Template (RFC6570) Processor |
| 5 """ |
| 6 |
| 7 __copyright__ = """\ |
| 8 Copyright 2011-2013 Joe Gregorio |
| 9 |
| 10 Licensed under the Apache License, Version 2.0 (the "License"); |
| 11 you may not use this file except in compliance with the License. |
| 12 You may obtain a copy of the License at |
| 13 |
| 14 http://www.apache.org/licenses/LICENSE-2.0 |
| 15 |
| 16 Unless required by applicable law or agreed to in writing, software |
| 17 distributed under the License is distributed on an "AS IS" BASIS, |
| 18 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 19 See the License for the specific language governing permissions and |
| 20 limitations under the License. |
| 21 """ |
| 22 |
| 23 import re |
| 24 try: |
| 25 from urllib.parse import quote |
| 26 except ImportError: |
| 27 from urllib import quote |
| 28 |
| 29 |
| 30 |
| 31 __version__ = "0.6" |
| 32 |
| 33 RESERVED = ":/?#[]@!$&'()*+,;=" |
| 34 OPERATOR = "+#./;?&|!@" |
| 35 MODIFIER = ":^" |
| 36 TEMPLATE = re.compile("{([^\}]+)}") |
| 37 |
| 38 |
| 39 def variables(template): |
| 40 '''Returns the set of keywords in a uri template''' |
| 41 vars = set() |
| 42 for varlist in TEMPLATE.findall(template): |
| 43 if varlist[0] in OPERATOR: |
| 44 varlist = varlist[1:] |
| 45 varspecs = varlist.split(',') |
| 46 for var in varspecs: |
| 47 # handle prefix values |
| 48 var = var.split(':')[0] |
| 49 # handle composite values |
| 50 if var.endswith('*'): |
| 51 var = var[:-1] |
| 52 vars.add(var) |
| 53 return vars |
| 54 |
| 55 |
| 56 def _quote(value, safe, prefix=None): |
| 57 if prefix is not None: |
| 58 return quote(str(value)[:prefix], safe) |
| 59 return quote(str(value), safe) |
| 60 |
| 61 |
| 62 def _tostring(varname, value, explode, prefix, operator, safe=""): |
| 63 if isinstance(value, list): |
| 64 return ",".join([_quote(x, safe) for x in value]) |
| 65 if isinstance(value, dict): |
| 66 keys = sorted(value.keys()) |
| 67 if explode: |
| 68 return ",".join([_quote(key, safe) + "=" + \ |
| 69 _quote(value[key], safe) for key in keys]) |
| 70 else: |
| 71 return ",".join([_quote(key, safe) + "," + \ |
| 72 _quote(value[key], safe) for key in keys]) |
| 73 elif value is None: |
| 74 return |
| 75 else: |
| 76 return _quote(value, safe, prefix) |
| 77 |
| 78 |
| 79 def _tostring_path(varname, value, explode, prefix, operator, safe=""): |
| 80 joiner = operator |
| 81 if isinstance(value, list): |
| 82 if explode: |
| 83 out = [_quote(x, safe) for x in value if value is not None] |
| 84 else: |
| 85 joiner = "," |
| 86 out = [_quote(x, safe) for x in value if value is not None] |
| 87 if out: |
| 88 return joiner.join(out) |
| 89 else: |
| 90 return |
| 91 elif isinstance(value, dict): |
| 92 keys = sorted(value.keys()) |
| 93 if explode: |
| 94 out = [_quote(key, safe) + "=" + \ |
| 95 _quote(value[key], safe) for key in keys \ |
| 96 if value[key] is not None] |
| 97 else: |
| 98 joiner = "," |
| 99 out = [_quote(key, safe) + "," + \ |
| 100 _quote(value[key], safe) \ |
| 101 for key in keys if value[key] is not None] |
| 102 if out: |
| 103 return joiner.join(out) |
| 104 else: |
| 105 return |
| 106 elif value is None: |
| 107 return |
| 108 else: |
| 109 return _quote(value, safe, prefix) |
| 110 |
| 111 |
| 112 def _tostring_semi(varname, value, explode, prefix, operator, safe=""): |
| 113 joiner = operator |
| 114 if operator == "?": |
| 115 joiner = "&" |
| 116 if isinstance(value, list): |
| 117 if explode: |
| 118 out = [varname + "=" + _quote(x, safe) \ |
| 119 for x in value if x is not None] |
| 120 if out: |
| 121 return joiner.join(out) |
| 122 else: |
| 123 return |
| 124 else: |
| 125 return varname + "=" + ",".join([_quote(x, safe) \ |
| 126 for x in value]) |
| 127 elif isinstance(value, dict): |
| 128 keys = sorted(value.keys()) |
| 129 if explode: |
| 130 return joiner.join([_quote(key, safe) + "=" + \ |
| 131 _quote(value[key], safe) \ |
| 132 for key in keys if key is not None]) |
| 133 else: |
| 134 return varname + "=" + ",".join([_quote(key, safe) + "," + \ |
| 135 _quote(value[key], safe) for key in keys \ |
| 136 if key is not None]) |
| 137 else: |
| 138 if value is None: |
| 139 return |
| 140 elif value: |
| 141 return (varname + "=" + _quote(value, safe, prefix)) |
| 142 else: |
| 143 return varname |
| 144 |
| 145 |
| 146 def _tostring_query(varname, value, explode, prefix, operator, safe=""): |
| 147 joiner = operator |
| 148 if operator in ["?", "&"]: |
| 149 joiner = "&" |
| 150 if isinstance(value, list): |
| 151 if 0 == len(value): |
| 152 return None |
| 153 if explode: |
| 154 return joiner.join([varname + "=" + _quote(x, safe) \ |
| 155 for x in value]) |
| 156 else: |
| 157 return (varname + "=" + ",".join([_quote(x, safe) \ |
| 158 for x in value])) |
| 159 elif isinstance(value, dict): |
| 160 if 0 == len(value): |
| 161 return None |
| 162 keys = sorted(value.keys()) |
| 163 if explode: |
| 164 return joiner.join([_quote(key, safe) + "=" + \ |
| 165 _quote(value[key], safe) \ |
| 166 for key in keys]) |
| 167 else: |
| 168 return varname + "=" + \ |
| 169 ",".join([_quote(key, safe) + "," + \ |
| 170 _quote(value[key], safe) for key in keys]) |
| 171 else: |
| 172 if value is None: |
| 173 return |
| 174 elif value: |
| 175 return (varname + "=" + _quote(value, safe, prefix)) |
| 176 else: |
| 177 return (varname + "=") |
| 178 |
| 179 |
| 180 TOSTRING = { |
| 181 "" : _tostring, |
| 182 "+": _tostring, |
| 183 "#": _tostring, |
| 184 ";": _tostring_semi, |
| 185 "?": _tostring_query, |
| 186 "&": _tostring_query, |
| 187 "/": _tostring_path, |
| 188 ".": _tostring_path, |
| 189 } |
| 190 |
| 191 |
| 192 def expand(template, variables): |
| 193 """ |
| 194 Expand template as a URI Template using variables. |
| 195 """ |
| 196 def _sub(match): |
| 197 expression = match.group(1) |
| 198 operator = "" |
| 199 if expression[0] in OPERATOR: |
| 200 operator = expression[0] |
| 201 varlist = expression[1:] |
| 202 else: |
| 203 varlist = expression |
| 204 |
| 205 safe = "" |
| 206 if operator in ["+", "#"]: |
| 207 safe = RESERVED |
| 208 varspecs = varlist.split(",") |
| 209 varnames = [] |
| 210 defaults = {} |
| 211 for varspec in varspecs: |
| 212 default = None |
| 213 explode = False |
| 214 prefix = None |
| 215 if "=" in varspec: |
| 216 varname, default = tuple(varspec.split("=", 1)) |
| 217 else: |
| 218 varname = varspec |
| 219 if varname[-1] == "*": |
| 220 explode = True |
| 221 varname = varname[:-1] |
| 222 elif ":" in varname: |
| 223 try: |
| 224 prefix = int(varname[varname.index(":")+1:]) |
| 225 except ValueError: |
| 226 raise ValueError("non-integer prefix '{0}'".format( |
| 227 varname[varname.index(":")+1:])) |
| 228 varname = varname[:varname.index(":")] |
| 229 if default: |
| 230 defaults[varname] = default |
| 231 varnames.append((varname, explode, prefix)) |
| 232 |
| 233 retval = [] |
| 234 joiner = operator |
| 235 start = operator |
| 236 if operator == "+": |
| 237 start = "" |
| 238 joiner = "," |
| 239 if operator == "#": |
| 240 joiner = "," |
| 241 if operator == "?": |
| 242 joiner = "&" |
| 243 if operator == "&": |
| 244 start = "&" |
| 245 if operator == "": |
| 246 joiner = "," |
| 247 for varname, explode, prefix in varnames: |
| 248 if varname in variables: |
| 249 value = variables[varname] |
| 250 if not value and value != "" and varname in defaults: |
| 251 value = defaults[varname] |
| 252 elif varname in defaults: |
| 253 value = defaults[varname] |
| 254 else: |
| 255 continue |
| 256 expanded = TOSTRING[operator]( |
| 257 varname, value, explode, prefix, operator, safe=safe) |
| 258 if expanded is not None: |
| 259 retval.append(expanded) |
| 260 if len(retval) > 0: |
| 261 return start + joiner.join(retval) |
| 262 else: |
| 263 return "" |
| 264 |
| 265 return TEMPLATE.sub(_sub, template) |
OLD | NEW |