new models, frontend functions, public pages
This commit is contained in:
35
.venv/lib/python3.10/site-packages/uritemplate/__init__.py
Normal file
35
.venv/lib/python3.10/site-packages/uritemplate/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""
|
||||
|
||||
uritemplate
|
||||
===========
|
||||
|
||||
URI templates implemented as close to :rfc:`6570` as possible
|
||||
|
||||
See http://uritemplate.rtfd.org/ for documentation
|
||||
|
||||
:copyright:
|
||||
(c) 2013 Ian Stapleton Cordasco
|
||||
:license:
|
||||
Modified BSD Apache License (Version 2.0), see LICENSE for more details
|
||||
and either LICENSE.BSD or LICENSE.APACHE for the details of those specific
|
||||
licenses
|
||||
|
||||
"""
|
||||
|
||||
__title__ = "uritemplate"
|
||||
__author__ = "Ian Stapleton Cordasco"
|
||||
__license__ = "Modified BSD or Apache License, Version 2.0"
|
||||
__copyright__ = "Copyright 2013 Ian Stapleton Cordasco"
|
||||
__version__ = "4.1.1"
|
||||
__version_info__ = tuple(
|
||||
int(i) for i in __version__.split(".") if i.isdigit()
|
||||
)
|
||||
|
||||
from uritemplate.api import (
|
||||
URITemplate,
|
||||
expand,
|
||||
partial,
|
||||
variables,
|
||||
)
|
||||
|
||||
__all__ = ("URITemplate", "expand", "partial", "variables")
|
||||
85
.venv/lib/python3.10/site-packages/uritemplate/api.py
Normal file
85
.venv/lib/python3.10/site-packages/uritemplate/api.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
|
||||
uritemplate.api
|
||||
===============
|
||||
|
||||
This module contains the very simple API provided by uritemplate.
|
||||
|
||||
"""
|
||||
import typing as t
|
||||
|
||||
from uritemplate import variable
|
||||
from uritemplate.orderedset import OrderedSet
|
||||
from uritemplate.template import URITemplate
|
||||
|
||||
__all__ = ("OrderedSet", "URITemplate", "expand", "partial", "variables")
|
||||
|
||||
|
||||
def expand(
|
||||
uri: str,
|
||||
var_dict: t.Optional[variable.VariableValueDict] = None,
|
||||
**kwargs: variable.VariableValue,
|
||||
) -> str:
|
||||
"""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: str,
|
||||
var_dict: t.Optional[variable.VariableValueDict] = None,
|
||||
**kwargs: variable.VariableValue,
|
||||
) -> URITemplate:
|
||||
"""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: str) -> OrderedSet:
|
||||
"""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 OrderedSet(URITemplate(uri).variable_names)
|
||||
92
.venv/lib/python3.10/site-packages/uritemplate/orderedset.py
Normal file
92
.venv/lib/python3.10/site-packages/uritemplate/orderedset.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# From: https://github.com/ActiveState/code/blob/master/recipes/Python/576696_OrderedSet_with_Weakrefs/ # noqa
|
||||
import typing as t
|
||||
import weakref
|
||||
|
||||
|
||||
class Link:
|
||||
"""Representation of one item in a doubly-linked list."""
|
||||
|
||||
__slots__ = ("prev", "next", "key", "__weakref__")
|
||||
prev: "Link"
|
||||
next: "Link"
|
||||
key: str
|
||||
|
||||
|
||||
class OrderedSet(t.MutableSet[str]):
|
||||
"""A set that remembers the order in which items were added."""
|
||||
|
||||
# Big-O running times for all methods are the same as for regular sets.
|
||||
# The internal self.__map dictionary maps keys to links in a doubly linked
|
||||
# list. The circular doubly linked list starts and ends with a sentinel
|
||||
# element. The sentinel element never gets deleted (this simplifies the
|
||||
# algorithm). The prev/next links are weakref proxies (to prevent circular
|
||||
# references). Individual links are kept alive by the hard reference in
|
||||
# self.__map. Those hard references disappear when a key is deleted from
|
||||
# an OrderedSet.
|
||||
|
||||
def __init__(self, iterable: t.Optional[t.Iterable[str]] = None):
|
||||
self.__root = root = Link() # sentinel node for doubly linked list
|
||||
root.prev = root.next = root
|
||||
self.__map: t.MutableMapping[str, Link] = {} # key --> link
|
||||
if iterable is not None:
|
||||
self |= iterable # type: ignore
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self.__map)
|
||||
|
||||
def __contains__(self, key: object) -> bool:
|
||||
return key in self.__map
|
||||
|
||||
def add(self, key: str) -> None:
|
||||
# Store new key in a new link at the end of the linked list
|
||||
if key not in self.__map:
|
||||
self.__map[key] = link = Link()
|
||||
root = self.__root
|
||||
last = root.prev
|
||||
link.prev, link.next, link.key = last, root, key
|
||||
last.next = root.prev = weakref.proxy(link)
|
||||
|
||||
def discard(self, key: str) -> None:
|
||||
# Remove an existing item using self.__map to find the link which is
|
||||
# then removed by updating the links in the predecessor and successors.
|
||||
if key in self.__map:
|
||||
link = self.__map.pop(key)
|
||||
link.prev.next = link.next
|
||||
link.next.prev = link.prev
|
||||
|
||||
def __iter__(self) -> t.Generator[str, None, None]:
|
||||
# Traverse the linked list in order.
|
||||
root = self.__root
|
||||
curr = root.next
|
||||
while curr is not root:
|
||||
yield curr.key
|
||||
curr = curr.next
|
||||
|
||||
def __reversed__(self) -> t.Generator[str, None, None]:
|
||||
# Traverse the linked list in reverse order.
|
||||
root = self.__root
|
||||
curr = root.prev
|
||||
while curr is not root:
|
||||
yield curr.key
|
||||
curr = curr.prev
|
||||
|
||||
def pop(self, last: bool = True) -> str:
|
||||
if not self:
|
||||
raise KeyError("set is empty")
|
||||
key = next(reversed(self)) if last else next(iter(self))
|
||||
self.discard(key)
|
||||
return key
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if not self:
|
||||
return f"{self.__class__.__name__}()"
|
||||
return f"{self.__class__.__name__}({list(self)!r})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, OrderedSet):
|
||||
return len(self) == len(other) and list(self) == list(other)
|
||||
other = t.cast(t.Iterable[str], other)
|
||||
return not self.isdisjoint(other)
|
||||
169
.venv/lib/python3.10/site-packages/uritemplate/template.py
Normal file
169
.venv/lib/python3.10/site-packages/uritemplate/template.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
|
||||
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
|
||||
import typing as t
|
||||
|
||||
from uritemplate import orderedset
|
||||
from uritemplate import variable
|
||||
|
||||
template_re = re.compile("{([^}]+)}")
|
||||
|
||||
|
||||
def _merge(
|
||||
var_dict: t.Optional[variable.VariableValueDict],
|
||||
overrides: variable.VariableValueDict,
|
||||
) -> variable.VariableValueDict:
|
||||
if var_dict:
|
||||
opts = var_dict.copy()
|
||||
opts.update(overrides)
|
||||
return opts
|
||||
return overrides
|
||||
|
||||
|
||||
class URITemplate:
|
||||
|
||||
"""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: str):
|
||||
#: The original URI to be parsed.
|
||||
self.uri: str = uri
|
||||
#: A list of the variables in the URI. They are stored as
|
||||
#: :class:`~uritemplate.variable.URIVariable`\ s
|
||||
self.variables: t.List[variable.URIVariable] = [
|
||||
variable.URIVariable(m.groups()[0])
|
||||
for m in template_re.finditer(self.uri)
|
||||
]
|
||||
#: A set of variable names in the URI.
|
||||
self.variable_names = orderedset.OrderedSet()
|
||||
for var in self.variables:
|
||||
for name in var.variable_names:
|
||||
self.variable_names.add(name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return 'URITemplate("%s")' % self
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.uri
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, URITemplate):
|
||||
return NotImplemented
|
||||
return self.uri == other.uri
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.uri)
|
||||
|
||||
def _expand(
|
||||
self, var_dict: variable.VariableValueDict, replace: bool
|
||||
) -> str:
|
||||
if not self.variables:
|
||||
return self.uri
|
||||
|
||||
expansion = var_dict
|
||||
expanded: t.Dict[str, str] = {}
|
||||
for v in self.variables:
|
||||
expanded.update(v.expand(expansion))
|
||||
|
||||
def replace_all(match: "re.Match[str]") -> str:
|
||||
return expanded.get(match.groups()[0], "")
|
||||
|
||||
def replace_partial(match: "re.Match[str]") -> str:
|
||||
match_group = match.groups()[0]
|
||||
var = "{%s}" % match_group
|
||||
return expanded.get(match_group) or var
|
||||
|
||||
replace_func = replace_partial if replace else replace_all
|
||||
|
||||
return template_re.sub(replace_func, self.uri)
|
||||
|
||||
def expand(
|
||||
self,
|
||||
var_dict: t.Optional[variable.VariableValueDict] = None,
|
||||
**kwargs: variable.VariableValue,
|
||||
) -> str:
|
||||
"""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: t.Optional[variable.VariableValueDict] = None,
|
||||
**kwargs: variable.VariableValue,
|
||||
) -> "URITemplate":
|
||||
"""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))
|
||||
419
.venv/lib/python3.10/site-packages/uritemplate/variable.py
Normal file
419
.venv/lib/python3.10/site-packages/uritemplate/variable.py
Normal file
@@ -0,0 +1,419 @@
|
||||
"""
|
||||
|
||||
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.abc
|
||||
import typing as t
|
||||
import urllib.parse
|
||||
|
||||
ScalarVariableValue = t.Union[int, float, complex, str]
|
||||
VariableValue = t.Union[
|
||||
t.Sequence[ScalarVariableValue],
|
||||
t.Mapping[str, ScalarVariableValue],
|
||||
t.Tuple[str, ScalarVariableValue],
|
||||
ScalarVariableValue,
|
||||
]
|
||||
VariableValueDict = t.Dict[str, VariableValue]
|
||||
|
||||
|
||||
class URIVariable:
|
||||
|
||||
"""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: str):
|
||||
#: The original string that comes through with the variable
|
||||
self.original: str = var
|
||||
#: The operator for the variable
|
||||
self.operator: str = ""
|
||||
#: List of safe characters when quoting the string
|
||||
self.safe: str = ""
|
||||
#: List of variables in this variable
|
||||
self.variables: t.List[
|
||||
t.Tuple[str, t.MutableMapping[str, t.Any]]
|
||||
] = []
|
||||
#: List of variable names
|
||||
self.variable_names: t.List[str] = []
|
||||
#: List of defaults passed in
|
||||
self.defaults: t.MutableMapping[str, ScalarVariableValue] = {}
|
||||
# Parse the variable itself.
|
||||
self.parse()
|
||||
self.post_parse()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "URIVariable(%s)" % self
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.original
|
||||
|
||||
def parse(self) -> None:
|
||||
"""Parse the variable.
|
||||
|
||||
This finds the:
|
||||
- operator,
|
||||
- set of safe characters,
|
||||
- variables, and
|
||||
- defaults.
|
||||
|
||||
"""
|
||||
var_list_str = self.original
|
||||
if self.original[0] in URIVariable.operators:
|
||||
self.operator = self.original[0]
|
||||
var_list_str = self.original[1:]
|
||||
|
||||
if self.operator in URIVariable.operators[:2]:
|
||||
self.safe = URIVariable.reserved
|
||||
|
||||
var_list = var_list_str.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: t.Optional[int] = None
|
||||
if ":" in name:
|
||||
name, prefix_str = tuple(name.split(":", 1))
|
||||
prefix = int(prefix_str)
|
||||
|
||||
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) -> None:
|
||||
"""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: str,
|
||||
value: VariableValue,
|
||||
explode: bool,
|
||||
prefix: t.Optional[int],
|
||||
) -> t.Optional[str]:
|
||||
"""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
|
||||
value = t.cast(t.Sequence[ScalarVariableValue], value)
|
||||
if explode:
|
||||
return self.join_str.join(
|
||||
f"{name}={quote(v, safe)}" for v in value
|
||||
)
|
||||
else:
|
||||
value = ",".join(quote(v, safe) for v in value)
|
||||
return f"{name}={value}"
|
||||
|
||||
if dict_test(value) or tuples:
|
||||
if not value:
|
||||
return None
|
||||
value = t.cast(t.Mapping[str, ScalarVariableValue], value)
|
||||
items = items or sorted(value.items())
|
||||
if explode:
|
||||
return self.join_str.join(
|
||||
f"{quote(k, safe)}={quote(v, safe)}" for k, v in items
|
||||
)
|
||||
else:
|
||||
value = ",".join(
|
||||
f"{quote(k, safe)},{quote(v, safe)}" for k, v in items
|
||||
)
|
||||
return f"{name}={value}"
|
||||
|
||||
if value:
|
||||
value = t.cast(t.Text, value)
|
||||
value = value[:prefix] if prefix else value
|
||||
return f"{name}={quote(value, safe)}"
|
||||
return name + "="
|
||||
|
||||
def _label_path_expansion(
|
||||
self,
|
||||
name: str,
|
||||
value: VariableValue,
|
||||
explode: bool,
|
||||
prefix: t.Optional[int],
|
||||
) -> t.Optional[str]:
|
||||
"""Label and path expansion method.
|
||||
|
||||
Expands for operators: '/', '.'
|
||||
|
||||
"""
|
||||
join_str = self.join_str
|
||||
safe = self.safe
|
||||
|
||||
if value is None or (
|
||||
not isinstance(value, (str, int, float, complex))
|
||||
and len(value) == 0
|
||||
):
|
||||
return None
|
||||
|
||||
tuples, items = is_list_of_tuples(value)
|
||||
|
||||
if list_test(value) and not tuples:
|
||||
if not explode:
|
||||
join_str = ","
|
||||
|
||||
value = t.cast(t.Sequence[ScalarVariableValue], value)
|
||||
fragments = [quote(v, safe) for v in value if v is not None]
|
||||
return join_str.join(fragments) if fragments else None
|
||||
|
||||
if dict_test(value) or tuples:
|
||||
value = t.cast(t.Mapping[str, ScalarVariableValue], value)
|
||||
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 = t.cast(t.Text, value)
|
||||
value = value[:prefix] if prefix else value
|
||||
return quote(value, safe)
|
||||
|
||||
def _semi_path_expansion(
|
||||
self,
|
||||
name: str,
|
||||
value: VariableValue,
|
||||
explode: bool,
|
||||
prefix: t.Optional[int],
|
||||
) -> t.Optional[str]:
|
||||
"""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:
|
||||
value = t.cast(t.Sequence[ScalarVariableValue], value)
|
||||
if explode:
|
||||
expanded = join_str.join(
|
||||
f"{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 f"{name}={value}"
|
||||
|
||||
if dict_test(value) or tuples:
|
||||
value = t.cast(t.Mapping[str, ScalarVariableValue], value)
|
||||
items = items or sorted(value.items())
|
||||
|
||||
if explode:
|
||||
return join_str.join(
|
||||
f"{quote(k, safe)}={quote(v, safe)}"
|
||||
for k, v in items
|
||||
if v is not None
|
||||
)
|
||||
else:
|
||||
expanded = ",".join(
|
||||
f"{quote(k, safe)},{quote(v, safe)}"
|
||||
for k, v in items
|
||||
if v is not None
|
||||
)
|
||||
return f"{name}={expanded}"
|
||||
|
||||
value = t.cast(t.Text, value)
|
||||
value = value[:prefix] if prefix else value
|
||||
if value:
|
||||
return f"{name}={quote(value, safe)}"
|
||||
|
||||
return name
|
||||
|
||||
def _string_expansion(
|
||||
self,
|
||||
name: str,
|
||||
value: VariableValue,
|
||||
explode: bool,
|
||||
prefix: t.Optional[int],
|
||||
) -> t.Optional[str]:
|
||||
if value is None:
|
||||
return None
|
||||
|
||||
tuples, items = is_list_of_tuples(value)
|
||||
|
||||
if list_test(value) and not tuples:
|
||||
value = t.cast(t.Sequence[ScalarVariableValue], value)
|
||||
return ",".join(quote(v, self.safe) for v in value)
|
||||
|
||||
if dict_test(value) or tuples:
|
||||
value = t.cast(t.Mapping[str, ScalarVariableValue], value)
|
||||
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 = t.cast(t.Text, value)
|
||||
value = value[:prefix] if prefix else value
|
||||
return quote(value, self.safe)
|
||||
|
||||
def expand(
|
||||
self, var_dict: t.Optional[VariableValueDict] = None
|
||||
) -> t.Mapping[str, str]:
|
||||
"""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 = []
|
||||
if var_dict is None:
|
||||
return {self.original: self.original}
|
||||
|
||||
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: t.Any,
|
||||
) -> t.Tuple[bool, t.Optional[t.Sequence[t.Tuple[str, ScalarVariableValue]]]]:
|
||||
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: t.Any) -> bool:
|
||||
return isinstance(value, (list, tuple))
|
||||
|
||||
|
||||
def dict_test(value: t.Any) -> bool:
|
||||
return isinstance(value, (dict, collections.abc.MutableMapping))
|
||||
|
||||
|
||||
def _encode(value: t.AnyStr, encoding: str = "utf-8") -> bytes:
|
||||
if isinstance(value, str):
|
||||
return value.encode(encoding)
|
||||
return value
|
||||
|
||||
|
||||
def quote(value: t.Any, safe: str) -> str:
|
||||
if not isinstance(value, (str, bytes)):
|
||||
value = str(value)
|
||||
return urllib.parse.quote(_encode(value), safe)
|
||||
Reference in New Issue
Block a user