clarity-api-sdk-python 0.3.35__tar.gz → 0.3.38__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.
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/PKG-INFO +1 -1
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/pyproject.toml +1 -1
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/PKG-INFO +1 -1
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/api/sonar_wiz_api.py +17 -9
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/api/sonar_wiz_async_api.py +16 -10
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/cli/main.py +228 -53
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/processing_log.py +11 -4
- clarity_api_sdk_python-0.3.38/src/cti/model/raw_file_state.py +21 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/tests/test_cli.py +302 -65
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/tests/test_sdk_async_methods.py +44 -17
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/tests/test_sdk_methods.py +42 -20
- clarity_api_sdk_python-0.3.35/src/cti/model/raw_file_state.py +0 -12
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/README.md +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/setup.cfg +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/SOURCES.txt +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/dependency_links.txt +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/entry_points.txt +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/requires.txt +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/clarity_api_sdk_python.egg-info/top_level.txt +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/api/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/api/async_client.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/api/client.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/api/session.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/cli/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/cli/__main__.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/cli/client.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/logger/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/logger/logger.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/main.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/main_api.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/__init__.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/altitude_source.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/attitude_source.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/deferred_object_deletion.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/depth_source.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/device.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/device_type.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/final_product.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/hierarchy.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/layback_algorithm.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/layback_source.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/layback_type.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/organization.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/platform.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/platform_type.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/position_source.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/processing_job.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/project.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/projection_option.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/raw_file.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/raw_file_configuration.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/raw_file_device_mapping.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/s3.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/sidescan_ping_source.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/source.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/survey.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/target.py +0 -0
- {clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/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.
|
|
3
|
+
Version: 0.3.38
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clarity-api-sdk-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.38
|
|
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
|
{clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/api/sonar_wiz_api.py
RENAMED
|
@@ -2116,7 +2116,9 @@ class SonarWizApi:
|
|
|
2116
2116
|
response.raise_for_status()
|
|
2117
2117
|
configs = [RawFileConfiguration.model_validate(c) for c in response.json()]
|
|
2118
2118
|
if not configs:
|
|
2119
|
-
raise ValueError(
|
|
2119
|
+
raise ValueError(
|
|
2120
|
+
f"No raw file configuration found for survey {survey_id}"
|
|
2121
|
+
)
|
|
2120
2122
|
raw_file_configuration_id = configs[0].raw_file_configuration_id
|
|
2121
2123
|
|
|
2122
2124
|
# Initiate multipart upload
|
|
@@ -2156,7 +2158,9 @@ class SonarWizApi:
|
|
|
2156
2158
|
f"/api/v1/rawfiles/{raw_file.raw_file_id}/upload-parts/{part_number}/url"
|
|
2157
2159
|
)
|
|
2158
2160
|
url_response.raise_for_status()
|
|
2159
|
-
presigned_url = PartPresignedURL.model_validate(
|
|
2161
|
+
presigned_url = PartPresignedURL.model_validate(
|
|
2162
|
+
url_response.json()
|
|
2163
|
+
).url
|
|
2160
2164
|
|
|
2161
2165
|
# The server generates presigned URLs using its S3_ENDPOINT_URL,
|
|
2162
2166
|
# which may be a Docker-internal hostname (e.g. minio:9000).
|
|
@@ -2210,23 +2214,27 @@ class SonarWizApi:
|
|
|
2210
2214
|
)
|
|
2211
2215
|
return completed
|
|
2212
2216
|
|
|
2213
|
-
def
|
|
2214
|
-
"""
|
|
2215
|
-
|
|
2216
|
-
Creates a processing job and dispatches it via SQS.
|
|
2217
|
+
def process(self, survey_id: UUID, target_phase: str = "products") -> ProcessingJob:
|
|
2218
|
+
"""Dispatch a pipeline run for a survey.
|
|
2217
2219
|
|
|
2218
2220
|
Args:
|
|
2219
2221
|
survey_id: Survey to process.
|
|
2222
|
+
target_phase: Pipeline phase to run up to. Defaults to "products"
|
|
2223
|
+
(full pipeline). "scan" runs only setup → scan. The engine's
|
|
2224
|
+
pull-chain resolves any required predecessor phases.
|
|
2220
2225
|
|
|
2221
2226
|
Returns:
|
|
2222
2227
|
The created ProcessingJob.
|
|
2223
2228
|
"""
|
|
2224
|
-
response = self._client.post(
|
|
2229
|
+
response = self._client.post(
|
|
2230
|
+
"/api/v1/process",
|
|
2231
|
+
json={"survey_id": str(survey_id), "target_phase": target_phase},
|
|
2232
|
+
)
|
|
2225
2233
|
response.raise_for_status()
|
|
2226
2234
|
data = response.json()
|
|
2227
2235
|
|
|
2228
|
-
# The
|
|
2229
|
-
# Fetch the full job record.
|
|
2236
|
+
# The /process endpoint returns a dict with job_id, not a full
|
|
2237
|
+
# ProcessingJob. Fetch the full job record.
|
|
2230
2238
|
if job_id := data.get("job_id"):
|
|
2231
2239
|
return self.get_job(job_id)
|
|
2232
2240
|
|
{clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/api/sonar_wiz_async_api.py
RENAMED
|
@@ -2164,7 +2164,9 @@ class SonarWizAsyncApi:
|
|
|
2164
2164
|
response.raise_for_status()
|
|
2165
2165
|
configs = [RawFileConfiguration.model_validate(c) for c in response.json()]
|
|
2166
2166
|
if not configs:
|
|
2167
|
-
raise ValueError(
|
|
2167
|
+
raise ValueError(
|
|
2168
|
+
f"No raw file configuration found for survey {survey_id}"
|
|
2169
|
+
)
|
|
2168
2170
|
raw_file_configuration_id = configs[0].raw_file_configuration_id
|
|
2169
2171
|
|
|
2170
2172
|
# Initiate multipart upload
|
|
@@ -2206,7 +2208,9 @@ class SonarWizAsyncApi:
|
|
|
2206
2208
|
f"/api/v1/rawfiles/{raw_file.raw_file_id}/upload-parts/{part_number}/url"
|
|
2207
2209
|
)
|
|
2208
2210
|
url_response.raise_for_status()
|
|
2209
|
-
presigned_url = PartPresignedURL.model_validate(
|
|
2211
|
+
presigned_url = PartPresignedURL.model_validate(
|
|
2212
|
+
url_response.json()
|
|
2213
|
+
).url
|
|
2210
2214
|
|
|
2211
2215
|
# Upload directly to S3/MinIO (not through API client)
|
|
2212
2216
|
put_response = await s3_client.put(presigned_url, content=chunk)
|
|
@@ -2246,27 +2250,29 @@ class SonarWizAsyncApi:
|
|
|
2246
2250
|
)
|
|
2247
2251
|
return completed
|
|
2248
2252
|
|
|
2249
|
-
async def
|
|
2250
|
-
self, survey_id: UUID
|
|
2253
|
+
async def process(
|
|
2254
|
+
self, survey_id: UUID, target_phase: str = "products"
|
|
2251
2255
|
) -> ProcessingJob:
|
|
2252
|
-
"""
|
|
2253
|
-
|
|
2254
|
-
Creates a processing job and dispatches it via SQS.
|
|
2256
|
+
"""Dispatch a pipeline run for a survey.
|
|
2255
2257
|
|
|
2256
2258
|
Args:
|
|
2257
2259
|
survey_id: Survey to process.
|
|
2260
|
+
target_phase: Pipeline phase to run up to. Defaults to "products"
|
|
2261
|
+
(full pipeline). "scan" runs only setup → scan. The engine's
|
|
2262
|
+
pull-chain resolves any required predecessor phases.
|
|
2258
2263
|
|
|
2259
2264
|
Returns:
|
|
2260
2265
|
The created ProcessingJob.
|
|
2261
2266
|
"""
|
|
2262
2267
|
response = await self._client.post(
|
|
2263
|
-
|
|
2268
|
+
"/api/v1/process",
|
|
2269
|
+
json={"survey_id": str(survey_id), "target_phase": target_phase},
|
|
2264
2270
|
)
|
|
2265
2271
|
response.raise_for_status()
|
|
2266
2272
|
data = response.json()
|
|
2267
2273
|
|
|
2268
|
-
# The
|
|
2269
|
-
# Fetch the full job record.
|
|
2274
|
+
# The /process endpoint returns a dict with job_id, not a full
|
|
2275
|
+
# ProcessingJob. Fetch the full job record.
|
|
2270
2276
|
if job_id := data.get("job_id"):
|
|
2271
2277
|
return await self.get_job(job_id)
|
|
2272
2278
|
|
|
@@ -18,9 +18,16 @@ app = typer.Typer(
|
|
|
18
18
|
no_args_is_help=True,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
list_app = typer.Typer(
|
|
21
|
+
list_app = typer.Typer(
|
|
22
|
+
help="List resources (surveys, projects, jobs).", no_args_is_help=True
|
|
23
|
+
)
|
|
22
24
|
app.add_typer(list_app, name="list")
|
|
23
25
|
|
|
26
|
+
delete_app = typer.Typer(
|
|
27
|
+
help="Delete resources (rawfile, survey, project).", no_args_is_help=True
|
|
28
|
+
)
|
|
29
|
+
app.add_typer(delete_app, name="delete")
|
|
30
|
+
|
|
24
31
|
|
|
25
32
|
def _output(data: dict, as_json: bool) -> None:
|
|
26
33
|
"""Print output as JSON or human-readable."""
|
|
@@ -42,10 +49,21 @@ def _get_error_detail(e: httpx.HTTPStatusError) -> str:
|
|
|
42
49
|
def upload(
|
|
43
50
|
file: Path = typer.Argument(..., help="Path to sonar file (.xtf, .jsf, etc.)"),
|
|
44
51
|
survey: UUID = typer.Option(..., "--survey", "-s", help="Survey ID to upload to"),
|
|
45
|
-
config_id: UUID | None = typer.Option(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
config_id: UUID | None = typer.Option(
|
|
53
|
+
None,
|
|
54
|
+
"--config",
|
|
55
|
+
"-c",
|
|
56
|
+
help="Raw file configuration ID (default: survey's default)",
|
|
57
|
+
),
|
|
58
|
+
no_mappings: bool = typer.Option(
|
|
59
|
+
False, "--no-mappings", help="Skip automatic device mapping creation"
|
|
60
|
+
),
|
|
61
|
+
sidescan_stream: str = typer.Option(
|
|
62
|
+
"0", "--sidescan-stream", help="Source stream identifier for sidescan device"
|
|
63
|
+
),
|
|
64
|
+
gnss_stream: str = typer.Option(
|
|
65
|
+
"1", "--gnss-stream", help="Source stream identifier for GNSS device"
|
|
66
|
+
),
|
|
49
67
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
50
68
|
) -> None:
|
|
51
69
|
"""Upload a sonar file to a survey.
|
|
@@ -72,7 +90,9 @@ def upload(
|
|
|
72
90
|
typer.echo(f"Error: {_get_error_detail(e)}", err=True)
|
|
73
91
|
raise typer.Exit(1)
|
|
74
92
|
|
|
75
|
-
typer.echo(
|
|
93
|
+
typer.echo(
|
|
94
|
+
f"Uploaded: {result.raw_file_id} ({result.file_name}, {result.file_size} bytes)"
|
|
95
|
+
)
|
|
76
96
|
|
|
77
97
|
# Auto-create device mappings
|
|
78
98
|
if not no_mappings:
|
|
@@ -101,30 +121,45 @@ def upload(
|
|
|
101
121
|
else:
|
|
102
122
|
continue
|
|
103
123
|
|
|
104
|
-
api.create_raw_file_device_mapping(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
api.create_raw_file_device_mapping(
|
|
125
|
+
RawFileDeviceMappingCreate(
|
|
126
|
+
raw_file_id=result.raw_file_id,
|
|
127
|
+
device_id=device.device_id,
|
|
128
|
+
source_stream_identifier=stream_id,
|
|
129
|
+
input_srid=4326,
|
|
130
|
+
input_timezone=tz,
|
|
131
|
+
)
|
|
132
|
+
)
|
|
111
133
|
typer.echo(f"Mapped: {device.name} → stream {stream_id}")
|
|
112
134
|
mappings_created += 1
|
|
113
135
|
|
|
114
136
|
if mappings_created == 0:
|
|
115
|
-
typer.echo(
|
|
137
|
+
typer.echo(
|
|
138
|
+
"Warning: no sidescan/GNSS devices found on survey platform — no mappings created",
|
|
139
|
+
err=True,
|
|
140
|
+
)
|
|
116
141
|
except httpx.HTTPStatusError as e:
|
|
117
142
|
if e.response.status_code == 404:
|
|
118
|
-
typer.echo(
|
|
143
|
+
typer.echo(
|
|
144
|
+
"Warning: no platform found on survey — skipping device mappings. Run 'ftm init' first.",
|
|
145
|
+
err=True,
|
|
146
|
+
)
|
|
119
147
|
else:
|
|
120
|
-
typer.echo(
|
|
148
|
+
typer.echo(
|
|
149
|
+
f"Warning: failed to create device mappings: {_get_error_detail(e)}",
|
|
150
|
+
err=True,
|
|
151
|
+
)
|
|
121
152
|
|
|
122
153
|
_output(
|
|
123
154
|
{
|
|
124
155
|
"raw_file_id": str(result.raw_file_id),
|
|
125
156
|
"file_name": result.file_name,
|
|
126
157
|
"file_size": result.file_size,
|
|
127
|
-
"state":
|
|
158
|
+
"state": (
|
|
159
|
+
result.state.value
|
|
160
|
+
if hasattr(result.state, "value")
|
|
161
|
+
else str(result.state)
|
|
162
|
+
),
|
|
128
163
|
},
|
|
129
164
|
output_json,
|
|
130
165
|
)
|
|
@@ -133,16 +168,25 @@ def upload(
|
|
|
133
168
|
@app.command()
|
|
134
169
|
def process(
|
|
135
170
|
survey: UUID = typer.Argument(..., help="Survey ID to process"),
|
|
136
|
-
|
|
137
|
-
|
|
171
|
+
target_phase: str = typer.Option(
|
|
172
|
+
"products",
|
|
173
|
+
"--target-phase",
|
|
174
|
+
help="Pipeline phase to run up to. 'products' (default) runs the full pipeline; 'scan' runs scan only.",
|
|
175
|
+
),
|
|
176
|
+
wait: bool = typer.Option(
|
|
177
|
+
False, "--wait", "-w", help="Wait for processing to complete"
|
|
178
|
+
),
|
|
179
|
+
timeout: float = typer.Option(
|
|
180
|
+
300, "--timeout", "-t", help="Wait timeout in seconds"
|
|
181
|
+
),
|
|
138
182
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
139
183
|
) -> None:
|
|
140
184
|
"""Trigger processing for a survey."""
|
|
141
185
|
api = get_api()
|
|
142
|
-
typer.echo(f"Triggering processing for survey {survey}...")
|
|
186
|
+
typer.echo(f"Triggering processing for survey {survey} (target: {target_phase})...")
|
|
143
187
|
|
|
144
188
|
try:
|
|
145
|
-
job = api.
|
|
189
|
+
job = api.process(survey_id=survey, target_phase=target_phase)
|
|
146
190
|
except httpx.HTTPStatusError as e:
|
|
147
191
|
typer.echo(f"Error: {_get_error_detail(e)}", err=True)
|
|
148
192
|
raise typer.Exit(1)
|
|
@@ -231,12 +275,27 @@ def status(
|
|
|
231
275
|
def init(
|
|
232
276
|
project: str = typer.Option(..., "--project", "-p", help="Project name"),
|
|
233
277
|
srid: str = typer.Option(..., "--srid", help="Projected CRS (e.g. EPSG:32618)"),
|
|
234
|
-
timezone: str = typer.Option(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
278
|
+
timezone: str = typer.Option(
|
|
279
|
+
"Etc/UTC", "--timezone", "-tz", help="IANA timezone (default: Etc/UTC)"
|
|
280
|
+
),
|
|
281
|
+
org: str = typer.Option(
|
|
282
|
+
"Default Organization",
|
|
283
|
+
"--org",
|
|
284
|
+
"-o",
|
|
285
|
+
help="Organization name (found or created)",
|
|
286
|
+
),
|
|
287
|
+
survey_name: Optional[str] = typer.Option(
|
|
288
|
+
None, "--survey", "-s", help="Survey name (default: project name)"
|
|
289
|
+
),
|
|
290
|
+
description: Optional[str] = typer.Option(
|
|
291
|
+
None, "--description", "-d", help="Survey description"
|
|
292
|
+
),
|
|
293
|
+
platform_type: str = typer.Option(
|
|
294
|
+
"Towed Body", "--platform-type", help="Platform type name (from seed data)"
|
|
295
|
+
),
|
|
296
|
+
platform_name: str = typer.Option(
|
|
297
|
+
"Platform", "--platform-name", help="Platform name"
|
|
298
|
+
),
|
|
240
299
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
241
300
|
) -> None:
|
|
242
301
|
"""Initialize a new project and survey for processing.
|
|
@@ -304,7 +363,10 @@ def init(
|
|
|
304
363
|
pt = next((p for p in pt_list if p.name == platform_type), None)
|
|
305
364
|
if not pt:
|
|
306
365
|
available = ", ".join(p.name for p in pt_list)
|
|
307
|
-
typer.echo(
|
|
366
|
+
typer.echo(
|
|
367
|
+
f"Error: platform type '{platform_type}' not found. Available: {available}",
|
|
368
|
+
err=True,
|
|
369
|
+
)
|
|
308
370
|
raise typer.Exit(1)
|
|
309
371
|
except httpx.HTTPStatusError as e:
|
|
310
372
|
typer.echo(f"Error listing platform types: {_get_error_detail(e)}", err=True)
|
|
@@ -335,21 +397,43 @@ def init(
|
|
|
335
397
|
sidescan_dt = dt_by_enum.get("sidescan_sonar")
|
|
336
398
|
gnss_dt = dt_by_enum.get("gnss_gps_position_sensor")
|
|
337
399
|
if not sidescan_dt or not gnss_dt:
|
|
338
|
-
typer.echo(
|
|
400
|
+
typer.echo(
|
|
401
|
+
"Error: required device types (sidescan_sonar, gnss_gps_position_sensor) not found in seed data",
|
|
402
|
+
err=True,
|
|
403
|
+
)
|
|
339
404
|
raise typer.Exit(1)
|
|
340
405
|
|
|
341
|
-
device_defaults = dict(
|
|
406
|
+
device_defaults = dict(
|
|
407
|
+
channel=0,
|
|
408
|
+
offset_x=0,
|
|
409
|
+
offset_y=0,
|
|
410
|
+
offset_z=0,
|
|
411
|
+
offset_heading=0,
|
|
412
|
+
offset_pitch=0,
|
|
413
|
+
offset_roll=0,
|
|
414
|
+
latency=0,
|
|
415
|
+
)
|
|
342
416
|
try:
|
|
343
|
-
ss_device = api.create_device(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
417
|
+
ss_device = api.create_device(
|
|
418
|
+
DeviceCreate(
|
|
419
|
+
platform_id=new_platform.platform_id,
|
|
420
|
+
device_type_id=sidescan_dt.device_type_id,
|
|
421
|
+
name="Sidescan",
|
|
422
|
+
is_towed=True,
|
|
423
|
+
**device_defaults,
|
|
424
|
+
)
|
|
425
|
+
)
|
|
347
426
|
typer.echo(f"Created device: Sidescan ({ss_device.device_id})")
|
|
348
427
|
|
|
349
|
-
gnss_device = api.create_device(
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
428
|
+
gnss_device = api.create_device(
|
|
429
|
+
DeviceCreate(
|
|
430
|
+
platform_id=new_platform.platform_id,
|
|
431
|
+
device_type_id=gnss_dt.device_type_id,
|
|
432
|
+
name="GNSS",
|
|
433
|
+
is_towed=False,
|
|
434
|
+
**device_defaults,
|
|
435
|
+
)
|
|
436
|
+
)
|
|
353
437
|
typer.echo(f"Created device: GNSS ({gnss_device.device_id})")
|
|
354
438
|
except httpx.HTTPStatusError as e:
|
|
355
439
|
typer.echo(f"Error creating device: {_get_error_detail(e)}", err=True)
|
|
@@ -376,7 +460,9 @@ def init(
|
|
|
376
460
|
|
|
377
461
|
@list_app.command("surveys")
|
|
378
462
|
def list_surveys(
|
|
379
|
-
project_name: Optional[str] = typer.Option(
|
|
463
|
+
project_name: Optional[str] = typer.Option(
|
|
464
|
+
None, "--project", "-p", help="Filter by project name"
|
|
465
|
+
),
|
|
380
466
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
381
467
|
) -> None:
|
|
382
468
|
"""List surveys."""
|
|
@@ -393,18 +479,35 @@ def list_surveys(
|
|
|
393
479
|
raise typer.Exit(0)
|
|
394
480
|
|
|
395
481
|
if output_json:
|
|
396
|
-
typer.echo(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
482
|
+
typer.echo(
|
|
483
|
+
json.dumps(
|
|
484
|
+
[
|
|
485
|
+
{
|
|
486
|
+
"survey_id": str(s.survey_id),
|
|
487
|
+
"name": s.name,
|
|
488
|
+
"project_id": str(s.project_id),
|
|
489
|
+
"geodesy_srid": s.geodesy_srid,
|
|
490
|
+
"timezone": s.timezone_name,
|
|
491
|
+
"created": str(s.created_date),
|
|
492
|
+
}
|
|
493
|
+
for s in surveys
|
|
494
|
+
],
|
|
495
|
+
indent=2,
|
|
496
|
+
default=str,
|
|
497
|
+
)
|
|
498
|
+
)
|
|
400
499
|
else:
|
|
401
500
|
for s in surveys:
|
|
402
|
-
typer.echo(
|
|
501
|
+
typer.echo(
|
|
502
|
+
f" {s.survey_id} {s.name} (srid={s.geodesy_srid}, tz={s.timezone_name})"
|
|
503
|
+
)
|
|
403
504
|
|
|
404
505
|
|
|
405
506
|
@list_app.command("projects")
|
|
406
507
|
def list_projects(
|
|
407
|
-
org_name: Optional[str] = typer.Option(
|
|
508
|
+
org_name: Optional[str] = typer.Option(
|
|
509
|
+
None, "--org", "-o", help="Filter by organization name"
|
|
510
|
+
),
|
|
408
511
|
output_json: bool = typer.Option(False, "--json", "-j", help="Output as JSON"),
|
|
409
512
|
) -> None:
|
|
410
513
|
"""List projects."""
|
|
@@ -421,10 +524,21 @@ def list_projects(
|
|
|
421
524
|
raise typer.Exit(0)
|
|
422
525
|
|
|
423
526
|
if output_json:
|
|
424
|
-
typer.echo(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
527
|
+
typer.echo(
|
|
528
|
+
json.dumps(
|
|
529
|
+
[
|
|
530
|
+
{
|
|
531
|
+
"project_id": str(p.project_id),
|
|
532
|
+
"name": p.project_name,
|
|
533
|
+
"organization_id": str(p.organization_id),
|
|
534
|
+
"created": str(p.created_date),
|
|
535
|
+
}
|
|
536
|
+
for p in projects
|
|
537
|
+
],
|
|
538
|
+
indent=2,
|
|
539
|
+
default=str,
|
|
540
|
+
)
|
|
541
|
+
)
|
|
428
542
|
else:
|
|
429
543
|
for p in projects:
|
|
430
544
|
typer.echo(f" {p.project_id} {p.project_name}")
|
|
@@ -444,15 +558,76 @@ def list_jobs(
|
|
|
444
558
|
raise typer.Exit(0)
|
|
445
559
|
|
|
446
560
|
if output_json:
|
|
447
|
-
typer.echo(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
561
|
+
typer.echo(
|
|
562
|
+
json.dumps(
|
|
563
|
+
[
|
|
564
|
+
{
|
|
565
|
+
"job_id": str(j.job_id),
|
|
566
|
+
"status": j.status,
|
|
567
|
+
"job_type": j.job_type,
|
|
568
|
+
"created_at": str(j.created_at),
|
|
569
|
+
"completed_at": str(j.completed_at) if j.completed_at else None,
|
|
570
|
+
}
|
|
571
|
+
for j in result.jobs
|
|
572
|
+
],
|
|
573
|
+
indent=2,
|
|
574
|
+
default=str,
|
|
575
|
+
)
|
|
576
|
+
)
|
|
451
577
|
else:
|
|
452
578
|
for j in result.jobs:
|
|
453
579
|
typer.echo(f" {j.job_id} {j.status} {j.job_type} {j.created_at}")
|
|
454
580
|
|
|
455
581
|
|
|
582
|
+
def _delete_resource(
|
|
583
|
+
path: str, resource_label: str, resource_id: UUID, yes: bool
|
|
584
|
+
) -> None:
|
|
585
|
+
"""Confirm and DELETE a resource by its API path. Exits non-zero on error."""
|
|
586
|
+
if not yes and not typer.confirm(f"Delete {resource_label} {resource_id}?"):
|
|
587
|
+
typer.echo("Aborted.")
|
|
588
|
+
raise typer.Exit(1)
|
|
589
|
+
|
|
590
|
+
api = get_api()
|
|
591
|
+
try:
|
|
592
|
+
response = api._client.delete(path)
|
|
593
|
+
response.raise_for_status()
|
|
594
|
+
except httpx.HTTPStatusError as e:
|
|
595
|
+
if e.response.status_code == 404:
|
|
596
|
+
typer.echo(f"Error: {resource_label} {resource_id} not found.", err=True)
|
|
597
|
+
else:
|
|
598
|
+
typer.echo(f"Error: {_get_error_detail(e)}", err=True)
|
|
599
|
+
raise typer.Exit(1)
|
|
600
|
+
|
|
601
|
+
typer.echo(f"Deleted {resource_label} {resource_id}.")
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
@delete_app.command("rawfile")
|
|
605
|
+
def delete_rawfile(
|
|
606
|
+
raw_file_id: UUID = typer.Argument(..., help="Raw file ID"),
|
|
607
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
608
|
+
) -> None:
|
|
609
|
+
"""Delete a raw file."""
|
|
610
|
+
_delete_resource(f"/api/v1/rawfiles/{raw_file_id}", "rawfile", raw_file_id, yes)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
@delete_app.command("survey")
|
|
614
|
+
def delete_survey(
|
|
615
|
+
survey_id: UUID = typer.Argument(..., help="Survey ID"),
|
|
616
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
617
|
+
) -> None:
|
|
618
|
+
"""Delete a survey."""
|
|
619
|
+
_delete_resource(f"/api/v1/surveys/{survey_id}", "survey", survey_id, yes)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
@delete_app.command("project")
|
|
623
|
+
def delete_project(
|
|
624
|
+
project_id: UUID = typer.Argument(..., help="Project ID"),
|
|
625
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
626
|
+
) -> None:
|
|
627
|
+
"""Delete a project."""
|
|
628
|
+
_delete_resource(f"/api/v1/projects/{project_id}", "project", project_id, yes)
|
|
629
|
+
|
|
630
|
+
|
|
456
631
|
def main() -> None:
|
|
457
632
|
"""Entry point for the ftm CLI."""
|
|
458
633
|
app()
|
{clarity_api_sdk_python-0.3.35 → clarity_api_sdk_python-0.3.38}/src/cti/model/processing_log.py
RENAMED
|
@@ -17,6 +17,9 @@ class ProcessingLogBase(BaseModel):
|
|
|
17
17
|
end_timestamp: End timestamp of processing.
|
|
18
18
|
status: Status of the processing step.
|
|
19
19
|
error_details: Error details if processing failed.
|
|
20
|
+
result: Processing step result payload (e.g., stream manifest JSON
|
|
21
|
+
for scan steps); opaque string.
|
|
22
|
+
processing_hash: Content-addressable cache key (SHA-256 hex digest).
|
|
20
23
|
"""
|
|
21
24
|
|
|
22
25
|
processing_step: str
|
|
@@ -26,6 +29,8 @@ class ProcessingLogBase(BaseModel):
|
|
|
26
29
|
end_timestamp: datetime
|
|
27
30
|
status: str
|
|
28
31
|
error_details: str | None = None
|
|
32
|
+
result: str | None = None
|
|
33
|
+
processing_hash: str
|
|
29
34
|
|
|
30
35
|
|
|
31
36
|
class ProcessingLogCreate(ProcessingLogBase):
|
|
@@ -33,11 +38,11 @@ class ProcessingLogCreate(ProcessingLogBase):
|
|
|
33
38
|
|
|
34
39
|
Attributes:
|
|
35
40
|
survey_id: Foreign key reference to Survey.
|
|
36
|
-
|
|
41
|
+
raw_file_id: Foreign key reference to RawFile.
|
|
37
42
|
"""
|
|
38
43
|
|
|
39
44
|
survey_id: UUID
|
|
40
|
-
|
|
45
|
+
raw_file_id: UUID
|
|
41
46
|
|
|
42
47
|
|
|
43
48
|
class ProcessingLogUpdate(ProcessingLogBase):
|
|
@@ -51,6 +56,8 @@ class ProcessingLogUpdate(ProcessingLogBase):
|
|
|
51
56
|
end_timestamp: End timestamp of processing.
|
|
52
57
|
status: Status of the processing step.
|
|
53
58
|
error_details: Error details if processing failed.
|
|
59
|
+
result: Processing step result payload.
|
|
60
|
+
processing_hash: Content-addressable cache key.
|
|
54
61
|
"""
|
|
55
62
|
|
|
56
63
|
|
|
@@ -60,11 +67,11 @@ class ProcessingLog(ProcessingLogBase):
|
|
|
60
67
|
Attributes:
|
|
61
68
|
processing_log_id: Unique identifier for the processing log.
|
|
62
69
|
survey_id: Foreign key reference to Survey.
|
|
63
|
-
|
|
70
|
+
raw_file_id: Foreign key reference to RawFile.
|
|
64
71
|
"""
|
|
65
72
|
|
|
66
73
|
processing_log_id: UUID
|
|
67
74
|
survey_id: UUID
|
|
68
|
-
|
|
75
|
+
raw_file_id: UUID
|
|
69
76
|
|
|
70
77
|
model_config = ConfigDict(from_attributes=True)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Raw file state"""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RawFileState(str, Enum):
|
|
7
|
+
"""Status of a raw file through upload, scan, and ingest.
|
|
8
|
+
|
|
9
|
+
The legal transition graph is enforced server-side; this enum is a
|
|
10
|
+
passthrough carrying the value over the wire. See clarity-server
|
|
11
|
+
`model.raw_file_state` for the authoritative transition rules.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
UPLOADING = "uploading"
|
|
15
|
+
READY = "ready"
|
|
16
|
+
ERROR_UPLOAD = "error_upload"
|
|
17
|
+
SCANNING = "scanning"
|
|
18
|
+
SCANNED = "scanned"
|
|
19
|
+
SCAN_ERROR = "scan_error"
|
|
20
|
+
ERROR_INGEST = "error_ingest"
|
|
21
|
+
INGEST_COMPLETE = "ingest_complete"
|