Skip to content

Commit

Permalink
Merge pull request #33 from linkml/fix-funowl-version
Browse files Browse the repository at this point in the history
fix funowl version
  • Loading branch information
cmungall authored Mar 21, 2023
2 parents 55784b8 + 816062d commit 38faf18
Show file tree
Hide file tree
Showing 12 changed files with 1,853 additions and 405 deletions.
65 changes: 24 additions & 41 deletions .github/workflows/pypi-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,28 @@ jobs:
name: Build and publish Python 🐍 distributions 📦 to PyPI
runs-on: ubuntu-latest

#----------------------------------------------
# check-out repo and set-up python
#----------------------------------------------
steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2.2.2
with:
python-version: 3.9

#----------------------------------------------
# install & configure poetry
#----------------------------------------------
- name: Install Poetry
uses: snok/install-poetry@v1.1.6
with:
virtualenvs-create: true
virtualenvs-in-project: true

#----------------------------------------------
# install dependencies
#----------------------------------------------
- name: Install dependencies
run: poetry install --no-interaction

#----------------------------------------------
# build dist
#----------------------------------------------
- name: Build source and wheel archives
run: |
poetry version $(git describe --tags --abbrev=0)
poetry build
#----------------------------------------------
# publish package to PyPI
#----------------------------------------------
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@v1.2.2
with:
user: __token__
password: ${{ secrets.PYPI_PASSWORD }}
- uses: actions/checkout@v3.1.0

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: 3.9

- name: Install Poetry
run: |
pip install poetry
poetry self add "poetry-dynamic-versioning[plugin]"
# - name: Install dependencies
# run: poetry install --no-interaction

- name: Build source and wheel archives
run: |
poetry build
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@v1.5.0
with:
user: __token__
password: ${{ secrets.pypi_password }}
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Minimally you need to specify two inputs:
To convert:

```bash
linkml-data2owl -s my_schema.yaml my_data.{yaml,json,tsv,rdf} -o my_ontology.owl.ttl
linkml-data2owl -s my_schema.yaml my_data.{yaml,json,tsv,rdf} -o my_ontology.ofn
```

For all options, see:
Expand Down
72 changes: 45 additions & 27 deletions linkml_owl/dumpers/owl_dumper.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from collections import defaultdict
from typing import Optional, List, Set, Any, Union, Dict, Tuple
from dataclasses import dataclass, field
Expand All @@ -15,7 +16,7 @@
from linkml_runtime.index.object_index import ObjectIndex
from linkml_runtime.utils.inference_utils import infer_all_slot_values, infer_slot_value, Config

from rdflib import URIRef
from rdflib import URIRef, Graph

