Skip to content

Commit 3df9f47

Browse files
committed
Major refactor, some changes to UI
1 parent 635984d commit 3df9f47

File tree

3 files changed

+300
-169
lines changed

3 files changed

+300
-169
lines changed

src/sssom_validate_ui/app.py

Lines changed: 125 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -2,144 +2,97 @@
22

33
import logging
44
import sys
5-
from contextlib import contextmanager, redirect_stderr, redirect_stdout
6-
from importlib.metadata import PackageNotFoundError, version
75
from io import StringIO
86

97
import pandas as pd
8+
import requests
109
import streamlit as st
11-
from linkml.generators.pythongen import PythonGenerator
12-
from linkml.validators.jsonschemavalidator import JsonSchemaDataValidator
13-
from sssom.constants import SCHEMA_YAML, SchemaValidationType
14-
from sssom.parsers import parse_sssom_table, to_mapping_set_document
15-
from sssom.util import MappingSetDataFrame
16-
from sssom.validators import validate
17-
from tsvalid.tsvalid import validates
10+
11+
from sssom_validate_ui.utils import SSSOMValidation, generate_example, get_package_version
1812

1913
sys.tracebacklimit = 0
2014

2115

22-
def validate_linkml(msdf: MappingSetDataFrame):
23-
"""Validate the contents of the mapping set using the LinkML JSON Schema validator."""
24-
mod = PythonGenerator(SCHEMA_YAML).compile_module()
25-
validator = JsonSchemaDataValidator(schema=SCHEMA_YAML)
16+
def _maybe_prune_sssom_text(sssom_text, limit_lines_evaluated):
17+
sssom_length_within_limit = True
18+
if len(sssom_text.splitlines()) > limit_lines_evaluated:
19+
truncated_text = "\n".join(sssom_text.splitlines()[:limit_lines_evaluated])
20+
sssom_length_within_limit = False
21+
else:
22+
truncated_text = sssom_text
2623

27-
mapping_set = to_mapping_set_document(msdf).mapping_set
28-
result = validator.validate_object(mapping_set, target_class=mod.MappingSet)
29-
return result
24+
if not sssom_length_within_limit:
25+
logging.warning(
26+
f"Your file is too long, only the first {limit_lines_evaluated} lines will be evaluated."
27+
)
28+
return truncated_text
29+
30+
31+
def _get_sssom_text(sssom_text_str, sssom_url_str, limit_lines_evaluated):
32+
"""Get the SSSOM text from a string or URL. URL takes precedence.
33+
34+
Args:
35+
sssom_text_str (str): The SSSOM text.
36+
sssom_url_str (str): The SSSOM URL.
37+
limit_lines_evaluated (int): The maximum number of lines to evaluate.
38+
39+
Returns:
40+
StringIO: The SSSOM text as a StringIO object.
41+
42+
Raises:
43+
ValueError: If the denominator is zero.
44+
"""
45+
if sssom_text_str and sssom_url_str:
46+
logging.warning("Both SSSOM text and URL provided. URL will be used.")
47+
sssom_text = ""
48+
if sssom_text_str:
49+
sssom_text = sssom_text_str
50+
elif sssom_url_str:
51+
sssom_text = requests.get(sssom_url_str, timeout=60).text
52+
else:
53+
raise ValueError("No SSSOM text or URL provided.")
54+
return StringIO(_maybe_prune_sssom_text(sssom_text, limit_lines_evaluated))
3055

3156

