aidbox-python-sdk 0.1.7__tar.gz → 0.1.9__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.
- {aidbox-python-sdk-0.1.7/aidbox_python_sdk.egg-info → aidbox_python_sdk-0.1.9}/PKG-INFO +17 -7
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/README.md +2 -4
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/__init__.py +2 -2
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/aidboxpy.py +16 -17
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/db.py +26 -35
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/exceptions.py +1 -1
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/handlers.py +8 -10
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/main.py +9 -18
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/pytest_plugin.py +12 -15
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/sdk.py +15 -24
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/settings.py +4 -4
- aidbox_python_sdk-0.1.9/aidbox_python_sdk/types.py +16 -0
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9/aidbox_python_sdk.egg-info}/PKG-INFO +17 -7
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk.egg-info/SOURCES.txt +1 -1
- aidbox_python_sdk-0.1.9/aidbox_python_sdk.egg-info/requires.txt +16 -0
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/pyproject.toml +24 -14
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/tests/test_sdk.py +8 -8
- aidbox-python-sdk-0.1.7/aidbox_python_sdk/gunicorn.py +0 -20
- aidbox-python-sdk-0.1.7/aidbox_python_sdk.egg-info/requires.txt +0 -19
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/LICENSE.md +0 -0
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/MANIFEST.in +0 -0
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk/db_migrations.py +0 -0
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk.egg-info/dependency_links.txt +0 -0
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk.egg-info/not-zip-safe +0 -0
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk.egg-info/top_level.txt +0 -0
- {aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aidbox-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Aidbox SDK for python
|
|
5
5
|
Author-email: "beda.software" <aidbox-python-sdk@beda.software>
|
|
6
6
|
License: MIT License
|
|
@@ -35,33 +35,43 @@ Classifier: Intended Audience :: Developers
|
|
|
35
35
|
Classifier: Operating System :: OS Independent
|
|
36
36
|
Classifier: Programming Language :: Python
|
|
37
37
|
Classifier: Programming Language :: Python :: 3
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
39
38
|
Classifier: Programming Language :: Python :: 3.9
|
|
40
39
|
Classifier: Programming Language :: Python :: 3.10
|
|
41
40
|
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
42
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
43
43
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
44
|
Requires-Python: >=3.8
|
|
45
45
|
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE.md
|
|
47
|
+
Requires-Dist: aiohttp>=3.6.2
|
|
48
|
+
Requires-Dist: SQLAlchemy>=1.3.10
|
|
49
|
+
Requires-Dist: fhirpy>=1.3.0
|
|
50
|
+
Requires-Dist: jsonschema>=4.4.0
|
|
46
51
|
Provides-Extra: test
|
|
52
|
+
Requires-Dist: pytest~=6.0.0; extra == "test"
|
|
53
|
+
Requires-Dist: pytest-asyncio~=0.20.0; extra == "test"
|
|
54
|
+
Requires-Dist: pytest-aiohttp~=1.0.4; extra == "test"
|
|
55
|
+
Requires-Dist: pytest-cov~=4.0.0; extra == "test"
|
|
47
56
|
Provides-Extra: dev
|
|
48
|
-
|
|
57
|
+
Requires-Dist: black; extra == "dev"
|
|
58
|
+
Requires-Dist: autohooks; extra == "dev"
|
|
59
|
+
Requires-Dist: autohooks-plugin-ruff; extra == "dev"
|
|
60
|
+
Requires-Dist: autohooks-plugin-black; extra == "dev"
|
|
49
61
|
|
|
50
62
|
[](https://github.com/Aidbox/aidbox-python-sdk/actions/workflows/build.yaml)
|
|
51
63
|
[](https://pypi.org/project/aidbox-python-sdk/)
|
|
52
|
-
[](https://www.python.org/downloads/release/python-390/)
|
|
53
65
|
|
|
54
66
|
# aidbox-python-sdk
|
|
55
67
|
|
|
56
|
-
1. Create a python 3.
|
|
68
|
+
1. Create a python 3.9+ environment `pyenv `
|
|
57
69
|
2. Set env variables and activate virtual environment `source activate_settings.sh`
|
|
58
70
|
2. Install the required packages with `pipenv install --dev`
|
|
59
71
|
3. Make sure the app's settings are configured correctly (see `activate_settings.sh` and `aidbox_python_sdk/settings.py`). You can also
|
|
60
72
|
use environment variables to define sensitive settings, eg. DB connection variables (see example `.env-ptl`)
|
|
61
73
|
4. You can then run example with `python example.py`.
|
|
62
74
|
|
|
63
|
-
Add `APP_FAST_START_MODE=TRUE` to env_tests for fast start mode.
|
|
64
|
-
|
|
65
75
|
# Getting started
|
|
66
76
|
## Minimal application
|
|
67
77
|
```Python
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
[](https://github.com/Aidbox/aidbox-python-sdk/actions/workflows/build.yaml)
|
|
2
2
|
[](https://pypi.org/project/aidbox-python-sdk/)
|
|
3
|
-
[](https://www.python.org/downloads/release/python-390/)
|
|
4
4
|
|
|
5
5
|
# aidbox-python-sdk
|
|
6
6
|
|
|
7
|
-
1. Create a python 3.
|
|
7
|
+
1. Create a python 3.9+ environment `pyenv `
|
|
8
8
|
2. Set env variables and activate virtual environment `source activate_settings.sh`
|
|
9
9
|
2. Install the required packages with `pipenv install --dev`
|
|
10
10
|
3. Make sure the app's settings are configured correctly (see `activate_settings.sh` and `aidbox_python_sdk/settings.py`). You can also
|
|
11
11
|
use environment variables to define sensitive settings, eg. DB connection variables (see example `.env-ptl`)
|
|
12
12
|
4. You can then run example with `python example.py`.
|
|
13
13
|
|
|
14
|
-
Add `APP_FAST_START_MODE=TRUE` to env_tests for fast start mode.
|
|
15
|
-
|
|
16
14
|
# Getting started
|
|
17
15
|
## Minimal application
|
|
18
16
|
```Python
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
__title__ = "aidbox-python-sdk"
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.9"
|
|
3
3
|
__author__ = "beda.software"
|
|
4
4
|
__license__ = "None"
|
|
5
|
-
__copyright__ = "Copyright
|
|
5
|
+
__copyright__ = "Copyright 2024 beda.software"
|
|
6
6
|
|
|
7
7
|
# Version synonym
|
|
8
8
|
VERSION = __version__
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
# type: ignore because fhir-py is not typed properly
|
|
1
2
|
from abc import ABC
|
|
2
3
|
|
|
3
4
|
from fhirpy.base import (
|
|
4
|
-
SyncClient,
|
|
5
5
|
AsyncClient,
|
|
6
|
-
|
|
7
|
-
AsyncSearchSet,
|
|
8
|
-
SyncResource,
|
|
6
|
+
AsyncReference,
|
|
9
7
|
AsyncResource,
|
|
8
|
+
AsyncSearchSet,
|
|
9
|
+
SyncClient,
|
|
10
10
|
SyncReference,
|
|
11
|
-
|
|
11
|
+
SyncResource,
|
|
12
|
+
SyncSearchSet,
|
|
12
13
|
)
|
|
13
|
-
from fhirpy.base.resource import
|
|
14
|
+
from fhirpy.base.resource import BaseReference, BaseResource
|
|
14
15
|
from fhirpy.base.searchset import AbstractSearchSet
|
|
15
16
|
|
|
16
17
|
__title__ = "aidbox-py"
|
|
@@ -76,13 +77,14 @@ class BaseAidboxReference(BaseReference, ABC):
|
|
|
76
77
|
Returns reference if local resource is saved
|
|
77
78
|
"""
|
|
78
79
|
if self.is_local:
|
|
79
|
-
return "{
|
|
80
|
+
return f"{self.resource_type}/{self.id}"
|
|
80
81
|
return self.get("url", None)
|
|
81
82
|
|
|
82
83
|
@property
|
|
83
84
|
def id(self):
|
|
84
85
|
if self.is_local:
|
|
85
86
|
return self.get("id", None)
|
|
87
|
+
return None
|
|
86
88
|
|
|
87
89
|
@property
|
|
88
90
|
def resource_type(self):
|
|
@@ -91,6 +93,7 @@ class BaseAidboxReference(BaseReference, ABC):
|
|
|
91
93
|
"""
|
|
92
94
|
if self.is_local:
|
|
93
95
|
return self.get("resourceType", None)
|
|
96
|
+
return None
|
|
94
97
|
|
|
95
98
|
@property
|
|
96
99
|
def is_local(self):
|
|
@@ -109,16 +112,14 @@ class SyncAidboxClient(SyncClient):
|
|
|
109
112
|
searchset_class = SyncAidboxSearchSet
|
|
110
113
|
resource_class = SyncAidboxResource
|
|
111
114
|
|
|
112
|
-
def reference(self, resource_type=None, id=None, reference=None, **kwargs):
|
|
115
|
+
def reference(self, resource_type=None, id=None, reference=None, **kwargs): # noqa: A002
|
|
113
116
|
resource_type = kwargs.pop("resourceType", resource_type)
|
|
114
117
|
if reference:
|
|
115
118
|
if reference.count("/") > 1:
|
|
116
119
|
return SyncAidboxReference(self, url=reference, **kwargs)
|
|
117
|
-
resource_type, id = reference.split("/")
|
|
120
|
+
resource_type, id = reference.split("/") # noqa: A001
|
|
118
121
|
if not resource_type and not id:
|
|
119
|
-
raise TypeError(
|
|
120
|
-
"Arguments `resource_type` and `id` or `reference`" "are required"
|
|
121
|
-
)
|
|
122
|
+
raise TypeError("Arguments `resource_type` and `id` or `reference`are required")
|
|
122
123
|
return SyncAidboxReference(self, resourceType=resource_type, id=id, **kwargs)
|
|
123
124
|
|
|
124
125
|
|
|
@@ -126,14 +127,12 @@ class AsyncAidboxClient(AsyncClient):
|
|
|
126
127
|
searchset_class = AsyncAidboxSearchSet
|
|
127
128
|
resource_class = AsyncAidboxResource
|
|
128
129
|
|
|
129
|
-
def reference(self, resource_type=None, id=None, reference=None, **kwargs):
|
|
130
|
+
def reference(self, resource_type=None, id=None, reference=None, **kwargs): # noqa: A002
|
|
130
131
|
resource_type = kwargs.pop("resourceType", resource_type)
|
|
131
132
|
if reference:
|
|
132
133
|
if reference.count("/") > 1:
|
|
133
134
|
return AsyncAidboxReference(self, url=reference, **kwargs)
|
|
134
|
-
resource_type, id = reference.split("/")
|
|
135
|
+
resource_type, id = reference.split("/") # noqa: A001
|
|
135
136
|
if not resource_type and not id:
|
|
136
|
-
raise TypeError(
|
|
137
|
-
"Arguments `resource_type` and `id` or `reference`" "are required"
|
|
138
|
-
)
|
|
137
|
+
raise TypeError("Arguments `resource_type` and `id` or `reference`are required")
|
|
139
138
|
return AsyncAidboxReference(self, resourceType=resource_type, id=id, **kwargs)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import json
|
|
2
|
+
import logging
|
|
3
3
|
|
|
4
4
|
from aiohttp import BasicAuth, ClientSession
|
|
5
5
|
from sqlalchemy import (
|
|
@@ -7,14 +7,17 @@ from sqlalchemy import (
|
|
|
7
7
|
Column,
|
|
8
8
|
DateTime,
|
|
9
9
|
Enum,
|
|
10
|
+
MetaData,
|
|
11
|
+
Table,
|
|
10
12
|
Text,
|
|
11
|
-
text,
|
|
12
13
|
TypeDecorator,
|
|
13
|
-
|
|
14
|
-
MetaData,
|
|
14
|
+
text,
|
|
15
15
|
)
|
|
16
|
+
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
|
|
17
|
+
from sqlalchemy.dialects.postgresql import dialect as postgresql_dialect
|
|
16
18
|
from sqlalchemy.sql.elements import ClauseElement
|
|
17
|
-
|
|
19
|
+
|
|
20
|
+
from aidbox_python_sdk.settings import Settings
|
|
18
21
|
|
|
19
22
|
from .exceptions import AidboxDBException
|
|
20
23
|
|
|
@@ -34,11 +37,9 @@ class _JSONB(TypeDecorator):
|
|
|
34
37
|
def process_literal_param(self, value, dialect):
|
|
35
38
|
if isinstance(value, dict):
|
|
36
39
|
return "'{}'".format(json.dumps(value).replace("'", "''"))
|
|
37
|
-
|
|
40
|
+
if isinstance(value, str):
|
|
38
41
|
return value
|
|
39
|
-
raise ValueError(
|
|
40
|
-
"Don't know how to literal-quote " "value of type {}".format(type(value))
|
|
41
|
-
)
|
|
42
|
+
raise ValueError(f"Don't know how to literal-quote value of type {type(value)}")
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
class _ARRAY(TypeDecorator):
|
|
@@ -46,12 +47,10 @@ class _ARRAY(TypeDecorator):
|
|
|
46
47
|
|
|
47
48
|
def process_literal_param(self, value, dialect):
|
|
48
49
|
if isinstance(value, list):
|
|
49
|
-
return "ARRAY{}"
|
|
50
|
-
|
|
50
|
+
return f"ARRAY{value}"
|
|
51
|
+
if isinstance(value, str):
|
|
51
52
|
return value
|
|
52
|
-
raise ValueError(
|
|
53
|
-
"Don't know how to literal-quote value of type {}".format(type(value))
|
|
54
|
-
)
|
|
53
|
+
raise ValueError(f"Don't know how to literal-quote value of type {type(value)}")
|
|
55
54
|
|
|
56
55
|
|
|
57
56
|
def create_table(table_name):
|
|
@@ -69,17 +68,18 @@ def create_table(table_name):
|
|
|
69
68
|
nullable=False,
|
|
70
69
|
),
|
|
71
70
|
Column("resource", _JSONB(astext_type=Text()), nullable=False, index=True),
|
|
72
|
-
extend_existing=True
|
|
71
|
+
extend_existing=True,
|
|
73
72
|
)
|
|
74
73
|
|
|
75
74
|
|
|
76
|
-
class DBProxy
|
|
75
|
+
class DBProxy:
|
|
77
76
|
_client = None
|
|
78
77
|
_settings = None
|
|
79
|
-
_table_cache =
|
|
78
|
+
_table_cache = None
|
|
80
79
|
|
|
81
|
-
def __init__(self, settings):
|
|
80
|
+
def __init__(self, settings: Settings):
|
|
82
81
|
self._settings = settings
|
|
82
|
+
self._table_cache = {}
|
|
83
83
|
|
|
84
84
|
async def initialize(self):
|
|
85
85
|
basic_auth = BasicAuth(
|
|
@@ -103,19 +103,17 @@ class DBProxy(object):
|
|
|
103
103
|
if not self._client:
|
|
104
104
|
raise ValueError("Client not set")
|
|
105
105
|
if not isinstance(sql_query, str):
|
|
106
|
-
ValueError("sql_query must be a str")
|
|
106
|
+
raise ValueError("sql_query must be a str")
|
|
107
107
|
if not execute and sql_query.count(";") > 1:
|
|
108
|
-
logger.warning(
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
query_url = "{}/$psql".format(self._settings.APP_INIT_URL)
|
|
108
|
+
logger.warning("Check that your query does not contain two queries separated by `;`")
|
|
109
|
+
query_url = f"{self._settings.APP_INIT_URL}/$psql"
|
|
112
110
|
async with self._client.post(
|
|
113
111
|
query_url,
|
|
114
112
|
json={"query": sql_query},
|
|
115
113
|
params={"execute": "true"} if execute else {},
|
|
116
114
|
raise_for_status=True,
|
|
117
115
|
) as resp:
|
|
118
|
-
logger.debug("$psql answer
|
|
116
|
+
logger.debug("$psql answer %s", await resp.text())
|
|
119
117
|
results = await resp.json()
|
|
120
118
|
|
|
121
119
|
if results[0]["status"] == "error":
|
|
@@ -132,16 +130,14 @@ class DBProxy(object):
|
|
|
132
130
|
|
|
133
131
|
async def alchemy(self, statement, *, execute=False):
|
|
134
132
|
if not isinstance(statement, ClauseElement):
|
|
135
|
-
ValueError("statement must be a sqlalchemy expression")
|
|
133
|
+
raise ValueError("statement must be a sqlalchemy expression")
|
|
136
134
|
query = self.compile_statement(statement)
|
|
137
135
|
logger.debug("Built query:\n%s", query)
|
|
138
136
|
return await self.raw_sql(query, execute=execute)
|
|
139
137
|
|
|
140
138
|
async def _get_all_entities_name(self):
|
|
141
139
|
# TODO: refactor using sdk.client and fetch_all
|
|
142
|
-
query_url = "{}/Entity?type=resource&_elements=id&_count=999"
|
|
143
|
-
self._settings.APP_INIT_URL
|
|
144
|
-
)
|
|
140
|
+
query_url = f"{self._settings.APP_INIT_URL}/Entity?type=resource&_elements=id&_count=999"
|
|
145
141
|
async with self._client.get(query_url, raise_for_status=True) as resp:
|
|
146
142
|
json_resp = await resp.json()
|
|
147
143
|
return [entry["resource"]["id"] for entry in json_resp["entry"]]
|
|
@@ -149,14 +145,9 @@ class DBProxy(object):
|
|
|
149
145
|
async def _init_table_cache(self):
|
|
150
146
|
table_names = await self._get_all_entities_name()
|
|
151
147
|
self._table_cache = {
|
|
148
|
+
**{table_name: {"table_name": table_name.lower()} for table_name in table_names},
|
|
152
149
|
**{
|
|
153
|
-
table_name: {"table_name": table_name.lower()}
|
|
154
|
-
for table_name in table_names
|
|
155
|
-
},
|
|
156
|
-
**{
|
|
157
|
-
"{}History".format(table_name): {
|
|
158
|
-
"table_name": "{}_history".format(table_name.lower())
|
|
159
|
-
}
|
|
150
|
+
f"{table_name}History": {"table_name": f"{table_name.lower()}_history"}
|
|
160
151
|
for table_name in table_names
|
|
161
152
|
},
|
|
162
153
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
import asyncio
|
|
3
|
-
import
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
4
|
from aiohttp import web
|
|
5
5
|
from fhirpy.base.exceptions import OperationOutcome
|
|
6
6
|
|
|
@@ -9,13 +9,13 @@ routes = web.RouteTableDef()
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
async def subscription(request, data):
|
|
12
|
-
logger.debug("Subscription handler:
|
|
12
|
+
logger.debug("Subscription handler: %s", data["handler"])
|
|
13
13
|
if "handler" not in data or "event" not in data:
|
|
14
|
-
logger.error("`handler` and/or `event` param is missing, data:
|
|
14
|
+
logger.error("`handler` and/or `event` param is missing, data: %s", data)
|
|
15
15
|
raise web.HTTPBadRequest()
|
|
16
16
|
handler = request.app["sdk"].get_subscription_handler(data["handler"])
|
|
17
17
|
if not handler:
|
|
18
|
-
logger.error("Subscription handler `
|
|
18
|
+
logger.error("Subscription handler `%s` was not found", "handler")
|
|
19
19
|
raise web.HTTPNotFound()
|
|
20
20
|
result = handler(data["event"], request)
|
|
21
21
|
if asyncio.iscoroutine(result):
|
|
@@ -26,9 +26,7 @@ async def subscription(request, data):
|
|
|
26
26
|
async def operation(request, data):
|
|
27
27
|
logger.debug("Operation handler: %s", data["operation"]["id"])
|
|
28
28
|
if "operation" not in data or "id" not in data["operation"]:
|
|
29
|
-
logger.error(
|
|
30
|
-
"`operation` or `operation[id]` param is missing, data: %s", data
|
|
31
|
-
)
|
|
29
|
+
logger.error("`operation` or `operation[id]` param is missing, data: %s", data)
|
|
32
30
|
raise web.HTTPBadRequest()
|
|
33
31
|
handler = request.app["sdk"].get_operation_handler(data["operation"]["id"])
|
|
34
32
|
if not handler:
|
|
@@ -57,10 +55,10 @@ TYPES = {
|
|
|
57
55
|
|
|
58
56
|
@routes.post("/aidbox")
|
|
59
57
|
async def dispatch(request):
|
|
60
|
-
logger.debug("Dispatch new request
|
|
58
|
+
logger.debug("Dispatch new request %s %s", request.method, request.url)
|
|
61
59
|
json = await request.json()
|
|
62
60
|
if "type" in json and json["type"] in TYPES:
|
|
63
|
-
logger.debug("Dispatch to `
|
|
61
|
+
logger.debug("Dispatch to `%s` handler", json["type"])
|
|
64
62
|
return await TYPES[json["type"]](request, json)
|
|
65
63
|
req = {
|
|
66
64
|
"method": request.method,
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import errno
|
|
3
2
|
import json
|
|
4
3
|
import logging
|
|
5
|
-
import os
|
|
6
4
|
import sys
|
|
7
5
|
from pathlib import Path
|
|
8
6
|
|
|
9
7
|
from aiohttp import BasicAuth, client_exceptions, web
|
|
8
|
+
from fhirpy.base.exceptions import OperationOutcome
|
|
10
9
|
|
|
11
10
|
from .aidboxpy import AsyncAidboxClient
|
|
12
11
|
from .db import DBProxy
|
|
13
12
|
from .handlers import routes
|
|
14
13
|
from .sdk import SDK
|
|
15
14
|
from .settings import Settings
|
|
16
|
-
from fhirpy.base.exceptions import OperationOutcome
|
|
17
15
|
|
|
18
16
|
logger = logging.getLogger("aidbox_sdk")
|
|
19
17
|
THIS_DIR = Path(__file__).parent
|
|
@@ -26,48 +24,41 @@ def setup_routes(app):
|
|
|
26
24
|
|
|
27
25
|
async def register_app(sdk: SDK, client: AsyncAidboxClient):
|
|
28
26
|
app_manifest = sdk.build_manifest()
|
|
29
|
-
|
|
27
|
+
|
|
30
28
|
try:
|
|
31
29
|
# We create app directly using execute to avoid conversion
|
|
32
|
-
await client.execute(
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
await client.execute(f"/App/{app_manifest['id']}", method="put", data=app_manifest)
|
|
31
|
+
|
|
35
32
|
logger.info("Creating seeds and applying migrations")
|
|
36
33
|
await sdk.create_seed_resources(client)
|
|
37
34
|
await sdk.apply_migrations(client)
|
|
38
35
|
logger.info("Aidbox app successfully registered")
|
|
39
36
|
except OperationOutcome as error:
|
|
40
|
-
logger.error(
|
|
41
|
-
"Error during the App registration: %s", json.dumps(error, indent=2)
|
|
42
|
-
)
|
|
37
|
+
logger.error("Error during the App registration: %s", json.dumps(error, indent=2))
|
|
43
38
|
sys.exit(errno.EINTR)
|
|
44
39
|
except (
|
|
45
40
|
client_exceptions.ServerDisconnectedError,
|
|
46
41
|
client_exceptions.ClientConnectionError,
|
|
47
42
|
):
|
|
48
|
-
logger.error(
|
|
49
|
-
"Aidbox address is unreachable {}".format(sdk.settings.APP_INIT_URL)
|
|
50
|
-
)
|
|
43
|
+
logger.error("Aidbox address is unreachable %s", sdk.settings.APP_INIT_URL)
|
|
51
44
|
sys.exit(errno.EINTR)
|
|
52
45
|
|
|
53
46
|
|
|
54
47
|
async def init_client(settings: Settings):
|
|
55
|
-
|
|
48
|
+
aidbox_client_cls = settings.AIDBOX_CLIENT_CLASS
|
|
56
49
|
basic_auth = BasicAuth(
|
|
57
50
|
login=settings.APP_INIT_CLIENT_ID,
|
|
58
51
|
password=settings.APP_INIT_CLIENT_SECRET,
|
|
59
52
|
)
|
|
60
53
|
|
|
61
|
-
return
|
|
62
|
-
"{}".format(settings.APP_INIT_URL), authorization=basic_auth.encode()
|
|
63
|
-
)
|
|
54
|
+
return aidbox_client_cls(f"{settings.APP_INIT_URL}", authorization=basic_auth.encode())
|
|
64
55
|
|
|
65
56
|
|
|
66
57
|
async def init(app):
|
|
67
58
|
app["client"] = await init_client(app["settings"])
|
|
68
59
|
app["db"] = DBProxy(app["settings"])
|
|
69
|
-
await app["db"].initialize()
|
|
70
60
|
await register_app(app["sdk"], app["client"])
|
|
61
|
+
await app["db"].initialize()
|
|
71
62
|
yield
|
|
72
63
|
await app["db"].deinitialize()
|
|
73
64
|
|
|
@@ -1,27 +1,22 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import pytest_asyncio
|
|
3
|
-
import pytest
|
|
4
1
|
import os
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import pytest_asyncio
|
|
5
|
+
from aiohttp import BasicAuth, ClientSession
|
|
5
6
|
from yarl import URL
|
|
6
|
-
from aiohttp import ClientSession, BasicAuth, hdrs
|
|
7
|
-
from aiohttp.test_utils import TestServer, TestClient, BaseTestServer
|
|
8
|
-
from aiohttp.web import Application
|
|
9
|
-
from aiohttp.client import _RequestContextManager
|
|
10
7
|
|
|
11
8
|
from main import create_app as _create_app
|
|
12
9
|
|
|
13
10
|
|
|
14
11
|
async def start_app(aiohttp_client):
|
|
15
|
-
app = await aiohttp_client(
|
|
16
|
-
_create_app(), server_kwargs={"host": "0.0.0.0", "port": 8081}
|
|
17
|
-
)
|
|
12
|
+
app = await aiohttp_client(_create_app(), server_kwargs={"host": "0.0.0.0", "port": 8081})
|
|
18
13
|
sdk = app.server.app["sdk"]
|
|
19
14
|
sdk._test_start_txid = -1
|
|
20
15
|
|
|
21
16
|
return app
|
|
22
17
|
|
|
23
18
|
|
|
24
|
-
@pytest.fixture
|
|
19
|
+
@pytest.fixture()
|
|
25
20
|
def client(event_loop, aiohttp_client):
|
|
26
21
|
"""Instance of app's server and client"""
|
|
27
22
|
return event_loop.run_until_complete(start_app(aiohttp_client))
|
|
@@ -29,7 +24,9 @@ def client(event_loop, aiohttp_client):
|
|
|
29
24
|
|
|
30
25
|
class AidboxSession(ClientSession):
|
|
31
26
|
def __init__(self, *args, base_url=None, **kwargs):
|
|
32
|
-
|
|
27
|
+
base_url_resolved = base_url or os.environ.get("AIDBOX_BASE_URL")
|
|
28
|
+
assert base_url_resolved, "Either base_url arg or AIDBOX_BASE_URL env var must be set"
|
|
29
|
+
self.base_url = URL(base_url_resolved)
|
|
33
30
|
super().__init__(*args, **kwargs)
|
|
34
31
|
|
|
35
32
|
def make_url(self, path):
|
|
@@ -71,18 +68,18 @@ async def safe_db(aidbox, client):
|
|
|
71
68
|
sdk._test_start_txid = -1
|
|
72
69
|
await aidbox.post(
|
|
73
70
|
"/$psql",
|
|
74
|
-
json={"query": "select drop_before_all({});"
|
|
71
|
+
json={"query": f"select drop_before_all({txid});"},
|
|
75
72
|
params={"execute": "true"},
|
|
76
73
|
raise_for_status=True,
|
|
77
74
|
)
|
|
78
75
|
|
|
79
76
|
|
|
80
|
-
@pytest.fixture
|
|
77
|
+
@pytest.fixture()
|
|
81
78
|
def sdk(client):
|
|
82
79
|
return client.server.app["sdk"]
|
|
83
80
|
|
|
84
81
|
|
|
85
|
-
@pytest.fixture
|
|
82
|
+
@pytest.fixture()
|
|
86
83
|
def aidbox_client(client):
|
|
87
84
|
return client.server.app["client"]
|
|
88
85
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import logging
|
|
3
|
-
import os
|
|
4
3
|
|
|
5
4
|
import jsonschema
|
|
6
5
|
from fhirpy.base.exceptions import OperationOutcome
|
|
@@ -11,8 +10,8 @@ from .db_migrations import sdk_migrations
|
|
|
11
10
|
logger = logging.getLogger("aidbox_sdk")
|
|
12
11
|
|
|
13
12
|
|
|
14
|
-
class SDK
|
|
15
|
-
def __init__(
|
|
13
|
+
class SDK:
|
|
14
|
+
def __init__( # noqa: PLR0913
|
|
16
15
|
self,
|
|
17
16
|
settings,
|
|
18
17
|
*,
|
|
@@ -40,7 +39,7 @@ class SDK(object):
|
|
|
40
39
|
self._entities = entities or {}
|
|
41
40
|
self._seeds = seeds or {}
|
|
42
41
|
self._migrations = migrations or []
|
|
43
|
-
self._app_endpoint_name = "{}-endpoint"
|
|
42
|
+
self._app_endpoint_name = f"{settings.APP_ID}-endpoint"
|
|
44
43
|
self._sub_triggered = {}
|
|
45
44
|
self._test_start_txid = None
|
|
46
45
|
|
|
@@ -67,13 +66,11 @@ class SDK(object):
|
|
|
67
66
|
resource_id,
|
|
68
67
|
resource["id"],
|
|
69
68
|
)
|
|
70
|
-
entry = {
|
|
71
|
-
"resource": {**resource, "id": resource_id, "resourceType": entity}
|
|
72
|
-
}
|
|
69
|
+
entry = {"resource": {**resource, "id": resource_id, "resourceType": entity}}
|
|
73
70
|
# Conditional create
|
|
74
71
|
entry["request"] = {
|
|
75
72
|
"method": "POST",
|
|
76
|
-
"url": "/{
|
|
73
|
+
"url": f"/{entity}?_id={resource_id}",
|
|
77
74
|
}
|
|
78
75
|
entries.append(entry)
|
|
79
76
|
bundle = client.resource("Bundle", type="transaction", entry=entries)
|
|
@@ -99,30 +96,27 @@ class SDK(object):
|
|
|
99
96
|
if self._test_start_txid is not None:
|
|
100
97
|
# Skip outside test
|
|
101
98
|
if self._test_start_txid == -1:
|
|
102
|
-
return
|
|
99
|
+
return None
|
|
103
100
|
|
|
104
101
|
# Skip inside another test
|
|
105
102
|
if int(event["tx"]["id"]) < self._test_start_txid:
|
|
106
|
-
return
|
|
103
|
+
return None
|
|
107
104
|
coro_or_result = func(event, request)
|
|
108
105
|
if asyncio.iscoroutine(coro_or_result):
|
|
109
106
|
result = await coro_or_result
|
|
110
107
|
else:
|
|
111
|
-
logger.warning(
|
|
112
|
-
"Synchronous subscription handler is deprecated: %s", path
|
|
113
|
-
)
|
|
108
|
+
logger.warning("Synchronous subscription handler is deprecated: %s", path)
|
|
114
109
|
result = coro_or_result
|
|
115
110
|
|
|
116
111
|
if entity in self._sub_triggered:
|
|
117
112
|
future, counter = self._sub_triggered[entity]
|
|
118
113
|
if counter > 1:
|
|
119
114
|
self._sub_triggered[entity] = (future, counter - 1)
|
|
115
|
+
elif future.done():
|
|
116
|
+
pass
|
|
117
|
+
# logger.warning('Uncaught subscription for %s', entity)
|
|
120
118
|
else:
|
|
121
|
-
|
|
122
|
-
pass
|
|
123
|
-
# logger.warning('Uncaught subscription for %s', entity)
|
|
124
|
-
else:
|
|
125
|
-
future.set_result(True)
|
|
119
|
+
future.set_result(True)
|
|
126
120
|
|
|
127
121
|
return result
|
|
128
122
|
|
|
@@ -149,7 +143,7 @@ class SDK(object):
|
|
|
149
143
|
def was_subscription_triggered(self, entity):
|
|
150
144
|
return self.was_subscription_triggered_n_times(entity, 1)
|
|
151
145
|
|
|
152
|
-
def operation(
|
|
146
|
+
def operation( # noqa: PLR0913
|
|
153
147
|
self,
|
|
154
148
|
methods,
|
|
155
149
|
path,
|
|
@@ -158,10 +152,8 @@ class SDK(object):
|
|
|
158
152
|
request_schema=None,
|
|
159
153
|
timeout=None,
|
|
160
154
|
):
|
|
161
|
-
if public
|
|
162
|
-
raise ValueError(
|
|
163
|
-
"Operation might be public or have access policy, not both"
|
|
164
|
-
)
|
|
155
|
+
if public and access_policy is not None:
|
|
156
|
+
raise ValueError("Operation might be public or have access policy, not both")
|
|
165
157
|
|
|
166
158
|
request_validator = None
|
|
167
159
|
if request_schema:
|
|
@@ -177,7 +169,6 @@ class SDK(object):
|
|
|
177
169
|
if isinstance(p, str):
|
|
178
170
|
_str_path.append(p)
|
|
179
171
|
elif isinstance(p, dict):
|
|
180
|
-
|
|
181
172
|
_str_path.append("__{}__".format(p["name"]))
|
|
182
173
|
|
|
183
174
|
def wrapped_func(operation, request):
|
|
@@ -44,7 +44,7 @@ class Settings:
|
|
|
44
44
|
# if not hasattr(self, name):
|
|
45
45
|
# raise TypeError('{} is not a valid setting name'.format(name))
|
|
46
46
|
setattr(self, name, value)
|
|
47
|
-
|
|
47
|
+
self.static_path = None
|
|
48
48
|
|
|
49
49
|
def substitute_environ(self):
|
|
50
50
|
"""
|
|
@@ -71,9 +71,9 @@ class Settings:
|
|
|
71
71
|
setattr(self, attr_name, env_var)
|
|
72
72
|
elif is_required and attr_name not in self._custom_settings:
|
|
73
73
|
raise RuntimeError(
|
|
74
|
-
'The required environment variable "{
|
|
74
|
+
f'The required environment variable "{env_var_name}" is currently not set, '
|
|
75
75
|
"you'll need to run `source activate.settings.sh` "
|
|
76
76
|
"or you can set that single environment variable with "
|
|
77
|
-
'`export {
|
|
78
|
-
"argument"
|
|
77
|
+
f'`export {env_var_name}="<value>"` or pass variable in `custom_settings` '
|
|
78
|
+
"argument"
|
|
79
79
|
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from typing_extensions import TypedDict
|
|
2
|
+
|
|
3
|
+
from aidbox_python_sdk.aidboxpy import AsyncAidboxClient
|
|
4
|
+
from aidbox_python_sdk.db import DBProxy
|
|
5
|
+
from aidbox_python_sdk.sdk import SDK
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SDKOperationRequestApp(TypedDict):
|
|
9
|
+
client: AsyncAidboxClient
|
|
10
|
+
db: DBProxy
|
|
11
|
+
sdk: SDK
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
SDKOperationRequest = TypedDict(
|
|
15
|
+
"SDKOperationRequest", {"app": SDKOperationRequestApp, "route-params": dict, "resource": dict}
|
|
16
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: aidbox-python-sdk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Aidbox SDK for python
|
|
5
5
|
Author-email: "beda.software" <aidbox-python-sdk@beda.software>
|
|
6
6
|
License: MIT License
|
|
@@ -35,33 +35,43 @@ Classifier: Intended Audience :: Developers
|
|
|
35
35
|
Classifier: Operating System :: OS Independent
|
|
36
36
|
Classifier: Programming Language :: Python
|
|
37
37
|
Classifier: Programming Language :: Python :: 3
|
|
38
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
39
38
|
Classifier: Programming Language :: Python :: 3.9
|
|
40
39
|
Classifier: Programming Language :: Python :: 3.10
|
|
41
40
|
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
42
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
43
43
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
44
|
Requires-Python: >=3.8
|
|
45
45
|
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE.md
|
|
47
|
+
Requires-Dist: aiohttp>=3.6.2
|
|
48
|
+
Requires-Dist: SQLAlchemy>=1.3.10
|
|
49
|
+
Requires-Dist: fhirpy>=1.3.0
|
|
50
|
+
Requires-Dist: jsonschema>=4.4.0
|
|
46
51
|
Provides-Extra: test
|
|
52
|
+
Requires-Dist: pytest~=6.0.0; extra == "test"
|
|
53
|
+
Requires-Dist: pytest-asyncio~=0.20.0; extra == "test"
|
|
54
|
+
Requires-Dist: pytest-aiohttp~=1.0.4; extra == "test"
|
|
55
|
+
Requires-Dist: pytest-cov~=4.0.0; extra == "test"
|
|
47
56
|
Provides-Extra: dev
|
|
48
|
-
|
|
57
|
+
Requires-Dist: black; extra == "dev"
|
|
58
|
+
Requires-Dist: autohooks; extra == "dev"
|
|
59
|
+
Requires-Dist: autohooks-plugin-ruff; extra == "dev"
|
|
60
|
+
Requires-Dist: autohooks-plugin-black; extra == "dev"
|
|
49
61
|
|
|
50
62
|
[](https://github.com/Aidbox/aidbox-python-sdk/actions/workflows/build.yaml)
|
|
51
63
|
[](https://pypi.org/project/aidbox-python-sdk/)
|
|
52
|
-
[](https://www.python.org/downloads/release/python-390/)
|
|
53
65
|
|
|
54
66
|
# aidbox-python-sdk
|
|
55
67
|
|
|
56
|
-
1. Create a python 3.
|
|
68
|
+
1. Create a python 3.9+ environment `pyenv `
|
|
57
69
|
2. Set env variables and activate virtual environment `source activate_settings.sh`
|
|
58
70
|
2. Install the required packages with `pipenv install --dev`
|
|
59
71
|
3. Make sure the app's settings are configured correctly (see `activate_settings.sh` and `aidbox_python_sdk/settings.py`). You can also
|
|
60
72
|
use environment variables to define sensitive settings, eg. DB connection variables (see example `.env-ptl`)
|
|
61
73
|
4. You can then run example with `python example.py`.
|
|
62
74
|
|
|
63
|
-
Add `APP_FAST_START_MODE=TRUE` to env_tests for fast start mode.
|
|
64
|
-
|
|
65
75
|
# Getting started
|
|
66
76
|
## Minimal application
|
|
67
77
|
```Python
|
|
@@ -7,12 +7,12 @@ aidbox_python_sdk/aidboxpy.py
|
|
|
7
7
|
aidbox_python_sdk/db.py
|
|
8
8
|
aidbox_python_sdk/db_migrations.py
|
|
9
9
|
aidbox_python_sdk/exceptions.py
|
|
10
|
-
aidbox_python_sdk/gunicorn.py
|
|
11
10
|
aidbox_python_sdk/handlers.py
|
|
12
11
|
aidbox_python_sdk/main.py
|
|
13
12
|
aidbox_python_sdk/pytest_plugin.py
|
|
14
13
|
aidbox_python_sdk/sdk.py
|
|
15
14
|
aidbox_python_sdk/settings.py
|
|
15
|
+
aidbox_python_sdk/types.py
|
|
16
16
|
aidbox_python_sdk.egg-info/PKG-INFO
|
|
17
17
|
aidbox_python_sdk.egg-info/SOURCES.txt
|
|
18
18
|
aidbox_python_sdk.egg-info/dependency_links.txt
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
aiohttp>=3.6.2
|
|
2
|
+
SQLAlchemy>=1.3.10
|
|
3
|
+
fhirpy>=1.3.0
|
|
4
|
+
jsonschema>=4.4.0
|
|
5
|
+
|
|
6
|
+
[dev]
|
|
7
|
+
black
|
|
8
|
+
autohooks
|
|
9
|
+
autohooks-plugin-ruff
|
|
10
|
+
autohooks-plugin-black
|
|
11
|
+
|
|
12
|
+
[test]
|
|
13
|
+
pytest~=6.0.0
|
|
14
|
+
pytest-asyncio~=0.20.0
|
|
15
|
+
pytest-aiohttp~=1.0.4
|
|
16
|
+
pytest-cov~=4.0.0
|
|
@@ -7,7 +7,7 @@ log_cli_level = "INFO"
|
|
|
7
7
|
|
|
8
8
|
[tool.autohooks]
|
|
9
9
|
mode = "pipenv"
|
|
10
|
-
pre-commit = ["autohooks.plugins.black", "autohooks.plugins.
|
|
10
|
+
pre-commit = ["autohooks.plugins.black", "autohooks.plugins.ruff"]
|
|
11
11
|
|
|
12
12
|
[tool.black]
|
|
13
13
|
line-length = 100
|
|
@@ -23,10 +23,23 @@ exclude = '''
|
|
|
23
23
|
)
|
|
24
24
|
'''
|
|
25
25
|
|
|
26
|
-
[tool.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
[tool.ruff]
|
|
27
|
+
target-version = "py311"
|
|
28
|
+
line-length = 100
|
|
29
|
+
extend-exclude = ["example"]
|
|
30
|
+
|
|
31
|
+
[tool.ruff.lint]
|
|
32
|
+
select = ["I", "E", "F", "N", "B", "C4", "PT", "UP", "I001", "A", "RET", "TID251", "RUF", "SIM", "PYI", "T20", "PIE", "G", "ISC", "PL"]
|
|
33
|
+
# E501 is disabled because line limit is controlled by black
|
|
34
|
+
# RUF005 is disabled because we use asyncio tasks without cancelling
|
|
35
|
+
# RUF015 is disabled because index access is preferred way for us
|
|
36
|
+
# RUF019 is disabled because it's needed for pyright
|
|
37
|
+
# PIE804 is disabled because we often use FHIR like camelCase variables
|
|
38
|
+
# SIM102 is disabled because nested if's are more readable
|
|
39
|
+
# SIM117 is disabled because nested with's are more readable
|
|
40
|
+
ignore = ["E501", "RUF006", "RUF015", "RUF019", "PIE804", "SIM102", "SIM117", "PLR2004"]
|
|
41
|
+
unfixable = ["F401"]
|
|
42
|
+
|
|
30
43
|
|
|
31
44
|
[tool.setuptools]
|
|
32
45
|
zip-safe = false
|
|
@@ -50,11 +63,9 @@ authors = [
|
|
|
50
63
|
{ name = "beda.software", email = "aidbox-python-sdk@beda.software" },
|
|
51
64
|
]
|
|
52
65
|
dependencies = [
|
|
53
|
-
"uvloop>=0.13.0",
|
|
54
66
|
"aiohttp>=3.6.2",
|
|
55
67
|
"SQLAlchemy>=1.3.10",
|
|
56
68
|
"fhirpy>=1.3.0",
|
|
57
|
-
"coloredlogs",
|
|
58
69
|
"jsonschema>=4.4.0",
|
|
59
70
|
]
|
|
60
71
|
classifiers = [
|
|
@@ -64,10 +75,10 @@ classifiers = [
|
|
|
64
75
|
"Operating System :: OS Independent",
|
|
65
76
|
"Programming Language :: Python",
|
|
66
77
|
"Programming Language :: Python :: 3",
|
|
67
|
-
"Programming Language :: Python :: 3.8",
|
|
68
78
|
"Programming Language :: Python :: 3.9",
|
|
69
79
|
"Programming Language :: Python :: 3.10",
|
|
70
80
|
"Programming Language :: Python :: 3.11",
|
|
81
|
+
"Programming Language :: Python :: 3.12",
|
|
71
82
|
"Topic :: Internet :: WWW/HTTP",
|
|
72
83
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
73
84
|
]
|
|
@@ -75,16 +86,15 @@ requires-python = ">=3.8"
|
|
|
75
86
|
|
|
76
87
|
[project.optional-dependencies]
|
|
77
88
|
test = [
|
|
78
|
-
"pytest
|
|
79
|
-
"pytest-asyncio
|
|
80
|
-
"pytest-aiohttp
|
|
81
|
-
"pytest-cov
|
|
89
|
+
"pytest~=6.0.0",
|
|
90
|
+
"pytest-asyncio~=0.20.0",
|
|
91
|
+
"pytest-aiohttp~=1.0.4",
|
|
92
|
+
"pytest-cov~=4.0.0"
|
|
82
93
|
]
|
|
83
94
|
dev = [
|
|
84
95
|
"black",
|
|
85
|
-
"isort",
|
|
86
96
|
"autohooks",
|
|
87
|
-
"autohooks-plugin-
|
|
97
|
+
"autohooks-plugin-ruff",
|
|
88
98
|
"autohooks-plugin-black",
|
|
89
99
|
]
|
|
90
100
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import pytest
|
|
3
2
|
import logging
|
|
4
|
-
from aiohttp import web, ClientSession, BasicAuth
|
|
5
3
|
from unittest import mock
|
|
6
4
|
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
7
|
import main
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
@pytest.mark.asyncio
|
|
10
|
+
@pytest.mark.asyncio()
|
|
11
11
|
async def test_health_check(client):
|
|
12
12
|
resp = await client.get("/health")
|
|
13
13
|
assert resp.status == 200
|
|
@@ -15,7 +15,7 @@ async def test_health_check(client):
|
|
|
15
15
|
assert json == {"status": "OK"}
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
@pytest.mark.asyncio
|
|
18
|
+
@pytest.mark.asyncio()
|
|
19
19
|
async def test_live_health_check(client):
|
|
20
20
|
resp = await client.get("/live")
|
|
21
21
|
assert resp.status == 200
|
|
@@ -23,7 +23,7 @@ async def test_live_health_check(client):
|
|
|
23
23
|
assert json == {"status": "OK"}
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
@pytest.mark.skip
|
|
26
|
+
@pytest.mark.skip()
|
|
27
27
|
async def test_signup_reg_op(client, aidbox):
|
|
28
28
|
resp = await aidbox.post("signup/register/21.02.19/testvalue")
|
|
29
29
|
assert resp.status == 200
|
|
@@ -34,7 +34,7 @@ async def test_signup_reg_op(client, aidbox):
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
@pytest.mark.skip
|
|
37
|
+
@pytest.mark.skip()
|
|
38
38
|
async def test_appointment_sub(client, aidbox):
|
|
39
39
|
with mock.patch.object(main, "_appointment_sub") as appointment_sub:
|
|
40
40
|
f = asyncio.Future()
|
|
@@ -65,7 +65,7 @@ async def test_appointment_sub(client, aidbox):
|
|
|
65
65
|
assert expected["resource"].items() <= event["resource"].items()
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
@pytest.mark.asyncio
|
|
68
|
+
@pytest.mark.asyncio()
|
|
69
69
|
async def test_database_isolation__1(aidbox_client, safe_db):
|
|
70
70
|
patients = await aidbox_client.resources("Patient").fetch_all()
|
|
71
71
|
assert len(patients) == 2
|
|
@@ -77,7 +77,7 @@ async def test_database_isolation__1(aidbox_client, safe_db):
|
|
|
77
77
|
assert len(patients) == 3
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
@pytest.mark.asyncio
|
|
80
|
+
@pytest.mark.asyncio()
|
|
81
81
|
async def test_database_isolation__2(aidbox_client, safe_db):
|
|
82
82
|
patients = await aidbox_client.resources("Patient").fetch_all()
|
|
83
83
|
assert len(patients) == 2
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
This file allows your to serve your application using gunicorn. gunicorn is not installed by default
|
|
3
|
-
by the requirements file adev creates, you'll need to install it yourself and add it to requirements.txt.
|
|
4
|
-
|
|
5
|
-
To run the aidbox_python_sdk using gunicorn, in the terminal run
|
|
6
|
-
|
|
7
|
-
pip install gunicorn
|
|
8
|
-
gunicorn aidbox_python_sdk.gunicorn:aidbox_python_sdk --worker-class aiohttp.worker.GunicornWebWorker
|
|
9
|
-
|
|
10
|
-
You could use a variant of the above with heroku (in the `Procfile`) or with Docker in the ENTRYPOINT statement.
|
|
11
|
-
"""
|
|
12
|
-
import asyncio
|
|
13
|
-
import uvloop
|
|
14
|
-
|
|
15
|
-
from .main import create_app
|
|
16
|
-
|
|
17
|
-
uvloop.install()
|
|
18
|
-
loop = asyncio.get_event_loop()
|
|
19
|
-
|
|
20
|
-
app = loop.run_until_complete(create_app())
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
uvloop>=0.13.0
|
|
2
|
-
aiohttp>=3.6.2
|
|
3
|
-
SQLAlchemy>=1.3.10
|
|
4
|
-
fhirpy>=1.3.0
|
|
5
|
-
coloredlogs
|
|
6
|
-
jsonschema>=4.4.0
|
|
7
|
-
|
|
8
|
-
[dev]
|
|
9
|
-
black
|
|
10
|
-
isort
|
|
11
|
-
autohooks
|
|
12
|
-
autohooks-plugin-isort
|
|
13
|
-
autohooks-plugin-black
|
|
14
|
-
|
|
15
|
-
[test]
|
|
16
|
-
pytest>=6.0.0
|
|
17
|
-
pytest-asyncio>=0.20.0
|
|
18
|
-
pytest-aiohttp>=1.0.4
|
|
19
|
-
pytest-cov>=4.0.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{aidbox-python-sdk-0.1.7 → aidbox_python_sdk-0.1.9}/aidbox_python_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|