| 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 |