clarity-api-sdk-python 0.3.28__tar.gz → 0.3.30__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.
Files changed (62) hide show
  1. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/PKG-INFO +5 -5
  2. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/README.md +4 -4
  3. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/pyproject.toml +1 -1
  4. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/clarity_api_sdk_python.egg-info/PKG-INFO +5 -5
  5. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/api/__init__.py +1 -1
  6. clarity_api_sdk_python-0.3.30/src/cti/api/session.py +122 -0
  7. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/api/sonar_wiz_api.py +106 -54
  8. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/api/sonar_wiz_async_api.py +106 -54
  9. clarity_api_sdk_python-0.3.28/src/cti/api/session.py +0 -76
  10. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/setup.cfg +0 -0
  11. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/clarity_api_sdk_python.egg-info/SOURCES.txt +0 -0
  12. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/clarity_api_sdk_python.egg-info/dependency_links.txt +0 -0
  13. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/clarity_api_sdk_python.egg-info/requires.txt +0 -0
  14. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/clarity_api_sdk_python.egg-info/top_level.txt +0 -0
  15. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/__init__.py +0 -0
  16. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/api/async_client.py +0 -0
  17. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/api/client.py +0 -0
  18. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/logger/__init__.py +0 -0
  19. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/logger/logger.py +0 -0
  20. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/main.py +0 -0
  21. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/main_api.py +0 -0
  22. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/__init__.py +0 -0
  23. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/altitude_source.py +0 -0
  24. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/attitude_source.py +0 -0
  25. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/deferred_object_deletion.py +0 -0
  26. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/depth_source.py +0 -0
  27. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/device.py +0 -0
  28. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/device_type.py +0 -0
  29. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/final_product.py +0 -0
  30. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/hierarchy.py +0 -0
  31. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/layback_algorithm.py +0 -0
  32. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/layback_source.py +0 -0
  33. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/layback_type.py +0 -0
  34. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/organization.py +0 -0
  35. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/platform.py +0 -0
  36. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/platform_type.py +0 -0
  37. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/position_source.py +0 -0
  38. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/processed_altitude.py +0 -0
  39. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/processed_attitude.py +0 -0
  40. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/processed_depth.py +0 -0
  41. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/processed_layback.py +0 -0
  42. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/processed_position.py +0 -0
  43. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/processed_sidescan_ping.py +0 -0
  44. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/processing_log.py +0 -0
  45. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/project.py +0 -0
  46. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/projection_option.py +0 -0
  47. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_altitude.py +0 -0
  48. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_attitude.py +0 -0
  49. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_depth.py +0 -0
  50. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_file.py +0 -0
  51. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_file_configuration.py +0 -0
  52. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_file_device_mapping.py +0 -0
  53. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_file_state.py +0 -0
  54. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_layback.py +0 -0
  55. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_position.py +0 -0
  56. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/raw_sidescan_ping.py +0 -0
  57. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/s3.py +0 -0
  58. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/sidescan_ping_source.py +0 -0
  59. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/source.py +0 -0
  60. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/survey.py +0 -0
  61. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/target.py +0 -0
  62. {clarity_api_sdk_python-0.3.28 → clarity_api_sdk_python-0.3.30}/src/cti/model/tow_system.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clarity-api-sdk-python
3
- Version: 0.3.28
3
+ Version: 0.3.30
4
4
  Summary: A Python SDK to connect to the CTI Clarity API server.
5
5
  Author-email: "Chesapeake Technology Inc." <support@chesapeaketech.com>
6
6
  Project-URL: Homepage, https://github.com/chesapeake-tech/clarity-api-sdk-python
@@ -114,10 +114,10 @@ if __name__ == "__main__":
114
114
  asyncio.run(main())
115
115
 
116
116
  # In other modules
117
- from cti.api.session import async_client
117
+ from cti.api.session import get_async_client
118
118
 
119
119
  async def fetch_data():
