diff options
author | cylan <cylan@google.com> | 2018-06-04 09:49:39 +0000 |
---|---|---|
committer | cylan <cylan@google.com> | 2018-06-12 07:34:31 +0000 |
commit | 43c5a5f39f223f03912989aef8247975ec36eecb (patch) | |
tree | 5b580b38af5f26a80363afdf694cb666d95a3d7e /uritemplate | |
parent | 4edb22282e4cfaa9250d3fdb2d2f7be9c0a34fb4 (diff) | |
parent | 623fce3c9065dead7e2892f716680ed3064f1a56 (diff) | |
download | uritemplates-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.bp | 30 | ||||
-rw-r--r-- | uritemplate/__init__.py | 26 | ||||
-rw-r--r-- | uritemplate/api.py | 71 | ||||
-rw-r--r-- | uritemplate/template.py | 150 | ||||
-rw-r--r-- | uritemplate/variable.py | 384 |
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) |