from linkml_runtime.linkml_model.meta import ClassDefinition, SchemaDefinition, SlotDefinition, Definition, \
ClassDefinitionName
Expand Down Expand Up @@ -168,20 +169,26 @@ def to_ontology_document(self, element: Union[YAMLRoot, List[YAMLRoot]], schema:
doc.prefixDeclarations.append(Prefix(pfx.prefix_prefix, pfx.prefix_reference))
return doc

def dumps(self, element: YAMLRoot, schema: SchemaDefinition = None, schemaview: SchemaView = None, iri=None) -> str:
def dumps(self, element: YAMLRoot, schema: SchemaDefinition = None, schemaview: SchemaView = None, iri=None, output_type=None) -> str:
"""
Dump a linkml instance tree to a function syntax OWL ontology string
:param element:
:param schema:
:param schemaview:
:param iri:
:param output_type:
:return:
"""
if schemaview:
schema = schemaview.schema
doc = self.to_ontology_document(element, schema, iri=iri)
return str(doc)
if output_type == "ttl":
g = Graph()
doc.to_rdf(g)
return g.serialize(format="ttl")
else:
return str(doc)

def transform(self, element: YAMLRoot, schema: SchemaDefinition, is_element_an_object=True) -> Any:
"""
Expand Down Expand Up @@ -296,7 +303,7 @@ def transform(self, element: YAMLRoot, schema: SchemaDefinition, is_element_an_o
axiom_annotations.append(Annotation(ann_slot_iri, ann_val))
# templates
for tmpl in owl_templates:
self.add_axioms_from_template(tmpl, element)
self.add_axioms_from_template(tmpl, element, schema=schema)
if schema_level_slot.slot_uri is not None:
slot_uri = self._get_IRI_str(schema_level_slot.slot_uri)
else:
Expand Down Expand Up @@ -524,8 +531,9 @@ def _get_inferred_slot_annotations(self, slot: SlotDefinition, ann_key: str,
anc_slots = [slot]
sv = self.schemaview
for anc_c in sv.class_ancestors(class_name, reflexive=True):
induced_slot = sv.induced_slot(slot.name, anc_c)
anc_slots.append(induced_slot)
if slot.name in sv.class_slots(anc_c):
induced_slot = sv.induced_slot(slot.name, anc_c)
anc_slots.append(induced_slot)
for a in sv.slot_ancestors(slot.name, reflexive=True):
anc_slots.append(sv.get_slot(a))
for s in anc_slots:
Expand Down Expand Up @@ -634,20 +642,20 @@ def parse_axioms_string(self, owl_str: str, schemaview: SchemaView = None) -> On
for prefix, url in schemaview.namespaces().items():
prefix_lines.append(f'Prefix( {prefix}: = <{url}> )')
header = "\n".join(prefix_lines)
owl_str = f'{header}\nOntology(\n{owl_str}\n)'
owl_str = f'{header}\nOntology(<http://example.org>\n{owl_str}\n)'
logging.debug(owl_str)
try:
doc = to_python(owl_str)
except Exception as e:
logging.error(f'Error parsing generated OWL: {owl_str}')
raise e
from funowl.writers.FunctionalWriter import FunctionalWriter
from rdflib import Graph
g = Graph()
for p in doc.prefixDeclarations:
g.namespace_manager.bind(p.prefixName, p.fullIRI)
fw = FunctionalWriter(g=g)
owl_str_roundtrip = doc.to_functional(fw)
#from funowl.writers.FunctionalWriter import FunctionalWriter
#from rdflib import Graph
#g = Graph()
#for p in doc.prefixDeclarations:
# g.namespace_manager.bind(p.prefixName, p.fullIRI)
#fw = FunctionalWriter(g=g)
#owl_str_roundtrip = doc.to_functional(fw)
#logging.debug(f'ROUNDTRIP = {owl_str_roundtrip}')
return doc

Expand Down Expand Up @@ -686,6 +694,12 @@ def tr(e: YAMLRoot):
else:
d["tr"] = tr
jt = Template(tstr)

def _tr(x):
fw = FunctionalWriter()
expr = self.transform(x, schema)
return expr.to_functional(fw)
d["tr"] = _tr
owl_str = jt.render(**d)
axioms = self.parse_axioms_string(owl_str).ontology.axioms
self.ontology.axioms += axioms
Expand Down Expand Up @@ -724,34 +738,42 @@ def populate_missing_values(self, element: YAMLRoot):
help="Path to python datamodel module")
@click.option("--format", "-f",
help="Input format (will be inferred from file suffix if not specified)")
@click.option('-o', '--output', required=True,
@click.option('-o', '--output',
type=click.File(mode="w"),
default=sys.stdout,
help="Path to OWL functional syntax output")
@click.option('-O', '--output-type',
type=click.Choice(["ofn", "ttl"]),
help="Output format")
@click.option("--autofill/--no-autofill",
default=False,
show_default=True,
help="If True, fill missing data slots using string_serialization")
@click.argument('inputfile')
def cli(inputfile: str, schema: str, target_class, module, output, format, autofill: bool, verbose: int, quiet: bool, **args):
def cli(inputfile: str, schema: str, target_class, module, output, output_type, format, autofill: bool, verbose: int, quiet: bool, **args):
"""
Dump LinkML instance data as OWL
Examples:
Convert a CSV to OWL
linkml-data2owl -s tests/inputs/owl_dumper_test.yaml tests/inputs/parts.csv -o parts.ofn
linkml-data2owl -s owl_dumper_test.yaml parts.csv -o parts.ofn
Note in this example, there must be a class type designator column `@type` in the CSV
Note in this example, there must be a class type designator column `@type` in the CSV
Convert a CSV to OWL, homogeneous classes:
linkml-data2owl -C EquivGenusAndPartOf -s tests/inputs/owl_dumper_test.yaml \
tests/inputs/parts_implicit_type.csv -o parts.ofn
linkml-data2owl -C EquivGenusAndPartOf -s owl_dumper_test.yaml \
parts_implicit_type.csv -o parts.ofn
Convert YAML or JSON to OWL:
linkml-data2owl -s tests/inputs/owl_dumper_test.yaml tests/inputs/owl_dumper_test_data.yaml -o ont.ofn
linkml-data2owl -s owl_dumper_test.yaml owl_dumper_test_data.yaml -o ont.ofn
More documentation:
https://linkml.io/linkml-owl/
"""
logger = logging.getLogger()
if verbose >= 2:
Expand All @@ -775,12 +797,8 @@ def cli(inputfile: str, schema: str, target_class, module, output, format, autof
dumper = OWLDumper()
if autofill:
dumper.autofill = True
doc = dumper.dumps(element, schemaview=sv)
if output is None:
print(str(doc))
else:
with open(output, 'w') as stream:
stream.write(str(doc))
doc = dumper.dumps(element, schemaview=sv, output_type=output_type)
output.write(str(doc))


if __name__ == '__main__':
Expand Down
Loading

0 comments on commit 38faf18

Please sign in to comment.