TEMdb-client 1.0.0__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.
- temdb_client-1.0.0/PKG-INFO +10 -0
- temdb_client-1.0.0/README.md +2 -0
- temdb_client-1.0.0/TEMdb_client.egg-info/PKG-INFO +10 -0
- temdb_client-1.0.0/TEMdb_client.egg-info/SOURCES.txt +47 -0
- temdb_client-1.0.0/TEMdb_client.egg-info/dependency_links.txt +1 -0
- temdb_client-1.0.0/TEMdb_client.egg-info/requires.txt +7 -0
- temdb_client-1.0.0/TEMdb_client.egg-info/top_level.txt +1 -0
- temdb_client-1.0.0/pyproject.toml +21 -0
- temdb_client-1.0.0/setup.cfg +4 -0
- temdb_client-1.0.0/temdb_client/__init__.py +11 -0
- temdb_client-1.0.0/temdb_client/client.py +347 -0
- temdb_client-1.0.0/temdb_client/exceptions.py +5 -0
- temdb_client-1.0.0/temdb_client/models/__init__.py +0 -0
- temdb_client-1.0.0/temdb_client/models/acquisition.py +122 -0
- temdb_client-1.0.0/temdb_client/models/block.py +40 -0
- temdb_client-1.0.0/temdb_client/models/cutting_session.py +45 -0
- temdb_client-1.0.0/temdb_client/models/enum_schemas.py +26 -0
- temdb_client-1.0.0/temdb_client/models/roi.py +59 -0
- temdb_client-1.0.0/temdb_client/models/section.py +77 -0
- temdb_client-1.0.0/temdb_client/models/specimen.py +47 -0
- temdb_client-1.0.0/temdb_client/models/substrate.py +63 -0
- temdb_client-1.0.0/temdb_client/models/task.py +54 -0
- temdb_client-1.0.0/temdb_client/models/tile.py +80 -0
- temdb_client-1.0.0/temdb_client/resources/__init__.py +0 -0
- temdb_client-1.0.0/temdb_client/resources/acquisition.py +166 -0
- temdb_client-1.0.0/temdb_client/resources/base.py +25 -0
- temdb_client-1.0.0/temdb_client/resources/block.py +138 -0
- temdb_client-1.0.0/temdb_client/resources/cutting_session.py +194 -0
- temdb_client-1.0.0/temdb_client/resources/roi.py +86 -0
- temdb_client-1.0.0/temdb_client/resources/section.py +241 -0
- temdb_client-1.0.0/temdb_client/resources/specimen.py +145 -0
- temdb_client-1.0.0/temdb_client/resources/substrate.py +124 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/__init__.py +19 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/acquisition.py +142 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/block.py +69 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/cutting_session.py +99 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/roi.py +73 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/section.py +119 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/specimen.py +59 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/substrate.py +68 -0
- temdb_client-1.0.0/temdb_client/resources/sync_wrappers/task.py +87 -0
- temdb_client-1.0.0/temdb_client/resources/task.py +112 -0
- temdb_client-1.0.0/tests/test_acquisition.py +45 -0
- temdb_client-1.0.0/tests/test_block.py +46 -0
- temdb_client-1.0.0/tests/test_client.py +17 -0
- temdb_client-1.0.0/tests/test_cutting_session.py +48 -0
- temdb_client-1.0.0/tests/test_imaging_session.py +43 -0
- temdb_client-1.0.0/tests/test_roi.py +54 -0
- temdb_client-1.0.0/tests/test_specimen.py +45 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: TEMdb_client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Author-email: Derrick Brittain <derrickb@alleninstitute.org>
|
|
5
|
+
Requires-Dist: httpx
|
|
6
|
+
Requires-Dist: tenacity
|
|
7
|
+
Requires-Dist: pydantic
|
|
8
|
+
Provides-Extra: test
|
|
9
|
+
Requires-Dist: pytest; extra == "test"
|
|
10
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: TEMdb_client
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Author-email: Derrick Brittain <derrickb@alleninstitute.org>
|
|
5
|
+
Requires-Dist: httpx
|
|
6
|
+
Requires-Dist: tenacity
|
|
7
|
+
Requires-Dist: pydantic
|
|
8
|
+
Provides-Extra: test
|
|
9
|
+
Requires-Dist: pytest; extra == "test"
|
|
10
|
+
Requires-Dist: pytest-asyncio; extra == "test"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
TEMdb_client.egg-info/PKG-INFO
|
|
4
|
+
TEMdb_client.egg-info/SOURCES.txt
|
|
5
|
+
TEMdb_client.egg-info/dependency_links.txt
|
|
6
|
+
TEMdb_client.egg-info/requires.txt
|
|
7
|
+
TEMdb_client.egg-info/top_level.txt
|
|
8
|
+
temdb_client/__init__.py
|
|
9
|
+
temdb_client/client.py
|
|
10
|
+
temdb_client/exceptions.py
|
|
11
|
+
temdb_client/models/__init__.py
|
|
12
|
+
temdb_client/models/acquisition.py
|
|
13
|
+
temdb_client/models/block.py
|
|
14
|
+
temdb_client/models/cutting_session.py
|
|
15
|
+
temdb_client/models/enum_schemas.py
|
|
16
|
+
temdb_client/models/roi.py
|
|
17
|
+
temdb_client/models/section.py
|
|
18
|
+
temdb_client/models/specimen.py
|
|
19
|
+
temdb_client/models/substrate.py
|
|
20
|
+
temdb_client/models/task.py
|
|
21
|
+
temdb_client/models/tile.py
|
|
22
|
+
temdb_client/resources/__init__.py
|
|
23
|
+
temdb_client/resources/acquisition.py
|
|
24
|
+
temdb_client/resources/base.py
|
|
25
|
+
temdb_client/resources/block.py
|
|
26
|
+
temdb_client/resources/cutting_session.py
|
|
27
|
+
temdb_client/resources/roi.py
|
|
28
|
+
temdb_client/resources/section.py
|
|
29
|
+
temdb_client/resources/specimen.py
|
|
30
|
+
temdb_client/resources/substrate.py
|
|
31
|
+
temdb_client/resources/task.py
|
|
32
|
+
temdb_client/resources/sync_wrappers/__init__.py
|
|
33
|
+
temdb_client/resources/sync_wrappers/acquisition.py
|
|
34
|
+
temdb_client/resources/sync_wrappers/block.py
|
|
35
|
+
temdb_client/resources/sync_wrappers/cutting_session.py
|
|
36
|
+
temdb_client/resources/sync_wrappers/roi.py
|
|
37
|
+
temdb_client/resources/sync_wrappers/section.py
|
|
38
|
+
temdb_client/resources/sync_wrappers/specimen.py
|
|
39
|
+
temdb_client/resources/sync_wrappers/substrate.py
|
|
40
|
+
temdb_client/resources/sync_wrappers/task.py
|
|
41
|
+
tests/test_acquisition.py
|
|
42
|
+
tests/test_block.py
|
|
43
|
+
tests/test_client.py
|
|
44
|
+
tests/test_cutting_session.py
|
|
45
|
+
tests/test_imaging_session.py
|
|
46
|
+
tests/test_roi.py
|
|
47
|
+
tests/test_specimen.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
temdb_client
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "TEMdb_client"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Derrick Brittain", email="derrickb@alleninstitute.org" }
|
|
6
|
+
]
|
|
7
|
+
dependencies = [
|
|
8
|
+
"httpx",
|
|
9
|
+
"tenacity",
|
|
10
|
+
"pydantic",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[project.optional-dependencies]
|
|
14
|
+
test = [
|
|
15
|
+
"pytest",
|
|
16
|
+
"pytest-asyncio"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[build-system]
|
|
20
|
+
requires = ["setuptools", "wheel"]
|
|
21
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .client import create_client, AsyncTEMdbClient, SyncTEMdbClient
|
|
2
|
+
from .exceptions import TEMdbClientError, NotFoundError
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"create_client",
|
|
6
|
+
"SyncTEMdbClient",
|
|
7
|
+
"AsyncTEMdbClient",
|
|
8
|
+
"TEMdbClientError",
|
|
9
|
+
"NotFoundError",
|
|
10
|
+
]
|
|
11
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import logging
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Dict, Any, Optional, Union, List, cast
|
|
5
|
+
|
|
6
|
+
from tenacity import (
|
|
7
|
+
retry,
|
|
8
|
+
stop_after_attempt,
|
|
9
|
+
wait_exponential,
|
|
10
|
+
retry_if_exception_type,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from .resources.specimen import SpecimenResource
|
|
14
|
+
from .resources.block import BlockResource
|
|
15
|
+
from .resources.cutting_session import CuttingSessionResource
|
|
16
|
+
from .resources.substrate import SubstrateResource
|
|
17
|
+
from .resources.task import AcquisitionTaskResource
|
|
18
|
+
from .resources.roi import ROIResource
|
|
19
|
+
from .resources.acquisition import AcquisitionResource
|
|
20
|
+
from .resources.section import SectionResource
|
|
21
|
+
|
|
22
|
+
from .resources.sync_wrappers import (
|
|
23
|
+
SyncAcquisitionResourceWrapper,
|
|
24
|
+
SyncSpecimenResourceWrapper,
|
|
25
|
+
SyncBlockResourceWrapper,
|
|
26
|
+
SyncCuttingSessionResourceWrapper,
|
|
27
|
+
SyncSubstrateResourceWrapper,
|
|
28
|
+
SyncAcquisitionTaskResourceWrapper,
|
|
29
|
+
SyncROIResourceWrapper,
|
|
30
|
+
SyncSectionResourceWrapper,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from .exceptions import TEMdbClientError, NotFoundError
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AsyncTEMdbClient:
|
|
37
|
+
"""Asynchronous client for interacting with the TEMdb API."""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
base_url: str,
|
|
42
|
+
api_version: str = "v2",
|
|
43
|
+
api_key: Optional[str] = None,
|
|
44
|
+
timeout: float = 30.0,
|
|
45
|
+
debug: bool = False,
|
|
46
|
+
):
|
|
47
|
+
"""Initialize the async client."""
|
|
48
|
+
self.raw_base_url = base_url
|
|
49
|
+
self.api_version = api_version
|
|
50
|
+
self.api_url = f"{base_url}/api/{api_version}"
|
|
51
|
+
self.api_key = api_key
|
|
52
|
+
self.timeout = timeout
|
|
53
|
+
|
|
54
|
+
self.logger = logging.getLogger("temdb_client.async")
|
|
55
|
+
level = logging.DEBUG if debug else logging.INFO
|
|
56
|
+
self.logger.setLevel(level)
|
|
57
|
+
|
|
58
|
+
headers = {}
|
|
59
|
+
if api_key:
|
|
60
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
61
|
+
|
|
62
|
+
self._http_client = httpx.AsyncClient(
|
|
63
|
+
base_url=self.api_url,
|
|
64
|
+
headers=headers,
|
|
65
|
+
timeout=timeout,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
self.logger.info(
|
|
69
|
+
f"Async TEMdb client initialized: {base_url} (API v{api_version})"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
self._specimen = SpecimenResource(self._async_request, self.api_url)
|
|
73
|
+
self._block = BlockResource(self._async_request, self.api_url)
|
|
74
|
+
self._cutting_session = CuttingSessionResource(
|
|
75
|
+
self._async_request, self.api_url
|
|
76
|
+
)
|
|
77
|
+
self._substrate = SubstrateResource(self._async_request, self.api_url)
|
|
78
|
+
self._acquisition_task = AcquisitionTaskResource(
|
|
79
|
+
self._async_request, self.api_url
|
|
80
|
+
)
|
|
81
|
+
self._roi = ROIResource(self._async_request, self.api_url)
|
|
82
|
+
self._acquisition = AcquisitionResource(self._async_request, self.api_url)
|
|
83
|
+
self._section = SectionResource(self._async_request, self.api_url)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def specimen(self) -> SpecimenResource:
|
|
87
|
+
"""Access specimen-related async operations."""
|
|
88
|
+
return self._specimen
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def block(self) -> BlockResource:
|
|
92
|
+
"""Access block-related async operations."""
|
|
93
|
+
return self._block
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def cutting_session(self) -> CuttingSessionResource:
|
|
97
|
+
"""Access cutting session-related async operations."""
|
|
98
|
+
return self._cutting_session
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def substrate(self) -> SubstrateResource:
|
|
102
|
+
"""Access substrate-related async operations."""
|
|
103
|
+
return self._substrate
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def acquisition_task(self) -> AcquisitionTaskResource: # Renamed property
|
|
107
|
+
"""Access acquisition task-related async operations."""
|
|
108
|
+
return self._acquisition_task
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def roi(self) -> ROIResource:
|
|
112
|
+
"""Access ROI-related async operations."""
|
|
113
|
+
return self._roi
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def acquisition(self) -> AcquisitionResource:
|
|
117
|
+
"""Access acquisition-related async operations."""
|
|
118
|
+
return self._acquisition
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def section(self) -> SectionResource:
|
|
122
|
+
"""Access section-related async operations."""
|
|
123
|
+
return self._section
|
|
124
|
+
|
|
125
|
+
@retry(
|
|
126
|
+
stop=stop_after_attempt(3),
|
|
127
|
+
wait=wait_exponential(multiplier=1, min=4, max=10),
|
|
128
|
+
retry=retry_if_exception_type(httpx.RequestError),
|
|
129
|
+
)
|
|
130
|
+
async def _async_request(
|
|
131
|
+
self, method: str, endpoint: str, **kwargs
|
|
132
|
+
) -> Union[Dict[str, Any], List[Any]]:
|
|
133
|
+
"""Make an async HTTP request to the API."""
|
|
134
|
+
self.logger.debug(f"Async Request: {method} {endpoint}")
|
|
135
|
+
try:
|
|
136
|
+
response = await self._http_client.request(method, endpoint, **kwargs)
|
|
137
|
+
response.raise_for_status()
|
|
138
|
+
if response.status_code == 204:
|
|
139
|
+
return {}
|
|
140
|
+
return response.json()
|
|
141
|
+
except httpx.HTTPStatusError as e:
|
|
142
|
+
self.logger.error(
|
|
143
|
+
f"HTTP error: {e.response.status_code} - {e.response.text}"
|
|
144
|
+
)
|
|
145
|
+
if e.response.status_code == 404:
|
|
146
|
+
raise NotFoundError(f"Resource not found: {endpoint}") from e
|
|
147
|
+
raise TEMdbClientError(
|
|
148
|
+
f"HTTP {e.response.status_code}: {e.response.text}"
|
|
149
|
+
) from e
|
|
150
|
+
except httpx.RequestError as e:
|
|
151
|
+
self.logger.error(f"Request failed: {str(e)}")
|
|
152
|
+
raise TEMdbClientError(f"Request failed: {str(e)}") from e
|
|
153
|
+
except Exception as e:
|
|
154
|
+
self.logger.exception(
|
|
155
|
+
f"An unexpected error occurred during request to {endpoint}"
|
|
156
|
+
)
|
|
157
|
+
raise TEMdbClientError(f"Unexpected error: {str(e)}") from e
|
|
158
|
+
|
|
159
|
+
async def health_check(self) -> Dict[str, Any]:
|
|
160
|
+
"""Check if the API is available."""
|
|
161
|
+
try:
|
|
162
|
+
result = await self._async_request("GET", "/health")
|
|
163
|
+
self.logger.info(f"Async Health check: {result.get('status', 'unknown')}")
|
|
164
|
+
return cast(Dict[str, Any], result)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
self.logger.error(f"Async Health check failed: {str(e)}")
|
|
167
|
+
raise
|
|
168
|
+
|
|
169
|
+
async def get_api_info(self) -> Dict[str, Any]:
|
|
170
|
+
"""Get API information."""
|
|
171
|
+
result = await self._async_request("GET", "/")
|
|
172
|
+
return cast(Dict[str, Any], result)
|
|
173
|
+
|
|
174
|
+
async def close(self) -> None:
|
|
175
|
+
"""Close the async client and release resources."""
|
|
176
|
+
self.logger.info("Closing async TEMdb client")
|
|
177
|
+
await self._http_client.aclose()
|
|
178
|
+
|
|
179
|
+
async def __aenter__(self) -> "AsyncTEMdbClient":
|
|
180
|
+
"""Allow using client as async context manager."""
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
184
|
+
"""Close client when exiting async context manager."""
|
|
185
|
+
await self.close()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class SyncTEMdbClient:
|
|
189
|
+
"""Synchronous client for interacting with the TEMdb API (wraps AsyncTEMdbClient)."""
|
|
190
|
+
|
|
191
|
+
def __init__(
|
|
192
|
+
self,
|
|
193
|
+
base_url: str,
|
|
194
|
+
api_version: str = "v2",
|
|
195
|
+
api_key: Optional[str] = None,
|
|
196
|
+
timeout: float = 30.0,
|
|
197
|
+
debug: bool = False,
|
|
198
|
+
):
|
|
199
|
+
"""Initialize the sync client."""
|
|
200
|
+
self._async_client = AsyncTEMdbClient(
|
|
201
|
+
base_url, api_version, api_key, timeout, debug
|
|
202
|
+
)
|
|
203
|
+
self.logger = logging.getLogger("temdb_client.sync")
|
|
204
|
+
level = logging.DEBUG if debug else logging.INFO
|
|
205
|
+
self.logger.setLevel(level)
|
|
206
|
+
self.logger.info(
|
|
207
|
+
f"Sync TEMdb client initialized (wrapping async): {base_url} (API v{api_version})"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
self._acquisition = SyncAcquisitionResourceWrapper(
|
|
211
|
+
self._async_client.acquisition
|
|
212
|
+
)
|
|
213
|
+
self._specimen = SyncSpecimenResourceWrapper(self._async_client.specimen)
|
|
214
|
+
self._block = SyncBlockResourceWrapper(self._async_client.block)
|
|
215
|
+
self._cutting_session = SyncCuttingSessionResourceWrapper(
|
|
216
|
+
self._async_client.cutting_session
|
|
217
|
+
)
|
|
218
|
+
self._substrate = SyncSubstrateResourceWrapper(self._async_client.substrate)
|
|
219
|
+
self._acquisition_task = SyncAcquisitionTaskResourceWrapper(
|
|
220
|
+
self._async_client.acquisition_task
|
|
221
|
+
)
|
|
222
|
+
self._roi = SyncROIResourceWrapper(self._async_client.roi)
|
|
223
|
+
self._section = SyncSectionResourceWrapper(self._async_client.section)
|
|
224
|
+
|
|
225
|
+
@property
|
|
226
|
+
def acquisition(self) -> SyncAcquisitionResourceWrapper:
|
|
227
|
+
"""Access acquisition-related sync operations."""
|
|
228
|
+
return self._acquisition
|
|
229
|
+
|
|
230
|
+
@property
|
|
231
|
+
def specimen(self) -> SyncSpecimenResourceWrapper:
|
|
232
|
+
"""Access specimen-related sync operations."""
|
|
233
|
+
return self._specimen
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def block(self) -> SyncBlockResourceWrapper:
|
|
237
|
+
"""Access block-related sync operations."""
|
|
238
|
+
return self._block
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def cutting_session(self) -> SyncCuttingSessionResourceWrapper:
|
|
242
|
+
"""Access cutting session-related sync operations."""
|
|
243
|
+
return self._cutting_session
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def substrate(self) -> SyncSubstrateResourceWrapper:
|
|
247
|
+
"""Access substrate-related sync operations."""
|
|
248
|
+
return self._substrate
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def acquisition_task(self) -> SyncAcquisitionTaskResourceWrapper:
|
|
252
|
+
"""Access acquisition task-related sync operations."""
|
|
253
|
+
return self._acquisition_task
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def roi(self) -> SyncROIResourceWrapper:
|
|
257
|
+
"""Access ROI-related sync operations."""
|
|
258
|
+
return self._roi
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def section(self) -> SyncSectionResourceWrapper:
|
|
262
|
+
"""Access section-related sync operations."""
|
|
263
|
+
return self._section
|
|
264
|
+
|
|
265
|
+
def health_check(self) -> Dict[str, Any]:
|
|
266
|
+
"""Check if the API is available."""
|
|
267
|
+
self.logger.info("Running sync health check...")
|
|
268
|
+
try:
|
|
269
|
+
return asyncio.run(self._async_client.health_check())
|
|
270
|
+
except Exception as e:
|
|
271
|
+
self.logger.error(f"Sync Health check failed: {str(e)}")
|
|
272
|
+
raise
|
|
273
|
+
|
|
274
|
+
def get_api_info(self) -> Dict[str, Any]:
|
|
275
|
+
"""Get API information."""
|
|
276
|
+
self.logger.info("Getting sync API info...")
|
|
277
|
+
return asyncio.run(self._async_client.get_api_info())
|
|
278
|
+
|
|
279
|
+
def close(self) -> None:
|
|
280
|
+
"""Close the client and release resources."""
|
|
281
|
+
self.logger.info("Closing sync TEMdb client (and underlying async client)")
|
|
282
|
+
asyncio.run(self._async_client.close())
|
|
283
|
+
|
|
284
|
+
def __enter__(self) -> "SyncTEMdbClient":
|
|
285
|
+
"""Allow using client as sync context manager."""
|
|
286
|
+
return self
|
|
287
|
+
|
|
288
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
289
|
+
"""Close client when exiting sync context manager."""
|
|
290
|
+
self.close()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def create_client(
|
|
294
|
+
base_url: str,
|
|
295
|
+
api_version: str = "v2",
|
|
296
|
+
api_key: Optional[str] = None,
|
|
297
|
+
timeout: float = 30.0,
|
|
298
|
+
debug: bool = False,
|
|
299
|
+
async_mode: bool = True,
|
|
300
|
+
) -> Union[AsyncTEMdbClient, SyncTEMdbClient]:
|
|
301
|
+
"""
|
|
302
|
+
Factory function to create either an async or sync TEMdb client.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
base_url: Base URL of the TEMdb API (e.g., "http://localhost:8000").
|
|
306
|
+
api_version: API version string (default: "v2").
|
|
307
|
+
api_key: Optional API key for authentication.
|
|
308
|
+
timeout: Request timeout in seconds.
|
|
309
|
+
debug: Enable debug logging for the client.
|
|
310
|
+
async_mode: If True (default), returns AsyncTEMdbClient. If False, returns SyncTEMdbClient.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
An instance of AsyncTEMdbClient or SyncTEMdbClient.
|
|
314
|
+
"""
|
|
315
|
+
if async_mode:
|
|
316
|
+
return AsyncTEMdbClient(base_url, api_version, api_key, timeout, debug)
|
|
317
|
+
else:
|
|
318
|
+
return SyncTEMdbClient(base_url, api_version, api_key, timeout, debug)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
if __name__ == "__main__":
|
|
322
|
+
# Example usage
|
|
323
|
+
client = create_client("http://localhost:8000", async_mode=False)
|
|
324
|
+
try:
|
|
325
|
+
info = client.get_api_info()
|
|
326
|
+
print("API Info:", info)
|
|
327
|
+
finally:
|
|
328
|
+
client.close()
|
|
329
|
+
|
|
330
|
+
# with context manager
|
|
331
|
+
with create_client("http://localhost:8000", async_mode=False) as client:
|
|
332
|
+
try:
|
|
333
|
+
info = client.get_api_info()
|
|
334
|
+
print("API Info:", info)
|
|
335
|
+
except Exception as e:
|
|
336
|
+
print(f"Error: {e}")
|
|
337
|
+
|
|
338
|
+
# Example usage of async client
|
|
339
|
+
async def main():
|
|
340
|
+
async with create_client("http://localhost:8000", async_mode=True) as client:
|
|
341
|
+
try:
|
|
342
|
+
info = await client.get_api_info()
|
|
343
|
+
print("API Info:", info)
|
|
344
|
+
except Exception as e:
|
|
345
|
+
print(f"Error: {e}")
|
|
346
|
+
|
|
347
|
+
asyncio.run(main())
|
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
from typing import Dict, List, Optional, Any
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from .enum_schemas import AcquisitionStatus
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StorageLocation(BaseModel):
|
|
9
|
+
location_type: str
|
|
10
|
+
base_path: str
|
|
11
|
+
is_current: bool
|
|
12
|
+
date_added: datetime
|
|
13
|
+
metadata: Dict[str, Any]
|
|
14
|
+
|
|
15
|
+
class Config:
|
|
16
|
+
extra = "allow"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StorageLocationCreate(BaseModel):
|
|
20
|
+
location_type: str
|
|
21
|
+
base_path: str
|
|
22
|
+
metadata: Dict[str, Any]
|
|
23
|
+
|
|
24
|
+
class Config:
|
|
25
|
+
extra = "allow"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LensCorrectionModel(BaseModel):
|
|
29
|
+
id: int
|
|
30
|
+
type: str
|
|
31
|
+
class_name: str
|
|
32
|
+
data_string: str
|
|
33
|
+
|
|
34
|
+
class Config:
|
|
35
|
+
extra = "allow"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Calibration(BaseModel):
|
|
39
|
+
pixel_size: float
|
|
40
|
+
rotation_angle: float
|
|
41
|
+
lens_model: Optional[LensCorrectionModel] = None
|
|
42
|
+
aperture_centroid: Optional[List[float]] = None
|
|
43
|
+
|
|
44
|
+
class Config:
|
|
45
|
+
extra = "allow"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class HardwareParams(BaseModel):
|
|
49
|
+
scope_id: str
|
|
50
|
+
camera_model: str
|
|
51
|
+
camera_serial: str
|
|
52
|
+
camera_bit_depth: int
|
|
53
|
+
media_type: str
|
|
54
|
+
|
|
55
|
+
class Config:
|
|
56
|
+
extra = "allow"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AcquisitionParams(BaseModel):
|
|
60
|
+
magnification: int
|
|
61
|
+
spot_size: int
|
|
62
|
+
exposure_time: int
|
|
63
|
+
tile_size: List[int]
|
|
64
|
+
tile_overlap: float
|
|
65
|
+
saved_bit_depth: int
|
|
66
|
+
|
|
67
|
+
class Config:
|
|
68
|
+
extra = "allow"
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class AcquisitionBase(BaseModel):
|
|
72
|
+
hardware_settings: Optional[HardwareParams] = None
|
|
73
|
+
acquisition_settings: Optional[AcquisitionParams] = None
|
|
74
|
+
calibration_info: Optional[Calibration] = None
|
|
75
|
+
status: Optional[AcquisitionStatus] = None
|
|
76
|
+
tilt_angle: Optional[float] = None
|
|
77
|
+
lens_correction: Optional[bool] = None
|
|
78
|
+
end_time: Optional[datetime] = None
|
|
79
|
+
storage_locations: Optional[List[StorageLocation]] = None
|
|
80
|
+
montage_set_name: Optional[str] = None
|
|
81
|
+
sub_region: Optional[Dict[str, int]] = None
|
|
82
|
+
replaces_acquisition_id: Optional[int] = None
|
|
83
|
+
montage_id: Optional[str] = None
|
|
84
|
+
acquisition_id: Optional[str] = None
|
|
85
|
+
roi_id: Optional[int] = None
|
|
86
|
+
acquisition_task_id: Optional[str] = None
|
|
87
|
+
specimen_id: Optional[str] = None
|
|
88
|
+
|
|
89
|
+
class Config:
|
|
90
|
+
extra = "allow"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class AcquisitionCreate(AcquisitionBase):
|
|
94
|
+
montage_id: str = Field(...)
|
|
95
|
+
acquisition_id: str = Field(...)
|
|
96
|
+
roi_id: int = Field(...)
|
|
97
|
+
acquisition_task_id: str = Field(...)
|
|
98
|
+
hardware_settings: HardwareParams = Field(...)
|
|
99
|
+
acquisition_settings: AcquisitionParams = Field(...)
|
|
100
|
+
tilt_angle: float = Field(...)
|
|
101
|
+
lens_correction: bool = Field(...)
|
|
102
|
+
|
|
103
|
+
status: AcquisitionStatus = AcquisitionStatus.IMAGING
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class AcquisitionUpdate(AcquisitionBase):
|
|
107
|
+
calibration_info: Optional[Dict[str, Any]] = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class AcquisitionResponse(AcquisitionBase):
|
|
111
|
+
acquisition_id: str = Field(...)
|
|
112
|
+
montage_id: str = Field(...)
|
|
113
|
+
specimen_id: str = Field(...)
|
|
114
|
+
roi_id: int = Field(...)
|
|
115
|
+
acquisition_task_id: str = Field(...)
|
|
116
|
+
hardware_settings: HardwareParams
|
|
117
|
+
acquisition_settings: AcquisitionParams
|
|
118
|
+
status: AcquisitionStatus
|
|
119
|
+
start_time: datetime
|
|
120
|
+
|
|
121
|
+
created_at: Optional[datetime] = None
|
|
122
|
+
updated_at: Optional[datetime] = None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BlockBase(BaseModel):
|
|
7
|
+
microCT_info: Optional[Dict[str, Any]] = Field(
|
|
8
|
+
None, description="MicroCT information of block"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
class Config:
|
|
12
|
+
extra = "allow"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BlockCreate(BlockBase):
|
|
16
|
+
block_id: str = Field(
|
|
17
|
+
..., description="Unique human-readable identifier for the block"
|
|
18
|
+
)
|
|
19
|
+
specimen_id: str = Field(
|
|
20
|
+
..., description="Human-readable identifier of the parent specimen"
|
|
21
|
+
)
|
|
22
|
+
microCT_info: Dict[str, Any] = Field(
|
|
23
|
+
..., description="MicroCT information of block"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BlockUpdate(BlockBase):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BlockResponse(BlockBase):
|
|
32
|
+
|
|
33
|
+
block_id: str = Field(
|
|
34
|
+
..., description="Unique human-readable identifier for the block"
|
|
35
|
+
)
|
|
36
|
+
specimen_id: str = Field(
|
|
37
|
+
..., description="Human-readable identifier of the parent specimen"
|
|
38
|
+
)
|
|
39
|
+
created_at: Optional[datetime] = None
|
|
40
|
+
updated_at: Optional[datetime] = None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CuttingSessionBase(BaseModel):
|
|
7
|
+
start_time: Optional[datetime] = None
|
|
8
|
+
end_time: Optional[datetime] = None
|
|
9
|
+
operator: Optional[str] = None
|
|
10
|
+
sectioning_device: Optional[str] = None
|
|
11
|
+
media_type: Optional[str] = None
|
|
12
|
+
knife_id: Optional[str] = None
|
|
13
|
+
block_id: Optional[str] = None
|
|
14
|
+
specimen_id: Optional[str] = None
|
|
15
|
+
|
|
16
|
+
class Config:
|
|
17
|
+
extra = "allow"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CuttingSessionCreate(CuttingSessionBase):
|
|
21
|
+
cutting_session_id: str = Field(..., description="ID of cutting session")
|
|
22
|
+
start_time: datetime = Field(..., description="Time when cutting session started")
|
|
23
|
+
sectioning_device: str = Field(..., description="Device used for sectioning")
|
|
24
|
+
media_type: str = Field(
|
|
25
|
+
..., description="Type of substrate the sections are placed upon"
|
|
26
|
+
)
|
|
27
|
+
block_id: str = Field(
|
|
28
|
+
..., description="ID of block cutting session is associated with"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CuttingSessionUpdate(CuttingSessionBase):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CuttingSessionResponse(CuttingSessionBase):
|
|
37
|
+
cutting_session_id: str = Field(..., description="Unique ID of cutting session")
|
|
38
|
+
specimen_id: str = Field(..., description="Human-readable ID of specimen")
|
|
39
|
+
block_id: str = Field(..., description="Human-readable ID of block")
|
|
40
|
+
start_time: datetime
|
|
41
|
+
sectioning_device: str
|
|
42
|
+
media_type: str
|
|
43
|
+
|
|
44
|
+
created_at: datetime
|
|
45
|
+
updated_at: Optional[datetime] = None
|