def test_merge_contexts_base(self): self.assertEqual( JsonObj(**{'@context': JsonObj(**{'@base': 'file://relloc'})}), merge_contexts(base='file://relloc')) self.assertEqual( loads(f'{{"@context": {{"@base": "{META_BASE_URI}"}}}}'), merge_contexts(base=META_BASE_URI)) self.assertEqual( loads(""" {"@context": [ "https://w3id.org/biolink/biolinkml/context.jsonld", { "ex": "http://example.org/test/", "ex2": "http://example.org/test2/" }, { "ex": "http://example.org/test3/", "ex2": { "@id": "http://example.org/test4/" } }, { "@base": "https://w3id.org/biolink/biolinkml/" } ] }"""), merge_contexts([METAMODEL_CONTEXT_URI, json_1, json_2], base=META_BASE_URI))
def visit_schema(self, inline: bool=False, **kwargs) -> None: self.inline = inline self.schemaobj = JsonObj(title=self.schema.name, type="object", properties=JsonObj(), definitions=JsonObj()) self.schemaobj['$schema'] = "http://json-schema.org/draft-04/schema#" self.schemaobj['$id'] = self.schema.id
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 as_json(schema: YAMLRoot, context: Optional[Union[JsonObj, dict]] = None) -> JsonObj: rval = JsonObj(**schema.__dict__) rval['type'] = schema.__class__.__name__ if context: if isinstance(context, JsonObj): context = context.__dict__ for k, v in context.items(): rval[k] = JsonObj(**v) if isinstance(v, dict) else v return rval
def visit_schema(self, inline: bool = False, **kwargs) -> None: self.inline = inline self.schemaobj = JsonObj(title=self.schema.name, type="object", properties={}, definitions=JsonObj()) for p, c in self.entryProperties.items(): self.schemaobj['properties'][p] = { 'type': "array", 'items': {'$ref': f"#/definitions/{camelcase(c)}"}} self.schemaobj['$schema'] = "http://json-schema.org/draft-07/schema#" self.schemaobj['$id'] = self.schema.id
def visit_class_slot(self, cls: ClassDefinition, aliased_slot_name: str, slot: SlotDefinition) -> None: if self.inline: # If inline we have to include redefined slots prop = JsonObj(type=JsonObj()) prop.type['$ref'] = self.jref(underscore(slot.name)) elif slot.multivalued: prop = JsonObj(type="array", items=self.type_or_ref(slot.range)) else: prop = JsonObj(type="string") if slot.description: prop.description = slot.description self.clsobj.properties[underscore(aliased_slot_name)] = prop
def gen_reference(ref: str, refobject: JsonObj, server: Optional[str], id_map: Optional[Dict[str, str]])\ -> Optional[JsonObj]: """ Return the object of a fhir:link based on the reference in refObject :param ref: reference :param refobject: object containing the reference :param server: If supplied, :param id_map: Map of bundle reference names to URI/type tuples :return: link and optional type element """ if "://" not in ref and not ref.startswith('/'): # Relative path link = ('' if server else '../') + ref # If server is supplied, ref is local, else relative to parent else: link = ref if link: if hasattr(refobject, TYPE_KEY): typ = refobject[TYPE_KEY] else: # TODO: we need to decide whether to use R5 or R4 regular expressions here m = R5_FHIR_URI_RE.match(ref) typ = m[4] if m else None rval = JsonObj() if link in id_map: rval['@id'] = id_map[link] else: rval['@id'] = link if typ: rval['@type'] = "fhir:" + typ return rval return None
def parent_classes(self) -> str: """ Return the schema subclass entries""" rval = '' for e in self._obj._get('snapshot', JsonObj())._get('element', []): if e._get('path', '') == self._obj._get('name', None): for m in e._get('mapping', []): if m.identity == 'w5': rval += schema_subclass(FHIR_SDO[m.map], 'w5:', m.map) for e in self._obj._get('mapping', []): if e.identity == 'w5' and not url_leaf(e.uri) == 'w5': rval += schema_subclass(e.uri, 'w5:', url_leaf(e.uri)) base_url = self._obj._get('baseDefinition', None) base_ns = 'fhir:' if not base_url: base_url = self._obj._get('base', None) if not base_url and not rval: base_url = 'http://schema.org/Thing' base_ns = '' base_type = self._obj._get('baseType', None) if not base_type: base_type = self._obj._get('constrainedType', None) if base_url: rval += schema_subclass( base_url, base_ns, base_type if base_type else url_leaf(base_url)) return rval
def list_processor(k: str, lst: List) -> Any: """ Process a list, adding interior identifiers if its members are objects and then replicating it so that we end up with a SET and a LIST :param k: List key (for error reporting) :param lst: List to be processed """ bnode_generator = BNodeGenerator() def list_element(e: Any) -> Any: """ Add a list index to list element "e" :param e: Element in a list :return: adjusted object """ if isinstance(e, JsonObj): dict_processor(e, resource_type, True) if not hasattr(e, '@id'): e['@id'] = ('#' + e.id) if hasattr(e, 'id') else bnode_generator.next_node() elif isinstance(e, list): print(f"Problem: {k} has a list in a list", file=sys.stderr) else: e = to_value(e) return e adjusted_list = [list_element(le) for le in lst] return [adjusted_list, {"fhir:ordered": [JsonObj(**{"@id": e['@id']}) if isinstance(e, JsonObj) else e for e in adjusted_list]}]
def end_schema(self, base: Optional[str] = None, output: Optional[str] = None, **_) -> None: comments = f'''Auto generated from {self.schema.source_file} by {self.generatorname} version: {self.generatorversion} Generation date: {self.schema.generation_date} Schema: {self.schema.name} id: {self.schema.id} description: {be(self.schema.description)} license: {be(self.schema.license)} ''' context = JsonObj() context["_comments"] = comments context_content = {"_comments": None, "type": "@type"} if base: if '://' not in base: self.context_body['@base'] = os.path.relpath( base, os.path.dirname(self.schema.source_file)) else: self.context_body['@base'] = base for prefix in sorted(self.emit_prefixes): if self.namespaces[prefix] != self.context_body['@vocab']: context_content[prefix] = self.namespaces[prefix] for k, v in self.context_body.items(): context_content[k] = v for k, v in self.slot_class_maps.items(): context_content[k] = v context['@context'] = context_content if output: with open(output, 'w') as outf: outf.write(as_json(context)) else: print(as_json(context))
def gen_reference(rd: JsonObj) -> List[JsonObj]: """ Return the object of a fhir:link based on the reference in d :param rd: object containing the reference :return: link and optional type element """ # TODO: find the official regular expression for the type node. For the moment we make the (incorrect) # assumption that the type is everything that preceeds the first slash if "://" not in rd.reference and not rd.reference.startswith('/'): if hasattr(rd, 'type'): typ = rd.type else: typ = rd.reference.split('/', 1)[0] link = '../' + rd.reference else: link = rd.reference typ = getattr(rd, 'type', None) # "subject": [{"@id": "../Patient/foo1", # "@type": "fhir:Patient" }, # {"reference": "Patient/f001", # "display": "P. van de Heuvel"}] ref = JsonObj(**{"@id": link}) if typ: ref['@type'] = "fhir:" + typ return [ref, rd]
def visit_slot(self, slot_name: str, slot: SlotDefinition) -> None: # Don't emit redefined slots unless we are inlining if slot_name == slot.name or self.inline: defn = JsonObj(type="array", items=self.type_or_ref(slot.range)) if slot.multivalued \ else self.type_or_ref(slot.range) if slot.description: defn.description = slot.description self.schemaobj.definitions[underscore(slot.name)] = defn
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 end_schema(self, base: Optional[str] = None, output: Optional[str] = None, **_) -> None: context = JsonObj() if self.emit_metadata: comments = f'''Auto generated from {self.schema.source_file} by {self.generatorname} version: {self.generatorversion}''' if self.schema.generation_date: comments += f''' Generation date: {self.schema.generation_date} Schema: {self.schema.name} ''' comments += f''' id: {self.schema.id} description: {be(self.schema.description)} license: {be(self.schema.license)} ''' context["_comments"] = comments context_content = {} if base: if '://' not in base: self.context_body['@base'] = os.path.relpath( base, os.path.dirname(self.schema.source_file)) else: self.context_body['@base'] = base for prefix in sorted(self.emit_prefixes): url = str(self.namespaces[prefix]) if ':' == url[-1] or '/' == url[-1] or '?' == url[-1] or '#' == url[ -1] or '[' == url[-1] or ']' == url[-1] or '@' == url[-1]: context_content[prefix] = self.namespaces[prefix] else: prefix_obj = JsonObj() prefix_obj["@id"] = self.namespaces[prefix] prefix_obj["@prefix"] = True context_content[prefix] = prefix_obj for k, v in self.context_body.items(): context_content[k] = v for k, v in self.slot_class_maps.items(): context_content[k] = v context['@context'] = context_content if output: with open(output, 'w') as outf: outf.write(as_json(context)) else: print(as_json(context))
def merge_contexts(contexts: CONTEXTS_PARAM_TYPE = None, base: Optional[Any] = None, root_directory: Optional[str] = None) -> JsonObj: """ Take a list of JSON-LD contexts, which can be one of: * the name of a JSON-LD file * the URI of a JSON-lD file * JSON-LD text * A JsonObj object that contains JSON-LD * A dictionary that contains JSON-LD And turn it into an object that can be tacked onto the end of any JSON object for conversion into RDF The base is added back in because @base is ignored in imported and nested contexts -- it must be at the root in the object itself. :param contexts: Ordered list of contexts to add :param base: base to add in (optional) :return: aggregated context """ def prune_context_node(ctxt: Union[str, JsonObj]) -> Union[str, JsonObj]: return ctxt['@context'] if isinstance( ctxt, JsonObj) and '@context' in ctxt else ctxt def to_file_uri(fname: str) -> str: return 'file://' + fname context_list = [] for context in [] if contexts is None else [contexts] if not isinstance( contexts, (list, tuple, set)) else contexts: if isinstance(context, str): # One of filename, URL or json text if context.strip().startswith("{"): context = loads(context) elif '://' not in context: context = to_file_uri(context) elif not isinstance(context, (JsonObj, str)): context = JsonObj(**context) # dict context_list.append(prune_context_node(context)) if base: context_list.append(JsonObj(**{'@base': str(base)})) return None if not context_list else \ JsonObj(**{"@context": context_list[0] if len(context_list) == 1 else context_list})
def check_types(s: SchemaDefinition) -> None: output = os.path.join(outputdir, 'schema4.json') if not os.path.exists(output): with open(output, 'w') as f: f.write(as_json(JsonObj(**{k: as_dict(loads(as_json(v))) for k, v in s.types.items()}))) self.fail(f"File {output} created - rerun test") with open(output) as f: expected = as_dict(load(f)) self.assertEqual(expected, {k: as_dict(loads(as_json(v))) for k, v in s.types.items()}) s.types = None
def map_parameters(struct) -> Optional[JsonObj]: if struct.resourceType == "Parameters": rval = JsonObj() for p in struct.parameter: if hasattr(p, 'valueString'): rval[p.name] = p.valueString elif hasattr(p, 'part'): rval[p.name] = p.part else: pass return rval return None
def as_json_object(element: YAMLRoot, contexts: CONTEXTS_PARAM_TYPE = None) -> JsonObj: """ Return the representation of element as a JsonObj object :param element: element to return :param contexts: context(s) to include in the output :return: JsonObj representation of element """ rval = JsonObj(**element.__dict__) rval['type'] = element.__class__.__name__ context_element = merge_contexts(contexts) if context_element: rval['@context'] = context_element['@context'] return rval
def end_schema(self) -> None: comments = f'''Auto generated from {self.schema.source_file} by {self.generatorname} version: {self.generatorversion} Generation date: {self.schema.generation_date} Schema: {self.schema.name} id: {self.schema.id} description: {be(self.schema.description)} license: {be(self.schema.license)} ''' context = JsonObj(comments=comments) for k, v in self.slot_class_maps.items(): self.prefixmap[k] = v context['@context'] = self.prefixmap print(as_json(context))
def test_element_slots(self): """ Test all element slots and their inheritence """ schema = SchemaLoader(env.input_path('resolver3.yaml')).resolve() x = { k: v for k, v in as_dict(schema.slots['s1']).items() if v is not None and v != [] } outfile = env.expected_path('resolver3.json') if not os.path.exists(outfile): with open(outfile, 'w') as f: f.write(as_json(JsonObj(**x))) with open(outfile) as f: expected = as_dict(load(f)) self.assertEqual(expected, x)
def check_types(s: SchemaDefinition) -> None: output = env.expected_path('schema4.json') if not os.path.exists(output): with open(output, 'w') as f: f.write( as_json( JsonObj( **{ k: as_dict(loads(as_json(v))) for k, v in s.types.items() }))) with open(output) as f: expected = as_dict(load(f)) self.assertEqual( expected, {k: as_dict(loads(as_json(v))) for k, v in s.types.items()}) s.types = None
def type_last(self, obj: JsonObj) -> JsonObj: """ Move the type identifiers to the end of the object for print purposes """ def _tl_list(v: List) -> List: return [ self.type_last(e) if isinstance(e, JsonObj) else _tl_list(e) if isinstance(e, list) else e for e in v if e is not None ] rval = JsonObj() for k in as_dict(obj).keys(): v = obj[k] if v is not None and k not in ('type', '_context'): rval[k] = _tl_list(v) if isinstance( v, list) else self.type_last(v) if isinstance( v, JsonObj) else v if 'type' in obj and obj.type: rval.type = obj.type return rval
def gen_reference(do: JsonObj) -> JsonObj: """ Return the object of a fhir:link based on the reference in d :param do: object containing the reference :return: link and optional type element """ # TODO: find the official regular expression for the type node. For the moment we make the (incorrect) # assumption that the type is everything that preceeds the first slash if "://" not in do.reference and not do.reference.startswith('/'): if hasattr(do, 'type'): typ = do.type else: typ = do.reference.split('/', 1)[0] link = '../' + do.reference else: link = do.reference typ = getattr(do, 'type', None) rval = JsonObj(**{"@id": link}) if typ: rval['@type'] = "fhir:" + typ return rval
def add_date_type(date_type: URIRef, container: JsonObj) -> None: """ Add the appropriate type to container :param date_type: FHIR date/time type :param container: Target container """ date_str = from_value(container) dt = None if date_type in (FHIR.date, FHIR.dateTime): if gYear_re.match(date_str): dt = XSD.gYear elif gYearMonth_re.match(date_str): dt = XSD.gYearMonth elif date_re.match(date_str): dt = XSD.date elif date_type == FHIR.dateTime and dateTime_re.match(date_str): dt = XSD.dateTime if dt: typed_obj = JsonObj() typed_obj['@value'] = date_str typed_obj['@type'] = str(dt) container['value'] = typed_obj
def end_schema(self, base: Optional[str] = None, output: Optional[str] = None, **_) -> None: context = JsonObj() if base: if '://' not in base: self.context_body['@base'] = os.path.relpath( base, os.path.dirname(self.schema.source_file)) else: self.context_body['@base'] = base for prefix in sorted(self.emit_prefixes): context[prefix] = self.namespaces[prefix] for k, v in self.context_body.items(): context[k] = v for k, v in self.slot_class_maps.items(): context[k] = v if output: with open(output, 'w') as outf: outf.write(as_json(context)) else: print(as_json(context))
def dict_processor(d: JsonObj, resource_type: str, resource_type_set: Set[str], full_url: Optional[str] = None) \ -> None: """ Process the elements in dictionary d: 1) Ignore keys that begin with "@" - this is already JSON information 2) Add index numbers to lists 3) Recursively process values of type object 4) Convert any elements that are not one of "nodeRole", "index", "id" and "div" to objects 5) Merge :param d: dictionary to be processed :param resource_type: unedited resource type :param resource_type_set: set of all resource types in the model :param full_url: official ID of the resource from the containing fullURL :return: List of resourceTypes referenced by the resource """ def gen_reference(do: JsonObj) -> JsonObj: """ Return the object of a fhir:link based on the reference in d :param do: object containing the reference :return: link and optional type element """ # TODO: find the official regular expression for the type node. For the moment we make the (incorrect) # assumption that the type is everything that preceeds the first slash if "://" not in do.reference and not do.reference.startswith('/'): if hasattr(do, 'type'): typ = do.type else: typ = do.reference.split('/', 1)[0] link = '../' + do.reference else: link = do.reference typ = getattr(do, 'type', None) rval = JsonObj(**{"@id": link}) if typ: rval['@type'] = "fhir:" + typ return rval # Normalize all the elements in d. # We realize the keys as a list up front to prevent messing with our own updates # full_url is set if we're an embedded resource that has a full_url inner_type = getattr(d, 'resourceType', None) if inner_type: resource_type = inner_type if full_url: d['@id'] = full_url # If this has a resource type, it overrides what was passed in for k in list(as_dict(d).keys()): v = d[k] if k.startswith('@'): # Ignore JSON-LD components pass elif isinstance(v, JsonObj): # Inner object -- process recursively dict_processor(v, resource_type, resource_type_set, getattr(d, 'fullUrl', None)) elif isinstance(v, list): # Add ordering to the list d[k] = list_processor(k, v, resource_type_set) elif k == "id": # Internal ids are relative to the document if not full_url: d['@id'] = \ ('#' if not inner_type and not v.startswith('#') else (resource_type + '/')) + v d[k] = to_value(v) elif k == "reference": # Link to another document if not hasattr(d, 'link'): d["fhir:link"] = gen_reference(d) d[k] = to_value(v) elif k == "resourceType" and not (v.startswith('fhir:')): resource_type_set.add(v) d[k] = 'fhir:' + v elif k not in ["nodeRole", "index", "div"]: # Convert most other nodes to value entries d[k] = to_value(v) if k == 'coding': [add_type_arc(n) for n in v] # Merge any extensions (keys that start with '_') into the base for k in [k for k in as_dict(d).keys() if k.startswith('_')]: base_k = k[1:] if not hasattr(d, base_k) or not isinstance(d[base_k], JsonObj): d[base_k] = JsonObj() else: for kp, vp in as_dict(d[k]).items(): if kp in d[base_k]: print( f"Extension element {kp} is already in the base for {k}" ) else: d[base_k][kp] = vp del (d[k])
def to_value(v: Any) -> JsonObj: return JsonObj(**{VALUE_TAG: v})
def to_r4(o: JsonObj, server: Optional[str], add_context: bool) -> JsonObj: """ Convert the FHIR Resource in "o" into the R4 value notation :param o: FHIR resource :param server: Server root - if absent, use the file location :param add_context: True means add @context :return: reference to "o" with changes applied. Warning: object is NOT copied before change """ def to_value(v: Any) -> JsonObj: return JsonObj(**{VALUE_TAG: v}) def parse_resource_type(rt: str) -> str: """ Parse rt returning just the resource type """ return rt.rsplit(':')[1] if ':' in rt else rt def from_value(v: Any) -> Any: return v[VALUE_TAG] if isinstance( v, JsonObj) and VALUE_TAG in as_dict(v) else v def add_type_arc(n: JsonObj) -> None: if hasattr(n, 'system') and hasattr(n, 'code'): # Note: This is another reason we hate the value work system = from_value(n.system) code = urllib.parse.quote(from_value(n.code), safe='') system_root = system[:-1] if system[-1] in '/#' else system if system_root in CODE_SYSTEM_MAP: base = CODE_SYSTEM_MAP[system_root] + ':' else: base = system + ('' if system[-1] in '/#' else '/') # TODO: Escape code n['@type'] = base + code def dict_processor(d: JsonObj, resource_type: str, resource_type_set: Set[str], full_url: Optional[str] = None) \ -> None: """ Process the elements in dictionary d: 1) Ignore keys that begin with "@" - this is already JSON information 2) Add index numbers to lists 3) Recursively process values of type object 4) Convert any elements that are not one of "nodeRole", "index", "id" and "div" to objects 5) Merge :param d: dictionary to be processed :param resource_type: unedited resource type :param resource_type_set: set of all resource types in the model :param full_url: official ID of the resource from the containing fullURL :return: List of resourceTypes referenced by the resource """ def gen_reference(do: JsonObj) -> JsonObj: """ Return the object of a fhir:link based on the reference in d :param do: object containing the reference :return: link and optional type element """ # TODO: find the official regular expression for the type node. For the moment we make the (incorrect) # assumption that the type is everything that preceeds the first slash if "://" not in do.reference and not do.reference.startswith('/'): if hasattr(do, 'type'): typ = do.type else: typ = do.reference.split('/', 1)[0] link = '../' + do.reference else: link = do.reference typ = getattr(do, 'type', None) rval = JsonObj(**{"@id": link}) if typ: rval['@type'] = "fhir:" + typ return rval # Normalize all the elements in d. # We realize the keys as a list up front to prevent messing with our own updates # full_url is set if we're an embedded resource that has a full_url inner_type = getattr(d, 'resourceType', None) if inner_type: resource_type = inner_type if full_url: d['@id'] = full_url # If this has a resource type, it overrides what was passed in for k in list(as_dict(d).keys()): v = d[k] if k.startswith('@'): # Ignore JSON-LD components pass elif isinstance(v, JsonObj): # Inner object -- process recursively dict_processor(v, resource_type, resource_type_set, getattr(d, 'fullUrl', None)) elif isinstance(v, list): # Add ordering to the list d[k] = list_processor(k, v, resource_type_set) elif k == "id": # Internal ids are relative to the document if not full_url: d['@id'] = \ ('#' if not inner_type and not v.startswith('#') else (resource_type + '/')) + v d[k] = to_value(v) elif k == "reference": # Link to another document if not hasattr(d, 'link'): d["fhir:link"] = gen_reference(d) d[k] = to_value(v) elif k == "resourceType" and not (v.startswith('fhir:')): resource_type_set.add(v) d[k] = 'fhir:' + v elif k not in ["nodeRole", "index", "div"]: # Convert most other nodes to value entries d[k] = to_value(v) if k == 'coding': [add_type_arc(n) for n in v] # Merge any extensions (keys that start with '_') into the base for k in [k for k in as_dict(d).keys() if k.startswith('_')]: base_k = k[1:] if not hasattr(d, base_k) or not isinstance(d[base_k], JsonObj): d[base_k] = JsonObj() else: for kp, vp in as_dict(d[k]).items(): if kp in d[base_k]: print( f"Extension element {kp} is already in the base for {k}" ) else: d[base_k][kp] = vp del (d[k]) def list_processor(k: str, lo: List, rts: Set[str]) -> List[Any]: """ Process the elements in the supplied list adding indices and converting the interior nodes :param k: List key (for error reporting) :param lo: List to be processed :param rts: Set of referenced resource types. May be updated :return: Ordered list of entries """ def list_element(e: Any, pos: int) -> Any: """ Add a list index to list element "e" :param e: Element in a list :param pos: position of element :return: adjusted object """ if isinstance(e, JsonObj): dict_processor(e, resource_type, rts, getattr(e, 'fullUrl', None)) if getattr(e, 'index', None) is not None: print(f'Problem: "{k}" element {pos} already has an index') else: e.index = pos # Add positioning elif isinstance(e, list): print(f"Problem: {k} has a list in a list", file=sys.stderr) else: e = to_value(e) e.index = pos return e return [list_element(le, p) for p, le in enumerate(lo)] resource_type = parse_resource_type(o.resourceType) resource_type_set = {resource_type} dict_processor(o, resource_type, resource_type_set) # Add nodeRole o['nodeRole'] = "fhir:treeRoot" # Add the "ontology header" hdr = JsonObj() hdr["@id"] = o['@id'] + ".ttl" hdr["owl:versionIRI"] = hdr["@id"] hdr["owl:imports"] = "fhir:fhir.ttl" hdr["@type"] = 'owl:Ontology' # TODO: replace this with included once we get the bug fixed. o = JsonObj(**{"@graph": [deepcopy(o), hdr]}) # o["@included"] = hdr # Fill out the rest of the context if add_context: o['@context'] = [ f"{CONTEXT_SERVER}{rt.lower()}.context.jsonld" for rt in sorted(resource_type_set) ] o['@context'].append(f"{CONTEXT_SERVER}root.context.jsonld") local_context = JsonObj() local_context["nodeRole"] = JsonObj(**{ "@type": "@id", "@id": "fhir:nodeRole" }) if server: local_context["@base"] = server local_context['owl:imports'] = JsonObj(**{"@type": "@id"}) local_context['owl:versionIRI'] = JsonObj(**{"@type": "@id"}) o['@context'].append(local_context) return o
def type_or_ref(rng: str) -> JsonObj: # TODO jref return JsonObj(type="string")
def test_merge_contexts(self): self.assertIsNone(merge_contexts()) self.assertEqual('file://local.jsonld', merge_contexts("local.jsonld")['@context']) self.assertEqual('file://local.jsonld', merge_contexts(["local.jsonld"])['@context']) self.assertEqual(METAMODEL_CONTEXT_URI, merge_contexts(METAMODEL_CONTEXT_URI)['@context']) self.assertEqual(METAMODEL_CONTEXT_URI, merge_contexts([METAMODEL_CONTEXT_URI])['@context']) self.assertEqual(JsonObj(ex='http://example.org/test/', ex2='http://example.org/test2/'), merge_contexts(json_1)['@context']) self.assertEqual(JsonObj(ex='http://example.org/test/', ex2='http://example.org/test2/'), merge_contexts([json_1])['@context']) self.assertEqual(JsonObj(ex='http://example.org/test3/', ex2=JsonObj(**{'@id': 'http://example.org/test4/'})), merge_contexts(json_2)['@context']) self.assertEqual(JsonObj(ex='http://example.org/test3/', ex2=JsonObj(**{'@id': 'http://example.org/test4/'})), merge_contexts([json_2])['@context']) self.assertEqual([f'file://local.jsonld', 'https://w3id.org/linkml/meta.context.jsonld', JsonObj(ex='http://example.org/test/', ex2='http://example.org/test2/'), JsonObj(ex='http://example.org/test3/', ex2=JsonObj(**{'@id': 'http://example.org/test4/'}))], merge_contexts(["local.jsonld", METAMODEL_CONTEXT_URI, json_1, json_2])['@context']) self.assertEqual(loads(context_output), merge_contexts(["local.jsonld", METAMODEL_CONTEXT_URI, json_1, json_2])) # Dups are not removed self.assertEqual( JsonObj(**{'@context': [JsonObj(ex='http://example.org/test/', ex2='http://example.org/test2/'), JsonObj(ex='http://example.org/test/', ex2='http://example.org/test2/')]}), merge_contexts([json_1, json_1])) self.assertEqual('file://local.jsonld', merge_contexts("local.jsonld")['@context'])