11import pytest
22from 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
45import datamint
5- import responses
6+ import respx
67from aioresponses import aioresponses , CallbackResult
78import pydicom
89from pydicom .data import get_testdata_files
910import datamint .configs
1011from medimgkit .dicom_utils import to_bytesio
11- from datamint .apihandler .base_api_handler import DatamintException
1212import json
1313from aiohttp import FormData
1414from typing import IO
1515import os
1616import numpy as np
1717from 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 '
0 commit comments