From 0f900acdda4a9299eb7f56a0fd2e4b3b5fdf0710 Mon Sep 17 00:00:00 2001 From: cmungall Date: Mon, 13 Jun 2022 14:30:59 -0700 Subject: [PATCH] Initial pass at Relation Graph in rust/python using Crepe. Addresses first part of https://github.com/INCATools/semantic-sql/issues/41 --- .github/workflows/CI.yml | 66 ++++++ Cargo.lock | 411 +++++++++++++++++++++++++++++++++++ Cargo.toml | 15 ++ Makefile | 19 ++ README.md | 32 +++ environment.sh | 5 + pyproject.toml | 18 ++ relation_graph/__init__.py | 1 + relation_graph/runner.py | 35 +++ src/lib.rs | 99 +++++++++ tests/input/test.csv | 33 +++ tests/output/README.md | 1 + tests/test_relation_graph.py | 35 +++ 13 files changed, 770 insertions(+) create mode 100644 .github/workflows/CI.yml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 README.md create mode 100644 environment.sh create mode 100644 pyproject.toml create mode 100644 relation_graph/__init__.py create mode 100644 relation_graph/runner.py create mode 100644 src/lib.rs create mode 100644 tests/input/test.csv create mode 100644 tests/output/README.md create mode 100644 tests/test_relation_graph.py diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..e35652a --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,66 @@ +name: CI + +on: + push: + pull_request: + +jobs: + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: messense/maturin-action@v1 + with: + manylinux: auto + command: build + args: --release -o dist + - name: Upload wheels + uses: actions/upload-artifact@v2 + with: + name: wheels + path: dist + + windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - uses: messense/maturin-action@v1 + with: + command: build + args: --release --no-sdist -o dist + - name: Upload wheels + uses: actions/upload-artifact@v2 + with: + name: wheels + path: dist + + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + - uses: messense/maturin-action@v1 + with: + command: build + args: --release --no-sdist -o dist --universal2 + - name: Upload wheels + uses: actions/upload-artifact@v2 + with: + name: wheels + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags/')" + needs: [ macos, windows, linux ] + steps: + - uses: actions/download-artifact@v2 + with: + name: wheels + - name: Publish to PyPI + uses: messense/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --skip-existing * \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..269e650 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,411 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crepe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0c81f0055a7c877a9a69ec9d667a0b14c2b38394c712f54b9a400d035f49a9" +dependencies = [ + "petgraph", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "indexmap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "relation-graph" +version = "0.1.0" +dependencies = [ + "crepe", + "csv", + "pyo3", + "serde", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unindent" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fee519a3e570f7df377a06a1a7775cdbfb7aa460be7e08de2b1f0e69973a44" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bb3f335 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "relation-graph" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "relation_graph_impl" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.16.3", features = ["extension-module"] } +crepe="^0.1.6" +csv="*" +serde={ version = "1.0", features = ["derive"] } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..240fd71 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ + +test: dev + python -m unittest tests/test_*.py + +dev: + maturin develop + +release: + docker run --rm -v $(PWD):/io ghcr.io/pyo3/maturin build --release + +# TESTING + +DB = ../semantic-sql/db +tests/input/all-%.csv: $(DB)/%.db + sqlite3 -cmd ".headers on" -separator ',' $< "SELECT subject,predicate,object FROM edge UNION SELECT subject, predicate, object FROM statements WHERE predicate='rdf:type' AND subject NOT LIKE '_:%'" > $@ +.PRECIOUS: tests/input/all-%.csv + +tests/output/test-%.csv: tests/input/all-%.csv + (time python -m relation_graph.runner $< -o $@) >& $@.LOG diff --git a/README.md b/README.md new file mode 100644 index 0000000..83146d3 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +## Relation Graph, Python Port + +This is a minimal implementation of [relation-graph](https://github.com/balhoff/relation-graph/) in Python + +The goal is to reason over a minimal subset + +- subClassOf +- someValuesFrom +- transitive properties +- property chains + +To run: + +```python +run_relation_graph("tests/input/test.csv", "tests/output/results.csv") +``` + +- input must be csv +- headers must be subject, predicate, object +- domain entities can be any syntax, rdf/owl terms must be CURIEs +- rows are either: + - relation graph direct edges + - triples of the form `PRED,rdf:type,owl:TransitiveProperty` + +See tests/input for details + +## TODO + +- reflexive edges +- relax equivalence to paired subClassOf +- compare with scala relation-graph +- property chains diff --git a/environment.sh b/environment.sh new file mode 100644 index 0000000..07bf23c --- /dev/null +++ b/environment.sh @@ -0,0 +1,5 @@ +#!/bin/sh +python3 -m venv venv +source venv/bin/activate +export PYTHONPATH=.:$PYTHONPATH + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2599a51 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["maturin>=0.12,<0.13"] +build-backend = "maturin" + +[project] +name = "relation-graph" +version = "0.1.1" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + +dependencies = ["click"] + +[project.scripts] +pyrelgraph = "relation_graph.runner:main" diff --git a/relation_graph/__init__.py b/relation_graph/__init__.py new file mode 100644 index 0000000..f8d1188 --- /dev/null +++ b/relation_graph/__init__.py @@ -0,0 +1 @@ +from relation_graph.runner import run_relation_graph diff --git a/relation_graph/runner.py b/relation_graph/runner.py new file mode 100644 index 0000000..da6f1fe --- /dev/null +++ b/relation_graph/runner.py @@ -0,0 +1,35 @@ +import tempfile + +import click +import relation_graph_impl as impl + +def run_relation_graph(input_path: str, output_path: str = None) -> None: + """ + Runs relation-graph + + :param input_path: + :param output_path: + :return: + """ + if output_path is None: + tf = tempfile.NamedTemporaryFile(mode='r') + impl.run_relation_graph(input_path, tf.name) + for line in tf.readlines(): + print(line, end='') + else: + impl.run_relation_graph(input_path, output_path) + +@click.command() +@click.argument('input') +@click.option('-o', '--output', + required=False, + help="Path to entailed edges file") +def main(input, output): + """ + runs relation graph + """ + run_relation_graph(input, output) + + +if __name__ == '__main__': + main() diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..70ef0d3 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,99 @@ +use pyo3::prelude::*; + +use std::fs::File; +use std::io::{Write, Error, BufReader}; +use crepe::crepe; +use std::fmt; +use serde::Deserialize; + +const IS_A: &str = "rdfs:subClassOf"; +const SUB_PROPERTY_OF: &str = "rdfs:subPropertyOf"; +const TYPE: &str = "rdf:type"; +const TRANSITIVE_PROPERTY: &str = "owl:TransitiveProperty"; + +crepe! { + @input + struct Edge(&'static str, &'static str, &'static str); + struct Transitive(&'static str); + + @output + struct EntailedEdge(&'static str, &'static str, &'static str); + + EntailedEdge(x, p, y) <- Edge(x, p, y); + EntailedEdge(x, p, y) <- EntailedEdge(x, q, y), EntailedEdge(q, SUB_PROPERTY_OF, p); + EntailedEdge(x, p, z) <- EntailedEdge(x, p, y), EntailedEdge(y, p, z), Transitive(p); + EntailedEdge(x, p, z) <- EntailedEdge(x, p, y), EntailedEdge(y, IS_A, z); + EntailedEdge(x, p, z) <- EntailedEdge(x, IS_A, y), EntailedEdge(y, p, z); + Transitive(IS_A); + Transitive(SUB_PROPERTY_OF); + Transitive(p) <- Edge(p, TYPE, TRANSITIVE_PROPERTY); +} + +//#[derive(Debug)] +#[derive(Deserialize)] +struct Triple{ + subject: String, + predicate: String, + object: String, +} +struct Database<'a> { + triples: &'a mut Vec +} + +impl fmt::Display for Triple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} {} {}", self.subject, self.predicate, self.object) + } +} + +fn parse(database: &mut Database, path: String) -> Result<(), Error> { + let input = File::open(path)?; + let reader = BufReader::new(input); + let mut rdr = csv::Reader::from_reader(reader); + for triple in rdr.deserialize() { + let triple: Triple = triple?; + //println!("{}", triple); + database.triples.push(triple); + //println!("{}", database.triples.len()); + } + Ok(()) +} + +fn reason(triples: &'static Vec, output_path: String) -> Result<(), Error> { + let mut output = File::create(output_path)?; + let mut runtime = Crepe::new(); + //runtime.extend(&[Edge("1", IS_A, "2"), Edge("2", "is_a", "3"), Edge("3", "is_a", "4"), Edge("2", "is_a", "5")]); + for t in triples { + //println!("{}", t); + runtime.extend(&[Edge(&t.subject, &t.predicate, &t.object)]); + } + let (entailed_edges,) = runtime.run(); + for EntailedEdge(x, p, y) in entailed_edges { + writeln!(output, "{},{},{}", x, p, y)?; + } + Ok(()) +} + + +#[pyfunction] +fn run_relation_graph(path: String, output_path: String) -> PyResult<()> { + + unsafe { + static mut TRIPLES: Vec = Vec::new(); + let mut db: Database = Database{triples: &mut TRIPLES}; + if let Ok(_v) = parse(&mut db, path) { + if let Ok(_) = reason(&TRIPLES, output_path) { + () + } + } + } + Ok(()) +} + +/// A Python module implemented in Rust. +#[pymodule] +fn relation_graph_impl(_py: Python, m: &PyModule) -> PyResult<()> { + //m.add_function(wrap_pyfunction!(sum_as_string, m)?)?; + m.add_function(wrap_pyfunction!(run_relation_graph, m)?)?; + Ok(()) +} \ No newline at end of file diff --git a/tests/input/test.csv b/tests/input/test.csv new file mode 100644 index 0000000..c3b4ffd --- /dev/null +++ b/tests/input/test.csv @@ -0,0 +1,33 @@ +subject,predicate,object +PART_OF,rdf:type,owl:TransitiveProperty +PART_OF,rdfs:subPropertyOf,OVERLAPS +forelimb,rdfs:subClassOf,limb +hindlimb,rdfs:subClassOf,limb +limb,PART_OF,body +limb,rdfs:subClassOf,bodypart +hand,rdfs:subClassOf,autopod +hand,PART_OF,forelimb +foot,PART_OF,hindlimb +foot,rdfs:subClassOf,autopod +finger,PART_OF,hand +toe,PART_OF,foot +fingernail,PART_OF,finger +toenail,PART_OF,toe +finger,rdfs:subClassOf,digit +toe,rdfs:subClassOf,digit +autopod,rdfs:subClassOf,bodypart +digit,rdfs:subClassOf,bodypart +fingernail,rdfs:subClassOf,nail +toe,rdfs:subClassOf,nail +nail,rdfs:subClassOf,appendage +appendage,rdfs:subClassOf,anatomical +bodypart,rdfs:subClassOf,anatomical +anatomical,rdfs:subClassOf,continuant +continuant,rdfs:subClassOf,entity + + + + + + + diff --git a/tests/output/README.md b/tests/output/README.md new file mode 100644 index 0000000..a80679b --- /dev/null +++ b/tests/output/README.md @@ -0,0 +1 @@ +output for tests diff --git a/tests/test_relation_graph.py b/tests/test_relation_graph.py new file mode 100644 index 0000000..6b3da05 --- /dev/null +++ b/tests/test_relation_graph.py @@ -0,0 +1,35 @@ +import unittest +from relation_graph import run_relation_graph + +INPUT = "tests/input/test.csv" +OUTPUT = "tests/output/results.csv" + +FINGERNAIL = 'fingernail' +BODY = 'body' +HAND = 'hand' +FOOT = 'hand' +ENTITY = 'entity' +SUBCLASS_OF = 'rdfs:subClassOf' +PART_OF = 'PART_OF' +OVERLAPS = 'OVERLAPS' + +class RelationGraphTest(unittest.TestCase): + + def test_run_relation_graph(self): + run_relation_graph(INPUT, OUTPUT) + triples = [] + with open(OUTPUT, 'r') as file: + for line in file.readlines(): + [s, p, o] = line.strip().split(',') + triples.append((s, p, o)) + self.assertIn((FINGERNAIL, PART_OF, BODY), triples) + self.assertIn((FINGERNAIL, PART_OF, HAND), triples) + self.assertIn((HAND, PART_OF, BODY), triples) + self.assertIn((HAND, SUBCLASS_OF, ENTITY), triples) + self.assertIn((FINGERNAIL, SUBCLASS_OF, ENTITY), triples) + + + +if __name__ == '__main__': + unittest.main() +