2
2
3
3
import logging
4
4
import sys
5
- from contextlib import contextmanager , redirect_stderr , redirect_stdout
6
- from importlib .metadata import PackageNotFoundError , version
7
5
from io import StringIO
8
6
9
7
import pandas as pd
8
+ import requests
10
9
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
18
12
19
13
sys .tracebacklimit = 0
20
14
21
15
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
26
23
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 ))
30
55
31
56
32
- def validate_sssom ( sssom_text_str , limit_lines_displayed = 5 ):
57
+ def _validate_sssom ( sssom_text : str , limit_lines_displayed = 5 ):
33
58
"""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"
39
59
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"
88
80
else :
89
- report = "Some problems where found with your file."
90
- if not sssom_ok :
91
- report += f"\n \n ### SSSOM report\n \n For 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 \n For 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"" )
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 )
143
96
144
97
145
98
limit_lines_evaluated = 1000
@@ -156,56 +109,59 @@ def add_example():
156
109
157
110
area_txt = "Paste your SSSOM mapping text here:"
158
111
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
+ )
166
117
167
118
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 )
186
121
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"
194
124
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"
199
126
200
- with st .expander ("JSON" ):
201
- st .json (sssom_json )
127
+ _render_validation_batch (result .is_valid (), "Validation status overall" )
202
128
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 ())
204
136
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 ())
206
144
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
+ )
0 commit comments