def _visit(self, node: Any) -> Optional[Any]: if isinstance(node, (YAMLRoot, dict)): if isinstance(node, YAMLRoot): node = node.__dict__ 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, ElementName): return ClassDefinitionName(camelcase(node)) if node in self.schema.classes else \ SlotDefinitionName(underscore(node)) if node in self.schema.slots else \ TypeDefinitionName(underscore(node)) if node in self.schema.types else \ builtin_uri(str(node)) if str(node) in builtin_names else None elif str(node) in builtin_names: return builtin_uri(str(node)) return None
def range_cardinality(self, range_type: str, slot: SlotDefinition, cls: ClassDefinition) \ -> Tuple[str, Optional[str]]: if slot.multivalued: if slot.range in self.schema.classes: slot_range_cls = self.schema.classes[slot.range] pkeys = self.primary_keys_for(slot_range_cls) if pkeys: pkey = camelcase(slot.range) + camelcase(pkeys[0]) non_keys = set( self.all_slots_for(slot_range_cls)).difference( set(pkeys)) if len(non_keys) == 1: raw_range_type = self.range_type_name( self.schema.slots[non_keys.pop()], cls.name) else: raw_range_type = "dict" else: raw_range_type = "dict" else: pkeys = None pkey = 'str' range_signature = f"Union[{raw_range_type}, {range_type}]" \ if slot.inlined and slot.range is not None and slot.range not in builtin_names else range_type return (f'List[{range_signature}]', 'empty_list()') if not slot.inlined or not pkeys \ else (f'Dict[{pkey}, {range_signature}]', 'empty_dict()') elif slot.primary_key or slot.identifier or slot.required: return range_type, 'None' if cls.is_a else None elif range_type == 'bool': return 'bool', 'False' else: return f'Optional[{range_type}]', 'None'
def visit_class(self, cls: ClassDefinition) -> bool: etype = 'interface' if cls.abstract or cls.mixin else 'type' mixins = ', '.join([camelcase(mixin) for mixin in cls.mixins]) print(f"{camelcase(cls.name)} {etype}" + (f"implements {mixins}" if mixins else "")) print(" {") return True
def _default(self, obj): """ JSON serializer callback. 1) Filter out empty values (None, {}, [] and False) and mangle the names 2) Add ID entries for dictionary entries :param obj: YAMLRoot object to serialize :return: Serialized version of obj """ from metamodel.metamodel import ClassDefinition, SlotDefinition, TypeDefinition if isinstance(obj, JsonObj): rval = dict() for k, v in obj.__dict__.items(): if not k.startswith('_') and v is not None and ( not isinstance(v, (dict, list, bool)) or v): if isinstance(v, dict): itemslist = [] for vk, vv in v.items(): if isinstance(vv, ClassDefinition): vv['@id'] = camelcase(vk) elif isinstance(vv, (SlotDefinition, TypeDefinition)): if k != 'slot_usage': vv['@id'] = underscore(vk) itemslist.append(vv) rval[k] = itemslist else: rval[k] = v return rval else: return super()._default(obj)
def visit_class(self, cls: ClassDefinition) -> bool: if cls.abstract: return False self.clsobj = JsonObj(title=camelcase(cls.name), type='object', properties=JsonObj(), description=be(cls.description)) return True
def obj_name(self, obj: Union[str, Element]) -> str: """ Return the formatted name used for the supplied definition """ if isinstance(obj, str): obj = self.obj_for(obj) if isinstance(obj, SlotDefinition): return underscore(self.aliased_slot_name(obj)) else: return camelcase(obj if isinstance(obj, str) else obj.name)
def gen_references(self) -> str: """ Generate python type declarations for all identifiers (primary keys) """ rval = [] for cls in self.schema.classes.values(): pkeys = self.primary_keys_for(cls) for pk in pkeys: pk_slot = self.schema.slots[pk] classname = camelcase(cls.name) + camelcase(pk) if cls.is_a and getattr(self.schema.classes[cls.is_a], pk, None): parent = self.range_type_name(pk_slot, cls.is_a) else: parent = self.python_name_for(pk_slot.range) rval.append(f'class {classname}({parent}):\n\tpass') return '\n\n\n'.join(rval)
def test_idempotent(self): txt = "this is it" self.assertEqual(camelcase(txt), camelcase(camelcase(txt))) self.assertEqual("MicroRNA", camelcase("microRNA")) self.assertEqual("SellTheHouseNOW", camelcase("sell the\thouseNOW")) self.assertEqual("X", camelcase("x")) self.assertEqual("X", camelcase(" x "))
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> None: slotrange = camelcase( slot.range) if slot.range in self.schema.classes else "String" if slot.multivalued: slotrange = f"[{slotrange}]" if slot.required or slot.primary_key: slotrange = slotrange + '!' print(f" {lcamelcase(aliased_slot_name)}: {slotrange}")
def visit_class(self, cls: ClassDefinition) -> bool: class_def = {} cn = camelcase(cls.name) self.add_mappings(cls, class_def) if class_def: self.slot_class_maps[cn] = class_def # We don't bother to visit class slots - just all slots return False
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)) elif slot.range in (Builtin.uri, Builtin.anytype): slot.range = '@id' elif slot.range in builtin_names and builtin_names[slot.range] not in (Builtin.anytype, Builtin.uri): slot.range = builtin_uri(slot.range)
def range_type_name(self, slot: SlotDefinition, containing_class_name: ClassDefinitionName) -> str: """ Generate the type name for the slot """ if slot.primary_key or slot.identifier: return self.python_name_for(containing_class_name) + camelcase( slot.name) if slot.range in self.schema.classes and not slot.inlined: class_key = self.key_name_for(cast(ClassDefinitionName, slot.range)) if class_key: return class_key return self.python_name_for(slot.range)
def visit_schema(self, classes: Set[ClassDefinitionName] = None, directory: Optional[str] = None, load_image: bool = True) -> None: if directory: os.makedirs(directory, exist_ok=True) for cls in classes: if cls not in self.schema.classes: raise ValueError("Unknown class name: {cls}") self.box_generated = set() self.associations_generated = set() self.focus_classes = classes if classes: self.gen_classes = self.neighborhood( list(classes)).classrefs.union(classes) else: self.gen_classes = self.synopsis.roots.classrefs self.referenced = self.gen_classes self.generated = set() yumlclassdef: List[str] = [] while self.referenced.difference(self.generated): cn = sorted(list(self.referenced.difference(self.generated)), reverse=True)[0] self.generated.add(cn) assocs = self.class_associations(ClassDefinitionName(cn), cn in self.referenced) if assocs: yumlclassdef.append(assocs) yuml_url = str(YUML) + ', '.join(yumlclassdef) + \ (('.' + self.format) if self.format not in ('yuml', 'png') else '') file_suffix = '.png' if self.format == 'yuml' else '.' + self.format if directory: self.output_file_name = os.path.join( directory, camelcase(classes[0] if classes else self.schema.name) + file_suffix) if load_image: resp = requests.get(yuml_url, stream=True) if resp.ok: with open(self.output_file_name, 'wb') as f: for chunk in resp.iter_content(chunk_size=2048): f.write(chunk) else: print(str(YUML) + ', '.join(yumlclassdef), end='')
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: @param inherited: @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 slotname in self.filtered_cls_slots(cn, all_slots=True): slot = self.schema.slots[slotname] if not slot.range or slot.range in builtin_names or slot.range in self.schema.types: 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 visit_class(self, cls: ClassDefinition) -> 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): self.frontmatter(f"Class: {cls.name}") self.para(be(cls.description)) cls_uri = BIOENTITY[camelcase(cls.name)] print(f'URI: [{cls_uri}]({cls_uri})') print() if self.image_directory: yg = YumlGenerator(self.schema) 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: img_url = YumlGenerator(self.schema).serialize(classes=[cls.name])\ .replace('[', '\\[').replace('?', '%3F').replace(' ', '%20') print(f'![img]({img_url})') self.mappings(cls) self.header(2, 'Inheritance') if cls.is_a is not None: self.bullet(f' is_a: {self.link(cls.is_a, use_desc=True)}') for mixin in cls.mixins: self.bullet(f' mixin: {self.link(mixin, use_desc=True)}') self.header(2, 'Children') if cls.name in self.synopsis.isarefs: for child in sorted( self.synopsis.isarefs[cls.name].classrefs): self.bullet(f'{self.link(child, use_desc=True)}') if cls.name in self.synopsis.mixinrefs: for mixin in sorted( self.synopsis.mixinrefs[cls.name].classrefs): self.bullet( f'{self.link(mixin, use_desc=True, after_link="(mixin)")}' ) if cls.name in self.synopsis.classrefs: self.header(2, 'Used in') for sn in sorted( self.synopsis.classrefs[cls.name].slotrefs): slot = self.schema.slots[sn] if slot.range == cls.name: self.bullet( f' class: **{self.link(slot.domain)}** ' f'*{self.link(slot.name, add_subset=False)}* **{self.link(cls.name)}**' ) self.header(2, 'Fields') for sn in sorted(cls.slots): self.slot_field(cls, self.schema.slots[sn]) for slot in sorted(self.all_slots(cls), key=lambda s: s.name): if slot.name not in cls.slots: self.slot_field(cls, slot) return True
def class_uri(cn: ClassDefinitionName) -> URIRef: return BIOENTITY[camelcase(cn)]
def _shapeIRI(name: ClassDefinitionName) -> IRIREF: return IRIREF(BIOENTITY[camelcase(name)])
def end_class(self, cls: ClassDefinition) -> None: self.schemaobj.properties[camelcase(cls.name)] = self.clsobj
def key_name_for(self, class_name: ClassDefinitionName) -> Optional[str]: for slot_name in self.primary_keys_for( self.schema.classes[class_name]): return self.python_name_for(class_name) + camelcase(slot_name) return None
def python_class_name() -> str: return camelcase(name)
def python_type_name() -> str: return camelcase(name)