120
- if async_client:
121
- response = await async_client.get(...)
122
- return response.json()
120
+ client = get_async_client()
121
+ response = await client.get(...)
122
+ return response.json()
123
123
  ```
@@ -80,10 +80,10 @@ if __name__ == "__main__":
80
80
  asyncio.run(main())
81
81
 
82
82
  # In other modules
83
- from cti.api.session import async_client
83
+ from cti.api.session import get_async_client
84
84
 
85
85
  async def fetch_data():
86
- if async_client:
87
- response = await async_client.get(...)
88
- return response.json()
86
+ client = get_async_client()
87
+ response = await client.get(...)
88
+ return response.json()
89
89
  ```
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "clarity-api-sdk-python"
8
- version = "0.3.28"
8
+ version = "0.3.30"
9
9
  authors = [
10
10
  { name="Chesapeake Technology Inc.", email="support@chesapeaketech.com" },
11
11
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clarity-api-sdk-python
3
- Version: 0.3.28
3
+ Version: 0.3.30
4
4
  Summary: A Python SDK to connect to the CTI Clarity API server.
5
5
  Author-email: "Chesapeake Technology Inc." <support@chesapeaketech.com>
6
6
  Project-URL: Homepage, https://github.com/chesapeake-tech/clarity-api-sdk-python
@@ -114,10 +114,10 @@ if __name__ == "__main__":
114
114
  asyncio.run(main())
115
115
 
116
116
  # In other modules
117
- from cti.api.session import async_client
117
+ from cti.api.session import get_async_client
118
118
 
119
119
  async def fetch_data():
120
- if async_client:
121
- response = await async_client.get(...)
122
- return response.json()
120
+ client = get_async_client()
121
+ response = await client.get(...)
122
+ return response.json()
123
123
  ```
@@ -2,6 +2,6 @@
2
2
 
3
3
  from .async_client import ClarityApiAsyncClient
4
4
  from .client import ClarityApiClient
5
- from .session import async_client, initialize_async_client, close_async_client
5
+ from .session import get_async_client, initialize_async_client, close_async_client
6
6
  from .sonar_wiz_api import SonarWizApi
7
7
  from .sonar_wiz_async_api import SonarWizAsyncApi
@@ -0,0 +1,122 @@
1
+ """Provides event loop-scoped async client instances.
2
+
3
+ This module uses contextvars to ensure each event loop gets its own
4
+ ClarityApiAsyncClient instance, preventing issues when event loops are
5
+ created/destroyed (common in Dask workers).
6
+
7
+ Example:
8
+ # Each asyncio.run() gets its own isolated client
9
+ async def task():
10
+ await initialize_async_client()
11
+ client = get_async_client()
12
+ response = await client.get(...)
13
+ await close_async_client() # Optional, cleans up resources
14
+
15
+ # Task 1 - creates client in loop 1
16
+ asyncio.run(task())
17
+
18
+ # Task 2 - creates new client in loop 2
19
+ asyncio.run(task())
20
+ """
21
+
22
+ # pylint: disable=unnecessary-dunder-call
23
+
24
+ import asyncio
25
+ from contextvars import ContextVar
26
+ from typing import Optional
27
+
28
+ from cti.api.async_client import ClarityApiAsyncClient
29
+
30
+ # Context-local storage (isolated per event loop)
31
+ # Stores tuple of (client, loop) to track which loop the client was created in
32
+ _async_client_var: ContextVar[
33
+ Optional[tuple[ClarityApiAsyncClient, asyncio.AbstractEventLoop]]
34
+ ] = ContextVar("clarity_async_client", default=None)
35
+
36
+
37
+ def _is_client_valid(
38
+ client_tuple: Optional[tuple[ClarityApiAsyncClient, asyncio.AbstractEventLoop]],
39
+ current_loop: asyncio.AbstractEventLoop,
40
+ ) -> bool:
41
+ """Check if client is still valid for the current event loop.
42
+
43
+ Args:
44
+ client_tuple: Tuple of (client, loop) from context storage.
45
+ current_loop: The current event loop to check against.
46
+
47
+ Returns:
48
+ True if client is valid for the current loop, False otherwise.
49
+ """
50
+ if client_tuple is None:
51
+ return False
52
+
53
+ _, stored_loop = client_tuple
54
+
55
+ try:
56
+ # Check if stored loop is the same as current loop and not closed
57
+ return stored_loop is current_loop and not current_loop.is_closed()
58
+ except Exception:
59
+ pass
60
+ return False
61
+
62
+
63
+ async def initialize_async_client() -> None:
64
+ """Initialize async client for the current event loop context.
65
+
66
+ Creates a new client instance if:
67
+ - No client exists in current context, OR
68
+ - Existing client is bound to a different (closed) event loop
69
+
70
+ This function is idempotent within a single event loop.
71
+ """
72
+ current_client_tuple = _async_client_var.get()
73
+ current_loop = asyncio.get_running_loop()
74
+
75
+ # Check if we need a new client
76
+ needs_new_client = not _is_client_valid(current_client_tuple, current_loop)
77
+
78
+ if needs_new_client:
79
+ # Close old client if it exists
80
+ if current_client_tuple is not None:
81
+ old_client, _ = current_client_tuple
82
+ try:
83
+ await old_client.__aexit__(None, None, None)
84
+ except Exception:
85
+ pass # Best effort cleanup
86
+
87
+ # Create new client for this event loop
88
+ new_client = ClarityApiAsyncClient()
89
+ await new_client.__aenter__()
90
+ _async_client_var.set((new_client, current_loop))
91
+
92
+
93
+ def get_async_client() -> ClarityApiAsyncClient:
94
+ """Get the async client for the current context.
95
+
96
+ Returns:
97
+ ClarityApiAsyncClient instance for this event loop.
98
+
99
+ Raises:
100
+ ValueError: If initialize_async_client() hasn't been called in this context.
101
+ """
102
+ client_tuple = _async_client_var.get()
103
+ if client_tuple is None:
104
+ raise ValueError(
105
+ "initialize_async_client() must be called first to initialize client."
106
+ )
107
+ client, _ = client_tuple
108
+ return client
109
+
110
+
111
+ async def close_async_client() -> None:
112
+ """Close the async client for the current context.
113
+
114
+ This function should be awaited to ensure the underlying connection
115
+ pool is closed gracefully. Resources are cleaned up for the current
116
+ event loop context only.
117
+ """
118
+ client_tuple = _async_client_var.get()
119
+ if client_tuple:
120
+ client, _ = client_tuple
121
+ await client.__aexit__(None, None, None)
122
+ _async_client_var.set(None)
@@ -1164,21 +1164,20 @@ class SonarWizApi:
1164
1164
  response.raise_for_status()
1165
1165
  return [SidescanPingSource.model_validate(item) for item in response.json()]
1166
1166
 
1167
- def create_raw_position(self, position: RawPositionCreate) -> None:
1168
- """Create a raw position record.
1167
+ def create_raw_positions(self, positions: list[RawPositionCreate]) -> None:
1168
+ """Create raw position records.
1169
1169
 
1170
1170
  Args:
1171
- position: Raw position creation data.
1171
+ positions: Raw position creation data.
1172
1172
  """
1173
1173
  response = self._client.post(
1174
- "/api/v1/raw-positions", json=position.model_dump(mode="json")
1174
+ "/api/v1/raw-positions", json=[position.model_dump(mode="json") for position in positions]
1175
1175
  )
1176
1176
  response.raise_for_status()
1177
1177
  logger.debug(
1178
- "raw_position_created",
1178
+ "raw_positions_created",
1179
1179
  extra={
1180
- "position_source_id": str(position.position_source_id),
1181
- "time": str(position.time),
1180
+ "count": len(positions),
1182
1181
  },
1183
1182
  )
1184
1183
 
@@ -1224,21 +1223,20 @@ class SonarWizApi:
1224
1223
  response.raise_for_status()
1225
1224
  return RawPosition.model_validate(response.json())
1226
1225
 
1227
- def create_raw_attitude(self, attitude: RawAttitudeCreate) -> None:
1228
- """Create a raw attitude record.
1226
+ def create_raw_attitudes(self, attitudes: list[RawAttitudeCreate]) -> None:
1227
+ """Create raw attitude records.
1229
1228
 
1230
1229
  Args:
1231
- attitude: Raw attitude creation data.
1230
+ attitudes: Raw attitude creation data.
1232
1231
  """
1233
1232
  response = self._client.post(
1234
- "/api/v1/raw-attitudes", json=attitude.model_dump(mode="json")
1233
+ "/api/v1/raw-attitudes", json=[attitude.model_dump(mode="json") for attitude in attitudes]
1235
1234
  )
1236
1235
  response.raise_for_status()
1237
1236
  logger.debug(
1238
- "raw_attitude_created",
1237
+ "raw_attitudes_created",
1239
1238
  extra={
1240
- "attitude_source_id": str(attitude.attitude_source_id),
1241
- "time": str(attitude.time),
1239
+ "count": len(attitudes),
1242
1240
  },
1243
1241
  )
1244
1242
 
@@ -1284,16 +1282,22 @@ class SonarWizApi:
1284
1282
  response.raise_for_status()
1285
1283
  return RawAttitude.model_validate(response.json())
1286
1284
 
1287
- def create_raw_depth(self, depth: RawDepthCreate) -> None:
1288
- """Create a raw depth record.
1285
+ def create_raw_depths(self, depths: list[RawDepthCreate]) -> None:
1286
+ """Create raw depth records.
1289
1287
 
1290
1288
  Args:
1291
- depth: Raw depth creation data.
1289
+ depths: Raw depth creation data.
1292
1290
  """
1293
1291
  response = self._client.post(
1294
- "/api/v1/raw-depths", json=depth.model_dump(mode="json")
1292
+ "/api/v1/raw-depths", json=[depth.model_dump(mode="json") for depth in depths]
1295
1293
  )
1296
1294
  response.raise_for_status()
1295
+ logger.debug(
1296
+ "raw_depths_created",
1297
+ extra={
1298
+ "count": len(depths),
1299
+ },
1300
+ )
1297
1301
 
1298
1302
  def get_raw_depth(self, depth_id: UUID | str) -> RawDepth:
1299
1303
  """Get raw depth by ID.
@@ -1694,16 +1698,22 @@ class SonarWizApi:
1694
1698
  response.raise_for_status()
1695
1699
  return [AltitudeSource.model_validate(item) for item in response.json()]
1696
1700
 
1697
- def create_raw_altitude(self, altitude: RawAltitudeCreate) -> None:
1698
- """Create a raw altitude record.
1701
+ def create_raw_altitudes(self, altitudes: list[RawAltitudeCreate]) -> None:
1702
+ """Create raw altitude records.
1699
1703
 
1700
1704
  Args:
1701
- altitude: Raw altitude creation data.
1705
+ altitudes: Raw altitude creation data.
1702
1706
  """
1703
1707
  response = self._client.post(
1704
- "/api/v1/raw-altitudes", json=altitude.model_dump(mode="json")
1708
+ "/api/v1/raw-altitudes", json=[altitude.model_dump(mode="json") for altitude in altitudes]
1705
1709
  )
1706
1710
  response.raise_for_status()
1711
+ logger.debug(
1712
+ "raw_altitudes_created",
1713
+ extra={
1714
+ "count": len(altitudes),
1715
+ },
1716
+ )
1707
1717
 
1708
1718
  def get_raw_altitude(self, altitude_id: UUID | str) -> RawAltitude:
1709
1719
  """Get raw altitude by ID.
@@ -1811,16 +1821,22 @@ class SonarWizApi:
1811
1821
  response.raise_for_status()
1812
1822
  return [LaybackSource.model_validate(item) for item in response.json()]
1813
1823
 
1814
- def create_raw_layback(self, layback: RawLaybackCreate) -> None:
1815
- """Create a raw layback record.
1824
+ def create_raw_laybacks(self, laybacks: list[RawLaybackCreate]) -> None:
1825
+ """Create raw layback records.
1816
1826
 
1817
1827
  Args:
1818
- layback: Raw layback creation data.
1828
+ laybacks: Raw layback creation data.
1819
1829
  """
1820
1830
  response = self._client.post(
1821
- "/api/v1/raw-laybacks", json=layback.model_dump(mode="json")
1831
+ "/api/v1/raw-laybacks", json=[layback.model_dump(mode="json") for layback in laybacks]
1822
1832
  )
1823
1833
  response.raise_for_status()
1834
+ logger.debug(
1835
+ "raw_laybacks_created",
1836
+ extra={
1837
+ "count": len(laybacks),
1838
+ },
1839
+ )
1824
1840
 
1825
1841
  def get_raw_layback(self, layback_id: UUID | str) -> RawLayback:
1826
1842
  """Get raw layback by ID.
@@ -2468,18 +2484,24 @@ class SonarWizApi:
2468
2484
  response.raise_for_status()
2469
2485
  return ProcessingLog.model_validate(response.json())
2470
2486
 
2471
- def create_processed_altitude(
2472
- self, altitude: ProcessedAltitudeCreate
2487
+ def create_processed_altitudes(
2488
+ self, altitudes: list[ProcessedAltitudeCreate]
2473
2489
  ) -> None:
2474
- """Create a processed altitude record.
2490
+ """Create processed altitude records.
2475
2491
 
2476
2492
  Args:
2477
- altitude: Processed altitude creation data.
2493
+ altitudes: Processed altitude creation data.
2478
2494
  """
2479
2495
  response = self._client.post(
2480
- "/api/v1/processed-altitudes", json=altitude.model_dump(mode="json")
2496
+ "/api/v1/processed-altitudes", json=[altitude.model_dump(mode="json") for altitude in altitudes]
2481
2497
  )
2482
2498
  response.raise_for_status()
2499
+ logger.debug(
2500
+ "processed_altitudes_created",
2501
+ extra={
2502
+ "count": len(altitudes),
2503
+ },
2504
+ )
2483
2505
 
2484
2506
  def get_processed_altitude(
2485
2507
  self, altitude_id: UUID | str
@@ -2525,18 +2547,24 @@ class SonarWizApi:
2525
2547
  response.raise_for_status()
2526
2548
  return ProcessedAltitude.model_validate(response.json())
2527
2549
 
2528
- def create_processed_attitude(
2529
- self, attitude: ProcessedAttitudeCreate
2550
+ def create_processed_attitudes(
2551
+ self, attitudes: list[ProcessedAttitudeCreate]
2530
2552
  ) -> None:
2531
- """Create a processed attitude record.
2553
+ """Create processed attitude records.
2532
2554
 
2533
2555
  Args:
2534
- attitude: Processed attitude creation data.
2556
+ attitudes: Processed attitude creation data.
2535
2557
  """
2536
2558
  response = self._client.post(
2537
- "/api/v1/processed-attitudes", json=attitude.model_dump(mode="json")
2559
+ "/api/v1/processed-attitudes", json=[attitude.model_dump(mode="json") for attitude in attitudes]
2538
2560
  )
2539
2561
  response.raise_for_status()
2562
+ logger.debug(
2563
+ "processed_attitudes_created",
2564
+ extra={
2565
+ "count": len(attitudes),
2566
+ },
2567
+ )
2540
2568
 
2541
2569
  def get_processed_attitude(
2542
2570
  self, attitude_id: UUID | str
@@ -2582,16 +2610,22 @@ class SonarWizApi:
2582
2610
  response.raise_for_status()
2583
2611
  return ProcessedAttitude.model_validate(response.json())
2584
2612
 
2585
- def create_processed_depth(self, depth: ProcessedDepthCreate) -> None:
2586
- """Create a processed depth record.
2613
+ def create_processed_depths(self, depths: list[ProcessedDepthCreate]) -> None:
2614
+ """Create processed depth records.
2587
2615
 
2588
2616
  Args:
2589
- depth: Processed depth creation data.
2617
+ depths: Processed depth creation data.
2590
2618
  """
2591
2619
  response = self._client.post(
2592
- "/api/v1/processed-depths", json=depth.model_dump(mode="json")
2620
+ "/api/v1/processed-depths", json=[depth.model_dump(mode="json") for depth in depths]
2593
2621
  )
2594
2622
  response.raise_for_status()
2623
+ logger.debug(
2624
+ "processed_depths_created",
2625
+ extra={
2626
+ "count": len(depths),
2627
+ },
2628
+ )
2595
2629
 
2596
2630
  def get_processed_depth(self, depth_id: UUID | str) -> ProcessedDepth:
2597
2631
  """Get processed depth by ID.
@@ -2635,16 +2669,22 @@ class SonarWizApi:
2635
2669
  response.raise_for_status()
2636
2670
  return ProcessedDepth.model_validate(response.json())
2637
2671
 
2638
- def create_processed_layback(self, layback: ProcessedLaybackCreate) -> None:
2639
- """Create a processed layback record.
2672
+ def create_processed_laybacks(self, laybacks: list[ProcessedLaybackCreate]) -> None:
2673
+ """Create processed layback records.
2640
2674
 
2641
2675
  Args:
2642
- layback: Processed layback creation data.
2676
+ laybacks: Processed layback creation data.
2643
2677
  """
2644
2678
  response = self._client.post(
2645
- "/api/v1/processed-laybacks", json=layback.model_dump(mode="json")
2679
+ "/api/v1/processed-laybacks", json=[layback.model_dump(mode="json") for layback in laybacks]
2646
2680
  )
2647
2681
  response.raise_for_status()
2682
+ logger.debug(
2683
+ "processed_laybacks_created",
2684
+ extra={
2685
+ "count": len(laybacks),
2686
+ },
2687
+ )
2648
2688
 
2649
2689
  def get_processed_layback(self, layback_id: UUID | str) -> ProcessedLayback:
2650
2690
  """Get processed layback by ID.
@@ -2688,18 +2728,24 @@ class SonarWizApi:
2688
2728
  response.raise_for_status()
2689
2729
  return ProcessedLayback.model_validate(response.json())
2690
2730
 
2691
- def create_processed_position(
2692
- self, position: ProcessedPositionCreate
2731
+ def create_processed_positions(
2732
+ self, positions: list[ProcessedPositionCreate]
2693
2733
  ) -> None:
2694
- """Create a processed position record.
2734
+ """Create processed position records.
2695
2735
 
2696
2736
  Args:
2697
- position: Processed position creation data.
2737
+ positions: Processed position creation data.
2698
2738
  """
2699
2739
  response = self._client.post(
2700
- "/api/v1/processed-positions", json=position.model_dump(mode="json")
2740
+ "/api/v1/processed-positions", json=[position.model_dump(mode="json") for position in positions]
2701
2741
  )
2702
2742
  response.raise_for_status()
2743
+ logger.debug(
2744
+ "processed_positions_created",
2745
+ extra={
2746
+ "count": len(positions),
2747
+ },
2748
+ )
2703
2749
 
2704
2750
  def get_processed_position(
2705
2751
  self, position_id: UUID | str
@@ -2745,18 +2791,24 @@ class SonarWizApi:
2745
2791
  response.raise_for_status()
2746
2792
  return ProcessedPosition.model_validate(response.json())
2747
2793
 
2748
- def create_processed_sidescan_ping(
2749
- self, ping: ProcessedSidescanPingCreate
2794
+ def create_processed_sidescan_pings(
2795
+ self, pings: list[ProcessedSidescanPingCreate]
2750
2796
  ) -> None:
2751
- """Create a processed sidescan ping record.
2797
+ """Create processed sidescan ping records.
2752
2798
 
2753
2799
  Args:
2754
- ping: Processed sidescan ping creation data.
2800
+ pings: Processed sidescan ping creation data.
2755
2801
  """
2756
2802
  response = self._client.post(
2757
- "/api/v1/processed-sidescan-pings", json=ping.model_dump(mode="json")
2803
+ "/api/v1/processed-sidescan-pings", json=[ping.model_dump(mode="json") for ping in pings]
2758
2804
  )
2759
2805
  response.raise_for_status()
2806
+ logger.debug(
2807
+ "processed_sidescan_pings_created",
2808
+ extra={
2809
+ "count": len(pings),
2810
+ },
2811
+ )
2760
2812
 
2761
2813
  def get_processed_sidescan_ping(
2762
2814
  self, ping_id: UUID | str
@@ -1174,21 +1174,20 @@ class SonarWizAsyncApi:
1174
1174
  response.raise_for_status()
1175
1175
  return [SidescanPingSource.model_validate(item) for item in response.json()]
1176
1176
 
1177
- async def create_raw_position(self, position: RawPositionCreate) -> None:
1178
- """Create a raw position record.
1177
+ async def create_raw_positions(self, positions: list[RawPositionCreate]) -> None:
1178
+ """Create raw position records.
1179
1179
 
1180
1180
  Args:
1181
- position: Raw position creation data.
1181
+ positions: Raw position creation data.
1182
1182
  """
1183
1183
  response = await self._client.post(
1184
- "/api/v1/raw-positions", json=position.model_dump(mode="json")
1184
+ "/api/v1/raw-positions", json=[position.model_dump(mode="json") for position in positions]
1185
1185
  )
1186
1186
  response.raise_for_status()
1187
1187
  logger.debug(
1188
- "raw_position_created",
1188
+ "raw_positions_created",
1189
1189
  extra={
1190
- "position_source_id": str(position.position_source_id),
1191
- "time": str(position.time),
1190
+ "count": len(positions),
1192
1191
  },
1193
1192
  )
1194
1193
 
@@ -1234,21 +1233,20 @@ class SonarWizAsyncApi:
1234
1233
  response.raise_for_status()
1235
1234
  return RawPosition.model_validate(response.json())
1236
1235
 
1237
- async def create_raw_attitude(self, attitude: RawAttitudeCreate) -> None:
1238
- """Create a raw attitude record.
1236
+ async def create_raw_attitudes(self, attitudes: list[RawAttitudeCreate]) -> None:
1237
+ """Create raw attitude records.
1239
1238
 
1240
1239
  Args:
1241
- attitude: Raw attitude creation data.
1240
+ attitudes: Raw attitude creation data.
1242
1241
  """
1243
1242
  response = await self._client.post(
1244
- "/api/v1/raw-attitudes", json=attitude.model_dump(mode="json")
1243
+ "/api/v1/raw-attitudes", json=[attitude.model_dump(mode="json") for attitude in attitudes]
1245
1244
  )
1246
1245
  response.raise_for_status()
1247
1246
  logger.debug(
1248
- "raw_attitude_created",
1247
+ "raw_attitudes_created",
1249
1248
  extra={
1250
- "attitude_source_id": str(attitude.attitude_source_id),
1251
- "time": str(attitude.time),
1249
+ "count": len(attitudes),
1252
1250
  },
1253
1251
  )
1254
1252
 
@@ -1294,16 +1292,22 @@ class SonarWizAsyncApi:
1294
1292
  response.raise_for_status()
1295
1293
  return RawAttitude.model_validate(response.json())
1296
1294
 
1297
- async def create_raw_depth(self, depth: RawDepthCreate) -> None:
1298
- """Create a raw depth record.
1295
+ async def create_raw_depths(self, depths: list[RawDepthCreate]) -> None:
1296
+ """Create raw depth records.
1299
1297
 
1300
1298
  Args:
1301
- depth: Raw depth creation data.
1299
+ depths: Raw depth creation data.
1302
1300
  """
1303
1301
  response = await self._client.post(
1304
- "/api/v1/raw-depths", json=depth.model_dump(mode="json")
1302
+ "/api/v1/raw-depths", json=[depth.model_dump(mode="json") for depth in depths]
1305
1303
  )
1306
1304
  response.raise_for_status()
1305
+ logger.debug(
1306
+ "raw_depths_created",
1307
+ extra={
1308
+ "count": len(depths),
1309
+ },
1310
+ )
1307
1311
 
1308
1312
  async def get_raw_depth(self, depth_id: UUID | str) -> RawDepth:
1309
1313
  """Get raw depth by ID.
@@ -1704,16 +1708,22 @@ class SonarWizAsyncApi:
1704
1708
  response.raise_for_status()
1705
1709
  return [AltitudeSource.model_validate(item) for item in response.json()]
1706
1710
 
1707
- async def create_raw_altitude(self, altitude: RawAltitudeCreate) -> None:
1708
- """Create a raw altitude record.
1711
+ async def create_raw_altitudes(self, altitudes: list[RawAltitudeCreate]) -> None:
1712
+ """Create raw altitude records.
1709
1713
 
1710
1714
  Args:
1711
- altitude: Raw altitude creation data.
1715
+ altitudes: Raw altitude creation data.
1712
1716
  """
1713
1717
  response = await self._client.post(
1714
- "/api/v1/raw-altitudes", json=altitude.model_dump(mode="json")
1718
+ "/api/v1/raw-altitudes", json=[altitude.model_dump(mode="json") for altitude in altitudes]
1715
1719
  )
1716
1720
  response.raise_for_status()
1721
+ logger.debug(
1722
+ "raw_altitudes_created",
1723
+ extra={
1724
+ "count": len(altitudes),
1725
+ },
1726
+ )
1717
1727
 
1718
1728
  async def get_raw_altitude(self, altitude_id: UUID | str) -> RawAltitude:
1719
1729
  """Get raw altitude by ID.
@@ -1821,16 +1831,22 @@ class SonarWizAsyncApi:
1821
1831
  response.raise_for_status()
1822
1832
  return [LaybackSource.model_validate(item) for item in response.json()]
1823
1833
 
1824
- async def create_raw_layback(self, layback: RawLaybackCreate) -> None:
1825
- """Create a raw layback record.
1834
+ async def create_raw_laybacks(self, laybacks: list[RawLaybackCreate]) -> None:
1835
+ """Create raw layback records.
1826
1836
 
1827
1837
  Args:
1828
- layback: Raw layback creation data.
1838
+ laybacks: Raw layback creation data.
1829
1839
  """
1830
1840
  response = await self._client.post(
1831
- "/api/v1/raw-laybacks", json=layback.model_dump(mode="json")
1841
+ "/api/v1/raw-laybacks", json=[layback.model_dump(mode="json") for layback in laybacks]
1832
1842
  )
1833
1843
  response.raise_for_status()
1844
+ logger.debug(
1845
+ "raw_laybacks_created",
1846
+ extra={
1847
+ "count": len(laybacks),
1848
+ },
1849
+ )
1834
1850
 
1835
1851
  async def get_raw_layback(self, layback_id: UUID | str) -> RawLayback:
1836
1852
  """Get raw layback by ID.
@@ -2478,18 +2494,24 @@ class SonarWizAsyncApi:
2478
2494
  response.raise_for_status()
2479
2495
  return ProcessingLog.model_validate(response.json())
2480
2496
 
2481
- async def create_processed_altitude(
2482
- self, altitude: ProcessedAltitudeCreate
2497
+ async def create_processed_altitudes(
2498
+ self, altitudes: list[ProcessedAltitudeCreate]
2483
2499
  ) -> None:
2484
- """Create a processed altitude record.
2500
+ """Create processed altitude records.
2485
2501
 
2486
2502
  Args:
2487
- altitude: Processed altitude creation data.
2503
+ altitudes: Processed altitude creation data.
2488
2504
  """
2489
2505
  response = await self._client.post(
2490
- "/api/v1/processed-altitudes", json=altitude.model_dump(mode="json")
2506
+ "/api/v1/processed-altitudes", json=[altitude.model_dump(mode="json") for altitude in altitudes]
2491
2507
  )
2492
2508
  response.raise_for_status()
2509
+ logger.debug(
2510
+ "processed_altitudes_created",
2511
+ extra={
2512
+ "count": len(altitudes),
2513
+ },
2514
+ )
2493
2515
 
2494
2516
  async def get_processed_altitude(
2495
2517
  self, altitude_id: UUID | str
@@ -2535,18 +2557,24 @@ class SonarWizAsyncApi:
2535
2557
  response.raise_for_status()
2536
2558
  return ProcessedAltitude.model_validate(response.json())
2537
2559
 
2538
- async def create_processed_attitude(
2539
- self, attitude: ProcessedAttitudeCreate
2560
+ async def create_processed_attitudes(
2561
+ self, attitudes: list[ProcessedAttitudeCreate]
2540
2562
  ) -> None:
2541
- """Create a processed attitude record.
2563
+ """Create processed attitude records.
2542
2564
 
2543
2565
  Args:
2544
- attitude: Processed attitude creation data.
2566
+ attitudes: Processed attitude creation data.
2545
2567
  """
2546
2568
  response = await self._client.post(
2547
- "/api/v1/processed-attitudes", json=attitude.model_dump(mode="json")
2569
+ "/api/v1/processed-attitudes", json=[attitude.model_dump(mode="json") for attitude in attitudes]
2548
2570
  )
2549
2571
  response.raise_for_status()
2572
+ logger.debug(
2573
+ "processed_attitudes_created",
2574
+ extra={
2575
+ "count": len(attitudes),
2576
+ },
2577
+ )
2550
2578
 
2551
2579
  async def get_processed_attitude(
2552
2580
  self, attitude_id: UUID | str
@@ -2592,16 +2620,22 @@ class SonarWizAsyncApi:
2592
2620
  response.raise_for_status()
2593
2621
  return ProcessedAttitude.model_validate(response.json())
2594
2622
 
2595
- async def create_processed_depth(self, depth: ProcessedDepthCreate) -> None:
2596
- """Create a processed depth record.
2623
+ async def create_processed_depths(self, depths: list[ProcessedDepthCreate]) -> None:
2624
+ """Create processed depth records.
2597
2625
 
2598
2626
  Args:
2599
- depth: Processed depth creation data.
2627
+ depths: Processed depth creation data.
2600
2628
  """
2601
2629
  response = await self._client.post(
2602
- "/api/v1/processed-depths", json=depth.model_dump(mode="json")
2630
+ "/api/v1/processed-depths", json=[depth.model_dump(mode="json") for depth in depths]
2603
2631
  )
2604
2632
  response.raise_for_status()
2633
+ logger.debug(
2634
+ "processed_depths_created",
2635
+ extra={
2636
+ "count": len(depths),
2637
+ },
2638
+ )
2605
2639
 
2606
2640
  async def get_processed_depth(self, depth_id: UUID | str) -> ProcessedDepth:
2607
2641
  """Get processed depth by ID.
@@ -2645,16 +2679,22 @@ class SonarWizAsyncApi:
2645
2679
  response.raise_for_status()
2646
2680
  return ProcessedDepth.model_validate(response.json())
2647
2681
 
2648
- async def create_processed_layback(self, layback: ProcessedLaybackCreate) -> None:
2649
- """Create a processed layback record.
2682
+ async def create_processed_laybacks(self, laybacks: list[ProcessedLaybackCreate]) -> None:
2683
+ """Create processed layback records.
2650
2684
 
2651
2685
  Args:
2652
- layback: Processed layback creation data.
2686
+ laybacks: Processed layback creation data.
2653
2687
  """
2654
2688
  response = await self._client.post(
2655
- "/api/v1/processed-laybacks", json=layback.model_dump(mode="json")
2689
+ "/api/v1/processed-laybacks", json=[layback.model_dump(mode="json") for layback in laybacks]
2656
2690
  )
2657
2691
  response.raise_for_status()
2692
+ logger.debug(
2693
+ "processed_laybacks_created",
2694
+ extra={
2695
+ "count": len(laybacks),
2696
+ },
2697
+ )
2658
2698
 
2659
2699
  async def get_processed_layback(self, layback_id: UUID | str) -> ProcessedLayback:
2660
2700
  """Get processed layback by ID.
@@ -2698,18 +2738,24 @@ class SonarWizAsyncApi:
2698
2738
  response.raise_for_status()
2699
2739
  return ProcessedLayback.model_validate(response.json())
2700
2740
 
2701
- async def create_processed_position(
2702
- self, position: ProcessedPositionCreate
2741
+ async def create_processed_positions(
2742
+ self, positions: list[ProcessedPositionCreate]
2703
2743
  ) -> None:
2704
- """Create a processed position record.
2744
+ """Create processed position records.
2705
2745
 
2706
2746
  Args:
2707
- position: Processed position creation data.
2747
+ positions: Processed position creation data.
2708
2748
  """
2709
2749
  response = await self._client.post(
2710
- "/api/v1/processed-positions", json=position.model_dump(mode="json")
2750
+ "/api/v1/processed-positions", json=[position.model_dump(mode="json") for position in positions]
2711
2751
  )
2712
2752
  response.raise_for_status()
2753
+ logger.debug(
2754
+ "processed_positions_created",
2755
+ extra={
2756
+ "count": len(positions),
2757
+ },
2758
+ )
2713
2759
 
2714
2760
  async def get_processed_position(
2715
2761
  self, position_id: UUID | str
@@ -2755,18 +2801,24 @@ class SonarWizAsyncApi:
2755
2801
  response.raise_for_status()
2756
2802
  return ProcessedPosition.model_validate(response.json())
2757
2803
 
2758
- async def create_processed_sidescan_ping(
2759
- self, ping: ProcessedSidescanPingCreate
2804
+ async def create_processed_sidescan_pings(
2805
+ self, pings: list[ProcessedSidescanPingCreate]
2760
2806
  ) -> None:
2761
- """Create a processed sidescan ping record.
2807
+ """Create processed sidescan ping records.
2762
2808
 
2763
2809
  Args:
2764
- ping: Processed sidescan ping creation data.
2810
+ pings: Processed sidescan ping creation data.
2765
2811
  """
2766
2812
  response = await self._client.post(
2767
- "/api/v1/processed-sidescan-pings", json=ping.model_dump(mode="json")
2813
+ "/api/v1/processed-sidescan-pings", json=[ping.model_dump(mode="json") for ping in pings]
2768
2814
  )
2769
2815
  response.raise_for_status()
2816
+ logger.debug(
2817
+ "processed_sidescan_pings_created",
2818
+ extra={
2819
+ "count": len(pings),
2820
+ },
2821
+ )
2770
2822
 
2771
2823
  async def get_processed_sidescan_ping(
2772
2824
  self, ping_id: UUID | str
@@ -1,76 +0,0 @@
1
- """Provides a shared, singleton instance of the ClarityApiAsyncClient.
2
-
3
- This module allows for a single, reusable async client to be initialized
4
- and accessed throughout the application, promoting connection reuse and
5
- efficiency. The client should be initialized at application startup
6
- and closed gracefully on shutdown.
7
-
8
- Example:
9
- # In your main application entry point
10
- import asyncio
11
- from cti.api.session import initialize_async_client, close_async_client
12
-
13
- async def main():
14
- await initialize_async_client()
15
- # ... your application logic ...
16
- await close_async_client()
17
-
18
- if __name__ == "__main__":
19
- asyncio.run(main())
20
-
21
- # In other modules
22
- from cti.api.session import get_async_client
23
-
24
- async def fetch_data():
25
- response = await get_async_client().get(...)
26
- return response.json()
27
- """
28
-
29
- # pylint: disable=global-statement, unnecessary-dunder-call
30
-
31
- from cti.api.async_client import ClarityApiAsyncClient
32
-
33
- # The singleton instance, initially None.
34
- async_client: ClarityApiAsyncClient | None = None
35
-
36
-
37
- async def initialize_async_client() -> None:
38
- """Initializes the shared ClarityApiAsyncClient instance.
39
-
40
- If the client is already initialized, this function does nothing.
41
- This function should be awaited at application startup.
42
- """
43
- global async_client
44
- if async_client is None:
45
- async_client = ClarityApiAsyncClient()
46
- await async_client.__aenter__()
47
-
48
-
49
- def get_async_client() -> ClarityApiAsyncClient:
50
- """Returns the shared ClarityApiAsyncClient instance.
51
-
52
- If the client is not initialized, this function initializes it first.
53
-
54
- Returns:
55
- ClarityApiAsyncClient: The shared ClarityApiAsyncClient instance.
56
-
57
- Raises:
58
- ValueError: If initialize_async_client() has not been called first.
59
- """
60
- if async_client is None:
61
- raise ValueError(
62
- "initialize_async_client() must be called first to initialize client."
63
- )
64
- return async_client
65
-
66
-
67
- async def close_async_client() -> None:
68
- """Closes the shared ClarityApiAsyncClient instance.
69
-
70
- This function should be awaited at application shutdown to ensure
71
- the underlying connection pool is closed gracefully.
72
- """
73
- global async_client
74
- if async_client:
75
- await async_client.__aexit__(None, None, None)
76
- async_client = None