aidbox-python-sdk 0.1.25__tar.gz → 0.2.1__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.25/aidbox_python_sdk.egg-info → aidbox_python_sdk-0.2.1}/PKG-INFO +78 -2
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/README.md +76 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/__init__.py +1 -1
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/db.py +19 -8
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/handlers.py +12 -5
- aidbox_python_sdk-0.2.1/aidbox_python_sdk/pytest_plugin.py +165 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/sdk.py +12 -10
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/types.py +12 -3
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1/aidbox_python_sdk.egg-info}/PKG-INFO +78 -2
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/pyproject.toml +3 -3
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/tests/test_sdk.py +48 -19
- aidbox_python_sdk-0.1.25/aidbox_python_sdk/pytest_plugin.py +0 -89
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/LICENSE.md +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/MANIFEST.in +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/aidboxpy.py +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/app_keys.py +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/db_migrations.py +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/exceptions.py +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/main.py +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/py.typed +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk/settings.py +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/SOURCES.txt +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/dependency_links.txt +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/not-zip-safe +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/requires.txt +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/top_level.txt +0 -0
- {aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aidbox-python-sdk
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Aidbox SDK for python
|
|
5
5
|
Author-email: "beda.software" <aidbox-python-sdk@beda.software>
|
|
6
6
|
License: MIT License
|
|
@@ -41,7 +41,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
41
41
|
Classifier: Programming Language :: Python :: 3.12
|
|
42
42
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
43
43
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
-
Requires-Python: >=3.
|
|
44
|
+
Requires-Python: >=3.9
|
|
45
45
|
Description-Content-Type: text/markdown
|
|
46
46
|
License-File: LICENSE.md
|
|
47
47
|
Requires-Dist: aiohttp>=3.11.0
|
|
@@ -239,4 +239,80 @@ organizationType: non-profit
|
|
|
239
239
|
employeesCount: 10
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
+
## Testing with the pytest plugin
|
|
243
|
+
|
|
244
|
+
The SDK provides a pytest plugin that starts your app, exposes fixtures for the SDK and Aidbox client, and helps isolate tests that create resources.
|
|
245
|
+
|
|
246
|
+
### Activating the plugin
|
|
247
|
+
|
|
248
|
+
In your project’s **`conftest.py`** (e.g. `tests/conftest.py`), register the plugin:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
pytest_plugins = ["aidbox_python_sdk.pytest_plugin"]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Configuring the app factory
|
|
255
|
+
|
|
256
|
+
The plugin needs your app factory (the callable that returns the `web.Application`). You can set it in pytest ini:
|
|
257
|
+
|
|
258
|
+
**`pyproject.toml`**
|
|
259
|
+
```toml
|
|
260
|
+
[tool.pytest.ini_options]
|
|
261
|
+
aidbox_create_app = "main:create_app"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Use the dotted path to your callable: either `module:name` (e.g. `main:create_app`) or `module.submodule.name` (e.g. `mypackage.main.create_app`). The default is `main:create_app`.
|
|
265
|
+
|
|
266
|
+
To use a different factory without changing ini, override the fixture in your `conftest.py`:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
@pytest.fixture(scope="session")
|
|
270
|
+
def create_app():
|
|
271
|
+
from myapp.entry import create_app
|
|
272
|
+
return create_app
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Fixtures provided
|
|
276
|
+
|
|
277
|
+
| Fixture | Description |
|
|
278
|
+
|------------------|-------------|
|
|
279
|
+
| `app` | The running `web.Application` (server in a background thread on port 8081). |
|
|
280
|
+
| `client` | HTTP client for the app + `client.server.app` for the application instance. |
|
|
281
|
+
| `sdk` | The SDK instance: `app[ak.sdk]`. |
|
|
282
|
+
| `aidbox_client` | `AsyncAidboxClient` for calling Aidbox (operations, `/$psql`, etc.). |
|
|
283
|
+
| `aidbox_db` | DB proxy: `app[ak.db]`. |
|
|
284
|
+
| `safe_db` | Isolated DB for the test; see below. |
|
|
285
|
+
|
|
286
|
+
### Using `safe_db` for tests that create resources
|
|
287
|
+
|
|
288
|
+
Use the **`safe_db`** fixture in tests that create or change data. It records the current transaction id, runs your test, then rolls back everything created in that test so the DB stays clean.
|
|
289
|
+
|
|
290
|
+
**NOTE:** Without `safe_db`, all subscriptions are implicitly ignored for that test.
|
|
291
|
+
|
|
292
|
+
Example:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
@pytest.mark.asyncio
|
|
296
|
+
async def test_create_patient(aidbox_client, safe_db):
|
|
297
|
+
patient = await aidbox_client.resource("Patient", name=[{"family": "Test"}]).save()
|
|
298
|
+
patients = await aidbox_client.resources("Patient").fetch_all()
|
|
299
|
+
assert len(patients) >= 1
|
|
300
|
+
# after the test, safe_db rolls back and the patient is not persisted
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Subscription trigger helper (`was_subscription_triggered`)
|
|
304
|
+
|
|
305
|
+
Use **`sdk.was_subscription_triggered(entity)`** (or `was_subscription_triggered_n_times(entity, n)`) only together with the **`safe_db`** fixture. Without `safe_db`, subscription handling is skipped for the test and the returned future is never completed, so the test will hang until the timeout.
|
|
306
|
+
|
|
307
|
+
Example:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
@pytest.mark.asyncio
|
|
311
|
+
async def test_patient_subscription(aidbox_client, safe_db, sdk):
|
|
312
|
+
was_patient_sub_triggered = sdk.was_subscription_triggered("Patient")
|
|
313
|
+
patient = await aidbox_client.resource("Patient", name=[{"family": "Test"}]).save()
|
|
314
|
+
await was_patient_sub_triggered
|
|
315
|
+
# assertions...
|
|
316
|
+
```
|
|
317
|
+
|
|
242
318
|
|
|
@@ -177,4 +177,80 @@ organizationType: non-profit
|
|
|
177
177
|
employeesCount: 10
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
+
## Testing with the pytest plugin
|
|
181
|
+
|
|
182
|
+
The SDK provides a pytest plugin that starts your app, exposes fixtures for the SDK and Aidbox client, and helps isolate tests that create resources.
|
|
183
|
+
|
|
184
|
+
### Activating the plugin
|
|
185
|
+
|
|
186
|
+
In your project’s **`conftest.py`** (e.g. `tests/conftest.py`), register the plugin:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
pytest_plugins = ["aidbox_python_sdk.pytest_plugin"]
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Configuring the app factory
|
|
193
|
+
|
|
194
|
+
The plugin needs your app factory (the callable that returns the `web.Application`). You can set it in pytest ini:
|
|
195
|
+
|
|
196
|
+
**`pyproject.toml`**
|
|
197
|
+
```toml
|
|
198
|
+
[tool.pytest.ini_options]
|
|
199
|
+
aidbox_create_app = "main:create_app"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Use the dotted path to your callable: either `module:name` (e.g. `main:create_app`) or `module.submodule.name` (e.g. `mypackage.main.create_app`). The default is `main:create_app`.
|
|
203
|
+
|
|
204
|
+
To use a different factory without changing ini, override the fixture in your `conftest.py`:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
@pytest.fixture(scope="session")
|
|
208
|
+
def create_app():
|
|
209
|
+
from myapp.entry import create_app
|
|
210
|
+
return create_app
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Fixtures provided
|
|
214
|
+
|
|
215
|
+
| Fixture | Description |
|
|
216
|
+
|------------------|-------------|
|
|
217
|
+
| `app` | The running `web.Application` (server in a background thread on port 8081). |
|
|
218
|
+
| `client` | HTTP client for the app + `client.server.app` for the application instance. |
|
|
219
|
+
| `sdk` | The SDK instance: `app[ak.sdk]`. |
|
|
220
|
+
| `aidbox_client` | `AsyncAidboxClient` for calling Aidbox (operations, `/$psql`, etc.). |
|
|
221
|
+
| `aidbox_db` | DB proxy: `app[ak.db]`. |
|
|
222
|
+
| `safe_db` | Isolated DB for the test; see below. |
|
|
223
|
+
|
|
224
|
+
### Using `safe_db` for tests that create resources
|
|
225
|
+
|
|
226
|
+
Use the **`safe_db`** fixture in tests that create or change data. It records the current transaction id, runs your test, then rolls back everything created in that test so the DB stays clean.
|
|
227
|
+
|
|
228
|
+
**NOTE:** Without `safe_db`, all subscriptions are implicitly ignored for that test.
|
|
229
|
+
|
|
230
|
+
Example:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
@pytest.mark.asyncio
|
|
234
|
+
async def test_create_patient(aidbox_client, safe_db):
|
|
235
|
+
patient = await aidbox_client.resource("Patient", name=[{"family": "Test"}]).save()
|
|
236
|
+
patients = await aidbox_client.resources("Patient").fetch_all()
|
|
237
|
+
assert len(patients) >= 1
|
|
238
|
+
# after the test, safe_db rolls back and the patient is not persisted
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Subscription trigger helper (`was_subscription_triggered`)
|
|
242
|
+
|
|
243
|
+
Use **`sdk.was_subscription_triggered(entity)`** (or `was_subscription_triggered_n_times(entity, n)`) only together with the **`safe_db`** fixture. Without `safe_db`, subscription handling is skipped for the test and the returned future is never completed, so the test will hang until the timeout.
|
|
244
|
+
|
|
245
|
+
Example:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
@pytest.mark.asyncio
|
|
249
|
+
async def test_patient_subscription(aidbox_client, safe_db, sdk):
|
|
250
|
+
was_patient_sub_triggered = sdk.was_subscription_triggered("Patient")
|
|
251
|
+
patient = await aidbox_client.resource("Patient", name=[{"family": "Test"}]).save()
|
|
252
|
+
await was_patient_sub_triggered
|
|
253
|
+
# assertions...
|
|
254
|
+
```
|
|
255
|
+
|
|
180
256
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
from typing import Optional
|
|
3
4
|
|
|
4
5
|
from aiohttp import BasicAuth, ClientSession
|
|
5
6
|
from sqlalchemy import (
|
|
@@ -73,13 +74,13 @@ def create_table(table_name):
|
|
|
73
74
|
|
|
74
75
|
|
|
75
76
|
class DBProxy:
|
|
76
|
-
_client = None
|
|
77
|
-
_settings
|
|
78
|
-
_table_cache
|
|
77
|
+
_client: Optional[ClientSession] = None
|
|
78
|
+
_settings: Settings
|
|
79
|
+
_table_cache: dict
|
|
79
80
|
|
|
80
|
-
def __init__(self, settings: Settings):
|
|
81
|
+
def __init__(self, settings: Settings, _table_cache: Optional[dict] = None):
|
|
81
82
|
self._settings = settings
|
|
82
|
-
self._table_cache = {}
|
|
83
|
+
self._table_cache = _table_cache or {}
|
|
83
84
|
|
|
84
85
|
async def initialize(self):
|
|
85
86
|
basic_auth = BasicAuth(
|
|
@@ -87,12 +88,20 @@ class DBProxy:
|
|
|
87
88
|
password=self._settings.APP_INIT_CLIENT_SECRET,
|
|
88
89
|
)
|
|
89
90
|
self._client = ClientSession(auth=basic_auth)
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
if not self._table_cache:
|
|
92
|
+
await self._init_table_cache()
|
|
92
93
|
|
|
93
94
|
async def deinitialize(self):
|
|
94
95
|
await self._client.close()
|
|
95
96
|
|
|
97
|
+
def clone(self) -> "DBProxy":
|
|
98
|
+
"""
|
|
99
|
+
Create a new DBProxy with the same settings and table cache
|
|
100
|
+
NOTE: it should be initialized after cloning
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
return DBProxy(self._settings, _table_cache=self._table_cache)
|
|
104
|
+
|
|
96
105
|
async def raw_sql(self, sql_query, *, execute=False):
|
|
97
106
|
"""
|
|
98
107
|
Executes SQL query and returns result. Specify `execute` to True
|
|
@@ -146,7 +155,9 @@ class DBProxy:
|
|
|
146
155
|
|
|
147
156
|
if not result:
|
|
148
157
|
# Support legacy instalations with entity attribute data structure
|
|
149
|
-
query_url =
|
|
158
|
+
query_url = (
|
|
159
|
+
f"{self._settings.APP_INIT_URL}/Entity?type=resource&_elements=id&_count=999"
|
|
160
|
+
)
|
|
150
161
|
async with self._client.get(query_url) as resp:
|
|
151
162
|
json_resp = await resp.json()
|
|
152
163
|
result = [entry["resource"]["id"] for entry in json_resp.get("entry", [])]
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import json
|
|
2
3
|
import logging
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from aiohttp import web
|
|
6
|
-
from fhirpy.base.exceptions import OperationOutcome
|
|
7
|
+
from fhirpy.base.exceptions import BaseFHIRError, OperationOutcome
|
|
7
8
|
|
|
8
9
|
from . import app_keys as ak
|
|
9
10
|
|
|
@@ -48,6 +49,12 @@ async def operation(request: web.Request, data: dict[str, Any]):
|
|
|
48
49
|
return result
|
|
49
50
|
except OperationOutcome as exc:
|
|
50
51
|
return web.json_response(exc.resource, status=422)
|
|
52
|
+
except BaseFHIRError as exc:
|
|
53
|
+
try:
|
|
54
|
+
payload = json.loads(str(exc))
|
|
55
|
+
return web.json_response(payload, status=422)
|
|
56
|
+
except (json.JSONDecodeError, TypeError):
|
|
57
|
+
return web.Response(text=str(exc), status=422, content_type="text/plain")
|
|
51
58
|
|
|
52
59
|
|
|
53
60
|
TYPES = {
|
|
@@ -59,10 +66,10 @@ TYPES = {
|
|
|
59
66
|
@routes.post("/aidbox")
|
|
60
67
|
async def dispatch(request):
|
|
61
68
|
logger.debug("Dispatch new request %s %s", request.method, request.url)
|
|
62
|
-
|
|
63
|
-
if "type" in
|
|
64
|
-
logger.debug("Dispatch to `%s` handler",
|
|
65
|
-
return await TYPES[
|
|
69
|
+
data = await request.json()
|
|
70
|
+
if "type" in data and data["type"] in TYPES:
|
|
71
|
+
logger.debug("Dispatch to `%s` handler", data["type"])
|
|
72
|
+
return await TYPES[data["type"]](request, data)
|
|
66
73
|
req = {
|
|
67
74
|
"method": request.method,
|
|
68
75
|
"url": str(request.url),
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import importlib
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
5
|
+
import warnings
|
|
6
|
+
from collections.abc import Generator
|
|
7
|
+
from types import SimpleNamespace
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
from aiohttp import BasicAuth, ClientSession, web
|
|
11
|
+
from yarl import URL
|
|
12
|
+
|
|
13
|
+
from aidbox_python_sdk.aidboxpy import AsyncAidboxClient
|
|
14
|
+
|
|
15
|
+
from . import app_keys as ak
|
|
16
|
+
|
|
17
|
+
_TEST_SERVER_URL = "http://127.0.0.1:8081"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def pytest_addoption(parser):
|
|
21
|
+
parser.addini(
|
|
22
|
+
"aidbox_create_app",
|
|
23
|
+
"Dotted path to the create_app callable (module:name or module.name), e.g. main:create_app",
|
|
24
|
+
default="main:create_app",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _load_create_app(path: str):
|
|
29
|
+
"""Import and return the create_app callable from the given dotted path."""
|
|
30
|
+
if ":" in path:
|
|
31
|
+
module_path, attr = path.split(":", 1)
|
|
32
|
+
else:
|
|
33
|
+
module_path, attr = path.rsplit(".", 1)
|
|
34
|
+
mod = importlib.import_module(module_path)
|
|
35
|
+
return getattr(mod, attr)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture(scope="session")
|
|
39
|
+
def create_app(request):
|
|
40
|
+
"""App factory; override in conftest or set pytest ini option aidbox_create_app."""
|
|
41
|
+
path = request.config.getini("aidbox_create_app")
|
|
42
|
+
return _load_create_app(path)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture(scope="session", autouse=True)
|
|
46
|
+
def app(create_app) -> Generator[web.Application, None, None]:
|
|
47
|
+
"""Start the aiohttp application server in a background thread.
|
|
48
|
+
|
|
49
|
+
Uses a dedicated event loop running continuously via loop.run_forever()
|
|
50
|
+
in a daemon thread. This is necessary (rather than an async fixture) because
|
|
51
|
+
the app needs the loop running at all times to handle external callbacks
|
|
52
|
+
from Aidbox (subscriptions, SDK heartbeats, etc.), not just during await
|
|
53
|
+
points in test code.
|
|
54
|
+
|
|
55
|
+
The server is guaranteed to be listening before any test runs (no sleep-based
|
|
56
|
+
waiting) since site.start() completes synchronously before yielding.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
app = create_app()
|
|
60
|
+
app[ak.sdk]._test_start_txid = -1
|
|
61
|
+
loop = asyncio.new_event_loop()
|
|
62
|
+
|
|
63
|
+
runner = web.AppRunner(app)
|
|
64
|
+
loop.run_until_complete(runner.setup())
|
|
65
|
+
site = web.TCPSite(runner, host="0.0.0.0", port=8081)
|
|
66
|
+
loop.run_until_complete(site.start())
|
|
67
|
+
|
|
68
|
+
thread = threading.Thread(target=loop.run_forever, daemon=True)
|
|
69
|
+
thread.start()
|
|
70
|
+
|
|
71
|
+
yield app
|
|
72
|
+
|
|
73
|
+
loop.call_soon_threadsafe(loop.stop)
|
|
74
|
+
thread.join(timeout=5)
|
|
75
|
+
loop.run_until_complete(runner.cleanup())
|
|
76
|
+
loop.close()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.fixture
|
|
80
|
+
async def client(app):
|
|
81
|
+
server = SimpleNamespace(app=app)
|
|
82
|
+
session = ClientSession(base_url=URL(_TEST_SERVER_URL))
|
|
83
|
+
wrapper = SimpleNamespace(server=server)
|
|
84
|
+
wrapper.get = session.get
|
|
85
|
+
wrapper.post = session.post
|
|
86
|
+
wrapper.put = session.put
|
|
87
|
+
wrapper.patch = session.patch
|
|
88
|
+
wrapper.delete = session.delete
|
|
89
|
+
wrapper.request = session.request
|
|
90
|
+
wrapper._session = session
|
|
91
|
+
try:
|
|
92
|
+
yield wrapper
|
|
93
|
+
finally:
|
|
94
|
+
await session.close()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.fixture
|
|
98
|
+
async def safe_db(aidbox_client: AsyncAidboxClient, sdk):
|
|
99
|
+
results = await aidbox_client.execute(
|
|
100
|
+
"/$psql",
|
|
101
|
+
data={"query": "SELECT last_value from transaction_id_seq;"},
|
|
102
|
+
)
|
|
103
|
+
txid = results[0]["result"][0]["last_value"]
|
|
104
|
+
sdk._test_start_txid = int(txid)
|
|
105
|
+
|
|
106
|
+
yield txid
|
|
107
|
+
|
|
108
|
+
sdk._test_start_txid = -1
|
|
109
|
+
await aidbox_client.execute(
|
|
110
|
+
"/$psql",
|
|
111
|
+
data={"query": f"select drop_before_all({txid});"},
|
|
112
|
+
params={"execute": "true"},
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.fixture
|
|
117
|
+
def sdk(app):
|
|
118
|
+
return app[ak.sdk]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@pytest.fixture
|
|
122
|
+
def aidbox_client(app):
|
|
123
|
+
return app[ak.client]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.fixture
|
|
127
|
+
async def aidbox_db(app):
|
|
128
|
+
# We clone it to init client session in the test thread
|
|
129
|
+
# because app is running in another thread with own loop
|
|
130
|
+
db = app[ak.db].clone()
|
|
131
|
+
await db.initialize()
|
|
132
|
+
yield db
|
|
133
|
+
await db.deinitialize()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Deprecated
|
|
137
|
+
class AidboxSession(ClientSession):
|
|
138
|
+
def __init__(self, *args, base_url=None, **kwargs):
|
|
139
|
+
base_url_resolved = base_url or os.environ.get("AIDBOX_BASE_URL")
|
|
140
|
+
assert base_url_resolved, "Either base_url arg or AIDBOX_BASE_URL env var must be set"
|
|
141
|
+
self.base_url = URL(base_url_resolved)
|
|
142
|
+
super().__init__(*args, **kwargs)
|
|
143
|
+
|
|
144
|
+
def make_url(self, path):
|
|
145
|
+
return self.base_url.with_path(path)
|
|
146
|
+
|
|
147
|
+
async def _request(self, method, path, *args, **kwargs):
|
|
148
|
+
url = self.make_url(path)
|
|
149
|
+
return await super()._request(method, url, *args, **kwargs)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@pytest.fixture
|
|
153
|
+
async def aidbox(sdk, app):
|
|
154
|
+
warnings.warn(
|
|
155
|
+
"The 'aidbox' fixture is deprecated; use 'aidbox_client' for the Aidbox client instead.",
|
|
156
|
+
DeprecationWarning,
|
|
157
|
+
stacklevel=2,
|
|
158
|
+
)
|
|
159
|
+
basic_auth = BasicAuth(
|
|
160
|
+
login=app[ak.settings].APP_INIT_CLIENT_ID,
|
|
161
|
+
password=app[ak.settings].APP_INIT_CLIENT_SECRET,
|
|
162
|
+
)
|
|
163
|
+
session = AidboxSession(auth=basic_auth, base_url=app[ak.settings].APP_INIT_URL)
|
|
164
|
+
yield session
|
|
165
|
+
await session.close()
|
|
@@ -11,9 +11,12 @@ from .types import Compliance
|
|
|
11
11
|
|
|
12
12
|
logger = logging.getLogger("aidbox_sdk")
|
|
13
13
|
|
|
14
|
+
# (target_loop, future, counter) per entity for was_subscription_triggered_*
|
|
15
|
+
_SubTriggered = dict[str, tuple[asyncio.AbstractEventLoop, asyncio.Future[bool], int]]
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
class SDK:
|
|
16
|
-
def __init__(
|
|
19
|
+
def __init__(
|
|
17
20
|
self,
|
|
18
21
|
settings,
|
|
19
22
|
*,
|
|
@@ -42,7 +45,7 @@ class SDK:
|
|
|
42
45
|
self._seeds = seeds or {}
|
|
43
46
|
self._migrations = migrations or []
|
|
44
47
|
self._app_endpoint_name = f"{settings.APP_ID}-endpoint"
|
|
45
|
-
self._sub_triggered = {}
|
|
48
|
+
self._sub_triggered: _SubTriggered = {}
|
|
46
49
|
self._test_start_txid = None
|
|
47
50
|
|
|
48
51
|
async def apply_migrations(self, client: AsyncAidboxClient):
|
|
@@ -112,14 +115,14 @@ class SDK:
|
|
|
112
115
|
result = coro_or_result
|
|
113
116
|
|
|
114
117
|
if entity in self._sub_triggered:
|
|
115
|
-
future, counter = self._sub_triggered[entity]
|
|
118
|
+
target_loop, future, counter = self._sub_triggered[entity]
|
|
116
119
|
if counter > 1:
|
|
117
|
-
self._sub_triggered[entity] = (future, counter - 1)
|
|
120
|
+
self._sub_triggered[entity] = (target_loop, future, counter - 1)
|
|
118
121
|
elif future.done():
|
|
119
122
|
pass
|
|
120
123
|
# logger.warning('Uncaught subscription for %s', entity)
|
|
121
124
|
else:
|
|
122
|
-
future.set_result
|
|
125
|
+
target_loop.call_soon_threadsafe(future.set_result, True)
|
|
123
126
|
|
|
124
127
|
return result
|
|
125
128
|
|
|
@@ -133,14 +136,13 @@ class SDK:
|
|
|
133
136
|
|
|
134
137
|
def was_subscription_triggered_n_times(self, entity, counter):
|
|
135
138
|
timeout = 10
|
|
136
|
-
|
|
137
|
-
future =
|
|
138
|
-
self._sub_triggered[entity] = (future, counter)
|
|
139
|
-
|
|
139
|
+
target_loop = asyncio.get_running_loop()
|
|
140
|
+
future = target_loop.create_future()
|
|
141
|
+
self._sub_triggered[entity] = (target_loop, future, counter)
|
|
142
|
+
target_loop.call_later(
|
|
140
143
|
timeout,
|
|
141
144
|
lambda: None if future.done() else future.set_exception(Exception()),
|
|
142
145
|
)
|
|
143
|
-
|
|
144
146
|
return future
|
|
145
147
|
|
|
146
148
|
def was_subscription_triggered(self, entity):
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any
|
|
2
2
|
|
|
3
3
|
from aiohttp import web
|
|
4
4
|
from typing_extensions import TypedDict
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
class Compliance(TypedDict, total=True):
|
|
7
8
|
fhirUrl: str
|
|
8
9
|
fhirCode: str
|
|
9
|
-
fhirResource:
|
|
10
|
+
fhirResource: list[str]
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
SDKOperationRequest = TypedDict(
|
|
12
14
|
"SDKOperationRequest",
|
|
13
|
-
{
|
|
15
|
+
{
|
|
16
|
+
"app": web.Application,
|
|
17
|
+
"params": dict,
|
|
18
|
+
"route-params": dict,
|
|
19
|
+
"form-params": dict,
|
|
20
|
+
"headers": dict,
|
|
21
|
+
"resource": Any,
|
|
22
|
+
},
|
|
14
23
|
)
|
|
15
24
|
|
|
16
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aidbox-python-sdk
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Aidbox SDK for python
|
|
5
5
|
Author-email: "beda.software" <aidbox-python-sdk@beda.software>
|
|
6
6
|
License: MIT License
|
|
@@ -41,7 +41,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
41
41
|
Classifier: Programming Language :: Python :: 3.12
|
|
42
42
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
43
43
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
44
|
-
Requires-Python: >=3.
|
|
44
|
+
Requires-Python: >=3.9
|
|
45
45
|
Description-Content-Type: text/markdown
|
|
46
46
|
License-File: LICENSE.md
|
|
47
47
|
Requires-Dist: aiohttp>=3.11.0
|
|
@@ -239,4 +239,80 @@ organizationType: non-profit
|
|
|
239
239
|
employeesCount: 10
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
+
## Testing with the pytest plugin
|
|
243
|
+
|
|
244
|
+
The SDK provides a pytest plugin that starts your app, exposes fixtures for the SDK and Aidbox client, and helps isolate tests that create resources.
|
|
245
|
+
|
|
246
|
+
### Activating the plugin
|
|
247
|
+
|
|
248
|
+
In your project’s **`conftest.py`** (e.g. `tests/conftest.py`), register the plugin:
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
pytest_plugins = ["aidbox_python_sdk.pytest_plugin"]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Configuring the app factory
|
|
255
|
+
|
|
256
|
+
The plugin needs your app factory (the callable that returns the `web.Application`). You can set it in pytest ini:
|
|
257
|
+
|
|
258
|
+
**`pyproject.toml`**
|
|
259
|
+
```toml
|
|
260
|
+
[tool.pytest.ini_options]
|
|
261
|
+
aidbox_create_app = "main:create_app"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Use the dotted path to your callable: either `module:name` (e.g. `main:create_app`) or `module.submodule.name` (e.g. `mypackage.main.create_app`). The default is `main:create_app`.
|
|
265
|
+
|
|
266
|
+
To use a different factory without changing ini, override the fixture in your `conftest.py`:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
@pytest.fixture(scope="session")
|
|
270
|
+
def create_app():
|
|
271
|
+
from myapp.entry import create_app
|
|
272
|
+
return create_app
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Fixtures provided
|
|
276
|
+
|
|
277
|
+
| Fixture | Description |
|
|
278
|
+
|------------------|-------------|
|
|
279
|
+
| `app` | The running `web.Application` (server in a background thread on port 8081). |
|
|
280
|
+
| `client` | HTTP client for the app + `client.server.app` for the application instance. |
|
|
281
|
+
| `sdk` | The SDK instance: `app[ak.sdk]`. |
|
|
282
|
+
| `aidbox_client` | `AsyncAidboxClient` for calling Aidbox (operations, `/$psql`, etc.). |
|
|
283
|
+
| `aidbox_db` | DB proxy: `app[ak.db]`. |
|
|
284
|
+
| `safe_db` | Isolated DB for the test; see below. |
|
|
285
|
+
|
|
286
|
+
### Using `safe_db` for tests that create resources
|
|
287
|
+
|
|
288
|
+
Use the **`safe_db`** fixture in tests that create or change data. It records the current transaction id, runs your test, then rolls back everything created in that test so the DB stays clean.
|
|
289
|
+
|
|
290
|
+
**NOTE:** Without `safe_db`, all subscriptions are implicitly ignored for that test.
|
|
291
|
+
|
|
292
|
+
Example:
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
@pytest.mark.asyncio
|
|
296
|
+
async def test_create_patient(aidbox_client, safe_db):
|
|
297
|
+
patient = await aidbox_client.resource("Patient", name=[{"family": "Test"}]).save()
|
|
298
|
+
patients = await aidbox_client.resources("Patient").fetch_all()
|
|
299
|
+
assert len(patients) >= 1
|
|
300
|
+
# after the test, safe_db rolls back and the patient is not persisted
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Subscription trigger helper (`was_subscription_triggered`)
|
|
304
|
+
|
|
305
|
+
Use **`sdk.was_subscription_triggered(entity)`** (or `was_subscription_triggered_n_times(entity, n)`) only together with the **`safe_db`** fixture. Without `safe_db`, subscription handling is skipped for the test and the returned future is never completed, so the test will hang until the timeout.
|
|
306
|
+
|
|
307
|
+
Example:
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
@pytest.mark.asyncio
|
|
311
|
+
async def test_patient_subscription(aidbox_client, safe_db, sdk):
|
|
312
|
+
was_patient_sub_triggered = sdk.was_subscription_triggered("Patient")
|
|
313
|
+
patient = await aidbox_client.resource("Patient", name=[{"family": "Test"}]).save()
|
|
314
|
+
await was_patient_sub_triggered
|
|
315
|
+
# assertions...
|
|
316
|
+
```
|
|
317
|
+
|
|
242
318
|
|
|
@@ -12,7 +12,7 @@ pre-commit = ["autohooks.plugins.black", "autohooks.plugins.ruff"]
|
|
|
12
12
|
|
|
13
13
|
[tool.black]
|
|
14
14
|
line-length = 100
|
|
15
|
-
target-version = ['
|
|
15
|
+
target-version = ['py39']
|
|
16
16
|
exclude = '''
|
|
17
17
|
(
|
|
18
18
|
/(
|
|
@@ -25,7 +25,7 @@ exclude = '''
|
|
|
25
25
|
'''
|
|
26
26
|
|
|
27
27
|
[tool.ruff]
|
|
28
|
-
target-version = "
|
|
28
|
+
target-version = "py39"
|
|
29
29
|
line-length = 100
|
|
30
30
|
extend-exclude = ["example"]
|
|
31
31
|
|
|
@@ -83,7 +83,7 @@ classifiers = [
|
|
|
83
83
|
"Topic :: Internet :: WWW/HTTP",
|
|
84
84
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
85
85
|
]
|
|
86
|
-
requires-python = ">=3.
|
|
86
|
+
requires-python = ">=3.9"
|
|
87
87
|
|
|
88
88
|
[project.optional-dependencies]
|
|
89
89
|
test = [
|
|
@@ -4,12 +4,14 @@ from unittest import mock
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
from fhirpathpy import evaluate
|
|
7
|
+
from fhirpy.base.exceptions import OperationOutcome
|
|
7
8
|
|
|
8
9
|
import main
|
|
10
|
+
from aidbox_python_sdk.db import DBProxy
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
@pytest.mark.skip("Skipped because of regression in Aidbox 2510")
|
|
12
|
-
@pytest.mark.asyncio
|
|
14
|
+
@pytest.mark.asyncio
|
|
13
15
|
@pytest.mark.parametrize(
|
|
14
16
|
("expression", "expected"),
|
|
15
17
|
[
|
|
@@ -36,7 +38,7 @@ async def test_operation_with_compliance_params(aidbox_client, expression, expec
|
|
|
36
38
|
assert evaluate(response, expression, {})[0] == expected
|
|
37
39
|
|
|
38
40
|
|
|
39
|
-
@pytest.mark.asyncio
|
|
41
|
+
@pytest.mark.asyncio
|
|
40
42
|
async def test_health_check(client):
|
|
41
43
|
resp = await client.get("/health")
|
|
42
44
|
assert resp.status == 200
|
|
@@ -44,7 +46,7 @@ async def test_health_check(client):
|
|
|
44
46
|
assert json == {"status": "OK"}
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
@pytest.mark.asyncio
|
|
49
|
+
@pytest.mark.asyncio
|
|
48
50
|
async def test_live_health_check(client):
|
|
49
51
|
resp = await client.get("/live")
|
|
50
52
|
assert resp.status == 200
|
|
@@ -52,34 +54,30 @@ async def test_live_health_check(client):
|
|
|
52
54
|
assert json == {"status": "OK"}
|
|
53
55
|
|
|
54
56
|
|
|
55
|
-
@pytest.mark.
|
|
56
|
-
async def test_signup_reg_op(
|
|
57
|
-
|
|
58
|
-
assert resp.status == 200
|
|
59
|
-
json = await resp.json()
|
|
57
|
+
@pytest.mark.asyncio
|
|
58
|
+
async def test_signup_reg_op(aidbox_client):
|
|
59
|
+
json = await aidbox_client.execute("signup/register/21.02.19/testvalue")
|
|
60
60
|
assert json == {
|
|
61
61
|
"success": "Ok",
|
|
62
62
|
"request": {"date": "21.02.19", "test": "testvalue"},
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
|
|
66
|
-
@pytest.mark.
|
|
67
|
-
async def test_appointment_sub(
|
|
66
|
+
@pytest.mark.asyncio
|
|
67
|
+
async def test_appointment_sub(sdk, aidbox_client, safe_db):
|
|
68
68
|
with mock.patch.object(main, "_appointment_sub") as appointment_sub:
|
|
69
69
|
f = asyncio.Future()
|
|
70
70
|
f.set_result("")
|
|
71
71
|
appointment_sub.return_value = f
|
|
72
|
-
sdk = client.server.app["sdk"]
|
|
73
72
|
was_appointment_sub_triggered = sdk.was_subscription_triggered("Appointment")
|
|
74
|
-
|
|
73
|
+
resource = aidbox_client.resource(
|
|
75
74
|
"Appointment",
|
|
76
|
-
|
|
75
|
+
**{
|
|
77
76
|
"status": "proposed",
|
|
78
77
|
"participant": [{"status": "accepted"}],
|
|
79
|
-
"resourceType": "Appointment",
|
|
80
78
|
},
|
|
81
79
|
)
|
|
82
|
-
|
|
80
|
+
await resource.save()
|
|
83
81
|
await was_appointment_sub_triggered
|
|
84
82
|
event = appointment_sub.call_args_list[0][0][0]
|
|
85
83
|
logging.debug("event: %s", event)
|
|
@@ -94,7 +92,7 @@ async def test_appointment_sub(client, aidbox):
|
|
|
94
92
|
assert expected["resource"].items() <= event["resource"].items()
|
|
95
93
|
|
|
96
94
|
|
|
97
|
-
@pytest.mark.asyncio
|
|
95
|
+
@pytest.mark.asyncio
|
|
98
96
|
async def test_database_isolation__1(aidbox_client, safe_db):
|
|
99
97
|
patients = await aidbox_client.resources("Patient").fetch_all()
|
|
100
98
|
assert len(patients) == 2
|
|
@@ -106,7 +104,7 @@ async def test_database_isolation__1(aidbox_client, safe_db):
|
|
|
106
104
|
assert len(patients) == 3
|
|
107
105
|
|
|
108
106
|
|
|
109
|
-
@pytest.mark.asyncio
|
|
107
|
+
@pytest.mark.asyncio
|
|
110
108
|
async def test_database_isolation__2(aidbox_client, safe_db):
|
|
111
109
|
patients = await aidbox_client.resources("Patient").fetch_all()
|
|
112
110
|
assert len(patients) == 2
|
|
@@ -121,7 +119,7 @@ async def test_database_isolation__2(aidbox_client, safe_db):
|
|
|
121
119
|
assert len(patients) == 4
|
|
122
120
|
|
|
123
121
|
|
|
124
|
-
@pytest.mark.asyncio
|
|
122
|
+
@pytest.mark.asyncio
|
|
125
123
|
async def test_database_isolation_with_history_in_name__1(aidbox_client, safe_db):
|
|
126
124
|
resources = await aidbox_client.resources("FamilyMemberHistory").fetch_all()
|
|
127
125
|
assert len(resources) == 0
|
|
@@ -144,7 +142,7 @@ async def test_database_isolation_with_history_in_name__1(aidbox_client, safe_db
|
|
|
144
142
|
assert len(resources) == 1
|
|
145
143
|
|
|
146
144
|
|
|
147
|
-
@pytest.mark.asyncio
|
|
145
|
+
@pytest.mark.asyncio
|
|
148
146
|
async def test_database_isolation_with_history_in_name__2(aidbox_client, safe_db):
|
|
149
147
|
resources = await aidbox_client.resources("FamilyMemberHistory").fetch_all()
|
|
150
148
|
assert len(resources) == 0
|
|
@@ -179,3 +177,34 @@ async def test_database_isolation_with_history_in_name__2(aidbox_client, safe_db
|
|
|
179
177
|
|
|
180
178
|
resources = await aidbox_client.resources("FamilyMemberHistory").fetch_all()
|
|
181
179
|
assert len(resources) == 2
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
async def test_aidbox_db_fixture(client, aidbox_db: DBProxy, safe_db):
|
|
183
|
+
"""
|
|
184
|
+
Test that aidbox_db fixture works with isolated DB Proxy from app's instance
|
|
185
|
+
"""
|
|
186
|
+
response = await client.get("/db-tests")
|
|
187
|
+
assert response.status == 200
|
|
188
|
+
json = await response.json()
|
|
189
|
+
assert json == [{"id": "app-test"}]
|
|
190
|
+
|
|
191
|
+
app_ids = await main.get_app_ids(aidbox_db)
|
|
192
|
+
assert app_ids == [{"id": "app-test"}]
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
async def test_operation_base_fhir_error_json_test_op(aidbox_client):
|
|
196
|
+
with pytest.raises(OperationOutcome) as exc:
|
|
197
|
+
await aidbox_client.execute("/$base-fhir-error-json-test")
|
|
198
|
+
assert exc.value.resource.get("issue")[0].get("diagnostics") == "Resource Patient/id not found"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
async def test_operation_base_fhir_error_text_test_op(aidbox_client):
|
|
202
|
+
with pytest.raises(OperationOutcome) as exc:
|
|
203
|
+
await aidbox_client.execute("/$base-fhir-error-text-test")
|
|
204
|
+
assert exc.value.resource.get("issue")[0].get("diagnostics") == "plain"
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def test_operation_outcome_test_op(aidbox_client):
|
|
208
|
+
with pytest.raises(OperationOutcome) as exc:
|
|
209
|
+
await aidbox_client.execute("/$operation-outcome-test")
|
|
210
|
+
assert exc.value.resource.get("issue")[0].get("diagnostics") == "test reason"
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import cast
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
from aiohttp import BasicAuth, ClientSession, web
|
|
6
|
-
from yarl import URL
|
|
7
|
-
|
|
8
|
-
from main import create_app as _create_app
|
|
9
|
-
|
|
10
|
-
from . import app_keys as ak
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
async def start_app(aiohttp_client):
|
|
14
|
-
app = await aiohttp_client(_create_app(), server_kwargs={"host": "0.0.0.0", "port": 8081})
|
|
15
|
-
sdk = cast(web.Application, app.server.app)[ak.sdk]
|
|
16
|
-
sdk._test_start_txid = -1
|
|
17
|
-
|
|
18
|
-
return app
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@pytest.fixture
|
|
22
|
-
async def client(aiohttp_client):
|
|
23
|
-
"""Instance of app's server and client"""
|
|
24
|
-
return await start_app(aiohttp_client)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class AidboxSession(ClientSession):
|
|
28
|
-
def __init__(self, *args, base_url=None, **kwargs):
|
|
29
|
-
base_url_resolved = base_url or os.environ.get("AIDBOX_BASE_URL")
|
|
30
|
-
assert base_url_resolved, "Either base_url arg or AIDBOX_BASE_URL env var must be set"
|
|
31
|
-
self.base_url = URL(base_url_resolved)
|
|
32
|
-
super().__init__(*args, **kwargs)
|
|
33
|
-
|
|
34
|
-
def make_url(self, path):
|
|
35
|
-
return self.base_url.with_path(path)
|
|
36
|
-
|
|
37
|
-
async def _request(self, method, path, *args, **kwargs):
|
|
38
|
-
url = self.make_url(path)
|
|
39
|
-
return await super()._request(method, url, *args, **kwargs)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@pytest.fixture
|
|
43
|
-
async def aidbox(client):
|
|
44
|
-
"""HTTP client for making requests to Aidbox"""
|
|
45
|
-
app = cast(web.Application, client.server.app)
|
|
46
|
-
basic_auth = BasicAuth(
|
|
47
|
-
login=app[ak.settings].APP_INIT_CLIENT_ID,
|
|
48
|
-
password=app[ak.settings].APP_INIT_CLIENT_SECRET,
|
|
49
|
-
)
|
|
50
|
-
session = AidboxSession(auth=basic_auth, base_url=app[ak.settings].APP_INIT_URL)
|
|
51
|
-
yield session
|
|
52
|
-
await session.close()
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@pytest.fixture
|
|
56
|
-
async def safe_db(aidbox, client, sdk):
|
|
57
|
-
resp = await aidbox.post(
|
|
58
|
-
"/$psql",
|
|
59
|
-
json={"query": "SELECT last_value from transaction_id_seq;"},
|
|
60
|
-
raise_for_status=True,
|
|
61
|
-
)
|
|
62
|
-
results = await resp.json()
|
|
63
|
-
txid = results[0]["result"][0]["last_value"]
|
|
64
|
-
sdk._test_start_txid = int(txid)
|
|
65
|
-
|
|
66
|
-
yield txid
|
|
67
|
-
|
|
68
|
-
sdk._test_start_txid = -1
|
|
69
|
-
await aidbox.post(
|
|
70
|
-
"/$psql",
|
|
71
|
-
json={"query": f"select drop_before_all({txid});"},
|
|
72
|
-
params={"execute": "true"},
|
|
73
|
-
raise_for_status=True,
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
@pytest.fixture
|
|
78
|
-
def sdk(client):
|
|
79
|
-
return cast(web.Application, client.server.app)[ak.sdk]
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@pytest.fixture
|
|
83
|
-
def aidbox_client(client):
|
|
84
|
-
return cast(web.Application, client.server.app)[ak.client]
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
@pytest.fixture
|
|
88
|
-
def aidbox_db(client):
|
|
89
|
-
return cast(web.Application, client.server.app)[ak.db]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/requires.txt
RENAMED
|
File without changes
|
{aidbox_python_sdk-0.1.25 → aidbox_python_sdk-0.2.1}/aidbox_python_sdk.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|