Source code for multiconf.decorators

# Copyright (c) 2012 Lars Hupfeldt Nielsen, Hupfeldt IT
# All rights reserved. This work is under a BSD license, see LICENSE.TXT.

from __future__ import print_function

import sys
import re
import keyword

from .config_errors import ConfigDefinitionException, _line_msg, _error_msg, _warning_msg
from .repeatable import RepeatableDict
from . import ConfigBuilder, RepeatableConfigItem


def _isidentifier(name):
    if name in keyword.kwlist:
        return False
    return re.match(r'^[a-z_][a-z0-9_]*$', name, re.I) is not None


def _not_config_builder(cls, decorator_name):
    if issubclass(cls, ConfigBuilder):
        print(_line_msg(up_level=2), file=sys.stderr)
        msg = "Decorator '@" + decorator_name + "' is not allowed on instance of " + ConfigBuilder.__name__ + "."
        print(_error_msg(msg), file=sys.stderr)
        raise ConfigDefinitionException(msg)


def _repeatable_config_item(cls, decorator_name):
    if issubclass(cls, RepeatableConfigItem):
        return

    print(_line_msg(up_level=2), file=sys.stderr)
    msg = "Decorator '@" + decorator_name + "' is only allowed on instance of " + RepeatableConfigItem.__name__ + "."
    print(_error_msg(msg), file=sys.stderr)
    raise ConfigDefinitionException(msg)


def _check_valid_identifier(name):
    if not _isidentifier(name):
        raise ConfigDefinitionException(repr(name) + " is not a valid identifier.")


def _check_valid_identifiers(names):
    invalid = []
    for name in names:
        if not _isidentifier(name):
            invalid.append(name)
    if not invalid:
        return
    if len(invalid) == 1:
        raise ConfigDefinitionException(repr(invalid[0]) + " is not a valid identifier.")
    raise ConfigDefinitionException(repr(invalid) + " are not valid identifiers.")


def _add_super_list_deco_values(cls, attr_names, deco_attr_name):
    _check_valid_identifiers(attr_names)

    super_names = getattr(super(cls, cls), '_mc_deco_' + deco_attr_name)
    for attr in super_names:
        if attr in attr_names:
            print(_line_msg(3), file=sys.stderr)
            msg = "Item name: '{name}' re-specified as '@{deco_attr_name}' on class: '{class_name}', was already inherited from a super class."
            print(_warning_msg(msg.format(name=attr, deco_attr_name=deco_attr_name, class_name=cls.__name__)), file=sys.stderr)

    return attr_names + super_names


[docs]def named_as(insert_as_name): """Determine the name used to insert item in parent""" def deco(cls): _not_config_builder(cls, named_as.__name__) _check_valid_identifier(insert_as_name) cls._mc_deco_named_as = insert_as_name return cls return deco
[docs]def nested_repeatables(*attr_names): """Specify which nested (child) items will be repeatable.""" def deco(cls): _not_config_builder(cls, nested_repeatables.__name__) cls._mc_deco_nested_repeatables = _add_super_list_deco_values(cls, attr_names, 'nested_repeatables') # Make descriptor work, an instance of the descriptor class mut be assigened at the class level for name in attr_names: setattr(cls, name, RepeatableDict()) return cls return deco
[docs]def required(*attr_names): """Specify nested (child) items that must be defined.""" def deco(cls): cls._mc_deco_required = _add_super_list_deco_values(cls, attr_names, 'required') return cls return deco
[docs]def repeatable_key(**name_value): """Set name and default value for the mc_key repeatable item __init__ argument. Arguments: **name_value (dict[name, val]): There must be exactly one name/value pair. E.g.: Use 'name' argument as the mc_key.:: @named_as('xses') @repeatable_key(name=None) class X1(RepeatableConfigItem): def __init__(name, ...) Only a single item of the following class can be created in the 'xses' repeatable. Use 'name' argument as the mc_key for the parent class, use value 'xxx' as the mc_key.:: @repeatable_key(name='xxx') class X2(X1): def __init__(...) # No 'name' argument super(X2, self)._init__(name=None, ...) Only a single item of the following class can be created in the 'ys' repeatable. Use value 'nicekey' as the mc_key.:: @named_as('ys') @repeatable_key(mc_key='nicekey') class X2(X1): def __init__(...) # No 'mc_key' argument super(X2, self)._init__(...) """ def deco(cls): _repeatable_config_item(cls, repeatable_key.__name__) for key, value in name_value.items(): cls._mc_key_name = key cls._mc_key_value = value return cls print(_line_msg(up_level=1), file=sys.stderr) msg = "Decorator '@" + repeatable_key.__name__ + "' takes exactly one key-value pair." print(_error_msg(msg), file=sys.stderr) raise ConfigDefinitionException(msg) return deco