32-
def validate_sssom(sssom_text_str, limit_lines_displayed=5):
57+
def _validate_sssom(sssom_text: str, limit_lines_displayed=5):
3358
"""Validate a mapping set using SSSOM and tsvalid validations."""
34-
# Capture logs for SSSOM validation
35-
sssom_validation_capture = StringIO()
36-
sssom_text = StringIO(sssom_text_str)
37-
sssom_json = {"mapping_set_id": "NONE"}
38-
sssom_rdf = "NONE"
3959
pd.set_option("future.no_silent_downcasting", True)
40-
with redirect_stdout(sssom_validation_capture), redirect_stderr(
41-
sssom_validation_capture
42-
), configure_logger(sssom_validation_capture):
43-
validation_types = [
44-
SchemaValidationType.JsonSchema,
45-
SchemaValidationType.PrefixMapCompleteness,
46-
SchemaValidationType.StrictCurieFormat,
47-
]
48-
msdf = parse_sssom_table(sssom_text)
49-
validate(msdf=msdf, validation_types=validation_types, fail_on_error=False)
50-
msdf_subset_for_display = MappingSetDataFrame(
51-
msdf.df.head(limit_lines_displayed), converter=msdf.converter, metadata=msdf.metadata
52-
)
53-
msdf_subset_for_display.clean_prefix_map()
54-
from sssom.writers import to_json, to_rdf_graph
55-
56-
sssom_json = to_json(msdf_subset_for_display)
57-
if msdf.metadata.get("extension_definitions"):
58-
logging.warning(
59-
"Extension definitions are not supported in RDF output yet.\n"
60-
"This means that we could test if your code can be translated to RDF.\n"
61-
"Follow https://github.com/linkml/linkml/issues/2445 for updates."
62-
)
63-
sssom_rdf = None
64-
else:
65-
sssom_rdf = (
66-
to_rdf_graph(msdf=msdf).serialize(format="turtle", encoding="utf-8").decode("utf-8")
67-
)
68-
sssom_markdown = msdf_subset_for_display.df.to_markdown(index=False)
69-
log_output = sssom_validation_capture.getvalue() or "No validation issues detected."
70-
sssom_ok = "No validation issues detected." in log_output
71-
72-
# Capture logs for tsvalid validation
73-
tsvalid_capture = StringIO()
74-
with redirect_stdout(tsvalid_capture), redirect_stderr(tsvalid_capture), configure_logger(
75-
tsvalid_capture
76-
):
77-
validates(sssom_text, comment="#", exceptions=[], summary=True, fail=False)
78-
79-
# Restore outputs and get results
80-
tsvalid_report = tsvalid_capture.getvalue() or "No validation issues detected."
81-
tsvalid_ok = "No validation issues detected." in tsvalid_report
82-
# Compile the validation report
83-
84-
report = ""
85-
86-
if sssom_ok and tsvalid_ok:
87-
report = "No validation issues detected."
60+
result = SSSOMValidation(sssom_text=sssom_text, limit_lines_displayed=limit_lines_displayed)
61+
result.run()
62+
return result
63+
64+
65+
def _render_serialisation_section(serialisation_text, serialisation_format, markdown_type):
66+
if serialisation_text:
67+
with st.expander(serialisation_format):
68+
rendering_text_rdf = f"""\n\n
69+
```{markdown_type}
70+
{serialisation_text}
71+
```"""
72+
st.markdown(rendering_text_rdf)
73+
else:
74+
st.markdown(f"{serialisation_format} rendering is not available for this file, see log.")
75+
76+
77+
def _render_validation_batch(valid: bool, key: str):
78+
if valid:
79+
badge_url = f"https://img.shields.io/badge/{key}-SUCCESSFUL-green?style=green"
8880
else:
89-
report = "Some problems where found with your file."
90-
if not sssom_ok:
91-
report += f"\n\n### SSSOM report\n\nFor more information see [SSSOM documentation](https://mapping-commons.github.io/sssom/linkml-index/)\n\n{log_output}"
92-
if not tsvalid_ok:
93-
report += f"\n\n### TSVALID report\n\nFor more information see [tsvalid documentation](https://ontodev.github.io/tsvalid/checks.html)\n\n{tsvalid_report}"
94-
95-
return report.strip(), sssom_json, sssom_rdf, sssom_markdown
96-
97-
98-
# Helper function for logging configuration
99-
@contextmanager
100-
def configure_logger(capture_stream):
101-
"""Configure logger to write to a stream."""
102-
logger = logging.getLogger()
103-
log_handler = logging.StreamHandler(capture_stream)
104-
log_handler.setLevel(logging.DEBUG)
105-
log_handler.setFormatter(logging.Formatter("**%(levelname)s**: %(message)s"))
106-
logger.addHandler(log_handler)
107-
try:
108-
yield
109-
finally:
110-
log_handler.flush() # Ensure everything is written to the stream
111-
logger.removeHandler(log_handler)
112-
113-
114-
def _get_package_version(package_name):
115-
try:
116-
return version(package_name)
117-
except PackageNotFoundError:
118-
return f"{package_name} is not installed."
119-
120-
121-
def add_example():
122-
"""Add an example to the input text area."""
123-
example = """# curie_map:
124-
# HP: http://purl.obolibrary.org/obo/HP_
125-
# MP: http://purl.obolibrary.org/obo/MP_
126-
# owl: http://www.w3.org/2002/07/owl#
127-
# rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns#
128-
# rdfs: http://www.w3.org/2000/01/rdf-schemas#
129-
# semapv: https://w3id.org/semapv/vocab/
130-
# skos: http://www.w3.org/2004/02/skos/core#
131-
# sssom: https://w3id.org/sssom/
132-
# license: https://creativecommons.org/publicdomain/zero/1.0/
133-
# mapping_provider: http://purl.obolibrary.org/obo/upheno.owl
134-
# mapping_set_id: https://w3id.org/sssom/mappings/27f85fe9-8a72-4e76-909b-7ba4244d9ede
135-
subject_id subject_label predicate_id object_id object_label mapping_justification
136-
HP:0000175 Cleft palate skos:exactMatch MP:0000111 cleft palate semapv:LexicalMatching
137-
HP:0000252 Microcephaly skos:exactMatch MP:0000433 microcephaly semapv:LexicalMatching
138-
HP:0000260 Wide anterior fontanel skos:exactMatch MP:0000085 large anterior fontanelle semapv:LexicalMatching
139-
HP:0000375 Abnormal cochlea morphology skos:exactMatch MP:0000031 abnormal cochlea morphology semapv:LexicalMatching
140-
HP:0000411 Protruding ear skos:exactMatch MP:0000021 prominent ears semapv:LexicalMatching
141-
HP:0000822 Hypertension skos:exactMatch MP:0000231 hypertension semapv:LexicalMatching"""
142-
return example
81+
badge_url = f"https://img.shields.io/badge/{key}-UNSUCCESSFUL-red?style=flat"
82+
83+
st.markdown(f"![Badge]({badge_url})")
84+
85+
86+
def _render_tool_information():
87+
tool_versions = f"""\n\n
88+
89+
### Validation info report
90+
91+
**sssom-py** version: {get_package_version("sssom")}\n
92+
**tsvalid** version: {get_package_version("tsvalid")}\n
93+
**linkml** version: {get_package_version("linkml")}\n
94+
"""
95+
st.markdown(tool_versions)
14396

