示例#1
0
 def testBuild(self):
     self._orderdedDict = OrderedDict()
     clone = CloneBuilder.build(self)
     self.assertFalse(self is clone)
     self.assertFalse(self._orderdedDict is clone._orderdedDict)
     self.assertEqual(self._orderdedDict, clone._orderdedDict)
     self.assertFalse(self.testBuild is clone.testBuild)
    def testProcess(self):

        container = ContainerBuilder()
        container._ContainerBuilder__definitions = OrderedDict()
        ref1 = Reference('b')
        a = container.register('a').addArgument(ref1)

        ref2 = Reference('a')
        b = container.register('b').addMethodCall('setA', [ref2])

        ref3 = Reference('a')
        ref4 = Reference('b')
        c = container.register('c').addArgument(ref3).addArgument(ref4)

        ref5 = Reference('b')
        d = container.register('d').setProperty('foo', ref5)

        ref6 = Reference('b')
        e = container.register('e').setConfigurator([ref6, 'methodName'])

        graph = self._process(container)

        edges = graph.getNode('b').getInEdges()
        self.assertEqual(4, len(edges))
        self.assertEqual(ref1, edges[0].getValue())
        self.assertEqual(ref4, edges[1].getValue())
        self.assertEqual(ref5, edges[2].getValue())
        self.assertEqual(ref6, edges[3].getValue())
示例#3
0
    def enterScope(self, name):
        """This is called when you enter a scope

        @param: string name

        @raise RuntimeException         When the parent scope is inactive
        @raise InvalidArgumentException When the scope does not exist

        @api

        """

        if name not in self._scopes:
            raise InvalidArgumentException(
                'The scope "{0}" does not exist.'.format(name))

        if self.SCOPE_CONTAINER != self._scopes[name] and self._scopes[
                name] not in self._scopedServices:
            raise RuntimeException(
                'The parent scope "{0}" must be active when entering this '
                'scope.'.format(self._scopes[name]))

        # check if a scope of this name is already active, if so we need to
        # remove all services of this scope, and those of any of its child
        # scopes from the global services map
        if name in self._scopedServices:
            services = OrderedDict()
            services[0] = self._services
            services[name] = self._scopedServices[name]
            self._scopedServices.pop(name, None)

            for child in self._scopeChildren[name]:
                if child in self._scopedServices:
                    services[child] = self._scopedServices[child]
                    self._scopedServices.pop(child, None)

            # update global map
            self._services = Array.diffKey(*services.values())
            services.pop(0)

            # add stack entry for this scope so we can restore the removed services later
            if name not in self._scopeStacks:
                self._scopeStacks[name] = list()

            self._scopeStacks[name].append(services)

        self._scopedServices[name] = dict()
示例#4
0
    def testObjectSupportEnabled(self):

        dump = self._dumper.dump(OrderedDict([('foo', A()), ('bar', 1)]), 0, 0,
                                 False, True)

        self.assertEqual(
            '{{ foo: !!python/object:{0}, bar: 1 }}'.format(serialize(A())),
            dump, '->dump() is able to dump objects')
示例#5
0
    def __krsort(self, d):
        assert isinstance(d, dict)

        ret = OrderedDict()
        for k in sorted(list(d.keys()), reverse=True):
            ret[k] = d[k]

        return ret
示例#6
0
    def resolveString(self, value, resolving=None):
        if resolving is None:
            resolving = OrderedDict()
        assert isinstance(resolving, dict)
        resolving = resolving.copy()

        # we do this to deal with non string values (Boolean, integer, ...)
        # as the preg_replace_callback throw an exception when trying
        # a non-string in a parameter value
        match = re.search(r"^%([^%\s]+)%$", value)
        if match:
            key = match.group(1).lower()
            if key in resolving.keys():
                raise ParameterCircularReferenceException(
                    list(resolving.keys()))
            resolving[key] = True
            if self._resolved:
                return self.get(key)
            else:
                return self.resolveValue(self.get(key), resolving)

        def callback(match):
            _resolving = resolving.copy()
            key = match.group(1)
            if not key:
                return "%%"
            key = key.lower()
            if key in _resolving.keys():
                raise ParameterCircularReferenceException(
                    list(_resolving.keys()))
            resolved = self.get(key)
            if not isinstance(resolved, (String, float, int, complex)):
                raise RuntimeException(
                    'A string value must be composed of strings and/or '
                    'numbers, but found parameter "{0}" of type {1} inside '
                    'string value "{2}".'
                    "".format(key,
                              type(resolved).__name__, value))
            resolved = str(resolved)
            _resolving[key] = True
            if self.isResolved():
                return resolved
            else:
                return self.resolveString(resolved, _resolving)

        return re.sub(r"%%|%([^%\s]+)%", callback, value)
示例#7
0
    def __init__(self, name, parent=None):
        """Constructor.

        @param name:   string        The Node's name
        @param parent: NodeInterface The node parent

        """
        BaseNode.__init__(self, name, parent=parent)

        self._xmlRemappings = list()
        self._children = OrderedDict()
        self._allowFalse = False
        self._allowNewKeys = True
        self._addIfNotSet = False
        self._performDeepMerging = True
        self._ignoreExtraKeys = None
        self._normalizeKeys = True
示例#8
0
    def __init__(self, name, parent=None):
        NodeDefinition.__init__(self, name, parent=parent);
        self._performDeepMerging = True;
        self._ignoreExtraKeys = None;
        self._children = OrderedDict();
        self._prototype = None;
        self._atLeastOne = False;
        self._allowNewKeys = True;
        self._key = None;
        self._removeKeyItem = None;
        self._addDefaults = False;
        self._addDefaultChildren = False;
        self._nodeBuilder = None;
        self._normalizeKeys = True;

        self._allowEmptyValue = True;
        self._nullEquivalent = dict();
        self._trueEquivalent = dict();
示例#9
0
    def testObjectSupportDisabledButNoExceptions(self):

        inputv = """
foo: !!python/object:{0}
bar: 1
""".format(serialize(B()))

        self.assertEqual(OrderedDict([('foo', None), ('bar', 1)]),
                         self._parser.parse(inputv),
                         '->parse() does not parse objects')
示例#10
0
    def testObjectSupportDisabledWithExceptions(self):
        """@expectedException: Symfony\Component\Yaml\Exception\DumpException

        """

        try:
            self._dumper.dump(OrderedDict([('foo', A()), ('bar', 1)]), 0, 0,
                              True, False)

            self.fail()
        except Exception as e:
            self.assertTrue(isinstance(e, DumpException))
示例#11
0
    def resolveValue(self, value, resolving=None):
        if resolving is None:
            resolving = OrderedDict()
        assert isinstance(resolving, dict)
        resolving = resolving.copy()

        if isinstance(value, dict):
            args = dict()
            for k, v in value.items():
                args[self.resolveValue(k, resolving)] = self.resolveValue(
                    v, resolving)
            return args

        elif isinstance(value, list):
            args = list()
            for v in value:
                args.append(self.resolveValue(v, resolving))
            return args

        if not isinstance(value, String):
            return value

        return self.resolveString(value, resolving)
