datamint 1.9.3__py3-none-any.whl → 2.0.1__py3-none-any.whl
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.
Potentially problematic release.
This version of datamint might be problematic. Click here for more details.
- datamint/__init__.py +2 -0
- datamint/api/__init__.py +3 -0
- datamint/api/base_api.py +430 -0
- datamint/api/client.py +91 -0
- datamint/api/dto/__init__.py +10 -0
- datamint/api/endpoints/__init__.py +17 -0
- datamint/api/endpoints/annotations_api.py +984 -0
- datamint/api/endpoints/channels_api.py +28 -0
- datamint/api/endpoints/datasetsinfo_api.py +16 -0
- datamint/api/endpoints/projects_api.py +203 -0
- datamint/api/endpoints/resources_api.py +1013 -0
- datamint/api/endpoints/users_api.py +38 -0
- datamint/api/entity_base_api.py +347 -0
- datamint/apihandler/api_handler.py +3 -6
- datamint/apihandler/base_api_handler.py +6 -28
- datamint/apihandler/dto/__init__.py +0 -0
- datamint/apihandler/dto/annotation_dto.py +1 -1
- datamint/client_cmd_tools/datamint_upload.py +19 -30
- datamint/dataset/base_dataset.py +83 -86
- datamint/dataset/dataset.py +2 -2
- datamint/entities/__init__.py +20 -0
- datamint/entities/annotation.py +178 -0
- datamint/entities/base_entity.py +51 -0
- datamint/entities/channel.py +46 -0
- datamint/entities/datasetinfo.py +22 -0
- datamint/entities/project.py +64 -0
- datamint/entities/resource.py +130 -0
- datamint/entities/user.py +21 -0
- datamint/examples/example_projects.py +41 -44
- datamint/exceptions.py +27 -1
- {datamint-1.9.3.dist-info → datamint-2.0.1.dist-info}/METADATA +13 -9
- datamint-2.0.1.dist-info/RECORD +50 -0
- {datamint-1.9.3.dist-info → datamint-2.0.1.dist-info}/WHEEL +1 -1
- datamint-1.9.3.dist-info/RECORD +0 -29
- {datamint-1.9.3.dist-info → datamint-2.0.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from ..entity_base_api import CreatableEntityApi, ApiConfig
|
|
2
|
+
from datamint.entities import User
|
|
3
|
+
import httpx
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UsersApi(CreatableEntityApi[User]):
|
|
7
|
+
def __init__(self,
|
|
8
|
+
config: ApiConfig,
|
|
9
|
+
client: httpx.Client | None = None) -> None:
|
|
10
|
+
super().__init__(config, User, 'users', client)
|
|
11
|
+
|
|
12
|
+
def create(self,
|
|
13
|
+
email: str,
|
|
14
|
+
password: str | None = None,
|
|
15
|
+
firstname: str | None = None,
|
|
16
|
+
lastname: str | None = None,
|
|
17
|
+
roles: list[str] | None = None
|
|
18
|
+
) -> str:
|
|
19
|
+
"""Create a new user.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
email: The user's email address.
|
|
23
|
+
password: The user's password. If None, a random password will be generated.
|
|
24
|
+
firstname: The user's first name.
|
|
25
|
+
lastname: The user's last name.
|
|
26
|
+
roles: List of roles to assign to the user.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
The id of the created user.
|
|
30
|
+
"""
|
|
31
|
+
data = dict(
|
|
32
|
+
email=email,
|
|
33
|
+
password=password,
|
|
34
|
+
firstname=firstname,
|
|
35
|
+
lastname=lastname,
|
|
36
|
+
roles=roles
|
|
37
|
+
)
|
|
38
|
+
return self._create(data)
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
from typing import Any, TypeVar, Generic, Type, Sequence
|
|
2
|
+
import logging
|
|
3
|
+
import httpx
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datamint.entities.base_entity import BaseEntity
|
|
6
|
+
from datamint.exceptions import DatamintException, ResourceNotFoundError
|
|
7
|
+
import aiohttp
|
|
8
|
+
import asyncio
|
|
9
|
+
from .base_api import ApiConfig, BaseApi
|
|
10
|
+
import contextlib
|
|
11
|
+
from typing import AsyncGenerator
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
T = TypeVar('T', bound=BaseEntity)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EntityBaseApi(BaseApi, Generic[T]):
|
|
18
|
+
"""Base API handler for entity-related endpoints with CRUD operations.
|
|
19
|
+
|
|
20
|
+
This class provides a template for API handlers that work with specific
|
|
21
|
+
entity types, offering common CRUD operations with proper typing.
|
|
22
|
+
|
|
23
|
+
Type Parameters:
|
|
24
|
+
T: The entity type this API handler manages (must extend BaseEntity)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, config: ApiConfig,
|
|
28
|
+
entity_class: Type[T],
|
|
29
|
+
endpoint_base: str,
|
|
30
|
+
client: httpx.Client | None = None) -> None:
|
|
31
|
+
"""Initialize the entity API handler.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config: API configuration containing base URL, API key, etc.
|
|
35
|
+
entity_class: The entity class this handler manages
|
|
36
|
+
endpoint_base: Base endpoint path (e.g., 'projects', 'annotations')
|
|
37
|
+
client: Optional HTTP client instance. If None, a new one will be created.
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(config, client)
|
|
40
|
+
self.entity_class = entity_class
|
|
41
|
+
self.endpoint_base = endpoint_base.strip('/')
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _entid(entity: BaseEntity | str) -> str:
|
|
45
|
+
return entity if isinstance(entity, str) else entity.id
|
|
46
|
+
|
|
47
|
+
def _make_entity_request(self,
|
|
48
|
+
method: str,
|
|
49
|
+
entity_id: str | BaseEntity,
|
|
50
|
+
add_path: str = '',
|
|
51
|
+
**kwargs) -> httpx.Response:
|
|
52
|
+
try:
|
|
53
|
+
entity_id = self._entid(entity_id)
|
|
54
|
+
add_path = '/'.join(add_path.strip().strip('/').split('/'))
|
|
55
|
+
return self._make_request(method, f'/{self.endpoint_base}/{entity_id}/{add_path}', **kwargs)
|
|
56
|
+
except httpx.HTTPStatusError as e:
|
|
57
|
+
if e.response.status_code == 404:
|
|
58
|
+
raise ResourceNotFoundError(self.endpoint_base, {'id': entity_id}) from e
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
@contextlib.asynccontextmanager
|
|
62
|
+
async def _make_entity_request_async(self,
|
|
63
|
+
method: str,
|
|
64
|
+
entity_id: str | BaseEntity,
|
|
65
|
+
add_path: str = '',
|
|
66
|
+
session: aiohttp.ClientSession | None = None,
|
|
67
|
+
**kwargs) -> AsyncGenerator[aiohttp.ClientResponse, None]:
|
|
68
|
+
try:
|
|
69
|
+
entity_id = self._entid(entity_id)
|
|
70
|
+
add_path = '/'.join(add_path.strip().strip('/').split('/'))
|
|
71
|
+
async with self._make_request_async(method,
|
|
72
|
+
f'/{self.endpoint_base}/{entity_id}/{add_path}',
|
|
73
|
+
session=session,
|
|
74
|
+
**kwargs) as resp:
|
|
75
|
+
yield resp
|
|
76
|
+
except aiohttp.ClientResponseError as e:
|
|
77
|
+
if e.status == 404:
|
|
78
|
+
raise ResourceNotFoundError(self.endpoint_base, {'id': entity_id}) from e
|
|
79
|
+
raise
|
|
80
|
+
|
|
81
|
+
def _stream_entity_request(self,
|
|
82
|
+
method: str,
|
|
83
|
+
entity_id: str,
|
|
84
|
+
add_path: str = '',
|
|
85
|
+
**kwargs):
|
|
86
|
+
try:
|
|
87
|
+
add_path = '/'.join(add_path.strip().strip('/').split('/'))
|
|
88
|
+
return self._stream_request(method, f'/{self.endpoint_base}/{entity_id}/{add_path}', **kwargs)
|
|
89
|
+
except httpx.HTTPStatusError as e:
|
|
90
|
+
if e.response.status_code == 404:
|
|
91
|
+
raise ResourceNotFoundError(self.endpoint_base, {'id': entity_id}) from e
|
|
92
|
+
raise
|
|
93
|
+
|
|
94
|
+
def get_list(self, limit: int | None = None,
|
|
95
|
+
**kwargs) -> Sequence[T]:
|
|
96
|
+
"""Get entities with optional filtering.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
List of entity instances.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
httpx.HTTPStatusError: If the request fails.
|
|
103
|
+
"""
|
|
104
|
+
new_kwargs = dict(kwargs)
|
|
105
|
+
|
|
106
|
+
# Remove None values from the payload.
|
|
107
|
+
for k in list(new_kwargs.keys()):
|
|
108
|
+
if new_kwargs[k] is None:
|
|
109
|
+
del new_kwargs[k]
|
|
110
|
+
|
|
111
|
+
items_gen = self._make_request_with_pagination('GET', f'/{self.endpoint_base}',
|
|
112
|
+
return_field=self.endpoint_base,
|
|
113
|
+
limit=limit,
|
|
114
|
+
**new_kwargs)
|
|
115
|
+
|
|
116
|
+
all_items = []
|
|
117
|
+
for resp, items in items_gen:
|
|
118
|
+
all_items.extend(items)
|
|
119
|
+
|
|
120
|
+
return [self.entity_class(**item) for item in all_items]
|
|
121
|
+
|
|
122
|
+
def get_all(self, limit: int | None = None) -> Sequence[T]:
|
|
123
|
+
"""Get all entities with optional pagination and filtering.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
List of entity instances
|
|
127
|
+
|
|
128
|
+
Raises:
|
|
129
|
+
httpx.HTTPStatusError: If the request fails
|
|
130
|
+
"""
|
|
131
|
+
return self.get_list(limit=limit)
|
|
132
|
+
|
|
133
|
+
def get_by_id(self, entity_id: str) -> T:
|
|
134
|
+
"""Get a specific entity by its ID.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
entity_id: Unique identifier for the entity.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Entity instance.
|
|
141
|
+
|
|
142
|
+
Raises:
|
|
143
|
+
httpx.HTTPStatusError: If the entity is not found or request fails.
|
|
144
|
+
"""
|
|
145
|
+
response = self._make_entity_request('GET', entity_id)
|
|
146
|
+
return self.entity_class(**response.json())
|
|
147
|
+
|
|
148
|
+
async def _create_async(self, entity_data: dict[str, Any]) -> str | Sequence[str | dict]:
|
|
149
|
+
"""Create a new entity.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
entity_data: Dictionary containing entity data for creation.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The id of the created entity.
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
httpx.HTTPStatusError: If creation fails.
|
|
159
|
+
"""
|
|
160
|
+
respdata = await self._make_request_async_json('POST',
|
|
161
|
+
f'/{self.endpoint_base}',
|
|
162
|
+
json=entity_data)
|
|
163
|
+
if 'error' in respdata:
|
|
164
|
+
raise DatamintException(respdata['error'])
|
|
165
|
+
if isinstance(respdata, str):
|
|
166
|
+
return respdata
|
|
167
|
+
if isinstance(respdata, list):
|
|
168
|
+
return respdata
|
|
169
|
+
if isinstance(respdata, dict):
|
|
170
|
+
return respdata.get('id')
|
|
171
|
+
return respdata
|
|
172
|
+
|
|
173
|
+
def _get_child_entities(self,
|
|
174
|
+
parent_entity: BaseEntity | str,
|
|
175
|
+
child_entity_name: str) -> httpx.Response:
|
|
176
|
+
response = self._make_entity_request('GET', parent_entity,
|
|
177
|
+
add_path=child_entity_name)
|
|
178
|
+
return response
|
|
179
|
+
|
|
180
|
+
# def bulk_create(self, entities_data: list[dict[str, Any]]) -> list[T]:
|
|
181
|
+
# """Create multiple entities in a single request.
|
|
182
|
+
|
|
183
|
+
# Args:
|
|
184
|
+
# entities_data: List of dictionaries containing entity data
|
|
185
|
+
|
|
186
|
+
# Returns:
|
|
187
|
+
# List of created entity instances
|
|
188
|
+
|
|
189
|
+
# Raises:
|
|
190
|
+
# httpx.HTTPStatusError: If bulk creation fails
|
|
191
|
+
# """
|
|
192
|
+
# payload = {'items': entities_data} # Common bulk API format
|
|
193
|
+
# response = self._make_request('POST', f'/{self.endpoint_base}/bulk', json=payload)
|
|
194
|
+
# data = response.json()
|
|
195
|
+
|
|
196
|
+
# # Handle response format - may be direct list or wrapped
|
|
197
|
+
# items = data if isinstance(data, list) else data.get('items', [])
|
|
198
|
+
# return [self.entity_class(**item) for item in items]
|
|
199
|
+
|
|
200
|
+
# def count(self, **params: Any) -> int:
|
|
201
|
+
# """Get the total count of entities matching the given filters.
|
|
202
|
+
|
|
203
|
+
# Args:
|
|
204
|
+
# **params: Query parameters for filtering
|
|
205
|
+
|
|
206
|
+
# Returns:
|
|
207
|
+
# Total count of matching entities
|
|
208
|
+
|
|
209
|
+
# Raises:
|
|
210
|
+
# httpx.HTTPStatusError: If the request fails
|
|
211
|
+
# """
|
|
212
|
+
# response = self._make_request('GET', f'/{self.endpoint_base}/count', params=params)
|
|
213
|
+
# data = response.json()
|
|
214
|
+
# return data.get('count', 0) if isinstance(data, dict) else data
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class DeletableEntityApi(EntityBaseApi[T]):
|
|
218
|
+
"""Extension of EntityBaseApi for entities that support soft deletion.
|
|
219
|
+
|
|
220
|
+
This class adds methods to handle soft-deleted entities, allowing
|
|
221
|
+
retrieval and restoration of such entities.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def delete(self, entity: str | BaseEntity) -> None:
|
|
225
|
+
"""Delete an entity.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
entity: Unique identifier for the entity to delete or the entity instance itself.
|
|
229
|
+
|
|
230
|
+
Raises:
|
|
231
|
+
httpx.HTTPStatusError: If deletion fails or entity not found
|
|
232
|
+
"""
|
|
233
|
+
self._make_entity_request('DELETE', entity)
|
|
234
|
+
|
|
235
|
+
def bulk_delete(self, entities: Sequence[str | BaseEntity]) -> None:
|
|
236
|
+
"""Delete multiple entities.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
entities: Sequence of unique identifiers for the entities to delete or the entity instances themselves.
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
httpx.HTTPStatusError: If deletion fails or any entity not found
|
|
243
|
+
"""
|
|
244
|
+
async def _delete_all_async():
|
|
245
|
+
async with aiohttp.ClientSession() as session:
|
|
246
|
+
tasks = [
|
|
247
|
+
self._delete_async(entity, session)
|
|
248
|
+
for entity in entities
|
|
249
|
+
]
|
|
250
|
+
await asyncio.gather(*tasks)
|
|
251
|
+
|
|
252
|
+
loop = asyncio.get_event_loop()
|
|
253
|
+
loop.run_until_complete(_delete_all_async())
|
|
254
|
+
|
|
255
|
+
async def _delete_async(self,
|
|
256
|
+
entity: str | BaseEntity,
|
|
257
|
+
session: aiohttp.ClientSession | None = None) -> None:
|
|
258
|
+
"""Asynchronously delete an entity by its ID.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
entity: Unique identifier for the entity to delete or the entity instance itself.
|
|
262
|
+
|
|
263
|
+
Raises:
|
|
264
|
+
httpx.HTTPStatusError: If deletion fails or entity not found
|
|
265
|
+
"""
|
|
266
|
+
async with self._make_entity_request_async('DELETE', entity,
|
|
267
|
+
session=session) as resp:
|
|
268
|
+
await resp.text() # Consume response to complete request
|
|
269
|
+
|
|
270
|
+
# def get_deleted(self, **kwargs) -> Sequence[T]:
|
|
271
|
+
# pass
|
|
272
|
+
|
|
273
|
+
# def restore(self, entity_id: str | BaseEntity) -> T:
|
|
274
|
+
# pass
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class CreatableEntityApi(EntityBaseApi[T]):
|
|
278
|
+
"""Extension of EntityBaseApi for entities that support creation.
|
|
279
|
+
|
|
280
|
+
This class adds methods to handle creation of new entities.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
def _create(self, entity_data: dict[str, Any]) -> str | list[str | dict]:
|
|
284
|
+
"""Create a new entity.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
entity_data: Dictionary containing entity data for creation.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
The id of the created entity.
|
|
291
|
+
|
|
292
|
+
Raises:
|
|
293
|
+
httpx.HTTPStatusError: If creation fails.
|
|
294
|
+
"""
|
|
295
|
+
response = self._make_request('POST', f'/{self.endpoint_base}', json=entity_data)
|
|
296
|
+
respdata = response.json()
|
|
297
|
+
if isinstance(respdata, str):
|
|
298
|
+
return respdata
|
|
299
|
+
if isinstance(respdata, list):
|
|
300
|
+
return respdata
|
|
301
|
+
if isinstance(respdata, dict):
|
|
302
|
+
return respdata.get('id')
|
|
303
|
+
return respdata
|
|
304
|
+
|
|
305
|
+
def create(self, *args, **kwargs) -> str | T:
|
|
306
|
+
raise NotImplementedError("Subclasses must implement the create method with their own custom parameters")
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class UpdatableEntityApi(EntityBaseApi[T]):
|
|
310
|
+
# def update(self, entity_id: str, entity_data: dict[str, Any]):
|
|
311
|
+
# """Update an existing entity.
|
|
312
|
+
|
|
313
|
+
# Args:
|
|
314
|
+
# entity_id: Unique identifier for the entity.
|
|
315
|
+
# entity_data: Dictionary containing updated entity data.
|
|
316
|
+
|
|
317
|
+
# Returns:
|
|
318
|
+
# Updated entity instance.
|
|
319
|
+
|
|
320
|
+
# Raises:
|
|
321
|
+
# httpx.HTTPStatusError: If update fails or entity not found.
|
|
322
|
+
# """
|
|
323
|
+
# self._make_entity_request('PUT', entity_id, json=entity_data)
|
|
324
|
+
|
|
325
|
+
def patch(self, entity: str | T, entity_data: dict[str, Any]):
|
|
326
|
+
"""Partially update an existing entity.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
entity: Unique identifier for the entity or the entity instance.
|
|
330
|
+
entity_data: Dictionary containing fields to update. Only provided fields will be updated.
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
Updated entity instance.
|
|
334
|
+
|
|
335
|
+
Raises:
|
|
336
|
+
httpx.HTTPStatusError: If update fails or entity not found.
|
|
337
|
+
"""
|
|
338
|
+
self._make_entity_request('PATCH', entity, json=entity_data)
|
|
339
|
+
|
|
340
|
+
def partial_update(self, entity: str | T, entity_data: dict[str, Any]):
|
|
341
|
+
"""Alias for :py:meth:`patch` to partially update an entity."""
|
|
342
|
+
return self.patch(entity, entity_data)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class CRUDEntityApi(CreatableEntityApi[T], UpdatableEntityApi[T], DeletableEntityApi[T]):
|
|
346
|
+
"""Full CRUD API handler for entities supporting create, read, update, delete operations."""
|
|
347
|
+
pass
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
from .root_api_handler import RootAPIHandler
|
|
2
2
|
from .annotation_api_handler import AnnotationAPIHandler
|
|
3
3
|
from .exp_api_handler import ExperimentAPIHandler
|
|
4
|
+
from deprecated.sphinx import deprecated
|
|
4
5
|
|
|
5
6
|
|
|
7
|
+
@deprecated(reason="Please use `from datamint import Api` instead.", version="2.0.0")
|
|
6
8
|
class APIHandler(RootAPIHandler, ExperimentAPIHandler, AnnotationAPIHandler):
|
|
7
9
|
"""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
.. code-block:: python
|
|
11
|
-
|
|
12
|
-
from datamint import APIHandler
|
|
13
|
-
api = APIHandler()
|
|
10
|
+
Deprecated. Use `from datamint import Api` instead.
|
|
14
11
|
"""
|
|
15
12
|
pass
|
|
@@ -15,7 +15,8 @@ import nibabel as nib
|
|
|
15
15
|
from nibabel.filebasedimages import FileBasedImage as nib_FileBasedImage
|
|
16
16
|
from datamint import configs
|
|
17
17
|
import gzip
|
|
18
|
-
from datamint.exceptions import DatamintException
|
|
18
|
+
from datamint.exceptions import DatamintException, ResourceNotFoundError
|
|
19
|
+
from deprecated.sphinx import deprecated
|
|
19
20
|
|
|
20
21
|
_LOGGER = logging.getLogger(__name__)
|
|
21
22
|
|
|
@@ -30,33 +31,7 @@ ResourceFields: TypeAlias = Literal['modality', 'created_by', 'published_by', 'p
|
|
|
30
31
|
_PAGE_LIMIT = 5000
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
"""
|
|
35
|
-
Exception raised when a resource is not found.
|
|
36
|
-
For instance, when trying to get a resource by a non-existing id.
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
def __init__(self,
|
|
40
|
-
resource_type: str,
|
|
41
|
-
params: dict):
|
|
42
|
-
""" Constructor.
|
|
43
|
-
|
|
44
|
-
Args:
|
|
45
|
-
resource_type (str): A resource type.
|
|
46
|
-
params (dict): Dict of params identifying the sought resource.
|
|
47
|
-
"""
|
|
48
|
-
super().__init__()
|
|
49
|
-
self.resource_type = resource_type
|
|
50
|
-
self.params = params
|
|
51
|
-
|
|
52
|
-
def set_params(self, resource_type: str, params: dict):
|
|
53
|
-
self.resource_type = resource_type
|
|
54
|
-
self.params = params
|
|
55
|
-
|
|
56
|
-
def __str__(self):
|
|
57
|
-
return f"Resource '{self.resource_type}' not found for parameters: {self.params}"
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
@deprecated(reason="Please use `from datamint import Api` instead.", version="2.0.0")
|
|
60
35
|
class BaseAPIHandler:
|
|
61
36
|
"""
|
|
62
37
|
Class to handle the API requests to the Datamint API
|
|
@@ -68,6 +43,9 @@ class BaseAPIHandler:
|
|
|
68
43
|
root_url: Optional[str] = None,
|
|
69
44
|
api_key: Optional[str] = None,
|
|
70
45
|
check_connection: bool = True):
|
|
46
|
+
# deprecated
|
|
47
|
+
_LOGGER.warning("The class APIHandler is deprecated and will be removed in future versions. "
|
|
48
|
+
"Please use `from datamint import Api` instead.")
|
|
71
49
|
nest_asyncio.apply() # For running asyncio in jupyter notebooks
|
|
72
50
|
self.root_url = root_url if root_url is not None else configs.get_value(configs.APIURL_KEY)
|
|
73
51
|
if self.root_url is None:
|
|
File without changes
|
|
@@ -152,7 +152,7 @@ class CreateAnnotationDto:
|
|
|
152
152
|
type: AnnotationType | str,
|
|
153
153
|
identifier: str,
|
|
154
154
|
scope: str,
|
|
155
|
-
annotation_worklist_id: str,
|
|
155
|
+
annotation_worklist_id: str | None = None,
|
|
156
156
|
value=None,
|
|
157
157
|
imported_from: str | None = None,
|
|
158
158
|
import_author: str | None = None,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from datamint.exceptions import DatamintException
|
|
2
2
|
import argparse
|
|
3
|
-
from datamint.apihandler.api_handler import APIHandler
|
|
3
|
+
# from datamint.apihandler.api_handler import APIHandler
|
|
4
|
+
from datamint import Api
|
|
4
5
|
import os
|
|
5
6
|
from humanize import naturalsize
|
|
6
7
|
import logging
|
|
@@ -780,45 +781,33 @@ def main():
|
|
|
780
781
|
has_a_dicom_file = any(is_dicom(f) for f in files_path)
|
|
781
782
|
|
|
782
783
|
try:
|
|
783
|
-
|
|
784
|
+
api = Api(check_connection=True)
|
|
784
785
|
except DatamintException as e:
|
|
785
786
|
_USER_LOGGER.error(f'❌ Connection failed: {e}')
|
|
786
787
|
return
|
|
787
788
|
try:
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
789
|
+
print('>>>', segfiles)
|
|
790
|
+
results = api.resources.upload_resources(channel=args.channel,
|
|
791
|
+
files_path=files_path,
|
|
792
|
+
tags=args.tag,
|
|
793
|
+
on_error='skip',
|
|
794
|
+
anonymize=args.retain_pii == False and has_a_dicom_file,
|
|
795
|
+
anonymize_retain_codes=args.retain_attribute,
|
|
796
|
+
mung_filename=args.mungfilename,
|
|
797
|
+
publish=args.publish,
|
|
798
|
+
publish_to=args.project,
|
|
799
|
+
segmentation_files=segfiles,
|
|
800
|
+
transpose_segmentation=args.transpose_segmentation,
|
|
801
|
+
assemble_dicoms=True,
|
|
802
|
+
metadata=metadata_files,
|
|
803
|
+
progress_bar=True
|
|
804
|
+
)
|
|
802
805
|
except pydicom.errors.InvalidDicomError as e:
|
|
803
806
|
_USER_LOGGER.error(f'❌ Invalid DICOM file: {e}')
|
|
804
807
|
return
|
|
805
808
|
_USER_LOGGER.info('Upload finished!')
|
|
806
809
|
_LOGGER.debug(f"Number of results: {len(results)}")
|
|
807
810
|
|
|
808
|
-
# Add resources to project if specified
|
|
809
|
-
if args.project is not None:
|
|
810
|
-
_USER_LOGGER.info(f"Adding uploaded resources to project '{args.project}'...")
|
|
811
|
-
try:
|
|
812
|
-
# Filter successful uploads to get resource IDs
|
|
813
|
-
successful_resource_ids = [r for r in results if not isinstance(r, Exception)]
|
|
814
|
-
if successful_resource_ids:
|
|
815
|
-
api_handler.add_to_project(project_name=args.project, resource_ids=successful_resource_ids)
|
|
816
|
-
_USER_LOGGER.info(f"✅ Successfully added {len(successful_resource_ids)} resources to project '{args.project}'")
|
|
817
|
-
else:
|
|
818
|
-
_USER_LOGGER.warning("No successful uploads to add to project")
|
|
819
|
-
except Exception as e:
|
|
820
|
-
_USER_LOGGER.error(f"❌ Failed to add resources to project '{args.project}': {e}")
|
|
821
|
-
|
|
822
811
|
num_failures = print_results_summary(files_path, results)
|
|
823
812
|
if num_failures > 0:
|
|
824
813
|
sys.exit(1)
|