def test_csolink_model(self):
     """ Make sure the csolink model is valid """
     schema = SchemaLoader(CSOLINK_MODEL_YAML)
     errors = []
     try:
         schema.resolve()
     except ValueError as e:
         errors.append(str(e))
     if not errors:
         errors = schema.synopsis.errors()
     self.assertEqual([], errors, "csolink-model.yaml - errors detected")
示例#2
0
def cli(inputs, view: bool):
    """
    Generates SQL VIEW commands from hints embedded in linkml
    """
    for input in inputs:
        with open(input, 'r') as stream:
            schema = load_raw_schema(input)
            print('-- ** REWRITE TABLES AS VIEWS **')
            print(f'-- SCHEMA: {schema.id}')
            loader = SchemaLoader(schema, mergeimports=True)
            loader.resolve()
            generate_views_from_linkml(schema, view)
示例#3
0
    def eval_synopsis(self,
                      base_name: str,
                      source: Optional[str] = None) -> None:
        schema = SchemaLoader(source if source else env.input_path(base_name +
                                                                   '.yaml'),
                              importmap=env.import_map)
        schema.resolve()
        self.summary = schema.synopsis.summary()

        self.env.generate_single_file(
            base_name + '.errs',
            lambda: '\n'.join(schema.synopsis.errors()),
            value_is_returned=True)
        self.env.generate_single_file(base_name + '.synopsis',
                                      lambda: self.summary,
                                      value_is_returned=True)
示例#4
0
def cli(inputs, limit: int):
    """
    Generates SQL VIEW commands from hints embedded in linkml linkml
    """
    for input in inputs:
        with open(input, 'r') as stream:
            schema = load_raw_schema(input)
            print('-- ** REWRITE TABLES AS VIEWS **')
            print(f'-- SCHEMA: {schema.id}')
            loader = SchemaLoader(schema, mergeimports=True)
            loader.resolve()
            for cn, c in schema.classes.items():
                if c.mixin:
                    continue
                if len(c.slots) > 0:
                    if not c.abstract and not c.mixin:
                        sql_table = underscore(cn)
                        print(f'SELECT * FROM {sql_table} LIMIT {limit};')
                else:
                    print(f'-- No slots for {cn}')
示例#5
0
 def eval_loader(self,
                 base_name: str,
                 logger: Optional[logging.Logger] = None,
                 source: Optional[str] = None) -> None:
     loader = SchemaLoader(source
                           or self.env.input_path(base_name + '.yaml'),
                           logger=logger)
     self.env.generate_single_file(base_name + '.json',
                                   lambda: as_json(loader.resolve()),
                                   filtr=json_metadata_filter,
                                   value_is_returned=True)
     self.env.generate_single_file(
         base_name + '.errs',
         lambda: '\n'.join(loader.synopsis.errors()),
         filtr=json_metadata_filter,
         value_is_returned=True)
