vantage6-algorithm-store 4.3.0__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.
- vantage6-algorithm-store-4.3.0/PKG-INFO +168 -0
- vantage6-algorithm-store-4.3.0/setup.cfg +4 -0
- vantage6-algorithm-store-4.3.0/setup.py +55 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/__build__ +1 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/__init__.py +358 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/_version.py +23 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/db.py +83 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/default_roles.py +85 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/globals.py +22 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/__init__.py +15 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/algorithm.py +44 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/argument.py +36 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/base.py +426 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/common/enums.py +37 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/database.py +33 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/developer_algorithm_association.py +15 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/function.py +43 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/permission.py +30 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/review.py +45 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/role.py +80 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/role_rule_association.py +17 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/rule.py +96 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/user.py +146 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/model/vantage6_server.py +42 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/permission.py +290 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/__init__.py +291 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/algorithm.py +540 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/role.py +176 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/rule.py +217 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/schema/input_schema.py +104 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/schema/output_schema.py +150 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/user.py +431 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/vantage6_server.py +242 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/resource/version.py +62 -0
- vantage6-algorithm-store-4.3.0/vantage6/algorithm/store/wsgi.py +17 -0
- vantage6-algorithm-store-4.3.0/vantage6_algorithm_store.egg-info/PKG-INFO +168 -0
- vantage6-algorithm-store-4.3.0/vantage6_algorithm_store.egg-info/SOURCES.txt +38 -0
- vantage6-algorithm-store-4.3.0/vantage6_algorithm_store.egg-info/dependency_links.txt +1 -0
- vantage6-algorithm-store-4.3.0/vantage6_algorithm_store.egg-info/requires.txt +17 -0
- vantage6-algorithm-store-4.3.0/vantage6_algorithm_store.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: vantage6-algorithm-store
|
|
3
|
+
Version: 4.3.0
|
|
4
|
+
Summary: Vantage6 algorithm store
|
|
5
|
+
Home-page: https://github.com/vantage6/vantage6
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
|
|
10
|
+
<h1 align="center">
|
|
11
|
+
<br>
|
|
12
|
+
<a href="https://vantage6.ai"><img src="https://github.com/IKNL/guidelines/blob/master/resources/logos/vantage6.png?raw=true" alt="vantage6" width="350"></a>
|
|
13
|
+
</h1>
|
|
14
|
+
|
|
15
|
+
<h3 align=center> A Privacy Enhancing Technology (PET) Operations platform</h3>
|
|
16
|
+
<h3 align="center">
|
|
17
|
+
|
|
18
|
+
<!-- Badges go here-->
|
|
19
|
+
[](https://github.com/vantage6/vantage6/actions/workflows/release.yml)
|
|
20
|
+
[](https://badge.fury.io/py/vantage6)
|
|
21
|
+
[](https://github.com/vantage6/vantage6/actions/workflows/unit_tests.yml)
|
|
22
|
+
[](https://coveralls.io/github/vantage6/vantage6?branch=main)
|
|
23
|
+
[](https://www.codacy.com/gh/vantage6/vantage6/dashboard?utm_source=github.com&utm_medium=referral&utm_content=vantage6/vantage6&utm_campaign=Badge_Grade)
|
|
24
|
+
[](https://doi.org/10.5281/zenodo.7382602)
|
|
25
|
+
</h3>
|
|
26
|
+
|
|
27
|
+
<p align="center">
|
|
28
|
+
<a href="#books-quickstart">Quickstart</a> •
|
|
29
|
+
<a href="#project-structure">Project structure</a> •
|
|
30
|
+
<a href="#gift_heart-join-the-community">Join the community</a> •
|
|
31
|
+
<a href="#black_nib-references">References</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
-----------------------------------------------------------------------------------------------------
|
|
36
|
+
This repository is contains all the **vantage6** infrastructure source code. The **vantage6** technology enables to manage and deploy privacy enhancing technologies like Federated Learning (FL) and Multi-Party Computation (MPC). Please visit our [website (vantage6.ai)](https://vantage6.ai) to learn more!
|
|
37
|
+
|
|
38
|
+
You can find more (user) documentation at [readthedocs (docs.vantage6.ai)](https://docs.vantage6.ai). If you have any questions, suggestions or just want to chat about federated learning: join our [Discord (https://discord.gg/yAyFf6Y)](https://discord.gg/yAyFf6Y) channel.
|
|
39
|
+
|
|
40
|
+
## Infrastructure overview
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
*A High level overview of the vantage6 infrastructure. Vantage6 has both a client-server and peer-to-peer architecture. The client is used by the researcher to create (PET) computation requests. It is also used to manage users, organizations and collaborations. The server contains users, organizations, collaborations, tasks and their results. It provides a central access point for both the clients and nodes. The nodes have access to privacy sensitive data and handle computation requests retrieved from the server. Computation request are executed as separate containers on the node. These containers are connected to containers at other nodes by a VPN network.*
|
|
45
|
+
|
|
46
|
+
## :books: Quickstart
|
|
47
|
+
|
|
48
|
+
### Requirements
|
|
49
|
+
The **vantage6** infrastructure is delivered in Docker images. To run these images, you need to have [Docker](https://docs.docker.com/get-docker/) installed. To install the latest version of the vantage6 CLI, you need to have [Python](https://www.python.org/downloads/), we recommend using an environment manager like [mini-conda](https://docs.conda.io/en/latest/miniconda.html).
|
|
50
|
+
|
|
51
|
+
Install the latest version of the vantage6 CLI by using:
|
|
52
|
+
```bash
|
|
53
|
+
pip install vantage6
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
This install the `v6` commands, which allows you to manage your nodes and servers. To view all available options, run:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
v6 --help
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
For example you can create a local test setup by using:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
v6 dev create-demo-network
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This creates a local network with a server and two nodes. You can start the network by running:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
v6 dev start-demo-network
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This will start the server and nodes in the background. You can view the logs by running:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# View node logs
|
|
78
|
+
v6 node attach
|
|
79
|
+
|
|
80
|
+
# View server logs
|
|
81
|
+
v6 server attach
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
From here you can use the [vantage6-client](https://pypi.org/project/vantage6-client) to interact with the server. The demo network has a pre-configured organization with the following credentials:
|
|
85
|
+
|
|
86
|
+
* Username: `org_1-admin`
|
|
87
|
+
* Password: `password`
|
|
88
|
+
|
|
89
|
+
For example, you can create a new organization by running:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from vantage6.client import Client
|
|
93
|
+
|
|
94
|
+
client = Client('http://127.0.0.1', 5000, '/api', log_level='debug')
|
|
95
|
+
client.authenticate('org_1-admin', 'password')
|
|
96
|
+
client.setup_encryption(None)
|
|
97
|
+
|
|
98
|
+
client.organization.create(
|
|
99
|
+
name='My organization',
|
|
100
|
+
address1='My address',
|
|
101
|
+
address2='My address',
|
|
102
|
+
zipcode='1234AB',
|
|
103
|
+
country='The Netherlands',
|
|
104
|
+
domain='my-organization.com'
|
|
105
|
+
)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
You can find more (user) documentation at [readthedocs (docs.vantage6.ai)](https://docs.vantage6.ai)
|
|
109
|
+
|
|
110
|
+
## Project structure
|
|
111
|
+
|
|
112
|
+
### PYPI packages
|
|
113
|
+
This repository is home to 6 PyPi packages:
|
|
114
|
+
|
|
115
|
+
* [vantage6](https://pypi.org/project/vantage6) -> _CLI for managing node and server instances_
|
|
116
|
+
* [vantage6-client](https://pypi.org/project/vantage6-client) -> _Python client for interacting with the vantage6-server_
|
|
117
|
+
* [vantage6-algorithm-tools](https://pypi.org/project/vantage6-algorithm-tools) -> _Python tools to facilitate algorithm development_
|
|
118
|
+
* [vantage6-node](https://pypi.org/project/vantage6-node) -> _Node application package_
|
|
119
|
+
* [vantage6-server](https://pypi.org/project/vantage6-server) -> _Server application package_
|
|
120
|
+
* [vantage6-algorithm-store](https://pypi.org/project/vantage6-algorithm-store) -> _Algorithm store application package_
|
|
121
|
+
* [vantage6-common](https://pypi.org/project/vantage6-common) -> _Package with common vantage6 functions_
|
|
122
|
+
* [vantage6-backend-common](https://pypi.org/project/vantage6-backend-common) -> _Package with functions common to central server and algorithm store_
|
|
123
|
+
|
|
124
|
+
**Note that when using vantage6 you do not install the _server_ and _node_ packages. These are delivered to you in Docker images.**
|
|
125
|
+
|
|
126
|
+
### Docker images
|
|
127
|
+
The vantage6 infrastructure is delivered in Docker images. All Docker images are stored in our private [Harbor](https://goharbor.io/) registry. The most important images contain the server and node:
|
|
128
|
+
|
|
129
|
+
* `harbor2.vantage6.ai/infrastructure/node:VERSION` -> _Node application Docker image_
|
|
130
|
+
* `harbor2.vantage6.ai/infrastructure/server:VERSION` -> _Server application Docker image_
|
|
131
|
+
|
|
132
|
+
with `VERSION` being the full semantic version of the vantage6 infrastructure, e.g. `4.0.0` or `4.1.0rc0`.
|
|
133
|
+
|
|
134
|
+
Several other images are used to support the infrastructure:
|
|
135
|
+
|
|
136
|
+
* `harbor2.vantage6.ai/infrastructure/infrastructure-base:VERSION` -> _Base image for the infrastructure_
|
|
137
|
+
* `harbor2.vantage6.ai/infrastructure/squid:VERSION` -> _Squid proxy image used for the whitelisting service_
|
|
138
|
+
* `harbor2.vantage6.ai/infrastructure/alpine` -> _Alpine image used for vpn traffic forwarding_
|
|
139
|
+
* `harbor2.vantage6.ai/infrastructure/vpn-client` -> _VPN image used to connect to the VPN_
|
|
140
|
+
* `harbor2.vantage6.ai/infrastructure/vpn-configurator` -> _VPN image used for initialization_
|
|
141
|
+
* `harbor2.vantage6.ai/infrastructure/ssh-tunnel` -> _SSH tunnel image used for connecting algorithms to external services_
|
|
142
|
+
|
|
143
|
+
And finally there are some images released for algorithm development:
|
|
144
|
+
|
|
145
|
+
* `harbor2.vantage6.ai/infrastructure/algorithm-base:MAJOR.MINOR` -> _Base image for algorithm development_
|
|
146
|
+
* `harbor2.vantage6.ai/infrastructure/algorithm-ohdsi-base:MAJOR.MINOR` -> _Extended algorithm base image for OHDSI algorithm development_
|
|
147
|
+
|
|
148
|
+
## :gift_heart: Join the community!
|
|
149
|
+
We hope to continue developing, improving, and supporting **vantage6** with the help of the federated learning community. If you are interested in contributing, first of all, thank you! Second, please take a look at our [contributing guidelines](https://docs.vantage6.ai/en/main/devops/contribute.html)
|
|
150
|
+
|
|
151
|
+
<a href="https://github.com/vantage6/vantage6/graphs/contributors">
|
|
152
|
+
<img src="https://contrib.rocks/image?repo=vantage6/vantage6" />
|
|
153
|
+
</a>
|
|
154
|
+
|
|
155
|
+
## :black_nib: References
|
|
156
|
+
If you are using **vantage6**, please cite this repository as well as the accompanying papers as follows:
|
|
157
|
+
|
|
158
|
+
> * F. Martin, M. Sieswerda, H. Alradhi, et al. vantage6. Available at https://doi.org/10.5281/zenodo.7221216. Accessed on MONTH, 20XX.
|
|
159
|
+
> * A. Moncada-Torres, F. Martin, M. Sieswerda, J. van Soest, G. Gelijnse. VANTAGE6: an open source priVAcy preserviNg federaTed leArninG infrastructurE for Secure Insight eXchange. AMIA Annual Symposium Proceedings, 2020, p. 870-877. [[BibTeX](https://arturomoncadatorres.com/bibtex/moncada-torres2020vantage6.txt), [PDF](https://vantage6.ai/vantage6/)]
|
|
160
|
+
> * D. Smits\*, B. van Beusekom\*, F. Martin, L. Veen, G. Geleijnse, A. Moncada-Torres, An Improved Infrastructure for Privacy-Preserving Analysis of Patient Data, Proceedings of the International Conference of Informatics, Management, and Technology in Healthcare (ICIMTH), vol. 25, 2022, p. 144-147. [[BibTeX](https://arturomoncadatorres.com/bibtex/smits2022improved.txt), [PDF](https://ebooks.iospress.nl/volumearticle/60190)]
|
|
161
|
+
|
|
162
|
+
-----------------------------------------------------------------------------------------------------
|
|
163
|
+
<p align="center">
|
|
164
|
+
<a href="https://vantage6.ai">vantage6.ai</a> •
|
|
165
|
+
<a href="https://discord.gg/yAyFf6Y">Discord</a> •
|
|
166
|
+
<a href="https://vantage6.discourse.group/">Discourse</a> •
|
|
167
|
+
<a href="https://docs.vantage6.ai">User documentation</a> •
|
|
168
|
+
</p>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import codecs
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from os import path
|
|
5
|
+
from setuptools import setup, find_namespace_packages
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
# get current directory
|
|
9
|
+
here = Path(path.abspath(path.dirname(__file__)))
|
|
10
|
+
parent_dir = here.parent.absolute()
|
|
11
|
+
|
|
12
|
+
# get the long description from the README file
|
|
13
|
+
with codecs.open(path.join(parent_dir, "README.md"), encoding="utf-8") as f:
|
|
14
|
+
long_description = f.read()
|
|
15
|
+
|
|
16
|
+
# Read the API version from disk. This file should be located in the package
|
|
17
|
+
# folder, since it's also used to set the pkg.__version__ variable.
|
|
18
|
+
version_path = os.path.join(here, "vantage6", "algorithm", "store", "_version.py")
|
|
19
|
+
version_ns = {"__file__": version_path}
|
|
20
|
+
with codecs.open(version_path) as f:
|
|
21
|
+
exec(f.read(), {}, version_ns)
|
|
22
|
+
|
|
23
|
+
# setup the package
|
|
24
|
+
setup(
|
|
25
|
+
name="vantage6-algorithm-store",
|
|
26
|
+
version=version_ns["__version__"],
|
|
27
|
+
description="Vantage6 algorithm store",
|
|
28
|
+
long_description=long_description,
|
|
29
|
+
long_description_content_type="text/markdown",
|
|
30
|
+
url="https://github.com/vantage6/vantage6",
|
|
31
|
+
packages=find_namespace_packages(),
|
|
32
|
+
python_requires=">=3.10",
|
|
33
|
+
install_requires=[
|
|
34
|
+
"flasgger==0.9.5",
|
|
35
|
+
"flask==2.2.5",
|
|
36
|
+
"Flask-Cors==3.0.10",
|
|
37
|
+
"Flask-Principal==0.4.0",
|
|
38
|
+
"flask-marshmallow==0.15.0",
|
|
39
|
+
"Flask-RESTful==0.3.10",
|
|
40
|
+
"gevent==23.9.1",
|
|
41
|
+
"marshmallow==3.19.0",
|
|
42
|
+
"requests==2.31.0",
|
|
43
|
+
"schema==0.7.5",
|
|
44
|
+
"SQLAlchemy==1.4.46",
|
|
45
|
+
"werkzeug==3.0.1",
|
|
46
|
+
f'vantage6 == {version_ns["__version__"]}',
|
|
47
|
+
f'vantage6-common == {version_ns["__version__"]}',
|
|
48
|
+
],
|
|
49
|
+
extras_require={"dev": ["coverage==6.4.4"]},
|
|
50
|
+
package_data={
|
|
51
|
+
"vantage6.algorithm.store": [
|
|
52
|
+
"__build__",
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The algorithm store holds the algorithms that are available to the vantage6
|
|
3
|
+
nodes. It is a repository of algorithms that can be coupled to a vantage6
|
|
4
|
+
server. The algorithms are stored in a database and can be managed through
|
|
5
|
+
the API. Note that is possible to couple multiple algorithm stores to a
|
|
6
|
+
vantage6 server. This allows both coupling a community store and a private
|
|
7
|
+
store to a vantage6 server.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from gevent import monkey
|
|
12
|
+
|
|
13
|
+
from vantage6.algorithm.store.default_roles import get_default_roles
|
|
14
|
+
|
|
15
|
+
# This is a workaround for readthedocs
|
|
16
|
+
if not os.environ.get("READTHEDOCS"):
|
|
17
|
+
# flake8: noqa: E402 (ignore import error)
|
|
18
|
+
monkey.patch_all()
|
|
19
|
+
|
|
20
|
+
# pylint: disable=C0413, C0411
|
|
21
|
+
import importlib
|
|
22
|
+
import logging
|
|
23
|
+
import json
|
|
24
|
+
import traceback
|
|
25
|
+
|
|
26
|
+
from http import HTTPStatus
|
|
27
|
+
from werkzeug.exceptions import HTTPException
|
|
28
|
+
from flask import Flask, make_response, request, send_from_directory, Request, Response
|
|
29
|
+
from flask_cors import CORS
|
|
30
|
+
from flask_marshmallow import Marshmallow
|
|
31
|
+
from flask_restful import Api
|
|
32
|
+
from flask_principal import Principal
|
|
33
|
+
from flasgger import Swagger
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
from vantage6.common import logger_name
|
|
37
|
+
from vantage6.common.globals import APPNAME
|
|
38
|
+
from vantage6.backend.common.resource.output_schema import BaseHATEOASModelSchema
|
|
39
|
+
|
|
40
|
+
# TODO move this to common, then remove dependency on CLI in algorithm store
|
|
41
|
+
from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
|
|
42
|
+
from vantage6.algorithm.store._version import __version__
|
|
43
|
+
from vantage6.algorithm.store.globals import API_PATH, HOST_URI_ENV
|
|
44
|
+
from vantage6.algorithm.store.globals import RESOURCES, SERVER_MODULE_NAME
|
|
45
|
+
|
|
46
|
+
# TODO the following are simply copies of the same files in the server - refactor
|
|
47
|
+
from vantage6.algorithm.store.model.base import Base, DatabaseSessionManager, Database
|
|
48
|
+
from vantage6.algorithm.store import db
|
|
49
|
+
|
|
50
|
+
# TODO move server imports to common / refactor
|
|
51
|
+
from vantage6.algorithm.store.permission import PermissionManager
|
|
52
|
+
|
|
53
|
+
module_name = logger_name(__name__)
|
|
54
|
+
log = logging.getLogger(module_name)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class AlgorithmStoreApp:
|
|
58
|
+
"""
|
|
59
|
+
Vantage6 server instance.
|
|
60
|
+
|
|
61
|
+
Attributes
|
|
62
|
+
----------
|
|
63
|
+
ctx : AlgorithmStoreContext
|
|
64
|
+
Context object that contains the configuration of the algorithm store.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, ctx: AlgorithmStoreContext) -> None:
|
|
68
|
+
"""Create a vantage6-server application."""
|
|
69
|
+
|
|
70
|
+
self.ctx = ctx
|
|
71
|
+
|
|
72
|
+
# initialize, configure Flask
|
|
73
|
+
self.app = Flask(
|
|
74
|
+
SERVER_MODULE_NAME,
|
|
75
|
+
root_path=Path(__file__),
|
|
76
|
+
template_folder=Path(__file__).parent / "templates",
|
|
77
|
+
static_folder=Path(__file__).parent / "static",
|
|
78
|
+
)
|
|
79
|
+
self.debug: dict = self.ctx.config.get("debug", {})
|
|
80
|
+
self.configure_flask()
|
|
81
|
+
|
|
82
|
+
# Setup SQLAlchemy and Marshmallow for marshalling/serializing
|
|
83
|
+
self.ma = Marshmallow(self.app)
|
|
84
|
+
|
|
85
|
+
# Setup Principal, granular API access manegement
|
|
86
|
+
self.principal = Principal(self.app, use_sessions=False)
|
|
87
|
+
|
|
88
|
+
# Enable cross-origin resource sharing
|
|
89
|
+
self.cors = CORS(self.app)
|
|
90
|
+
|
|
91
|
+
# SWAGGER documentation
|
|
92
|
+
self.swagger = Swagger(self.app, template={})
|
|
93
|
+
|
|
94
|
+
# setup the permission manager for the API endpoints
|
|
95
|
+
self.permissions = PermissionManager()
|
|
96
|
+
|
|
97
|
+
# Api - REST JSON-rpc
|
|
98
|
+
self.api = Api(self.app)
|
|
99
|
+
self.configure_api()
|
|
100
|
+
self.load_resources()
|
|
101
|
+
|
|
102
|
+
# set the server version
|
|
103
|
+
self.__version__ = __version__
|
|
104
|
+
|
|
105
|
+
# set environment variable for dev environment
|
|
106
|
+
host_uri = self.ctx.config.get("dev", {}).get("host_uri")
|
|
107
|
+
if host_uri:
|
|
108
|
+
os.environ[HOST_URI_ENV] = host_uri
|
|
109
|
+
|
|
110
|
+
log.info("Initialization done")
|
|
111
|
+
|
|
112
|
+
def configure_flask(self) -> None:
|
|
113
|
+
"""Configure the Flask settings of the vantage6 server."""
|
|
114
|
+
|
|
115
|
+
# let us handle exceptions
|
|
116
|
+
self.app.config["PROPAGATE_EXCEPTIONS"] = True
|
|
117
|
+
|
|
118
|
+
# Open Api Specification (f.k.a. swagger)
|
|
119
|
+
self.app.config["SWAGGER"] = {
|
|
120
|
+
"title": f"{APPNAME} algorithm store",
|
|
121
|
+
"uiversion": "3",
|
|
122
|
+
"openapi": "3.0.0",
|
|
123
|
+
"version": __version__,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
debug_mode = self.debug.get("flask", False)
|
|
127
|
+
if debug_mode:
|
|
128
|
+
log.debug("Flask debug mode enabled")
|
|
129
|
+
self.app.debug = debug_mode
|
|
130
|
+
|
|
131
|
+
def _get_request_path(request: Request) -> str:
|
|
132
|
+
"""
|
|
133
|
+
Return request extension of request URL, e.g.
|
|
134
|
+
http://localhost:5000/api/task/1 -> api/task/1
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
request: Request
|
|
139
|
+
Flask request object
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
string:
|
|
144
|
+
The endpoint path of the request
|
|
145
|
+
"""
|
|
146
|
+
return request.url.replace(request.url_root, "")
|
|
147
|
+
|
|
148
|
+
# before request
|
|
149
|
+
@self.app.before_request
|
|
150
|
+
def do_before_request():
|
|
151
|
+
"""Before every flask request method."""
|
|
152
|
+
# Add log message before each request
|
|
153
|
+
log.debug(
|
|
154
|
+
"Received request: %s %s", request.method, _get_request_path(request)
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# This will obtain a (scoped) db session from the session factory
|
|
158
|
+
# that is linked to the flask request global `g`. In every endpoint
|
|
159
|
+
# we then can access the database by using this session. We ensure
|
|
160
|
+
# that the session is removed (and uncommited changes are rolled
|
|
161
|
+
# back) at the end of every request.
|
|
162
|
+
DatabaseSessionManager.new_session()
|
|
163
|
+
|
|
164
|
+
@self.app.after_request
|
|
165
|
+
def remove_db_session(response):
|
|
166
|
+
"""After every flask request.
|
|
167
|
+
|
|
168
|
+
This will close the database session created by the
|
|
169
|
+
`before_request`.
|
|
170
|
+
"""
|
|
171
|
+
DatabaseSessionManager.clear_session()
|
|
172
|
+
return response
|
|
173
|
+
|
|
174
|
+
@self.app.errorhandler(HTTPException)
|
|
175
|
+
def error_remove_db_session(error: HTTPException):
|
|
176
|
+
"""In case an HTTP-exception occurs during the request.
|
|
177
|
+
|
|
178
|
+
It is important to close the db session to avoid having dangling
|
|
179
|
+
sessions.
|
|
180
|
+
"""
|
|
181
|
+
if error.code == 404:
|
|
182
|
+
log.debug("404 error for route '%s'", _get_request_path(request))
|
|
183
|
+
else:
|
|
184
|
+
log.warning("HTTP Exception occured during request")
|
|
185
|
+
log.debug("%s", traceback.format_exc())
|
|
186
|
+
DatabaseSessionManager.clear_session()
|
|
187
|
+
return error.get_response()
|
|
188
|
+
|
|
189
|
+
@self.app.errorhandler(Exception)
|
|
190
|
+
def error2_remove_db_session(error):
|
|
191
|
+
"""In case an exception occurs during the request.
|
|
192
|
+
|
|
193
|
+
It is important to close the db session to avoid having dangling
|
|
194
|
+
sessions.
|
|
195
|
+
"""
|
|
196
|
+
log.exception("Exception occured during request")
|
|
197
|
+
DatabaseSessionManager.clear_session()
|
|
198
|
+
return {
|
|
199
|
+
"msg": "An unexpected error occurred on the server!"
|
|
200
|
+
}, HTTPStatus.INTERNAL_SERVER_ERROR
|
|
201
|
+
|
|
202
|
+
@self.app.route("/robots.txt")
|
|
203
|
+
def static_from_root():
|
|
204
|
+
return send_from_directory(self.app.static_folder, request.path[1:])
|
|
205
|
+
|
|
206
|
+
def configure_api(self) -> None:
|
|
207
|
+
"""Define global API output and its structure."""
|
|
208
|
+
|
|
209
|
+
# helper to create HATEOAS schemas
|
|
210
|
+
BaseHATEOASModelSchema.api = self.api
|
|
211
|
+
|
|
212
|
+
# whatever you get try to json it
|
|
213
|
+
@self.api.representation("application/json")
|
|
214
|
+
# pylint: disable=unused-argument
|
|
215
|
+
def output_json(
|
|
216
|
+
data: Base | list[Base], code: HTTPStatus, headers: dict = None
|
|
217
|
+
) -> Response:
|
|
218
|
+
"""
|
|
219
|
+
Return jsonified data for request responses.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
data: Base | list[Base]
|
|
224
|
+
The data to be jsonified
|
|
225
|
+
code: HTTPStatus
|
|
226
|
+
The HTTP status code of the response
|
|
227
|
+
headers: dict
|
|
228
|
+
Additional headers to be added to the response
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
if isinstance(data, Base):
|
|
232
|
+
data = db.jsonable(data)
|
|
233
|
+
elif isinstance(data, list) and len(data) and isinstance(data[0], Base):
|
|
234
|
+
data = db.jsonable(data)
|
|
235
|
+
|
|
236
|
+
resp = make_response(json.dumps(data), code)
|
|
237
|
+
resp.headers.extend(headers or {})
|
|
238
|
+
return resp
|
|
239
|
+
|
|
240
|
+
def load_resources(self) -> None:
|
|
241
|
+
"""Import the modules containing API resources."""
|
|
242
|
+
|
|
243
|
+
# make services available to the endpoints, this way each endpoint can
|
|
244
|
+
# make use of 'em.
|
|
245
|
+
services = {
|
|
246
|
+
"api": self.api,
|
|
247
|
+
"config": self.ctx.config,
|
|
248
|
+
"permissions": self.permissions,
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
for res in RESOURCES:
|
|
252
|
+
module = importlib.import_module("vantage6.algorithm.store.resource." + res)
|
|
253
|
+
module.setup(self.api, API_PATH, services)
|
|
254
|
+
|
|
255
|
+
@staticmethod
|
|
256
|
+
def _add_default_roles() -> None:
|
|
257
|
+
for role in get_default_roles():
|
|
258
|
+
if not db.Role.get_by_name(role["name"]):
|
|
259
|
+
log.warn("Creating new default role %s", role["name"])
|
|
260
|
+
new_role = db.Role(
|
|
261
|
+
name=role["name"],
|
|
262
|
+
description=role["description"],
|
|
263
|
+
rules=role["rules"],
|
|
264
|
+
)
|
|
265
|
+
new_role.save()
|
|
266
|
+
|
|
267
|
+
def start(self) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Start the server.
|
|
270
|
+
|
|
271
|
+
Before server is really started, some database settings are checked and
|
|
272
|
+
(re)set where appropriate.
|
|
273
|
+
"""
|
|
274
|
+
self._add_default_roles()
|
|
275
|
+
|
|
276
|
+
# add whitelisted server and root user from config file if they do not exist
|
|
277
|
+
if root_user := self.ctx.config.get("root_user", {}):
|
|
278
|
+
whitelisted_uri = root_user.get("v6_server_uri")
|
|
279
|
+
root_username = root_user.get("username")
|
|
280
|
+
if whitelisted_uri and root_username:
|
|
281
|
+
if not (v6_server := db.Vantage6Server.get_by_url(whitelisted_uri)):
|
|
282
|
+
log.debug("This server will be whitelisted: %s", whitelisted_uri)
|
|
283
|
+
v6_server = db.Vantage6Server(url=whitelisted_uri)
|
|
284
|
+
v6_server.save()
|
|
285
|
+
|
|
286
|
+
# if the user does not exist already, add it
|
|
287
|
+
if not db.User.get_by_server(
|
|
288
|
+
username=root_username, v6_server_id=v6_server.id
|
|
289
|
+
):
|
|
290
|
+
log.warning("Creating root user")
|
|
291
|
+
|
|
292
|
+
root = db.Role.get_by_name("Root")
|
|
293
|
+
|
|
294
|
+
user = db.User(
|
|
295
|
+
v6_server_id=v6_server.id,
|
|
296
|
+
username=root_username,
|
|
297
|
+
roles=[root],
|
|
298
|
+
)
|
|
299
|
+
user.save()
|
|
300
|
+
else:
|
|
301
|
+
log.info(
|
|
302
|
+
"The root user given in the configuration already exists -"
|
|
303
|
+
" no action taken."
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
else:
|
|
307
|
+
log.warning(
|
|
308
|
+
"No v6_server_uri and/or username found in the configuration file "
|
|
309
|
+
"in the root_user section. This means no-one can alter resources on"
|
|
310
|
+
" this server, unless one or more users were already authorized to "
|
|
311
|
+
"make changes to the algorithm store previously."
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
log.warning(
|
|
315
|
+
"No root user found in the configuration file. This means "
|
|
316
|
+
"no-one can alter resources on this server, unless one or "
|
|
317
|
+
"more users were already authorized to make changes to the "
|
|
318
|
+
"algorithm store prevoiusly."
|
|
319
|
+
)
|
|
320
|
+
return self
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def run_server(config: str, system_folders: bool = True) -> AlgorithmStoreApp:
|
|
324
|
+
"""
|
|
325
|
+
Run a vantage6 server.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
config: str
|
|
330
|
+
Configuration file path
|
|
331
|
+
system_folders: bool
|
|
332
|
+
Whether to use system or user folders. Default is True.
|
|
333
|
+
|
|
334
|
+
Returns
|
|
335
|
+
-------
|
|
336
|
+
AlgorithmStoreApp
|
|
337
|
+
A running instance of the vantage6 server
|
|
338
|
+
"""
|
|
339
|
+
ctx = AlgorithmStoreContext.from_external_config_file(config, system_folders)
|
|
340
|
+
allow_drop_all = ctx.config["allow_drop_all"]
|
|
341
|
+
Database().connect(uri=ctx.get_database_uri(), allow_drop_all=allow_drop_all)
|
|
342
|
+
return AlgorithmStoreApp(ctx).start()
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def run_dev_server(server_app: AlgorithmStoreApp, *args, **kwargs) -> None:
|
|
346
|
+
"""
|
|
347
|
+
Run a vantage6 development server (outside of a Docker container).
|
|
348
|
+
|
|
349
|
+
Parameters
|
|
350
|
+
----------
|
|
351
|
+
server_app: AlgorithmStoreApp
|
|
352
|
+
Instance of a vantage6 server
|
|
353
|
+
"""
|
|
354
|
+
log.warning("*" * 80)
|
|
355
|
+
log.warning(" DEVELOPMENT SERVER ".center(80, "*"))
|
|
356
|
+
log.warning("*" * 80)
|
|
357
|
+
kwargs.setdefault("log_output", False)
|
|
358
|
+
server_app.socketio.run(server_app.app, *args, **kwargs)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
here = os.path.abspath(os.path.dirname(__file__))
|
|
5
|
+
|
|
6
|
+
with open(os.path.join(here, "__build__")) as fp:
|
|
7
|
+
__build__ = json.load(fp)
|
|
8
|
+
|
|
9
|
+
# Module version
|
|
10
|
+
version_info = (4, 3, 0, "final", __build__, 0)
|
|
11
|
+
|
|
12
|
+
# Module version stage suffix map
|
|
13
|
+
_specifier_ = {"alpha": "a", "beta": "b", "candidate": "rc", "final": ""}
|
|
14
|
+
version = f"{version_info[0]}.{version_info[1]}.{version_info[2]}"
|
|
15
|
+
pre_release = (
|
|
16
|
+
""
|
|
17
|
+
if version_info[3] == "final"
|
|
18
|
+
else _specifier_[version_info[3]] + str(version_info[4])
|
|
19
|
+
)
|
|
20
|
+
post_release = "" if not version_info[5] else f".post{version_info[5]}"
|
|
21
|
+
|
|
22
|
+
# Module version accessible using thomas.__version__
|
|
23
|
+
__version__ = f"{version}{pre_release}{post_release}"
|