Example #1
0
    def _calc_document_children(self):
        """Determine each document's children.

        For each document, attempts to find the document's children. Adds a new
        key called "children" to the document's dictionary.

        .. note::

            A document should only have exactly one parent.

            If a document does not have a parent, then its layer must be
            the topmost layer defined by the ``layerOrder``.

        :returns: Ordered list of documents that need to be layered. Each
            document contains a "children" property in addition to original
            data. List of documents returned is ordered from highest to lowest
            layer.
        :rtype: List[:class:`DocumentDict`]
        :raises IndeterminateDocumentParent: If more than one parent document
            was found for a document.
        """

        # ``all_children`` is a counter utility for verifying that each
        # document has exactly one parent.
        all_children = collections.Counter()

        # Mapping of (doc.name, doc.metadata.name) => children, where children
        # are the documents whose `parentSelector` references the doc.
        self._children = {}

        def _get_children(doc):
            children = []
            doc_layer = doc.layer
            try:
                next_layer_idx = self._layer_order.index(doc_layer) + 1
                children_doc_layer = self._layer_order[next_layer_idx]
            except IndexError:
                # The lowest layer has been reached, so no children. Return
                # empty list.
                return children

            for other_doc in self._layered_docs:
                # Documents with different schemas are never layered together,
                # so consider only documents with same schema as candidates.
                is_potential_child = (other_doc.layer == children_doc_layer
                                      and other_doc.schema == doc.schema)
                if is_potential_child:
                    # A document can have many labels but should only have one
                    # explicit label for the parentSelector.
                    parent_sel = other_doc.parent_selector
                    try:
                        parent_sel_key = list(parent_sel.keys())[0]
                        parent_sel_val = list(parent_sel.values())[0]
                    except IndexError:
                        continue

                    if (parent_sel_key in doc.labels
                            and parent_sel_val == doc.labels[parent_sel_key]):
                        children.append(other_doc)

            return children

        for layer in self._layer_order:
            docs_by_layer = list(
                filter((lambda x: x.layer == layer), self._layered_docs))
            for doc in docs_by_layer:
                children = _get_children(doc)
                if children:
                    all_children.update(children)
                    self._children.setdefault((doc.name, doc.schema), children)

        all_children_elements = list(all_children.elements())
        secondary_docs = list(
            filter(lambda d: d.layer != self._layer_order[0],
                   self._layered_docs)) if self._layer_order else []
        for doc in secondary_docs:
            # Unless the document is the topmost document in the
            # `layerOrder` of the LayeringPolicy, it should be a child document
            # of another document.
            if doc not in all_children_elements:
                LOG.info(
                    'Could not find parent for document with name=%s, '
                    'schema=%s, layer=%s, parentSelector=%s.', doc.name,
                    doc.schema, doc.layer, doc.parent_selector)
                self._parentless_documents.append(doc)
            # If the document is a child document of more than 1 parent, then
            # the document has too many parents, which is a validation error.
            elif all_children[doc] > 1:
                LOG.info(
                    '%d parent documents were found for child document '
                    'with name=%s, schema=%s, layer=%s, parentSelector=%s'
                    '. Each document must have exactly 1 parent.',
                    all_children[doc], doc.name, doc.schema, doc.layer,
                    doc.parent_selector)
                raise errors.IndeterminateDocumentParent(document=doc)

        return self._layered_docs
