ose-plugin-bcio 0.2.5__tar.gz
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_plugin_bcio-0.2.5/PKG-INFO +47 -0
- ose_plugin_bcio-0.2.5/README.md +36 -0
- ose_plugin_bcio-0.2.5/pyproject.toml +29 -0
- ose_plugin_bcio-0.2.5/setup.cfg +4 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/BCIOSearchClient.py +52 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/BCIOSearchReleaseStep.py +82 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/BCIOSearchService.py +31 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/__init__.py +28 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/scripts/cleanup_bcio_vocab.py +91 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/scripts/import_missing_externals.py +118 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/scripts/set_pre_proposed_curation_status.py +124 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/scripts/update_imports_to_latest_versions.py +107 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/static/ose-plugin-bcio.js +39 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio/static/ose-plugin-bcio.umd.cjs +1 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio.egg-info/PKG-INFO +47 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio.egg-info/SOURCES.txt +18 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio.egg-info/dependency_links.txt +1 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio.egg-info/entry_points.txt +2 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio.egg-info/requires.txt +4 -0
- ose_plugin_bcio-0.2.5/src/ose_plugin_bcio.egg-info/top_level.txt +2 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ose-plugin-bcio
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Plugin for BCIO services and workflows
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: ose-core==0.2.5
|
|
8
|
+
Requires-Dist: ose-plugin-hbcp==0.2.5
|
|
9
|
+
Requires-Dist: GitHub-Flask
|
|
10
|
+
Requires-Dist: Flask-SQLAlchemy==3.1.1
|
|
11
|
+
|
|
12
|
+
# OSE Plugin: BCIO
|
|
13
|
+
|
|
14
|
+
OntoSpreadEd plugin for BCIO (Behaviour Change Intervention Ontology) services and workflows.
|
|
15
|
+
|
|
16
|
+
## Description
|
|
17
|
+
|
|
18
|
+
This plugin extends OntoSpreadEd with functionality specific to the BCIO ontology project. It provides:
|
|
19
|
+
|
|
20
|
+
- BCIO search release step for automated release pipelines
|
|
21
|
+
- Custom scripts for BCIO vocabulary management:
|
|
22
|
+
- Clean up BCIO vocabulary
|
|
23
|
+
- Import missing external terms
|
|
24
|
+
- Update imports to latest versions
|
|
25
|
+
- Set pre-proposed curation status
|
|
26
|
+
- Integration with BCIO search services
|
|
27
|
+
- Custom UI components for BCIO-specific workflows
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install ose-plugin-bcio
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Python 3.12+
|
|
38
|
+
- ose-core
|
|
39
|
+
- ose-plugin-hbcp
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
The plugin is automatically discovered and loaded when installed. Register it in your release script to use BCIO-specific release steps and scripts.
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
LGPL-3.0-or-later
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# OSE Plugin: BCIO
|
|
2
|
+
|
|
3
|
+
OntoSpreadEd plugin for BCIO (Behaviour Change Intervention Ontology) services and workflows.
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
This plugin extends OntoSpreadEd with functionality specific to the BCIO ontology project. It provides:
|
|
8
|
+
|
|
9
|
+
- BCIO search release step for automated release pipelines
|
|
10
|
+
- Custom scripts for BCIO vocabulary management:
|
|
11
|
+
- Clean up BCIO vocabulary
|
|
12
|
+
- Import missing external terms
|
|
13
|
+
- Update imports to latest versions
|
|
14
|
+
- Set pre-proposed curation status
|
|
15
|
+
- Integration with BCIO search services
|
|
16
|
+
- Custom UI components for BCIO-specific workflows
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install ose-plugin-bcio
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
- Python 3.12+
|
|
27
|
+
- ose-core
|
|
28
|
+
- ose-plugin-hbcp
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
The plugin is automatically discovered and loaded when installed. Register it in your release script to use BCIO-specific release steps and scripts.
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
LGPL-3.0-or-later
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ose-plugin-bcio"
|
|
7
|
+
version = "0.2.5"
|
|
8
|
+
description = "Plugin for BCIO services and workflows"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"ose-core==0.2.5",
|
|
13
|
+
"ose-plugin-hbcp==0.2.5",
|
|
14
|
+
"GitHub-Flask",
|
|
15
|
+
"Flask-SQLAlchemy==3.1.1",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.entry-points.'ose.plugins']
|
|
19
|
+
bcio = "ose_plugin_bcio:BCIOPlugin"
|
|
20
|
+
|
|
21
|
+
[tool.uv.sources]
|
|
22
|
+
ose-core = { workspace = true }
|
|
23
|
+
ose-plugin-hbcp = { workspace = true }
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["src"]
|
|
27
|
+
|
|
28
|
+
[tool.setuptools.package-data]
|
|
29
|
+
ose_plugin_bcio = ["static/*"]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Literal, Dict, Tuple
|
|
3
|
+
|
|
4
|
+
from aiohttp import ClientSession
|
|
5
|
+
|
|
6
|
+
from ose_plugin_hbcp.search_api.APIClient import APIClient
|
|
7
|
+
from ose.model.Term import Term
|
|
8
|
+
from ose.model.TermIdentifier import TermIdentifier
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BCIOSearchClient(APIClient):
|
|
12
|
+
_session: ClientSession
|
|
13
|
+
_logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
_term_link_to_relation_mapping: Dict[
|
|
16
|
+
str, Tuple[TermIdentifier, Literal["single", "multiple", "multiple-per-line"]]
|
|
17
|
+
] = {
|
|
18
|
+
"synonyms": (TermIdentifier(id="IAO:0000118", label="alternative label"), "multiple"),
|
|
19
|
+
"definition": (TermIdentifier(id="IAO:0000115", label="definition"), "single"),
|
|
20
|
+
"informalDefinition": (TermIdentifier(label="informalDefinition"), "single"),
|
|
21
|
+
"lowerLevelOntology": (TermIdentifier(label="lowerLevelOntology"), "multiple"),
|
|
22
|
+
"curatorNote": (TermIdentifier(id="IAO:0000232", label="curator note"), "single"),
|
|
23
|
+
"curationStatus": (TermIdentifier(id="IAO:0000114", label="has curation status"), "single"),
|
|
24
|
+
"comment": (TermIdentifier(id="rdfs:comment", label="rdfs:comment"), "single"),
|
|
25
|
+
"examples": (TermIdentifier(id="IAO:0000112", label="example of usage"), "multiple-per-line"),
|
|
26
|
+
"fuzzySet": (TermIdentifier(label="fuzzySet"), "single"),
|
|
27
|
+
"fuzzyExplanation": (TermIdentifier(label="fuzzyExplanation"), "single"),
|
|
28
|
+
"crossReferences": (TermIdentifier(label="crossReference"), "multiple"),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
def convert_to_api_term(self, term: Term, with_references=True) -> Dict:
|
|
32
|
+
data = super().convert_to_api_term(term, with_references)
|
|
33
|
+
|
|
34
|
+
if "lowerLevelOntology" in data and data["lowerLevelOntology"] is not None:
|
|
35
|
+
lower_level_ontologies = [d.lower().strip() for d in data["lowerLevelOntology"]]
|
|
36
|
+
if "upper level" in lower_level_ontologies:
|
|
37
|
+
lower_level_ontologies.remove("upper level")
|
|
38
|
+
|
|
39
|
+
data["lowerLevelOntology"] = lower_level_ontologies
|
|
40
|
+
|
|
41
|
+
return data
|
|
42
|
+
|
|
43
|
+
def terms_equal(self, old: Term, new: Term) -> bool:
|
|
44
|
+
ignore_if_not_exists = [
|
|
45
|
+
"informalDefinition",
|
|
46
|
+
"lowerLevelOntology",
|
|
47
|
+
"fuzzySet",
|
|
48
|
+
"fuzzyExplanation",
|
|
49
|
+
"crossReference",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
return self._terms_equal(ignore_if_not_exists, new, old)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
from flask_github import GitHub
|
|
7
|
+
from flask_sqlalchemy import SQLAlchemy
|
|
8
|
+
|
|
9
|
+
from ose.model.ExcelOntology import ExcelOntology
|
|
10
|
+
from ose.model.ReleaseScript import ReleaseScript
|
|
11
|
+
from ose.model.Result import Result
|
|
12
|
+
from ose.release.ReleaseStep import ReleaseStep
|
|
13
|
+
from ose.release.common import order_sources
|
|
14
|
+
from .BCIOSearchService import BCIOSearchService
|
|
15
|
+
from ose.services.ConfigurationService import ConfigurationService
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class BCIOSearchReleaseStep(ReleaseStep):
|
|
19
|
+
_included_files: List[str]
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def name(cls) -> str:
|
|
23
|
+
return "BCIO_SEARCH"
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
db: SQLAlchemy,
|
|
28
|
+
gh: GitHub,
|
|
29
|
+
release_script: ReleaseScript,
|
|
30
|
+
release_id: int,
|
|
31
|
+
tmp: str,
|
|
32
|
+
config: ConfigurationService,
|
|
33
|
+
*,
|
|
34
|
+
included_files: List[str],
|
|
35
|
+
):
|
|
36
|
+
super().__init__(db, gh, release_script, release_id, tmp, config)
|
|
37
|
+
|
|
38
|
+
self._included_files = included_files
|
|
39
|
+
|
|
40
|
+
def run(self) -> bool:
|
|
41
|
+
result = Result()
|
|
42
|
+
sources = order_sources(
|
|
43
|
+
dict([(k, f) for k, f in self._release_script.files.items() if k in self._included_files])
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
ontology = ExcelOntology("")
|
|
47
|
+
for s in self._release_script.external.sources:
|
|
48
|
+
xlsx = self._local_name(s.file)
|
|
49
|
+
result += ontology.add_imported_terms(s.file, xlsx)
|
|
50
|
+
|
|
51
|
+
for i, (k, file) in enumerate(sources):
|
|
52
|
+
for s in file.sources:
|
|
53
|
+
if s.type == "classes":
|
|
54
|
+
result += ontology.add_terms_from_excel(s.file, self._local_name(s.file))
|
|
55
|
+
elif s.type == "relations":
|
|
56
|
+
result += ontology.add_relations_from_excel(s.file, self._local_name(s.file))
|
|
57
|
+
|
|
58
|
+
self._raise_if_canceled()
|
|
59
|
+
|
|
60
|
+
ontology.resolve()
|
|
61
|
+
self._raise_if_canceled()
|
|
62
|
+
|
|
63
|
+
ontology.remove_duplicates()
|
|
64
|
+
self._raise_if_canceled()
|
|
65
|
+
|
|
66
|
+
externals = [self._local_name(self._release_script.external.target.file)]
|
|
67
|
+
result += asyncio.run(self.run_service(ontology, externals))
|
|
68
|
+
|
|
69
|
+
self._raise_if_canceled()
|
|
70
|
+
|
|
71
|
+
self._set_release_result(result)
|
|
72
|
+
return result.ok()
|
|
73
|
+
|
|
74
|
+
async def run_service(self, ontology: ExcelOntology, externals: List[str]) -> Result[tuple]:
|
|
75
|
+
async with aiohttp.ClientSession() as session:
|
|
76
|
+
service = BCIOSearchService(self._config, session)
|
|
77
|
+
return await service.update_api(
|
|
78
|
+
ontology,
|
|
79
|
+
externals,
|
|
80
|
+
f"{datetime.utcnow().strftime('%B %Y')} Release",
|
|
81
|
+
lambda step, total, msg: self._update_progress(position=(step, total), current_item=msg),
|
|
82
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import aiohttp
|
|
5
|
+
|
|
6
|
+
from ose_plugin_hbcp.search_api.APIService import APIService
|
|
7
|
+
from .BCIOSearchClient import BCIOSearchClient
|
|
8
|
+
from ose.services.ConfigurationService import ConfigurationService
|
|
9
|
+
|
|
10
|
+
PROP_BCIO_SEARCH_API_PATH = "BCIO_SEARCH_API_PATH"
|
|
11
|
+
PROP_BCIO_SEARCH_API_AUTH_TOKEN = "BCIO_SEARCH_API_AUTH_TOKEN"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BCIOSearchService(APIService[BCIOSearchClient]):
|
|
15
|
+
_logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: ConfigurationService, session: aiohttp.ClientSession):
|
|
18
|
+
path = config.app_config.get(PROP_BCIO_SEARCH_API_PATH, os.environ.get(PROP_BCIO_SEARCH_API_PATH))
|
|
19
|
+
auth_token = config.app_config.get(
|
|
20
|
+
PROP_BCIO_SEARCH_API_AUTH_TOKEN, os.environ.get(PROP_BCIO_SEARCH_API_AUTH_TOKEN, None)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
api_client = BCIOSearchClient(
|
|
24
|
+
path, session, auth_token, config.app_config.get("ENVIRONMENT", "debug") != "production"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
super().__init__(config, api_client)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def repository_name(self):
|
|
31
|
+
return "BCIO"
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from ose.model.Plugin import Plugin, PluginComponent
|
|
2
|
+
from .BCIOSearchReleaseStep import BCIOSearchReleaseStep
|
|
3
|
+
from .scripts.cleanup_bcio_vocab import CleanUpBCIOVocabScript
|
|
4
|
+
from .scripts.import_missing_externals import ImportMissingExternalsScript
|
|
5
|
+
from .scripts.update_imports_to_latest_versions import UpdateImportsToLatestVersionsScript
|
|
6
|
+
from .scripts.set_pre_proposed_curation_status import SetPreProposedCurationStatusScript
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
BCIOPlugin = Plugin(
|
|
10
|
+
id="org.bssofoundry.bcio",
|
|
11
|
+
name="BCIO Plugin",
|
|
12
|
+
version="0.1.0",
|
|
13
|
+
description="Plugin for BCIO services and workflows.",
|
|
14
|
+
contents=[
|
|
15
|
+
BCIOSearchReleaseStep,
|
|
16
|
+
CleanUpBCIOVocabScript,
|
|
17
|
+
ImportMissingExternalsScript,
|
|
18
|
+
UpdateImportsToLatestVersionsScript,
|
|
19
|
+
SetPreProposedCurationStatusScript,
|
|
20
|
+
],
|
|
21
|
+
components=[
|
|
22
|
+
PluginComponent(
|
|
23
|
+
step_name="BCIO_SEARCH",
|
|
24
|
+
component_name="BCIOSearch",
|
|
25
|
+
),
|
|
26
|
+
],
|
|
27
|
+
js_module="ose-plugin-bcio.js",
|
|
28
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import io
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import aiohttp
|
|
6
|
+
from flask_github import GitHub
|
|
7
|
+
from injector import inject
|
|
8
|
+
|
|
9
|
+
from ose.model.ExcelOntology import ExcelOntology
|
|
10
|
+
from ose.model.Script import Script
|
|
11
|
+
from ose.model.TermIdentifier import TermIdentifier
|
|
12
|
+
from ..BCIOSearchService import BCIOSearchService
|
|
13
|
+
from ose.services.ConfigurationService import ConfigurationService
|
|
14
|
+
from ose.utils import get_spreadsheets, get_spreadsheet
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CleanUpBCIOVocabScript(Script):
|
|
18
|
+
@property
|
|
19
|
+
def id(self) -> str:
|
|
20
|
+
return "cleanup-bcio-vocab"
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def title(self) -> str:
|
|
24
|
+
return "Cleanup terms on BCIO Vocab"
|
|
25
|
+
|
|
26
|
+
@inject
|
|
27
|
+
def __init__(self, gh: GitHub, config: ConfigurationService) -> None:
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.gh = gh
|
|
30
|
+
self.config = config
|
|
31
|
+
|
|
32
|
+
async def run(self) -> str:
|
|
33
|
+
repo = self.config.get("BCIO")
|
|
34
|
+
|
|
35
|
+
assert repo is not None, "BCIO repository configuration not found."
|
|
36
|
+
|
|
37
|
+
# get all BCIO Vocab terms
|
|
38
|
+
async with aiohttp.ClientSession() as session:
|
|
39
|
+
service = BCIOSearchService(self.config, session)
|
|
40
|
+
bcio_terms = await service.get_all_terms()
|
|
41
|
+
|
|
42
|
+
bcio_vocab_by_id = dict([(t.id.strip(), t) for t in bcio_terms if t.id is not None])
|
|
43
|
+
|
|
44
|
+
# get all BCIO excel terms
|
|
45
|
+
branch = repo.main_branch
|
|
46
|
+
active_sheets = repo.indexed_files
|
|
47
|
+
regex = "|".join(f"({r})" for r in active_sheets)
|
|
48
|
+
|
|
49
|
+
excel_files = get_spreadsheets(self.gh, repo.full_name, branch, include_pattern=regex)
|
|
50
|
+
|
|
51
|
+
for file in excel_files:
|
|
52
|
+
_, data, _ = get_spreadsheet(self.gh, repo.full_name, file)
|
|
53
|
+
|
|
54
|
+
for entity in data:
|
|
55
|
+
term_id = entity.get("ID", "").strip()
|
|
56
|
+
if term_id in bcio_vocab_by_id:
|
|
57
|
+
del bcio_vocab_by_id[term_id]
|
|
58
|
+
|
|
59
|
+
exclude = set()
|
|
60
|
+
for k, v in bcio_vocab_by_id.items():
|
|
61
|
+
# Population is expanded
|
|
62
|
+
if v.get_relation_value(TermIdentifier(id=None, label="lowerLevelOntology")) == "population":
|
|
63
|
+
exclude.add(k)
|
|
64
|
+
|
|
65
|
+
if v.curation_status() == "external":
|
|
66
|
+
exclude.add(k)
|
|
67
|
+
|
|
68
|
+
for k in exclude:
|
|
69
|
+
del bcio_vocab_by_id[k]
|
|
70
|
+
|
|
71
|
+
with open("ids.json", "w") as f:
|
|
72
|
+
json.dump(bcio_vocab_by_id, f)
|
|
73
|
+
|
|
74
|
+
excel_ontology = ExcelOntology("tmp://tmp")
|
|
75
|
+
for t in bcio_vocab_by_id.values():
|
|
76
|
+
excel_ontology.add_term(t)
|
|
77
|
+
|
|
78
|
+
excel = excel_ontology.to_excel()
|
|
79
|
+
excel.save("to_be_deleted.xlsx")
|
|
80
|
+
|
|
81
|
+
stream = io.BytesIO()
|
|
82
|
+
excel.save(stream)
|
|
83
|
+
|
|
84
|
+
# stream to base64
|
|
85
|
+
b64content = base64.b64encode(stream.getvalue()).decode()
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
f"The entities in this excel sheet have been identified to be deleted. "
|
|
89
|
+
f'<a download="to_be_deleted.xlsx" href="data:application/vnd.ms-excel;base64,{b64content}>'
|
|
90
|
+
f"to_be_deleted.xlsx</a>"
|
|
91
|
+
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Annotated, Dict
|
|
3
|
+
|
|
4
|
+
from flask_github import GitHub
|
|
5
|
+
from injector import inject
|
|
6
|
+
import pyhornedowl
|
|
7
|
+
|
|
8
|
+
from ose.model.Script import Script
|
|
9
|
+
import ose.utils.github as github
|
|
10
|
+
from ose import constants
|
|
11
|
+
from ose.model.ExcelOntology import ExcelOntology, OntologyImport
|
|
12
|
+
from ose.model.Term import Term
|
|
13
|
+
from ose.model.TermIdentifier import TermIdentifier
|
|
14
|
+
# from ose.routes.api.external import update_imports
|
|
15
|
+
from ose.services.ConfigurationService import ConfigurationService
|
|
16
|
+
from ose.utils import get_spreadsheets, lower
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ImportMissingExternalsScript(Script):
|
|
20
|
+
@property
|
|
21
|
+
def id(self) -> str:
|
|
22
|
+
return "import-missing-externals"
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def title(self) -> str:
|
|
26
|
+
return "Import Missing Externals"
|
|
27
|
+
|
|
28
|
+
@inject
|
|
29
|
+
def __init__(self, gh: GitHub, config: ConfigurationService) -> None:
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.gh = gh
|
|
32
|
+
self.config = config
|
|
33
|
+
|
|
34
|
+
def run(self, repo: Annotated[str, "Repository name"]) -> str:
|
|
35
|
+
repository = self.config.get(repo)
|
|
36
|
+
|
|
37
|
+
assert repository is not None, f"Repository configuration for '{repo}' not found."
|
|
38
|
+
|
|
39
|
+
full_repo = repository.full_name
|
|
40
|
+
branch = repository.main_branch
|
|
41
|
+
active_sheets = repository.indexed_files
|
|
42
|
+
regex = "|".join(f"({r})" for r in active_sheets)
|
|
43
|
+
|
|
44
|
+
externals_owl = "addicto_external.owl"
|
|
45
|
+
externals_content = github.get_file(self.gh, full_repo, externals_owl).decode()
|
|
46
|
+
|
|
47
|
+
external_ontology = ExcelOntology(externals_owl)
|
|
48
|
+
ontology = pyhornedowl.open_ontology(externals_content)
|
|
49
|
+
for p, d in repository.prefixes.items():
|
|
50
|
+
ontology.prefix_mapping.add_prefix(p, d)
|
|
51
|
+
|
|
52
|
+
for c in ontology.get_classes():
|
|
53
|
+
id = ontology.get_id_for_iri(c)
|
|
54
|
+
labels = ontology.get_annotations(c, constants.RDFS_LABEL)
|
|
55
|
+
|
|
56
|
+
if id is not None:
|
|
57
|
+
for label in labels:
|
|
58
|
+
external_ontology.add_term(
|
|
59
|
+
Term(
|
|
60
|
+
id=id,
|
|
61
|
+
label=label,
|
|
62
|
+
origin=("<external>", -1),
|
|
63
|
+
relations=[],
|
|
64
|
+
sub_class_of=[],
|
|
65
|
+
equivalent_to=[],
|
|
66
|
+
disjoint_with=[],
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
excel_files = get_spreadsheets(self.gh, full_repo, branch, include_pattern=regex)
|
|
71
|
+
|
|
72
|
+
missing_imports: Dict[str, OntologyImport] = {}
|
|
73
|
+
|
|
74
|
+
for file in excel_files:
|
|
75
|
+
content = github.get_file(self.gh, full_repo, file)
|
|
76
|
+
o = ExcelOntology(file)
|
|
77
|
+
o.add_terms_from_excel(file, content)
|
|
78
|
+
|
|
79
|
+
for t in o._terms:
|
|
80
|
+
if (
|
|
81
|
+
t.id is not None
|
|
82
|
+
and lower(t.curation_status()) == "external"
|
|
83
|
+
and external_ontology.term_by_id(t.id) is None
|
|
84
|
+
):
|
|
85
|
+
pref = t.id.split(":")[0]
|
|
86
|
+
imp = missing_imports.setdefault(
|
|
87
|
+
pref,
|
|
88
|
+
OntologyImport(
|
|
89
|
+
id=pref,
|
|
90
|
+
iri=f"http://purl.obolibrary.org/obo/{pref.lower()}.owl",
|
|
91
|
+
root_id=TermIdentifier("BFO:0000001", "entity"),
|
|
92
|
+
intermediates="all",
|
|
93
|
+
prefixes=[],
|
|
94
|
+
imported_terms=[],
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
imp.imported_terms.append(t.identifier())
|
|
99
|
+
|
|
100
|
+
change_branch = f"script/import-missing-{datetime.utcnow().strftime('%Y-%m-%d_%H-%M-%S')}"
|
|
101
|
+
github.create_branch(self.gh, full_repo, change_branch, branch)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# update_imports(repo, full_repo, self.gh, list(missing_imports.values()), change_branch)
|
|
105
|
+
|
|
106
|
+
body = "Imported the terms:\n"
|
|
107
|
+
body += "\n\n".join(
|
|
108
|
+
f"{m.id}: \n" + "\n".join(f" - {t.label} [{t.id}]" for t in m.imported_terms)
|
|
109
|
+
for m in missing_imports.values()
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
pr = github.create_pr(self.gh, full_repo, "Import missing external terms", body, change_branch, branch)
|
|
113
|
+
github.merge_pr(self.gh, full_repo, pr)
|
|
114
|
+
|
|
115
|
+
return "<p>Imported the terms:</p>\n" + "\n".join(
|
|
116
|
+
f"<p>{m.id}: \n<ul>" + "\n".join(f"<li>{t.label} [{t.id}]</li>" for t in m.imported_terms) + "</ul></p>"
|
|
117
|
+
for m in missing_imports.values()
|
|
118
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
from io import BytesIO
|
|
2
|
+
from typing import Annotated, Union
|
|
3
|
+
|
|
4
|
+
from injector import inject
|
|
5
|
+
import openpyxl
|
|
6
|
+
from flask_github import GitHub
|
|
7
|
+
from openpyxl.styles import PatternFill
|
|
8
|
+
import pyhornedowl
|
|
9
|
+
|
|
10
|
+
from ose.model.Script import Script
|
|
11
|
+
import ose.utils.github as github
|
|
12
|
+
from ose import constants
|
|
13
|
+
from ose.model.ExcelOntology import ExcelOntology
|
|
14
|
+
from ose.model.Term import UnresolvedTerm, Term
|
|
15
|
+
from ose.services.ConfigurationService import ConfigurationService
|
|
16
|
+
from ose.utils import get_spreadsheets, str_empty
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_incomplete(term: Union[UnresolvedTerm, Term]):
|
|
20
|
+
return str_empty(term.label) or len(term.sub_class_of) <= 0 or str_empty(term.definition())
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SetPreProposedCurationStatusScript(Script):
|
|
24
|
+
@property
|
|
25
|
+
def id(self) -> str:
|
|
26
|
+
return "set-pre-proposed-curation-status"
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def title(self) -> str:
|
|
30
|
+
return "Set Pre-proposed Curation Status"
|
|
31
|
+
|
|
32
|
+
@inject
|
|
33
|
+
def __init__(self, gh: GitHub, config: ConfigurationService) -> None:
|
|
34
|
+
super().__init__()
|
|
35
|
+
self.gh = gh
|
|
36
|
+
self.config = config
|
|
37
|
+
|
|
38
|
+
def run(self, repo: Annotated[str, "Repository name"]) -> str:
|
|
39
|
+
repository = self.config.get(repo)
|
|
40
|
+
|
|
41
|
+
assert repository is not None, f"Repository configuration for '{repo}' not found."
|
|
42
|
+
|
|
43
|
+
full_repo = repository.full_name
|
|
44
|
+
branch = repository.main_branch
|
|
45
|
+
active_sheets = repository.indexed_files
|
|
46
|
+
regex = "|".join(f"({r})" for r in active_sheets)
|
|
47
|
+
|
|
48
|
+
externals_owl = "addicto_external.owl"
|
|
49
|
+
externals_content = github.get_file(self.gh, full_repo, externals_owl).decode()
|
|
50
|
+
|
|
51
|
+
external_ontology = ExcelOntology(externals_owl)
|
|
52
|
+
ontology = pyhornedowl.open_ontology(externals_content, "rdf")
|
|
53
|
+
for [p, d] in repository.prefixes:
|
|
54
|
+
ontology.prefix_mapping.add_prefix(p, d)
|
|
55
|
+
|
|
56
|
+
for c in ontology.get_classes():
|
|
57
|
+
id = ontology.get_id_for_iri(c)
|
|
58
|
+
labels = ontology.get_annotations(c, constants.RDFS_LABEL)
|
|
59
|
+
|
|
60
|
+
if id is not None:
|
|
61
|
+
for label in labels:
|
|
62
|
+
external_ontology.add_term(Term(
|
|
63
|
+
id=id,
|
|
64
|
+
label=label,
|
|
65
|
+
origin=("<external>", -1),
|
|
66
|
+
relations=[],
|
|
67
|
+
sub_class_of=[],
|
|
68
|
+
equivalent_to=[],
|
|
69
|
+
disjoint_with=[]
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
excel_files = get_spreadsheets(self.gh, full_repo, branch, include_pattern=regex)
|
|
73
|
+
|
|
74
|
+
changed_files = 0
|
|
75
|
+
|
|
76
|
+
for file in excel_files:
|
|
77
|
+
has_changed = False
|
|
78
|
+
content = github.get_file(self.gh, full_repo, file)
|
|
79
|
+
o = ExcelOntology(file)
|
|
80
|
+
o.import_other_excel_ontology(external_ontology)
|
|
81
|
+
o.add_terms_from_excel(file, content)
|
|
82
|
+
|
|
83
|
+
wb = openpyxl.load_workbook(BytesIO(content))
|
|
84
|
+
sheet = wb.active
|
|
85
|
+
|
|
86
|
+
assert sheet is not None, f"Could not load active sheet from {file}"
|
|
87
|
+
|
|
88
|
+
header = [cell.value for cell in sheet[1] if cell.value]
|
|
89
|
+
|
|
90
|
+
if "Label" not in header or "Curation status" not in header:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
c_label = header.index("Label")
|
|
94
|
+
c_curation_status = header.index("Curation status")
|
|
95
|
+
|
|
96
|
+
o.resolve()
|
|
97
|
+
|
|
98
|
+
for row in sheet:
|
|
99
|
+
label = row[c_label].value
|
|
100
|
+
label = label.strip() if isinstance(label, str) else None
|
|
101
|
+
term = o._term_by_label(label) if label is not None else None
|
|
102
|
+
|
|
103
|
+
if term is not None and (str_empty(term.id) or term.id.startswith(repo.upper())):
|
|
104
|
+
incomplete = is_incomplete(term)
|
|
105
|
+
incomplete = incomplete or all(
|
|
106
|
+
p.is_unresolved() or
|
|
107
|
+
o._term_by_id(p.id) is not None and o._term_by_id(p.id).curation_status() == 'Pre-proposed'
|
|
108
|
+
for p in term.sub_class_of
|
|
109
|
+
)
|
|
110
|
+
if incomplete and row[c_curation_status].value != "Pre-proposed":
|
|
111
|
+
row[c_curation_status].value = "Pre-proposed"
|
|
112
|
+
for c in row:
|
|
113
|
+
c.fill = PatternFill(fgColor="ebfad0", fill_type="solid")
|
|
114
|
+
|
|
115
|
+
changed_files += 1
|
|
116
|
+
has_changed = True
|
|
117
|
+
|
|
118
|
+
if has_changed:
|
|
119
|
+
spreadsheet_stream = BytesIO()
|
|
120
|
+
wb.save(spreadsheet_stream)
|
|
121
|
+
msg = f"Updating {file.split('/')[-1]}\n\nSet terms to Pre-proposed"
|
|
122
|
+
github.save_file(self.gh, full_repo, file, spreadsheet_stream.getvalue(), msg, branch)
|
|
123
|
+
|
|
124
|
+
return f"Updated {changed_files} terms in {len(excel_files)} spreadsheets."
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from io import BytesIO
|
|
3
|
+
from typing import Annotated, List
|
|
4
|
+
|
|
5
|
+
from injector import inject
|
|
6
|
+
import openpyxl
|
|
7
|
+
import pyhornedowl
|
|
8
|
+
import requests
|
|
9
|
+
from flask_github import GitHub
|
|
10
|
+
|
|
11
|
+
from ose.model.Script import Script
|
|
12
|
+
import ose.utils.github as github
|
|
13
|
+
from ose.model.ReleaseScript import ReleaseScript
|
|
14
|
+
from ose.model.Result import Result
|
|
15
|
+
from ose.services.ConfigurationService import ConfigurationService
|
|
16
|
+
from ose.utils import str_space_eq, lower
|
|
17
|
+
|
|
18
|
+
class UpdateImportsToLatestVersionsScript(Script):
|
|
19
|
+
@property
|
|
20
|
+
def id(self) -> str:
|
|
21
|
+
return "update-imports-to-latest-versions"
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def title(self) -> str:
|
|
25
|
+
return "Update Imports to Latest Versions"
|
|
26
|
+
|
|
27
|
+
@inject
|
|
28
|
+
def __init__(self, gh: GitHub, config: ConfigurationService) -> None:
|
|
29
|
+
self.gh = gh
|
|
30
|
+
self.config = config
|
|
31
|
+
|
|
32
|
+
def run(self, repo: Annotated[str, "Repository name"]) -> str:
|
|
33
|
+
result = Result([])
|
|
34
|
+
repository = self.config.get(repo)
|
|
35
|
+
|
|
36
|
+
assert repository is not None, f"Repository configuration for '{repo}' not found."
|
|
37
|
+
|
|
38
|
+
release_script_json = self.config.get_file(repository, repository.release_script_path)
|
|
39
|
+
|
|
40
|
+
assert release_script_json is not None, f"Release script not found at '{repository.release_script_path}'"
|
|
41
|
+
|
|
42
|
+
release_script = ReleaseScript.from_json(json.loads(release_script_json))
|
|
43
|
+
|
|
44
|
+
for source in release_script.external.sources:
|
|
45
|
+
file = self.config.get_file_raw(repository, source.file)
|
|
46
|
+
assert file is not None, f"Could not load file '{source.file}' from repository '{repository.full_name}'"
|
|
47
|
+
file = BytesIO(file)
|
|
48
|
+
|
|
49
|
+
wb = openpyxl.load_workbook(file)
|
|
50
|
+
sheet = wb.active
|
|
51
|
+
|
|
52
|
+
assert sheet is not None, f"Could not load active sheet from '{source.file}'"
|
|
53
|
+
|
|
54
|
+
rows = sheet.rows
|
|
55
|
+
|
|
56
|
+
header = next(rows)
|
|
57
|
+
iri_index = next((i for i, h in enumerate(header) if
|
|
58
|
+
str_space_eq(lower(str(h.value)), "purl") or str_space_eq(lower(str(h.value)), "iri")), None)
|
|
59
|
+
version_index = next((i for i, h in enumerate(header) if str_space_eq(lower(str(h.value)), "version")), None)
|
|
60
|
+
id_index = next((i for i, h in enumerate(header) if str_space_eq(lower(str(h.value)), "ontology id")), None)
|
|
61
|
+
|
|
62
|
+
if None in [iri_index, version_index, id_index]:
|
|
63
|
+
continue
|
|
64
|
+
|
|
65
|
+
changes: List[str] = []
|
|
66
|
+
for row in rows:
|
|
67
|
+
id = row[id_index].value
|
|
68
|
+
iri = row[iri_index].value
|
|
69
|
+
|
|
70
|
+
if id in ["GAZ", "OPMI"]: # GAZ is weird and crashes HornedOwl
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
response = requests.get(iri)
|
|
75
|
+
content = response.text
|
|
76
|
+
|
|
77
|
+
serialisation = iri[iri.rfind(".") + 1:]
|
|
78
|
+
onto = pyhornedowl.open_ontology_from_string(content, serialisation)
|
|
79
|
+
|
|
80
|
+
version_iri = onto.get_version_iri()
|
|
81
|
+
|
|
82
|
+
old_version = row[version_index].value
|
|
83
|
+
if version_iri is None:
|
|
84
|
+
result.warning(type="no-version-iri",
|
|
85
|
+
msg=f"Ontology '{id}' has no version IRI")
|
|
86
|
+
else:
|
|
87
|
+
version_iri = str(version_iri)
|
|
88
|
+
if not str_space_eq(old_version, version_iri):
|
|
89
|
+
row[version_index].value = version_iri
|
|
90
|
+
|
|
91
|
+
changes.append(f"Updated '{id}' from {old_version} to {version_iri}")
|
|
92
|
+
|
|
93
|
+
except Exception as e:
|
|
94
|
+
result.error(type="load-ontology",
|
|
95
|
+
msg=f"Failed to load external ontology '{id}' from '{iri}'",
|
|
96
|
+
e=e)
|
|
97
|
+
|
|
98
|
+
if len(changes) > 0:
|
|
99
|
+
spreadsheet_stream = BytesIO()
|
|
100
|
+
wb.save(spreadsheet_stream)
|
|
101
|
+
|
|
102
|
+
github.save_file(self.gh, repository.full_name, source.file, spreadsheet_stream.getvalue(), "\n".join(changes),
|
|
103
|
+
repository.main_branch)
|
|
104
|
+
|
|
105
|
+
result.value += changes
|
|
106
|
+
|
|
107
|
+
return "\n".join(result.value)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineComponent as o, createElementBlock as a, openBlock as e, Fragment as i, createElementVNode as s, createBlock as d, createCommentVNode as c, renderList as h, toDisplayString as n, unref as u, withCtx as m, createTextVNode as p } from "vue";
|
|
2
|
+
import { ProgressIndicator as g } from "@ose/js-core";
|
|
3
|
+
const y = { class: "alert alert-danger" }, k = { key: 1 }, f = { key: 2 }, b = /* @__PURE__ */ o({
|
|
4
|
+
__name: "BCIOSearch",
|
|
5
|
+
props: {
|
|
6
|
+
data: {},
|
|
7
|
+
release: {},
|
|
8
|
+
selectedSubStep: {}
|
|
9
|
+
},
|
|
10
|
+
emits: ["release-control"],
|
|
11
|
+
setup(t) {
|
|
12
|
+
return (S, r) => (e(), a(i, null, [
|
|
13
|
+
r[1] || (r[1] = s("h3", null, "Publishing the release", -1)),
|
|
14
|
+
t.release.state === "waiting-for-user" && t.data?.errors?.length > 0 ? (e(!0), a(i, { key: 0 }, h(t.data.errors, (l) => (e(), a("div", y, [
|
|
15
|
+
l.details && l?.response?.["hydra:description"] ? (e(), a(i, { key: 0 }, [
|
|
16
|
+
s("h4", null, n(l.response["hydra:title"]), 1),
|
|
17
|
+
s("p", null, n(l.details), 1),
|
|
18
|
+
s("p", null, n(l.response["hydra:description"]), 1)
|
|
19
|
+
], 64)) : (e(), a("pre", k, n(JSON.stringify(l, void 0, 2)), 1))
|
|
20
|
+
]))), 256)) : (e(), d(u(g), {
|
|
21
|
+
key: 1,
|
|
22
|
+
details: t.data,
|
|
23
|
+
release: t.release
|
|
24
|
+
}, {
|
|
25
|
+
default: m(() => [...r[0] || (r[0] = [
|
|
26
|
+
s("p", null, [
|
|
27
|
+
p(" The ontologies are being published to BCIOSearch. This will take a while."),
|
|
28
|
+
s("br")
|
|
29
|
+
], -1)
|
|
30
|
+
])]),
|
|
31
|
+
_: 1
|
|
32
|
+
}, 8, ["details", "release"])),
|
|
33
|
+
t.release.state === "completed" ? (e(), a("p", f, " The ontologies were published to BCIOSearch. ")) : c("", !0)
|
|
34
|
+
], 64));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
export {
|
|
38
|
+
b as BCIOSearch
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(t,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("@ose/js-core")):typeof define=="function"&&define.amd?define(["exports","vue","@ose/js-core"],e):(t=typeof globalThis<"u"?globalThis:t||self,e(t.ose_plugin_bcio={},t.Vue,t.OseJsCore))})(this,(function(t,e,r){"use strict";const s={class:"alert alert-danger"},i={key:1},a={key:2},c=e.defineComponent({__name:"BCIOSearch",props:{data:{},release:{},selectedSubStep:{}},emits:["release-control"],setup(n){return(d,l)=>(e.openBlock(),e.createElementBlock(e.Fragment,null,[l[1]||(l[1]=e.createElementVNode("h3",null,"Publishing the release",-1)),n.release.state==="waiting-for-user"&&n.data?.errors?.length>0?(e.openBlock(!0),e.createElementBlock(e.Fragment,{key:0},e.renderList(n.data.errors,o=>(e.openBlock(),e.createElementBlock("div",s,[o.details&&o?.response?.["hydra:description"]?(e.openBlock(),e.createElementBlock(e.Fragment,{key:0},[e.createElementVNode("h4",null,e.toDisplayString(o.response["hydra:title"]),1),e.createElementVNode("p",null,e.toDisplayString(o.details),1),e.createElementVNode("p",null,e.toDisplayString(o.response["hydra:description"]),1)],64)):(e.openBlock(),e.createElementBlock("pre",i,e.toDisplayString(JSON.stringify(o,void 0,2)),1))]))),256)):(e.openBlock(),e.createBlock(e.unref(r.ProgressIndicator),{key:1,details:n.data,release:n.release},{default:e.withCtx(()=>[...l[0]||(l[0]=[e.createElementVNode("p",null,[e.createTextVNode(" The ontologies are being published to BCIOSearch. This will take a while."),e.createElementVNode("br")],-1)])]),_:1},8,["details","release"])),n.release.state==="completed"?(e.openBlock(),e.createElementBlock("p",a," The ontologies were published to BCIOSearch. ")):e.createCommentVNode("",!0)],64))}});t.BCIOSearch=c,Object.defineProperty(t,Symbol.toStringTag,{value:"Module"})}));
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ose-plugin-bcio
|
|
3
|
+
Version: 0.2.5
|
|
4
|
+
Summary: Plugin for BCIO services and workflows
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: ose-core==0.2.5
|
|
8
|
+
Requires-Dist: ose-plugin-hbcp==0.2.5
|
|
9
|
+
Requires-Dist: GitHub-Flask
|
|
10
|
+
Requires-Dist: Flask-SQLAlchemy==3.1.1
|
|
11
|
+
|
|
12
|
+
# OSE Plugin: BCIO
|
|
13
|
+
|
|
14
|
+
OntoSpreadEd plugin for BCIO (Behaviour Change Intervention Ontology) services and workflows.
|
|
15
|
+
|
|
16
|
+
## Description
|
|
17
|
+
|
|
18
|
+
This plugin extends OntoSpreadEd with functionality specific to the BCIO ontology project. It provides:
|
|
19
|
+
|
|
20
|
+
- BCIO search release step for automated release pipelines
|
|
21
|
+
- Custom scripts for BCIO vocabulary management:
|
|
22
|
+
- Clean up BCIO vocabulary
|
|
23
|
+
- Import missing external terms
|
|
24
|
+
- Update imports to latest versions
|
|
25
|
+
- Set pre-proposed curation status
|
|
26
|
+
- Integration with BCIO search services
|
|
27
|
+
- Custom UI components for BCIO-specific workflows
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install ose-plugin-bcio
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- Python 3.12+
|
|
38
|
+
- ose-core
|
|
39
|
+
- ose-plugin-hbcp
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
The plugin is automatically discovered and loaded when installed. Register it in your release script to use BCIO-specific release steps and scripts.
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
LGPL-3.0-or-later
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/ose_plugin_bcio/BCIOSearchClient.py
|
|
4
|
+
src/ose_plugin_bcio/BCIOSearchReleaseStep.py
|
|
5
|
+
src/ose_plugin_bcio/BCIOSearchService.py
|
|
6
|
+
src/ose_plugin_bcio/__init__.py
|
|
7
|
+
src/ose_plugin_bcio.egg-info/PKG-INFO
|
|
8
|
+
src/ose_plugin_bcio.egg-info/SOURCES.txt
|
|
9
|
+
src/ose_plugin_bcio.egg-info/dependency_links.txt
|
|
10
|
+
src/ose_plugin_bcio.egg-info/entry_points.txt
|
|
11
|
+
src/ose_plugin_bcio.egg-info/requires.txt
|
|
12
|
+
src/ose_plugin_bcio.egg-info/top_level.txt
|
|
13
|
+
src/ose_plugin_bcio/scripts/cleanup_bcio_vocab.py
|
|
14
|
+
src/ose_plugin_bcio/scripts/import_missing_externals.py
|
|
15
|
+
src/ose_plugin_bcio/scripts/set_pre_proposed_curation_status.py
|
|
16
|
+
src/ose_plugin_bcio/scripts/update_imports_to_latest_versions.py
|
|
17
|
+
src/ose_plugin_bcio/static/ose-plugin-bcio.js
|
|
18
|
+
src/ose_plugin_bcio/static/ose-plugin-bcio.umd.cjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|