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.
Files changed (127) hide show
  1. ose_app/AppModule.py +98 -0
  2. ose_app/OntologyDataStore.py +464 -0
  3. ose_app/PermissionManager.py +78 -0
  4. ose_app/SpreadsheetSearcher.py +197 -0
  5. ose_app/__init__.py +130 -0
  6. ose_app/app.py +10 -0
  7. ose_app/constants.py +1 -0
  8. ose_app/custom_json.py +23 -0
  9. ose_app/default_config.py +39 -0
  10. ose_app/gh.py +60 -0
  11. ose_app/guards/__init__.py +0 -0
  12. ose_app/guards/verify_login.py +20 -0
  13. ose_app/guards/with_permission.py +54 -0
  14. ose_app/jinja_extensions/__init__.py +42 -0
  15. ose_app/routes/__init__.py +24 -0
  16. ose_app/routes/admin.py +108 -0
  17. ose_app/routes/api/__init__.py +23 -0
  18. ose_app/routes/api/edit.py +162 -0
  19. ose_app/routes/api/external.py +198 -0
  20. ose_app/routes/api/plugins.py +22 -0
  21. ose_app/routes/api/release.py +380 -0
  22. ose_app/routes/api/repos.py +55 -0
  23. ose_app/routes/api/scripts.py +49 -0
  24. ose_app/routes/api/search.py +19 -0
  25. ose_app/routes/api/settings.py +115 -0
  26. ose_app/routes/api/validate.py +264 -0
  27. ose_app/routes/api/visualise.py +228 -0
  28. ose_app/routes/auth.py +44 -0
  29. ose_app/routes/edit.py +690 -0
  30. ose_app/routes/main.py +100 -0
  31. ose_app/routes/search.py +42 -0
  32. ose_app/routes/status.py +10 -0
  33. ose_app/routes/visualize.py +108 -0
  34. ose_app/static/addicto.release.json +129 -0
  35. ose_app/static/bcio.release.json +321 -0
  36. ose_app/static/example.json +134 -0
  37. ose_app/static/excel-bootstrap-table-filter-bundle.js +326 -0
  38. ose_app/static/excel-bootstrap-table-filter-bundle.js.map +1 -0
  39. ose_app/static/excel-bootstrap-table-filter-bundle.min.js +2 -0
  40. ose_app/static/excel-bootstrap-table-filter-bundle.min.js.map +1 -0
  41. ose_app/static/excel-bootstrap-table-filter-style.css +78 -0
  42. ose_app/static/gmho.release.json +139 -0
  43. ose_app/static/js/admin.js +2 -0
  44. ose_app/static/js/admin.js.map +1 -0
  45. ose_app/static/js/assets/CollapsibleCard.css +1 -0
  46. ose_app/static/js/assets/editor.css +1 -0
  47. ose_app/static/js/assets/release.css +1 -0
  48. ose_app/static/js/assets/settings.css +1 -0
  49. ose_app/static/js/assets/visualise.css +1 -0
  50. ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-CgGO-ddH-BDr_frBe.js +2 -0
  51. ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-CgGO-ddH-BDr_frBe.js.map +1 -0
  52. ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-CgGO-ddH-MonxH8xj.js +2 -0
  53. ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-CgGO-ddH-MonxH8xj.js.map +1 -0
  54. ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-DKAZyKKr-DTksKjtf.js +2 -0
  55. ose_app/static/js/chunks/BToastOrchestrator.vue_vue_type_style_index_0_lang-DKAZyKKr-DTksKjtf.js.map +1 -0
  56. ose_app/static/js/chunks/CollapsibleCard-BQR8R54V.js +2 -0
  57. ose_app/static/js/chunks/CollapsibleCard-BQR8R54V.js.map +1 -0
  58. ose_app/static/js/chunks/CollapsibleCard-DgwkQKDg.js +2 -0
  59. ose_app/static/js/chunks/CollapsibleCard-DgwkQKDg.js.map +1 -0
  60. ose_app/static/js/chunks/CollapsibleCard-DoX0kPCZ.js +2 -0
  61. ose_app/static/js/chunks/CollapsibleCard-DoX0kPCZ.js.map +1 -0
  62. ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-CBzsmnqW.js +43 -0
  63. ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-CBzsmnqW.js.map +1 -0
  64. ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-EcINi797.js +43 -0
  65. ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-EcINi797.js.map +1 -0
  66. ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-bumDOBqD.js +43 -0
  67. ose_app/static/js/chunks/Diagnostic.vue_vue_type_script_setup_true_lang-bumDOBqD.js.map +1 -0
  68. ose_app/static/js/chunks/_plugin-vue_export-helper-DlAUqK2U.js +2 -0
  69. ose_app/static/js/chunks/_plugin-vue_export-helper-DlAUqK2U.js.map +1 -0
  70. ose_app/static/js/chunks/bootbox-D3SJ_Fwv.js +2 -0
  71. ose_app/static/js/chunks/bootbox-D3SJ_Fwv.js.map +1 -0
  72. ose_app/static/js/chunks/constants-CzBHF1EN.js +2 -0
  73. ose_app/static/js/chunks/constants-CzBHF1EN.js.map +1 -0
  74. ose_app/static/js/chunks/constants-DL6Hx3V0.js +2 -0
  75. ose_app/static/js/chunks/constants-DL6Hx3V0.js.map +1 -0
  76. ose_app/static/js/chunks/constants-DPgwRV4l.js +2 -0
  77. ose_app/static/js/chunks/constants-DPgwRV4l.js.map +1 -0
  78. ose_app/static/js/chunks/filter-Bx-PU1OL.js +18 -0
  79. ose_app/static/js/chunks/filter-Bx-PU1OL.js.map +1 -0
  80. ose_app/static/js/chunks/filter-CdAXK93b.js +18 -0
  81. ose_app/static/js/chunks/filter-CdAXK93b.js.map +1 -0
  82. ose_app/static/js/chunks/filter-CiYqP1_r.js +2 -0
  83. ose_app/static/js/chunks/filter-CiYqP1_r.js.map +1 -0
  84. ose_app/static/js/chunks/index-sLCKmIWG-B5BYNF2N.js +2 -0
  85. ose_app/static/js/chunks/index-sLCKmIWG-B5BYNF2N.js.map +1 -0
  86. ose_app/static/js/chunks/index-sLCKmIWG-Cb9lQq-c.js +9 -0
  87. ose_app/static/js/chunks/index-sLCKmIWG-Cb9lQq-c.js.map +1 -0
  88. ose_app/static/js/chunks/index-sLCKmIWG-umQeu_-w.js +3 -0
  89. ose_app/static/js/chunks/index-sLCKmIWG-umQeu_-w.js.map +1 -0
  90. ose_app/static/js/editor.js +36 -0
  91. ose_app/static/js/editor.js.map +1 -0
  92. ose_app/static/js/release.js +6 -0
  93. ose_app/static/js/release.js.map +1 -0
  94. ose_app/static/js/settings.js +2 -0
  95. ose_app/static/js/settings.js.map +1 -0
  96. ose_app/static/js/visualise.js +3 -0
  97. ose_app/static/js/visualise.js.map +1 -0
  98. ose_app/static/main.css +1 -0
  99. ose_app/static/main.css.map +7 -0
  100. ose_app/static/main.scss +147 -0
  101. ose_app/static/mindmup-editabletable.js +131 -0
  102. ose_app/static/schema/release_script.json +215 -0
  103. ose_app/static/schema/req_body_edit.json +35 -0
  104. ose_app/static/schema/req_body_guess_parent.json +38 -0
  105. ose_app/static/schema/req_body_import.json +47 -0
  106. ose_app/static/schema/req_body_release_start.json +9 -0
  107. ose_app/static/schema/req_body_visualise.json +18 -0
  108. ose_app/static/test.dot +190 -0
  109. ose_app/static/workflows/externals.yaml.jinja2 +44 -0
  110. ose_app/templates/access_show_visualisation_for_ids.html +120 -0
  111. ose_app/templates/admin/dashboard.html +18 -0
  112. ose_app/templates/admin/rebuild_index.html +85 -0
  113. ose_app/templates/admin/release.html +46 -0
  114. ose_app/templates/admin/settings.html +23 -0
  115. ose_app/templates/base.html +151 -0
  116. ose_app/templates/edit.html +21 -0
  117. ose_app/templates/edit_external.html +491 -0
  118. ose_app/templates/forbidden.html +9 -0
  119. ose_app/templates/index.html +33 -0
  120. ose_app/templates/loggedout.html +21 -0
  121. ose_app/templates/repo.html +294 -0
  122. ose_app/templates/testgeneratedbyapi.html +294 -0
  123. ose_app/templates/visualise.html +17 -0
  124. ose_app-0.2.5.dist-info/METADATA +27 -0
  125. ose_app-0.2.5.dist-info/RECORD +127 -0
  126. ose_app-0.2.5.dist-info/WHEEL +5 -0
  127. 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