示例#12
0
    def __init__(self, name, parent=None):
        """Constructor.

        @param name: string The name of the node
        @param parent: NodeInterface The parent of this node

        @raise InvalidArgumentException: if the name contains a period.
        """
        assert isinstance(name, String)
        if parent is not None:
            assert isinstance(parent, NodeInterface)

        self._attributes = OrderedDict()

        self._name = name
        self._parent = parent
        self._normalizationClosures = list()
        self._finalValidationClosures = list()
        self._allowOverwrite = True
        self._required = False
        self._equivalentValues = list()

        if '.' in name:
            raise InvalidArgumentException('The name must not contain ".".')
示例#13
0
    def __init__(self, name, parent=None):
        """Constructor.

        @param name:   string        The Node's name
        @param parent: NodeInterface The node parent

        """
        BaseNode.__init__(self, name, parent=parent);

        self._xmlRemappings = list();
        self._children = OrderedDict();
        self._allowFalse = False;
        self._allowNewKeys = True;
        self._addIfNotSet = False;
        self._performDeepMerging = True;
        self._ignoreExtraKeys = None;
        self._normalizeKeys = True;
示例#14
0
def JSONObject(match, context, _w=decoder.WHITESPACE.match):
    pairs = OrderedDict(); # Change to an ordered dict
    s = match.string
    end = _w(s, match.end()).end()
    nextchar = s[end:end + 1]
    # Trivial empty object
    if nextchar == '}':
        return pairs, end + 1
    if nextchar != '"':
        raise ValueError(decoder.errmsg("Expecting property name", s, end))
    end += 1
    encoding = getattr(context, 'encoding', None)
    strict = getattr(context, 'strict', True)
    iterscan = JSONScanner.iterscan
    while True:
        key, end = decoder.scanstring(s, end, encoding, strict)
        end = _w(s, end).end()
        if s[end:end + 1] != ':':
            raise ValueError(decoder.errmsg("Expecting : delimiter", s, end))
        end = _w(s, end + 1).end()
        try:
            value, end = iterscan(s, idx=end, context=context).next()
        except StopIteration:
            raise ValueError(decoder.errmsg("Expecting object", s, end))
        pairs[key] = value
        end = _w(s, end).end()
        nextchar = s[end:end + 1]
        end += 1
        if nextchar == '}':
            break
        if nextchar != ',':
            raise ValueError(decoder.errmsg("Expecting , delimiter", s, end - 1))
        end = _w(s, end).end()
        nextchar = s[end:end + 1]
        end += 1
        if nextchar != '"':
            raise ValueError(decoder.errmsg("Expecting property name", s, end - 1))
    object_hook = getattr(context, 'object_hook', None)
    if object_hook is not None:
        pairs = object_hook(pairs)
    return pairs, end
示例#15
0
    def __init__(self, name, parent=None):
        """Constructor.

        @param name: string The name of the node
        @param parent: NodeInterface The parent of this node

        @raise InvalidArgumentException: if the name contains a period.
        """
        assert isinstance(name, String);
        if parent is not None:
            assert isinstance(parent, NodeInterface);

        self._attributes = OrderedDict();

        self._name = name;
        self._parent = parent;
        self._normalizationClosures = list();
        self._finalValidationClosures = list();
        self._allowOverwrite = True;
        self._required = False;
        self._equivalentValues = list();

        if '.' in name:
            raise InvalidArgumentException('The name must not contain ".".');
示例#16
0
    def clear(self):
        """Clears all nodes.

        """

        self.__nodes = OrderedDict()
示例#17
0
class ArrayNode(BaseNode, PrototypeNodeInterface):
    """Represents an Array node in the config tree.

    @author Johannes M. Schmitt <*****@*****.**>

    """
    def __init__(self, name, parent=None):
        """Constructor.

        @param name:   string        The Node's name
        @param parent: NodeInterface The node parent

        """
        BaseNode.__init__(self, name, parent=parent);

        self._xmlRemappings = list();
        self._children = OrderedDict();
        self._allowFalse = False;
        self._allowNewKeys = True;
        self._addIfNotSet = False;
        self._performDeepMerging = True;
        self._ignoreExtraKeys = None;
        self._normalizeKeys = True;

    def setNormalizeKeys(self, normalizeKeys):
        self._normalizeKeys = bool(normalizeKeys);

    def _preNormalize(self, value):
        """Normalizes keys between the different configuration formats.

        Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
        After running this method, all keys are normalized to foo_bar.

        If you have a mixed key like foo-bar_moo, it will not be altered.
        The key will also not be altered if the target key already exists.

        @param value: mixed

        @return dict The value with normalized keys

        """
        if not self._normalizeKeys or not isinstance(value, dict):
            return value;

        for k, v in value.items():
            if '-' in str(k):
                if not '_' in str(k):
                    normalizedKey = str(k).replace('-', '_');
                    if not normalizedKey in value:
                        value[normalizedKey] = v;
                        value.pop(k);

        return value;

    def getChildren(self):
        """Retrieves the children of this node.

        @return: dict The children
        """
        return self._children;

    def setXmlRemappings(self, xmlRemappings):
        """Sets the xml remappings that should be performed.

        @param xmlRemappings: an list of the form list(list(string, string))
        """
        self._xmlRemappings = list(xmlRemappings);

    def setAddIfNotSet(self, boolean):
        """Sets whether to add default values for this array if it has not
        been defined in any of the configuration files.

        @param boolean: Boolean
        """
        self._addIfNotSet = bool(boolean);

    def setAllowFalse(self, allow):
        """Sets whether false is allowed as value indicating that
        the array should be unset.

        @param allow: Boolean
        """
        self._allowFalse = bool(allow);

    def setAllowNewKeys(self, allow):
        """Sets whether new keys can be defined in subsequent configurations.

        @param allow: Boolean
        """
        self._allowNewKeys = bool(allow);

    def setPerformDeepMerging(self, boolean):
        """Sets if deep merging should occur.

        @param boolean: Boolean
        """
        self._performDeepMerging = bool(boolean);

    def setIgnoreExtraKeys(self, boolean):
        """Whether extra keys should just be ignore without an exception.

        @param boolean: Boolean To allow extra keys
        """
        self._ignoreExtraKeys = bool(boolean);

    def setName(self, name):
        """Sets the node Name.

        @param name: string The node's name
        """
        self._name = str(name);

    def hasDefaultValue(self):
        """Checks if the node has a default value.

        @return Boolean

        """
        return self._addIfNotSet;

    def getDefaultValue(self):
        """Retrieves the default value.

        @return: dict The default value

        @raise RuntimeException: if the node has no default value
        """
        if not self.hasDefaultValue():
            raise RuntimeException(
                'The node at path "{0}" has no default value.'
                ''.format(self.getPath())
            );

        default = dict();
        for name, child in self._children.items():
            if child.hasDefaultValue():
                default[name] = child.getDefaultValue();

        return default;


    def addChild(self, node):
        """Adds a child node.

        @param child: NodeInterface The child node to add

        @raise InvalidArgumentException: when the child node has no name
        @raise InvalidArgumentException: when the child node's name
            is not unique
        """
        assert isinstance(node, NodeInterface);

        name = node.getName();

        if not name:
            raise InvalidArgumentException('Child nodes must be named.');

        if name in self._children:
            raise InvalidArgumentException(
                'A child node named "{0}" already exists.'
                ''.format(name)
            );

        self._children[name] = node;


    def _finalizeValue(self, value):
        """Finalizes the value of this node.

        @param value: mixed

        @return: mixed The finalised value

        @raise UnsetKeyException:
        @raise InvalidConfigurationException: if the node doesn't have enough children

        """
        if value is False:
            raise UnsetKeyException(
                'Unsetting key for path "{0}", value: {1}'
                ''.format(self.getPath(), json.dumps(value))
            );

        for name, child in self._children.items():
            assert isinstance(child, NodeInterface);
            if not name in value:
                if child.isRequired():
                    ex = InvalidConfigurationException(
                        'The child node "{0}" at path "{1}" must be '
                        'configured.'.format(name, self.getPath())
                    );
                    ex.setPath(self.getPath());
                    raise ex;

                if child.hasDefaultValue():
                    value[name] = child.getDefaultValue();

                continue;

            try:
                value[name] = child.finalize(value[name]);
            except UnsetKeyException:
                value.pop(name);

        return value;

    def _validateType(self, value):
        """Validates the type of the value.

        @param value: mixed

        @raise InvalidTypeException:

        """
        if not isinstance(value, (dict, list)):
            if not self._allowFalse or value:
                ex = InvalidTypeException(
                    'Invalid type for path "{0}". Expected array, but got {1}'
                    ''.format(self.getPath(), type(value).__name__)
                );
                ex.setPath(self.getPath())
                raise ex;

    def _normalizeValue(self, value):
        """Normalizes the value.

        @param value: mixed The value to normalize

        @return: mixed The normalized value

        @raise InvalidConfigurationException:

        """
        if value is False:
            return value;

        if isinstance(value, list):
            value = Array.toDict(value);

        assert isinstance(value, dict);

        value = self._remapXml(value);
        normalized = dict();

        valueCopy = value.copy();

        for name, child in self._children.items():
            assert isinstance(child, NodeInterface)
            if name in valueCopy:
                normalized[name] = child.normalize(value[name]);
                valueCopy.pop(name);

        # if extra fields are present, throw exception
        if valueCopy and not self._ignoreExtraKeys:
            ex = InvalidConfigurationException(
                'Unrecognized options "{0}" under "{1}"'
                ''.format(", ".join(value.keys()), self.getPath())
            );
            ex.setPath(self.getPath());
            raise ex;

        return normalized;

    def _remapXml(self, value):
        """Remaps multiple singular values to a single plural value.

        @param value: cict The source values

        @return: dict The remapped values
        """
        assert isinstance(value, dict);

        for singular, plural in self._xmlRemappings:
            if not singular in value:
                continue;

            value[plural] = Processor.normalizeConfig(value, singular, plural);
            value.pop(singular);

        return value;


    def _mergeValues(self, leftSide, rightSide):
        """Merges values together.

        @param leftSide:  mixed The left side to merge.
        @param rightSide: mixed The right side to merge.

        @return: mixed The merged values

        @rasie InvalidConfigurationException:
        @rasie RuntimeException:

        """
        if rightSide is False:
            # if this is still false after the last config has been merged the
            # finalization pass will take care of removing this key entirely
            return False;

        if not leftSide or not self._performDeepMerging:
            return rightSide;

        if isinstance(rightSide, list):
            rightSide = Array.toDict(rightSide);

        for k, v in rightSide.items():
            # no conflict
            if k not in leftSide:
                if not self._allowNewKeys:
                    ex = InvalidConfigurationException(
                        'You are not allowed to define new elements for path '
                        '"{0}". Please define all elements for this path in '
                        'one config file. If you are trying to overwrite an '
                        'element, make sure you redefine it with the same '
                        'name.'.format(self.getPath())
                    );
                    ex.setPath(self.getPath());
                    raise ex;

                leftSide[k] = v;
                continue;

            if k not in self._children:
                raise RuntimeException(
                    'merge() expects a normalized config array.'
                );

            leftSide[k] = self._children[k].merge(leftSide[k], v);

        return leftSide;
