waveium 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,247 @@
1
+ """Environment models for the Waveium SDK."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from .common import EnvironmentType, _fmt_dt, _tabulate
9
+
10
+
11
+ class BoundsInput(BaseModel):
12
+ """Bounding box input for environment creation."""
13
+
14
+ min: List[float] = Field(description="Southwest corner [lat_min, lon_min]")
15
+ max: List[float] = Field(description="Northeast corner [lat_max, lon_max]")
16
+
17
+
18
+ class RouteInput(BaseModel):
19
+ """Address-based route input for environment creation."""
20
+
21
+ start: str = Field(description="Start address")
22
+ end: str = Field(description="End address")
23
+ area: str = Field(description="Geographic area for geocoding")
24
+ transport_mode: Optional[str] = Field(
25
+ default="walk",
26
+ description="Transport mode: walk, bike, drive",
27
+ )
28
+
29
+
30
+ class CreateEnvironmentRequest(BaseModel):
31
+ """Request to create a new environment."""
32
+
33
+ name: str = Field(
34
+ description="Environment name",
35
+ min_length=1,
36
+ max_length=255,
37
+ )
38
+ description: Optional[str] = Field(
39
+ default=None, description="Environment description"
40
+ )
41
+ environment_type: Optional[EnvironmentType] = Field(
42
+ default="outdoor",
43
+ description="Type: outdoor, indoor, underground",
44
+ )
45
+ bounds: Optional[BoundsInput] = Field(
46
+ default=None, description="Direct WGS84 bounding box"
47
+ )
48
+ track_file_id: Optional[str] = Field(
49
+ default=None, description="Uploaded track file ID"
50
+ )
51
+ route: Optional[RouteInput] = Field(
52
+ default=None, description="Address-based route"
53
+ )
54
+ margin: Optional[float] = Field(
55
+ default=None,
56
+ description="Margin in meters",
57
+ ge=0,
58
+ le=1000,
59
+ )
60
+ auto_create_scene: Optional[bool] = Field(
61
+ default=None, description="Auto-create scene"
62
+ )
63
+ scene_name: Optional[str] = Field(
64
+ default=None,
65
+ description="Name for auto-created scene",
66
+ max_length=255,
67
+ )
68
+ stations: Optional[Dict[str, List[float]]] = Field(
69
+ default=None, description="Station positions"
70
+ )
71
+ parameters: Optional[Dict[str, Any]] = Field(
72
+ default=None,
73
+ description="Type-specific generation parameters",
74
+ )
75
+
76
+
77
+ class UpdateEnvironmentRequest(BaseModel):
78
+ """Request to update an existing environment."""
79
+
80
+ name: Optional[str] = Field(
81
+ default=None,
82
+ description="New name",
83
+ min_length=1,
84
+ max_length=255,
85
+ )
86
+ description: Optional[str] = Field(
87
+ default=None, description="New description"
88
+ )
89
+ parameters: Optional[Dict[str, Any]] = Field(
90
+ default=None,
91
+ description="Updated generation parameters",
92
+ )
93
+
94
+
95
+ class EnvironmentResponse(BaseModel):
96
+ """Full environment response from the API."""
97
+
98
+ environment_id: str = Field(description="Environment ID")
99
+ organization_id: str = Field(description="Organization ID")
100
+ created_by_user_id: Optional[str] = Field(
101
+ default=None, description="Creator user ID"
102
+ )
103
+ name: str = Field(description="Environment name")
104
+ description: Optional[str] = Field(default=None, description="Description")
105
+ environment_type: EnvironmentType = Field(description="Environment type")
106
+ parameters: Dict[str, Any] = Field(description="Generation parameters")
107
+ parameters_hash: str = Field(description="Hash of parameters")
108
+ bounds: Optional[Dict[str, Any]] = Field(
109
+ default=None, description="Environment bounds"
110
+ )
111
+ status: str = Field(description="Environment status")
112
+ storage_path: str = Field(description="Storage path")
113
+ storage_size_bytes: Optional[int] = Field(
114
+ default=None, description="Storage size"
115
+ )
116
+ created_at: datetime = Field(description="Creation timestamp")
117
+ started_at: Optional[datetime] = Field(
118
+ default=None, description="Start timestamp"
119
+ )
120
+ completed_at: Optional[datetime] = Field(
121
+ default=None, description="Completion timestamp"
122
+ )
123
+ exit_code: Optional[int] = Field(default=None, description="Exit code")
124
+ error_message: Optional[str] = Field(
125
+ default=None, description="Error message"
126
+ )
127
+ scene_count: Optional[int] = Field(
128
+ default=None, description="Number of scenes"
129
+ )
130
+ reused: bool = Field(
131
+ default=False,
132
+ description="Whether existing was returned",
133
+ )
134
+
135
+
136
+ class CreateEnvironmentResponse(BaseModel):
137
+ """Response from creating an environment."""
138
+
139
+ environment: EnvironmentResponse = Field(
140
+ description="Created or reused environment"
141
+ )
142
+ scene: Optional[Dict[str, Any]] = Field(
143
+ default=None, description="Auto-created scene"
144
+ )
145
+ reused: bool = Field(
146
+ default=False,
147
+ description="Whether existing was returned",
148
+ )
149
+
150
+
151
+ class EnvironmentListItem(BaseModel):
152
+ """Summary item for environment listings."""
153
+
154
+ environment_id: str = Field(description="Environment ID")
155
+ name: str = Field(description="Name")
156
+ description: Optional[str] = Field(default=None, description="Description")
157
+ environment_type: EnvironmentType = Field(description="Type")
158
+ status: str = Field(description="Status")
159
+ created_at: datetime = Field(description="Created at")
160
+ completed_at: Optional[datetime] = Field(
161
+ default=None, description="Completed at"
162
+ )
163
+ scene_count: Optional[int] = Field(default=None, description="Scene count")
164
+
165
+
166
+ class PaginatedEnvironmentResponse(BaseModel):
167
+ """Paginated list of environments."""
168
+
169
+ environments: List[EnvironmentListItem] = Field(
170
+ description="List of environments"
171
+ )
172
+ total: int = Field(description="Total count")
173
+ limit: int = Field(description="Page size")
174
+ offset: int = Field(description="Offset")
175
+
176
+ def __repr__(self) -> str:
177
+ page = (self.offset // self.limit) + 1 if self.limit else 1
178
+ total_pages = (
179
+ (self.total + self.limit - 1) // self.limit if self.limit else 1
180
+ )
181
+ header = (
182
+ f"PaginatedEnvironmentResponse: {self.total} environments"
183
+ f" (page {page}/{total_pages})"
184
+ )
185
+ if not self.environments:
186
+ return header
187
+
188
+ rows = []
189
+ for env in self.environments:
190
+ rows.append(
191
+ (
192
+ env.environment_id,
193
+ env.status,
194
+ env.name,
195
+ env.environment_type,
196
+ _fmt_dt(env.created_at),
197
+ (
198
+ str(env.scene_count)
199
+ if env.scene_count is not None
200
+ else "-"
201
+ ),
202
+ )
203
+ )
204
+
205
+ table = _tabulate(
206
+ ("ID", "Status", "Name", "Type", "Created", "Scenes"),
207
+ rows,
208
+ )
209
+ return f"{header}\n{table}"
210
+
211
+
212
+ class SimilarEnvironmentResponse(BaseModel):
213
+ """Response listing similar environments."""
214
+
215
+ similar_environments: List[EnvironmentListItem] = Field(
216
+ description="Similar environments"
217
+ )
218
+ parameters_hash: str = Field(description="Parameters hash")
219
+
220
+
221
+ class EnvironmentUploadRequest(BaseModel):
222
+ """Request to initiate an environment file upload."""
223
+
224
+ filename: str = Field(description="Filename", min_length=1, max_length=255)
225
+ content_type: Optional[str] = Field(
226
+ default="application/json", description="MIME type"
227
+ )
228
+
229
+
230
+ class EnvironmentUploadResponse(BaseModel):
231
+ """Response with signed upload URL for environment files."""
232
+
233
+ upload_id: str = Field(description="Upload ID")
234
+ upload_url: str = Field(description="Signed upload URL")
235
+ method: str = Field(default="PUT", description="HTTP method")
236
+ expires_at: datetime = Field(description="URL expiration")
237
+ file_id: str = Field(description="File ID for use in parameters")
238
+
239
+
240
+ class EnvironmentUploadCompleteResponse(BaseModel):
241
+ """Response after completing an environment file upload."""
242
+
243
+ file_id: str = Field(description="File ID")
244
+ message: str = Field(
245
+ default="Upload completed successfully",
246
+ description="Status message",
247
+ )
@@ -0,0 +1,132 @@
1
+ """SSE event models for the Waveium SDK."""
2
+
3
+ import json
4
+ from typing import Literal, Optional, Union
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class ExecutionProgress(BaseModel):
10
+ """Detailed progress information for a running execution."""
11
+
12
+ overall_percent: Optional[str] = Field(
13
+ default=None, description="Overall completion percentage"
14
+ )
15
+ overall_waypoints: Optional[str] = Field(
16
+ default=None,
17
+ description="Overall waypoint progress (e.g. '4586/11466')",
18
+ )
19
+ entities_completed: Optional[str] = Field(
20
+ default=None, description="Entity completion count (e.g. '0/1')"
21
+ )
22
+ entity: Optional[str] = Field(
23
+ default=None, description="Current entity identifier"
24
+ )
25
+ entity_progress: Optional[str] = Field(
26
+ default=None, description="Current entity progress percentage"
27
+ )
28
+ entity_waypoint: Optional[str] = Field(
29
+ default=None, description="Current entity waypoint progress"
30
+ )
31
+
32
+
33
+ class StatusEvent(BaseModel):
34
+ """A status update event from an SSE stream."""
35
+
36
+ event_type: Literal["status"] = "status"
37
+ resource_id: str = Field(description="ID of the resource")
38
+ status: str = Field(description="Current resource status")
39
+ started_at: Optional[str] = Field(
40
+ default=None, description="When processing started"
41
+ )
42
+ completed_at: Optional[str] = Field(
43
+ default=None, description="When processing completed"
44
+ )
45
+ error_message: Optional[str] = Field(
46
+ default=None, description="Error message if failed"
47
+ )
48
+ progress: Optional[ExecutionProgress] = Field(
49
+ default=None, description="Detailed progress (executions only)"
50
+ )
51
+
52
+
53
+ class TimeoutEvent(BaseModel):
54
+ """Server-side SSE connection timeout event.
55
+
56
+ The server sends this when its 5-minute connection limit
57
+ is reached. The client should reconnect immediately.
58
+ """
59
+
60
+ event_type: Literal["timeout"] = "timeout"
61
+ message: Optional[str] = Field(default=None, description="Timeout message")
62
+
63
+
64
+ class ErrorEvent(BaseModel):
65
+ """An error event from an SSE stream."""
66
+
67
+ event_type: Literal["error"] = "error"
68
+ message: str = Field(description="Error message")
69
+ code: Optional[str] = Field(default=None, description="Error code")
70
+
71
+
72
+ ServerEvent = Union[StatusEvent, TimeoutEvent, ErrorEvent]
73
+
74
+ # Terminal statuses shared across resource types
75
+ TERMINAL_STATUSES = frozenset({"completed", "failed", "cancelled"})
76
+
77
+ # Resource ID field names in SSE payloads
78
+ _RESOURCE_ID_FIELDS = (
79
+ "execution_id",
80
+ "environment_id",
81
+ "scene_id",
82
+ )
83
+
84
+
85
+ def parse_server_event(event_type: str, data: str) -> ServerEvent:
86
+ """Parse a raw SSE event into a typed ServerEvent model.
87
+
88
+ Args:
89
+ event_type: The SSE event type field (status, timeout, error).
90
+ data: The JSON-encoded event data string.
91
+
92
+ Returns:
93
+ A typed ServerEvent instance.
94
+
95
+ Raises:
96
+ ValueError: If event_type is unknown or data is invalid JSON.
97
+ """
98
+ try:
99
+ payload = json.loads(data)
100
+ except json.JSONDecodeError as e:
101
+ raise ValueError(f"Invalid SSE event data: {e}") from e
102
+
103
+ resource_id = ""
104
+ for field in _RESOURCE_ID_FIELDS:
105
+ if field in payload:
106
+ resource_id = payload[field]
107
+ break
108
+
109
+ if event_type == "status":
110
+ return StatusEvent(
111
+ resource_id=resource_id,
112
+ status=payload.get("status", "unknown"),
113
+ started_at=payload.get("started_at"),
114
+ completed_at=payload.get("completed_at"),
115
+ error_message=payload.get("error_message"),
116
+ progress=(
117
+ ExecutionProgress(**payload["progress"])
118
+ if payload.get("progress")
119
+ else None
120
+ ),
121
+ )
122
+ elif event_type == "timeout":
123
+ return TimeoutEvent(
124
+ message=payload.get("message"),
125
+ )
126
+ elif event_type == "error":
127
+ return ErrorEvent(
128
+ message=payload.get("message", "Unknown error"),
129
+ code=payload.get("code"),
130
+ )
131
+ else:
132
+ raise ValueError(f"Unknown SSE event type: {event_type}")
@@ -0,0 +1,145 @@
1
+ """Execution models for the Waveium SDK."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from .common import _fmt_dt, _tabulate
9
+
10
+
11
+ class CreateExecutionRequest(BaseModel):
12
+ """Request to create a new execution."""
13
+
14
+ name: Optional[str] = Field(
15
+ default=None,
16
+ description="Execution name",
17
+ max_length=255,
18
+ )
19
+ parameters: Optional[Dict[str, Any]] = Field(
20
+ default=None, description="Execution parameters"
21
+ )
22
+
23
+
24
+ class UpdateExecutionRequest(BaseModel):
25
+ """Request to update an execution."""
26
+
27
+ name: str = Field(
28
+ description="New execution name",
29
+ min_length=1,
30
+ max_length=255,
31
+ )
32
+
33
+
34
+ class ExecutionResponse(BaseModel):
35
+ """Full execution response from the API."""
36
+
37
+ execution_id: str = Field(description="Execution ID")
38
+ scene_id: str = Field(description="Scene ID")
39
+ organization_id: str = Field(description="Organization ID")
40
+ created_by_user_id: Optional[str] = Field(
41
+ default=None, description="Creator user ID"
42
+ )
43
+ name: Optional[str] = Field(default=None, description="Execution name")
44
+ execution_number: int = Field(description="Execution number")
45
+ parameters: Dict[str, Any] = Field(description="Execution parameters")
46
+ status: str = Field(description="Execution status")
47
+ attempt_number: Optional[int] = Field(
48
+ default=None, description="Attempt number"
49
+ )
50
+ storage_path: str = Field(description="Storage path")
51
+ storage_size_bytes: Optional[int] = Field(
52
+ default=None, description="Storage size"
53
+ )
54
+ cloud_run_execution_id: Optional[str] = Field(
55
+ default=None, description="Cloud Run execution ID"
56
+ )
57
+ created_at: datetime = Field(description="Created at")
58
+ started_at: Optional[datetime] = Field(
59
+ default=None, description="Started at"
60
+ )
61
+ completed_at: Optional[datetime] = Field(
62
+ default=None, description="Completed at"
63
+ )
64
+ exit_code: Optional[int] = Field(default=None, description="Exit code")
65
+ error_message: Optional[str] = Field(
66
+ default=None, description="Error message"
67
+ )
68
+ output_metadata: Optional[Dict[str, Any]] = Field(
69
+ default=None, description="Output metadata"
70
+ )
71
+
72
+
73
+ class ExecutionListItem(BaseModel):
74
+ """Summary item for execution listings."""
75
+
76
+ execution_id: str = Field(description="Execution ID")
77
+ scene_id: str = Field(description="Scene ID")
78
+ name: Optional[str] = Field(default=None, description="Name")
79
+ execution_number: int = Field(description="Execution number")
80
+ status: str = Field(description="Status")
81
+ created_at: datetime = Field(description="Created at")
82
+ started_at: Optional[datetime] = Field(
83
+ default=None, description="Started at"
84
+ )
85
+ completed_at: Optional[datetime] = Field(
86
+ default=None, description="Completed at"
87
+ )
88
+ parameters: Optional[Dict[str, Any]] = Field(
89
+ default=None, description="Parameters"
90
+ )
91
+
92
+
93
+ class PaginatedExecutionResponse(BaseModel):
94
+ """Paginated list of executions."""
95
+
96
+ executions: List[ExecutionListItem] = Field(
97
+ description="List of executions"
98
+ )
99
+ total: int = Field(description="Total count")
100
+ limit: int = Field(description="Page size")
101
+ offset: int = Field(description="Offset")
102
+
103
+ def __repr__(self) -> str:
104
+ page = (self.offset // self.limit) + 1 if self.limit else 1
105
+ total_pages = (
106
+ (self.total + self.limit - 1) // self.limit if self.limit else 1
107
+ )
108
+ header = (
109
+ f"PaginatedExecutionResponse: {self.total} executions"
110
+ f" (page {page}/{total_pages})"
111
+ )
112
+ if not self.executions:
113
+ return header
114
+
115
+ rows = []
116
+ for ex in self.executions:
117
+ rows.append(
118
+ (
119
+ ex.execution_id,
120
+ ex.status,
121
+ ex.name or "-",
122
+ ex.scene_id,
123
+ _fmt_dt(ex.created_at),
124
+ )
125
+ )
126
+
127
+ table = _tabulate(
128
+ ("ID", "Status", "Name", "Scene", "Created"),
129
+ rows,
130
+ )
131
+ return f"{header}\n{table}"
132
+
133
+
134
+ class ExecutionProgressResponse(BaseModel):
135
+ """Progress information for an execution."""
136
+
137
+ execution_id: str = Field(description="Execution ID")
138
+ status: str = Field(description="Execution status")
139
+ progress: Optional[Dict[str, Any]] = Field(
140
+ default=None, description="Progress data"
141
+ )
142
+ last_log_timestamp: Optional[datetime] = Field(
143
+ default=None, description="Last log timestamp"
144
+ )
145
+ message: Optional[str] = Field(default=None, description="Progress message")
@@ -0,0 +1,158 @@
1
+ """File models for the Waveium SDK."""
2
+
3
+ from datetime import datetime
4
+ from typing import List, Optional
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+ from .common import _tabulate
9
+
10
+
11
+ def _fmt_size(n: int) -> str:
12
+ """Format byte count as human-readable size."""
13
+ if n < 1024:
14
+ return f"{n} B"
15
+ elif n < 1024 * 1024:
16
+ return f"{n / 1024:.1f} KB"
17
+ elif n < 1024 * 1024 * 1024:
18
+ return f"{n / (1024 * 1024):.1f} MB"
19
+ else:
20
+ return f"{n / (1024 * 1024 * 1024):.2f} GB"
21
+
22
+
23
+ class FileInfo(BaseModel):
24
+ """File metadata common to all resource types."""
25
+
26
+ file_id: str = Field(description="URL-safe file identifier")
27
+ filename: str = Field(description="Filename")
28
+ subdir: str = Field(description="Subdirectory")
29
+ file_size_bytes: int = Field(description="File size in bytes")
30
+ checksum: Optional[str] = Field(default=None, description="File checksum")
31
+
32
+
33
+ def _files_repr(
34
+ label: str,
35
+ resource_id: str,
36
+ total_files: int,
37
+ total_size_bytes: int,
38
+ files: List["FileInfo"],
39
+ ) -> str:
40
+ """Build a tabular repr for file list responses."""
41
+ header = (
42
+ f"{label}: {total_files} files, "
43
+ f"{_fmt_size(total_size_bytes)} ({resource_id})"
44
+ )
45
+ if not files:
46
+ return header
47
+
48
+ rows = []
49
+ for file_info in files:
50
+ rows.append(
51
+ (
52
+ file_info.filename,
53
+ file_info.subdir,
54
+ _fmt_size(file_info.file_size_bytes),
55
+ )
56
+ )
57
+
58
+ table = _tabulate(("Filename", "Subdir", "Size"), rows)
59
+ return f"{header}\n{table}"
60
+
61
+
62
+ class EnvironmentFilesResponse(BaseModel):
63
+ """List of files in an environment."""
64
+
65
+ environment_id: str = Field(description="Environment ID")
66
+ total_files: int = Field(description="Total file count")
67
+ total_size_bytes: int = Field(description="Total size in bytes")
68
+ files: List[FileInfo] = Field(description="List of files")
69
+
70
+ def __repr__(self) -> str:
71
+ return _files_repr(
72
+ "EnvironmentFilesResponse",
73
+ self.environment_id,
74
+ self.total_files,
75
+ self.total_size_bytes,
76
+ self.files,
77
+ )
78
+
79
+
80
+ class SceneFilesResponse(BaseModel):
81
+ """List of files in a scene."""
82
+
83
+ scene_id: str = Field(description="Scene ID")
84
+ total_files: int = Field(description="Total file count")
85
+ total_size_bytes: int = Field(description="Total size in bytes")
86
+ files: List[FileInfo] = Field(description="List of files")
87
+
88
+ def __repr__(self) -> str:
89
+ return _files_repr(
90
+ "SceneFilesResponse",
91
+ self.scene_id,
92
+ self.total_files,
93
+ self.total_size_bytes,
94
+ self.files,
95
+ )
96
+
97
+
98
+ class ExecutionFilesResponse(BaseModel):
99
+ """List of files in an execution."""
100
+
101
+ execution_id: str = Field(description="Execution ID")
102
+ total_files: int = Field(description="Total file count")
103
+ total_size_bytes: int = Field(description="Total size in bytes")
104
+ files: List[FileInfo] = Field(description="List of files")
105
+
106
+ def __repr__(self) -> str:
107
+ return _files_repr(
108
+ "ExecutionFilesResponse",
109
+ self.execution_id,
110
+ self.total_files,
111
+ self.total_size_bytes,
112
+ self.files,
113
+ )
114
+
115
+
116
+ class FileDownloadResponse(BaseModel):
117
+ """Signed download URL for a file."""
118
+
119
+ file_id: str = Field(description="File ID")
120
+ filename: str = Field(description="Filename")
121
+ download_url: str = Field(description="Signed download URL")
122
+ method: str = Field(default="GET", description="HTTP method")
123
+ expires_at: datetime = Field(description="URL expiration")
124
+ file_size_bytes: int = Field(description="File size in bytes")
125
+ checksum: Optional[str] = Field(default=None, description="File checksum")
126
+
127
+
128
+ class BatchDownloadFile(BaseModel):
129
+ """File entry in a batch download response."""
130
+
131
+ file_id: str = Field(description="File ID")
132
+ filename: str = Field(description="Filename")
133
+ subdir: Optional[str] = Field(default=None, description="Subdirectory")
134
+ download_url: str = Field(description="Signed download URL")
135
+ method: str = Field(default="GET", description="HTTP method")
136
+ expires_at: datetime = Field(description="URL expiration")
137
+ file_size_bytes: int = Field(description="File size in bytes")
138
+ checksum: Optional[str] = Field(default=None, description="File checksum")
139
+
140
+
141
+ class BatchDownloadResponse(BaseModel):
142
+ """Batch download response with signed URLs."""
143
+
144
+ model_config = {"extra": "allow"}
145
+
146
+ total_files: int = Field(description="Total file count")
147
+ total_size_bytes: int = Field(description="Total size in bytes")
148
+ expires_at: datetime = Field(description="URLs expiration")
149
+ files: List[BatchDownloadFile] = Field(description="List of download URLs")
150
+
151
+
152
+ class ArchiveDownloadResponse(BaseModel):
153
+ """Response for zip archive download."""
154
+
155
+ download_url: str = Field(description="Signed GCS URL")
156
+ filename: str = Field(description="Archive filename")
157
+ size: int = Field(description="Archive size in bytes")
158
+ expires_at: datetime = Field(description="URL expiration")