示例#6
0
class OwlSchemaGenerator(Generator):
    generatorname = os.path.basename(__file__)
    generatorversion = "0.1.1"
    valid_formats = ['owl', 'ttl'] + [
        x.name
        for x in rdflib_plugins(None, rdflib_Parser) if '/' not in str(x.name)
    ]
    visits_are_sorted = True

    def __init__(self, schema: Union[str, TextIO, SchemaDefinition],
                 **kwargs) -> None:
        super().__init__(schema, **kwargs)
        self.graph: Optional[Graph] = None
        self.metamodel = SchemaLoader(LOCAL_METAMODEL_YAML_FILE, importmap=kwargs.get('importmap', None),
                                      mergeimports=self.merge_imports) \
            if os.path.exists(LOCAL_METAMODEL_YAML_FILE) else\
            SchemaLoader(METAMODEL_YAML_URI, base_dir=META_BASE_URI, importmap=kwargs.get('importmap', None),
                         mergeimports=self.merge_imports)
        self.metamodel.resolve()
        self.top_value_uri: Optional[URIRef] = None

    def visit_schema(self, output: Optional[str] = None, **_):
        base = URIRef(self.schema.id)
        self.graph = Graph(identifier=base)
        for prefix in self.metamodel.schema.emit_prefixes:
            self.graph.bind(prefix, self.metamodel.namespaces[prefix])

        self.graph.add((base, RDF.type, OWL.Ontology))
        self._add_element_properties(base, self.schema)

        # add the model types
        for name in [
                'class_definition', 'type_definition', 'slot_definition',
                'subset_definition'
        ]:
            self._add_metamodel_class(name)

        # add value placeholder
        self.top_value_uri = self.metamodel.namespaces[
            METAMODEL_NAMESPACE_NAME]['topValue']
        self.graph.add((self.top_value_uri, RDF.type, OWL.DatatypeProperty))
        self.graph.add((self.top_value_uri, RDFS.label, Literal("value")))

    def end_schema(self, output: Optional[str] = None, **_) -> None:
        data = self.graph.serialize(format='turtle' if self.format in
                                    ['owl', 'ttl'] else self.format).decode()
        if output:
            with open(output, 'w') as outf:
                outf.write(data)
        else:
            print(data)

    def add_metadata(self, e: SchemaDefinition, uri: URIRef) -> None:
        if e.aliases is not None:
            for s in e.aliases:
                self.graph.add((uri, SKOS.altLabel, Literal(s)))
        if e.mappings is not None:
            for m in e.mappings:
                m_uri = self.namespaces.uri_for(m)
                if m_uri is not None:
                    self.graph.add((uri, SKOS.exactMatch, m_uri))
                else:
                    logging.warning(f'No URI for {m}')
        if e.exact_mappings is not None:
            for m in e.exact_mappings:
                m_uri = self.namespaces.uri_for(m)
                if m_uri is not None:
                    self.graph.add((uri, SKOS.exactMatch, m_uri))
                else:
                    logging.warning(f'No URI for {m}')
        if e.close_mappings is not None:
            for m in e.close_mappings:
                m_uri = self.namespaces.uri_for(m)
                if m_uri is not None:
                    self.graph.add((uri, SKOS.closeMatch, m_uri))
                else:
                    logging.warning(f'No URI for {m}')
        if e.narrow_mappings is not None:
            for m in e.narrow_mappings:
                m_uri = self.namespaces.uri_for(m)
                if m_uri is not None:
                    self.graph.add((uri, SKOS.narrowMatch, m_uri))
                else:
                    logging.warning(f'No URI for {m}')
        if e.broad_mappings is not None:
            for m in e.broad_mappings:
                m_uri = self.namespaces.uri_for(m)
                if m_uri is not None:
                    self.graph.add((uri, SKOS.broadMatch, m_uri))
                else:
                    logging.warning(f'No URI for {m}')
        if e.related_mappings is not None:
            for m in e.related_mappings:
                m_uri = self.namespaces.uri_for(m)
                if m_uri is not None:
                    self.graph.add((uri, SKOS.relatedMatch, m_uri))
                else:
                    logging.warning(f'No URI for {m}')

    def visit_class(self, cls: ClassDefinition) -> bool:
        cls_uri = self._class_uri(cls.name)
        self.add_metadata(cls, cls_uri)
        self.graph.add((cls_uri, RDF.type, OWL.Class))
        self.graph.add((cls_uri, RDF.type,
                        self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
                            camelcase('class definition')]))
        self._add_element_properties(cls_uri, cls)

        # Parent classes
        # TODO: reintroduce this
        # if not cls.defining_slots:
        if True:
            if cls.is_a:
                self.graph.add(
                    (cls_uri, RDFS.subClassOf, self._class_uri(cls.is_a)))
            if cls.mixin:
                self.graph.add(
                    (cls_uri, RDFS.subClassOf, METAMODEL_NAMESPACE.mixin))
            for mixin in sorted(cls.mixins):
                self.graph.add(
                    (cls_uri, RDFS.subClassOf, self._class_uri(mixin)))
            if cls.name in self.synopsis.applytorefs:
                for appl in sorted(
                        self.synopsis.applytorefs[cls.name].classrefs):
                    self.graph.add(
                        (cls_uri, RDFS.subClassOf, self._class_uri(appl)))
        else:
            raise NotImplementedError("Defining slots need to be implemented")
            # If defining slots, we generate an equivalentClass entry
            # equ_node = BNode()
            # self.graph.add((cls_uri, OWL.equivalentClass, equ_node))
            # self.graph.add((equ_node, RDF.type, OWL.Class))
            #
            # elts = []
            # if cls.is_a:
            #     elts.append(self._class_uri(cls.is_a))
            # if cls.mixin:
            #     self.graph.add((cls_uri, RDFS.subClassOf, META_NS.mixin))
            # for mixin in cls.mixins:
            #     self.graph.add((cls_uri, RDFS.subClassOf, self._class_uri(mixin)))
            # if cls.name in self.synopsis.applytorefs:
            #     for appl in self.synopsis.applytorefs[cls.name].classrefs:
            #         self.graph.add((cls_uri, RDFS.subClassOf, self._class_uri(appl)))
            #
            # for slotname in cls.defining_slots:
            #     restr_node = BNode()
            #     slot = self.schema.slots[slotname]
            #
            #     self.graph.add((restr_node, RDF.type, OWL.Restriction))
            #     self.graph.add((restr_node, OWL.onProperty, self._prop_uri(slotname)))
            #     self._add_cardinality(restr_node, slot)
            #     # TODO: fix this
            #     # self.graph.add((restr_node, OWL.someValuesFrom, self._build_range(slot)))
            #     elts.append(restr_node)
            #
            # coll_bnode = BNode()
            # Collection(self.graph, coll_bnode, elts)
            # self.graph.add((equ_node, OWL.intersectionOf, coll_bnode))

        # TODO: see whether unions belong
        # if cls.union_of:
        #     union_node = BNode()
        #     Collection(self.graph, union_coll, [self.class_uri(union_node) for union_node in cls.union_of])
        #     self.graph.add((union_node, OWL.unionOf, union_coll))
        #     self.graph.add((cls_uri, RDFS.subClassOf, union_node))

        for sn in sorted(self.own_slot_names(cls)):
            # Defining_slots are covered above
            if sn not in cls.defining_slots:
                slot = self.schema.slots[sn]
                # Non-inherited slots are annotation properties
                if self.is_slot_object_property(slot):
                    slot_node = BNode()
                    self.graph.add((cls_uri, RDFS.subClassOf, slot_node))

                    #         required multivalued
                    if slot.required:
                        if slot.multivalued:
                            #    y         y     intersectionOf(restriction(slot only type) restriction(slot some type)
                            restr1 = BNode()
                            self.graph.add((restr1, RDF.type, OWL.Restriction))
                            self.graph.add((restr1, OWL.allValuesFrom,
                                            self._range_uri(slot)))
                            self.graph.add(
                                (restr1, OWL.onProperty,
                                 self._prop_uri(self.aliased_slot_name(slot))))

                            restr2 = BNode()
                            self.graph.add((restr2, RDF.type, OWL.Restriction))
                            self.graph.add((restr2, OWL.someValuesFrom,
                                            self._range_uri(slot)))
                            self.graph.add(
                                (restr2, OWL.onProperty,
                                 self._prop_uri(self.aliased_slot_name(slot))))

                            coll_bnode = BNode()
                            Collection(self.graph, coll_bnode,
                                       [restr1, restr2])
                            self.graph.add(
                                (slot_node, OWL.intersectionOf, coll_bnode))
                            self.graph.add((slot_node, RDF.type, OWL.Class))
                        else:
                            #    y         n      restriction(slot exactly 1 type)
                            self.graph.add(
                                (slot_node, RDF.type, OWL.Restriction))
                            self.graph.add(
                                (slot_node, OWL.qualifiedCardinality,
                                 Literal(1)))
                            self.graph.add(
                                (slot_node, OWL.onProperty,
                                 self._prop_uri(self.aliased_slot_name(slot))))
                            self.graph.add((slot_node, OWL.onClass,
                                            self._range_uri(slot)))
                    else:
                        if slot.multivalued:
                            #    n         y      restriction(slot only type)
                            self.graph.add(
                                (slot_node, RDF.type, OWL.Restriction))
                            self.graph.add((slot_node, OWL.allValuesFrom,
                                            self._range_uri(slot)))
                            self.graph.add(
                                (slot_node, OWL.onProperty,
                                 self._prop_uri(self.aliased_slot_name(slot))))
                        else:
                            #    n         n      intersectionOf(restriction(slot only type) restriction(slot max 1 type))
                            self.graph.add(
                                (slot_node, RDF.type, OWL.Restriction))
                            self.graph.add((slot_node, OWL.onClass,
                                            self._range_uri(slot)))
                            self.graph.add(
                                (slot_node, OWL.maxQualifiedCardinality,
                                 Literal(1)))
                            self.graph.add(
                                (slot_node, OWL.onProperty,
                                 self._prop_uri(self.aliased_slot_name(slot))))

        return True

    def visit_slot(self, slot_name: str, slot: SlotDefinition) -> None:
        """ Add a slot definition per slot

        @param slot_name:
        @param slot:
        @return:
        """
        # Note: We use the raw name in OWL and add a subProperty arc
        slot_uri = self._prop_uri(slot.name)
        self._add_element_properties(slot_uri, slot)
        # Inherited slots are object or data properties
        # All others are annotation properties
        self.graph.add(
            (slot_uri, RDF.type, OWL.ObjectProperty if
             self.is_slot_object_property(slot) else OWL.AnnotationProperty))
        self.graph.add((slot_uri, RDF.type,
                        self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
                            camelcase('slot definition')]))
        self.graph.add((slot_uri, RDFS.range, self._range_uri(slot)))
        if slot.domain:
            self.graph.add(
                (slot_uri, RDFS.domain, self._class_uri(slot.domain)))
        if slot.inverse:
            self.graph.add(
                (slot_uri, OWL.inverseOf, self._prop_uri(slot.inverse)))
        if slot.symmetric:
            self.graph.add((slot_uri, RDF.type, OWL.SymmetricProperty))

        # Parent slots
        if slot.is_a:
            self.graph.add(
                (slot_uri, RDFS.subPropertyOf, self._prop_uri(slot.is_a)))
        for mixin in slot.mixins:
            self.graph.add(
                (slot_uri, RDFS.subPropertyOf, self._prop_uri(mixin)))
        if slot.name in self.synopsis.applytorefs:
            for appl in self.synopsis.applytorefs[slot.name].slotrefs:
                self.graph.add(
                    (slot_uri, RDFS.subClassOf, self._prop_uri(appl)))

    def visit_type(self, typ: TypeDefinition) -> None:
        type_uri = self._type_uri(typ.name)
        self.graph.add((type_uri, RDF.type, OWL.Class))
        self.graph.add((type_uri, RDF.type,
                        self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
                            camelcase('type definition')]))
        self._add_element_properties(type_uri, typ)
        if typ.typeof:
            self.graph.add(
                (type_uri, RDFS.subClassOf, self._type_uri(typ.typeof)))
        else:
            restr = BNode()
            self.graph.add((restr, RDF.type, OWL.Restriction))
            self.graph.add((restr, OWL.qualifiedCardinality, Literal(1)))
            self.graph.add((restr, OWL.onProperty, self.top_value_uri))
            self.graph.add(
                (restr, OWL.onDataRange, self.namespaces.uri_for(typ.uri)))
            self.graph.add((type_uri, RDFS.subClassOf, restr))

    def _add_element_properties(self, uri: URIRef, el: Element) -> None:
        for k, v in el.__dict__.items():
            if k in self.metamodel.schema.slots:
                defining_slot = self.metamodel.schema.slots[k]
                if v is not None and 'owl' in defining_slot.in_subset:
                    ve = v if isinstance(v, list) else [v]
                    for e in ve:
                        self.graph.add(
                            (uri,
                             URIRef(
                                 self.metamodel.namespaces.uri_for(
                                     defining_slot.slot_uri)), Literal(e)))

    def _add_cardinality(self, subj: Union[BNode, URIRef], slot) -> None:
        """ Add cardinality restrictions to node """
        if slot.required:
            if slot.multivalued:
                self.graph.add((subj, OWL.minCardinality, Literal(1)))
            else:
                self.graph.add((subj, OWL.cardinality, Literal(1)))
        elif not slot.multivalued:
            self.graph.add((subj, OWL.maxCardinality, Literal(1)))

    def _range_uri(self, slot: SlotDefinition) -> URIRef:
        if slot.range in self.schema.types:
            return self._type_uri(TypeDefinitionName(slot.range))
        elif slot.range in self.schema.enums:
            # TODO: enums fill this in
            return self._enum_uri(EnumDefinitionName(slot.range))
        else:
            return self._class_uri(ClassDefinitionName(slot.range))

    def _class_uri(self, cn: ClassDefinitionName) -> URIRef:
        c = self.schema.classes[cn]
        return URIRef(c.definition_uri)

    def _enum_uri(self, en: EnumDefinitionName) -> URIRef:
        # TODO: enums
        e = self.schema.enums[en]
        return URIRef(f"http://UNKNOWN.org/{en}")

    def _prop_uri(self, pn: SlotDefinitionName) -> URIRef:
        p = self.schema.slots.get(pn, None)
        if p is None or p.definition_uri is None:
            return self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
                underscore(pn)]
        else:
            return URIRef(p.definition_uri)

    def _type_uri(self, tn: TypeDefinitionName) -> URIRef:
        t = self.schema.types[tn]
        return URIRef(t.definition_uri)

    def _add_metamodel_class(self, cname: str) -> None:
        metac = self.metamodel.schema.classes[cname]
        metac_uri = self.metamodel.namespaces[METAMODEL_NAMESPACE_NAME][
            camelcase(metac.name)]
        self.graph.add((metac_uri, RDF.type, OWL.Class))
        self._add_element_properties(metac_uri, metac)

    def is_slot_object_property(self, slot: SlotDefinition) -> bool:
        return True
