Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def convert(model: OssieOntology) -> OssieSpec:
requires=[f.raw_expr for f in ont.requires],
ontology=_convert_ontology_concepts(ont),
ontology_mappings=ontology_mappings,
prefixes=model.prefixes,
)


Expand Down Expand Up @@ -117,6 +118,7 @@ def _convert_concept(concept: Concept, rels: list[Relationship]) -> ConceptCompo
derived_by=[f.raw_expr for f in concept.derived_by],
requires=[f.raw_expr for f in concept.requires],
relationships=[_convert_relationship(rel) for rel in rels],
iri=concept.iri,
)


Expand All @@ -135,6 +137,7 @@ def _convert_relationship(rel: Relationship) -> SpecRelationship:
multiplicity=multiplicity, # type: ignore[arg-type]
derived_by=[f.raw_expr for f in rel.derived_by],
requires=[f.raw_expr for f in rel.requires],
iri=rel.iri,
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def convert(self, spec: OssieSpec) -> OssieOntology:
description=spec.description,
ai_context=spec.ai_context,
version=spec.version,
prefixes=spec.prefixes,
)

self._populate_ontology(ontology, spec)
Expand Down Expand Up @@ -131,6 +132,7 @@ def _populate_ontology(self, ontology: OntologyComponent, spec: OssieSpec) -> No
type=ConceptType.from_value(concept_spec.type),
description=concept_spec.description,
extends=extends,
iri=concept_spec.iri,
)
)

Expand Down Expand Up @@ -219,6 +221,7 @@ def _convert_relationship(
description=rel_spec.description,
verbalizes=list(rel_spec.verbalizes) if rel_spec.verbalizes else None,
multiplicity=multiplicity,
iri=rel_spec.iri,
)
ontology.add_relationship(relationship)

Expand Down
24 changes: 23 additions & 1 deletion converters/ontology/src/ossie_ontology/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class Concept:
_derived_by: list[Formula]
_requires: list[Formula]
_is_component: bool
_iri: str | None