示例#18
0
class BaseNode(NodeInterface):
    """The base node class

    @author Johannes M. Schmitt <*****@*****.**>

    """
    def __init__(self, name, parent=None):
        """Constructor.

        @param name: string The name of the node
        @param parent: NodeInterface The parent of this node

        @raise InvalidArgumentException: if the name contains a period.
        """
        assert isinstance(name, String);
        if parent is not None:
            assert isinstance(parent, NodeInterface);

        self._attributes = OrderedDict();

        self._name = name;
        self._parent = parent;
        self._normalizationClosures = list();
        self._finalValidationClosures = list();
        self._allowOverwrite = True;
        self._required = False;
        self._equivalentValues = list();

        if '.' in name:
            raise InvalidArgumentException('The name must not contain ".".');

    def setAttribute(self, key, value):
        self._attributes[key] = value;

    def hasAttribute(self, key):
        return key in self._attributes;

    def getAttribute(self, key, default=None):
        if self.hasAttribute(key):
            return self._attributes[key];
        return default;

    def getAttributes(self, key):
        return self._attributes;

    def setAttributes(self, attributes):
        assert isinstance(attributes, dict);
        self._attributes = attributes;

    def removeAttribute(self, key):
        return self._attributes.pop(key);

    def setInfo(self, info):
        """Sets an info message.

        @param info: string
        """
        self.setAttribute('info', info);

    def getInfo(self):
        """Returns info message.

        @return: string The info message.
        """
        return self.getAttribute('info');

    def setExample(self, example):
        """Sets the example configuration for this node.

        @param example: string|array
        """
        self.setAttribute('example', example);

    def getExample(self):
        """Retrieves the example configuration for this node.

        @return: string|array
        """
        return self.getAttribute('example');

    def addEquivalentValue(self, originalValue, equivalentValue):
        """Adds an equivalent value.

        @param originalValue: mixed
        @param equivalentValue: mixed
        """
        self._equivalentValues.append([originalValue, equivalentValue]);

    def setRequired(self, boolean):
        """Set this node as required.

        @param boolean: Boolean Required node
        """
        self._required = bool(boolean);

    def setAllowOverwrite(self, allow):
        """Sets if this node can be overridden.

        @param allow: Boolean
        """
        self._allowOverwrite = bool(allow);

    def setNormalizationClosures(self, closures):
        """Sets the closures used for normalization.

        @param closures: callable[] An array of Closures used for normalization
        """
        assert isinstance(closures, list);
        self._normalizationClosures = closures;

    def setFinalValidationClosures(self, closures):
        """Sets the closures used for final validation.

        @param closures: callable[] An array of Closures used 
            for final validation
        """
        assert isinstance(closures, list);
        self._finalValidationClosures = closures;

    def isRequired(self):
        """Checks if this node is required.

        @return Boolean

        """
        return self._required;

    def getName(self):
        """Returns the name of this node

        @return string The Node's name.

        """
        return self._name;

    def getPath(self):
        """Retrieves the path of this node.

        @return string The Node's path

        """
        path = self._name;
        if not self._parent is None:
            path = ".".join([self._parent.getPath(), self._name]);
        return path;

    @final
    def merge(self, leftSide, rightSide):
        """Merges two values together.

        @param leftSide:  mixed
        @param rightSide: mixed

        @return mixed The merged value

        @raise ForbiddenOverwriteException:

        """
        if not self._allowOverwrite:
            raise ForbiddenOverwriteException(
                'Configuration path "{0}" cannot be overwritten. You have to '
                'define all options for this path, and any of its sub-paths '
                'in one configuration section.'.format(self.getPath())
            );

        self._validateType(leftSide);
        self._validateType(rightSide);

        return self._mergeValues(leftSide, rightSide);

    @final
    def normalize(self, value):
        """Normalizes a value, applying all normalization closures.

        @param value: mixed Value to normalize.

        @return: mixed The normalized value.

        """
        # pre-normalize value
        value  = self._preNormalize(value);

        # run custom normalization closures
        for closure in self._normalizationClosures:
            value = closure(value);

        # replace value with their equivalent
        for data in self._equivalentValues:
            if data[0] == value:
                value = data[1];

        # validate type
        self._validateType(value);

        # normalize value
        return self._normalizeValue(value);

    def _preNormalize(self, value):
        """Normalizes the value before any other normalization is applied.

        @param value:

        @return: mixed The normalized array value

        """
        return value;

    @final
    def finalize(self, value):
        """Finalizes a value, applying all finalization closures.

        @param mixed $value The value to finalize

        @return mixed The finalized value

        @raise InvalidConfigurationException:

        """
        self._validateType(value);
        value = self._finalizeValue(value);

        # Perform validation on the final value if a closure has been set.
        # The closure is also allowed to return another value.
        for closure in self._finalValidationClosures:
            try:
                value = closure(value);
            except DefinitionException as correctEx:
                raise correctEx;
            except Exception as invalid:
                raise InvalidConfigurationException(
                    'Invalid configuration for path "{0}": {1}'
                    ''.format(self.getPath(), str(invalid)),
                    previous=invalid
                );
        return value;

    @abstract
    def _validateType(self, value):
        """Validates the type of a Node.

        @param value: mixed The value to validate

        @raise InvalidTypeException: When the value is invalid
        """
        pass;

    @abstract
    def _normalizeValue(self, value):
        """Normalizes the value.

        @param value: mixed The value to normalize.

        @return: mixed The normalized value
        """
        pass;

    @abstract
    def _mergeValues(self, leftSide, rightSide):
        """Merges two values together.

        @param leftSide: mixed
        @param rightSide: mixed

        @return: mixed
        """
        pass;

    @abstract
    def _finalizeValue(self, value):
        """Finalizes a value.

        @param value: The value to finalize

        @return: mixed The finalized value
        """
        pass;
