ose-app 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ose_app/AppModule.py +98 -0
- ose_app/OntologyDataStore.py +464 -0
- ose_app/PermissionManager.py +78 -0
- ose_app/SpreadsheetSearcher.py +197 -0
- ose_app/__init__.py +130 -0
- ose_app/app.py +10 -0
- ose_app/constants.py +1 -0
- ose_app/custom_json.py +23 -0
- ose_app/default_config.py +39 -0
- ose_app/gh.py +60 -0
- ose_app/guards/__init__.py +0 -0
- ose_app/guards/verify_login.py +20 -0
- ose_app/guards/with_permission.py +54 -0
- ose_app/jinja_extensions/__init__.py +42 -0
- ose_app/routes/__init__.py +24 -0
- ose_app/routes/admin.py +108 -0
- ose_app/routes/api/__init__.py +23 -0
- ose_app/routes/api/edit.py +162 -0
- ose_app/routes/api/external.py +198 -0
- ose_app/routes/api/plugins.py +22 -0
- ose_app/routes/api/release.py +380 -0
- ose_app/routes/api/repos.py +55 -0
- ose_app/routes/api/scripts.py +49 -0
- ose_app/routes/api/search.py +19 -0
- ose_app/routes/api/settings.py +115 -0
- ose_app/routes/api/validate.py +264 -0
- ose_app/routes/api/visualise.py +228 -0
- ose_app/routes/auth.py +44 -0
- ose_app/routes/edit.py +690 -0
- ose_app/routes/main.py +100 -0
- ose_app/routes/search.py +42 -0
- ose_app/routes/status.py +10 -0
- ose_app/routes/visualize.py +108 -0
- ose_app/static/addicto.release.json +129 -0
- ose_app/static/bcio.release.json +321 -0
- ose_app/static/example.json +134 -0
- ose_app/static/excel-bootstrap-table-filter-bundle.js +326 -0
- ose_app/static/excel-bootstrap-table-filter-bundle.js.map +1 -0
- ose_app/static/excel-bootstrap-table-filter-bundle.min.js +2 -0
- ose_app/static/excel-bootstrap-table-filter-bundle.min.js.map +1 -0
- ose_app/static/excel-bootstrap-table-filter-style.css +78 -0
- ose_app/static/gmho.release.json +139 -0
- ose_app/static/js/admin.js +2 -0
- ose_app/static/js/admin.js.map +1 -0
- ose_app/static/js/assets/CollapsibleCard.css +1 -0
- ose_app/static/js/assets/editor.css +1 -0
- ose_app/static/js/assets/release.css +1 -0
- ose_app/static/js/assets/settings.css +1 -0
- ose_app/static/js/assets/visualise.css +1 -0
- ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-CgGO-ddH-BDr_frBe.js +2 -0
- ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-CgGO-ddH-BDr_frBe.js.map +1 -0
- ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-CgGO-ddH-MonxH8xj.js +2 -0
- ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-CgGO-ddH-MonxH8xj.js.map +1 -0
- ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-DKAZyKKr-DTksKjtf.js +2 -0
- ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-DKAZyKKr-DTksKjtf.js.map +1 -0
- ose_app/static/js/chunks/CollapsibleCard-BQR8R54V.js +2 -0
- ose_app/static/js/chunks/CollapsibleCard-BQR8R54V.js.map +1 -0
- ose_app/static/js/chunks/CollapsibleCard-DgwkQKDg.js +2 -0
- ose_app/static/js/chunks/CollapsibleCard-DgwkQKDg.js.map +1 -0
- ose_app/static/js/chunks/CollapsibleCard-DoX0kPCZ.js +2 -0
- ose_app/static/js/chunks/CollapsibleCard-DoX0kPCZ.js.map +1 -0
- ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-CBzsmnqW.js +43 -0
- ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-CBzsmnqW.js.map +1 -0
- ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-EcINi797.js +43 -0
- ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-EcINi797.js.map +1 -0
- ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-bumDOBqD.js +43 -0
- ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-bumDOBqD.js.map +1 -0
- ose_app/static/js/chunks/_plugin-vue_export-helper-DlAUqK2U.js +2 -0
- ose_app/static/js/chunks/_plugin-vue_export-helper-DlAUqK2U.js.map +1 -0
- ose_app/static/js/chunks/bootbox-D3SJ_Fwv.js +2 -0
- ose_app/static/js/chunks/bootbox-D3SJ_Fwv.js.map +1 -0
- ose_app/static/js/chunks/constants-CzBHF1EN.js +2 -0
- ose_app/static/js/chunks/constants-CzBHF1EN.js.map +1 -0
- ose_app/static/js/chunks/constants-DL6Hx3V0.js +2 -0
- ose_app/static/js/chunks/constants-DL6Hx3V0.js.map +1 -0
- ose_app/static/js/chunks/constants-DPgwRV4l.js +2 -0
- ose_app/static/js/chunks/constants-DPgwRV4l.js.map +1 -0
- ose_app/static/js/chunks/filter-Bx-PU1OL.js +18 -0
- ose_app/static/js/chunks/filter-Bx-PU1OL.js.map +1 -0
- ose_app/static/js/chunks/filter-CdAXK93b.js +18 -0
- ose_app/static/js/chunks/filter-CdAXK93b.js.map +1 -0
- ose_app/static/js/chunks/filter-CiYqP1_r.js +2 -0
- ose_app/static/js/chunks/filter-CiYqP1_r.js.map +1 -0
- ose_app/static/js/chunks/index-sLCKmIWG-B5BYNF2N.js +2 -0
- ose_app/static/js/chunks/index-sLCKmIWG-B5BYNF2N.js.map +1 -0
- ose_app/static/js/chunks/index-sLCKmIWG-Cb9lQq-c.js +9 -0
- ose_app/static/js/chunks/index-sLCKmIWG-Cb9lQq-c.js.map +1 -0
- ose_app/static/js/chunks/index-sLCKmIWG-umQeu_-w.js +3 -0
- ose_app/static/js/chunks/index-sLCKmIWG-umQeu_-w.js.map +1 -0
- ose_app/static/js/editor.js +36 -0
- ose_app/static/js/editor.js.map +1 -0
- ose_app/static/js/release.js +6 -0
- ose_app/static/js/release.js.map +1 -0
- ose_app/static/js/settings.js +2 -0
- ose_app/static/js/settings.js.map +1 -0
- ose_app/static/js/visualise.js +3 -0
- ose_app/static/js/visualise.js.map +1 -0
- ose_app/static/main.css +1 -0
- ose_app/static/main.css.map +7 -0
- ose_app/static/main.scss +147 -0
- ose_app/static/mindmup-editabletable.js +131 -0
- ose_app/static/schema/release_script.json +215 -0
- ose_app/static/schema/req_body_edit.json +35 -0
- ose_app/static/schema/req_body_guess_parent.json +38 -0
- ose_app/static/schema/req_body_import.json +47 -0
- ose_app/static/schema/req_body_release_start.json +9 -0
- ose_app/static/schema/req_body_visualise.json +18 -0
- ose_app/static/test.dot +190 -0
- ose_app/static/workflows/externals.yaml.jinja2 +44 -0
- ose_app/templates/access_show_visualisation_for_ids.html +120 -0
- ose_app/templates/admin/dashboard.html +18 -0
- ose_app/templates/admin/rebuild_index.html +85 -0
- ose_app/templates/admin/release.html +46 -0
- ose_app/templates/admin/settings.html +23 -0
- ose_app/templates/base.html +151 -0
- ose_app/templates/edit.html +21 -0
- ose_app/templates/edit_external.html +491 -0
- ose_app/templates/forbidden.html +9 -0
- ose_app/templates/index.html +33 -0
- ose_app/templates/loggedout.html +21 -0
- ose_app/templates/repo.html +294 -0
- ose_app/templates/testgeneratedbyapi.html +294 -0
- ose_app/templates/visualise.html +17 -0
- ose_app-0.2.5.dist-info/METADATA +27 -0
- ose_app-0.2.5.dist-info/RECORD +127 -0
- ose_app-0.2.5.dist-info/WHEEL +5 -0
- ose_app-0.2.5.dist-info/top_level.txt +1 -0
ose_app/AppModule.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
from flask import Flask
|
|
4
|
+
from flask_executor import Executor
|
|
5
|
+
from flask_github import GitHub
|
|
6
|
+
from flask_injector import request
|
|
7
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
8
|
+
from injector import Injector, Module, provider, singleton
|
|
9
|
+
|
|
10
|
+
from ose.services.PluginService import PluginService
|
|
11
|
+
|
|
12
|
+
import ose.database as database
|
|
13
|
+
|
|
14
|
+
from . import gh
|
|
15
|
+
from .OntologyDataStore import OntologyDataStore
|
|
16
|
+
from .PermissionManager import PermissionManager
|
|
17
|
+
from .SpreadsheetSearcher import SpreadsheetSearcher
|
|
18
|
+
from ose.services.ConfigurationService import ConfigurationService
|
|
19
|
+
from ose.services.FileCache import FileCache
|
|
20
|
+
from ose.services.LocalConfigurationService import LocalConfigurationService
|
|
21
|
+
from ose.services.OntoloyBuildService import OntologyBuildService
|
|
22
|
+
from ose.services.RepositoryConfigurationService import RepositoryConfigurationService
|
|
23
|
+
from ose.services.RobotOntologyBuildService import RobotOntologyBuildService
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AppModule(Module):
|
|
27
|
+
"""Configure the application."""
|
|
28
|
+
|
|
29
|
+
def init_app(self, app: Flask):
|
|
30
|
+
database.init_app(app)
|
|
31
|
+
gh.init_app(app)
|
|
32
|
+
|
|
33
|
+
@provider
|
|
34
|
+
@request
|
|
35
|
+
def db(self) -> SQLAlchemy:
|
|
36
|
+
# We configure the DB here, explicitly, as Flask-SQLAlchemy requires
|
|
37
|
+
# the DB to be configured before request handlers are called.
|
|
38
|
+
return database.db
|
|
39
|
+
|
|
40
|
+
@provider
|
|
41
|
+
@request
|
|
42
|
+
def github(self) -> GitHub:
|
|
43
|
+
return gh.github
|
|
44
|
+
|
|
45
|
+
@provider
|
|
46
|
+
@singleton
|
|
47
|
+
def ontodb(self, config: ConfigurationService) -> OntologyDataStore:
|
|
48
|
+
return OntologyDataStore(config)
|
|
49
|
+
|
|
50
|
+
@provider
|
|
51
|
+
@singleton
|
|
52
|
+
def searcher(self, github: GitHub, config: ConfigurationService) -> SpreadsheetSearcher:
|
|
53
|
+
return SpreadsheetSearcher(config, github)
|
|
54
|
+
|
|
55
|
+
@provider
|
|
56
|
+
@singleton
|
|
57
|
+
def configuration_service(self, app: Flask, github: GitHub) -> ConfigurationService:
|
|
58
|
+
configuration_service = app.config.get("REPOSITORIES_SOURCE", "local")
|
|
59
|
+
|
|
60
|
+
service = {
|
|
61
|
+
"local": LocalConfigurationService,
|
|
62
|
+
"repository": RepositoryConfigurationService
|
|
63
|
+
}.get(configuration_service, LocalConfigurationService)
|
|
64
|
+
|
|
65
|
+
return service(app.config, github)
|
|
66
|
+
|
|
67
|
+
@provider
|
|
68
|
+
@singleton
|
|
69
|
+
def executor(self, app: Flask) -> Executor:
|
|
70
|
+
return Executor(app)
|
|
71
|
+
|
|
72
|
+
@provider
|
|
73
|
+
@singleton
|
|
74
|
+
def ontology_builder(self) -> OntologyBuildService:
|
|
75
|
+
return RobotOntologyBuildService()
|
|
76
|
+
|
|
77
|
+
@provider
|
|
78
|
+
@singleton
|
|
79
|
+
def permission_manager(self, app: Flask) -> PermissionManager:
|
|
80
|
+
return PermissionManager(app.config)
|
|
81
|
+
|
|
82
|
+
@provider
|
|
83
|
+
@singleton
|
|
84
|
+
def file_cache(self, config: ConfigurationService) -> FileCache:
|
|
85
|
+
life_time: typing.Union[int, None] = config.app_config.get("CACHE_LIFETIME", None)
|
|
86
|
+
cache_dir = config.app_config.get("CACHE_DIR")
|
|
87
|
+
|
|
88
|
+
if life_time is not None:
|
|
89
|
+
return FileCache(cache_dir, life_time)
|
|
90
|
+
else:
|
|
91
|
+
return FileCache(cache_dir)
|
|
92
|
+
|
|
93
|
+
@provider
|
|
94
|
+
@singleton
|
|
95
|
+
def plugin_service(self, config: ConfigurationService, injector: Injector) -> PluginService:
|
|
96
|
+
plugin_service = PluginService(config, injector)
|
|
97
|
+
plugin_service.load_plugins()
|
|
98
|
+
return plugin_service
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import re
|
|
3
|
+
from datetime import date
|
|
4
|
+
from urllib.error import HTTPError
|
|
5
|
+
from urllib.request import urlopen
|
|
6
|
+
|
|
7
|
+
import networkx
|
|
8
|
+
import pyhornedowl
|
|
9
|
+
from pyhornedowl.model import SubClassOf, ObjectSomeValuesFrom, ObjectProperty, Class
|
|
10
|
+
|
|
11
|
+
from ose_app import constants
|
|
12
|
+
from ose.services.ConfigurationService import ConfigurationService
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class OntologyDataStore:
|
|
16
|
+
_logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
config: ConfigurationService
|
|
19
|
+
node_props = {"shape": "box", "style": "rounded", "font": "helvetica"}
|
|
20
|
+
rel_cols = {"has part": "blue", "part of": "blue", "contains": "green",
|
|
21
|
+
"has role": "darkgreen", "is about": "darkgrey",
|
|
22
|
+
"has participant": "darkblue"}
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: ConfigurationService):
|
|
25
|
+
self.releases = {}
|
|
26
|
+
self.releasedates = {}
|
|
27
|
+
self.label_to_id = {}
|
|
28
|
+
self.graphs = {}
|
|
29
|
+
self.config = config
|
|
30
|
+
|
|
31
|
+
def parseRelease(self, repo):
|
|
32
|
+
config = self.config.get(repo)
|
|
33
|
+
# Keep track of when you parsed this release
|
|
34
|
+
self.graphs[repo] = networkx.MultiDiGraph()
|
|
35
|
+
self.releasedates[repo] = date.today()
|
|
36
|
+
# print("Release date ",self.releasedates[repo])
|
|
37
|
+
|
|
38
|
+
# Get the ontology from the repository
|
|
39
|
+
ontofilename = config.release_file
|
|
40
|
+
full_repo_name = config.full_name
|
|
41
|
+
branch = config.main_branch
|
|
42
|
+
location = f"https://raw.githubusercontent.com/{full_repo_name}/{branch}/{ontofilename}"
|
|
43
|
+
try:
|
|
44
|
+
self._logger.debug("Fetching release file from", location)
|
|
45
|
+
data = urlopen(location).read() # bytes
|
|
46
|
+
ontofile = data.decode('utf-8')
|
|
47
|
+
except HTTPError as e:
|
|
48
|
+
self.releases[repo] = pyhornedowl.PyIndexedOntology()
|
|
49
|
+
self._logger.error(f"Failed to fetch release file from {location}: {e}")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Parse it
|
|
53
|
+
if ontofile:
|
|
54
|
+
self.releases[repo] = pyhornedowl.open_ontology(ontofile)
|
|
55
|
+
prefixes = config.prefixes
|
|
56
|
+
for prefix in prefixes.items():
|
|
57
|
+
self.releases[repo].prefix_mapping.add_prefix(prefix[0], prefix[1])
|
|
58
|
+
for classIri in self.releases[repo].get_classes():
|
|
59
|
+
classId = self.releases[repo].get_id_for_iri(classIri)
|
|
60
|
+
if classId:
|
|
61
|
+
classId = classId.replace(":", "_")
|
|
62
|
+
# is it already in the graph?
|
|
63
|
+
if classId not in self.graphs[repo].nodes:
|
|
64
|
+
label = self.releases[repo].get_annotation(classIri, constants.RDFS_LABEL)
|
|
65
|
+
if label:
|
|
66
|
+
self.label_to_id[label.strip()] = classId
|
|
67
|
+
self.graphs[repo].add_node(classId,
|
|
68
|
+
label=label.strip().replace(" ", "\n"),
|
|
69
|
+
**OntologyDataStore.node_props)
|
|
70
|
+
else:
|
|
71
|
+
print("Could not determine label for IRI", classIri)
|
|
72
|
+
else:
|
|
73
|
+
print("Could not determine ID for IRI", classIri)
|
|
74
|
+
for classIri in self.releases[repo].get_classes():
|
|
75
|
+
classId = self.releases[repo].get_id_for_iri(classIri)
|
|
76
|
+
if classId:
|
|
77
|
+
parents = self.releases[repo].get_superclasses(classIri)
|
|
78
|
+
for p in parents:
|
|
79
|
+
plabel = self.releases[repo].get_annotation(p, constants.RDFS_LABEL)
|
|
80
|
+
if plabel and plabel.strip() in self.label_to_id:
|
|
81
|
+
self.graphs[repo].add_edge(self.label_to_id[plabel.strip()],
|
|
82
|
+
classId.replace(":", "_"), dir="back")
|
|
83
|
+
axioms = self.releases[repo].get_axioms_for_iri(classIri) # other relationships
|
|
84
|
+
for a in axioms:
|
|
85
|
+
if isinstance(a.component, SubClassOf) and \
|
|
86
|
+
isinstance(a.component.sup, ObjectSomeValuesFrom) and \
|
|
87
|
+
isinstance(a.component.sup.ope, ObjectProperty) and \
|
|
88
|
+
isinstance(a.component.sup.bce, Class):
|
|
89
|
+
relIri = str(a.component.sup.ope.first)
|
|
90
|
+
targetIri = str(a.component.sup.bce.first)
|
|
91
|
+
rel_name = self.releases[repo].get_annotation(relIri, constants.RDFS_LABEL)
|
|
92
|
+
targetLabel = self.releases[repo].get_annotation(targetIri, constants.RDFS_LABEL)
|
|
93
|
+
if targetLabel and targetLabel.strip() in self.label_to_id:
|
|
94
|
+
if rel_name in OntologyDataStore.rel_cols:
|
|
95
|
+
rcolour = OntologyDataStore.rel_cols[rel_name]
|
|
96
|
+
else:
|
|
97
|
+
rcolour = "orange"
|
|
98
|
+
self.graphs[repo].add_edge(classId.replace(":", "_"),
|
|
99
|
+
self.label_to_id[targetLabel.strip()],
|
|
100
|
+
color=rcolour,
|
|
101
|
+
label=rel_name)
|
|
102
|
+
|
|
103
|
+
def getReleaseLabels(self, repo):
|
|
104
|
+
all_labels = set()
|
|
105
|
+
for classIri in self.releases[repo].get_classes():
|
|
106
|
+
all_labels.add(self.releases[repo].get_annotation(classIri, constants.RDFS_LABEL))
|
|
107
|
+
return all_labels
|
|
108
|
+
|
|
109
|
+
def parseSheetData(self, repo, data):
|
|
110
|
+
for entry in data:
|
|
111
|
+
if entry.get('ID', None) is not None and \
|
|
112
|
+
entry.get('Label') is not None and \
|
|
113
|
+
'Definition' in entry and \
|
|
114
|
+
'Parent' in entry and \
|
|
115
|
+
len(entry['ID']) > 0:
|
|
116
|
+
entryId = entry['ID'].replace(":", "_")
|
|
117
|
+
entryLabel = entry['Label'].strip()
|
|
118
|
+
self.label_to_id[entryLabel] = entryId
|
|
119
|
+
self.graphs[repo].add_node(entryId, label=entryLabel.replace(" ", "\n"), **OntologyDataStore.node_props)
|
|
120
|
+
if entryId in self.graphs[repo].nodes:
|
|
121
|
+
self.graphs[repo].remove_node(entryId)
|
|
122
|
+
self.graphs[repo].add_node(entryId, label=entryLabel.replace(" ", "\n"),
|
|
123
|
+
**OntologyDataStore.node_props)
|
|
124
|
+
for entry in data:
|
|
125
|
+
if entry.get('ID', None) is not None and \
|
|
126
|
+
entry.get('Label') is not None and \
|
|
127
|
+
'Definition' in entry and \
|
|
128
|
+
'Parent' in entry and \
|
|
129
|
+
len(entry['ID']) > 0:
|
|
130
|
+
entryParent = re.sub(r"[\[].*?[\]]", "", str(entry['Parent'])).strip()
|
|
131
|
+
if entryParent in self.label_to_id: # Subclass relations
|
|
132
|
+
# Subclass relations must be reversed for layout
|
|
133
|
+
self.graphs[repo].add_edge(self.label_to_id[entryParent],
|
|
134
|
+
entry['ID'].replace(":", "_"), dir="back")
|
|
135
|
+
for header in entry.keys(): # Other relations
|
|
136
|
+
if entry[header] and str(entry[header]).strip() and "REL" in header:
|
|
137
|
+
# Get the rel name
|
|
138
|
+
rel_names = re.findall(r"'([^']+)'", header)
|
|
139
|
+
if len(rel_names) > 0:
|
|
140
|
+
rel_name = rel_names[0]
|
|
141
|
+
if rel_name in OntologyDataStore.rel_cols:
|
|
142
|
+
rcolour = OntologyDataStore.rel_cols[rel_name]
|
|
143
|
+
else:
|
|
144
|
+
rcolour = "orange"
|
|
145
|
+
|
|
146
|
+
relValues = entry[header].split(";")
|
|
147
|
+
for relValue in relValues:
|
|
148
|
+
if relValue.strip() in self.label_to_id:
|
|
149
|
+
self.graphs[repo].add_edge(entry['ID'].replace(":", "_"),
|
|
150
|
+
self.label_to_id[relValue.strip()],
|
|
151
|
+
color=rcolour,
|
|
152
|
+
label=rel_name)
|
|
153
|
+
|
|
154
|
+
# re-factored the following:
|
|
155
|
+
# *new : getIDsFromSheetMultiSelect
|
|
156
|
+
# getIDsFromSheet - related ID's from whole sheet
|
|
157
|
+
# getIDsFromSelection - related ID's from selection in sheet
|
|
158
|
+
# getRelatedIds - related ID's from list of ID's
|
|
159
|
+
|
|
160
|
+
# *new: getIDSForSheetGraphMultiSelect
|
|
161
|
+
# getDotForSheetGraph - graph from whole sheet
|
|
162
|
+
# getDotForSelection - graph from selection in sheet
|
|
163
|
+
# getDotForIDs - graph from ID list
|
|
164
|
+
|
|
165
|
+
def getIDsFromSheetMultiSelect(self, repo, data, filter):
|
|
166
|
+
ids = []
|
|
167
|
+
for entry in data:
|
|
168
|
+
if 'Curation status' in entry and str(entry['Curation status']) == "Obsolete":
|
|
169
|
+
print("Obsolete: ", entry)
|
|
170
|
+
else:
|
|
171
|
+
if filter != [""] and filter != []:
|
|
172
|
+
for f in filter:
|
|
173
|
+
if str(entry['Curation status']) == f:
|
|
174
|
+
if 'ID' in entry and len(entry['ID']) > 0:
|
|
175
|
+
ids.append(entry['ID'].replace(":", "_"))
|
|
176
|
+
if 'Parent' in entry:
|
|
177
|
+
entryParent = re.sub(r"[\[].*?[\]]", "", str(entry['Parent'])).strip()
|
|
178
|
+
if entryParent in self.label_to_id:
|
|
179
|
+
ids.append(self.label_to_id[entryParent])
|
|
180
|
+
if ":" in entry['ID'] or "_" in entry['ID']:
|
|
181
|
+
entryIri = self.releases[repo].get_iri_for_id(entry['ID'].replace("_", ":"))
|
|
182
|
+
if entryIri:
|
|
183
|
+
descs = pyhornedowl.get_descendants(self.releases[repo], entryIri)
|
|
184
|
+
for d in descs:
|
|
185
|
+
ids.append(self.releases[repo].get_id_for_iri(d).replace(":", "_"))
|
|
186
|
+
if self.graphs[repo]:
|
|
187
|
+
graph_descs = None
|
|
188
|
+
try:
|
|
189
|
+
graph_descs = networkx.algorithms.dag.descendants(self.graphs[repo],
|
|
190
|
+
entry['ID'].replace(":", "_"))
|
|
191
|
+
except networkx.exception.NetworkXError:
|
|
192
|
+
print("NetworkXError sheet multiselect: ", entry['ID'])
|
|
193
|
+
|
|
194
|
+
if graph_descs is not None:
|
|
195
|
+
for g in graph_descs:
|
|
196
|
+
if g not in ids:
|
|
197
|
+
ids.append(g)
|
|
198
|
+
|
|
199
|
+
return ids
|
|
200
|
+
|
|
201
|
+
def getIDsFromSheet(self, repo, data, filter):
|
|
202
|
+
# list of ids from sheetExternal
|
|
203
|
+
ids = []
|
|
204
|
+
for entry in data:
|
|
205
|
+
if 'Curation status' in entry and str(entry['Curation status']) == "Obsolete":
|
|
206
|
+
print("Obsolete: ", entry)
|
|
207
|
+
else:
|
|
208
|
+
if filter != "":
|
|
209
|
+
if str(entry['Curation status']) == filter:
|
|
210
|
+
if 'ID' in entry and len(entry['ID']) > 0:
|
|
211
|
+
ids.append(entry['ID'].replace(":", "_"))
|
|
212
|
+
|
|
213
|
+
if 'Parent' in entry:
|
|
214
|
+
entryParent = re.sub(r"[\[].*?[\]]", "", str(entry['Parent'])).strip()
|
|
215
|
+
if entryParent in self.label_to_id:
|
|
216
|
+
ids.append(self.label_to_id[entryParent])
|
|
217
|
+
|
|
218
|
+
if ":" in entry['ID'] or "_" in entry['ID']:
|
|
219
|
+
entryIri = self.releases[repo].get_iri_for_id(entry['ID'].replace("_", ":"))
|
|
220
|
+
if entryIri:
|
|
221
|
+
descs = pyhornedowl.get_descendants(self.releases[repo], entryIri)
|
|
222
|
+
for d in descs:
|
|
223
|
+
ids.append(self.releases[repo].get_id_for_iri(d).replace(":", "_"))
|
|
224
|
+
if self.graphs[repo]:
|
|
225
|
+
graph_descs = None
|
|
226
|
+
try:
|
|
227
|
+
graph_descs = networkx.algorithms.dag.descendants(self.graphs[repo],
|
|
228
|
+
entry['ID'].replace(":", "_"))
|
|
229
|
+
except networkx.exception.NetworkXError:
|
|
230
|
+
print("NetworkXError sheet filter: ", entry['ID'])
|
|
231
|
+
|
|
232
|
+
if graph_descs is not None:
|
|
233
|
+
for g in graph_descs:
|
|
234
|
+
if g not in ids:
|
|
235
|
+
ids.append(g)
|
|
236
|
+
else:
|
|
237
|
+
if 'ID' in entry and len(entry['ID']) > 0:
|
|
238
|
+
ids.append(entry['ID'].replace(":", "_"))
|
|
239
|
+
|
|
240
|
+
if 'Parent' in entry:
|
|
241
|
+
entryParent = re.sub(r"[\[].*?[\]]", "", str(entry['Parent'])).strip()
|
|
242
|
+
print("found entryParent: ", entryParent)
|
|
243
|
+
if entryParent in self.label_to_id:
|
|
244
|
+
ids.append(self.label_to_id[entryParent])
|
|
245
|
+
if ":" in entry['ID'] or "_" in entry['ID']:
|
|
246
|
+
entryIri = self.releases[repo].get_iri_for_id(entry['ID'].replace("_", ":"))
|
|
247
|
+
if entryIri:
|
|
248
|
+
descs = pyhornedowl.get_descendants(self.releases[repo], entryIri)
|
|
249
|
+
for d in descs:
|
|
250
|
+
ids.append(self.releases[repo].get_id_for_iri(d).replace(":", "_"))
|
|
251
|
+
if self.graphs[repo]:
|
|
252
|
+
graph_descs = None
|
|
253
|
+
try:
|
|
254
|
+
graph_descs = networkx.algorithms.dag.descendants(self.graphs[repo],
|
|
255
|
+
entry['ID'].replace(":", "_"))
|
|
256
|
+
except networkx.exception.NetworkXError:
|
|
257
|
+
print("NetworkXError Sheet: ", entry['ID'])
|
|
258
|
+
|
|
259
|
+
if graph_descs is not None:
|
|
260
|
+
for g in graph_descs:
|
|
261
|
+
if g not in ids:
|
|
262
|
+
ids.append(g)
|
|
263
|
+
return ids
|
|
264
|
+
|
|
265
|
+
def getIDsFromSelectionMultiSelect(self, repo, data, selectedIds, filter):
|
|
266
|
+
# Add all descendents of the selected IDs, the IDs and their parents.
|
|
267
|
+
ids = []
|
|
268
|
+
for id in selectedIds:
|
|
269
|
+
entry = data[id]
|
|
270
|
+
# don't visualise rows which are set to "Obsolete":
|
|
271
|
+
if 'Curation status' in entry and str(entry['Curation status']) == "Obsolete":
|
|
272
|
+
pass
|
|
273
|
+
else:
|
|
274
|
+
if filter != [""] and filter != []:
|
|
275
|
+
for f in filter:
|
|
276
|
+
if str(entry['Curation status']) == f:
|
|
277
|
+
if str(entry['ID']) and str(entry['ID']).strip(): # check for none and blank ID's
|
|
278
|
+
if 'ID' in entry and len(entry['ID']) > 0:
|
|
279
|
+
ids.append(entry['ID'].replace(":", "_"))
|
|
280
|
+
if 'Parent' in entry:
|
|
281
|
+
entryParent = re.sub(r"[\[].*?[\]]", "", str(entry['Parent'])).strip()
|
|
282
|
+
if entryParent in self.label_to_id:
|
|
283
|
+
ids.append(self.label_to_id[entryParent])
|
|
284
|
+
if ":" in entry['ID'] or "_" in entry['ID']:
|
|
285
|
+
entryIri = self.releases[repo].get_iri_for_id(entry['ID'].replace("_", ":"))
|
|
286
|
+
if entryIri:
|
|
287
|
+
descs = pyhornedowl.get_descendants(self.releases[repo], entryIri)
|
|
288
|
+
for d in descs:
|
|
289
|
+
ids.append(self.releases[repo].get_id_for_iri(d).replace(":", "_"))
|
|
290
|
+
if self.graphs[repo]:
|
|
291
|
+
graph_descs = None
|
|
292
|
+
try:
|
|
293
|
+
graph_descs = networkx.algorithms.dag.descendants(self.graphs[repo],
|
|
294
|
+
entry['ID'].replace(":", "_"))
|
|
295
|
+
except networkx.exception.NetworkXError:
|
|
296
|
+
print("NetworkXError selection multiselect: ", entry['ID'])
|
|
297
|
+
|
|
298
|
+
if graph_descs is not None:
|
|
299
|
+
for g in graph_descs:
|
|
300
|
+
if g not in ids:
|
|
301
|
+
ids.append(g)
|
|
302
|
+
return ids
|
|
303
|
+
|
|
304
|
+
def getIDsFromSelection(self, repo, data, selectedIds, filter):
|
|
305
|
+
# Add all descendents of the selected IDs, the IDs and their parents.
|
|
306
|
+
ids = []
|
|
307
|
+
for id in selectedIds:
|
|
308
|
+
entry = data[id]
|
|
309
|
+
# don't visualise rows which are set to "Obsolete":
|
|
310
|
+
if 'Curation status' in entry and str(entry['Curation status']) == "Obsolete":
|
|
311
|
+
pass
|
|
312
|
+
else:
|
|
313
|
+
id_ = entry.get('ID', None)
|
|
314
|
+
if id_ is None:
|
|
315
|
+
continue
|
|
316
|
+
|
|
317
|
+
if filter != "":
|
|
318
|
+
if str(entry['Curation status']) == filter:
|
|
319
|
+
if str(id_) and str(id_).strip(): # check for none and blank ID's
|
|
320
|
+
if 'ID' in entry and len(id_) > 0:
|
|
321
|
+
ids.append(id_.replace(":", "_"))
|
|
322
|
+
if 'Parent' in entry:
|
|
323
|
+
entryParent = re.sub(r"[\[].*?[\]]", "", str(entry['Parent'])).strip()
|
|
324
|
+
if entryParent in self.label_to_id:
|
|
325
|
+
ids.append(self.label_to_id[entryParent])
|
|
326
|
+
if ":" in id_ or "_" in id_:
|
|
327
|
+
entryIri = self.releases[repo].get_iri_for_id(id_.replace("_", ":"))
|
|
328
|
+
if entryIri:
|
|
329
|
+
descs = pyhornedowl.get_descendants(self.releases[repo], entryIri)
|
|
330
|
+
for d in descs:
|
|
331
|
+
ids.append(self.releases[repo].get_id_for_iri(d).replace(":", "_"))
|
|
332
|
+
if self.graphs[repo]:
|
|
333
|
+
graph_descs = None
|
|
334
|
+
try:
|
|
335
|
+
graph_descs = networkx.algorithms.dag.descendants(self.graphs[repo],
|
|
336
|
+
id_.replace(":", "_"))
|
|
337
|
+
except networkx.exception.NetworkXError:
|
|
338
|
+
print("NetworkXError selection filter: ", str(id_))
|
|
339
|
+
|
|
340
|
+
if graph_descs is not None:
|
|
341
|
+
for g in graph_descs:
|
|
342
|
+
if g not in ids:
|
|
343
|
+
ids.append(g)
|
|
344
|
+
else:
|
|
345
|
+
if str(id_) and str(id_).strip(): # check for none and blank ID's
|
|
346
|
+
if 'ID' in entry and len(id_) > 0:
|
|
347
|
+
ids.append(id_.replace(":", "_"))
|
|
348
|
+
if 'Parent' in entry:
|
|
349
|
+
entryParent = re.sub(r"[\[].*?[\]]", "", str(entry['Parent'])).strip()
|
|
350
|
+
if entryParent in self.label_to_id:
|
|
351
|
+
ids.append(self.label_to_id[entryParent])
|
|
352
|
+
if ":" in id_ or "_" in id_:
|
|
353
|
+
entryIri = self.releases[repo].get_iri_for_id(id_.replace("_", ":"))
|
|
354
|
+
if entryIri:
|
|
355
|
+
descs = pyhornedowl.get_descendants(self.releases[repo], entryIri)
|
|
356
|
+
for d in descs:
|
|
357
|
+
ids.append(self.releases[repo].get_id_for_iri(d).replace(":", "_"))
|
|
358
|
+
if self.graphs[repo]:
|
|
359
|
+
graph_descs = None
|
|
360
|
+
try:
|
|
361
|
+
graph_descs = networkx.algorithms.dag.descendants(self.graphs[repo],
|
|
362
|
+
id_.replace(":", "_"))
|
|
363
|
+
except networkx.exception.NetworkXError:
|
|
364
|
+
print("NetworkXError selection all: ", str(id_))
|
|
365
|
+
|
|
366
|
+
if graph_descs is not None:
|
|
367
|
+
for g in graph_descs:
|
|
368
|
+
if g not in ids:
|
|
369
|
+
ids.append(g)
|
|
370
|
+
return ids
|
|
371
|
+
|
|
372
|
+
def getRelatedIDs(self, repo, selectedIds):
|
|
373
|
+
# Add all descendents of the selected IDs, the IDs and their parents.
|
|
374
|
+
ids = []
|
|
375
|
+
for id in selectedIds:
|
|
376
|
+
ids.append(id.replace(":", "_"))
|
|
377
|
+
if ":" in id or "_" in id:
|
|
378
|
+
entryIri = self.releases[repo].get_iri_for_id(id.replace("_", ":"))
|
|
379
|
+
|
|
380
|
+
if entryIri:
|
|
381
|
+
descs = pyhornedowl.get_descendants(self.releases[repo], entryIri)
|
|
382
|
+
for d in descs:
|
|
383
|
+
ids.append(self.releases[repo].get_id_for_iri(d).replace(":", "_"))
|
|
384
|
+
|
|
385
|
+
superclasses = self.releases[repo].get_superclasses(entryIri)
|
|
386
|
+
for s in superclasses:
|
|
387
|
+
ids.append(self.releases[repo].get_id_for_iri(s).replace(":", "_"))
|
|
388
|
+
if self.graphs[repo]:
|
|
389
|
+
graph_descs = None
|
|
390
|
+
try:
|
|
391
|
+
graph_descs = networkx.algorithms.dag.descendants(self.graphs[repo], id.replace(":", "_"))
|
|
392
|
+
except networkx.exception.NetworkXError:
|
|
393
|
+
print("NetworkXError relatedIDs: ", str(id))
|
|
394
|
+
|
|
395
|
+
if graph_descs is not None:
|
|
396
|
+
for g in graph_descs:
|
|
397
|
+
if g not in ids:
|
|
398
|
+
ids.append(g)
|
|
399
|
+
return ids
|
|
400
|
+
|
|
401
|
+
def getDotForSheetGraph(self, repo, data, filter):
|
|
402
|
+
# Get a list of IDs from the sheet graph
|
|
403
|
+
# todo: is there a better way to do this?
|
|
404
|
+
if hasattr(filter, 'lower'): # check if filter is a string
|
|
405
|
+
ids = OntologyDataStore.getIDsFromSheet(self, repo, data, filter)
|
|
406
|
+
else: # should be a list then
|
|
407
|
+
ids = OntologyDataStore.getIDsFromSheetMultiSelect(self, repo, data, filter)
|
|
408
|
+
subgraph = self.graphs[repo].subgraph(ids)
|
|
409
|
+
P = networkx.nx_pydot.to_pydot(subgraph)
|
|
410
|
+
return P
|
|
411
|
+
|
|
412
|
+
def getDotForSelection(self, repo, data, selectedIds, filter):
|
|
413
|
+
# Add all descendents of the selected IDs, the IDs and their parents.
|
|
414
|
+
# todo: is there a better way to do this?
|
|
415
|
+
if hasattr(filter, 'lower'): # check if filter is a string
|
|
416
|
+
ids = OntologyDataStore.getIDsFromSelection(self, repo, data, selectedIds, filter)
|
|
417
|
+
else: # should be a list then
|
|
418
|
+
ids = OntologyDataStore.getIDsFromSelectionMultiSelect(self, repo, data, selectedIds, filter)
|
|
419
|
+
# Then get the subgraph as usual
|
|
420
|
+
subgraph = self.graphs[repo].subgraph(ids)
|
|
421
|
+
P = networkx.nx_pydot.to_pydot(subgraph)
|
|
422
|
+
return P
|
|
423
|
+
|
|
424
|
+
def getDotForIDs(self, repo, selectedIds):
|
|
425
|
+
# Add all descendents of the selected IDs, the IDs and their parents.
|
|
426
|
+
ids = OntologyDataStore.getRelatedIDs(self, repo, selectedIds)
|
|
427
|
+
# Then get the subgraph as usual
|
|
428
|
+
subgraph = self.graphs[repo].subgraph(ids)
|
|
429
|
+
P = networkx.nx_pydot.to_pydot(subgraph)
|
|
430
|
+
return P
|
|
431
|
+
|
|
432
|
+
# to create a dictionary and add all info to it, in the relevant place
|
|
433
|
+
def getMetaData(self, repo, allIDS):
|
|
434
|
+
DEFN = "http://purl.obolibrary.org/obo/IAO_0000115"
|
|
435
|
+
SYN = "http://purl.obolibrary.org/obo/IAO_0000118"
|
|
436
|
+
|
|
437
|
+
entries = []
|
|
438
|
+
|
|
439
|
+
for classIri in self.releases[repo].get_classes():
|
|
440
|
+
classId = self.releases[repo].get_id_for_iri(classIri).replace(":", "_")
|
|
441
|
+
for id in allIDS:
|
|
442
|
+
if id is not None:
|
|
443
|
+
if classId == id:
|
|
444
|
+
label = self.releases[repo].get_annotation(classIri, constants.RDFS_LABEL) # yes
|
|
445
|
+
if self.releases[repo].get_annotation(classIri, DEFN) is not None:
|
|
446
|
+
definition = self.releases[repo].get_annotation(classIri, DEFN).replace(",", "").replace(
|
|
447
|
+
"'", "").replace("\"", "")
|
|
448
|
+
else:
|
|
449
|
+
definition = ""
|
|
450
|
+
if self.releases[repo].get_annotation(classIri, SYN) is not None:
|
|
451
|
+
synonyms = self.releases[repo] \
|
|
452
|
+
.get_annotation(classIri, SYN) \
|
|
453
|
+
.replace(",", "") \
|
|
454
|
+
.replace("'", "") \
|
|
455
|
+
.replace("\"", "")
|
|
456
|
+
else:
|
|
457
|
+
synonyms = ""
|
|
458
|
+
entries.append({
|
|
459
|
+
"id": id,
|
|
460
|
+
"label": label,
|
|
461
|
+
"synonyms": synonyms,
|
|
462
|
+
"definition": definition,
|
|
463
|
+
})
|
|
464
|
+
return entries
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from typing import Dict, Set, Optional
|
|
2
|
+
|
|
3
|
+
from flask import g
|
|
4
|
+
|
|
5
|
+
PERMISSION_IMPLICATIONS: Dict[str, Set[str]] = {
|
|
6
|
+
"admin": set(),
|
|
7
|
+
"view": set(),
|
|
8
|
+
"edit": {"view"},
|
|
9
|
+
"add-external-ontology": {"edit"},
|
|
10
|
+
"release": {"edit"},
|
|
11
|
+
"index": {"view"},
|
|
12
|
+
"hierarchical-spreadsheets": {"view"},
|
|
13
|
+
"repository-config-view": {"view"},
|
|
14
|
+
"repository-config-manage-loaded": {"repository-config-view"},
|
|
15
|
+
"repository-config-manage-startup": {"repository-config-view"},
|
|
16
|
+
"scripts": {"edit"},
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PermissionManager:
|
|
21
|
+
_group_permissions: Dict[str, Set[str]]
|
|
22
|
+
_user_permissions: Dict[str, Set[str]]
|
|
23
|
+
_user_repos: Dict[str, Set[str]]
|
|
24
|
+
|
|
25
|
+
def __init__(self, config):
|
|
26
|
+
self._group_permissions = {k: self.expand_permissions(set(v["permissions"])) for k, v in
|
|
27
|
+
config["PERMISSION_GROUPS"].items()}
|
|
28
|
+
|
|
29
|
+
user_permissions = {
|
|
30
|
+
name: set.union(set(user.get("permissions", [])),
|
|
31
|
+
*(self._group_permissions[g] for g in user.get("groups", [])))
|
|
32
|
+
for name, user in config["USERS"].items()
|
|
33
|
+
}
|
|
34
|
+
self._user_permissions = {name: self.expand_permissions(p) for name, p in user_permissions.items()}
|
|
35
|
+
|
|
36
|
+
self._user_repos = {
|
|
37
|
+
name: set(user.get("repositories", []))
|
|
38
|
+
for name, user in config["USERS"].items()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def expand_permissions(self, permissions: Set[str]) -> Set[str]:
|
|
42
|
+
"""
|
|
43
|
+
Applies implication rules
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
expanded = set.union(permissions, *[PERMISSION_IMPLICATIONS[p] for p in permissions])
|
|
47
|
+
|
|
48
|
+
if expanded == permissions:
|
|
49
|
+
return expanded
|
|
50
|
+
|
|
51
|
+
return self.expand_permissions(expanded)
|
|
52
|
+
|
|
53
|
+
def user_permission(self, username: str) -> Set[str]:
|
|
54
|
+
return self._user_permissions.get(username, self._user_permissions.get("*", set()))
|
|
55
|
+
|
|
56
|
+
def has_permissions(self, user_name: str, *permissions: str, repository: Optional[str] = None) -> bool:
|
|
57
|
+
user_permission = self.user_permission(user_name)
|
|
58
|
+
|
|
59
|
+
if "admin" in user_permission:
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
return all(p in user_permission for p in permissions) and (
|
|
63
|
+
repository is None or self.can_access_repo(user_name, repository))
|
|
64
|
+
|
|
65
|
+
def current_user_has_permissions(self, *permissions: str, repository: Optional[str] = None) -> bool:
|
|
66
|
+
"""
|
|
67
|
+
Checks whether the currently requesting user has specific permissions.
|
|
68
|
+
Note: This requires an application context e.g. from a request
|
|
69
|
+
"""
|
|
70
|
+
if not g.user:
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
user_name = g.user.github_login if g.user else "*"
|
|
74
|
+
return self.has_permissions(user_name, *permissions, repository=repository)
|
|
75
|
+
|
|
76
|
+
def can_access_repo(self, user_name: str, repository: Optional[str] = None) -> bool:
|
|
77
|
+
user_repos = self._user_repos.get(user_name, {})
|
|
78
|
+
return repository == "*" or repository in user_repos
|