Beispiel #1
0
def main(schema_yaml: str = None, output: str = None):
    yaml = ruamel.yaml.YAML()
    schema = yaml.load(open(schema_yaml, "r"))
    schemaview = SchemaView(schema=schema_yaml)
    creation_classes = get_creation_classes(schemaview)
    if not output:
        output = "new-schema.yaml"

    new_schema = parse_schema(
        schema, schemaview, creation_classes, SLOTS_TO_RELAX, SLOTS_TO_ENFORCE
    )
    if SLOTS_TO_RELAX:
        relax_slots(new_schema, SLOTS_TO_RELAX)
    if CLASS_SLOTS_TO_RELAX:
        relax_class_slots(new_schema, CLASS_SLOTS_TO_RELAX)
    if SLOTS_TO_ENFORCE:
        enforce_slots(new_schema, SLOTS_TO_ENFORCE)
    if CLASS_SLOTS_TO_ENFORCE:
        enforce_class_slots(new_schema, CLASS_SLOTS_TO_ENFORCE)
    if SLOTS_TO_REMOVE:
        remove_slots(new_schema, SLOTS_TO_REMOVE)
    if CLASS_SLOTS_TO_REMOVE:
        remove_class_slots(new_schema, CLASS_SLOTS_TO_REMOVE)

    yaml.dump(new_schema, open(output, 'w'))
    print(
        f"Total classes: {len(schema['classes'])} (original) vs {len(new_schema['classes'])} (new)"
    )
    print(f"Total slots: {len(schema['slots'])} (original) vs {len(new_schema['slots'])} (new)")
    print(f"Total enums: {len(schema['enums'])} (original) vs {len(new_schema['enums'])} (new)")
Beispiel #2
0
def get_creation_classes(schemaview: SchemaView) -> Set[str]:
    """
    Get a list of classes for which to generate creation
    specific classes.

    Args:
        schemaview: The SchemaView instance of the schema

    Returns:
        A set of class names

    """
    classes = schemaview.class_descendants(ROOT_CLASS)
    creation_classes = set([x for x in classes if not schemaview.get_class(x).abstract])
    creation_classes.add("submission")
    return creation_classes
Beispiel #3
0
def to_rdf_graph(msdf: MappingSetDataFrame) -> Graph:
    """Convert a mapping set dataframe to an RDF graph."""
    doc = to_mapping_set_document(msdf)
    # cntxt = prepare_context(doc.prefix_map)

    # rdflib_dumper.dump(
    #     element=doc.mapping_set,
    #     schemaview=SchemaView(os.path.join(os.getcwd(), "schema/sssom.yaml")),
    #     prefix_map=msdf.prefix_map,
    #     to_file="sssom.ttl",
    # )
    # graph = Graph()
    # graph = graph.parse("sssom.ttl", format="ttl")

    # os.remove("sssom.ttl")  # remove the intermediate file.
    graph = rdflib_dumper.as_rdf_graph(
        element=doc.mapping_set,
        schemaview=SchemaView(SCHEMA_YAML),
        prefix_map=msdf.prefix_map,
    )
    return graph
Beispiel #4
0
 def __init__(self, schema: Union[str, TextIO, SchemaDefinition],
              **kwargs) -> None:
     super().__init__(schema, **kwargs)
     self.schemaview = SchemaView(schema)