示例#19
0
class BaseNode(NodeInterface):
    """The base node class

    @author Johannes M. Schmitt <*****@*****.**>

    """
    def __init__(self, name, parent=None):
        """Constructor.

        @param name: string The name of the node
        @param parent: NodeInterface The parent of this node

        @raise InvalidArgumentException: if the name contains a period.
        """
        assert isinstance(name, String)
        if parent is not None:
            assert isinstance(parent, NodeInterface)

        self._attributes = OrderedDict()

        self._name = name
        self._parent = parent
        self._normalizationClosures = list()
        self._finalValidationClosures = list()
        self._allowOverwrite = True
        self._required = False
        self._equivalentValues = list()

        if '.' in name:
            raise InvalidArgumentException('The name must not contain ".".')

    def setAttribute(self, key, value):
        self._attributes[key] = value

    def hasAttribute(self, key):
        return key in self._attributes

    def getAttribute(self, key, default=None):
        if self.hasAttribute(key):
            return self._attributes[key]
        return default

    def getAttributes(self, key):
        return self._attributes

    def setAttributes(self, attributes):
        assert isinstance(attributes, dict)
        self._attributes = attributes

    def removeAttribute(self, key):
        return self._attributes.pop(key)

    def setInfo(self, info):
        """Sets an info message.

        @param info: string
        """
        self.setAttribute('info', info)

    def getInfo(self):
        """Returns info message.

        @return: string The info message.
        """
        return self.getAttribute('info')

    def setExample(self, example):
        """Sets the example configuration for this node.

        @param example: string|array
        """
        self.setAttribute('example', example)

    def getExample(self):
        """Retrieves the example configuration for this node.

        @return: string|array
        """
        return self.getAttribute('example')

    def addEquivalentValue(self, originalValue, equivalentValue):
        """Adds an equivalent value.

        @param originalValue: mixed
        @param equivalentValue: mixed
        """
        self._equivalentValues.append([originalValue, equivalentValue])

    def setRequired(self, boolean):
        """Set this node as required.

        @param boolean: Boolean Required node
        """
        self._required = bool(boolean)

    def setAllowOverwrite(self, allow):
        """Sets if this node can be overridden.

        @param allow: Boolean
        """
        self._allowOverwrite = bool(allow)

    def setNormalizationClosures(self, closures):
        """Sets the closures used for normalization.

        @param closures: callable[] An array of Closures used for normalization
        """
        assert isinstance(closures, list)
        self._normalizationClosures = closures

    def setFinalValidationClosures(self, closures):
        """Sets the closures used for final validation.

        @param closures: callable[] An array of Closures used 
            for final validation
        """
        assert isinstance(closures, list)
        self._finalValidationClosures = closures

    def isRequired(self):
        """Checks if this node is required.

        @return Boolean

        """
        return self._required

    def getName(self):
        """Returns the name of this node

        @return string The Node's name.

        """
        return self._name

    def getPath(self):
        """Retrieves the path of this node.

        @return string The Node's path

        """
        path = self._name
        if not self._parent is None:
            path = ".".join([self._parent.getPath(), self._name])
        return path

    @final
    def merge(self, leftSide, rightSide):
        """Merges two values together.

        @param leftSide:  mixed
        @param rightSide: mixed

        @return mixed The merged value

        @raise ForbiddenOverwriteException:

        """
        if not self._allowOverwrite:
            raise ForbiddenOverwriteException(
                'Configuration path "{0}" cannot be overwritten. You have to '
                'define all options for this path, and any of its sub-paths '
                'in one configuration section.'.format(self.getPath()))

        self._validateType(leftSide)
        self._validateType(rightSide)

        return self._mergeValues(leftSide, rightSide)

    @final
    def normalize(self, value):
        """Normalizes a value, applying all normalization closures.

        @param value: mixed Value to normalize.

        @return: mixed The normalized value.

        """
        # pre-normalize value
        value = self._preNormalize(value)

        # run custom normalization closures
        for closure in self._normalizationClosures:
            value = closure(value)

        # replace value with their equivalent
        for data in self._equivalentValues:
            if data[0] == value:
                value = data[1]

        # validate type
        self._validateType(value)

        # normalize value
        return self._normalizeValue(value)

    def _preNormalize(self, value):
        """Normalizes the value before any other normalization is applied.

        @param value:

        @return: mixed The normalized array value

        """
        return value

    @final
    def finalize(self, value):
        """Finalizes a value, applying all finalization closures.

        @param mixed $value The value to finalize

        @return mixed The finalized value

        @raise InvalidConfigurationException:

        """
        self._validateType(value)
        value = self._finalizeValue(value)

        # Perform validation on the final value if a closure has been set.
        # The closure is also allowed to return another value.
        for closure in self._finalValidationClosures:
            try:
                value = closure(value)
            except DefinitionException as correctEx:
                raise correctEx
            except Exception as invalid:
                raise InvalidConfigurationException(
                    'Invalid configuration for path "{0}": {1}'
                    ''.format(self.getPath(), str(invalid)),
                    previous=invalid)
        return value

    @abstract
    def _validateType(self, value):
        """Validates the type of a Node.

        @param value: mixed The value to validate

        @raise InvalidTypeException: When the value is invalid
        """
        pass

    @abstract
    def _normalizeValue(self, value):
        """Normalizes the value.

        @param value: mixed The value to normalize.

        @return: mixed The normalized value
        """
        pass

    @abstract
    def _mergeValues(self, leftSide, rightSide):
        """Merges two values together.

        @param leftSide: mixed
        @param rightSide: mixed

        @return: mixed
        """
        pass

    @abstract
    def _finalizeValue(self, value):
        """Finalizes a value.

        @param value: The value to finalize

        @return: mixed The finalized value
        """
        pass