def __init__(
self,
Expand All @@ -109,7 +110,8 @@ def __init__(
identify_by: dict[str, Relationship] | None = None,
derived_by: list[Formula] | None = None,
requires: list[Formula] | None = None,
is_component: bool = True
is_component: bool = True,
iri: str | None = None,
):
self._name = name
self._type = type
Expand All @@ -120,6 +122,7 @@ def __init__(
self._derived_by = derived_by if derived_by else []
self._requires = requires if requires else []
self._is_component = is_component
self._iri = iri

def add_require(self, require: Formula) -> None:
self._requires.append(require)
Expand All @@ -145,6 +148,10 @@ def type(self) -> ConceptType | None:
def description(self) -> str | None:
return self._description

@property
def iri(self) -> str | None:
return self._iri

@property
def is_builtin(self) -> bool:
return self._builtin
Expand Down Expand Up @@ -207,6 +214,7 @@ class Relationship:
_multiplicity: RelationshipMultiplicity | None
_derived_by: list[Formula]
_requires: list[Formula]
_iri: str | None

def __init__(
self,
Expand All @@ -216,6 +224,7 @@ def __init__(
description: str | None = None,
verbalizes: list[str] | None = None,
multiplicity: RelationshipMultiplicity | None = None,
iri: str | None = None,
):
self._name = name
self._container = container
Expand All @@ -228,6 +237,7 @@ def __init__(
self._verbalizations = parse_verbalizations(self, verbalizes)
self._derived_by = []
self._requires = []
self._iri = iri

@property
def name(self) -> str:
Expand All @@ -245,6 +255,10 @@ def container(self) -> Concept:
def description(self) -> str | None:
return self._description

@property
def iri(self) -> str | None:
return self._iri

@property
def signature(self) -> list[Concept]:
return [role.player for role in self._roles]
Expand Down Expand Up @@ -1016,6 +1030,7 @@ class OssieOntology:
_ontology: OntologyComponent
_ontology_mappings: list[OntologyMapping]
_ontology_mapping_index: dict[str, OntologyMapping]
_prefixes: dict[str, str]

def __init__(
self,
Expand All @@ -1024,6 +1039,7 @@ def __init__(
description: str | None = None,
ai_context: AiContext | None = None,
version: str | None = None,
prefixes: dict[str, str] | None = None,
):
self._name = name
self._description = description
Expand All @@ -1032,6 +1048,7 @@ def __init__(
self._ontology = ontology
self._ontology_mappings = []
self._ontology_mapping_index = {}
self._prefixes = dict(prefixes) if prefixes else {}

@property
def name(self) -> str:
Expand Down Expand Up @@ -1063,6 +1080,11 @@ def add_ontology_mapping(self, mapping: OntologyMapping) -> None:
def ontology_mappings(self) -> list[OntologyMapping]:
return list(self._ontology_mappings)

@property
def prefixes(self) -> dict[str, str]:
"""Namespace prefixes the document's QName `iri` values resolve against."""
return dict(self._prefixes)


# ---------------------------------------------------------------------------
# Verbalization parser (handles a list of verbalization patterns)
Expand Down
3 changes: 3 additions & 0 deletions converters/ontology/src/ossie_ontology/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class Relationship(OssieObject):
"""
name: str
description: str | None = None
iri: str | None = None
roles: list[Role] = Field(default_factory=list)
verbalizes: list[str] = Field(default_factory=list)
multiplicity: Literal["OneToOne", "ManyToOne"] | None = None
Expand Down Expand Up @@ -100,6 +101,7 @@ class ConceptComponent(OssieObject):
concept: str
type: Literal["EntityType", "ValueType"] | None = None
description: str | None = None
iri: str | None = None
extends: list[str] | None = None
identify_by: list[str] = Field(default_factory=list)
derived_by: list[str] = Field(default_factory=list)
Expand Down Expand Up @@ -243,6 +245,7 @@ class OssieSpec(OssieObject):
version: str | None = None
name: str
description: str | None = None
prefixes: dict[str, str] = Field(default_factory=dict)
requires: list[str] = Field(default_factory=list)
ai_context: AiContext | None = None
ontology: list[ConceptComponent] = Field(default_factory=list)
Expand Down
65 changes: 65 additions & 0 deletions converters/ontology/tests/test_ossie_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,3 +356,68 @@ def test_relationships_are_grouped_under_their_own_container():
"Alpha": ["a1", "a2"],
"Beta": ["b1", "b2"],
}


# ----- Global identifiers -----------------------------------------------

_IRI_SPEC = {
"version": "0.2.0.dev0",
"name": "Demo",
"prefixes": {"foaf": "http://xmlns.com/foaf/0.1/"},
"ontology": [
{
"concept": "Person",
"type": "EntityType",
"iri": "foaf:Person",
"identify_by": ["person_name"],
"relationships": [
{
"name": "person_name",
"iri": "foaf:name",
"roles": [{"concept": "String"}],
"verbalizes": ["{Person} is identified by {String}"],
"multiplicity": "OneToOne",
}
],
}
],
}


def test_iri_and_prefixes_survive_a_round_trip(tmp_path: Path):
"""A document that pins its concepts to an external vocabulary must come
back out carrying the same pins.

`iri` and `prefixes` are what tie an Ossie ontology to RDF/OWL vocabularies,
so dropping them silently turns a mapped ontology into an unmapped one.
"""
path = tmp_path / "spec.yaml"
path.write_text(yaml.safe_dump(_IRI_SPEC))

model = OssieParser().parse(path)

assert model.prefixes == {"foaf": "http://xmlns.com/foaf/0.1/"}
person = model.ontology.lookup_concept("Person")
assert person is not None
assert person.iri == "foaf:Person"
assert model.ontology.lookup_concept_relationship(person, "person_name").iri == "foaf:name"

roundtrip_path = tmp_path / "roundtrip.yaml"
roundtrip_path.write_text(OssieToSpecConverter.convert(model).dump_yaml())
reparsed = OssieParser().parse(roundtrip_path)

assert reparsed.prefixes == model.prefixes
reparsed_person = reparsed.ontology.lookup_concept("Person")
assert reparsed_person.iri == "foaf:Person"
assert reparsed.ontology.lookup_concept_relationship(reparsed_person, "person_name").iri == "foaf:name"


def test_a_document_without_iris_dumps_neither_field(tmp_path: Path):
"""Both fields are optional, so a document that declares neither must dump
exactly as it did before they existed."""
path = _write_spec(tmp_path, [_concept("Gadget")])

dumped = OssieToSpecConverter.convert(OssieParser().parse(path)).dump_yaml()

assert "iri:" not in dumped
assert "prefixes:" not in dumped
Loading