def visit_slot(self, slot_name: str, slot: SlotDefinition) -> None: """ Add a slot definition per slot @param slot_name: @param slot: @return: """ sn = underscore(slot_name) self.emit('slot', sn) if slot.domain: self.emit('domain', sn, underscore(slot.domain)) if slot.range: self.emit('range', sn, underscore(slot.range)) for p in slot.mixins: self.emit('mixin', sn, underscore(p)) if slot.is_a: is_a = underscore(slot.is_a) #uri = self.owlgen._prop_uri(slot.name) uri = f'http://w3id.org/biolink/vocab/{sn}' self.emit('has_uri', sn, uri) if slot.multivalued: self.emit('multivalued', sn) if slot.required: self.emit('required', sn)
def _visit(self, node: Any) -> Optional[Any]: if isinstance(node, (YAMLRoot, dict)) or is_YAMLROOT(node): if isinstance(node, YAMLRoot) or is_YAMLROOT(node): node = self._add_type(node) for k, v in list(node.items()): if v: new_v = self._visit(v) if new_v is not None: node[k] = new_v elif isinstance(node, list): for i in range(0, len(node)): new_v = self._visit(node[i]) if new_v is not None: node[i] = new_v elif isinstance(node, set): for v in list(node): new_v = self._visit(v) if new_v is not None: node.remove(v) node.add(new_v) elif isinstance(node, ClassDefinitionName): return ClassDefinitionName(camelcase(node)) elif isinstance(node, SlotDefinitionName): return SlotDefinitionName(underscore(node)) elif isinstance(node, TypeDefinitionName): return TypeDefinitionName(underscore(node)) elif isinstance(node, SubsetDefinitionName): return SubsetDefinitionName(underscore(node)) elif isinstance(node, ElementName): return ClassDefinitionName(camelcase(node)) if node in self.schema.classes else \ SlotDefinitionName(underscore(node)) if node in self.schema.slots else \ SubsetDefinitionName(camelcase(node)) if node in self.schema.subsets else \ TypeDefinitionName(underscore(node)) if node in self.schema.types else None return None
def adjust_slot(self, slot: SlotDefinition) -> None: if slot.range in self.schema.classes: slot.range = ClassDefinitionName(camelcase(slot.range)) elif slot.range in self.schema.slots: slot.range = SlotDefinitionName(underscore(slot.range)) elif slot.range in self.schema.types: slot.range = TypeDefinitionName(underscore(slot.range)) slot.slot_uri = self.namespaces.uri_for(slot.slot_uri)
def test_formats(self): self.assertEqual("ThisIsIt", camelcase("this is it")) self.assertEqual("ThisIsIT", camelcase(" this is iT ")) self.assertEqual("IBeY", camelcase("i be y ")) self.assertEqual("ThisIsIt", camelcase("This__is_it")) self.assertEqual("this_is_it", underscore(" this is it ")) self.assertEqual("this_is_it", underscore("this is it")) self.assertEqual("thisIsIt", lcamelcase(" this is\t it\n")) self.assertEqual('abc', be(' abc\n')) self.assertEqual('', be(None)) self.assertEqual('', be(' '))
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> None: sn = underscore(aliased_slot_name) cn = underscore(cls.name) self.emit('class_slot', cn, sn) self.emit('required', sn) self.emit( 'slotrange', underscore(slot.range) ) if slot.range in self.schema.classes or slot.range in self.schema.types or slot.range in self.schema.enums else "String" if slot.multivalued: self.emit('multivalued_in', sn, cn) if slot.required: self.emit('required_in', sn, cn) if slot.range: self.emit('range_in', sn, underscore(slot.range), cn)
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> None: if slot.identifier: slot_def = '@id' else: slot_def = {} if not slot.usage_slot_name: if slot.range in self.schema.classes: slot_def['@type'] = '@id' elif slot.range in self.schema.enums: slot_def['@context'] = ENUM_CONTEXT # Add the necessary prefixes to the namespace skos = self.namespaces.prefix_for(SKOS) if not skos: self.namespaces['skos'] = SKOS skos = 'skos' self.emit_prefixes.add(skos) else: range_type = self.schema.types[slot.range] if self.namespaces.uri_for(range_type.uri) == XSD.string: pass elif self.namespaces.uri_for(range_type.uri) in URI_RANGES: slot_def['@type'] = '@id' else: slot_def['@type'] = range_type.uri slot_prefix = self.namespaces.prefix_for(slot.slot_uri) if not self.default_ns or not slot_prefix or slot_prefix != self.default_ns: slot_def['@id'] = slot.slot_uri if slot_prefix: self.add_prefix(slot_prefix) self.add_mappings(slot) if slot_def: self.context_body[underscore(aliased_slot_name)] = slot_def
def gen_slot(self, slot: SlotDefinition) -> str: python_slot_name = underscore(slot.name) slot_uri, slot_curie = self.python_uri_for(slot.slot_uri) slot_model_uri, slot_model_curie = \ self.python_uri_for(self.namespaces.uri_or_curie_for(self.schema.default_prefix, python_slot_name)) domain = camelcase( slot.domain) if slot.domain and not self.schema.classes[ slot.domain].mixin else "None" # Going to omit the range on keys where the domain isn't specified (for now) if slot.domain is None and (slot.key or slot.identifier): rnge = "URIRef" else: rnge, _ = self.range_cardinality( slot, self.schema.classes[slot.domain] if slot.domain else None, True) if slot.mappings: map_texts = [ self.namespaces.curie_for(self.namespaces.uri_for(m), default_ok=True, pythonform=True) for m in slot.mappings if m != slot.slot_uri ] else: map_texts = [] if map_texts: mappings = ', mappings = [' + ', '.join(map_texts) + ']' else: mappings = '' pattern = f",\n pattern=re.compile(r'{slot.pattern}')" if slot.pattern else "" return f"""slots.{python_slot_name} = Slot(uri={slot_uri}, name="{slot.name}", curie={slot_curie},
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 visit_enum(self, enum: EnumDefinition) -> None: with open(self.exist_warning(self.dir_path(enum)), 'w') as enumfile: with redirect_stdout(enumfile): enum_curie = self.namespaces.uri_or_curie_for(self.namespaces._base, underscore(enum.name)) enum_uri = self.namespaces.uri_for(enum_curie) self.element_header(obj=enum, name=enum.name, curie=enum_curie, uri=enum_uri) self.element_properties(enum)
def visit_slot(self, aliased_slot_name: str, slot: SlotDefinition) -> None: with open(self.exist_warning(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) self.element_header(slot,aliased_slot_name, slot_curie, slot_uri) self.mappings(slot) 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): 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 _link(self, obj: Optional[Element], *, after_link: str = None, use_desc: bool=False, add_subset: bool=True) -> str: """ Create a link to ref if appropriate. @param ref: the name or value of a class, slot, type or the name of a built in type. @param after_link: Text to put between link and description @param use_desc: True means append a description after the link if available @param add_subset: True means add any subset information that is available @return: """ nl = '\n' if obj is None or not self.is_secondary_ref(obj.name): return self.bbin(obj) if isinstance(obj, SlotDefinition): link_name = ((be(obj.domain) + '➞') if obj.alias else '') + self.aliased_slot_name(obj) link_ref = underscore(obj.name) elif isinstance(obj, TypeDefinition): link_name = camelcase(obj.name) link_ref = f"types/{link_name}" if not self.no_types_dir else f"{link_name}" elif isinstance(obj, ClassDefinition): link_name = camelcase(obj.name) link_ref = camelcase(link_name) else: link_name = obj.name link_ref = link_name desc = self.desc_for(obj, use_desc) return f'[{link_name}]' \ f'({link_ref}.{self.format})' + \ (f' {after_link} ' if after_link else '') + (f' - {desc.split(nl)[0]}' if desc else '')
def visit_class(self, cls: ClassDefinition) -> bool: cn = underscore(cls.name) self.emit('class', cn) for p in cls.mixins: self.emit('mixin', cn, underscore(p)) if cls.is_a: is_a = underscore(cls.is_a) self.emit('is_a', cn, is_a) if cls.defining_slots: self.emit('defining_slots', is_a, [underscore(s) for s in cls.defining_slots]) uri = f'http://w3id.org/biolink/vocab/{camelcase(cls.name)}' self.emit('has_uri', cn, uri) return True
def slot_name(self, name: str) -> str: """ Return the underscored version of the aliased slot name if name is a slot. Prepend "unknown_" if the name isn't valid. """ slot = self.slot_for(name) return underscore( self.aliased_slot_name(slot) if slot else ("unknown " + name))
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> None: if slot.range in self.schema.classes and slot.inlined: rng = f"#/definitions/{camelcase(slot.range)}" elif slot.range in self.schema.types: rng = self.schema.types[slot.range].base else: # note we assume string for non-lined complex objects rng = "string" # translate to json-schema builtins if rng == 'int': rng = 'integer' elif rng == 'Bool': rng = 'boolean' elif rng == 'str': rng = 'string' elif rng == 'float' or rng == 'double': rng = 'number' elif not rng.startswith('#'): # URIorCURIE, etc rng = 'string' if slot.inlined: # If inline we have to include redefined slots ref = JsonObj() ref['$ref'] = rng if slot.multivalued: prop = JsonObj(type="array", items=ref) else: prop = ref else: if slot.multivalued: prop = JsonObj(type="array", items={'type': rng}) else: prop = JsonObj(type=rng) if slot.description: prop.description = slot.description if slot.required: self.clsobj.required.append(underscore(aliased_slot_name)) self.clsobj.properties[underscore(aliased_slot_name)] = prop if self.topCls is not None and camelcase(self.topCls) == camelcase(cls.name) or \ self.topCls is None and cls.tree_root: self.schemaobj.properties[underscore(aliased_slot_name)] = prop
def visit_class(self, cls: ClassDefinition) -> bool: # TODO: find out what to do with mappings if not self.closure or cls.name in self.closure: self.writer.writerow({ 'id': underscore(cls.name), # 'mappings': "|".join(cls.mappings), 'mappings': '', 'description': be(cls.description) }) return True return False
def visit_class(self, cls: ClassDefinition) -> bool: if not (cls.mixin or cls.abstract): self.class_obj = GOLRClass(id=underscore(cls.name), schema_generating=True, description=cls.description, display_name=cls.name, document_category=cls.name, weight=20) return True else: return False
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> None: if slot.range in self.schema.classes: rng = camelcase(slot.range) elif slot.range in self.schema.types: # XXX Why does `linkml.utils.metamodelcore.Identifier` subclass `str`?? rng = str(self.schema.types[slot.range].uri) else: rng = "xsd:string" name = (f"{cls.name} {aliased_slot_name}" if slot.is_usage_slot else aliased_slot_name) # translate to terminusdb xsd builtins: if rng == "xsd:int": rng = "xsd:integer" elif rng == "xsd:float": rng = "xsd:double" elif rng == "xsd:language": rng = "xsd:string" if rng not in XSD_Ok and slot.range not in self.schema.classes: raise Exception( f"slot range for {name} must be schema class or supported xsd type. " f"Range {rng} is of type {type(rng)}.") self.clswq.property(underscore(name), rng, label=name, description=slot.description) if not slot.multivalued: self.clswq.max(1) if slot.required: self.clswq.min(1) if slot.is_a: self.raw_additions.append(WQ().add_quad( underscore(name), "rdfs:subPropertyOf", self.clswq.iri(underscore(slot.is_a)), "inference/main", ))
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> None: #qual = 'repeated ' if slot.multivalued else 'optional ' if not slot.required or slot.key else '' slotname = underscore(aliased_slot_name) tname = camelcase(cls.name) slot_range = self.get_sql_range(slot) if slot.multivalued: linktable_name = f'{tname}_to_{slotname}' pk = self._get_primary_key(cls) if pk is None: if self.inject_primary_keys: pk = 'id' self.columns[camelcase(cls.name)][pk] = Column( pk, Text(), primary_key=True) else: raise Exception( f'Cannot have multivalued on cols with no PK: {tname} . {slotname}' ) ref = f'ref_{tname}' linkrange = None if isinstance(slot_range, ForeignKey): linkrange = Text() else: linkrange = slot_range linktable_slot = slotname if slot.singular_name is not None: linktable_slot = underscore(slot.singular_name) self.columns[linktable_name] = { linktable_slot: Column(linktable_slot, linkrange, nullable=False), ref: Column(ref, ForeignKey(f'{tname}.{pk}'), nullable=False) } is_pk = slot.identifier col = Column(slotname, slot_range, primary_key=is_pk, nullable=not slot.required) self.columns[tname][slotname] = col
def class_box(self, cn: ClassDefinitionName) -> str: """ Generate a box for the class. Populate its interior only if (a) it hasn't previously been generated and (b) it appears in the gen_classes list @param cn: @return: """ slot_defs: List[str] = [] if cn not in self.box_generated and (not self.focus_classes or cn in self.focus_classes): cls = self.schema.classes[cn] for slot in self.filtered_cls_slots( cn, all_slots=True, filtr=lambda s: s.range not in self.schema.classes): if True or cn in slot.domain_of: mod = self.prop_modifier(cls, slot) slot_defs.append( underscore(self.aliased_slot_name(slot)) + mod + ':' + underscore(slot.range) + self.cardinality(slot)) self.box_generated.add(cn) self.referenced.add(cn) return '[' + camelcase(cn) + ('|' + ';'.join(slot_defs) if slot_defs else '') + ']'
def set_from_schema(schema: SchemaDefinition) -> None: for t in [ schema.subsets, schema.classes, schema.slots, schema.types, schema.enums ]: for k in t.keys(): t[k].from_schema = schema.id if isinstance(t[k], SlotDefinition): fragment = underscore(t[k].name) else: fragment = camelcase(t[k].name) if schema.default_prefix in schema.prefixes: ns = schema.prefixes[schema.default_prefix].prefix_reference else: ns = str(URIRef(schema.id) + "/") t[k].definition_uri = f'{ns}{fragment}'
def end_class(self, cls: ClassDefinition) -> None: if self.cls_subj and self.cls_obj: rnode = 'relation' self.edge(self.aliased_slot_name(self.cls_subj), self.aliased_slot_name(self.cls_obj), label=rnode) self.edge(self.aliased_slot_name(self.cls_subj), rnode, style='dotted') self.edge(self.aliased_slot_name(self.cls_obj), rnode, style='dotted') if self.classdot: self.classdot.render(underscore(cls.name), self.dirname, view=False, cleanup=True, format=self.format)
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}')
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_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 dir_path(self, obj: Union[ClassDefinition, SlotDefinition, TypeDefinition]) -> str: filename = self.formatted_element_name(obj) if isinstance(obj, ClassDefinition) \ else underscore(obj.name) if isinstance(obj, SlotDefinition) \ else camelcase(obj.name) subdir = '/types' if isinstance(obj, TypeDefinition) and not self.no_types_dir else '' return f'{self.directory}{subdir}/{filename}.md'
def end_class(self, cls: ClassDefinition) -> None: fn = os.path.join(self.dirname, underscore(cls.name + '-config.yaml')) if len(self.class_obj.fields) > 1: with open(fn, 'w') as f: f.write(as_yaml(self.class_obj))
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> None: field = GOLRField(id=underscore(aliased_slot_name), description=slot.description, display_name=slot.name) if slot.multivalued: field.cardinality = 'multi' self.class_obj.fields.append(field)
def slot_name_for(slot: SlotDefinition) -> str: return underscore(slot.alias if slot.alias else slot.name)