Skip to content
Open
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
36 changes: 35 additions & 1 deletion converters/ontology/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@

# Ossie Ontology Converters

Converters between Ossie, Palantir, and Spec ontology formats.
Converters between Ossie, Palantir, LinkML, and Spec ontology formats.

| Converter | Direction |
|---------------------|-----------|
| `palantir_to_ossie` | Palantir ontology → Ossie model |
| `ossie_to_spec` | Ossie model → Spec YAML |
| `spec_to_ossie` | Spec YAML → Ossie model |
| `linkml_to_ossie` | LinkML schema → Ossie model |
| `ossie_to_linkml` | Ossie model → LinkML schema |

### LinkML

The LinkML converters use the natively-compiled [LinkML-Scala](https://github.com/NeverBlink-OSS/linkml-scala) library (see: [converter source code](https://github.com/NeverBlink-OSS/linkml-scala/tree/main/generator/src/eu/neverblink/linkml/generator/ossie)). This repository only provides wrappers – please file any issues [here](https://github.com/NeverBlink-OSS/linkml-scala/issues).

The Ossie <-> LinkML mapping and its limitations are documented [here](https://github.com/NeverBlink-OSS/linkml-scala/blob/main/docs/ossie_mapping.md). In general, LinkML supports only a subset of restriction expressions in Ossie, `derived_by` is not yet supported, and `ontology_mappings` are not representable in LinkML. Conversely, Ossie does not support many of the features of LinkML, such as all possible inheritance patterns. We are working to iteratively improve the coverage of the mapping in both directions.

## Prerequisites

Expand Down Expand Up @@ -62,6 +70,8 @@ The package is importable as `ossie_ontology` after installation:
from ossie_ontology.converter.palantir_to_ossie.converter import PalantirToOssieConverter
from ossie_ontology.converter.ossie_to_spec.converter import OssieToSpecConverter
from ossie_ontology.converter.spec_to_ossie.converter import SpecToOssieConverter
from ossie_ontology.converter.linkml_to_ossie.converter import LinkmlToOssieConverter
from ossie_ontology.converter.ossie_to_linkml.converter import OssieToLinkmlConverter
```

## Scripts
Expand Down Expand Up @@ -94,6 +104,30 @@ SNOWFLAKE_DATABASE_NAME=MY_DB SNOWFLAKE_SCHEMA_NAME=MY_SCHEMA \
uv run python scripts/palantir_to_ossie.py path/to/palantir_export.zip
```

### `scripts/ossie_to_linkml.py`

Converts an Ossie ontology (YAML or JSON) into the LinkML schema that describes it, printed to stdout.

**Usage:**

```bash
uv run python scripts/ossie_to_linkml.py path/to/ossie.yaml
# Second argument is optional, and if provided will be used as the schema's `id`:
uv run python scripts/ossie_to_linkml.py path/to/ossie.yaml https://example.org/my-schema
```

### `scripts/linkml_to_ossie.py`

Converts a LinkML schema into an Ossie-compliant YAML representation of the ontology it describes, printed to stdout. The schema's `imports` are resolved from disk.

**Usage:**

```bash
uv run python scripts/linkml_to_ossie.py path/to/schema.yaml
```

A schema can load and still have errors and warnings against it. Errors and warnings are written to stderr, only fatal problems stop the run.

## Running the tests

```bash
Expand Down
3 changes: 2 additions & 1 deletion converters/ontology/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ dev = [
[project]
name = "apache-ossie-ontology"
version = "0.1.0"
description = "Ossie ontology converters — Palantir → Ossie, Ossie → Spec, Spec → Ossie"
description = "Ossie ontology converters — Palantir → Ossie, Spec ↔ Ossie, LinkML ↔ Ossie"
authors = [{ name = "RelationalAI", email = "support@relational.ai" }]
requires-python = ">=3.11"
readme = "README.md"
Expand All @@ -39,6 +39,7 @@ keywords = [
"Ontology"
]
dependencies = [
"neverblink-linkml",
"pydantic",
"pyyaml",
]
Expand Down
61 changes: 61 additions & 0 deletions converters/ontology/scripts/linkml_to_ossie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.


# Description:
#
# This script converts a LinkML schema into an Ossie compliant YAML
# representation of the ontology it describes, printed to stdout. The schema's
# 'imports' are resolved from disk, exactly as the linkml-scala CLI resolves
# them.
#
# A schema can load and still have errors and warnings against it.
# Any issues the loader reports are written to stderr. Only fatal problems stop the run.
# The full mapping and all limitations are documented at
# https://github.com/NeverBlink-OSS/linkml-scala/blob/main/docs/ossie_mapping.md
#
# Usage:
#
# $ python linkml_to_ossie.py <path_to_linkml_schema>
#
# Outputs:
#
# - stdout: The Ossie ontology, as YAML
# - stderr: Schema issues reported while loading
#
import sys
from pathlib import Path

import linkml_scala

from ossie_ontology.converter.linkml_to_ossie.converter import LinkmlToOssieConverter

if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit(f"Usage: {sys.argv[0]} <path to LinkML schema (.yaml)>")

path = Path(sys.argv[1])

# Loaded here rather than through LinkmlToOssieConverter.convert_file so the
# schema's own issues can be reported before anything is converted.
with linkml_scala.load_file(path) as schema:
for issue in schema.issues():
print(f"{issue.get('severity')}: {issue.get('message')}", file=sys.stderr)

spec = LinkmlToOssieConverter().convert_to_spec(schema)

print(spec.dump_yaml())
55 changes: 55 additions & 0 deletions converters/ontology/scripts/ossie_to_linkml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.


# Description:
#
# This script converts an Ossie ontology into the LinkML schema that describes
# it, printed to stdout. The input is a single Ossie document, as YAML or JSON.
#
# Only the ontology crosses over: LinkML has nowhere to put an
# 'ontology_mappings' block, so datasets, join paths and metrics are dropped.
# The full mapping and all limitations are documented at
# https://github.com/NeverBlink-OSS/linkml-scala/blob/main/docs/ossie_mapping.md
#
# Usage:
#
# $ python ossie_to_linkml.py <path_to_ossie_document> [schema_id]
#
# An Ossie ontology carries no schema id of its own, so LinkML gets a
# placeholder built from the ontology's name unless one is given here.
#
# Outputs:
#
# - stdout: The LinkML schema, as YAML
#
import sys
from pathlib import Path

from ossie_ontology.converter.ossie_to_linkml.converter import OssieToLinkmlConverter
from ossie_ontology.parser import OssieParser

if __name__ == "__main__":
if len(sys.argv) not in (2, 3):
sys.exit(f"Usage: {sys.argv[0]} <path to Ossie document (.yaml or .json)> [schema id]")

path = Path(sys.argv[1])
schema_id = sys.argv[2] if len(sys.argv) == 3 else None

model = OssieParser().parse(path)

print(OssieToLinkmlConverter.convert(model, schema_id=schema_id))
4 changes: 4 additions & 0 deletions converters/ontology/src/ossie_ontology/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
from ossie_ontology.converter.spec_to_ossie.converter import SpecToOssieConverter
from ossie_ontology.converter.ossie_to_spec.converter import OssieToSpecConverter
from ossie_ontology.converter.palantir_to_ossie.converter import PalantirToOssieConverter
from ossie_ontology.converter.linkml_to_ossie.converter import LinkmlToOssieConverter
from ossie_ontology.converter.ossie_to_linkml.converter import OssieToLinkmlConverter

__all__ = [
# Model — ontology layer
Expand Down Expand Up @@ -88,4 +90,6 @@
"SpecToOssieConverter",
"OssieToSpecConverter",
"PalantirToOssieConverter",
"LinkmlToOssieConverter",
"OssieToLinkmlConverter",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

"""Converter from a LinkML schema to OssieOntology (runtime semantic model).

`linkml_scala` – the `neverblink-linkml` distribution – loads the schema and
emits an Ossie document from it. This module feeds that through the existing
spec -> model conversion, so the result is the same OssieOntology every other
converter in this package produces.

Pairs with ossie_to_linkml.OssieToLinkmlConverter for the other direction."""

from __future__ import annotations

from pathlib import Path
from typing import Mapping

import linkml_scala

from ossie_ontology.converter.spec_to_ossie.converter import SpecToOssieConverter
from ossie_ontology.model import FormulaFactory, MappingFormulaFactory, OssieOntology
from ossie_ontology.spec import OssieSpec


class LinkmlToOssieConverter:
"""Converts a LinkML schema to OssieOntology (runtime model).

Takes the same *formula_factory* / *mapping_formula_factory* as
SpecToOssieConverter, because it hands the spec -> model step to it.

model = LinkmlToOssieConverter().convert_file("model.yaml")
model = LinkmlToOssieConverter(formula_factory=my_parser).convert(schema)

`convert` takes an already-loaded `linkml_scala.Schema`; use it when you
want to run other generators over the same schema, since loading is the
expensive part. `convert_file` and `convert_text` load and release one for
you.
"""

def __init__(self, formula_factory: FormulaFactory | None = None,
mapping_formula_factory: MappingFormulaFactory | None = None):
self._formula_factory = formula_factory or FormulaFactory()
self._mapping_formula_factory = mapping_formula_factory or MappingFormulaFactory()

def convert(
self,
schema: linkml_scala.Schema,
pruning_mode: str = "skip",
tree_root: str | None = None,
metadata_language: str = "en",
) -> OssieOntology:
"""Convert a loaded schema.

*pruning_mode* selects which elements become concepts, and *tree_root*
names the class to prune from (only with `pruning_mode="treeRoot"`).
*metadata_language* picks the language of `description` fields in
schemas that carry translations.
"""
spec = self.convert_to_spec(
schema,
pruning_mode=pruning_mode,
tree_root=tree_root,
metadata_language=metadata_language,
)
return SpecToOssieConverter(
formula_factory=self._formula_factory,
mapping_formula_factory=self._mapping_formula_factory,
).convert(spec)

def convert_to_spec(
self,
schema: linkml_scala.Schema,
pruning_mode: str = "skip",
tree_root: str | None = None,
metadata_language: str = "en",
) -> OssieSpec:
"""Convert a loaded schema, stopping at the spec DTO.

The shorter path when the caller only wants to write the Ossie document
out rather than build the runtime model.
"""
return OssieSpec.load_yaml(
schema.ossie(
pruning_mode=pruning_mode,
tree_root=tree_root,
output_format="yaml",
metadata_language=metadata_language,
)
)

def convert_file(self, path: str | Path, **options) -> OssieOntology:
"""Load a schema from disk — resolving its `imports` from disk too —
and convert it. *options* are those of `convert`."""
with linkml_scala.load_file(path) as schema:
return self.convert(schema, **options)

def convert_text(
self,
schema_text: str,
imports: Mapping[str, str] | None = None,
**options,
) -> OssieOntology:
"""Convert a schema held in memory, resolving its `imports` against the
*imports* map of filename to YAML text. *options* are those of `convert`."""
with linkml_scala.load_string(schema_text, imports) as schema:
return self.convert(schema, **options)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
Loading