Skip to content

Commit cbe9796

Browse files
committed
Fixed automated tests
1 parent 74fdb8b commit cbe9796

5 files changed

Lines changed: 41 additions & 276 deletions

File tree

datamint/api/endpoints/resources_api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ def _open_io(file_path: str | Path | IO, mode: str = 'rb') -> IO:
5454
class ResourcesApi(CreatableEntityApi[Resource], DeletableEntityApi[Resource]):
5555
"""API handler for resource-related endpoints."""
5656

57+
_ENDPOINT_BASE = 'resources'
58+
5759
def __init__(self,
5860
config: ApiConfig,
5961
client: httpx.Client | None = None,
@@ -68,7 +70,7 @@ def __init__(self,
6870
"""
6971
from .annotations_api import AnnotationsApi
7072
from .projects_api import ProjectsApi
71-
super().__init__(config, Resource, 'resources', client)
73+
super().__init__(config, Resource, ResourcesApi._ENDPOINT_BASE, client)
7274
nest_asyncio.apply()
7375
self.annotations_api = AnnotationsApi(
7476
config, client, resources_api=self) if annotations_api is None else annotations_api

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "datamint"
33
description = "A library for interacting with the Datamint API, designed for efficient data management, processing and Deep Learning workflows."
4-
version = "2.7.1"
4+
version = "2.8.0"
55
dynamic = ["dependencies"]
66
requires-python = ">=3.10"
77
readme = "README.md"
@@ -66,6 +66,7 @@ pytest = "^7.0.0"
6666
pytest-cov = "^4.0.0"
6767
responses = "^0.20.0"
6868
aioresponses = "^0.7.0"
69+
respx = ">=0.22.0"
6970

7071
# Extra dependencies for docs
7172
[tool.poetry.extras]

tests/test_api_handler.py

Lines changed: 32 additions & 224 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import pytest
22
from unittest.mock import patch
3-
from datamint.apihandler.api_handler import APIHandler
3+
from datamint.api.client import Api
4+
from datamint.exceptions import DatamintException
45
import datamint
5-
import responses
6+
import respx
67
from aioresponses import aioresponses, CallbackResult
78
import pydicom
89
from pydicom.data import get_testdata_files
910
import datamint.configs
1011
from medimgkit.dicom_utils import to_bytesio
11-
from datamint.apihandler.base_api_handler import DatamintException
1212
import json
1313
from aiohttp import FormData
1414
from typing import IO
1515
import os
1616
import numpy as np
1717
from copy import deepcopy
18+
import httpx
1819

1920
# pytest tests --log-cli-level=INFO
2021

@@ -103,38 +104,33 @@ def get_projects_sample(self) -> dict:
103104

104105
@pytest.fixture
105106
def get_resources_sample(self) -> dict:
106-
res = {'id': 'f0fea905-6823-48c3-838a-b0c62c5a0466',
107-
'resource_uri': '/resources/f0fea905-6823-48c3-838a-b0c62c5a0466/file',
107+
res = {'id': 'cd69c126-02ee-44af-8672-13d61b09eee4',
108+
'resource_uri': '/resources/cd69c126-02ee-44af-8672-13d61b09eee4/file',
108109
'storage': 'ImageResource',
109-
'location': 'resources/f5a18308-ab64-4c1e-8a1f-db4bccd77f4a/f0fea905-6823-48c3-838a-b0c62c5a0466',
110+
'location': 'resources/79113ed1-0535-4f53-9359-7fe3fa9f28a8/cd69c126-02ee-44af-8672-13d61b09eee4',
110111
'upload_channel': 'Unknown',
111-
'filename': 'IMG0002121.jpg',
112-
'modality': 'DX',
113-
'mimetype': 'image/jpeg',
114-
'size': 15662,
115-
'upload_mechanism': 'api',
116-
'customer_id': 'f5a18308-ab64-4c1e-8a1f-db4bccd77f4a',
117-
'status': 'published',
118-
'created_at': '2025-02-05T14:32:30.378Z',
112+
'filename': 'normal (95).png',
113+
'modality': 'US',
114+
'mimetype': 'image/png',
115+
'size': 444584, 'upload_mechanism': 'api',
116+
'customer_id': '79113ed1-0535-4f53-9359-7fe3fa9f28a8',
117+
'status': 'inbox',
118+
'created_at': '2025-12-24T11:34:18.036Z',
119119
'created_by': 'datamint-dev@mail.com',
120120
'published': False,
121-
'published_on': '2025-02-05T14:47:47.942Z',
122-
'published_by': 'datamint-dev@mail.com',
121+
'published_on': None,
122+
'published_by': None,
123123
'publish_transforms': None,
124124
'deleted': False,
125125
'deleted_at': None,
126126
'deleted_by': None,
127-
'metadata': {},
128-
'source_filepath': 'images/IMG0002121.jpg',
129-
'tags': ['non_fractured', 'leg'],
130-
'segmentations': None,
131-
'measurements': None,
132-
'categories': None,
133-
'labels': [],
134-
'user_info': {'firstname': 'data', 'lastname': 'mint'},
135-
'projects': []}
127+
'metadata': {'width': 693, 'height': 582},
128+
'source_filepath': '/tmp/normal/normal (95).png',
129+
'tags': ['ultrasound', 'split:train'],
130+
'user_info': {'firstname': None, 'lastname': None},
131+
'projects': [], 'labels': []}
136132

137-
return {'data': [{'resources': [res]}]}
133+
return {'data': [{'resources': [res]}], 'totalCount': 1}
138134

139135
@pytest.fixture
140136
def sample_2dmask1(self) -> np.ndarray:
@@ -145,214 +141,26 @@ def sample_2dmask1(self) -> np.ndarray:
145141
rng = np.random.RandomState(42)
146142
return rng.randint(0, 2, (32, 32), dtype=np.uint8)
147143

148-
@responses.activate
144+
@respx.mock
149145
@patch('os.getenv')
150146
def test_api_handler_init(self, mock_getenv, get_projects_sample: dict):
151-
def mock_getenv_side_effect(key):
152-
if key == datamint.configs.get_env_var_name(datamint.configs.APIKEY_KEY):
153-
return 'test_api_key'
154-
if key == datamint.configs.get_env_var_name(datamint.configs.APIURL_KEY):
155-
return _TEST_URL
156-
return None
157-
158-
mock_getenv.side_effect = mock_getenv_side_effect
159-
api_handler = APIHandler(check_connection=False)
160-
assert api_handler.api_key == 'test_api_key'
161-
assert api_handler.root_url == _TEST_URL
162-
163-
responses.get(
164-
f"{_TEST_URL}/projects",
165-
status=200,
166-
json=get_projects_sample
167-
)
168-
169-
api_handler = APIHandler(check_connection=True)
147+
api_handler = Api(check_connection=False)
170148

171149
### Test wrong url ###
172150
with pytest.raises(DatamintException):
173-
api_handler = APIHandler('wrong', check_connection=True)
174-
175-
def test_upload_dicoms_resources(self, sample_dicom1):
176-
def _callback1(url, data, **kwargs):
177-
dicom_bytes, data = _get_request_data(data)
178-
ds = pydicom.dcmread(dicom_bytes)
179-
180-
assert str(sample_dicom1.PatientName) == str(ds.PatientName)
181-
return CallbackResult(status=201, payload={"id": "newdicomid"})
182-
183-
with aioresponses() as mock_aioresp:
184-
# check that post request has data 'batch_id'
185-
mock_aioresp.post(
186-
f"{_TEST_URL}/{APIHandler.ENDPOINT_RESOURCES}",
187-
callback=_callback1,
188-
repeat=2
189-
)
190-
191-
api_handler = APIHandler(_TEST_URL, 'test_api_key', check_connection=False)
192-
new_dicoms_id = api_handler.upload_resources(files_path=to_bytesio(sample_dicom1, 'sample_dicom1'),
193-
channel='mychannel',
194-
anonymize=False)
195-
assert new_dicoms_id == 'newdicomid'
196-
197-
### Same thing, but in a list ###
198-
new_dicoms_id = api_handler.upload_resources(files_path=[to_bytesio(sample_dicom1, 'sample_dicom1')],
199-
channel='mychannel',
200-
anonymize=False)
201-
assert len(new_dicoms_id) == 1 and new_dicoms_id[0] == 'newdicomid'
202-
######
203-
204-
def test_upload_dicoms_mungfilename(self, sample_dicom1):
205-
from builtins import open
206-
207-
def my_open_mock(file, *args, **kwargs):
208-
if str(file).endswith('.dcm'):
209-
return to_bytesio(sample_dicom1, file)
210-
return open(file, *args, **kwargs)
211-
212-
def _request_callback(url, data, **kwargs):
213-
dicom_bytes, data = _get_request_data(data)
214-
assert '__data_test_dicom.dcm' in data
215-
return CallbackResult(status=201, payload={"id": "newdicomid"})
216-
217-
def _request_callback2(url, data, **kwargs):
218-
if isinstance(data, FormData):
219-
data = str(data._fields)
220-
else:
221-
data = str(data)
222-
assert 'test_dicom.dcm' in data and 'data_test' not in data
223-
return CallbackResult(status=201, payload={"id": "newdicomid"})
224-
225-
def _request_callback3(url, data, **kwargs):
226-
dicom_bytes, data = _get_request_data(data)
227-
assert 'me_data_test_dicom.dcm' in data
228-
return CallbackResult(status=201, payload={"id": "newdicomid"})
229-
230-
api_handler = APIHandler(_TEST_URL, 'test_api_key', check_connection=False)
231-
232-
with patch('builtins.open', new=my_open_mock):
233-
with aioresponses() as mock_aioresp:
234-
mock_aioresp.post(
235-
f"{_TEST_URL}/{APIHandler.ENDPOINT_RESOURCES}",
236-
callback=_request_callback
237-
)
238-
new_dicoms_id = api_handler.upload_resources(files_path=os.path.join('..', 'data', 'test_dicom.dcm'),
239-
anonymize=False,
240-
mung_filename='all')
241-
assert new_dicoms_id == 'newdicomid'
242-
243-
with aioresponses() as mock_aioresp:
244-
mock_aioresp.post(
245-
f"{_TEST_URL}/{APIHandler.ENDPOINT_RESOURCES}",
246-
callback=_request_callback2
247-
)
248-
new_dicoms_id = api_handler.upload_resources(files_path=os.path.join('data', 'test_dicom.dcm'),
249-
anonymize=False,
250-
mung_filename=None)
251-
assert new_dicoms_id == 'newdicomid'
252-
253-
with aioresponses() as mock_aioresp:
254-
mock_aioresp.post(
255-
f"{_TEST_URL}/{APIHandler.ENDPOINT_RESOURCES}",
256-
callback=_request_callback3
257-
)
258-
new_dicoms_id = api_handler.upload_resources(files_path=os.path.join('home', 'me', 'data', 'test_dicom.dcm'),
259-
anonymize=False,
260-
mung_filename=[2, 3])
261-
assert new_dicoms_id == 'newdicomid'
262-
263-
@responses.activate
264-
def test_upload_resources_video(self):
265-
def _callback(url, data, *args, **kwargs):
266-
assert kwargs['headers']['apikey'] == 'test_api_key'
267-
268-
return CallbackResult(status=201, payload={"id": "new_resource_id"})
269-
270-
with aioresponses() as mock_aioresp:
271-
api_handler = APIHandler(_TEST_URL, 'test_api_key', check_connection=False)
272-
mock_aioresp.post(
273-
api_handler._get_endpoint_url(APIHandler.ENDPOINT_RESOURCES),
274-
callback=_callback
275-
)
276-
277-
new_resources_id = api_handler.upload_resources(files_path=MP4_TEST_FILE,
278-
tags=['label1', 'label2'],
279-
channel='mychannel',
280-
anonymize=False)
281-
new_resources_id == 'new_resource_id'
282-
283-
def test_upload_resources_assembling_dicoms(self, sample_dicom1: pydicom.Dataset):
284-
sample_dicom2 = deepcopy(sample_dicom1)
285-
sample_dicom2.InstanceNumber = 2
286-
sample_dicom2.SOPInstanceUID = pydicom.uid.generate_uid()
287-
api_handler = APIHandler(_TEST_URL, 'test_api_key', check_connection=False)
288-
289-
with aioresponses() as mock_aioresp:
290-
mock_aioresp.post(
291-
f"{_TEST_URL}/{APIHandler.ENDPOINT_RESOURCES}",
292-
status=201,
293-
payload={"id": "new_res_id"},
294-
repeat=2
295-
)
296-
297-
new_res_ids = api_handler.upload_resources(files_path=[to_bytesio(sample_dicom1, 'sample_dicom1'),
298-
to_bytesio(sample_dicom2, 'sample_dicom2'),
299-
MP4_TEST_FILE,
300-
],
301-
channel='mychannel',
302-
assemble_dicoms=True,
303-
anonymize=False)
304-
assert len(new_res_ids) == 2
305-
306-
### Same thing, but assemble_dicoms=False ###
307-
with aioresponses() as mock_aioresp:
308-
mock_aioresp.post(
309-
f"{_TEST_URL}/{APIHandler.ENDPOINT_RESOURCES}",
310-
status=201,
311-
payload={"id": "new_res_id"},
312-
repeat=3
313-
)
314-
315-
new_res_ids = api_handler.upload_resources(files_path=[to_bytesio(sample_dicom1, 'sample_dicom1'),
316-
to_bytesio(sample_dicom2, 'sample_dicom2'),
317-
MP4_TEST_FILE,
318-
],
319-
channel='mychannel',
320-
assemble_dicoms=False,
321-
anonymize=False)
322-
assert len(new_res_ids) == 3
323-
324-
@responses.activate
325-
def test_get_channels(self, get_channels_sample: dict):
326-
def _request_callback(request):
327-
return (200, "", json.dumps(get_channels_sample))
328-
329-
# Mocking the response from the server
330-
responses.add_callback(
331-
responses.GET,
332-
f"{_TEST_URL}/{APIHandler.ENDPOINT_RESOURCES}/channels",
333-
content_type='application/json',
334-
callback=_request_callback
335-
)
336-
337-
api_handler = APIHandler(_TEST_URL, 'test_api_key', check_connection=False)
338-
channels_info = list(api_handler.get_channels())
339-
assert len(channels_info) == 2 # two channels
151+
api_handler = Api(server_url='wrong', check_connection=True)
340152

341-
@responses.activate
153+
@respx.mock
342154
def test_get_resources(self, get_resources_sample: dict):
343-
def _request_callback(request):
344-
return (200, "", json.dumps(get_resources_sample))
155+
from datamint.api.endpoints.resources_api import ResourcesApi
345156

346157
# Mocking the response from the server
347-
responses.add_callback(
348-
responses.GET,
349-
f"{_TEST_URL}/{APIHandler.ENDPOINT_RESOURCES}",
350-
content_type='application/json',
351-
callback=_request_callback
158+
respx.get(f"{_TEST_URL}/{ResourcesApi._ENDPOINT_BASE}").mock(
159+
return_value=httpx.Response(200, json=get_resources_sample)
352160
)
353161

354-
api_handler = APIHandler(_TEST_URL, 'test_api_key', check_connection=False)
355-
resources = list(api_handler.get_resources())
162+
api = Api(_TEST_URL, 'test_api_key', check_connection=False)
163+
resources = api.resources.get_list()
356164
assert len(resources) == 1
357165
r = resources[0]
358-
assert r['id'] == 'f0fea905-6823-48c3-838a-b0c62c5a0466'
166+
assert r.filename == 'normal (95).png'

tests/test_datamint_config.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@
44
"""
55
import pytest
66
import sys
7-
import subprocess
8-
import tempfile
9-
import os
10-
from unittest.mock import patch, MagicMock
11-
from pathlib import Path
7+
from unittest.mock import patch
128
import logging
139

1410
_LOGGER = logging.getLogger(__name__)

0 commit comments

Comments
 (0)