示例#7
0
    def __init__(self,
                 schema: Union[str, TextIO, SchemaDefinition, "Generator"],
                 format: Optional[str] = None,
                 emit_metadata: bool = True,
                 useuris: Optional[bool] = None,
                 importmap: Optional[str] = None,
                 log_level: int = DEFAULT_LOG_LEVEL_INT,
                 mergeimports: Optional[bool] = True,
                 source_file_date: Optional[str] = None,
                 source_file_size: Optional[int] = None,
                 logger: Optional[logging.Logger] = None,
                 **kwargs) -> None:
        """
        Constructor

        :param schema: metamodel compliant schema.  Can be URI, file name, actual schema, another generator, an
        open file or a pre-parsed schema.
        :param format: expected output format
        :param emit_metadata: True means include date, generator, etc. information in source header if appropriate
        :param useuris: True means declared class slot uri's are used.  False means use model uris
        :param importmap: File name of import mapping file -- maps import name/uri to target
        :param log_level: Logging level
        :param mergeimports: True means merge non-linkml sources into importing package.  False means separate packages.
        :param source_file_date: Modification date of input source file
        :param source_file_size: Source file size
        :param logger: pre-set logger
        """
        if logger:
            self.logger = logger
        else:
            logging.basicConfig()
            self.logger = logging.getLogger(self.__class__.__name__)
            self.logger.setLevel(log_level)

        if format is None:
            format = self.valid_formats[0]
        assert format in self.valid_formats, f"Unrecognized format: {format}"
        self.format = format
        self.emit_metadata = emit_metadata
        self.merge_imports = mergeimports
        self.source_file_date = source_file_date if emit_metadata else None
        self.source_file_size = source_file_size if emit_metadata else None
        if isinstance(schema, Generator):
            gen = schema
            self.schema = gen.schema
            self.synopsis = gen.synopsis
            self.loaded = gen.loaded
            self.namespaces = gen.namespaces
            self.base_dir = gen.base_dir
            self.importmap = gen.importmap
            self.source_file_data = gen.source_file_date
            self.source_file_size = gen.source_file_size
            self.schema_location = gen.schema_location
            self.schema_defaults = gen.schema_defaults
            self.logger = gen.logger
        else:
            loader = SchemaLoader(schema,
                                  self.base_dir,
                                  useuris=useuris,
                                  importmap=importmap,
                                  logger=self.logger,
                                  mergeimports=mergeimports,
                                  emit_metadata=emit_metadata,
                                  source_file_date=self.source_file_date,
                                  source_file_size=self.source_file_size)
            loader.resolve()
            self.schema = loader.schema
            self.synopsis = loader.synopsis
            self.loaded = loader.loaded
            self.namespaces = loader.namespaces
            self.base_dir = loader.base_dir
            self.importmap = loader.importmap
            self.source_file_data = loader.source_file_date
            self.source_file_size = loader.source_file_size
            self.schema_location = loader.schema_location
            self.schema_defaults = loader.schema_defaults