Example #1
0
    def _entrypoint_name(self, doc_concepts):
        """ 
        Used to find the name of the correct entrypoint given a set of concepts from a document (JSON or XML).
        Currently assumes that a JSON/XML document contains one and only one entrypoint and raises
        an exception if this is not true.

        Args:
            doc_concepts (list of strings): A list of all concepts in the input JSON/XML document.

        Returns:
            A string contianing the name of the correct entrypoint.
        TODO: This is algorithm is fairly slow since it loops through all entry points which is time
        consuming for a small number of input concepts.  With this said there is not currently enough
        support functions in Taxonomy to accomodate other algorithms.  It may be better to move this
        into taxonomy_semantic and simultaneously improve the speed.
        """

        entrypoints_found = set()
        for entrypoint in self._taxonomy.semantic.get_all_entrypoints():
            for concept in self._taxonomy.semantic.get_entrypoint_concepts(
                    entrypoint):
                for doc_concept in doc_concepts:
                    if doc_concept == concept:
                        entrypoints_found.add(entrypoint)

        if len(entrypoints_found) == 0:
            raise ob.OBValidationError(
                "No entrypoint found given the set of facts")
        elif len(entrypoints_found) == 1:
            for entrypoint in entrypoints_found:
                return entrypoint
        else:
            # Multiple candidate entrypoints are found.  See if there is one and only one
            # perfect fit.
            the_one = None
            for entrypoint in entrypoints_found:
                ok = True
                for doc_concept in doc_concepts:
                    ok2 = False
                    for doc_concept2 in self._taxonomy.semantic.get_entrypoint_concepts(
                            entrypoint):
                        if doc_concept == doc_concept2:
                            ok2 = True
                            break
                    if not ok2:
                        ok = False
                        break
                if ok:
                    if the_one is None:
                        the_one = entrypoint
                    else:
                        raise ob.OBValidationError(
                            "Multiple entrypoints ({}, {}) found given the set of facts"
                            .format(the_one, entrypoint))
            if the_one == None:
                raise ob.OBValidationError(
                    "No entrypoint found given the set of facts")
            else:
                return the_one
Example #2
0
 def test_ob_errors(self):
     self.assertIsInstance(ob.OBError("test message"), ob.OBError)
     self.assertIsInstance(ob.OBTypeError("test message"), ob.OBTypeError)
     self.assertIsInstance(ob.OBContextError("test message"), ob.OBContextError)
     self.assertIsInstance(ob.OBConceptError("test message"), ob.OBConceptError)
     self.assertIsInstance(ob.OBNotFoundError("test message"), ob.OBNotFoundError)
     self.assertIsInstance(ob.OBUnitError("test message"), ob.OBUnitError)
     self.assertIsInstance(ob.OBValidationError("test message"), ob.OBValidationError)
Example #3
0
    def validate_concept_value(self, concept_details, value):
        """
        Validate a concept value.

        Args:
            concept_details (ConceptDetails): concept details.
            value (*): value to be validated.

        Returns:
            A tuple (*, list of str) containing original or converted value 
            and list of errors (can be empty).
        """
        errors = []
        result = (value, [])
        # If null check if nillable is ok and return
        if value is None and not concept_details.nillable:
            errors += [
                "'{}' is not allowed to be nillable (null).".format(
                    concept_details.id)
            ]
        enum = self._taxonomy.types.get_type_enum(concept_details.type_name)
        # Check data type and validator calling
        if type(concept_details.type_name).__name__ in ["str", "unicode"]:
            method_name = self._get_validator_method_name(
                concept_details.type_name)
            # validator_module = sys.modules[__name__]
            found_method = getattr(self, method_name, None)
            if found_method:
                if enum:
                    result = found_method(value, enum)
                else:
                    result = found_method(value)
            elif enum:
                result = self._generic_enum_validator(value, concept_details,
                                                      enum)
            else:
                raise ob.OBValidationError(
                    "Concept '{}' could not be processed. Missing method '{}'."
                    .format(concept_details.type_name, method_name))

        # Check identifiers.  This is based upon the name of the field containing
        # the word Identifier in it.  Avoid UtilityIdentifier which is a LEI.
        if concept_details.id != "solar:UtilityIdentifier" and concept_details.id.find(
                "Identifier") != -1:
            if not identifier.validate(value):
                errors += [
                    "'{}' is not valid identifier.".format(concept_details.id)
                ]

        # If all conditions clear then the value passes.
        errors += result[1]
        return result[0], errors
