aboutsummaryrefslogtreecommitdiff
path: root/uritemplate
diff options
context:
space:
mode:
authorcylan <cylan@google.com>2018-06-04 09:49:39 +0000
committercylan <cylan@google.com>2018-06-12 07:34:31 +0000
commit43c5a5f39f223f03912989aef8247975ec36eecb (patch)
tree5b580b38af5f26a80363afdf694cb666d95a3d7e /uritemplate
parent4edb22282e4cfaa9250d3fdb2d2f7be9c0a34fb4 (diff)
parent623fce3c9065dead7e2892f716680ed3064f1a56 (diff)
downloaduritemplates-43c5a5f39f223f03912989aef8247975ec36eecb.tar.gz
Merge commit '623fce3' into uritemplate 3.0.0.android-p-preview-5android-p-preview-4
Initial commitcd of uritemplate 3.0.0. Added: - Android.bp - MODULE_LICENSE_APACHE2 - NOTICE - METADATA Bug: b/80314772 Test: Complied acloud with uritemplate and was able to import uritemplate. Change-Id: I639ff73317d8c528e978477444ded25600220b79
Diffstat (limited to 'uritemplate')
-rw-r--r--uritemplate/Android.bp30
-rw-r--r--uritemplate/__init__.py26
-rw-r--r--uritemplate/api.py71
-rw-r--r--uritemplate/template.py150
-rw-r--r--uritemplate/variable.py384
5 files changed, 661 insertions, 0 deletions
diff --git a/uritemplate/Android.bp b/uritemplate/Android.bp
new file mode 100644
index 0000000..706dba5
--- /dev/null
+++ b/uritemplate/Android.bp
@@ -0,0 +1,30 @@
+// Copyright 2018 Google Inc. All rights reserved.
+//
+// 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.
+python_library {
+ name: "py-uritemplate",
+ host_supported: true,
+ srcs: [
+ "*.py",
+ ],
+ version: {
+ py2: {
+ enabled: true,
+ },
+ py3: {
+ enabled: true,
+ },
+ },
+ pkg_path: "uritemplate",
+}
+
diff --git a/uritemplate/__init__.py b/uritemplate/__init__.py
new file mode 100644
index 0000000..40c0320
--- /dev/null
+++ b/uritemplate/__init__.py
@@ -0,0 +1,26 @@
+"""
+
+uritemplate
+===========
+
+The URI templating library for humans.
+
+See http://uritemplate.rtfd.org/ for documentation
+
+:copyright: (c) 2013-2015 Ian Cordasco
+:license: Modified BSD, see LICENSE for more details
+
+"""
+
+__title__ = 'uritemplate'
+__author__ = 'Ian Cordasco'
+__license__ = 'Modified BSD or Apache License, Version 2.0'
+__copyright__ = 'Copyright 2013 Ian Cordasco'
+__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')
diff --git a/uritemplate/api.py b/uritemplate/api.py
new file mode 100644
index 0000000..37c7c45
--- /dev/null
+++ b/uritemplate/api.py
@@ -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)
diff --git a/uritemplate/template.py b/uritemplate/template.py
new file mode 100644
index 0000000..c9d7c7e
--- /dev/null
+++ b/uritemplate/template.py
@@ -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))
diff --git a/uritemplate/variable.py b/uritemplate/variable.py
new file mode 100644
index 0000000..1842830
--- /dev/null
+++ b/uritemplate/variable.py
@@ -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)