14497

14598
limit_lines_evaluated = 1000
@@ -156,56 +109,59 @@ def add_example():
156109

157110
area_txt = "Paste your SSSOM mapping text here:"
158111

159-
result = add_example()
160-
sssom_text = st.text_area(area_txt, result, height=400, key="sssom_input")
161-
162-
sssom_length_within_limit = True
163-
if len(sssom_text.splitlines()) > limit_lines_evaluated:
164-
truncated_text = "\n".join(sssom_text.splitlines()[:limit_lines_evaluated])
165-
sssom_length_within_limit = False
112+
sssom_text = st.text_area(area_txt, generate_example(), height=400, key="sssom_input")
113+
example_url = ""
114+
sssom_url_input = st.text_input(
115+
"Paste a URL to your SSSOM file here.", example_url, key="sssom_input_url"
116+
)
166117

167118
if st.button("Validate"):
168-
if not sssom_length_within_limit:
169-
st.markdown(
170-
f"**Warning**: your file is too long, only the first {limit_lines_evaluated} lines will be evaluated."
171-
)
172-
173-
result, sssom_json, sssom_rdf, sssom_markdown = validate_sssom(
174-
sssom_text, limit_lines_displayed
175-
)
176-
177-
st.markdown(str(result).replace("\n", "\n\n"))
178-
179-
rendering_text = f"""\n\n
180-
### SSSOM Rendered\n\n"
181-
182-
This is how the first {limit_lines_displayed} lines of your SSSOM file look like when rendered in various formats.
183-
"""
184-
185-
st.markdown(sssom_markdown)
119+
sssom_text = _get_sssom_text(sssom_text, sssom_url_input, limit_lines_evaluated)
120+
result: SSSOMValidation = _validate_sssom(sssom_text, limit_lines_displayed)
186121

