Source code for hyperpython.fragment

import re
from collections import OrderedDict

from .core import BaseElement

identity = (lambda x: x)
FRAGMENT_REGISTRY = OrderedDict()
SIMPLE_PATH_REGISTRY = {}
PATH_REGEX = re.compile(r'<[a-zA-Z]\w*(?::[a-zA-Z]\w*)?>')
KIND_REGEX_MAP = {
    'str': r'.+',
    'int': r'\d+',
    'float': r'\d+(\.\d+)?',
    'slug': r'[\w-]+',
}
KIND_COERCION_MAP = {
    'str': identity,
    'int': int,
    'float': float,
    'slug': identity,
}


[docs]def fragment(path, **kwargs): """ Compute the Hyperpython element for the given fragment path. Args: path (str): Argument path. Examples: >>> fragment('page.header', user='me!') # doctest: +SKIP h('header', ['Hello me!']) """ try: func = SIMPLE_PATH_REGISTRY[path] except KeyError: for key, func in FRAGMENT_REGISTRY.items(): args = key(path) if args is not None: try: result = func(**args, **kwargs) except FragmentNotFound: continue else: break else: raise FragmentNotFound(f'no fragment registered to {path}') else: result = func(**kwargs) if not isinstance(result, BaseElement): cls = type(result).__name__ msg = f'fragment function returned {cls} instead of element' raise TypeError(msg) return result
def register(path): """ Register render function to the given fragment path spec. Args: path (str): A path spec. """ def decorator(func): if '<' in path or '>' in path: FRAGMENT_REGISTRY[make_validator(path)] = func else: SIMPLE_PATH_REGISTRY[path] = func return func return decorator def make_validator(spec): """ Return a validator function from the given spec. The validator function returns None if the path does not correspond to the spec or a dictionary of arguments otherwise. Args: spec: A path spec using Django-like path specification. """ regex, coercion = make_regex(spec) def validator(path): m = regex.match(path) if m is not None: return coercion(m.groupdict()) else: return None return validator def make_regex(spec): """ Convert a path spec to a regular expression + map of coercion functions. Args: spec (str): A valid path spec in the form of "foo.<bar>.<int:baz>". Returns: A tuple of (compiled regex, coercion function). """ data = spec parts = ['^'] coercions = {} search = PATH_REGEX.search variables = set() m = search(data) while m: i, j = m.span() start, sep, data = data[:i], data[i:j], data[j:] pattern = sep[1:-1] if ':' in pattern: kind, name = pattern.split(':') else: kind = 'str' name = pattern # Prevent repeated variables in the spec if name in variables: raise ValueError(f'{name} is used twice in path spec') variables.add(name) # Prevent malformed paths validate_simple_path(start, spec) # Create fragment path and coercion function arg_regex = rf'(?P<{name}>{argument_regex(kind)})' coercions[name] = argument_coercion(kind) parts.append(re.escape(start)) parts.append(arg_regex) m = search(data) validate_simple_path(data, spec) parts.append(re.escape(data)) parts.append('$') return re.compile(''.join(parts)), coercion_function(coercions) fragment.register = register def argument_regex(kind): """ Return a regex corresponding to the given path type (e.g., str, int, float, etc) Args: kind (str): One of 'str', 'int', 'float', 'slug' Returns: A regex string. """ try: return KIND_REGEX_MAP[kind] except KeyError: raise ValueError(f'invalid path type: {kind}') def argument_coercion(kind): """ Return a function that transform a string corresponding to the path fragment to the proper Python type (e.g., str, int, float, etc) Args: kind (str): One of 'str', 'int', 'float', 'slug' Returns: A coercion function. """ try: return KIND_COERCION_MAP[kind] except KeyError: raise ValueError(f'invalid path type: {kind}') def coercion_function(coercions): """ Return a coercion function from a mapping of coercions. The coercion function converts a map to strings to a map to the correct types. """ def coercion(dic): return {k: coercions.get(k, identity)(v) for k, v in dic.items()} return coercion def validate_simple_path(st, path): """ Prevent incomplete path specifications. """ if '<' in st or '>' in st: raise ValueError(f'invalid path spec: {path}') # # Exceptions # class FragmentNotFound(Exception): """ Raised for non-registered fragments. """