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.
waveium/exceptions.py ADDED
@@ -0,0 +1,131 @@
1
+ """Exceptions for the Waveium SDK."""
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+
6
+ class WaveiumError(Exception):
7
+ """Base exception for all Waveium SDK errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ code: Optional[str] = None,
13
+ details: Optional[Dict[str, Any]] = None,
14
+ ) -> None:
15
+ """Initialize WaveiumError.
16
+
17
+ Args:
18
+ message: Error message
19
+ code: Optional error code from API
20
+ details: Optional additional error details
21
+ """
22
+ super().__init__(message)
23
+ self.message = message
24
+ self.code = code
25
+ self.details = details or {}
26
+
27
+ def __str__(self) -> str:
28
+ """Return string representation of error."""
29
+ if self.code:
30
+ return f"[{self.code}] {self.message}"
31
+ return self.message
32
+
33
+
34
+ class AuthenticationError(WaveiumError):
35
+ """Raised when authentication fails (401)."""
36
+
37
+ pass
38
+
39
+
40
+ class AuthorizationError(WaveiumError):
41
+ """Raised when user lacks permission for operation (403)."""
42
+
43
+ pass
44
+
45
+
46
+ class ConflictError(WaveiumError):
47
+ """Raised when operation conflicts with resource state (409)."""
48
+
49
+ pass
50
+
51
+
52
+ class NotFoundError(WaveiumError):
53
+ """Raised when a resource is not found (404)."""
54
+
55
+ pass
56
+
57
+
58
+ class ValidationError(WaveiumError):
59
+ """Raised when request validation fails (400)."""
60
+
61
+ pass
62
+
63
+
64
+ class RateLimitError(WaveiumError):
65
+ """Raised when rate limit is exceeded (429)."""
66
+
67
+ pass
68
+
69
+
70
+ class APIError(WaveiumError):
71
+ """Raised for general API errors (500, 502, 503, etc.)."""
72
+
73
+ pass
74
+
75
+
76
+ class NetworkError(WaveiumError):
77
+ """Raised when network communication fails."""
78
+
79
+ pass
80
+
81
+
82
+ class FileUploadError(WaveiumError):
83
+ """Raised when file upload fails."""
84
+
85
+ pass
86
+
87
+
88
+ class FileDownloadError(WaveiumError):
89
+ """Raised when file download fails."""
90
+
91
+ pass
92
+
93
+
94
+ class ConfigurationError(WaveiumError):
95
+ """Raised when SDK configuration is invalid."""
96
+
97
+ pass
98
+
99
+
100
+ class StreamError(WaveiumError):
101
+ """Base exception for SSE streaming errors."""
102
+
103
+ pass
104
+
105
+
106
+ class StreamTimeoutError(StreamError):
107
+ """Raised when total stream duration exceeds the configured limit."""
108
+
109
+ pass
110
+
111
+
112
+ class StreamReconnectError(StreamError):
113
+ """Raised when maximum SSE reconnection attempts are exceeded."""
114
+
115
+ pass
116
+
117
+
118
+ class ExecutionFailedError(WaveiumError):
119
+ """Raised by wait() when an execution reaches 'failed' status."""
120
+
121
+ def __init__(
122
+ self,
123
+ message: str,
124
+ execution_id: str,
125
+ error_message: Optional[str] = None,
126
+ code: Optional[str] = None,
127
+ details: Optional[Dict[str, Any]] = None,
128
+ ) -> None:
129
+ super().__init__(message, code, details)
130
+ self.execution_id = execution_id
131
+ self.error_message = error_message
@@ -0,0 +1,224 @@
1
+ """Data models for the Waveium SDK."""
2
+
3
+ from .auth import (
4
+ AuthTokenResponse,
5
+ CreateTokenRequest,
6
+ TokenExchangeResponse,
7
+ TokenListResponse,
8
+ TokenUser,
9
+ )
10
+ from .common import (
11
+ BaseWaveiumModel,
12
+ ErrorDetail,
13
+ ErrorResponse,
14
+ MessageResponse,
15
+ PaginationInfo,
16
+ StorageStatsResponse,
17
+ )
18
+ from .environments import (
19
+ BoundsInput,
20
+ CreateEnvironmentRequest,
21
+ CreateEnvironmentResponse,
22
+ EnvironmentListItem,
23
+ EnvironmentResponse,
24
+ EnvironmentUploadCompleteResponse,
25
+ EnvironmentUploadRequest,
26
+ EnvironmentUploadResponse,
27
+ PaginatedEnvironmentResponse,
28
+ RouteInput,
29
+ SimilarEnvironmentResponse,
30
+ UpdateEnvironmentRequest,
31
+ )
32
+ from .executions import (
33
+ CreateExecutionRequest,
34
+ ExecutionListItem,
35
+ ExecutionProgressResponse,
36
+ ExecutionResponse,
37
+ PaginatedExecutionResponse,
38
+ UpdateExecutionRequest,
39
+ )
40
+ from .files import (
41
+ ArchiveDownloadResponse,
42
+ BatchDownloadFile,
43
+ BatchDownloadResponse,
44
+ EnvironmentFilesResponse,
45
+ ExecutionFilesResponse,
46
+ FileDownloadResponse,
47
+ FileInfo,
48
+ SceneFilesResponse,
49
+ )
50
+ from .organizations import (
51
+ CreateOrganizationRequest,
52
+ OrganizationResponse,
53
+ OrganizationStatsResponse,
54
+ UpdateOrganizationRequest,
55
+ )
56
+ from .projects import (
57
+ AddProjectEnvironmentRequest,
58
+ AddProjectMemberRequest,
59
+ CreateProjectRequest,
60
+ PaginatedProjectEnvironmentResponse,
61
+ PaginatedProjectMemberResponse,
62
+ PaginatedProjectResponse,
63
+ ProjectEnvironmentResponse,
64
+ ProjectListItem,
65
+ ProjectMemberResponse,
66
+ ProjectResponse,
67
+ UpdateProjectMemberRequest,
68
+ UpdateProjectRequest,
69
+ )
70
+ from .roles import (
71
+ PermissionResponse,
72
+ RoleDetailResponse,
73
+ RolePermissionResponse,
74
+ RoleResponse,
75
+ )
76
+ from .scenes import (
77
+ CreateSceneRequest,
78
+ PaginatedSceneResponse,
79
+ PatchSceneRequest,
80
+ SceneListItem,
81
+ SceneParameters,
82
+ SceneResponse,
83
+ SceneUploadCompleteResponse,
84
+ SceneUploadRequest,
85
+ SceneUploadResponse,
86
+ SimilarSceneResponse,
87
+ UpdateSceneRequest,
88
+ )
89
+ from .shares import (
90
+ CreateShareRequest,
91
+ PaginatedShareResponse,
92
+ ShareResponse,
93
+ )
94
+ from .teams import (
95
+ AddTeamMemberRequest,
96
+ CreateTeamRequest,
97
+ PaginatedTeamMemberResponse,
98
+ PaginatedTeamResponse,
99
+ TeamMemberResponse,
100
+ TeamResponse,
101
+ UpdateTeamRequest,
102
+ )
103
+ from .events import (
104
+ ErrorEvent,
105
+ ExecutionProgress,
106
+ ServerEvent,
107
+ StatusEvent,
108
+ TimeoutEvent,
109
+ )
110
+ from .users import (
111
+ AdminUpdateUserRequest,
112
+ CreateUserRequest,
113
+ PaginatedUserResponse,
114
+ RoleInUserResponse,
115
+ UpdateOwnUserRequest,
116
+ UserResponse,
117
+ UserRoleResponse,
118
+ )
119
+
120
+ __all__ = [
121
+ # Common
122
+ "BaseWaveiumModel",
123
+ "ErrorDetail",
124
+ "ErrorResponse",
125
+ "MessageResponse",
126
+ "PaginationInfo",
127
+ "StorageStatsResponse",
128
+ # Auth
129
+ "AuthTokenResponse",
130
+ "CreateTokenRequest",
131
+ "TokenExchangeResponse",
132
+ "TokenListResponse",
133
+ "TokenUser",
134
+ # Environments
135
+ "BoundsInput",
136
+ "CreateEnvironmentRequest",
137
+ "CreateEnvironmentResponse",
138
+ "EnvironmentListItem",
139
+ "EnvironmentResponse",
140
+ "EnvironmentUploadCompleteResponse",
141
+ "EnvironmentUploadRequest",
142
+ "EnvironmentUploadResponse",
143
+ "PaginatedEnvironmentResponse",
144
+ "RouteInput",
145
+ "SimilarEnvironmentResponse",
146
+ "UpdateEnvironmentRequest",
147
+ # Scenes
148
+ "CreateSceneRequest",
149
+ "PaginatedSceneResponse",
150
+ "PatchSceneRequest",
151
+ "SceneListItem",
152
+ "SceneParameters",
153
+ "SceneResponse",
154
+ "SceneUploadCompleteResponse",
155
+ "SceneUploadRequest",
156
+ "SceneUploadResponse",
157
+ "SimilarSceneResponse",
158
+ "UpdateSceneRequest",
159
+ # Executions
160
+ "CreateExecutionRequest",
161
+ "ExecutionListItem",
162
+ "ExecutionProgressResponse",
163
+ "ExecutionResponse",
164
+ "PaginatedExecutionResponse",
165
+ "UpdateExecutionRequest",
166
+ # Files
167
+ "ArchiveDownloadResponse",
168
+ "BatchDownloadFile",
169
+ "BatchDownloadResponse",
170
+ "EnvironmentFilesResponse",
171
+ "ExecutionFilesResponse",
172
+ "FileDownloadResponse",
173
+ "FileInfo",
174
+ "SceneFilesResponse",
175
+ # Organizations
176
+ "CreateOrganizationRequest",
177
+ "OrganizationResponse",
178
+ "OrganizationStatsResponse",
179
+ "UpdateOrganizationRequest",
180
+ # Projects
181
+ "AddProjectEnvironmentRequest",
182
+ "AddProjectMemberRequest",
183
+ "CreateProjectRequest",
184
+ "PaginatedProjectEnvironmentResponse",
185
+ "PaginatedProjectMemberResponse",
186
+ "PaginatedProjectResponse",
187
+ "ProjectEnvironmentResponse",
188
+ "ProjectListItem",
189
+ "ProjectMemberResponse",
190
+ "ProjectResponse",
191
+ "UpdateProjectMemberRequest",
192
+ "UpdateProjectRequest",
193
+ # Roles
194
+ "PermissionResponse",
195
+ "RoleDetailResponse",
196
+ "RolePermissionResponse",
197
+ "RoleResponse",
198
+ # Shares
199
+ "CreateShareRequest",
200
+ "PaginatedShareResponse",
201
+ "ShareResponse",
202
+ # Teams
203
+ "AddTeamMemberRequest",
204
+ "CreateTeamRequest",
205
+ "PaginatedTeamMemberResponse",
206
+ "PaginatedTeamResponse",
207
+ "TeamMemberResponse",
208
+ "TeamResponse",
209
+ "UpdateTeamRequest",
210
+ # Events (SSE)
211
+ "ErrorEvent",
212
+ "ExecutionProgress",
213
+ "ServerEvent",
214
+ "StatusEvent",
215
+ "TimeoutEvent",
216
+ # Users
217
+ "AdminUpdateUserRequest",
218
+ "CreateUserRequest",
219
+ "PaginatedUserResponse",
220
+ "RoleInUserResponse",
221
+ "UpdateOwnUserRequest",
222
+ "UserResponse",
223
+ "UserRoleResponse",
224
+ ]
waveium/models/auth.py ADDED
@@ -0,0 +1,58 @@
1
+ """Authentication models for the Waveium SDK."""
2
+
3
+ from datetime import datetime
4
+ from typing import List, Optional
5
+
6
+ from pydantic import Field
7
+
8
+ from .common import BaseWaveiumModel
9
+
10
+
11
+ class CreateTokenRequest(BaseWaveiumModel):
12
+ """Request to create a new API token."""
13
+
14
+ name: str = Field(description="Name/description for the API key")
15
+ expires_days: int = Field(
16
+ default=30,
17
+ ge=1,
18
+ le=365,
19
+ description="Number of days until token expires (1-365)",
20
+ )
21
+
22
+
23
+ class TokenUser(BaseWaveiumModel):
24
+ """User information included in token response."""
25
+
26
+ id: str = Field(description="User ID")
27
+ email: str = Field(description="User email")
28
+ name: Optional[str] = Field(default=None, description="User name")
29
+ organization_id: str = Field(description="Organization ID")
30
+
31
+
32
+ class TokenExchangeResponse(BaseWaveiumModel):
33
+ """Response from token exchange endpoint."""
34
+
35
+ api_key: str = Field(description="Generated API key (only returned once)")
36
+ expires_in: int = Field(description="Expiration time in seconds")
37
+ user: TokenUser = Field(description="User information")
38
+
39
+
40
+ class AuthTokenResponse(BaseWaveiumModel):
41
+ """Individual API token information."""
42
+
43
+ id: str = Field(description="Token ID")
44
+ name: str = Field(description="Token name/description")
45
+ is_active: bool = Field(description="Whether the token is active")
46
+ expires_at: Optional[datetime] = Field(
47
+ default=None, description="When the token expires"
48
+ )
49
+ created_at: datetime = Field(description="When the token was created")
50
+ last_used: Optional[datetime] = Field(
51
+ default=None, description="When the token was last used"
52
+ )
53
+
54
+
55
+ class TokenListResponse(BaseWaveiumModel):
56
+ """Response containing list of tokens."""
57
+
58
+ tokens: List[AuthTokenResponse] = Field(description="List of tokens")
@@ -0,0 +1,130 @@
1
+ """Common models used across the Waveium SDK."""
2
+
3
+ from datetime import datetime
4
+ from typing import Any, Dict, List, Literal, Optional, Sequence # noqa: F401
5
+
6
+ from pydantic import BaseModel, Field
7
+
8
+
9
+ class ErrorDetail(BaseModel):
10
+ """Error detail from API responses."""
11
+
12
+ code: str = Field(description="Error code")
13
+ message: str = Field(description="Error message")
14
+ details: Optional[Dict[str, Any]] = Field(
15
+ default=None, description="Additional error details"
16
+ )
17
+
18
+
19
+ class ErrorResponse(BaseModel):
20
+ """Error response from the API."""
21
+
22
+ error: ErrorDetail = Field(description="Error information")
23
+
24
+
25
+ class MessageResponse(BaseModel):
26
+ """Generic message response."""
27
+
28
+ message: str = Field(description="Response message")
29
+
30
+
31
+ class PaginationInfo(BaseModel):
32
+ """Pagination information for list responses."""
33
+
34
+ page: int = Field(description="Current page number (1-based)")
35
+ page_size: int = Field(description="Items per page")
36
+ total: int = Field(description="Total number of items")
37
+ total_pages: int = Field(description="Total number of pages")
38
+ has_next: bool = Field(description="Whether there is a next page")
39
+ has_prev: bool = Field(description="Whether there is a previous page")
40
+
41
+
42
+ class BaseWaveiumModel(BaseModel):
43
+ """Base model for all Waveium API models."""
44
+
45
+ model_config = {"populate_by_name": True, "use_enum_values": True}
46
+
47
+
48
+ # Status type aliases
49
+ EnvironmentStatus = Literal[
50
+ "pending",
51
+ "queued",
52
+ "running",
53
+ "completed",
54
+ "failed",
55
+ "cancelled",
56
+ ]
57
+ SceneStatus = Literal[
58
+ "pending",
59
+ "pending_environment",
60
+ "queued",
61
+ "running",
62
+ "completed",
63
+ "failed",
64
+ "cancelled",
65
+ ]
66
+ ExecutionStatus = Literal[
67
+ "queued",
68
+ "running",
69
+ "completed",
70
+ "failed",
71
+ "cancelled",
72
+ ]
73
+ EnvironmentType = Literal["outdoor", "indoor", "underground"]
74
+ ScopeFilter = Literal["mine", "shared", "org"]
75
+ SceneScopeFilter = Literal["mine", "shared", "project", "org"]
76
+ PermissionLevel = Literal["view", "use", "full"]
77
+ ShareTargetType = Literal["user", "team", "organization"]
78
+ ProjectRole = Literal["owner", "admin", "member"]
79
+ TeamRole = Literal["member", "admin"]
80
+ TransportMode = Literal["walk", "bike", "drive"]
81
+ SubscriptionTier = Literal["free", "starter", "pro", "enterprise"]
82
+ ProjectMemberType = Literal["user", "team"]
83
+
84
+
85
+ class StorageStatsResponse(BaseModel):
86
+ """Storage statistics for a resource."""
87
+
88
+ total_files: int = Field(description="Total number of files")
89
+ total_size_bytes: int = Field(description="Total size in bytes")
90
+ total_size_mb: float = Field(description="Total size in megabytes")
91
+ objects: Optional[Dict[str, Any]] = Field(
92
+ default=None, description="Object file statistics"
93
+ )
94
+
95
+
96
+ def _fmt_dt(dt: Optional[datetime]) -> str:
97
+ """Format a datetime for table display."""
98
+ if dt is None:
99
+ return "-"
100
+ return dt.strftime("%Y-%m-%d %H:%M")
101
+
102
+
103
+ def _tabulate(
104
+ headers: Sequence[str],
105
+ rows: Sequence[Sequence[str]],
106
+ ) -> str:
107
+ """Render a simple aligned text table.
108
+
109
+ Args:
110
+ headers: Column header strings.
111
+ rows: List of row tuples (same length as headers).
112
+
113
+ Returns:
114
+ Formatted table string with aligned columns.
115
+ """
116
+ # Compute column widths from headers and data
117
+ widths = [len(h) for h in headers]
118
+ for row in rows:
119
+ for i, cell in enumerate(row):
120
+ widths[i] = max(widths[i], len(cell))
121
+
122
+ # Build format string
123
+ fmt = " ".join(f"{{:<{w}}}" for w in widths)
124
+
125
+ lines = [fmt.format(*headers)]
126
+ lines.append(" ".join("-" * w for w in widths))
127
+ for row in rows:
128
+ lines.append(fmt.format(*row))
129
+
130
+ return "\n".join(lines)