from .item import Item
from .exceptions import NodeException
[docs]class CommonArgumentProxy(object):
def __init__(self, node):
self._node = node
def __getattr__(self, name):
def _search(node):
for arg in node.arguments.values():
if not arg.is_common():
continue
if name in node.instance_arguments:
return node.instance_arguments[name]
return _search(node.parent) if node.parent else None
value = _search(self._node)
if value is None:
raise AttributeError("Attribute '{}' does not exists".format(name))
return value
[docs]class Node(Item):
"""An item what may have sub nodes or leafs.
Holding a class type and instance.
For other args see :py:class:`command_tree.item.Item`
Args:
cls (type): the class handler type
items (list): list of :py:class:`command_tree.item.Item` decorated objects
"""
def __init__(self, name, cls, id, arguments, items = None, parser_args = None, docstring_parser = None, name_generator = None):
super(Node, self).__init__(name, cls, id, arguments, parser_args, docstring_parser, name_generator)
self._sub_items = []
self._instance = None
self._handler_func = None
self._common_arg_proxy = CommonArgumentProxy(self)
self._instance_arguments = {}
extra_items = items or []
self._subparser_args = {}
self._required = True
# need to reindex the items because the original order may comes from import order
start = len(extra_items) * -1
for idx, obj in enumerate(extra_items):
item = obj._item
item.reindex(start + idx)
self._sub_items.append(item)
# add 'parent' property to the handler class
cls.parent = property(lambda s: self._parent._instance)
# add 'common' property to the handler class
cls.common = property(lambda s: self._common_arg_proxy)
@property
def instance(self):
"""Getter for instance."""
return self._instance
@property
def has_handler(self):
return self._handler_func is not None
@property
def obj_name(self):
"""See :py:func:`command_tree.item.Item.obj_name`."""
return self.obj.__name__
@property
def items(self):
"""Getter for the list of the sub items.
Returns:
list: Item based instances
"""
return self._sub_items
@property
def instance_arguments(self):
"""Getter for the instance arguments.
Returns:
dict: the handler constructor arguments
"""
return self._instance_arguments
@property
def parent(self):
return self._parent
@property
def required(self):
return self._required
@required.setter
def required(self, val):
"""Set the required flag for the subparser
Args:
val (bool): value
"""
self._required = val
[docs] def set_subparser_arguments(self, kwargs):
"""Set subparser arguments
Args:
kwargs (dict): key-value pairs for :py:func:`ArgumentParser.add_subparsers`
"""
self._subparser_args = kwargs
[docs] def create_handler_instance(self, arguments):
"""TODO"""
self._instance = self._obj(**arguments)
self._instance_arguments = arguments
[docs] def handle(self, kwargs):
if not self._handler_func:
raise NodeException("Handler not found!", self)
func = getattr(self._instance, self._handler_func.__name__)
return func(**kwargs)
[docs] def fetch(self):
"""Iterate throught the class attributes (classes or functions) and search for sub items.
It is assumes that the sub items has been decorated already.
"""
for attr_name in dir(self.obj):
attr = getattr(self.obj, attr_name)
if hasattr(attr, "_item"):
self._sub_items.append(getattr(attr, "_item"))
if hasattr(attr, '_node_handler'):
if self._handler_func:
raise NodeException("Initialzer was set already to {}".format(self._handler_func), self)
self._handler_func = attr
[docs] def get_item(self, name):
"""Get the specfified sub item by name.
Args:
name (str): the sub item's name
Returns:
Item: the sub item or None
"""
for item in self._sub_items:
if item.name == name:
return item
return None
def __contains__(self, key):
return True if self.get_item(key) else False
def __getitem__(self, key):
item = self.get_item(key)
if item is None:
raise KeyError("Key '{}' not found".format(key))
return item
[docs] def build(self, parser):
"""See :py:func:`command_tree.item.Item.build`."""
if not len(self._sub_items) and not len(self.arguments):
raise NodeException("There is no sub nodes or leafs and not even an argument defined!", self)
self.build_arguments(parser)
if not len(self._sub_items):
return
dest = self.name + "_command"
if 'dest' in self._subparser_args:
raise NodeException("The subparser's 'dest' is reserved for internal use by the command-tree.", self)
if 'metavar' not in self._subparser_args:
self._subparser_args['metavar'] = "subcommand"
subparsers = parser.add_subparsers(dest = dest, **self._subparser_args)
if self.required:
subparsers.required = True
for item in sorted(self._sub_items, key = lambda item: item.id):
sub_parser = subparsers.add_parser(item.name, **item.parser_args)
item.build(sub_parser)
item._parent = self
[docs] def traverse_for_common_arguments(self):
"""See :py:func:`command_tree.item.Item.traverse_for_common_arguments`."""
for item in self.items:
add_argument_handler = None
new_add_argument_handler = None
for name, arg in self.arguments.items():
# add all common arguments to the sub items
if arg.is_common():
if name in item.arguments:
raise NodeException("This argument '{}' is already exists this node, so cannot add a common argument".format(name),
self)
new_arg = arg.clone(exclude = ['add_argument_handler'])
if arg.add_argument_handler == add_argument_handler:
new_arg.add_argument_handler = new_add_argument_handler
else:
new_arg.add_argument_handler = arg.add_argument_handler.clone()
new_add_argument_handler = new_arg.add_argument_handler
add_argument_handler = arg.add_argument_handler
item.arguments[name] = new_arg
item.traverse_for_common_arguments()