示例#20
0
class ArrayNodeDefinition(NodeDefinition, ParentNodeDefinitionInterface):
    """This class provides a fluent interface for defining an array node.

    @author: Johannes M. Schmitt <*****@*****.**>

    """
    def __init__(self, name, parent=None):
        NodeDefinition.__init__(self, name, parent=parent);
        self._performDeepMerging = True;
        self._ignoreExtraKeys = None;
        self._children = OrderedDict();
        self._prototype = None;
        self._atLeastOne = False;
        self._allowNewKeys = True;
        self._key = None;
        self._removeKeyItem = None;
        self._addDefaults = False;
        self._addDefaultChildren = False;
        self._nodeBuilder = None;
        self._normalizeKeys = True;

        self._allowEmptyValue = True;
        self._nullEquivalent = dict();
        self._trueEquivalent = dict();

    def setBuilder(self, builder):
        """Sets a custom children builder.

        @param builder: NodeBuilder A custom NodeBuilder
        """
        assert isinstance(builder, NodeBuilder);
        self._nodeBuilder = builder;

    def children(self):
        """Returns a builder to add children nodes.

        @return: NodeBuilder
        """
        return self._getNodeBuilder();

    def prototype(self, nodeType):
        """Sets a prototype for child nodes.

        @param nodeType: string the type of node

        @return: NodeDefinition
        """
        self._prototype = \
            self._getNodeBuilder().node(None, nodeType).setParent(self);
        return self._prototype;

    def addDefaultsIfNotSet(self):
        """Adds the default value if the node is not set in the configuration.

        This method is applicable to concrete nodes only
        (not to prototype nodes). If this function has been called
        and the node is not set during the finalization phase,
        it's default value will be derived from its children default values.

        @return: ArrayNodeDefinition
        """
        self._addDefaults = True;
        return self;

    def addDefaultChildrenIfNoneSet(self, children=None):
        """Adds children with a default value when none are defined.

        This method is applicable to prototype nodes only.

        @param children: integer|string|dict|None The number of
            children|The child name|The children names to be added

        @return: ArrayNodeDefinition
        """
        self._addDefaultChildren = children;
        return self;

    def requiresAtLeastOneElement(self):
        """Requires the node to have at least one element.

        This method is applicable to prototype nodes only.

        @return: ArrayNodeDefinition
        """
        self._atLeastOne = True;
        return self;

    def disallowNewKeysInSubsequentConfigs(self):
        """Disallows adding news keys in a subsequent configuration.

        If used all keys have to be defined in the same configuration file.

        @return: ArrayNodeDefinition
        """
        self._allowNewKeys = False;
        return self;

    def fixXmlConfig(self, singular, plural=None):
        """Sets a normalization rule for XML configurations.

        @param singular: string The key to remap
        @param plural: string The plural of the key for irregular plurals

        @return: ArrayNodeDefinition
        """
        self._normalization().remap(singular, plural);
        return self;

    def useAttributeAsKey(self, name, removeKeyItem=True):
        """Sets the attribute which value is to be used as key.

        This method is applicable to prototype nodes only.

        This is useful when you have an indexed array that should be an
        associative array. You can select an item from within the array
        to be the key of the particular item. For example, if "id" is the
        "key", then:
            {
                {'id': 'my_name', 'foo': 'bar'},
            }
        becomes
            {
                my_name', {'foo': 'bar'},
            }

        If you'd like "'id' => 'my_name'" to still be present in the resulting
        array, then you can set the second argument of this method to false.

        @param name: string The name of the key
        @param removeKeyItem: Boolean Whether
            or not the key item should be removed.

        @return: ArrayNodeDefinition
        """
        self._key = name;
        self._removeKeyItem = removeKeyItem;
        return self;

    def canBeUnset(self, allow=True):
        """Sets whether the node can be unset.

        @param allow: Boolean

        @return: ArrayNodeDefinition
        """
        self._merge().allowUnset(allow);
        return self;

    def canBeEnabled(self):
        """Adds an "enabled" boolean to enable the current section.

        By default, the section is disabled.

        @return: ArrayNodeDefinition
        """
        self.treatFalseLike({'enabled': False});
        self.treatTrueLike({'enabled': True});
        self.treatNullLike({'enabled': True});
        self.children().booleanNode('enabled').defaultFalse();
        return self;

    def canBeDisabled(self):
        """Adds an "enabled" boolean to enable the current section.

        By default, the section is enabled.

        @return: ArrayNodeDefinition
        """
        self.treatFalseLike({'enabled': False});
        self.treatTrueLike({'enabled': True});
        self.treatNullLike({'enabled': True});
        self.children().booleanNode('enabled').defaultFalse();
        return self;

    def performNoDeepMerging(self):
        """Disables the deep merging of the node.

        @return: ArrayNodeDefinition
        """
        self._performDeepMerging = False;
        return self;

    def ignoreExtraKeys(self):
        """Allows extra config keys to be specified under an array without
        throwing an exception.

        Those config values are simply ignored. This should be used only
        in special cases where you want to send an entire configuration
        array through a special tree that processes only part of the array.

        @return: ArrayNodeDefinition
        """
        self._ignoreExtraKeys = True;
        return self;

    def normalizeKeys(self, boolean):
        """Sets key normalization.

        @param boolean: boolean Whether to enable key normalization

        @return: ArrayNodeDefinition
        """
        self._normalizeKeys = bool(boolean);
        return self;

    def append(self, node):
        """Appends a node definition.

        $node = ArrayNodeDefinition()
            ->children()
            ->scalarNode('foo')->end()
            ->scalarNode('baz')->end()
            ->end()
            ->append($this->getBarNodeDefinition())
        ;

        @param node: NodeDefinition A NodeDefinition instance

        @return: ArrayNodeDefinition
        """
        assert isinstance(node, NodeDefinition);
        self._children[node._name] = node.setParent(self);
        return self;

    def _getNodeBuilder(self):
        """Returns a node builder to be used to add children and prototype

        @return: NodeBuilder The node builder
        """
        if self._nodeBuilder is None:
            self._nodeBuilder = NodeBuilder();
        return self._nodeBuilder.setParent(self);

    def _createNode(self):
        if self._prototype is None:
            node = ArrayNode(self._name, self._parent);
            self._validateConcreteNode(node);

            node.setAddIfNotSet(self._addDefaults);

            for child in self._children.values():
                child._parent = node;
                node.addChild(child.getNode());
        else:
            node = PrototypedArrayNode(self._name, self._parent);

            self._validatePrototypeNode(node);

            if not self._key is None:
                node.setKeyAttribute(self._key, self._removeKeyItem);

            if self._atLeastOne:
                node.setMinNumberOfElements(1);

            if self._default:
                node.setDefaultValue(self._defaultValue);

            if not self._addDefaultChildren is False:
                node.setAddChildrenIfNoneSet(self._addDefaultChildren);
                if isinstance(self._prototype, type(self)):
                    if self._prototype._prototype is None:
                        self._prototype.addDefaultsIfNotSet();

            self._prototype._parent = node;
            node.setPrototype(self._prototype.getNode());

        node.setAllowNewKeys(self._allowNewKeys);
        node.addEquivalentValue(None, self._nullEquivalent);
        node.addEquivalentValue(True, self._trueEquivalent);
        node.addEquivalentValue(False, self._falseEquivalent);
        node.setPerformDeepMerging(self._performDeepMerging);
        node.setRequired(self._required);
        node.setIgnoreExtraKeys(self._ignoreExtraKeys);
        node.setNormalizeKeys(self._normalizeKeys);

        if not self._normalizationBuilder is None:
            node.setNormalizationClosures(self._normalizationBuilder.befores);
            node.setXmlRemappings(self._normalizationBuilder.remappings);

        if not self._mergeBuilder is None:
            node.setAllowOverwrite(self._mergeBuilder.allowOverwrite);
            node.setAllowFalse(self._mergeBuilder.allowFalse);

        if not self._validationBuilder is None:
            node.setFinalValidationClosures(self._validationBuilder.rules);

        return node;

    def _validateConcreteNode(self, node):
        """Validate the configuration of a concrete node.

        @param node: ArrayNode  The related node

        @raise InvalidDefinitionException:
        """
        assert isinstance(node, ArrayNode);
        path = node.getPath();

        if not self._key is None:
            raise InvalidDefinitionException(
                '.useAttributeAsKey() is not applicable to concrete '
                'nodes at path "{0}"'.format(path)
            );

        if self._atLeastOne:
            raise InvalidDefinitionException(
                '.requiresAtLeastOneElement() is not applicable '
                'to concrete nodes at path "{0}"'.format(path)
            );

        if self._default:
            raise InvalidDefinitionException(
                '.defaultValue() is not applicable to concrete nodes '
                'at path "{0}"'.format(path)
            );

        if not self._addDefaultChildren is False:
            raise InvalidDefinitionException(
                '.addDefaultChildrenIfNoneSet() is not applicable '
                'to concrete nodes at path "{0}"'.format(path)
            );

    def _validatePrototypeNode(self, node):
        """Validate the configuration of a prototype node.

        @param node: PrototypedArrayNode The related node

        @raise InvalidDefinitionException:
        """
        assert isinstance(node, PrototypedArrayNode);
        path = node.getPath();

        if self._addDefaults:
            raise InvalidDefinitionException(
                '.addDefaultsIfNotSet() is not applicable to prototype '
                'nodes at path "{0}"'.format(path)
            );

        if not self._addDefaultChildren is False:
            if self._default:
                raise InvalidDefinitionException(
                    'A default value and default children might not be '
                    'used together at path "{0}"'.format(path)
                );

            if not self._key is None and (
                self._addDefaultChildren is None or \
                isinstance(self._addDefaultChildren, int) and \
                self._addDefaultChildren > 0
                ):
                raise InvalidDefinitionException(
                    '.addDefaultChildrenIfNoneSet() should set default '
                    'children names as ->useAttributeAsKey() is used '
                    'at path "{0}"'.format(path)
                );

            if self._key is None and (
                isinstance(self._addDefaultChildren, String) or \
                isinstance(self._addDefaultChildren, dict)
                ):
                raise InvalidDefinitionException(
                    '->addDefaultChildrenIfNoneSet() might not set default '
                    'children names as ->useAttributeAsKey() is not used '
                    'at path "{0}"'.format(path)
                );
