mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-04 12:51:36 +00:00
URITemplate 3.0
This commit is contained in:
@@ -1,265 +1,26 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
"""
|
|
||||||
URI Template (RFC6570) Processor
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__copyright__ = """\
|
uritemplate
|
||||||
Copyright 2011-2013 Joe Gregorio
|
===========
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
The URI templating library for humans.
|
||||||
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
|
See http://uritemplate.rtfd.org/ for documentation
|
||||||
|
|
||||||
|
:copyright: (c) 2013-2015 Ian Cordasco
|
||||||
|
:license: Modified BSD, see LICENSE for more details
|
||||||
|
|
||||||
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
|
__title__ = 'uritemplate'
|
||||||
try:
|
__author__ = 'Ian Cordasco'
|
||||||
from urllib.parse import quote
|
__license__ = 'Modified BSD or Apache License, Version 2.0'
|
||||||
except ImportError:
|
__copyright__ = 'Copyright 2013 Ian Cordasco'
|
||||||
from urllib import quote
|
__version__ = '3.0.0'
|
||||||
|
__version_info__ = tuple(int(i) for i in __version__.split('.') if i.isdigit())
|
||||||
|
|
||||||
|
from uritemplate.api import (
|
||||||
|
URITemplate, expand, partial, variables # noqa: E402
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ('URITemplate', 'expand', 'partial', 'variables')
|
||||||
__version__ = "0.6"
|
|
||||||
|
|
||||||
RESERVED = ":/?#[]@!$&'()*+,;="
|
|
||||||
OPERATOR = "+#./;?&|!@"
|
|
||||||
MODIFIER = ":^"
|
|
||||||
TEMPLATE = re.compile("{([^\}]+)}")
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|||||||
71
src/uritemplate/api.py
Normal file
71
src/uritemplate/api.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
uritemplate.api
|
||||||
|
===============
|
||||||
|
|
||||||
|
This module contains the very simple API provided by uritemplate.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from uritemplate.template import URITemplate
|
||||||
|
|
||||||
|
|
||||||
|
def expand(uri, var_dict=None, **kwargs):
|
||||||
|
"""Expand the template with the given parameters.
|
||||||
|
|
||||||
|
:param str uri: The templated URI to expand
|
||||||
|
:param dict var_dict: Optional dictionary with variables and values
|
||||||
|
:param kwargs: Alternative way to pass arguments
|
||||||
|
:returns: str
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
expand('https://api.github.com{/end}', {'end': 'users'})
|
||||||
|
expand('https://api.github.com{/end}', end='gists')
|
||||||
|
|
||||||
|
.. note:: Passing values by both parts, may override values in
|
||||||
|
``var_dict``. For example::
|
||||||
|
|
||||||
|
expand('https://{var}', {'var': 'val1'}, var='val2')
|
||||||
|
|
||||||
|
``val2`` will be used instead of ``val1``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return URITemplate(uri).expand(var_dict, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def partial(uri, var_dict=None, **kwargs):
|
||||||
|
"""Partially expand the template with the given parameters.
|
||||||
|
|
||||||
|
If all of the parameters for the template are not given, return a
|
||||||
|
partially expanded template.
|
||||||
|
|
||||||
|
:param dict var_dict: Optional dictionary with variables and values
|
||||||
|
:param kwargs: Alternative way to pass arguments
|
||||||
|
:returns: :class:`URITemplate`
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
t = URITemplate('https://api.github.com{/end}')
|
||||||
|
t.partial() # => URITemplate('https://api.github.com{/end}')
|
||||||
|
|
||||||
|
"""
|
||||||
|
return URITemplate(uri).partial(var_dict, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def variables(uri):
|
||||||
|
"""Parse the variables of the template.
|
||||||
|
|
||||||
|
This returns all of the variable names in the URI Template.
|
||||||
|
|
||||||
|
:returns: Set of variable names
|
||||||
|
:rtype: set
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
variables('https://api.github.com{/end})
|
||||||
|
# => {'end'}
|
||||||
|
variables('https://api.github.com/repos{/username}{/repository}')
|
||||||
|
# => {'username', 'repository'}
|
||||||
|
|
||||||
|
"""
|
||||||
|
return set(URITemplate(uri).variable_names)
|
||||||
150
src/uritemplate/template.py
Normal file
150
src/uritemplate/template.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
uritemplate.template
|
||||||
|
====================
|
||||||
|
|
||||||
|
This module contains the essential inner workings of uritemplate.
|
||||||
|
|
||||||
|
What treasures await you:
|
||||||
|
|
||||||
|
- URITemplate class
|
||||||
|
|
||||||
|
You see a treasure chest of knowledge in front of you.
|
||||||
|
What do you do?
|
||||||
|
>
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from uritemplate.variable import URIVariable
|
||||||
|
|
||||||
|
template_re = re.compile('{([^\}]+)}')
|
||||||
|
|
||||||
|
|
||||||
|
def _merge(var_dict, overrides):
|
||||||
|
if var_dict:
|
||||||
|
opts = var_dict.copy()
|
||||||
|
opts.update(overrides)
|
||||||
|
return opts
|
||||||
|
return overrides
|
||||||
|
|
||||||
|
|
||||||
|
class URITemplate(object):
|
||||||
|
|
||||||
|
"""This parses the template and will be used to expand it.
|
||||||
|
|
||||||
|
This is the most important object as the center of the API.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
from uritemplate import URITemplate
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
t = URITemplate(
|
||||||
|
'https://api.github.com/users/sigmavirus24/gists{/gist_id}'
|
||||||
|
)
|
||||||
|
uri = t.expand(gist_id=123456)
|
||||||
|
resp = requests.get(uri)
|
||||||
|
for gist in resp.json():
|
||||||
|
print(gist['html_url'])
|
||||||
|
|
||||||
|
Please note::
|
||||||
|
|
||||||
|
str(t)
|
||||||
|
# 'https://api.github.com/users/sigmavirus24/gists{/gistid}'
|
||||||
|
repr(t) # is equivalent to
|
||||||
|
# URITemplate(str(t))
|
||||||
|
# Where str(t) is interpreted as the URI string.
|
||||||
|
|
||||||
|
Also, ``URITemplates`` are hashable so they can be used as keys in
|
||||||
|
dictionaries.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, uri):
|
||||||
|
#: The original URI to be parsed.
|
||||||
|
self.uri = uri
|
||||||
|
#: A list of the variables in the URI. They are stored as
|
||||||
|
#: :class:`URIVariable`\ s
|
||||||
|
self.variables = [
|
||||||
|
URIVariable(m.groups()[0]) for m in template_re.finditer(self.uri)
|
||||||
|
]
|
||||||
|
#: A set of variable names in the URI.
|
||||||
|
self.variable_names = set()
|
||||||
|
for variable in self.variables:
|
||||||
|
self.variable_names.update(variable.variable_names)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'URITemplate("%s")' % self
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.uri
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.uri == other.uri
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.uri)
|
||||||
|
|
||||||
|
def _expand(self, var_dict, replace):
|
||||||
|
if not self.variables:
|
||||||
|
return self.uri
|
||||||
|
|
||||||
|
expansion = var_dict
|
||||||
|
expanded = {}
|
||||||
|
for v in self.variables:
|
||||||
|
expanded.update(v.expand(expansion))
|
||||||
|
|
||||||
|
def replace_all(match):
|
||||||
|
return expanded.get(match.groups()[0], '')
|
||||||
|
|
||||||
|
def replace_partial(match):
|
||||||
|
match = match.groups()[0]
|
||||||
|
var = '{%s}' % match
|
||||||
|
return expanded.get(match) or var
|
||||||
|
|
||||||
|
replace = replace_partial if replace else replace_all
|
||||||
|
|
||||||
|
return template_re.sub(replace, self.uri)
|
||||||
|
|
||||||
|
def expand(self, var_dict=None, **kwargs):
|
||||||
|
"""Expand the template with the given parameters.
|
||||||
|
|
||||||
|
:param dict var_dict: Optional dictionary with variables and values
|
||||||
|
:param kwargs: Alternative way to pass arguments
|
||||||
|
:returns: str
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
t = URITemplate('https://api.github.com{/end}')
|
||||||
|
t.expand({'end': 'users'})
|
||||||
|
t.expand(end='gists')
|
||||||
|
|
||||||
|
.. note:: Passing values by both parts, may override values in
|
||||||
|
``var_dict``. For example::
|
||||||
|
|
||||||
|
expand('https://{var}', {'var': 'val1'}, var='val2')
|
||||||
|
|
||||||
|
``val2`` will be used instead of ``val1``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._expand(_merge(var_dict, kwargs), False)
|
||||||
|
|
||||||
|
def partial(self, var_dict=None, **kwargs):
|
||||||
|
"""Partially expand the template with the given parameters.
|
||||||
|
|
||||||
|
If all of the parameters for the template are not given, return a
|
||||||
|
partially expanded template.
|
||||||
|
|
||||||
|
:param dict var_dict: Optional dictionary with variables and values
|
||||||
|
:param kwargs: Alternative way to pass arguments
|
||||||
|
:returns: :class:`URITemplate`
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
t = URITemplate('https://api.github.com{/end}')
|
||||||
|
t.partial() # => URITemplate('https://api.github.com{/end}')
|
||||||
|
|
||||||
|
"""
|
||||||
|
return URITemplate(self._expand(_merge(var_dict, kwargs), True))
|
||||||
384
src/uritemplate/variable.py
Normal file
384
src/uritemplate/variable.py
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
uritemplate.variable
|
||||||
|
====================
|
||||||
|
|
||||||
|
This module contains the URIVariable class which powers the URITemplate class.
|
||||||
|
|
||||||
|
What treasures await you:
|
||||||
|
|
||||||
|
- URIVariable class
|
||||||
|
|
||||||
|
You see a hammer in front of you.
|
||||||
|
What do you do?
|
||||||
|
>
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import sys
|
||||||
|
|
||||||
|
if (2, 6) <= sys.version_info < (2, 8):
|
||||||
|
import urllib
|
||||||
|
elif (3, 3) <= sys.version_info < (4, 0):
|
||||||
|
import urllib.parse as urllib
|
||||||
|
|
||||||
|
|
||||||
|
class URIVariable(object):
|
||||||
|
|
||||||
|
"""This object validates everything inside the URITemplate object.
|
||||||
|
|
||||||
|
It validates template expansions and will truncate length as decided by
|
||||||
|
the template.
|
||||||
|
|
||||||
|
Please note that just like the :class:`URITemplate <URITemplate>`, this
|
||||||
|
object's ``__str__`` and ``__repr__`` methods do not return the same
|
||||||
|
information. Calling ``str(var)`` will return the original variable.
|
||||||
|
|
||||||
|
This object does the majority of the heavy lifting. The ``URITemplate``
|
||||||
|
object finds the variables in the URI and then creates ``URIVariable``
|
||||||
|
objects. Expansions of the URI are handled by each ``URIVariable``
|
||||||
|
object. ``URIVariable.expand()`` returns a dictionary of the original
|
||||||
|
variable and the expanded value. Check that method's documentation for
|
||||||
|
more information.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
operators = ('+', '#', '.', '/', ';', '?', '&', '|', '!', '@')
|
||||||
|
reserved = ":/?#[]@!$&'()*+,;="
|
||||||
|
|
||||||
|
def __init__(self, var):
|
||||||
|
#: The original string that comes through with the variable
|
||||||
|
self.original = var
|
||||||
|
#: The operator for the variable
|
||||||
|
self.operator = ''
|
||||||
|
#: List of safe characters when quoting the string
|
||||||
|
self.safe = ''
|
||||||
|
#: List of variables in this variable
|
||||||
|
self.variables = []
|
||||||
|
#: List of variable names
|
||||||
|
self.variable_names = []
|
||||||
|
#: List of defaults passed in
|
||||||
|
self.defaults = {}
|
||||||
|
# Parse the variable itself.
|
||||||
|
self.parse()
|
||||||
|
self.post_parse()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'URIVariable(%s)' % self
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.original
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""Parse the variable.
|
||||||
|
|
||||||
|
This finds the:
|
||||||
|
- operator,
|
||||||
|
- set of safe characters,
|
||||||
|
- variables, and
|
||||||
|
- defaults.
|
||||||
|
|
||||||
|
"""
|
||||||
|
var_list = self.original
|
||||||
|
if self.original[0] in URIVariable.operators:
|
||||||
|
self.operator = self.original[0]
|
||||||
|
var_list = self.original[1:]
|
||||||
|
|
||||||
|
if self.operator in URIVariable.operators[:2]:
|
||||||
|
self.safe = URIVariable.reserved
|
||||||
|
|
||||||
|
var_list = var_list.split(',')
|
||||||
|
|
||||||
|
for var in var_list:
|
||||||
|
default_val = None
|
||||||
|
name = var
|
||||||
|
if '=' in var:
|
||||||
|
name, default_val = tuple(var.split('=', 1))
|
||||||
|
|
||||||
|
explode = False
|
||||||
|
if name.endswith('*'):
|
||||||
|
explode = True
|
||||||
|
name = name[:-1]
|
||||||
|
|
||||||
|
prefix = None
|
||||||
|
if ':' in name:
|
||||||
|
name, prefix = tuple(name.split(':', 1))
|
||||||
|
prefix = int(prefix)
|
||||||
|
|
||||||
|
if default_val:
|
||||||
|
self.defaults[name] = default_val
|
||||||
|
|
||||||
|
self.variables.append(
|
||||||
|
(name, {'explode': explode, 'prefix': prefix})
|
||||||
|
)
|
||||||
|
|
||||||
|
self.variable_names = [varname for (varname, _) in self.variables]
|
||||||
|
|
||||||
|
def post_parse(self):
|
||||||
|
"""Set ``start``, ``join_str`` and ``safe`` attributes.
|
||||||
|
|
||||||
|
After parsing the variable, we need to set up these attributes and it
|
||||||
|
only makes sense to do it in a more easily testable way.
|
||||||
|
"""
|
||||||
|
self.safe = ''
|
||||||
|
self.start = self.join_str = self.operator
|
||||||
|
if self.operator == '+':
|
||||||
|
self.start = ''
|
||||||
|
if self.operator in ('+', '#', ''):
|
||||||
|
self.join_str = ','
|
||||||
|
if self.operator == '#':
|
||||||
|
self.start = '#'
|
||||||
|
if self.operator == '?':
|
||||||
|
self.start = '?'
|
||||||
|
self.join_str = '&'
|
||||||
|
|
||||||
|
if self.operator in ('+', '#'):
|
||||||
|
self.safe = URIVariable.reserved
|
||||||
|
|
||||||
|
def _query_expansion(self, name, value, explode, prefix):
|
||||||
|
"""Expansion method for the '?' and '&' operators."""
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
tuples, items = is_list_of_tuples(value)
|
||||||
|
|
||||||
|
safe = self.safe
|
||||||
|
if list_test(value) and not tuples:
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
if explode:
|
||||||
|
return self.join_str.join(
|
||||||
|
'%s=%s' % (name, quote(v, safe)) for v in value
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
value = ','.join(quote(v, safe) for v in value)
|
||||||
|
return '%s=%s' % (name, value)
|
||||||
|
|
||||||
|
if dict_test(value) or tuples:
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
items = items or sorted(value.items())
|
||||||
|
if explode:
|
||||||
|
return self.join_str.join(
|
||||||
|
'%s=%s' % (
|
||||||
|
quote(k, safe), quote(v, safe)
|
||||||
|
) for k, v in items
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
value = ','.join(
|
||||||
|
'%s,%s' % (
|
||||||
|
quote(k, safe), quote(v, safe)
|
||||||
|
) for k, v in items
|
||||||
|
)
|
||||||
|
return '%s=%s' % (name, value)
|
||||||
|
|
||||||
|
if value:
|
||||||
|
value = value[:prefix] if prefix else value
|
||||||
|
return '%s=%s' % (name, quote(value, safe))
|
||||||
|
return name + '='
|
||||||
|
|
||||||
|
def _label_path_expansion(self, name, value, explode, prefix):
|
||||||
|
"""Label and path expansion method.
|
||||||
|
|
||||||
|
Expands for operators: '/', '.'
|
||||||
|
|
||||||
|
"""
|
||||||
|
join_str = self.join_str
|
||||||
|
safe = self.safe
|
||||||
|
|
||||||
|
if value is None or (len(value) == 0 and value != ''):
|
||||||
|
return None
|
||||||
|
|
||||||
|
tuples, items = is_list_of_tuples(value)
|
||||||
|
|
||||||
|
if list_test(value) and not tuples:
|
||||||
|
if not explode:
|
||||||
|
join_str = ','
|
||||||
|
|
||||||
|
expanded = join_str.join(
|
||||||
|
quote(v, safe) for v in value if value is not None
|
||||||
|
)
|
||||||
|
return expanded if expanded else None
|
||||||
|
|
||||||
|
if dict_test(value) or tuples:
|
||||||
|
items = items or sorted(value.items())
|
||||||
|
format_str = '%s=%s'
|
||||||
|
if not explode:
|
||||||
|
format_str = '%s,%s'
|
||||||
|
join_str = ','
|
||||||
|
|
||||||
|
expanded = join_str.join(
|
||||||
|
format_str % (
|
||||||
|
quote(k, safe), quote(v, safe)
|
||||||
|
) for k, v in items if v is not None
|
||||||
|
)
|
||||||
|
return expanded if expanded else None
|
||||||
|
|
||||||
|
value = value[:prefix] if prefix else value
|
||||||
|
return quote(value, safe)
|
||||||
|
|
||||||
|
def _semi_path_expansion(self, name, value, explode, prefix):
|
||||||
|
"""Expansion method for ';' operator."""
|
||||||
|
join_str = self.join_str
|
||||||
|
safe = self.safe
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.operator == '?':
|
||||||
|
join_str = '&'
|
||||||
|
|
||||||
|
tuples, items = is_list_of_tuples(value)
|
||||||
|
|
||||||
|
if list_test(value) and not tuples:
|
||||||
|
if explode:
|
||||||
|
expanded = join_str.join(
|
||||||
|
'%s=%s' % (
|
||||||
|
name, quote(v, safe)
|
||||||
|
) for v in value if v is not None
|
||||||
|
)
|
||||||
|
return expanded if expanded else None
|
||||||
|
else:
|
||||||
|
value = ','.join(quote(v, safe) for v in value)
|
||||||
|
return '%s=%s' % (name, value)
|
||||||
|
|
||||||
|
if dict_test(value) or tuples:
|
||||||
|
items = items or sorted(value.items())
|
||||||
|
|
||||||
|
if explode:
|
||||||
|
return join_str.join(
|
||||||
|
'%s=%s' % (
|
||||||
|
quote(k, safe), quote(v, safe)
|
||||||
|
) for k, v in items if v is not None
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expanded = ','.join(
|
||||||
|
'%s,%s' % (
|
||||||
|
quote(k, safe), quote(v, safe)
|
||||||
|
) for k, v in items if v is not None
|
||||||
|
)
|
||||||
|
return '%s=%s' % (name, expanded)
|
||||||
|
|
||||||
|
value = value[:prefix] if prefix else value
|
||||||
|
if value:
|
||||||
|
return '%s=%s' % (name, quote(value, safe))
|
||||||
|
|
||||||
|
return name
|
||||||
|
|
||||||
|
def _string_expansion(self, name, value, explode, prefix):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
tuples, items = is_list_of_tuples(value)
|
||||||
|
|
||||||
|
if list_test(value) and not tuples:
|
||||||
|
return ','.join(quote(v, self.safe) for v in value)
|
||||||
|
|
||||||
|
if dict_test(value) or tuples:
|
||||||
|
items = items or sorted(value.items())
|
||||||
|
format_str = '%s=%s' if explode else '%s,%s'
|
||||||
|
|
||||||
|
return ','.join(
|
||||||
|
format_str % (
|
||||||
|
quote(k, self.safe), quote(v, self.safe)
|
||||||
|
) for k, v in items
|
||||||
|
)
|
||||||
|
|
||||||
|
value = value[:prefix] if prefix else value
|
||||||
|
return quote(value, self.safe)
|
||||||
|
|
||||||
|
def expand(self, var_dict=None):
|
||||||
|
"""Expand the variable in question.
|
||||||
|
|
||||||
|
Using ``var_dict`` and the previously parsed defaults, expand this
|
||||||
|
variable and subvariables.
|
||||||
|
|
||||||
|
:param dict var_dict: dictionary of key-value pairs to be used during
|
||||||
|
expansion
|
||||||
|
:returns: dict(variable=value)
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
# (1)
|
||||||
|
v = URIVariable('/var')
|
||||||
|
expansion = v.expand({'var': 'value'})
|
||||||
|
print(expansion)
|
||||||
|
# => {'/var': '/value'}
|
||||||
|
|
||||||
|
# (2)
|
||||||
|
v = URIVariable('?var,hello,x,y')
|
||||||
|
expansion = v.expand({'var': 'value', 'hello': 'Hello World!',
|
||||||
|
'x': '1024', 'y': '768'})
|
||||||
|
print(expansion)
|
||||||
|
# => {'?var,hello,x,y':
|
||||||
|
# '?var=value&hello=Hello%20World%21&x=1024&y=768'}
|
||||||
|
|
||||||
|
"""
|
||||||
|
return_values = []
|
||||||
|
|
||||||
|
for name, opts in self.variables:
|
||||||
|
value = var_dict.get(name, None)
|
||||||
|
if not value and value != '' and name in self.defaults:
|
||||||
|
value = self.defaults[name]
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
expanded = None
|
||||||
|
if self.operator in ('/', '.'):
|
||||||
|
expansion = self._label_path_expansion
|
||||||
|
elif self.operator in ('?', '&'):
|
||||||
|
expansion = self._query_expansion
|
||||||
|
elif self.operator == ';':
|
||||||
|
expansion = self._semi_path_expansion
|
||||||
|
else:
|
||||||
|
expansion = self._string_expansion
|
||||||
|
|
||||||
|
expanded = expansion(name, value, opts['explode'], opts['prefix'])
|
||||||
|
|
||||||
|
if expanded is not None:
|
||||||
|
return_values.append(expanded)
|
||||||
|
|
||||||
|
value = ''
|
||||||
|
if return_values:
|
||||||
|
value = self.start + self.join_str.join(return_values)
|
||||||
|
return {self.original: value}
|
||||||
|
|
||||||
|
|
||||||
|
def is_list_of_tuples(value):
|
||||||
|
if (not value or
|
||||||
|
not isinstance(value, (list, tuple)) or
|
||||||
|
not all(isinstance(t, tuple) and len(t) == 2 for t in value)):
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
return True, value
|
||||||
|
|
||||||
|
|
||||||
|
def list_test(value):
|
||||||
|
return isinstance(value, (list, tuple))
|
||||||
|
|
||||||
|
|
||||||
|
def dict_test(value):
|
||||||
|
return isinstance(value, (dict, collections.MutableMapping))
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
texttype = unicode
|
||||||
|
except NameError: # Python 3
|
||||||
|
texttype = str
|
||||||
|
|
||||||
|
stringlikes = (texttype, bytes)
|
||||||
|
|
||||||
|
|
||||||
|
def _encode(value, encoding='utf-8'):
|
||||||
|
if (isinstance(value, texttype) and
|
||||||
|
getattr(value, 'encode', None) is not None):
|
||||||
|
return value.encode(encoding)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def quote(value, safe):
|
||||||
|
if not isinstance(value, stringlikes):
|
||||||
|
value = str(value)
|
||||||
|
return urllib.quote(_encode(value), safe)
|
||||||
Reference in New Issue
Block a user