You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
5.1 KiB
Python
140 lines
5.1 KiB
Python
"""Process URI templates per http://tools.ietf.org/html/rfc6570."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from typing import TYPE_CHECKING
|
|
|
|
from .expansions import (CommaExpansion, Expansion,
|
|
FormStyleQueryContinuation, FormStyleQueryExpansion,
|
|
FragmentExpansion, LabelExpansion, Literal,
|
|
PathExpansion, PathStyleExpansion,
|
|
ReservedCommaExpansion, ReservedExpansion, SimpleExpansion)
|
|
|
|
if (TYPE_CHECKING):
|
|
from collections.abc import Iterable
|
|
from .variable import Variable
|
|
|
|
|
|
class ExpansionReservedError(Exception):
|
|
"""Exception thrown for reserved but unsupported expansions."""
|
|
|
|
expansion: str
|
|
|
|
def __init__(self, expansion: str) -> None:
|
|
self.expansion = expansion
|
|
|
|
def __str__(self) -> str:
|
|
"""Convert to string."""
|
|
return 'Unsupported expansion: ' + self.expansion
|
|
|
|
|
|
class ExpansionInvalidError(Exception):
|
|
"""Exception thrown for unknown expansions."""
|
|
|
|
expansion: str
|
|
|
|
def __init__(self, expansion: str) -> None:
|
|
self.expansion = expansion
|
|
|
|
def __str__(self) -> str:
|
|
"""Convert to string."""
|
|
return 'Bad expansion: ' + self.expansion
|
|
|
|
|
|
class URITemplate:
|
|
"""
|
|
URI Template object.
|
|
|
|
Constructor may raise ExpansionReservedError, ExpansionInvalidError, or VariableInvalidError.
|
|
"""
|
|
|
|
expansions: list[Expansion]
|
|
|
|
def __init__(self, template: str) -> None:
|
|
self.expansions = []
|
|
parts = re.split(r'(\{[^\}]*\})', template)
|
|
for part in parts:
|
|
if (part):
|
|
if (('{' == part[0]) and ('}' == part[-1])):
|
|
expansion = part[1:-1]
|
|
if (re.match('^([a-zA-Z0-9_]|%[0-9a-fA-F][0-9a-fA-F]).*$', expansion)):
|
|
self.expansions.append(SimpleExpansion(expansion))
|
|
elif ('+' == part[1]):
|
|
self.expansions.append(ReservedExpansion(expansion))
|
|
elif ('#' == part[1]):
|
|
self.expansions.append(FragmentExpansion(expansion))
|
|
elif ('.' == part[1]):
|
|
self.expansions.append(LabelExpansion(expansion))
|
|
elif ('/' == part[1]):
|
|
self.expansions.append(PathExpansion(expansion))
|
|
elif (';' == part[1]):
|
|
self.expansions.append(PathStyleExpansion(expansion))
|
|
elif ('?' == part[1]):
|
|
self.expansions.append(FormStyleQueryExpansion(expansion))
|
|
elif ('&' == part[1]):
|
|
self.expansions.append(FormStyleQueryContinuation(expansion))
|
|
elif (',' == part[1]):
|
|
if ((1 < len(part)) and ('+' == part[2])):
|
|
self.expansions.append(ReservedCommaExpansion(expansion))
|
|
else:
|
|
self.expansions.append(CommaExpansion(expansion))
|
|
elif (part[1] in '=!@|'):
|
|
raise ExpansionReservedError(part)
|
|
else:
|
|
raise ExpansionInvalidError(part)
|
|
else:
|
|
if (('{' not in part) and ('}' not in part)):
|
|
self.expansions.append(Literal(part))
|
|
else:
|
|
raise ExpansionInvalidError(part)
|
|
|
|
@property
|
|
def variables(self) -> Iterable[Variable]:
|
|
"""Get all variables in template."""
|
|
vars: dict[str, Variable] = {}
|
|
for expansion in self.expansions:
|
|
for var in expansion.variables:
|
|
vars[var.name] = var
|
|
return vars.values()
|
|
|
|
@property
|
|
def variable_names(self) -> Iterable[str]:
|
|
"""Get names of all variables in template."""
|
|
vars: dict[str, Variable] = {}
|
|
for expansion in self.expansions:
|
|
for var in expansion.variables:
|
|
vars[var.name] = var
|
|
return [var.name for var in vars.values()]
|
|
|
|
def expand(self, **kwargs) -> str:
|
|
"""
|
|
Expand the template.
|
|
|
|
May raise ExpansionFailed if a composite value is passed to a variable with a prefix modifier.
|
|
"""
|
|
expanded = [expansion.expand(kwargs) for expansion in self.expansions]
|
|
return ''.join([expansion for expansion in expanded if (expansion is not None)])
|
|
|
|
def partial(self, **kwargs) -> URITemplate:
|
|
"""
|
|
Expand the template, preserving expansions for missing variables.
|
|
|
|
May raise ExpansionFailed if a composite value is passed to a variable with a prefix modifier.
|
|
"""
|
|
expanded = [expansion.partial(kwargs) for expansion in self.expansions]
|
|
return URITemplate(''.join(expanded))
|
|
|
|
@property
|
|
def expanded(self) -> bool:
|
|
"""Determine if template is fully expanded."""
|
|
return (str(self) == self.expand())
|
|
|
|
def __str__(self) -> str:
|
|
"""Convert to string, returns original template."""
|
|
return ''.join([str(expansion) for expansion in self.expansions])
|
|
|
|
def __repr__(self) -> str:
|
|
"""Convert to string, returns original template."""
|
|
return str(self)
|