Implementation of RFC 6570 URI Templates for Python applications
npx @tessl/cli install tessl/pypi-uritemplate@4.2.0A Python implementation of RFC 6570 URI Templates that enables creation and expansion of URI templates according to the official specification. Provides both class-based and functional interfaces for creating dynamic URIs from templates and variable substitutions.
pip install uritemplatefrom uritemplate import URITemplate, expand, partial, variablesAlternative imports for specific functionality:
from uritemplate import URITemplate # Class-based approach
from uritemplate import expand # Direct expansion function
from uritemplate import partial # Partial expansion function
from uritemplate import variables # Variable extraction functionInternal types and classes (for advanced usage):
from uritemplate.orderedset import OrderedSet
from uritemplate.variable import URIVariable, VariableValue, VariableValueDict, ScalarVariableValuefrom uritemplate import URITemplate, expand
# Basic template expansion using the URITemplate class
template = URITemplate('https://api.github.com/users/{username}/repos')
uri = template.expand(username='octocat')
print(uri) # https://api.github.com/users/octocat/repos
# Direct expansion using the expand function
uri = expand('https://api.github.com/users/{username}/repos', username='octocat')
print(uri) # https://api.github.com/users/octocat/repos
# Multiple variables with different expansion operators
api_template = URITemplate('https://api.github.com{/endpoint}{?page,per_page}')
uri = api_template.expand(endpoint='repos', page=2, per_page=50)
print(uri) # https://api.github.com/repos?page=2&per_page=50
# Using dictionary for variable values
variables_dict = {'username': 'octocat', 'repo': 'Hello-World'}
repo_template = URITemplate('https://api.github.com/repos/{username}/{repo}')
uri = repo_template.expand(variables_dict)
print(uri) # https://api.github.com/repos/octocat/Hello-WorldThe uritemplate package follows a simple, focused design with two main interfaces:
expand, partial, variables) for immediate operationsURITemplate class for reusable template objects with parsing optimizationCore components:
Expand URI templates with variable substitutions using either the functional or class-based interface.
def expand(
uri: str,
var_dict: Optional[VariableValueDict] = None,
**kwargs: VariableValue
) -> str:
"""
Expand the URI template with the given parameters.
Parameters:
- uri: The templated URI to expand
- var_dict: Optional dictionary with variables and values
- **kwargs: Alternative way to pass arguments
Returns:
Expanded URI string
"""class URITemplate:
def __init__(self, uri: str):
"""
Initialize a URI template.
Parameters:
- uri: The URI template string
"""
def expand(
self,
var_dict: Optional[VariableValueDict] = None,
**kwargs: VariableValue
) -> str:
"""
Expand the template with the given parameters.
Parameters:
- var_dict: Optional dictionary with variables and values
- **kwargs: Alternative way to pass arguments
Returns:
Expanded URI string
"""Partially expand templates, leaving unresolved variables as template expressions for later expansion.
def partial(
uri: str,
var_dict: Optional[VariableValueDict] = None,
**kwargs: VariableValue
) -> URITemplate:
"""
Partially expand the template with the given parameters.
If all parameters for the template are not given, return a
partially expanded template.
Parameters:
- uri: The templated URI to expand
- var_dict: Optional dictionary with variables and values
- **kwargs: Alternative way to pass arguments
Returns:
URITemplate instance with partially expanded template
"""class URITemplate:
def partial(
self,
var_dict: Optional[VariableValueDict] = None,
**kwargs: VariableValue
) -> URITemplate:
"""
Partially expand the template with the given parameters.
Parameters:
- var_dict: Optional dictionary with variables and values
- **kwargs: Alternative way to pass arguments
Returns:
URITemplate instance with partially expanded template
"""from uritemplate import partial, URITemplate
# Partial expansion with function
template_str = 'https://api.github.com/repos/{owner}/{repo}{/path}{?page,per_page}'
partial_template = partial(template_str, owner='octocat', repo='Hello-World')
print(str(partial_template)) # https://api.github.com/repos/octocat/Hello-World{/path}{?page,per_page}
# Complete the expansion later
final_uri = partial_template.expand(path='issues', page=1)
print(final_uri) # https://api.github.com/repos/octocat/Hello-World/issues?page=1
# Partial expansion with class
template = URITemplate('https://api.example.com{/version}/users{/user_id}{?fields}')
partial_result = template.partial(version='v1')
final_uri = partial_result.expand(user_id=123, fields='name,email')
print(final_uri) # https://api.example.com/v1/users/123?fields=name,emailExtract all variable names from a URI template for introspection and validation.
def variables(uri: str) -> OrderedSet:
"""
Parse the variables of the template.
This returns all of the variable names in the URI Template.
Parameters:
- uri: The URI template string
Returns:
OrderedSet of variable names found in the template
"""from uritemplate import variables
# Extract variables from template
template_str = 'https://api.github.com/repos/{owner}/{repo}/issues{/number}{?state,labels}'
vars_found = variables(template_str)
print(list(vars_found)) # ['owner', 'repo', 'number', 'state', 'labels']
# Check if specific variables are present
if 'owner' in vars_found and 'repo' in vars_found:
print("Template requires owner and repo parameters")
# Get variable count
print(f"Template contains {len(vars_found)} variables")Access template properties and metadata through the URITemplate class.
class URITemplate:
@property
def uri(self) -> str:
"""The original URI template string."""
@property
def variables(self) -> List[URIVariable]:
"""List of URIVariable objects representing parsed variables."""
@property
def variable_names(self) -> OrderedSet:
"""Set of variable names in the URI template."""from uritemplate import URITemplate
template = URITemplate('https://api.github.com/{endpoint}{?page,per_page}')
# Access original template string
print(template.uri) # https://api.github.com/{endpoint}{?page,per_page}
# Get variable names
print(list(template.variable_names)) # ['endpoint', 'page', 'per_page']
# Template comparison and hashing
template1 = URITemplate('https://api.example.com/{id}')
template2 = URITemplate('https://api.example.com/{id}')
print(template1 == template2) # True
# Templates can be used as dictionary keys
template_cache = {template1: "cached_result"}Access to lower-level components for advanced template manipulation and introspection.
class URIVariable:
def __init__(self, var: str):
"""
Initialize a URI variable from a template expression.
Parameters:
- var: The variable expression string (e.g., "var", "+var", "?var,x,y")
"""
def expand(self, var_dict: Optional[VariableValueDict] = None) -> Mapping[str, str]:
"""
Expand this variable using the provided variable dictionary.
Parameters:
- var_dict: Dictionary of variable names to values
Returns:
Dictionary mapping the original variable expression to its expanded form
"""
# Properties
original: str # The original variable expression
operator: Operator # The expansion operator used
variables: List[Tuple[str, Dict[str, Any]]] # Variable names and their options
variable_names: List[str] # List of variable names in this expression
defaults: Dict[str, str] # Default values for variablesfrom uritemplate.variable import URIVariable
# Parse a complex variable expression
var = URIVariable("?var,hello,x,y")
print(var.variable_names) # ['var', 'hello', 'x', 'y']
print(var.operator) # Operator.form_style_query
# Expand the variable
expansion = var.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'}class URITemplate:
def __init__(self, uri: str): ...
def expand(
self,
var_dict: Optional[VariableValueDict] = None,
**kwargs: VariableValue
) -> str: ...
def partial(
self,
var_dict: Optional[VariableValueDict] = None,
**kwargs: VariableValue
) -> URITemplate: ...
# Properties
uri: str # The original URI template string
variables: List[URIVariable] # List of parsed variable objects
variable_names: OrderedSet # Set of variable names in the template
# Standard methods
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __eq__(self, other: object) -> bool: ...
def __hash__(self) -> int: ...
def expand(
uri: str,
var_dict: Optional[VariableValueDict] = None,
**kwargs: VariableValue
) -> str: ...
def partial(
uri: str,
var_dict: Optional[VariableValueDict] = None,
**kwargs: VariableValue
) -> URITemplate: ...
def variables(uri: str) -> OrderedSet: ...class OrderedSet:
def __init__(self, iterable: Optional[Iterable[str]] = None): ...
def add(self, key: str) -> None: ...
def discard(self, key: str) -> None: ...
def pop(self, last: bool = True) -> str: ...
def __len__(self) -> int: ...
def __contains__(self, key: object) -> bool: ...
def __iter__(self) -> Generator[str, None, None]: ...
def __reversed__(self) -> Generator[str, None, None]: ...
class URIVariable:
def __init__(self, var: str): ...
def expand(self, var_dict: Optional[VariableValueDict] = None) -> Mapping[str, str]: ...
# Properties
original: str # The original variable expression
operator: Operator # The expansion operator
variables: List[Tuple[str, Dict[str, Any]]] # Variable names and options
variable_names: List[str] # List of variable names
defaults: Dict[str, str] # Default values for variables
class Operator(Enum):
"""RFC 6570 URI Template expansion operators."""
default = "" # Simple string expansion: {var}
reserved = "+" # Reserved expansion: {+var}
fragment = "#" # Fragment expansion: {#var}
label_with_dot_prefix = "." # Label expansion: {.var}
path_segment = "/" # Path segment expansion: {/var}
path_style_parameter = ";" # Path-style parameter expansion: {;var}
form_style_query = "?" # Form-style query expansion: {?var}
form_style_query_continuation = "&" # Form-style query continuation: {&var}
# Type aliases for variable values
ScalarVariableValue = Union[int, float, complex, str, None]
VariableValue = Union[
Sequence[ScalarVariableValue],
List[ScalarVariableValue],
Mapping[str, ScalarVariableValue],
Tuple[str, ScalarVariableValue],
ScalarVariableValue
]
VariableValueDict = Dict[str, VariableValue]from uritemplate import URITemplate
# List variables for path segments
template = URITemplate('https://api.example.com/{path*}')
uri = template.expand(path=['users', 'profile', 'settings'])
print(uri) # https://api.example.com/users/profile/settings
# Dictionary variables for query parameters
template = URITemplate('https://api.example.com/search{?params*}')
uri = template.expand(params={'q': 'python', 'sort': 'stars', 'order': 'desc'})
print(uri) # https://api.example.com/search?q=python&sort=stars&order=desc
# Mixed variable types
template = URITemplate('https://api.example.com/{+base}/search{?q,filters*}')
uri = template.expand(
base='https://search.example.com/api/v1',
q='machine learning',
filters={'category': 'tech', 'year': '2023'}
)from uritemplate import URITemplate
class GitHubAPI:
# Class-level template for reuse (parsed once)
repo_template = URITemplate('https://api.github.com/repos/{owner}/{repo}')
issues_template = URITemplate('https://api.github.com/repos/{owner}/{repo}/issues{/number}{?state,labels}')
def __init__(self, owner: str, repo: str):
self.owner = owner
self.repo = repo
def get_repo_url(self) -> str:
return self.repo_template.expand(owner=self.owner, repo=self.repo)
def get_issues_url(self, number: int = None, state: str = None) -> str:
params = {'owner': self.owner, 'repo': self.repo}
if number:
params['number'] = number
if state:
params['state'] = state
return self.issues_template.expand(params)
# Usage
api = GitHubAPI('octocat', 'Hello-World')
print(api.get_repo_url()) # https://api.github.com/repos/octocat/Hello-World
print(api.get_issues_url(state='open')) # https://api.github.com/repos/octocat/Hello-World/issues?state=openThe uritemplate package handles various edge cases gracefully:
from uritemplate import URITemplate, expand
# Missing variables in partial expansion
template = URITemplate('https://api.example.com/{service}/{version}{/endpoint}')
partial_result = template.partial(service='users')
print(str(partial_result)) # https://api.example.com/users/{version}{/endpoint}
# Empty and None values
uri = expand('https://api.example.com{/path}{?query}', path='', query=None)
print(uri) # https://api.example.com/
# Special character encoding
uri = expand('https://api.example.com/search{?q}', q='hello world & special chars!')
print(uri) # https://api.example.com/search?q=hello%20world%20%26%20special%20chars%21