mbuali's picture
Upload folder using huggingface_hub
d1ceb73 verified
"""Process URI templates per http://tools.ietf.org/html/rfc6570."""
from __future__ import annotations
import collections
from typing import Any, TYPE_CHECKING, cast
from .charset import Charset
from .variable import Variable
if (TYPE_CHECKING):
from collections.abc import Iterable, Mapping
class ExpansionFailedError(Exception):
"""Exception thrown when expansions fail."""
variable: str
def __init__(self, variable: str) -> None:
self.variable = variable
def __str__(self) -> str:
"""Convert to string."""
return 'Bad expansion: ' + self.variable
class Expansion:
"""
Base class for template expansions.
https://tools.ietf.org/html/rfc6570#section-3
"""
def __init__(self) -> None:
pass
@property
def variables(self) -> Iterable[Variable]:
"""Get all variables in this expansion."""
return []
@property
def variable_names(self) -> Iterable[str]:
"""Get the names of all variables in this expansion."""
return []
def _encode(self, value: str, legal: str, pct_encoded: bool) -> str:
"""Encode a string into legal values."""
output = ''
index = 0
while (index < len(value)):
codepoint = value[index]
if (codepoint in legal):
output += codepoint
elif (pct_encoded and ('%' == codepoint)
and ((index + 2) < len(value))
and (value[index + 1] in Charset.HEX_DIGIT)
and (value[index + 2] in Charset.HEX_DIGIT)):
output += value[index:index + 3]
index += 2
else:
utf8 = codepoint.encode('utf8')
for byte in utf8:
output += '%' + Charset.HEX_DIGIT[int(byte / 16)] + Charset.HEX_DIGIT[byte % 16]
index += 1
return output
def _uri_encode_value(self, value: str) -> str:
"""Encode a value into uri encoding."""
return self._encode(value, Charset.UNRESERVED, False)
def _uri_encode_name(self, name: (str | int)) -> str:
"""Encode a variable name into uri encoding."""
return self._encode(str(name), Charset.UNRESERVED + Charset.RESERVED, True) if (name) else ''
def _join(self, prefix: str, joiner: str, value: str) -> str:
"""Join a prefix to a value."""
if (prefix):
return prefix + joiner + value
return value
def _encode_str(self, variable: Variable, name: str, value: str, prefix: str, joiner: str, first: bool) -> str:
"""Encode a string value for a variable."""
if (variable.max_length):
if (not first):
raise ExpansionFailedError(str(variable))
return self._join(prefix, joiner, self._uri_encode_value(value[:variable.max_length]))
return self._join(prefix, joiner, self._uri_encode_value(value))
def _encode_dict_item(self, variable: Variable, name: str, key: (int | str), item: Any,
delim: str, prefix: str, joiner: str, first: bool) -> (str | None):
"""Encode a dict item for a variable."""
joiner = '=' if (variable.explode) else ','
if (variable.array):
name = self._uri_encode_name(key)
prefix = (prefix + '[' + name + ']') if (prefix and not first) else name
else:
prefix = self._join(prefix, '.', self._uri_encode_name(key))
return self._encode_var(variable, str(key), item, delim, prefix, joiner, False)
def _encode_list_item(self, variable: Variable, name: str, index: int, item: Any,
delim: str, prefix: str, joiner: str, first: bool) -> (str | None):
"""Encode a list item for a variable."""
if (variable.array):
prefix = prefix + '[' + str(index) + ']' if (prefix) else ''
return self._encode_var(variable, '', item, delim, prefix, joiner, False)
return self._encode_var(variable, name, item, delim, prefix, '.', False)
def _encode_var(self, variable: Variable, name: str, value: Any,
delim: str = ',', prefix: str = '', joiner: str = '=', first: bool = True) -> (str | None):
"""Encode a variable."""
if (isinstance(value, str)):
return self._encode_str(variable, name, value, prefix, joiner, first)
elif (isinstance(value, collections.abc.Mapping)):
if (len(value)):
encoded_items = [self._encode_dict_item(variable, name, key, value[key], delim, prefix, joiner, first)
for key in value.keys()]
return delim.join([item for item in encoded_items if (item is not None)])
return None
elif (isinstance(value, collections.abc.Sequence)):
if (len(value)):
encoded_items = [self._encode_list_item(variable, name, index, item, delim, prefix, joiner, first)
for index, item in enumerate(value)]
return delim.join([item for item in encoded_items if (item is not None)])
return None
elif (isinstance(value, bool)):
return self._encode_str(variable, name, str(value).lower(), prefix, joiner, first)
else:
return self._encode_str(variable, name, str(value), prefix, joiner, first)
def expand(self, values: Mapping[str, Any]) -> (str | None):
"""Expand values."""
return None
def partial(self, values: Mapping[str, Any]) -> str:
"""Perform partial expansion."""
return ''
class Literal(Expansion):
"""
A literal expansion.
https://tools.ietf.org/html/rfc6570#section-3.1
"""
value: str
def __init__(self, value: str) -> None:
super().__init__()
self.value = value
def expand(self, values: Mapping[str, Any]) -> (str | None):
"""Perform exansion."""
return self._encode(self.value, (Charset.UNRESERVED + Charset.RESERVED), True)
def __str__(self) -> str:
"""Convert to string."""
return self.value
class ExpressionExpansion(Expansion):
"""
Base class for expression expansions.
https://tools.ietf.org/html/rfc6570#section-3.2
"""
operator = ''
partial_operator = ','
output_prefix = ''
var_joiner = ','
partial_joiner = ','
vars: list[Variable]
trailing_joiner: str = ''
def __init__(self, variables: str) -> None:
super().__init__()
if (variables and (variables[-1] in (',', '.', '/', ';', '&'))):
self.trailing_joiner = variables[-1]
variables = variables[:-1]
self.vars = [Variable(var) for var in variables.split(',')]
@property
def variables(self) -> Iterable[Variable]:
"""Get all variables."""
return list(self.vars)
@property
def variable_names(self) -> Iterable[str]:
"""Get names of all variables."""
return [var.name for var in self.vars]
def _expand_var(self, variable: Variable, value: Any) -> (str | None):
"""Expand a single variable."""
return self._encode_var(variable, self._uri_encode_name(variable.name), value)
def expand(self, values: Mapping[str, Any]) -> (str | None):
"""Expand all variables, skip missing values."""
expanded_vars: list[str] = []
for var in self.vars:
value = values.get(var.key, var.default)
if (value is not None):
expanded_var = self._expand_var(var, value)
if (expanded_var is not None):
expanded_vars.append(expanded_var)
if (expanded_vars):
return ((self.output_prefix if (not self.trailing_joiner) else '') + self.var_joiner.join(expanded_vars)
+ self.trailing_joiner)
return None
def partial(self, values: Mapping[str, Any]) -> str:
"""Expand all variables, replace missing values with expansions."""
expanded_vars: list[str] = []
missing_vars: list[Variable] = []
result: list[tuple[(list[str] | None), (list[Variable] | None)]] = []
for var in self.vars:
value = values.get(var.name, var.default)
if (value is not None):
expanded_var = self._expand_var(var, value)
if (expanded_var is not None):
if (missing_vars):
result.append((None, missing_vars))
missing_vars = []
expanded_vars.append(expanded_var)
else:
if (expanded_vars):
result.append((expanded_vars, None))
expanded_vars = []
missing_vars.append(var)
if (expanded_vars):
result.append((expanded_vars, None))
if (missing_vars):
result.append((None, missing_vars))
output: str = ''
first = True
for index, (expanded, missing) in enumerate(result):
last = (index == (len(result) - 1))
if (expanded):
output += ((self.output_prefix if (first and (not self.trailing_joiner)) else '')
+ self.var_joiner.join(expanded) + self.trailing_joiner)
else:
output += ((self.output_prefix if (first and not last) else (self.var_joiner if (not last) else ''))
+ '{' + (self.operator if (first) else self.partial_operator)
+ ','.join([str(var) for var in cast('list[Variable]', missing)])
+ (self.partial_joiner if (not last) else '') + '}')
first = False
return output
def __str__(self) -> str:
"""Convert to string."""
return ('{' + self.operator + ','.join([str(var) for var in self.vars]) + self.trailing_joiner + '}')
class SimpleExpansion(ExpressionExpansion):
"""
Simple String expansion {var}.
https://tools.ietf.org/html/rfc6570#section-3.2.2
"""
def __init__(self, variables: str) -> None:
super().__init__(variables)
class ReservedExpansion(ExpressionExpansion):
"""
Reserved Expansion {+var}.
https://tools.ietf.org/html/rfc6570#section-3.2.3
"""
operator = '+'
partial_operator = ',+'
def __init__(self, variables: str) -> None:
super().__init__(variables[1:])
def _uri_encode_value(self, value: str) -> str:
"""Encode a value into uri encoding."""
return self._encode(value, (Charset.UNRESERVED + Charset.RESERVED), True)
class FragmentExpansion(ReservedExpansion):
"""
Fragment Expansion {#var}.
https://tools.ietf.org/html/rfc6570#section-3.2.4
"""
operator = '#'
output_prefix = '#'
def __init__(self, variables: str) -> None:
super().__init__(variables)
class LabelExpansion(ExpressionExpansion):
"""
Label Expansion with Dot-Prefix {.var}.
https://tools.ietf.org/html/rfc6570#section-3.2.5
"""
operator = '.'
partial_operator = '.'
output_prefix = '.'
var_joiner = '.'
partial_joiner = '.'
def __init__(self, variables: str) -> None:
super().__init__(variables[1:])
def _expand_var(self, variable: Variable, value: Any) -> (str | None):
"""Expand a single variable."""
return self._encode_var(variable, self._uri_encode_name(variable.name), value,
delim=('.' if variable.explode else ','))
class PathExpansion(ExpressionExpansion):
"""
Path Segment Expansion {/var}.
https://tools.ietf.org/html/rfc6570#section-3.2.6
"""
operator = '/'
partial_operator = '/'
output_prefix = '/'
var_joiner = '/'
partial_joiner = '/'
def __init__(self, variables: str) -> None:
super().__init__(variables[1:])
def _expand_var(self, variable: Variable, value: Any) -> (str | None):
"""Expand a single variable."""
return self._encode_var(variable, self._uri_encode_name(variable.name), value,
delim=('/' if variable.explode else ','))
class PathStyleExpansion(ExpressionExpansion):
"""
Path-Style Parameter Expansion {;var}.
https://tools.ietf.org/html/rfc6570#section-3.2.7
"""
operator = ';'
partial_operator = ';'
output_prefix = ';'
var_joiner = ';'
partial_joiner = ';'
def __init__(self, variables: str) -> None:
super().__init__(variables[1:])
def _encode_str(self, variable: Variable, name: str, value: Any, prefix: str, joiner: str, first: bool) -> str:
"""Encode a string for a variable."""
if (variable.array):
if (name):
prefix = prefix + '[' + name + ']' if (prefix) else name
elif (variable.explode):
prefix = self._join(prefix, '.', name)
return super()._encode_str(variable, name, value, prefix, joiner, first)
def _encode_dict_item(self, variable: Variable, name: str, key: (int | str), item: Any,
delim: str, prefix: str, joiner: str, first: bool) -> (str | None):
"""Encode a dict item for a variable."""
if (variable.array):
if (name):
prefix = prefix + '[' + name + ']' if (prefix) else name
if (prefix and not first):
prefix = (prefix + '[' + self._uri_encode_name(key) + ']')
else:
prefix = self._uri_encode_name(key)
elif (variable.explode):
prefix = self._join(prefix, '.', name) if (not first) else ''
else:
prefix = self._join(prefix, '.', self._uri_encode_name(key))
joiner = ','
return self._encode_var(variable, self._uri_encode_name(key) if (not variable.array) else '', item,
delim, prefix, joiner, False)
def _encode_list_item(self, variable: Variable, name: str, index: int, item: Any,
delim: str, prefix: str, joiner: str, first: bool) -> (str | None):
"""Encode a list item for a variable."""
if (variable.array):
if (name):
prefix = prefix + '[' + name + ']' if (prefix) else name
return self._encode_var(variable, str(index), item, delim, prefix, joiner, False)
return self._encode_var(variable, name, item, delim, prefix, '=' if (variable.explode) else '.', False)
def _expand_var(self, variable: Variable, value: Any) -> (str | None):
"""Expand a single variable."""
if (variable.explode):
return self._encode_var(variable, self._uri_encode_name(variable.name), value, delim=';')
value = self._encode_var(variable, self._uri_encode_name(variable.name), value, delim=',')
return (self._uri_encode_name(variable.name) + '=' + value) if (value) else variable.name
class FormStyleQueryExpansion(PathStyleExpansion):
"""
Form-Style Query Expansion {?var}.
https://tools.ietf.org/html/rfc6570#section-3.2.8
"""
operator = '?'
partial_operator = '&'
output_prefix = '?'
var_joiner = '&'
partial_joiner = '&'
def __init__(self, variables: str) -> None:
super().__init__(variables)
def _expand_var(self, variable: Variable, value: Any) -> (str | None):
"""Expand a single variable."""
if (variable.explode):
return self._encode_var(variable, self._uri_encode_name(variable.name), value, delim='&')
value = self._encode_var(variable, self._uri_encode_name(variable.name), value, delim=',')
return (self._uri_encode_name(variable.name) + '=' + value) if (value is not None) else None
class FormStyleQueryContinuation(FormStyleQueryExpansion):
"""
Form-Style Query Continuation {&var}.
https://tools.ietf.org/html/rfc6570#section-3.2.9
"""
operator = '&'
output_prefix = '&'
def __init__(self, variables: str) -> None:
super().__init__(variables)
# non-standard extension
class CommaExpansion(ExpressionExpansion):
"""
Label Expansion with Comma-Prefix {,var}.
Non-standard extension to support partial expansions.
"""
operator = ','
output_prefix = ','
def __init__(self, variables: str) -> None:
super().__init__(variables[1:])
def _expand_var(self, variable: Variable, value: Any) -> (str | None):
"""Expand a single variable."""
return self._encode_var(variable, self._uri_encode_name(variable.name), value,
delim=('.' if variable.explode else ','))
class ReservedCommaExpansion(ReservedExpansion):
"""
Reserved Expansion with comma prefix {,+var}.
Non-standard extension to support partial expansions.
"""
operator = ',+'
output_prefix = ','
def __init__(self, variables: str) -> None:
super().__init__(variables[1:])
def _expand_var(self, variable: Variable, value: Any) -> (str | None):
"""Expand a single variable."""
return self._encode_var(variable, self._uri_encode_name(variable.name), value,
delim=('.' if variable.explode else ','))