示例#21
0
    def getBlockChompingTests(self):

        tests = list()

        yaml = """
foo: |-
    one
    two
bar: |-
    one
    two
"""

        expected = OrderedDict([
            ('foo', 'one\ntwo'),
            ('bar', 'one\ntwo'),
        ])
        tests.append([
            'Literal block chomping strip with single trailing newline',
            expected, yaml
        ])

        yaml = """
foo: |-
    one
    two

bar: |-
    one
    two

"""
        expected = OrderedDict([
            ('foo', "one\ntwo"),
            ('bar', "one\ntwo"),
        ])
        tests.append([
            'Literal block chomping strip with multiple trailing newlines',
            expected, yaml
        ])

        yaml = """
foo: |-
    one
    two
bar: |-
    one
    two"""
        expected = OrderedDict([
            ('foo', "one\ntwo"),
            ('bar', "one\ntwo"),
        ])
        tests.append([
            'Literal block chomping strip without trailing newline', expected,
            yaml
        ])

        yaml = """
foo: |
    one
    two
bar: |
    one
    two
"""
        expected = OrderedDict([
            ('foo', "one\ntwo\n"),
            ('bar', "one\ntwo\n"),
        ])
        tests.append([
            'Literal block chomping clip with single trailing newline',
            expected, yaml
        ])

        yaml = """
foo: |
    one
    two

bar: |
    one
    two

"""
        expected = OrderedDict([
            ('foo', "one\ntwo\n"),
            ('bar', "one\ntwo\n"),
        ])
        tests.append([
            'Literal block chomping clip with multiple trailing newlines',
            expected, yaml
        ])

        yaml = """
foo: |
    one
    two
bar: |
    one
    two"""
        expected = OrderedDict([
            ('foo', "one\ntwo\n"),
            ('bar', "one\ntwo"),
        ])
        tests.append([
            'Literal block chomping clip without trailing newline', expected,
            yaml
        ])

        yaml = """
foo: |+
    one
    two
bar: |+
    one
    two
"""
        expected = OrderedDict([
            ('foo', "one\ntwo\n"),
            ('bar', "one\ntwo\n"),
        ])
        tests.append([
            'Literal block chomping keep with single trailing newline',
            expected, yaml
        ])

        yaml = """
foo: |+
    one
    two

bar: |+
    one
    two

"""
        expected = OrderedDict([
            ('foo', "one\ntwo\n\n"),
            ('bar', "one\ntwo\n\n"),
        ])
        tests.append([
            'Literal block chomping keep with multiple trailing newlines',
            expected, yaml
        ])

        yaml = """
foo: |+
    one
    two
bar: |+
    one
    two"""
        expected = OrderedDict([
            ('foo', "one\ntwo\n"),
            ('bar', "one\ntwo"),
        ])
        tests.append([
            'Literal block chomping keep without trailing newline', expected,
            yaml
        ])

        yaml = """
foo: >-
    one
    two
bar: >-
    one
    two
"""
        expected = OrderedDict([
            ('foo', "one two"),
            ('bar', "one two"),
        ])
        tests.append([
            'Folded block chomping strip with single trailing newline',
            expected, yaml
        ])

        yaml = """
foo: >-
    one
    two

bar: >-
    one
    two

"""
        expected = OrderedDict([
            ('foo', "one two"),
            ('bar', "one two"),
        ])
        tests.append([
            'Folded block chomping strip with multiple trailing newlines',
            expected, yaml
        ])

        yaml = """
foo: >-
    one
    two
bar: >-
    one
    two"""
        expected = OrderedDict([
            ('foo', "one two"),
            ('bar', "one two"),
        ])
        tests.append([
            'Folded block chomping strip without trailing newline', expected,
            yaml
        ])

        yaml = """
foo: >
    one
    two
bar: >
    one
    two
"""
        expected = OrderedDict([
            ('foo', "one two\n"),
            ('bar', "one two\n"),
        ])
        tests.append([
            'Folded block chomping clip with single trailing newline',
            expected, yaml
        ])

        yaml = """
foo: >
    one
    two

bar: >
    one
    two

"""
        expected = OrderedDict([
            ('foo', "one two\n"),
            ('bar', "one two\n"),
        ])
        tests.append([
            'Folded block chomping clip with multiple trailing newlines',
            expected, yaml
        ])

        yaml = """
foo: >
    one
    two
bar: >
    one
    two"""
        expected = OrderedDict([
            ('foo', "one two\n"),
            ('bar', "one two"),
        ])
        tests.append([
            'Folded block chomping clip without trailing newline', expected,
            yaml
        ])

        yaml = """
foo: >+
    one
    two
bar: >+
    one
    two
"""
        expected = OrderedDict([
            ('foo', "one two\n"),
            ('bar', "one two\n"),
        ])
        tests.append([
            'Folded block chomping keep with single trailing newline',
            expected, yaml
        ])

        yaml = """
foo: >+
    one
    two

bar: >+
    one
    two

"""
        expected = OrderedDict([
            ('foo', "one two\n\n"),
            ('bar', "one two\n\n"),
        ])
        tests.append([
            'Folded block chomping keep with multiple trailing newlines',
            expected, yaml
        ])

        yaml = """
foo: >+
    one
    two
bar: >+
    one
    two"""
        expected = OrderedDict([
            ('foo', "one two\n"),
            ('bar', "one two"),
        ])
        tests.append([
            'Folded block chomping keep without trailing newline', expected,
            yaml
        ])

        return tests