Example #4
0
    def from_XML_string(self, xml_string, entrypoint_name=None):
        """
        Loads the Entrypoint from an XML string.    If no entrypoint_name
        is given the entrypoint will be derived from the facts.  In some cases this is not
        possible because more than one entrypoint could exist given the list of facts and
        in these cases an entrypoint is required.

        Args:
            xml_string(str): String containing XML.
            entrypoint_name (str): Optional name of the entrypoint.

        Returns:
            OBInstance containing the loaded data.
        """

        # NOTE: The XML parser has much less effort placed into both the coding and testing as
        # opposed to the coding and testing effort performed on the JSON parser.  To some extent
        # this is on purpose since the hope is that the bulk of Orange Button data will be
        # transmitted using JSON.  With this you are invited to (a) refactor the XML parsing code
        # and (b) create XML test cases if you believe that XML parser should receive the same
        # effort level as the JSON parser.

        # Create a validation error which can be used to maintain a list of error messages
        validation_errors = ob.OBValidationErrors(
            "Error(s) found in input JSON")

        try:
            root = ElementTree.fromstring(xml_string)
        except Exception as e:
            validation_errors.append(e)
            raise validation_errors

        # Read all elements that are not a context or a unit:
        if not entrypoint_name:
            fact_names = []
            for child in root:
                if child.tag != _xn("link:schemaRef") and child.tag != _xn(
                        "unit") and child.tag != _xn("context"):

                    tag = child.tag
                    tag = tag.replace(
                        "{http://xbrl.us/Solar/v1.2/2018-03-31/solar}",
                        "solar:")
                    tag = tag.replace("{http://fasb.org/us-gaap/2017-01-31}",
                                      "us-gaap:")
                    tag = tag.replace("{http://xbrl.sec.gov/dei/2014-01-31}",
                                      "dei:")
                    fact_names.append(tag)
            try:
                entrypoint_name = self._entrypoint_name(fact_names)
            except ob.OBValidationError as ve:
                validation_errors.append(ve)
                raise validation_errors

        # Create an entrypoint.
        entrypoint = data_model.OBInstance(entrypoint_name,
                                           self._taxonomy,
                                           dev_validation_off=True)

        # Read in units
        units = {}
        for unit in root.iter(_xn("unit")):
            units[unit.attrib["id"]] = unit[0].text

        # Read in contexts
        contexts = {}
        for context in root.iter(_xn("context")):
            instant = None
            duration = None
            entity = None
            start_date = None
            end_date = None
            axis = {}
            for elem in context.iter():
                if elem.tag == _xn("period"):
                    if elem[0].tag == _xn("forever"):
                        duration = "forever"
                    elif elem[0].tag == _xn("startDate"):
                        start_date = elem[0].text
                    elif elem[0].tag == _xn("endDate"):
                        end_date = elem[0].text
                    elif elem[0].tag == _xn("instant"):
                        instant = elem[0].text
                elif elem.tag == _xn("entity"):
                    for elem2 in elem.iter():
                        if elem2.tag == _xn("identifier"):
                            entity = elem2.text
                        elif elem2.tag == _xn("segment"):
                            for elem3 in elem2.iter():
                                if elem3.tag == _xn("xbrldi:typedMember"):
                                    for elem4 in elem3.iter():
                                        if elem4.tag != _xn(
                                                "xbrldi:typedMember"):
                                            axis[elem3.attrib[
                                                "dimension"]] = elem4.text

            if duration is None and start_date is not None and end_date is not None:
                duration = {"start": start_date, "end": end_date}
            kwargs = {}
            if instant is not None:
                kwargs["instant"] = instant
            if duration is not None:
                kwargs["duration"] = duration
            if entity is not None:
                kwargs["entity"] = entity
            if axis is not None:
                for a in axis:
                    kwargs[a] = axis[a]

            if instant is None and duration is None:
                validation_errors.append(
                    ob.OBValidationError(
                        "Context is missing both a duration and instant tag"))
            if entity is None:
                validation_errors.append("Context is missing an entity tag")

            try:
                dm_ctx = data_model.Context(**kwargs)
                contexts[context.attrib["id"]] = dm_ctx
            except Exception as e:
                validation_errors.append(e)

        # Read all elements that are not a context or a unit:
        for child in root:
            if child.tag != _xn("link:schemaRef") and child.tag != _xn(
                    "unit") and child.tag != _xn("context"):
                kwargs = {}

                fact_id = None
                if "id" in child.attrib:
                    fact_id = child.attrib["id"]

                if "contextRef" in child.attrib:
                    if child.attrib["contextRef"] in contexts:
                        kwargs["context"] = contexts[
                            child.attrib["contextRef"]]
                        kwargs["fact_id"] = fact_id
                        tag = child.tag
                        tag = tag.replace(
                            "{http://xbrl.us/Solar/v1.2/2018-03-31/solar}",
                            "solar:")
                        tag = tag.replace(
                            "{http://fasb.org/us-gaap/2017-01-31}", "us-gaap:")
                        tag = tag.replace(
                            "{http://xbrl.sec.gov/dei/2014-01-31}", "dei:")
                        try:
                            entrypoint.set(tag, child.text, **kwargs)
                        except Exception as e:
                            validation_errors.append(e)
                    else:
                        validation_errors.append(
                            "referenced context is missing")
                else:
                    validation_errors.append("Element is missing a context")

        # Raise the errors if necessary
        if validation_errors.get_errors():
            raise validation_errors

        # Return populated entrypoint
        return entrypoint