Beispiel #5
0
class JekyllMarkdownGenerator(MarkdownGenerator):
    """
    Extends linkml.generators.markdowngen.MarkdownGenerator to add new styles
    and override certain existing styles.
    """
    generatorname = os.path.basename(__file__)
    generatorversion = "0.2.0"
    valid_formats = ["md"]
    visit_all_class_slots = False

    doc_root_title = None

    def __init__(self, schema: Union[str, TextIO, SchemaDefinition],
                 **kwargs) -> None:
        super().__init__(schema, **kwargs)
        self.schemaview = SchemaView(schema)

    def visit_schema(self,
                     directory: str = None,
                     classes: Set[ClassDefinitionName] = None,
                     image_dir: bool = False,
                     noimages: bool = False,
                     **_) -> None:
        """
        Visit the schema and generate Markdown for each ClassDefinition, SlotDefinition,
        and TypeDefinition.

        Parameters
        ----------
        directory: str
            The directory to write to
        classes: Set[ClassDefinitionName]
            A set of classes to subset
        image_dir: str
            The directory to write static images
        noimages: bool
            Whether or not to generate static images

        """
        self.gen_classes = classes if classes else []
        for cls in self.gen_classes:
            if cls not in self.schema.classes:
                raise ValueError("Unknown class name: {cls}")
        if self.gen_classes:
            self.gen_classes_neighborhood = self.neighborhood(
                list(self.gen_classes))

        self.directory = directory
        if directory:
            os.makedirs(directory, exist_ok=True)
        elif image_dir:
            raise ValueError(
                f"Image directory can only be used with '-d' option")
        if image_dir:
            self.image_directory = os.path.join(directory, 'images')
            if not noimages:
                os.makedirs(self.image_directory, exist_ok=True)
        self.noimages = noimages
        self.types_directory = os.path.join(directory, 'types')
        os.makedirs(self.types_directory, exist_ok=True)
        self.doc_root_title = f'Browse {self.schema.name.title().replace("-", " ")}'
        self.seen_elements = set()
        with open(os.path.join(directory, 'index.md'), 'w') as ixfile:
            # Create the data model index
            with redirect_stdout(ixfile):
                self.frontmatter(
                    **{
                        'title': self.doc_root_title,
                        'has_children': 'true',
                        'nav_order': 2,
                        'layout': 'default',
                        'has_toc': 'false'
                    })
                self.para(be(self.schema.description))

                self.header(2, 'Classes')

                self.header(3, "Entity")
                cls = self.schemaview.get_element('entity')
                self.seen_elements.add(cls.name)
                self.bullet(self.class_link(cls, use_desc=True))

                self.header(3, 'Named Things')

                cls = self.schemaview.get_element('named thing')
                self.seen_elements.add(cls.name)
                self.class_hier(cls)

                self.header(3, 'Associations')
                cls = self.schemaview.get_element('association')
                self.seen_elements.add(cls.name)
                self.class_hier(cls)

                self.header(3, 'Class Mixins')
                for cls in sorted(self.schema.classes.values(),
                                  key=lambda c: c.name.lower()):
                    if cls.mixin and self.is_secondary_ref(cls.name):
                        if cls.name not in self.seen_elements:
                            self.seen_elements.add(cls.name)
                            self.class_hier(cls)

                self.header(3, 'Other Classes')
                for cls in sorted(self.schema.classes.values(),
                                  key=lambda c: c.name.lower()):
                    if cls.name not in self.seen_elements:
                        self.seen_elements.add(cls.name)
                        self.class_hier(cls)

                self.header(2, 'Slots')

                self.header(3, 'Predicates')
                for slot in sorted(self.schema.slots.values(),
                                   key=lambda c: c.name.lower()):
                    if not slot.alias:
                        if 'related to' in self.ancestors(
                                slot) and not slot.mixin:
                            self.seen_elements.add(slot.name)
                            self.pred_hier(slot)

                self.header(3, 'Node Properties')
                for slot in sorted(self.schema.slots.values(),
                                   key=lambda s: s.name.lower()):
                    ancs = self.ancestors(slot)
                    if not slot.alias:
                        if 'node property' in ancs and not slot.mixin:
                            self.seen_elements.add(slot.name)
                            self.pred_hier(slot)

                self.header(3, 'Edge Properties')
                for slot in sorted(self.schema.slots.values(),
                                   key=lambda s: s.name.lower()):
                    ancs = self.ancestors(slot)
                    if not slot.alias:
                        if 'association slot' in ancs and not slot.mixin:
                            self.seen_elements.add(slot.name)
                            self.pred_hier(slot)

                self.header(3, 'Slot Mixins')
                for slot in sorted(self.schema.slots.values(),
                                   key=lambda s: s.name):
                    if not slot.alias:
                        if slot.mixin:
                            self.seen_elements.add(slot.name)
                            self.pred_hier(slot)

                self.header(3, 'Other Slots')
                for slot in sorted(self.schema.slots.values(),
                                   key=lambda s: s.name):
                    if not slot.alias:
                        if slot.name not in self.seen_elements:
                            ancs = self.ancestors(slot)
                            if len(ancs) <= 1:
                                self.seen_elements.add(slot.name)
                                self.pred_hier(slot)

                self.header(2, 'Subsets')
                for subset in sorted(self.schema.subsets.values(),
                                     key=lambda s: s.name):
                    self.seen_elements.add(subset.name)
                    self.bullet(self.subset_link(subset, use_desc=True))

                self.header(2, 'Types')
                self.header(3, 'Built in')
                for builtin_name in sorted(self.synopsis.typebases.keys()):
                    self.bullet(f'**{builtin_name}**')

                self.header(3, 'Defined')
                for typ in sorted(self.schema.types.values(),
                                  key=lambda t: t.name):
                    if self.is_secondary_ref(typ.name):
                        if typ.typeof:
                            typ_typ = self.type_link(typ.typeof)
                        else:
                            typ_typ = f'**{typ.base}**'
                        self.bullet(
                            self.type_link(typ,
                                           after_link=f' ({typ_typ})',
                                           use_desc=True))

                self.header(2, "Enums")
                for enum in sorted(self.schema.enums.values(),
                                   key=lambda s: s.name.lower()):
                    self.bullet(self.enum_link(enum, use_desc=True), 0)

        # create parent for organizing markdown based on Class types
        with open(os.path.join(directory, 'classes.md'), 'w') as file:
            file.write(
                f'---\nparent: {self.doc_root_title}\ntitle: Classes\nhas_children: true\nnav_order: 1\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'named_things.md'), 'w') as file:
            file.write(
                f'---\nparent: Classes\ngrand_parent: {self.doc_root_title}\ntitle: Named Things\nhas_children: true\nnav_order: 1\nlayout: default\n---'
            )

        with open(os.path.join(directory, 'associations.md'), 'w') as file:
            file.write(
                f'---\nparent: Classes\ngrand_parent: {self.doc_root_title}\ntitle: Associations\nhas_children: true\nnav_order: 2\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'class_mixins.md'), 'w') as file:
            file.write(
                f'---\nparent: Classes\ngrand_parent: {self.doc_root_title}\ntitle: Class Mixins\nhas_children: true\nnav_order: 3\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'other_classes.md'), 'w') as file:
            file.write(
                f'---\nparent: Classes\ngrand_parent: {self.doc_root_title}\ntitle: Other Classes\nhas_children: true\nnav_order: 4\nlayout: default\n---'
            )

        # create parent for organizing markdown based on Slot types
        with open(os.path.join(directory, 'slots.md'), 'w') as file:
            file.write(
                f'---\nparent: {self.doc_root_title}\ntitle: Slots\nhas_children: true\nnav_order: 2\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'predicates.md'), 'w') as file:
            file.write(
                f'---\nparent: Slots\n\ngrand_parent: {self.doc_root_title}\ntitle: Predicates\nhas_children: true\nnav_order: 1\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'node_properties.md'), 'w') as file:
            file.write(
                f'---\nparent: Slots\ngrand_parent: {self.doc_root_title}\ntitle: Node Properties\nhas_children: true\nnav_order: 2\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'edge_properties.md'), 'w') as file:
            file.write(
                f'---\nparent: Slots\ngrand_parent: {self.doc_root_title}\ntitle: Edge Properties\nhas_children: true\nnav_order: 3\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'slot_mixins.md'), 'w') as file:
            file.write(
                f'---\nparent: Slots\ngrand_parent: {self.doc_root_title}\ntitle: Slot Mixins\nhas_children: true\nnav_order: 4\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'other_slots.md'), 'w') as file:
            file.write(
                f'---\nparent: Slots\ngrand_parent: {self.doc_root_title}\ntitle: Other Slots\nhas_children: true\nnav_order: 5\nlayout: default\n---'
            )

        # create parent for organizing markdown based on Subset types
        with open(os.path.join(directory, 'subsets.md'), 'w') as file:
            file.write(
                f'---\nparent: {self.doc_root_title}\ntitle: Subsets\nhas_children: true\nnav_order: 3\nlayout: default\n---'
            )

        # create parent for organizing markdown based on Type types
        os.makedirs(os.path.join(directory, 'types'), exist_ok=True)
        with open(os.path.join(directory, 'types', 'index.md'), 'w') as file:
            file.write(
                f'---\nparent: {self.doc_root_title}\ntitle: Types\nhas_children: true\nnav_order: 4\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'types', 'built_in_types.md'),
                  'w') as file:
            file.write(
                f'---\nparent: Types\ngrand_parent: {self.doc_root_title}\ntitle: Built-in Types\nhas_children: true\nnav_order: 1\nlayout: default\n---'
            )
        with open(os.path.join(directory, 'types', 'defined_types.md'),
                  'w') as file:
            file.write(
                f'---\nparent: Types\ngrand_parent: {self.doc_root_title}\ntitle: Defined Types\nhas_children: true\nnav_order: 2\nlayout: default\n---'
            )

        # create parent for organizing markdown based on Enum types
        with open(os.path.join(directory, 'enums.md'), 'w') as file:
            file.write(
                f'---\nparent: {self.doc_root_title}\ntitle: Enums\nhas_children: true\nnav_order: 4\nlayout: default\n---'
            )

    def visit_class(self, cls: ClassDefinition) -> bool:
        """
        Visit a given class definition and write the following properties in Markdown,
        - Frontmatter
        - Mappings
        - Description
        - UML
        - Identifier prefixes
        - Parents
        - Uses Mixins
        - Children
        - Mixin for
        - Referenced by class
        - Attributes
        - Domain constraints for slots

        Parameters
        ----------
        cls: linkml_runtime.linkml_model.meta.ClassDefinition
            A ClassDefinition

        Returns
        -------
        bool

        """
        if self.gen_classes and cls.name not in self.gen_classes:
            return False
        with open(self.dir_path(cls), 'w') as clsfile:
            with redirect_stdout(clsfile):
                class_curi = self.namespaces.uri_or_curie_for(
                    self.namespaces._base, camelcase(cls.name))
                class_uri = self.namespaces.uri_for(class_curi)
                ancs = self.ancestors(cls)
                if 'named thing' in ancs:
                    parent = 'Entities'
                    grand_parent = 'Classes'
                elif 'association' in ancs:
                    parent = 'Associations'
                    grand_parent = 'Classes'
                elif cls.mixin:
                    parent = 'Class Mixins'
                    grand_parent = 'Classes'
                else:
                    parent = 'Other Classes'
                    grand_parent = 'Classes'
                self.frontmatter(
                    **{
                        'parent': parent,
                        'title': class_curi,
                        'grand_parent': grand_parent,
                        'layout': 'default'
                    })
                self.element_header(cls, cls.name, class_curi, class_uri)
                for m in cls.mappings:
                    self.badges(m, 'mapping-label')

                if self.image_directory:
                    yg = YumlGenerator(self)
                    yg.serialize(classes=[cls.name],
                                 directory=self.image_directory,
                                 load_image=not self.noimages)
                    img_url = os.path.join(
                        'images', os.path.basename(yg.output_file_name))
                else:
                    yg = YumlGenerator(self)
                    img_url = yg.serialize(classes=[cls.name])

                img_url = img_url.replace(' ', '%20')
                img_url = img_url.replace('<', '%3C')
                img_url = img_url.replace('^', '%5E')
                img_url = img_url.replace('>', '%3E')
                img_url = img_url.replace('|', '%7C')
                img_url = img_url.replace('*', '%2A')
                img_url = img_url.replace('&#124;', '%7C')

                self.horizontal_line()
                print(f'![img]({img_url})')
                self.horizontal_line()
                self.mappings(cls)

                if cls.id_prefixes:
                    self.header(2, 'Identifier prefixes')
                    for p in cls.id_prefixes:
                        self.bullet(f'{p}')

                if cls.is_a is not None:
                    self.header(2, 'Parents')
                    self.bullet(
                        f' is_a: {self.class_link(cls.is_a, use_desc=True)}')
                if cls.mixins:
                    self.header(2, 'Uses Mixins')
                    for mixin in cls.mixins:
                        self.bullet(
                            f' mixin: {self.class_link(mixin, use_desc=True)}')

                if cls.name in self.synopsis.isarefs:
                    self.header(2, 'Children')
                    for child in sorted(
                            self.synopsis.isarefs[cls.name].classrefs):
                        self.bullet(f'{self.class_link(child, use_desc=True)}')

                if cls.name in self.synopsis.mixinrefs:
                    self.header(2, 'Mixin for')
                    for mixin in sorted(
                            self.synopsis.mixinrefs[cls.name].classrefs):
                        self.bullet(
                            f'{self.class_link(mixin, use_desc=True, after_link="(mixin)")}'
                        )

                if cls.name in self.synopsis.classrefs:
                    self.header(2, 'Referenced by class')
                    for sn in sorted(
                            self.synopsis.classrefs[cls.name].slotrefs):
                        slot = self.schema.slots[sn]
                        if slot.range == cls.name:
                            if slot.alias and slot.usage_slot_name:
                                original_slot = self.schema.slots[
                                    slot.usage_slot_name]
                            else:
                                original_slot = slot
                            self.bullet(
                                f' **{self.class_link(slot.domain)}** '
                                f'*{self.slot_link(original_slot, add_subset=False)}*{self.predicate_cardinality(slot)}  '
                                f'**{self.class_type_link(slot.range)}**')

                self.header(2, 'Attributes')
                own_slots = [
                    slot for slot in
                    [self.schema.slots[sn] for sn in sorted(cls.slots)]
                    if slot.owner == cls.name
                ]
                if own_slots:
                    self.header(3, 'Own')
                    for slot in own_slots:
                        if slot.alias and slot.usage_slot_name:
                            slot = self.schema.slots[slot.usage_slot_name]
                        self.slot_field(cls, slot)

                for slot_owner in sorted({
                        slot.owner
                        for slot in
                    [self.schema.slots[sn] for sn in cls.slots]
                        if slot.owner != slot.name and slot.owner != cls.name
                }):
                    self.header(3, "Inherited from " + slot_owner + ':')
                    for owner_slot_name in self.schema.classes[
                            slot_owner].slots:
                        owner_slot = self.schema.slots[owner_slot_name]
                        if owner_slot.owner == slot_owner:
                            if owner_slot.alias and owner_slot.usage_slot_name:
                                owner_slot = self.schema.slots[
                                    owner_slot.usage_slot_name]
                            self.slot_field(cls, owner_slot)

                domain_for_slots = [
                    slot for slot in
                    [self.schema.slots[sn] for sn in sorted(cls.slots)]
                    if slot.domain == cls.name
                ]
                if domain_for_slots:
                    self.header(3, 'Domain for slot:')
                    for slot in domain_for_slots:
                        if slot.alias and slot.usage_slot_name:
                            slot = self.schema.slots[slot.usage_slot_name]
                        self.slot_field(cls, slot)

                self.element_properties(cls)

        return True

    def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> None:
        """
        Visit a given slot definition and write the following properties in Markdown,
        - Frontmatter
        - Mappings
        - Description
        - Domain and Range constraints
        - Parents
        - Children
        - Used by

        Parameters
        ----------
        cls: linkml_runtime.linkml_model.meta.SlotDefinition
            A SlotDefinition

        """
        if not slot.alias:
            with open(self.dir_path(slot), 'w') as slotfile:
                with redirect_stdout(slotfile):
                    slot_curie = self.namespaces.uri_or_curie_for(
                        self.namespaces._base, underscore(slot.name))
                    slot_uri = self.namespaces.uri_for(slot_curie)
                    ancs = self.ancestors(slot)
                    if 'related to' in ancs:
                        if slot.mixin:
                            parent = 'Slot Mixins'
                        else:
                            parent = 'Predicates'
                        grand_parent = 'Slots'
                        slot_type = 'Relation'
                    elif 'node property' in ancs:
                        if slot.mixin:
                            parent = 'Slot Mixins'
                        else:
                            parent = 'Node Properties'
                        grand_parent = 'Slots'
                        slot_type = 'Slot'
                    elif 'association slot' in ancs:
                        if slot.mixin:
                            parent = 'Slot Mixins'
                        else:
                            parent = 'Edge Properties'
                        grand_parent = 'Slots'
                        slot_type = 'Slot'
                    else:
                        if slot.mixin:
                            parent = 'Slot Mixins'
                        else:
                            parent = 'Other Slots'
                        grand_parent = 'Slots'
                        slot_type = 'Slot'
                    self.frontmatter(
                        **{
                            'parent': parent,
                            'title': slot_curie,
                            'grand_parent': grand_parent,
                            'layout': 'default'
                        })
                    simple_name = slot_curie.split(':', 1)[1]
                    self.header(
                        1, f"{slot_type}: {simple_name}" +
                        (f" _(deprecated)_" if slot.deprecated else ""))
                    for s in slot.in_subset:
                        self.badges(s, f'{s}-subset-label')

                    self.para(be(slot.description))
                    print(f'URI: [{slot_curie}]({slot_uri})')

                    self.header(2, 'Domain and Range')
                    print(
                        f'{self.class_link(slot.domain)} ->{self.predicate_cardinality(slot)} '
                        f'{self.class_type_link(slot.range)}')

                    self.header(2, 'Parents')
                    if slot.is_a:
                        self.bullet(f' is_a: {self.slot_link(slot.is_a)}')

                    self.header(2, 'Children')
                    if slot.name in sorted(self.synopsis.isarefs):
                        for child in sorted(
                                self.synopsis.isarefs[slot.name].slotrefs):
                            child_slot = self.schema.slots[child]
                            if not child_slot.alias:
                                self.bullet(f' {self.slot_link(child)}')

                    self.header(2, 'Used by')
                    if slot.name in sorted(self.synopsis.slotrefs):
                        for rc in sorted(
                                self.synopsis.slotrefs[slot.name].classrefs):
                            self.bullet(f'{self.class_link(rc)}')
                    if aliased_slot_name == 'relation':
                        if slot.subproperty_of:
                            self.bullet(
                                f' reifies: {self.slot_link(slot.subproperty_of) if slot.subproperty_of in self.schema.slots else slot.subproperty_of}'
                            )
                    self.element_properties(slot)

    def visit_subset(self, subset: SubsetDefinition) -> None:
        """
        Visit a given subset definition and write the following properties in Markdown,
        - Classes
        - Mixins
        - Slots
        - Types
        - Enums

        Parameters
        ----------
        cls: linkml_runtime.linkml_model.meta.SubsetDefinition
            A SubsetDefinition

        """
        grand_parent = self.doc_root_title
        parent = "Subsets"
        seen_subset_elements = set()
        with open(self.exist_warning(self.dir_path(subset)),
                  'w',
                  encoding='UTF-8') as subsetfile:
            with redirect_stdout(subsetfile):
                curie = self.namespaces.uri_or_curie_for(
                    str(self.namespaces._base), underscore(subset.name))
                uri = self.namespaces.uri_for(curie)
                self.frontmatter(
                    **{
                        'grand_parent': grand_parent,
                        'parent': parent,
                        'title': curie,
                        'layout': 'default'
                    })
                self.element_header(obj=subset,
                                    name=subset.name,
                                    curie=curie,
                                    uri=uri)
                # TODO: consider showing hierarchy within a subset
                self.header(3, 'Classes')
                for cls in sorted(self.schema.classes.values(),
                                  key=lambda c: c.name.lower()):
                    if not cls.mixin:
                        if cls.in_subset and subset.name in cls.in_subset:
                            if cls.name not in seen_subset_elements:
                                seen_subset_elements.add(cls.name)
                                self.bullet(
                                    self.class_link(cls, use_desc=True), 0)
                self.header(3, 'Mixins')
                for cls in sorted(self.schema.classes.values(),
                                  key=lambda c: c.name.lower()):
                    if cls.mixin:
                        if cls.in_subset and subset.name in cls.in_subset:
                            if cls.name not in seen_subset_elements:
                                seen_subset_elements.add(cls.name)
                                self.bullet(
                                    self.class_link(cls, use_desc=True), 0)
                self.header(3, 'Slots')
                for slot in sorted(self.schema.slots.values(),
                                   key=lambda s: s.name.lower()):
                    if slot.in_subset and subset.name in slot.in_subset:
                        if slot.alias and slot.usage_slot_name:
                            slot = self.schema.slots[slot.usage_slot_name]
                        if slot.name not in seen_subset_elements:
                            seen_subset_elements.add(slot.name)
                            self.bullet(self.slot_link(slot, use_desc=True), 0)
                self.header(3, 'Types')
                for type in sorted(self.schema.types.values(),
                                   key=lambda s: s.name.lower()):
                    if type.in_subset and subset.name in type.in_subset:
                        self.bullet(self.type_link(type, use_desc=True), 0)
                self.header(3, 'Enums')
                for enum in sorted(self.schema.enums.values(),
                                   key=lambda s: s.name.lower()):
                    if enum.in_subset and subset.name in enum.in_subset:
                        self.bullet(self.enum_link(enum, use_desc=True), 0)
                self.element_properties(subset)

    def visit_enum(self, enum: EnumDefinition) -> None:
        """
        Visit a given enum definition and write the following properties in Markdown,
        - Properties
        - Permissible Values

        Parameters
        ----------
        cls: linkml_runtime.linkml_model.meta.EnumDefinition
            A EnumDefinition

        """
        grand_parent = self.doc_root_title
        parent = "Enums"
        with open(self.exist_warning(self.dir_path(enum)),
                  'w',
                  encoding='UTF-8') as enumfile:
            with redirect_stdout(enumfile):
                enum_curie = self.namespaces.uri_or_curie_for(
                    str(self.namespaces._base), underscore(enum.name))
                enum_uri = self.namespaces.uri_for(enum_curie)
                self.frontmatter(
                    **{
                        'grand_parent': grand_parent,
                        'parent': parent,
                        'title': enum_curie,
                        'layout': 'default'
                    })
                self.element_header(obj=enum,
                                    name=enum.name,
                                    curie=enum_curie,
                                    uri=enum_uri)
                self.element_properties(enum)

    def class_hier(self, cls: ClassDefinition, level: int = 0) -> None:
        """
        Generate a bullet list representing the hierarchy of a given class.

        Parameters
        ----------
        cls: linkml_runtime.linkml_model.meta.ClassDefinition
            A ClassDefinition
        level: int
            Markdown level corresponding to H1, H2, H3, etc.

        """
        self.bullet(self.class_link(cls, use_desc=True), level)
        if cls.name in sorted(self.synopsis.isarefs):
            for child in sorted(self.synopsis.isarefs[cls.name].classrefs):
                self.seen_elements.add(child)
                self.class_hier(self.schema.classes[child], level + 1)

    def pred_hier(self, slot: SlotDefinition, level: int = 0) -> None:
        """
        Generate a bullet list representing the hierarchy of a given slot.

        Parameters
        ----------
        slot: linkml_runtime.linkml_model.meta.SlotDefinition
            A SlotDefinition
        level: int
            Markdown level corresponding to H1, H2, H3, etc.

        """
        self.bullet(self.slot_link(slot, use_desc=True), level)
        if slot.name in sorted(self.synopsis.isarefs):
            for child in sorted(self.synopsis.isarefs[slot.name].slotrefs):
                child_slot = self.schema.slots[child]
                if not child_slot.alias and not child_slot.mixin:
                    self.seen_elements.add(child)
                    self.pred_hier(child_slot, level + 1)

    def visit_type(self, typ: TypeDefinition) -> None:
        """
        Visit a given type definition and write the following properties in Markdown,
        - Frontmatter
        - Description
        - Domain and Range constraints
        - Parents
        - Children
        - Used by

        Parameters
        ----------
        typ: linkml_runtime.linkml_model.meta.TypeDefinition
            A TypeDefinition

        """
        with open(self.dir_path(typ), 'w') as typefile:
            with redirect_stdout(typefile):
                full_path = sfx(self.namespaces._base) + (sfx(
                    typ.imported_from) if typ.imported_from else '')
                type_curie = self.namespaces.uri_or_curie_for(
                    full_path, camelcase(typ.name))
                type_uri = self.namespaces.uri_for(type_curie)

                if type_curie.startswith(
                        'https://w3id.org/biolink/vocab/linkml:types/'):
                    ref = type_curie.split('/')[-1]
                    type_uri = f"https://linkml.github.io/linkml-model/docs/types/{ref}"
                    type_curie = f"metatype:{ref}"
                elif type_uri.startswith('https://w3id.org/biolink/vocab/'):
                    ref = type_curie.split('/')[-1]
                    type_uri = f"https://w3id.org/biolink/vocab/types/{ref}"
                if typ.imported_from and 'linkml:types' in typ.imported_from:
                    parent = 'Built-in Types'
                else:
                    parent = 'Defined Types'
                self.frontmatter(
                    **{
                        'parent': parent,
                        'grand_parent': 'Types',
                        'title': type_curie,
                        'layout': 'default'
                    })
                self.element_header(typ, typ.name, type_curie, type_uri)

                print("|  |  |  |")
                print("| --- | --- | --- |")
                if typ.typeof:
                    print(
                        f"| Parent type | | {self.class_type_link(typ.typeof)} |"
                    )
                print(f"| Root (builtin) type | | **{typ.base}** |")
                if typ.repr:
                    print(f"| Representation | | {typ.repr} |")

    def frontmatter(self, **kwargs: Dict) -> None:
        """
        Write frontmatter with the given set of key-value pairs.

        Parameters
        ----------
        kwargs: Dict
            A set of key-value pairs.

        """
        print('---')
        for k, v in kwargs.items():
            print(f'{k}: {v}')
        print('---')

    def element_header(self, obj: Element, name: str, curie: str,
                       uri: str) -> None:
        """
        Write the header for an element.

        Parameters
        ----------
        obj: linkml_runtime.linkml_model.meta.Element
            An element
        name: str
            The name of the element
        curie: str
            The CURIE of the element
        uri: str
            The URI of the element

        """
        if curie.startswith('http'):
            if curie.startswith(
                    'https://w3id.org/biolink/vocab/linkml:types/'):
                simple_name = curie.split('/')[-1]
                uri = f"https://linkml.github.io/linkml-model/docs/types/{simple_name}"
                simple_name = f"metatype:{simple_name}"
            else:
                simple_name = curie
        else:
            simple_name = curie.split(':', 1)[1]
        if isinstance(obj, TypeDefinition):
            obj_type = 'Type'
        elif isinstance(obj, ClassDefinition):
            obj_type = 'Class'
        elif isinstance(obj, SlotDefinition):
            obj_type = 'Slot'
        else:
            obj_type = 'Class'
        self.header(
            1, f"{obj_type}: {simple_name}" +
            (f" _(deprecated)_" if obj.deprecated else ""))
        self.para(be(obj.description))
        print(f'URI: [{curie}]({uri})')
        print()

    def badges(self, text: str, style: str = 'default-label'):
        """
        Write a badge with the given text.

        Parameters
        ----------
        text: str
            A text string
        style: str
            The badge style

        """
        print(text)
        print(f"{{: .{style} }}")
        print()

    def horizontal_line(self):
        """
        Write a horizontal line.
        """
        print('\n---\n')
 def __init__(
     self, schema: Union[Url, Path, TextIO, SchemaDefinition] = REMOTE_PATH
 ) -> None:
     self.generator = ToolkitGenerator(schema)
     self.generator.serialize()
     self.view = SchemaView(schema)
class Toolkit(object):
    """
    Provides a series of methods for performing lookups on the Biolink Model

    Parameters
    ----------
    schema: Union[str, TextIO, SchemaDefinition]
        The path or url to an instance of the biolink-model.yaml file.

    """

    def __init__(
        self, schema: Union[Url, Path, TextIO, SchemaDefinition] = REMOTE_PATH
    ) -> None:
        self.generator = ToolkitGenerator(schema)
        self.generator.serialize()
        self.view = SchemaView(schema)

    @lru_cache(CACHE_SIZE)
    def get_all_elements(self, formatted: bool = False) -> List[str]:
        """
        Get all elements from Biolink Model.

        This method returns a list containing all
        classes, slots, and types defined in the model.

        Parameters
        ----------
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of elements

        """
        classes = self.get_all_classes(formatted)
        slots = self.get_all_slots(formatted)
        types = self.get_all_types(formatted)
        all_elements = classes + slots + types
        return all_elements

    @lru_cache(CACHE_SIZE)
    def get_all_classes(self, formatted: bool = False) -> List[str]:
        """
        Get all classes from Biolink Model.

        This method returns a list containing all the
        classes defined in the model.

        Parameters
        ----------
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of elements

        """
        classes = []
        for x in self.generator.schema.classes:
            classes.append(x)
        filtered_classes = self._filter_secondary(classes)
        return self._format_all_elements(filtered_classes, formatted)

    @lru_cache(CACHE_SIZE)
    def get_all_slots(self, formatted: bool = False) -> List[str]:
        """
        Get all slots from Biolink Model.

        This method returns a list containing all the
        slots defined in the model.

        Parameters
        ----------
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of elements

        """
        slots = []
        for x in self.generator.schema.slots:
            slots.append(x)
        filtered_slots = self._filter_secondary(slots)
        return self._format_all_elements(filtered_slots, formatted)

    @lru_cache(CACHE_SIZE)
    def get_all_types(self, formatted: bool = False) -> List[str]:
        """
        Get all types from Biolink Model.

        This method returns a list containing all the
        built-in and defined types in the model.

        Parameters
        ----------
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of elements

        """
        types = []
        for x in self.generator.schema.types:
            types.append(x)
        return self._format_all_elements(types, formatted)

    @lru_cache(CACHE_SIZE)
    def get_all_entities(self, formatted: bool = False) -> List[str]:
        """
        Get all entities from Biolink Model.

        This method returns a list containing all the classes
        that are descendants of the class ``named thing``.

        Parameters
        ----------
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of elements

        """
        elements = self.get_descendants("named thing")
        return self._format_all_elements(elements, formatted)

    @lru_cache(CACHE_SIZE)
    def get_all_associations(self, formatted: bool = False) -> List[str]:
        """
        Get all associations from Biolink Model.

        This method returns a list containing all the classes
        that are descendants of the class ``association``.

        Parameters
        ----------
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of elements

        """
        elements = self.get_descendants("association")
        return self._format_all_elements(elements, formatted)

    @lru_cache(CACHE_SIZE)
    def get_all_node_properties(self, formatted: bool = False) -> List[str]:
        """
        Get all node properties from Biolink Model.

        This method returns a list containing all the slots
        that are descendants of the slot ``node property``.

        Parameters
        ----------
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of elements

        """
        elements = self.get_all_slots_with_class_domain("entity")
        elements += self.get_descendants("node property")
        filtered_elements = self._filter_secondary(elements)
        return self._format_all_elements(filtered_elements, formatted)

    @lru_cache(CACHE_SIZE)
    def get_all_edge_properties(self, formatted: bool = False) -> List[str]:
        """
        Get all edge properties from Biolink Model.

        This method returns a list containing all the slots
        that are descendants of the slot ``association slot``.

        Parameters
        ----------
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of elements

        """
        elements = self.get_all_slots_with_class_domain("entity")
        elements += self.get_descendants("association slot")
        filtered_elements = self._filter_secondary(elements)
        return self._format_all_elements(filtered_elements, formatted)

    def _filter_secondary(self, elements: List[str]) -> List[str]:
        """
        From a given list of elements, remove elements that are not proper slots.

        This method removes spurious slots like ``gene_to_gene_association_subject``
        that are artifact of domain/range constraints and not actual slots.

        Parameters
        ----------
        elements: List[str]
            List of elements

        Returns
        -------
        List[str]
            A filtered list of elements

        """
        filtered_elements = []
        for e in elements:
            eo = self.generator.obj_for(e)
            if isinstance(eo, SlotDefinition):
                if not eo.alias:
                    filtered_elements.append(e)
            else:
                filtered_elements.append(e)
        return filtered_elements

    @lru_cache(CACHE_SIZE)
    def get_ancestors(
        self,
        name: str,
        reflexive: bool = True,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Gets a list of names of ancestors.

        Parameters
        ----------
        name: str
            The name of an element in the Biolink Model
        reflexive: bool
            Whether to include the query element in the list of ancestors
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            The names of the given elements ancestors

        """
        element = self.get_element(name)
        ancs = []
        if isinstance(element, ClassDefinition):
            ancs = self.view.class_ancestors(element.name, mixins=mixin, reflexive=reflexive)
        if isinstance(element, SlotDefinition):
            ancs = self.view.slot_ancestors(element.name, mixins=mixin, reflexive=reflexive)
            filtered_ancs = self._filter_secondary(ancs)
        else:
            filtered_ancs = ancs
        return self._format_all_elements(filtered_ancs, formatted)

    def _get_mixin_descendants(self, ancestors: List[ElementName]) -> List[ElementName]:
        mixins_parents = []
        for ancestor in ancestors:
            a_element = self.get_element(ancestor)
            if a_element.mixins:
                for mixin in a_element.mixins:
                    mixin_element = self.get_element(mixin)
                    mixin_parents = self.generator.ancestors(mixin_element)
                    mixins_parents = mixins_parents + mixin_parents
        return mixins_parents

    @lru_cache(CACHE_SIZE)
    def get_descendants(
        self,
        name: str,
        reflexive: bool = True,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Gets a list of names of descendants.

        Parameters
        ----------
        name: str
            The name of an element in the Biolink Model
        reflexive: bool
            Whether to include the query element in the list of ancestors
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            The names of the given element's descendants

        """
        desc = []
        filtered_desc = []
        element = self.get_element(name)

        if element:
            if isinstance(element, ClassDefinition):
                desc = self.view.class_descendants(element.name, mixins=mixin, reflexive=reflexive)
            if isinstance(element, SlotDefinition):
                desc = self.view.slot_descendants(element.name, mixins=mixin, reflexive=reflexive)
                filtered_desc = self._filter_secondary(desc)
            else:
                filtered_desc = desc
        else:
            raise ValueError("not a valid biolink component")

        return self._format_all_elements(filtered_desc, formatted)

    @lru_cache(CACHE_SIZE)
    def get_children(
        self, name: str, formatted: bool = False, mixin: bool = True
    ) -> List[str]:
        """
        Gets a list of names of children.

        Parameters
        ----------
        name: str
            The name of an element in the Biolink Model
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            The names of the given elements children

        """
        children = []
        element = self.get_element(name)
        if element:
            children = self.generator.children(element.name, mixin)
        return self._format_all_elements(children, formatted)

    @lru_cache(CACHE_SIZE)
    def get_parent(self, name: str, formatted: bool = False) -> Optional[str]:
        """
        Gets the name of the parent.

        Parameters
        ----------
        name: str
            The name of an element in the Biolink Model
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        Optional[str]
            The name of the given elements parent

        """
        parent = None
        element = self.get_element(name)
        if element:
            p = element.is_a if isinstance(element, Definition) else None
            if p and formatted:
                parent = format_element(p)
            else:
                parent = p
        return parent

    @lru_cache(CACHE_SIZE)
    def get_element(self, name: str) -> Optional[Element]:
        """
        Gets an element that is identified by the given name, either as its name
        or as one of its aliases.

        Parameters
        ----------
        name: str
            The name or alias of an element in the Biolink Model

        Returns
        -------
        Element
            The element identified by the given name

        """
        parsed_name = parse_name(name)
        logger.debug(parsed_name)
        element = self.generator.obj_for(parsed_name)
        if element is None and name in self.generator.aliases:
            logger.debug("in aliases")
            logger.debug(self.generator.aliases)
            element = self.get_element(self.generator.aliases[name])
        if element is None and "_" in name:
            logger.debug("has a _")
            element = self.get_element(name.replace("_", " "))
        return element

    def get_slot_domain(
        self,
        slot_name,
        include_ancestors: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Get the domain for a given slot.

        Parameters
        ----------
        slot_name: str
            The name or alias of a slot in the Biolink Model
        include_ancestors: bool
            Whether or not to include ancestors of the domain class
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            The domain for a given slot

        """
        slot_domain = []
        domain_classes = set()
        element = self.get_element(slot_name)
        if element and element.domain:
            domain_classes.add(element.domain)
            if include_ancestors:
                slot_domain.extend(
                    self.get_ancestors(element.domain, reflexive=True, mixin=mixin)
                )
            else:
                slot_domain.append(element.domain)
        for d in element.domain_of:
            if d not in domain_classes:
                if include_ancestors:
                    slot_domain.extend(
                        self.get_ancestors(d, reflexive=True, mixin=mixin)
                    )
                else:
                    slot_domain.append(d)
        return self._format_all_elements(slot_domain, formatted)

    def get_slot_range(
        self,
        slot_name,
        include_ancestors: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Get the range for a given slot.

        Parameters
        ----------
        slot_name: str
            The name or alias of a slot in the Biolink Model
        include_ancestors: bool
            Whether or not to include ancestors of the range class
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors


        Returns
        -------
        List[str]
            The range for a given slot

        """
        slot_range = []
        element = self.get_element(slot_name)
        if element and element.range:
            slot_range.append(element.range)
            if include_ancestors:
                ancs = self.get_ancestors(element.range, reflexive=False, mixin=mixin)
                slot_range.extend(ancs)
        return self._format_all_elements(slot_range, formatted)

    def get_all_slots_with_class_domain(
        self,
        class_name,
        check_ancestors: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Given a class, get all the slots where the class is the domain.

        Parameters
        ----------
        class_name: str
            The name or alias of a class in the Biolink Model
        check_ancestors: bool
            Whether or not to lookup slots that include ancestors of the given class as its domain
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            A list of slots

        """
        element = self.get_element(class_name)
        slots = self._get_all_slots_with_class_domain(element, check_ancestors, mixin)
        slot_names = [x.name for x in slots]
        return self._format_all_elements(slot_names, formatted)

    def get_all_slots_with_class_range(
        self,
        class_name,
        check_ancestors: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Given a class, get all the slots where the class is the range.

        Parameters
        ----------
        class_name: str
            The name or alias of a class in the Biolink Model
        check_ancestors: bool
            Whether or not to lookup slots that include ancestors of the given class as its range
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            A list of slots

        """
        element = self.get_element(class_name)
        slots = self._get_all_slots_with_class_range(element, check_ancestors, mixin)
        slot_names = [x.name for x in slots]
        return self._format_all_elements(slot_names, formatted)

    def get_all_predicates_with_class_domain(
        self,
        class_name,
        check_ancestors: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Given a class, get all Biolink predicates where the class is the domain.

        Parameters
        ----------
        class_name: str
            The name or alias of a class in the Biolink Model
        check_ancestors: bool
            Whether or not to lookup slots that include ancestors of the given class as its domain
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            A list of slots

        """
        filtered_slots = []
        element = self.get_element(class_name)
        if element:
            slots = self._get_all_slots_with_class_domain(
                element, check_ancestors, mixin
            )
            for s in slots:
                if not s.alias and RELATED_TO in self.get_ancestors(s.name, mixin):
                    filtered_slots.append(s.name)
        return self._format_all_elements(filtered_slots, formatted)

    def get_all_predicates_with_class_range(
        self,
        class_name,
        check_ancestors: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ):
        """
        Given a class, get all Biolink predicates where the class is the range.

        Parameters
        ----------
        class_name: str
            The name or alias of a class in the Biolink Model
        check_ancestors: bool
            Whether or not to lookup slots that include ancestors of the given class as its range
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            A list of slots

        """
        filtered_slots = []
        element = self.get_element(class_name)
        if element:
            slots = self._get_all_slots_with_class_range(
                element, check_ancestors, mixin
            )
            for s in slots:
                if not s.alias and RELATED_TO in self.get_ancestors(s.name, mixin):
                    filtered_slots.append(s.name)
        return self._format_all_elements(filtered_slots, formatted)

    def get_all_properties_with_class_domain(
        self,
        class_name,
        check_ancestors: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Given a class, get all Biolink properties where the class is the domain.

        Parameters
        ----------
        class_name: str
            The name or alias of a class in the Biolink Model
        check_ancestors: bool
            Whether or not to lookup slots that include ancestors of the given class as its domain
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            A list of slots

        """
        filtered_slots = []
        element = self.get_element(class_name)
        if element:
            slots = self._get_all_slots_with_class_domain(
                element, check_ancestors, mixin
            )
            for s in slots:
                if not s.alias and RELATED_TO not in self.get_ancestors(s.name, mixin):
                    filtered_slots.append(s.name)
        return self._format_all_elements(filtered_slots, formatted)

    def get_all_properties_with_class_range(
        self,
        class_name,
        check_ancestors: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ) -> List[str]:
        """
        Given a class, get all Biolink properties where the class is the range.

        Parameters
        ----------
        class_name: str
            The name or alias of a class in the Biolink Model
        check_ancestors: bool
            Whether or not to lookup slots that include ancestors of the given class as its range
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[str]
            A list of slots

        """
        filtered_slots = []
        element = self.get_element(class_name)
        if element:
            slots = self._get_all_slots_with_class_range(
                element, check_ancestors, mixin
            )
            for s in slots:
                if not s.alias and RELATED_TO not in self.get_ancestors(s.name, mixin):
                    filtered_slots.append(s.name)
        return self._format_all_elements(filtered_slots, formatted)

    def get_value_type_for_slot(self, slot_name, formatted: bool = False) -> str:
        """
        Get the value type for a given slot.

        Parameters
        ----------
        slot_name: str
            The name or alias of a slot in the Biolink Model
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        str
            The slot type

        """
        element_type = None
        element = self.get_element(slot_name)
        if element:
            types = self.get_all_types()
            if element.range in types:
                et = element.range
            else:
                et = "uriorcurie"
            if formatted:
                element_type = format_element(self.generator.obj_for(et))
            else:
                element_type = et
        return element_type

    def _get_all_slots_with_class_domain(
        self, element: Element, check_ancestors: bool, mixin: bool = True
    ) -> List[Element]:
        """
        Given a class, get all the slots where the class is the domain.

        Parameters
        ----------
        element: linkml_model.meta.Element
            An element
        check_ancestors: bool
            Whether or not to lookup slots that include ancestors of the given class as its domain
        mixin:
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[linkml_model.meta.Element]
            A list of slots

        """
        slots = []
        for k, v in self.generator.schema.slots.items():
            if check_ancestors:
                if (v.domain == element.name or v.domain in self.get_ancestors(element.name, mixin)
                        or element.name in v.domain_of
                        or any(v.domain_of) in self.get_ancestors(element.name, mixin)):
                    slots.append(v)
            else:
                if element.name == v.domain or element.name in v.domain_of:
                    slots.append(v)
        return slots

    def _get_all_slots_with_class_range(
        self, element: Element, check_ancestors: bool, mixin: bool = True
    ) -> List[Element]:
        """
        Given a class, get all the slots where the class is the range.

        Parameters
        ----------
        element: linkml_model.meta.Element
            An element
        check_ancestors: bool
            Whether or not to lookup slots that include ancestors of the given class as its range
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        List[linkml_model.meta.Element]
            A list of slots

        """
        slots = []
        for k, v in self.generator.schema.slots.items():
            if check_ancestors:
                if v.range == element.name or v.range in self.get_ancestors(
                    element.name, mixin
                ):
                    slots.append(v)
            else:
                if v.range and element.name == v.range:
                    slots.append(v)
        return slots

    @lru_cache(CACHE_SIZE)
    def is_predicate(self, name: str, mixin: bool = True) -> bool:
        """
        Determines whether the given name is the name of an relation/predicate
        in the Biolink Model. An element is a predicate if it descends from
        `RELATED_TO`

        Parameters
        ----------
        name: str
            The name or alias of an element in the Biolink Model
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        bool
            That the named element is a valid relation/predicate in Biolink Model
        """
        return RELATED_TO in self.get_ancestors(name, mixin)

    @lru_cache(CACHE_SIZE)
    def is_translator_canonical_predicate(self, name: str, mixin: bool = True) -> bool:
        """
        Determines whether the given name is the name of a canonical relation/predicate
        in the Biolink Model. An element is a canonical predicate if it descends from
        `RELATED_TO` and is tagged with the annotation 'biolink:canonical_predicate'

        Parameters
        ----------
        name: str
            The name or alias of an element in the Biolink Model
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        bool
            That the named element is a valid translator canonical prediacte in Biolink Model
        """
        element = self.get_element(name)
        annotation_tags = []
        if element:
            for annotation in element.annotations:
                annotation_tags.append(annotation)
        is_canonical = (
            True
            if element is not None and "biolink:canonical_predicate" in annotation_tags
            else False
        )
        return (
            True
            if RELATED_TO in self.get_ancestors(name, mixin) and is_canonical
            else False
        )

    @lru_cache(CACHE_SIZE)
    def is_mixin(self, name: str) -> bool:
        """
        Determines whether the given name is the name of a mixin
        in the Biolink Model. An element is a mixin if one of its properties is "is_mixin:true"

        Parameters
        ----------
        name: str
            The name or alias of an element in the Biolink Model

        Returns
        -------
        bool
            That the named element is a valid mixin in Biolink Model
        """
        element = self.get_element(name)
        is_mixin = element.mixin if isinstance(element, Definition) else False
        return is_mixin

    @lru_cache(CACHE_SIZE)
    def get_inverse(self, slot_name: str):
        return self.view.inverse(slot_name)

    @lru_cache(CACHE_SIZE)
    def has_inverse(self, name: str) -> bool:
        """
        Determines whether the given name is a predicate and if that predicate has an inverse defined
        in the Biolink Model. An element is a predicate if it descends from
        `RELATED_TO`

        Parameters
        ----------
        name: str
            The name or alias of an element in the Biolink Model

        Returns
        -------
        bool
            That the named element is a valid mixin in Biolink Model
        """
        element = self.get_element(name)
        has_inverse = element.inverse if isinstance(element, SlotDefinition) else False
        return bool(has_inverse)

    @lru_cache(CACHE_SIZE)
    def in_subset(self, name: str, subset: str) -> bool:
        """
        Determines whether the given name is in a given subset
        in the Biolink Model.

        Parameters
        ----------
        name: str
            The name or alias of an element in the Biolink Model.
        subset: str
            The name of the subset

        Returns
        -------
        bool
            That the named element is part of a given subset in Biolink Model

        """
        parsed_name = parse_name(name)
        element = self.generator.obj_for(parsed_name)
        return subset in element.in_subset

    @lru_cache(CACHE_SIZE)
    def is_category(self, name: str, mixin: bool = True) -> bool:
        """
        Determines whether the given name is the name of a category in the
        Biolink Model. An element is a category if it descends from
        `named thing`

        Parameters
        ----------
        name : str
            The name or alias of an element in the Biolink Model
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        bool
            That the named element is a valid category in Biolink Model
        """
        return "named thing" in self.get_ancestors(name, mixin)

    @lru_cache(CACHE_SIZE)
    def get_element_by_prefix(
            self,
            identifier: str
    ) -> List[str]:
        """
        Get a Biolink Model element by prefix.
        This method return the common ancestor of the set of elements referenced by uriorcurie.

        Parameters
        ----------
        identifier: str
            The identifier as a CURIE

        Returns
        -------
        Optional[str]
                The Biolink element corresponding to the given URI/CURIE as available via
                the id_prefixes mapped to that element.

        """
        categories = []
        if ":" in identifier:
            id_components = identifier.split(":")
            prefix = id_components[0]
            elements = self.get_all_elements()
            for category in elements:
                element = self.get_element(category)
                if hasattr(element, 'id_prefixes') and prefix in element.id_prefixes:
                    categories.append(element.name)
        if len(categories) == 0:
            logger.warning("no biolink class found for the given curie: %s, try get_element_by_mapping?", identifier)

        return categories

    @lru_cache(CACHE_SIZE)
    def get_element_by_mapping(
        self,
        identifier: str,
        most_specific: bool = False,
        formatted: bool = False,
        mixin: bool = True,
    ) -> Optional[str]:
        """
        Get a Biolink Model element by mapping.
        This method return the common ancestor of the set of elements referenced by uriorcurie.

        Parameters
        ----------
        identifier: str
            The identifier as an IRI or CURIE
        most_specific: bool
            Whether or not to get the first available mapping in the order of specificity
            or to get all mappings of varying specificity
        formatted: bool
            Whether to format element names as CURIEs
        mixin: bool
            If True, then that means we want to find mixin ancestors as well as is_a ancestors

        Returns
        -------
        Optional[str]
            The Biolink element (or the common ancestor) corresponding to the given URI/CURIE

        """
        if most_specific:
            mappings = self._get_element_by_mapping(identifier)
        else:
            mappings = self.get_all_elements_by_mapping(identifier)
        if mappings:
            ancestors: List[List[str]] = []
            for m in mappings:
                ancestors.append(
                    [x for x in self.get_ancestors(m, mixin)[::-1] if x in mappings]
                )
                logger.debug(ancestors)
            without_empty_lists = list(filter(None, ancestors))
            common_ancestors = reduce(
                lambda s, l: s.intersection(set(l)), without_empty_lists[1:], set(without_empty_lists[0])
            )
            logger.debug("common_ancestors")
            logger.debug(common_ancestors)
            for a in without_empty_lists[0]:
                logger.debug("ancestors[0]")
                logger.debug(a)
                if a in common_ancestors:
                    if formatted:
                        element = format_element(self.generator.obj_for(a))
                    else:
                        element = a
                    return element

    @lru_cache(CACHE_SIZE)
    def _get_element_by_mapping(self, identifier: str) -> List[str]:
        """
        Get the most specific mapping corresponding to a given identifier.
        This method first checks for general mappings. If it can't find any then
        it starts looking for exact_mappings, close_mappings, related_mappings,
        narrow_mappings and finally broad_mappings. It will stop if it finds a
        mapping at first occurrence.

        Parameters
        ----------
        identifier: str
            The identifier as an IRI or CURIE

        Returns
        -------
        List[str]
            A list of Biolink elements that correspond to the given identifier IRI/CURIE

        """
        mappings = self.generator.mappings.get(
            self.generator.namespaces.uri_for(identifier), set()
        )
        if not mappings:
            exact = set(self.get_element_by_exact_mapping(identifier))
            mappings.update(exact)
        if not mappings:
            close = set(self.get_element_by_close_mapping(identifier))
            mappings.update(close)
        if not mappings:
            related = set(self.get_element_by_related_mapping(identifier))
            mappings.update(related)
        if not mappings:
            narrow = set(self.get_element_by_narrow_mapping(identifier))
            mappings.update(narrow)
        if not mappings:
            broad = set(self.get_element_by_broad_mapping(identifier))
            mappings.update(broad)
        return mappings

    @lru_cache(CACHE_SIZE)
    def get_element_by_exact_mapping(
        self, identifier: str, formatted: bool = False
    ) -> List[str]:
        """
        Given an identifier as IRI/CURIE, find a Biolink element that corresponds
        to the given identifier as part of its exact_mappings.

        Parameters
        ----------
        identifier: str
            The identifier as an IRI or CURIE
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of Biolink elements that correspond to the given identifier IRI/CURIE

        """
        mappings = self.generator.exact_mappings.get(
            self.generator.namespaces.uri_for(identifier), set()
        )
        logger.debug(mappings)
        return self._format_all_elements(mappings, formatted)

    @lru_cache(CACHE_SIZE)
    def get_element_by_close_mapping(
        self, identifier: str, formatted: bool = False
    ) -> List[str]:
        """
        Given an identifier as IRI/CURIE, find a Biolink element that corresponds
        to the given identifier as part of its close_mappings.

        Parameters
        ----------
        identifier:
            The identifier as an IRI or CURIE
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of Biolink elements that correspond to the given identifier IRI/CURIE

        """
        mappings = self.generator.close_mappings.get(
            self.generator.namespaces.uri_for(identifier), set()
        )
        return self._format_all_elements(mappings, formatted)

    @lru_cache(CACHE_SIZE)
    def get_element_by_related_mapping(
        self, identifier: str, formatted: bool = False
    ) -> List[str]:
        """
        Given an identifier as IRI/CURIE, find a Biolink element that corresponds
        to the given identifier as part of its related_mappings.

        Parameters
        ----------
        identifier: str
            The identifier as an IRI or CURIE
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of Biolink elements that correspond to the given identifier IRI/CURIE

        """
        mappings = self.generator.related_mappings.get(
            self.generator.namespaces.uri_for(identifier), set()
        )
        return self._format_all_elements(mappings, formatted)

    @lru_cache(CACHE_SIZE)
    def get_element_by_narrow_mapping(
        self, identifier: str, formatted: bool = False
    ) -> List[str]:
        """
        Given an identifier as IRI/CURIE, find a Biolink element that corresponds
        to the given identifier as part of its narrow_mappings.

        Parameters
        ----------
        identifier: str
            The identifier as an IRI or CURIE
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of Biolink elements that correspond to the given identifier IRI/CURIE

        """
        mappings = self.generator.narrow_mappings.get(
            self.generator.namespaces.uri_for(identifier), set()
        )
        return self._format_all_elements(mappings, formatted)

    @lru_cache(CACHE_SIZE)
    def get_element_by_broad_mapping(
        self, identifier: str, formatted: bool = False
    ) -> List[str]:
        """
        Given an identifier as IRI/CURIE, find a Biolink element that corresponds
        to the given identifier as part of its broad_mappings.

        Parameters
        ----------
        identifier: str
            The identifier as an IRI or CURIE
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of Biolink elements that correspond to the given identifier IRI/CURIE

        """
        mappings = self.generator.broad_mappings.get(
            self.generator.namespaces.uri_for(identifier), set()
        )
        return self._format_all_elements(mappings, formatted)

    @lru_cache(CACHE_SIZE)
    def get_all_elements_by_mapping(
        self, identifier: str, formatted: bool = False
    ) -> List[str]:
        """
        Given an identifier as IRI/CURIE, find all Biolink element that corresponds
        to the given identifier as part of its mappings.

        Parameters
        ----------
        identifier: str
            The identifier as an IRI or CURIE
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            A list of Biolink elements that correspond to the given identifier IRI/CURIE

        """
        mappings = self.generator.mappings.get(
            self.generator.namespaces.uri_for(identifier), set()
        )
        exact = set(self.get_element_by_exact_mapping(identifier))
        mappings.update(exact)
        close = set(self.get_element_by_close_mapping(identifier))
        mappings.update(close)
        related = set(self.get_element_by_related_mapping(identifier))
        mappings.update(related)
        narrow = set(self.get_element_by_narrow_mapping(identifier))
        mappings.update(narrow)
        broad = set(self.get_element_by_broad_mapping(identifier))
        mappings.update(broad)
        return self._format_all_elements(mappings, formatted)

    def _format_all_elements(
        self, elements: List[str], formatted: bool = False
    ) -> List[str]:
        """
        Format all the elements in a given list.

        Parameters
        ----------
        elements: str
            A list of elements
        formatted: bool
            Whether to format element names as CURIEs

        Returns
        -------
        List[str]
            The formatted list of elements

        """
        if formatted:
            formatted_elements = [
                format_element(self.generator.obj_for(x)) for x in elements
            ]
        else:
            formatted_elements = elements
        return formatted_elements

    @lru_cache(CACHE_SIZE)
    def get_model_version(self) -> str:
        """
        Return the version of the biolink-model in use.

        Returns
        -------
        str
            The biolink-model version

        """
        return self.generator.schema.version

    @deprecation.deprecated(
        deprecated_in="0.3.0",
        removed_in="1.0",
        details="Use get_all_elements method instead",
    )
    def names(self, formatted: bool = False) -> List[str]:
        return self.get_all_elements(formatted)

    @deprecation.deprecated(
        deprecated_in="0.2.0",
        removed_in="1.0",
        details="Use get_descendants method instead",
    )
    def descendents(self, name: str, mixin: bool = True) -> List[str]:
        return self.get_descendants(name, mixin)

    @deprecation.deprecated(
        deprecated_in="0.2.0",
        removed_in="1.0",
        details="Use get_ancestors method instead",
    )
    def ancestors(self, name: str, mixin: bool = True) -> List[str]:
        return self.get_ancestors(name, mixin)

    @deprecation.deprecated(
        deprecated_in="0.2.0",
        removed_in="1.0",
        details="Use get_children method instead",
    )
    def children(self, name: str, mixin: bool = True) -> List[str]:
        return self.get_children(name, mixin)

    @deprecation.deprecated(
        deprecated_in="0.2.0", removed_in="1.0", details="Use get_parent method instead"
    )
    def parent(self, name: str, mixin: bool = True) -> Optional[str]:
        return self.get_parent(name, mixin)

    @deprecation.deprecated(
        deprecated_in="0.1.1",
        removed_in="1.0",
        details="Use is_predicate method instead",
    )
    def is_edgelabel(self, name: str, mixin: bool = True) -> bool:
        return self.is_predicate(name, mixin)

    @deprecation.deprecated(
        deprecated_in="0.1.1",
        removed_in="1.0",
        details="Use get_all_elements_by_mapping method instead",
    )
    def get_all_by_mapping(self, uriorcurie: str) -> List[str]:
        return self.get_all_elements_by_mapping(uriorcurie)

    @deprecation.deprecated(
        deprecated_in="0.1.1",
        removed_in="1.0",
        details="Use get_element_by_mapping method instead",
    )
    def get_by_mapping(self, uriorcurie: str) -> Optional[str]:
        return self.get_element_by_mapping(uriorcurie)
Beispiel #8
0
"""Constants."""

import os
import pathlib

from linkml_runtime.utils.schema_as_dict import schema_as_dict
from linkml_runtime.utils.schemaview import SchemaView

HERE = pathlib.Path(__file__).parent.resolve()
SCHEMA_YAML = os.path.join(HERE, "sssom.yaml")

SCHEMA_VIEW = SchemaView(SCHEMA_YAML)
SCHEMA_DICT = schema_as_dict(SCHEMA_VIEW.schema)
MAPPING_SLOTS = SCHEMA_DICT["classes"]["mapping"]["slots"]
MAPPING_SET_SLOTS = SCHEMA_DICT["classes"]["mapping set"]["slots"]