示例#22
0
    def _getTestsForDump(self):

        return {
            'null':
            None,
            'false':
            False,
            'true':
            True,
            '12':
            12,
            "'quoted string'":
            'quoted string',
            '1230.0':
            12.30e+02,
            '1234':
            0x4D2,
            '1243':
            0o2333,
            '.Inf':
            1e10000,
            '-.Inf':
            -1e10000,
            "'686e444'":
            '686e444',
            '.Inf':
            646e444,
            '"foo\\r\\nbar"':
            "foo\r\nbar",
            "'foo#bar'":
            'foo#bar',
            "'foo # bar'":
            'foo # bar',
            "'#cfcfcf'":
            '#cfcfcf',
            "'a \"string\" with ''quoted strings inside'''":
            'a "string" with \'quoted strings inside\'',

            # sequences
            '[foo, bar, false, null, 12]': ['foo', 'bar', False, None, 12],
            '[\'foo,bar\', \'foo bar\']': ['foo,bar', 'foo bar'],

            # mappings
            '{ foo: bar, bar: foo, \'false\': false, \'null\': null, integer: 12 }':
            OrderedDict([('foo', 'bar'), ('bar', 'foo'), ('false', False),
                         ('null', None), ('integer', 12)]),
            '{ foo: bar, bar: \'foo: bar\' }':
            OrderedDict([('foo', 'bar'), ('bar', 'foo: bar')]),

            # nested sequences and mappings
            '[foo, [bar, foo]]': ['foo', ['bar', 'foo']],
            '[foo, [bar, [foo, [bar, foo]], foo]]':
            ['foo', ['bar', ['foo', ['bar', 'foo']], 'foo']],
            '{ foo: { bar: foo } }': {
                'foo': {
                    'bar': 'foo'
                }
            },
            '[foo, { bar: foo }]': ['foo', {
                'bar': 'foo'
            }],
            '[foo, { bar: foo, foo: [foo, { bar: foo }] }, [foo, { bar: foo }]]':
            [
                'foo',
                OrderedDict([('bar', 'foo'), ('foo', ['foo', {
                    'bar': 'foo'
                }])]), ['foo', {
                    'bar': 'foo'
                }]
            ],
            '[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']':
            [
                'foo',
                '@foo.baz',
                OrderedDict([
                    ('%foo%', 'foo is %foo%'),
                    ('bar', '%foo%'),
                ]),
                True,
                '@service_container',
            ],
        }
示例#23
0
    def testInlineLevel(self):

        # inline level
        array = OrderedDict([
            ('', 'bar'),
            ('foo', '#bar'),
            ('foo\'bar', dict()),
            ('bar', [1, 'foo']),
            ('foobar',
             OrderedDict([
                 ('foo', 'bar'),
                 ('bar', [1, 'foo']),
                 ('foobar', OrderedDict([
                     ('foo', 'bar'),
                     ('bar', [1, 'foo']),
                 ])),
             ])),
        ])

        expected = """{ '': bar, foo: '#bar', 'foo''bar': {  }, bar: [1, foo], foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } } }"""

        self.assertEqual(expected, self._dumper.dump(array, -10),
                         '->dump() takes an inline level argument')
        self.assertEqual(expected, self._dumper.dump(array, 0),
                         '->dump() takes an inline level argument')

        expected = """'': bar
foo: '#bar'
'foo''bar': {  }
bar: [1, foo]
foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } }
"""
        self.assertEqual(expected, self._dumper.dump(array, 1),
                         '->dump() takes an inline level argument')

        expected = """'': bar
foo: '#bar'
'foo''bar': {  }
bar:
    - 1
    - foo
foobar:
    foo: bar
    bar: [1, foo]
    foobar: { foo: bar, bar: [1, foo] }
"""
        self.assertEqual(expected, self._dumper.dump(array, 2),
                         '->dump() takes an inline level argument')

        expected = """'': bar
foo: '#bar'
'foo''bar': {  }
bar:
    - 1
    - foo
foobar:
    foo: bar
    bar:
        - 1
        - foo
    foobar:
        foo: bar
        bar: [1, foo]
"""
        self.assertEqual(expected, self._dumper.dump(array, 3),
                         '->dump() takes an inline level argument')

        expected = """'': bar
foo: '#bar'
'foo''bar': {  }
bar:
    - 1
    - foo
foobar:
    foo: bar
    bar:
        - 1
        - foo
    foobar:
        foo: bar
        bar:
            - 1
            - foo
"""
        self.assertEqual(expected, self._dumper.dump(array, 4),
                         '->dump() takes an inline level argument')
        self.assertEqual(expected, self._dumper.dump(array, 10),
                         '->dump() takes an inline level argument')
