#: Some meta prog stuff
import functools
import inspect
import collections
import copy
[docs]def enum(*sequential, **named):
"""
Build an enum statement
"""
#: build enums from parameter
enums = dict(zip(sequential, range(len(sequential))), **named)
enums['map'] = copy.copy(enums)
#: build reverse mapping
enums['rmap'] = {}
for key, value in enums.items():
if type(value) is int:
enums['rmap'][value] = key
return type('Enum', (), enums)
#From PEP 362: http://www.python.org/dev/peps/pep-0362/
[docs]def checktypes(func):
"""Decorator to verify arguments and return types."""
sig = inspect.signature(func)
types = {}
for param in sig.parameters.values():
# Iterate through function's parameters and build the list of
# arguments types
param_type = param.annotation
if param_type is param.empty or not inspect.isclass(param_type):
# Missing annotation or not a type, skip it
continue
types[param.name] = param_type
# If the argument has a type specified, let's check that its
# default value (if present) conforms with the type.
if (param.default is not param.empty and
not isinstance(param.default, param_type)):
raise ValueError(
"{func}: wrong type of a default value for {arg!r}".format(
func=func.__qualname__, arg=param.name)
)
def check_type(sig, arg_name, arg_type, arg_value):
# Internal function that encapsulates arguments type checking
if not isinstance(arg_value, arg_type):
raise ValueError("{func}: wrong type of {arg!r} argument, "
"{exp!r} expected, got {got!r}".
format(func=func.__qualname__, arg=arg_name,
exp=arg_type.__name__,
got=type(arg_value).__name__))
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Let's bind the arguments
ba = sig.bind(*args, **kwargs)
for arg_name, arg in ba.arguments.items():
# And iterate through the bound arguments
try:
type_ = types[arg_name]
except KeyError:
continue
else:
# OK, we have a type for the argument, lets get the
# corresponding parameter description from the signature object
param = sig.parameters[arg_name]
if param.kind == param.VAR_POSITIONAL:
# If this parameter is a variable-argument parameter,
# then we need to check each of its values
for value in arg:
check_type(sig, arg_name, type_, value)
elif param.kind == param.VAR_KEYWORD:
# If this parameter is a variable-keyword-argument
# parameter:
for subname, value in arg.items():
check_type(sig, arg_name + ':' + subname, type_, value)
else:
# And, finally, if this parameter a regular one:
check_type(sig, arg_name, type_, arg)
result = func(*ba.args, **ba.kwargs)
# The last bit - let's check that the result is correct
return_type = sig.return_annotation
if (return_type is not sig.empty and
isinstance(return_type, type) and
not isinstance(result, return_type)):
raise ValueError(
'{func}: wrong return type, {exp} expected, got {got}'.format(
func=func.__qualname__, exp=return_type.__name__,
got=type(result).__name__)
)
return result
return wrapper
# TODO: could be better in a tool module?
#: Addtototo
[docs]def set_one(chainmap, thing_name, callobject):
""" Add a mapping with key thing_name for callobject in chainmap with
namespace handling.
"""
namespaces = reversed(thing_name.split("."))
lstname = []
for name in namespaces:
lstname.insert(0, name)
strname = '.'.join(lstname)
chainmap[strname] = callobject
[docs]def _get_base_class(cls):
base = cls
while base.__bases__[0] is not object:
base = base.__bases__[0]
return base
[docs]def add_method(cls):
"""Attach a method to a class."""
def wrapper(f):
#if hasattr(cls, f.__name__):
# raise AttributeError("{} already has a '{}' attribute".format(
# cls.__name__, f.__name__))
setattr(cls, f.__name__, f)
return f
return wrapper
[docs]def hook(cls, hookname=None):
"""Attach a method to a parsing class and register it as a parser hook.
The method is registered with its name unless hookname is provided.
"""
if not hasattr(cls, '_hooks'):
raise TypeError(
"%s didn't seems to be a BasicParser subsclasse" % cls.__name__)
class_hook_list = cls._hooks
def wrapper(f):
nonlocal hookname
add_method(cls)(f)
if hookname is None:
hookname = f.__name__
if '.' not in hookname:
hookname = '.'.join([cls.__module__, cls.__name__, hookname])
set_one(class_hook_list, hookname, f)
return f
return wrapper
[docs]def rule(cls, rulename=None):
"""Attach a method to a parsing class and register it as a parser rule.
The method is registered with its name unless rulename is provided.
"""
if not hasattr(cls, '_rules'):
raise TypeError(
"%s didn't seems to be a BasicParser subsclasse" % cls.__name__)
class_rule_list = cls._rules
def wrapper(f):
nonlocal rulename
add_method(cls)(f)
if rulename is None:
rulename = f.__name__
if '.' not in rulename:
rulename = cls.__module__ + '.' + cls.__name__ + '.' + rulename
set_one(class_rule_list, rulename, f)
return f
return wrapper
# module variable for DirectiveWrapper registering
_directives = collections.ChainMap()
[docs]def directive(directname=None):
"""Attach a class to a parsing class and register it as a parser directive.
The class is registered with its name unless directname is provided.
"""
global _directives
class_dir_list = _directives
def wrapper(f):
nonlocal directname
if directname is None:
directname = f.__name__
f.ns_name = directname
set_one(class_dir_list, directname, f)
return f
return wrapper
# module variable for FunctorDecorator registration
_decorators = collections.ChainMap()
[docs]def decorator(directname=None):
"""
Attach a class to a parsing decorator and register it to the global
decorator list.
The class is registered with its name unless directname is provided
"""
global _decorators
class_deco_list = _decorators
def wrapper(f):
nonlocal directname
if directname is None:
directname = f.__name__
f.ns_name = directname
set_one(class_deco_list, directname, f)
return wrapper