Example #2
0
    def _calc_all_document_children(self):
        """Determine each document's children.

        For each document, attempts to find the document's children. Adds a new
        key called "children" to the document's dictionary.

        .. note::

            A document should only have exactly one parent.

            If a document does not have a parent, then its layer must be
            the topmost layer defined by the ``layerOrder``.

        :returns: Ordered list of documents that need to be layered. Each
            document contains a "children" property in addition to original
            data. List of documents returned is ordered from highest to lowest
            layer.
        :rtype: List[:class:`DocumentDict`]
        :raises IndeterminateDocumentParent: If more than one parent document
            was found for a document.
        """
        # ``all_children`` is a counter utility for verifying that each
        # document has exactly one parent.
        all_children = collections.Counter()
        # Mapping of (doc.name, doc.metadata.name) => children, where children
        # are the documents whose `parentSelector` references the doc.
        self._parents = {}

        for layer in self._layer_order:
            documents_in_layer = self._documents_by_layer.get(layer, [])
            for document in documents_in_layer:
                children = list(self._calc_document_children(document))

                if children:
                    all_children.update(children)

                for child in children:
                    self._replace_older_parent_with_younger_parent(
                        child, document, all_children)

        all_children_elements = list(all_children.elements())
        secondary_documents = []
        for layer, documents in self._documents_by_layer.items():
            if self._layer_order and layer != self._layer_order[0]:
                secondary_documents.extend(documents)

        for doc in secondary_documents:
            # Unless the document is the topmost document in the
            # `layerOrder` of the LayeringPolicy, it should be a child document
            # of another document.
            if doc not in all_children_elements:
                if doc.parent_selector:
                    LOG.debug(
                        'Could not find parent for document with name=%s, '
                        'schema=%s, layer=%s, parentSelector=%s.', doc.name,
                        doc.schema, doc.layer, doc.parent_selector)
            # If the document is a child document of more than 1 parent, then
            # the document has too many parents, which is a validation error.
            elif all_children[doc] > 1:
                LOG.info('%d parent documents were found for child document '
                         'with name=%s, schema=%s, layer=%s, parentSelector=%s'
                         '. Each document must have exactly 1 parent.',
                         all_children[doc], doc.name, doc.schema, doc.layer,
                         doc.parent_selector)
                raise errors.IndeterminateDocumentParent(
                    name=doc.name, schema=doc.schema, layer=doc.layer,
                    found=all_children[doc])
Example #3
0
    def _calc_document_children(self):
        """Determine each document's children.

        For each document, attempts to find the document's children. Adds a new
        key called "children" to the document's dictionary.

        .. note::

            A document should only have exactly one parent.

            If a document does not have a parent, then its layer must be
            the topmost layer defined by the `layerOrder`.

        :returns: Ordered list of documents that need to be layered. Each
            document contains a "children" property in addition to original
            data. List of documents returned is ordered from highest to lowest
            layer.
        :rtype: list of deckhand.engine.document.Document objects.
        :raises IndeterminateDocumentParent: If more than one parent document
            was found for a document.
        :raises MissingDocumentParent: If the parent document could not be
            found. Only applies documents with `layeringDefinition` property.
        """
        layered_docs = list(
            filter(lambda x: 'layeringDefinition' in x['metadata'],
                   self.documents))

        # ``all_children`` is a counter utility for verifying that each
        # document has exactly one parent.
        all_children = collections.Counter()

        def _get_children(doc):
            children = []
            doc_layer = doc.get_layer()
            try:
                next_layer_idx = self.layer_order.index(doc_layer) + 1
                children_doc_layer = self.layer_order[next_layer_idx]
            except IndexError:
                # The lowest layer has been reached, so no children. Return
                # empty list.
                return children

            for other_doc in layered_docs:
                # Documents with different schemas are never layered together,
                # so consider only documents with same schema as candidates.
                if (other_doc.get_layer() == children_doc_layer
                        and other_doc.get_schema() == doc.get_schema()):
                    # A document can have many labels but should only have one
                    # explicit label for the parentSelector.
                    parent_sel = other_doc.get_parent_selector()
                    parent_sel_key = list(parent_sel.keys())[0]
                    parent_sel_val = list(parent_sel.values())[0]
                    doc_labels = doc.get_labels()

                    if (parent_sel_key in doc_labels
                            and parent_sel_val == doc_labels[parent_sel_key]):
                        children.append(other_doc)

            return children

        for layer in self.layer_order:
            docs_by_layer = list(
                filter((lambda x: x.get_layer() == layer), layered_docs))

            for doc in docs_by_layer:
                children = _get_children(doc)

                if children:
                    all_children.update(children)
                    doc.to_dict().setdefault('children', children)

        all_children_elements = list(all_children.elements())
        secondary_docs = list(
            filter(lambda d: d.get_layer() != self.layer_order[0],
                   layered_docs))
        for doc in secondary_docs:
            # Unless the document is the topmost document in the
            # `layerOrder` of the LayeringPolicy, it should be a child document
            # of another document.
            if doc not in all_children_elements:
                raise errors.MissingDocumentParent(document=doc)
            # If the document is a child document of more than 1 parent, then
            # the document has too many parents, which is a validation error.
            elif all_children[doc] != 1:
                raise errors.IndeterminateDocumentParent(document=doc)

        return layered_docs