示例#24
0
class ArrayNode(BaseNode, PrototypeNodeInterface):
    """Represents an Array node in the config tree.

    @author Johannes M. Schmitt <*****@*****.**>

    """
    def __init__(self, name, parent=None):
        """Constructor.

        @param name:   string        The Node's name
        @param parent: NodeInterface The node parent

        """
        BaseNode.__init__(self, name, parent=parent)

        self._xmlRemappings = list()
        self._children = OrderedDict()
        self._allowFalse = False
        self._allowNewKeys = True
        self._addIfNotSet = False
        self._performDeepMerging = True
        self._ignoreExtraKeys = None
        self._normalizeKeys = True

    def setNormalizeKeys(self, normalizeKeys):
        self._normalizeKeys = bool(normalizeKeys)

    def _preNormalize(self, value):
        """Normalizes keys between the different configuration formats.

        Namely, you mostly have foo_bar in YAML while you have foo-bar in XML.
        After running this method, all keys are normalized to foo_bar.

        If you have a mixed key like foo-bar_moo, it will not be altered.
        The key will also not be altered if the target key already exists.

        @param value: mixed

        @return dict The value with normalized keys

        """
        if not self._normalizeKeys or not isinstance(value, dict):
            return value

        for k, v in value.items():
            if '-' in str(k):
                if not '_' in str(k):
                    normalizedKey = str(k).replace('-', '_')
                    if not normalizedKey in value:
                        value[normalizedKey] = v
                        value.pop(k)

        return value

    def getChildren(self):
        """Retrieves the children of this node.

        @return: dict The children
        """
        return self._children

    def setXmlRemappings(self, xmlRemappings):
        """Sets the xml remappings that should be performed.

        @param xmlRemappings: an list of the form list(list(string, string))
        """
        self._xmlRemappings = list(xmlRemappings)

    def setAddIfNotSet(self, boolean):
        """Sets whether to add default values for this array if it has not
        been defined in any of the configuration files.

        @param boolean: Boolean
        """
        self._addIfNotSet = bool(boolean)

    def setAllowFalse(self, allow):
        """Sets whether false is allowed as value indicating that
        the array should be unset.

        @param allow: Boolean
        """
        self._allowFalse = bool(allow)

    def setAllowNewKeys(self, allow):
        """Sets whether new keys can be defined in subsequent configurations.

        @param allow: Boolean
        """
        self._allowNewKeys = bool(allow)

    def setPerformDeepMerging(self, boolean):
        """Sets if deep merging should occur.

        @param boolean: Boolean
        """
        self._performDeepMerging = bool(boolean)

    def setIgnoreExtraKeys(self, boolean):
        """Whether extra keys should just be ignore without an exception.

        @param boolean: Boolean To allow extra keys
        """
        self._ignoreExtraKeys = bool(boolean)

    def setName(self, name):
        """Sets the node Name.

        @param name: string The node's name
        """
        self._name = str(name)

    def hasDefaultValue(self):
        """Checks if the node has a default value.

        @return Boolean

        """
        return self._addIfNotSet

    def getDefaultValue(self):
        """Retrieves the default value.

        @return: dict The default value

        @raise RuntimeException: if the node has no default value
        """
        if not self.hasDefaultValue():
            raise RuntimeException(
                'The node at path "{0}" has no default value.'
                ''.format(self.getPath()))

        default = dict()
        for name, child in self._children.items():
            if child.hasDefaultValue():
                default[name] = child.getDefaultValue()

        return default

    def addChild(self, node):
        """Adds a child node.

        @param child: NodeInterface The child node to add

        @raise InvalidArgumentException: when the child node has no name
        @raise InvalidArgumentException: when the child node's name
            is not unique
        """
        assert isinstance(node, NodeInterface)

        name = node.getName()

        if not name:
            raise InvalidArgumentException('Child nodes must be named.')

        if name in self._children:
            raise InvalidArgumentException(
                'A child node named "{0}" already exists.'
                ''.format(name))

        self._children[name] = node

    def _finalizeValue(self, value):
        """Finalizes the value of this node.

        @param value: mixed

        @return: mixed The finalised value

        @raise UnsetKeyException:
        @raise InvalidConfigurationException: if the node doesn't have enough children

        """
        if value is False:
            raise UnsetKeyException('Unsetting key for path "{0}", value: {1}'
                                    ''.format(self.getPath(),
                                              json.dumps(value)))

        for name, child in self._children.items():
            assert isinstance(child, NodeInterface)
            if not name in value:
                if child.isRequired():
                    ex = InvalidConfigurationException(
                        'The child node "{0}" at path "{1}" must be '
                        'configured.'.format(name, self.getPath()))
                    ex.setPath(self.getPath())
                    raise ex

                if child.hasDefaultValue():
                    value[name] = child.getDefaultValue()

                continue

            try:
                value[name] = child.finalize(value[name])
            except UnsetKeyException:
                value.pop(name)

        return value

    def _validateType(self, value):
        """Validates the type of the value.

        @param value: mixed

        @raise InvalidTypeException:

        """
        if not isinstance(value, (dict, list)):
            if not self._allowFalse or value:
                ex = InvalidTypeException(
                    'Invalid type for path "{0}". Expected array, but got {1}'
                    ''.format(self.getPath(),
                              type(value).__name__))
                ex.setPath(self.getPath())
                raise ex

    def _normalizeValue(self, value):
        """Normalizes the value.

        @param value: mixed The value to normalize

        @return: mixed The normalized value

        @raise InvalidConfigurationException:

        """
        if value is False:
            return value

        if isinstance(value, list):
            value = Array.toDict(value)

        assert isinstance(value, dict)

        value = self._remapXml(value)
        normalized = dict()

        valueCopy = value.copy()

        for name, child in self._children.items():
            assert isinstance(child, NodeInterface)
            if name in valueCopy:
                normalized[name] = child.normalize(value[name])
                valueCopy.pop(name)

        # if extra fields are present, throw exception
        if valueCopy and not self._ignoreExtraKeys:
            ex = InvalidConfigurationException(
                'Unrecognized options "{0}" under "{1}"'
                ''.format(", ".join(value.keys()), self.getPath()))
            ex.setPath(self.getPath())
            raise ex

        return normalized

    def _remapXml(self, value):
        """Remaps multiple singular values to a single plural value.

        @param value: cict The source values

        @return: dict The remapped values
        """
        assert isinstance(value, dict)

        for singular, plural in self._xmlRemappings:
            if not singular in value:
                continue

            value[plural] = Processor.normalizeConfig(value, singular, plural)
            value.pop(singular)

        return value

    def _mergeValues(self, leftSide, rightSide):
        """Merges values together.

        @param leftSide:  mixed The left side to merge.
        @param rightSide: mixed The right side to merge.

        @return: mixed The merged values

        @rasie InvalidConfigurationException:
        @rasie RuntimeException:

        """
        if rightSide is False:
            # if this is still false after the last config has been merged the
            # finalization pass will take care of removing this key entirely
            return False

        if not leftSide or not self._performDeepMerging:
            return rightSide

        if isinstance(rightSide, list):
            rightSide = Array.toDict(rightSide)

        for k, v in rightSide.items():
            # no conflict
            if k not in leftSide:
                if not self._allowNewKeys:
                    ex = InvalidConfigurationException(
                        'You are not allowed to define new elements for path '
                        '"{0}". Please define all elements for this path in '
                        'one config file. If you are trying to overwrite an '
                        'element, make sure you redefine it with the same '
                        'name.'.format(self.getPath()))
                    ex.setPath(self.getPath())
                    raise ex

                leftSide[k] = v
                continue

            if k not in self._children:
                raise RuntimeException(
                    'merge() expects a normalized config array.')

            leftSide[k] = self._children[k].merge(leftSide[k], v)

        return leftSide
示例#25
0
    def testObjectSupportDisabledButNoExceptions(self):

        dump = self._dumper.dump(OrderedDict([('foo', A()), ('bar', 1)]))

        self.assertEqual('{ foo: null, bar: 1 }', dump,
                         '->dump() does not dump objects when disabled')
示例#26
0
 def __object_pairs_hook(self, seq):
     return OrderedDict(seq)