187-
if sssom_rdf:
188-
with st.expander("RDF"):
189-
rendering_text_rdf = f"""\n\n
190-
```turtle
191-
{sssom_rdf}
192-
```"""
193-
st.markdown(rendering_text_rdf)
122+
if result.is_valid():
123+
badge_url = "https://img.shields.io/badge/VALID-green"
194124
else:
195-
st.markdown(
196-
"Extension definitions are not supported in RDF output yet.\n"
197-
"Follow https://github.com/linkml/linkml/issues/2445 for updates."
198-
)
125+
badge_url = "https://img.shields.io/badge/INVALID-red"
199126

200-
with st.expander("JSON"):
201-
st.json(sssom_json)
127+
_render_validation_batch(result.is_valid(), "Validation status overall")
202128

203-
tool_versions = f"""\n\n
129+
st.header("tsvalid validation")
130+
_render_validation_batch(result.is_ok_tsvalid(), "tsvalid")
131+
st.markdown(
132+
"For more information see [tsvalid documentation](https://ontodev.github.io/tsvalid/checks.html)"
133+
)
134+
with st.expander("Report"):
135+
st.markdown(result.get_tsvalid_report())
204136

205-
### Validation info report
137+
st.header("SSSOM Schema validation")
138+
_render_validation_batch(result.is_ok_sssom_validation(), "SSSOM")
139+
st.markdown(
140+
"For more information see [SSSOM documentation](https://mapping-commons.github.io/sssom/linkml-index/)"
141+
)
142+
with st.expander("Report"):
143+
st.markdown(result.get_sssom_validation_report())
206144

207-
**sssom-py** version: {_get_package_version("sssom")}\n
208-
**tsvalid** version: {_get_package_version("tsvalid")}\n
209-
**linkml** version: {_get_package_version("linkml")}\n
210-
"""
211-
st.markdown(tool_versions)
145+
st.header("SSSOM Sample Conversions")
146+
_render_validation_batch(result.is_ok_sssom_conversion(), "SSSOM")
147+
st.markdown(
148+
"This is how the first {limit_lines_displayed} lines of your SSSOM file look like when rendered in various formats."
149+
)
150+
st.markdown(
151+
"For more information see [SSSOM documentation](https://mapping-commons.github.io/sssom/spec-formats/)"
152+
)
153+
st.markdown(result.sssom_markdown)
154+
with st.expander("Conversion report"):
155+
st.markdown(result.get_sssom_conversion_report())
156+
_render_serialisation_section(result.sssom_rdf, "RDF", "turtle")
157+
_render_serialisation_section(result.sssom_json, "JSON", "json")
158+
159+
st.header("Additional information")
160+
_render_tool_information()
161+
162+
st.header("Contact")
163+
st.image("src/sssom_validate_ui/resources/monarch.png", use_container_width=False, width=300)
164+
st.markdown("Presented by the [Monarch Initiative](https://monarchinitiative.org/)")
165+
st.markdown(
166+
"For feedback use our [issue tracker](https://github.com/mapping-commons/sssom-validate-ui)."
167+
)
42 KB
Loading

0 commit comments

Comments
 (0)