waveium 0.2.4__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {waveium-0.2.4 → waveium-0.4.0}/.gitignore +3 -0
  2. waveium-0.4.0/CHANGELOG.md +109 -0
  3. waveium-0.4.0/PKG-INFO +314 -0
  4. waveium-0.4.0/README.md +276 -0
  5. waveium-0.4.0/data/helsinki/track.gpx +38 -0
  6. waveium-0.4.0/docs/getting_started.md +381 -0
  7. {waveium-0.2.4 → waveium-0.4.0}/examples/basic_usage.py +12 -6
  8. waveium-0.4.0/examples/helsinki_track_usage.py +213 -0
  9. waveium-0.4.0/examples/helsinki_usage.py +139 -0
  10. waveium-0.4.0/examples/inspect_scene.py +58 -0
  11. {waveium-0.2.4 → waveium-0.4.0}/examples/load_and_plot.py +2 -2
  12. {waveium-0.2.4 → waveium-0.4.0}/examples/underground_workflow.py +4 -4
  13. {waveium-0.2.4 → waveium-0.4.0}/examples/underground_workflow_sse.py +3 -3
  14. {waveium-0.2.4 → waveium-0.4.0}/pyproject.toml +1 -1
  15. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/__init__.py +17 -1
  16. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/_http.py +108 -88
  17. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/exceptions.py +10 -0
  18. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/__init__.py +14 -4
  19. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/common.py +23 -11
  20. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/environments.py +26 -2
  21. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/executions.py +67 -0
  22. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/post_processing.py +18 -0
  23. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/scenes.py +10 -10
  24. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/shares.py +10 -3
  25. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/teams.py +17 -1
  26. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_file_ops.py +2 -2
  27. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_share_ops.py +5 -13
  28. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_upload_ops.py +19 -0
  29. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/antenna_patterns.py +38 -17
  30. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/auth.py +4 -13
  31. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/environments.py +129 -84
  32. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/executions.py +46 -31
  33. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/organizations.py +6 -15
  34. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/projects.py +36 -42
  35. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/scenes.py +181 -72
  36. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/teams.py +10 -27
  37. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/users.py +12 -29
  38. {waveium-0.2.4 → waveium-0.4.0}/tests/test_exceptions.py +5 -0
  39. waveium-0.4.0/tests/test_http_204.py +111 -0
  40. waveium-0.4.0/tests/test_http_errors.py +188 -0
  41. waveium-0.4.0/tests/test_idempotency.py +137 -0
  42. waveium-0.4.0/tests/test_typed_inputs.py +232 -0
  43. waveium-0.2.4/PKG-INFO +0 -430
  44. waveium-0.2.4/README.md +0 -392
  45. waveium-0.2.4/examples/openapi.json +0 -1
  46. {waveium-0.2.4 → waveium-0.4.0}/.python-version +0 -0
  47. {waveium-0.2.4 → waveium-0.4.0}/LICENSE +0 -0
  48. {waveium-0.2.4 → waveium-0.4.0}/examples/.env.example +0 -0
  49. {waveium-0.2.4 → waveium-0.4.0}/examples/async_streaming_usage.py +0 -0
  50. {waveium-0.2.4 → waveium-0.4.0}/examples/async_usage.py +0 -0
  51. {waveium-0.2.4 → waveium-0.4.0}/examples/streaming_usage.py +0 -0
  52. {waveium-0.2.4 → waveium-0.4.0}/examples/upload_download.py +0 -0
  53. {waveium-0.2.4 → waveium-0.4.0}/examples/verify_sdk.py +0 -0
  54. {waveium-0.2.4 → waveium-0.4.0}/setup.cfg +0 -0
  55. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/_config.py +0 -0
  56. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/_sse.py +0 -0
  57. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/async_client.py +0 -0
  58. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/client.py +0 -0
  59. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/antenna_patterns.py +0 -0
  60. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/auth.py +0 -0
  61. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/events.py +0 -0
  62. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/files.py +0 -0
  63. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/organizations.py +0 -0
  64. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/projects.py +0 -0
  65. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/roles.py +0 -0
  66. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/users.py +0 -0
  67. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/__init__.py +0 -0
  68. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_base.py +0 -0
  69. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_stream_ops.py +0 -0
  70. {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/roles.py +0 -0
  71. {waveium-0.2.4 → waveium-0.4.0}/tests/__init__.py +0 -0
  72. {waveium-0.2.4 → waveium-0.4.0}/tests/test_events.py +0 -0
  73. {waveium-0.2.4 → waveium-0.4.0}/tests/test_parse_stations.py +0 -0
  74. {waveium-0.2.4 → waveium-0.4.0}/tests/test_repr.py +0 -0
  75. {waveium-0.2.4 → waveium-0.4.0}/tests/test_sse_connection.py +0 -0
  76. {waveium-0.2.4 → waveium-0.4.0}/tests/test_sse_parser.py +0 -0
@@ -3,6 +3,9 @@
3
3
  .DS_Store
4
4
  Thumbs.db
5
5
 
6
+ # Claude Code local settings (absolute paths, permissions, local state)
7
+ .claude/
8
+
6
9
 
7
10
  # ### Matlab
8
11
  # Windows default autosave extension
@@ -0,0 +1,109 @@
1
+ # Changelog
2
+
3
+ All notable changes to the Waveium Python SDK are documented here. The
4
+ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
5
+ and the project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.4.0] - 2026-07-08
8
+
9
+ Aligns the SDK with Waveium API contract `0.2.1`. Restores compatibility
10
+ for the endpoints that broke between contracts `0.1.0` and `0.2.1`. New
11
+ `0.2.1` capabilities (token scopes, `expand`, access-requests, etc.) are
12
+ not yet surfaced and are planned for a later release.
13
+
14
+ ### Breaking
15
+
16
+ - **`projects.update_member` and `projects.remove_member` now require a
17
+ `member_type` argument** (`"user"` or `"team"`), passed as a query
18
+ parameter. The API made `member_type` mandatory on both endpoints; calls
19
+ without it returned `422`. New signatures:
20
+ `update_member(project_id, member_type, member_id, role)` and
21
+ `remove_member(project_id, member_type, member_id)`.
22
+ - **`environments.get` replaced its `sync` parameter with `expand`.** The
23
+ API removed the `sync` query parameter (it was silently ignored) and added
24
+ `expand` for inlining related resources (e.g. `scenes`, `creator`). Callers
25
+ passing `sync=` must remove it.
26
+ - **`SceneParameters.trajectory_file_id` removed.** The contract dropped the
27
+ deprecated singular field; use `trajectory_file_ids` (list) instead. A
28
+ value set on the old field was silently discarded by the server.
29
+ - **`SceneResponse.reused` removed.** The contract no longer returns this
30
+ field for scenes (it was reporting `False` for every scene). Scene reuse is
31
+ now conveyed by the server via `similar_scenes` (not yet surfaced by the
32
+ SDK). `EnvironmentResponse.reused` is unaffected and remains.
33
+
34
+ ### Changed
35
+
36
+ - **`teams.list()` response items are now `TeamListItem`** instead of the
37
+ full `TeamResponse`. The list projection omits `organization_id` (and
38
+ `created_by_user_id`/`updated_at`); parsing full `TeamResponse` items raised
39
+ `ValidationError`. `teams.get()` still returns a full `TeamResponse`. New
40
+ `TeamListItem` model exported from `waveium.models`.
41
+ - **Shares-list responses no longer carry `limit`/`offset`.**
42
+ `PaginatedShareResponse` now matches the API's `ShareListResponse`
43
+ (`resource_type`, `resource_id`, `shares`, `total`); the previous required
44
+ `limit`/`offset` fields caused every `list_shares()` call to raise
45
+ `ValidationError`. The `limit`/`offset` arguments on `list_shares()` remain
46
+ for source compatibility but are ignored by the server.
47
+
48
+ ## [0.3.0] - 2026-04-29
49
+
50
+ Aligns the SDK with Waveium API contract `0.1.0`
51
+ (see `waveium-api/docs/api/changelog.md`).
52
+
53
+ ### Breaking
54
+
55
+ - **Delete methods return `None`.** Every `*.delete()`, `*.revoke_*()`,
56
+ and `*.remove_*()` method now returns `None` instead of a
57
+ `MessageResponse`. The API switched to `204 No Content` with empty
58
+ bodies, so the previous return type was no longer parseable. Code
59
+ that read `.message` off a delete result must be updated.
60
+ - **`scenes.find_similar` signature changed.** Was
61
+ `find_similar(environment_id, parameters: SceneParameters | dict)`.
62
+ Now `find_similar(environment_id, stations: Sequence[str] | None,
63
+ trajectory_file_ids: Sequence[str] | None)`. The server now matches
64
+ by station-name set and trajectory-file-id set within an
65
+ environment; coordinate-level matching has been removed. Names
66
+ containing commas raise `ValueError`.
67
+
68
+ ### Added
69
+
70
+ - `PreconditionFailedError` exception, raised on HTTP `412`. The
71
+ current ETag is exposed via `err.details["current_etag"]`.
72
+ - `ProblemDetails` model in `waveium.models` for RFC 7807 error
73
+ bodies (`application/problem+json`).
74
+ - Optional `idempotency_key` parameter on the seven endpoints the
75
+ API supports retry-safe replay for: `scenes.generate`,
76
+ `scenes.create_execution`, `scenes.complete_upload`,
77
+ `scenes.upload`, `environments.complete_upload`,
78
+ `environments.upload`, `executions.retry`,
79
+ `executions.submit_post_processing`,
80
+ `antenna_patterns.complete_upload`, `antenna_patterns.upload`
81
+ (sync and async).
82
+ - Optional `if_match` parameter on `scenes.update`, `scenes.patch`,
83
+ and `environments.update` for optimistic concurrency. The `_etag`
84
+ private attribute on `SceneResponse` and `EnvironmentResponse` is
85
+ populated from the `ETag` response header on `get`/`update`/`patch`.
86
+ - `Accept: application/json, application/problem+json` header on
87
+ every outgoing request.
88
+
89
+ ### Changed
90
+
91
+ - Error parsing now consumes RFC 7807 problem+json bodies.
92
+ `WaveiumError.code` reads the `code` extension; the rest of the
93
+ problem body (including `errors`, `invalid_fields`, `current_etag`)
94
+ flows through as `details`.
95
+ - HTTP `422` now maps to `ValidationError` (previously fell through
96
+ to `APIError`).
97
+ - `update_me`, `update`, and partial-update calls on users,
98
+ organizations, teams, projects, environments, and executions now
99
+ use `PATCH` instead of `PUT` (transparent to callers).
100
+ - Batch-download calls now use `GET` instead of `POST`
101
+ (transparent to callers): `_file_ops.download_batch`,
102
+ `executions.download_post_processing_batch`.
103
+ - `204 No Content` responses short-circuit to `None` regardless of
104
+ the requested response model.
105
+
106
+ ### Removed
107
+
108
+ - `ErrorDetail`, `ErrorResponse` from `waveium.models`. Replaced by
109
+ `ProblemDetails`. Direct importers must update.
waveium-0.4.0/PKG-INFO ADDED
@@ -0,0 +1,314 @@
1
+ Metadata-Version: 2.4
2
+ Name: waveium
3
+ Version: 0.4.0
4
+ Summary: Official Python SDK for the Waveium API
5
+ Project-URL: Homepage, https://waveium.io
6
+ Project-URL: Documentation, https://docs.waveium.io
7
+ Project-URL: Repository, https://github.com/waveium/waveium-sdk
8
+ Author: Waveium Team
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: api,environment,execution,rf-simulation,scene,sdk,waveium
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Requires-Dist: anyio>=4.0.0
24
+ Requires-Dist: httpx>=0.27.0
25
+ Requires-Dist: pydantic>=2.0.0
26
+ Requires-Dist: python-dotenv>=1.0.0
27
+ Requires-Dist: typing-extensions>=4.9.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: black>=24.0.0; extra == 'dev'
30
+ Requires-Dist: flake8>=7.0.0; extra == 'dev'
31
+ Requires-Dist: mypy>=1.8.0; extra == 'dev'
32
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
33
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
34
+ Provides-Extra: parquet
35
+ Requires-Dist: pandas>=2.0.0; extra == 'parquet'
36
+ Requires-Dist: pyarrow>=14.0.0; extra == 'parquet'
37
+ Description-Content-Type: text/markdown
38
+
39
+ # Waveium Python SDK
40
+
41
+ Official Python client library for the [Waveium API](https://waveium.io).
42
+ Build and run RF propagation simulations on top of a real environment
43
+ model, stream progress via Server-Sent Events, and read the parquet
44
+ results straight into a pandas DataFrame.
45
+
46
+ **New here?** Start with the step-by-step walkthrough at
47
+ [`docs/getting_started.md`](docs/getting_started.md).
48
+
49
+ ## Features
50
+
51
+ - Full coverage of the **Environment -> Scene -> Execution** workflow.
52
+ - Synchronous (`waveium.Client`) and asynchronous
53
+ (`waveium.AsyncClient`) clients.
54
+ - Built-in SSE progress streaming with blocking `wait()` helpers and
55
+ a `create_and_run()` convenience for the common case.
56
+ - `read_parquet()` streams result files straight into pandas without
57
+ writing to disk; `structured=True` merges xyz triplets and complex
58
+ re/im pairs for immediate analysis.
59
+ - Pydantic v2 models throughout for type safety.
60
+ - `.env` auto-loaded via `python-dotenv`.
61
+ - Typed exception hierarchy for precise error handling.
62
+
63
+ ## Installation
64
+
65
+ ```bash
66
+ pip install "waveium[parquet]"
67
+ ```
68
+
69
+ The `[parquet]` extra pulls in `pandas` and `pyarrow`, which
70
+ `client.executions.read_parquet()` requires. Drop it if you only need
71
+ to drive simulations without reading results in-process.
72
+
73
+ Python 3.9 or newer. Tested up to 3.13.
74
+
75
+ ## Quick start
76
+
77
+ ```python
78
+ import waveium
79
+
80
+ client = waveium.init() # reads WAVEIUM_API_KEY from env / .env
81
+
82
+ # 1. Create an environment (outdoor, route-based).
83
+ env_resp = client.environments.create(
84
+ name="Stockholm Route",
85
+ environment_type="outdoor",
86
+ route={
87
+ "start": "Stockholm Central Station",
88
+ "end": "Gamla Stan",
89
+ "area": "Stockholm, Sweden",
90
+ },
91
+ stations={"BS1": [59.3293, 18.0686, 25.0]},
92
+ )
93
+ env = client.environments.wait(
94
+ env_resp.environment.environment_id, timeout=900
95
+ )
96
+
97
+ # 2. Create and generate a scene.
98
+ scene = client.scenes.create(
99
+ environment_id=env.environment_id,
100
+ name="Default Scene",
101
+ parameters={
102
+ "stations": {"BS1": [59.3293, 18.0686, 25.0]},
103
+ "station_radio_config": {
104
+ "BS1": {"tx_power_dbm": 20.0, "noise_floor_dbm": -100.0}
105
+ },
106
+ },
107
+ )
108
+ client.scenes.generate(scene.scene_id)
109
+ client.scenes.wait(scene.scene_id, timeout=3600)
110
+
111
+ # 3. Run an execution and stream progress.
112
+ execution = client.scenes.create_and_run(
113
+ scene.scene_id,
114
+ execution_name="Quick Start",
115
+ timeout=3600,
116
+ )
117
+
118
+ # 4. Read the parquet results into a DataFrame.
119
+ df = client.executions.read_parquet(
120
+ execution.execution_id, structured=True
121
+ )
122
+ print(df.columns)
123
+ client.close()
124
+ ```
125
+
126
+ For a guided walkthrough -- including how to plot RSSI, SNR, and
127
+ channel gain -- see [`docs/getting_started.md`](docs/getting_started.md).
128
+
129
+ ## Authentication
130
+
131
+ The SDK reads the API key from the `WAVEIUM_API_KEY` (or
132
+ `WAVEIUM_SDK_TOKEN`) environment variable. Create keys at
133
+ <https://app.waveium.io>.
134
+
135
+ ### Using a `.env` file (recommended)
136
+
137
+ ```env
138
+ WAVEIUM_API_KEY=wvx_paste_your_key_here
139
+ ```
140
+
141
+ ```python
142
+ import waveium
143
+ client = waveium.init() # .env auto-loaded
144
+ ```
145
+
146
+ ### Passing the key explicitly
147
+
148
+ ```python
149
+ client = waveium.init(api_key="wvx_...")
150
+ ```
151
+
152
+ ### Firebase JWT exchange
153
+
154
+ If you already have a signed-in Firebase user, exchange the JWT for a
155
+ long-lived API key in one call:
156
+
157
+ ```python
158
+ client = waveium.auth(
159
+ firebase_token="eyJ...", # or set FIREBASE_TOKEN
160
+ token_name="My Python App",
161
+ expires_days=30,
162
+ )
163
+ ```
164
+
165
+ Under the hood this POSTs the Firebase JWT to `/auth/tokens` with
166
+ `Authorization: Bearer <jwt>` and returns a `Client` bound to the new
167
+ `wvx_` key.
168
+
169
+ ## Async usage
170
+
171
+ The `AsyncClient` mirrors the sync client one-for-one and works as an
172
+ async context manager:
173
+
174
+ ```python
175
+ import asyncio
176
+ import waveium
177
+
178
+
179
+ async def main() -> None:
180
+ async with waveium.AsyncClient() as client:
181
+ execution = await client.scenes.create_and_run(
182
+ "scn_xxx",
183
+ execution_name="Async run",
184
+ timeout=3600,
185
+ )
186
+ df = await client.executions.read_parquet(
187
+ execution.execution_id, structured=True
188
+ )
189
+ print(df.head())
190
+
191
+
192
+ asyncio.run(main())
193
+ ```
194
+
195
+ See `examples/async_streaming_usage.py` for the full set of async
196
+ streaming patterns.
197
+
198
+ ## Available resources
199
+
200
+ | Resource | Purpose |
201
+ |---|---|
202
+ | `client.environments` | Create / list / get / wait on environments. |
203
+ | `client.scenes` | Scene lifecycle, `generate()`, `wait()`, `create_execution()`, `create_and_run()`, file upload. |
204
+ | `client.executions` | Execution lifecycle, SSE streaming, `wait()`, `get()`, `progress()`, `read_parquet()`, `download_parquet()`, `download_archive()`, post-processing. |
205
+ | `client.antenna_patterns` | List, upload, and manage antenna patterns. |
206
+ | `client.auth` | Create, list, and revoke API tokens (Firebase exchange lives here). |
207
+ | `client.users` | `me()` and admin user management. |
208
+ | `client.organizations` | Organization admin. |
209
+ | `client.teams` | Team membership management. |
210
+ | `client.projects` | Project scoping for shared work. |
211
+ | `client.roles` | RBAC role and permission queries. |
212
+
213
+ ## Error handling
214
+
215
+ All SDK errors inherit from `waveium.WaveiumError`:
216
+
217
+ ```python
218
+ from waveium import (
219
+ WaveiumError, # Base class
220
+ AuthenticationError, # 401
221
+ AuthorizationError, # 403
222
+ NotFoundError, # 404
223
+ ConflictError, # 409 (e.g. scene not completed yet)
224
+ ValidationError, # 400
225
+ RateLimitError, # 429
226
+ APIError, # 5xx
227
+ NetworkError, # connection / DNS / timeout
228
+ FileUploadError,
229
+ FileDownloadError,
230
+ ConfigurationError,
231
+ StreamError,
232
+ StreamTimeoutError,
233
+ StreamReconnectError,
234
+ ExecutionFailedError, # terminal "failed" SSE event
235
+ )
236
+
237
+ try:
238
+ client.scenes.create_and_run(scene_id, timeout=3600)
239
+ except ExecutionFailedError as e:
240
+ print(f"Execution {e.execution_id} failed: {e.error_message}")
241
+ except ConflictError:
242
+ print("Scene is not in completed state yet")
243
+ except WaveiumError as e:
244
+ print(f"SDK error: {e}")
245
+ ```
246
+
247
+ ## Configuration
248
+
249
+ | Variable | Meaning | Default |
250
+ |---|---|---|
251
+ | `WAVEIUM_API_KEY` | API key (starts with `wvx_`) | -- |
252
+ | `WAVEIUM_SDK_TOKEN` | Alias for `WAVEIUM_API_KEY` | -- |
253
+ | `FIREBASE_TOKEN` | Firebase JWT for `waveium.auth()` | -- |
254
+ | `WAVEIUM_BASE_URL` | API base URL | `https://api.waveium.io` |
255
+ | `WAVEIUM_TIMEOUT` | HTTP timeout in seconds | `60` |
256
+ | `WAVEIUM_MAX_RETRIES` | Retry budget for idempotent requests | `3` |
257
+
258
+ Override programmatically:
259
+
260
+ ```python
261
+ client = waveium.init(
262
+ api_key="wvx_...",
263
+ base_url="https://api.waveium.io",
264
+ timeout=120.0,
265
+ max_retries=5,
266
+ )
267
+ ```
268
+
269
+ ## Examples
270
+
271
+ See the `examples/` directory for runnable scripts. Each reads
272
+ `WAVEIUM_API_KEY` from the environment (or a `.env` file).
273
+
274
+ | Script | What it shows |
275
+ |---|---|
276
+ | `basic_usage.py` | Full Env -> Scene -> Execution pipeline, synchronous. |
277
+ | `streaming_usage.py` | Four tiers of SSE streaming (raw stream, blocking wait, create-and-run, environment wait). |
278
+ | `async_usage.py` | Async environment creation. |
279
+ | `async_streaming_usage.py` | Async counterparts of the streaming tiers. |
280
+ | `load_and_plot.py` | Minimal results-only script: point it at an `execution_id` to plot channel power and CIR. |
281
+ | `helsinki_usage.py` | Full outdoor Helsinki route: run the sim, download the parquet, and plot a 2D PDP. |
282
+ | `helsinki_track_usage.py` | Outdoor bbox variant: generate/upload a GPX track with timestamps as the UE trajectory, run the sim, download the parquet, and plot a 2D PDP. |
283
+ | `upload_download.py` | Scene file upload and execution result download. |
284
+ | `underground_workflow.py` | Complex underground scenario driven by JSON tunnel graph and CSV stations/UE, using polling. |
285
+ | `underground_workflow_sse.py` | Same scenario using SSE streaming instead of polling. |
286
+ | `verify_sdk.py` | Quick smoke test: init, `users.me()`, list environments. |
287
+
288
+ ## Development
289
+
290
+ ```bash
291
+ cd python313
292
+ pip install -e ".[dev,parquet]"
293
+ ```
294
+
295
+ Code quality gates (match the project's `pyproject.toml` and
296
+ `setup.cfg`):
297
+
298
+ ```bash
299
+ black src/ examples/ tests/
300
+ flake8 src/ examples/ tests/
301
+ mypy --strict src/
302
+ pytest
303
+ ```
304
+
305
+ ## License
306
+
307
+ MIT -- see [`LICENSE`](LICENSE).
308
+
309
+ ## Support
310
+
311
+ - Dashboard: <https://app.waveium.io>
312
+ - Documentation: <https://docs.waveium.io>
313
+ - Issues: <https://github.com/waveium/waveium-sdk/issues>
314
+ - Email: <support@waveium.io>
@@ -0,0 +1,276 @@
1
+ # Waveium Python SDK
2
+
3
+ Official Python client library for the [Waveium API](https://waveium.io).
4
+ Build and run RF propagation simulations on top of a real environment
5
+ model, stream progress via Server-Sent Events, and read the parquet
6
+ results straight into a pandas DataFrame.
7
+
8
+ **New here?** Start with the step-by-step walkthrough at
9
+ [`docs/getting_started.md`](docs/getting_started.md).
10
+
11
+ ## Features
12
+
13
+ - Full coverage of the **Environment -> Scene -> Execution** workflow.
14
+ - Synchronous (`waveium.Client`) and asynchronous
15
+ (`waveium.AsyncClient`) clients.
16
+ - Built-in SSE progress streaming with blocking `wait()` helpers and
17
+ a `create_and_run()` convenience for the common case.
18
+ - `read_parquet()` streams result files straight into pandas without
19
+ writing to disk; `structured=True` merges xyz triplets and complex
20
+ re/im pairs for immediate analysis.
21
+ - Pydantic v2 models throughout for type safety.
22
+ - `.env` auto-loaded via `python-dotenv`.
23
+ - Typed exception hierarchy for precise error handling.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install "waveium[parquet]"
29
+ ```
30
+
31
+ The `[parquet]` extra pulls in `pandas` and `pyarrow`, which
32
+ `client.executions.read_parquet()` requires. Drop it if you only need
33
+ to drive simulations without reading results in-process.
34
+
35
+ Python 3.9 or newer. Tested up to 3.13.
36
+
37
+ ## Quick start
38
+
39
+ ```python
40
+ import waveium
41
+
42
+ client = waveium.init() # reads WAVEIUM_API_KEY from env / .env
43
+
44
+ # 1. Create an environment (outdoor, route-based).
45
+ env_resp = client.environments.create(
46
+ name="Stockholm Route",
47
+ environment_type="outdoor",
48
+ route={
49
+ "start": "Stockholm Central Station",
50
+ "end": "Gamla Stan",
51
+ "area": "Stockholm, Sweden",
52
+ },
53
+ stations={"BS1": [59.3293, 18.0686, 25.0]},
54
+ )
55
+ env = client.environments.wait(
56
+ env_resp.environment.environment_id, timeout=900
57
+ )
58
+
59
+ # 2. Create and generate a scene.
60
+ scene = client.scenes.create(
61
+ environment_id=env.environment_id,
62
+ name="Default Scene",
63
+ parameters={
64
+ "stations": {"BS1": [59.3293, 18.0686, 25.0]},
65
+ "station_radio_config": {
66
+ "BS1": {"tx_power_dbm": 20.0, "noise_floor_dbm": -100.0}
67
+ },
68
+ },
69
+ )
70
+ client.scenes.generate(scene.scene_id)
71
+ client.scenes.wait(scene.scene_id, timeout=3600)
72
+
73
+ # 3. Run an execution and stream progress.
74
+ execution = client.scenes.create_and_run(
75
+ scene.scene_id,
76
+ execution_name="Quick Start",
77
+ timeout=3600,
78
+ )
79
+
80
+ # 4. Read the parquet results into a DataFrame.
81
+ df = client.executions.read_parquet(
82
+ execution.execution_id, structured=True
83
+ )
84
+ print(df.columns)
85
+ client.close()
86
+ ```
87
+
88
+ For a guided walkthrough -- including how to plot RSSI, SNR, and
89
+ channel gain -- see [`docs/getting_started.md`](docs/getting_started.md).
90
+
91
+ ## Authentication
92
+
93
+ The SDK reads the API key from the `WAVEIUM_API_KEY` (or
94
+ `WAVEIUM_SDK_TOKEN`) environment variable. Create keys at
95
+ <https://app.waveium.io>.
96
+
97
+ ### Using a `.env` file (recommended)
98
+
99
+ ```env
100
+ WAVEIUM_API_KEY=wvx_paste_your_key_here
101
+ ```
102
+
103
+ ```python
104
+ import waveium
105
+ client = waveium.init() # .env auto-loaded
106
+ ```
107
+
108
+ ### Passing the key explicitly
109
+
110
+ ```python
111
+ client = waveium.init(api_key="wvx_...")
112
+ ```
113
+
114
+ ### Firebase JWT exchange
115
+
116
+ If you already have a signed-in Firebase user, exchange the JWT for a
117
+ long-lived API key in one call:
118
+
119
+ ```python
120
+ client = waveium.auth(
121
+ firebase_token="eyJ...", # or set FIREBASE_TOKEN
122
+ token_name="My Python App",
123
+ expires_days=30,
124
+ )
125
+ ```
126
+
127
+ Under the hood this POSTs the Firebase JWT to `/auth/tokens` with
128
+ `Authorization: Bearer <jwt>` and returns a `Client` bound to the new
129
+ `wvx_` key.
130
+
131
+ ## Async usage
132
+
133
+ The `AsyncClient` mirrors the sync client one-for-one and works as an
134
+ async context manager:
135
+
136
+ ```python
137
+ import asyncio
138
+ import waveium
139
+
140
+
141
+ async def main() -> None:
142
+ async with waveium.AsyncClient() as client:
143
+ execution = await client.scenes.create_and_run(
144
+ "scn_xxx",
145
+ execution_name="Async run",
146
+ timeout=3600,
147
+ )
148
+ df = await client.executions.read_parquet(
149
+ execution.execution_id, structured=True
150
+ )
151
+ print(df.head())
152
+
153
+
154
+ asyncio.run(main())
155
+ ```
156
+
157
+ See `examples/async_streaming_usage.py` for the full set of async
158
+ streaming patterns.
159
+
160
+ ## Available resources
161
+
162
+ | Resource | Purpose |
163
+ |---|---|
164
+ | `client.environments` | Create / list / get / wait on environments. |
165
+ | `client.scenes` | Scene lifecycle, `generate()`, `wait()`, `create_execution()`, `create_and_run()`, file upload. |
166
+ | `client.executions` | Execution lifecycle, SSE streaming, `wait()`, `get()`, `progress()`, `read_parquet()`, `download_parquet()`, `download_archive()`, post-processing. |
167
+ | `client.antenna_patterns` | List, upload, and manage antenna patterns. |
168
+ | `client.auth` | Create, list, and revoke API tokens (Firebase exchange lives here). |
169
+ | `client.users` | `me()` and admin user management. |
170
+ | `client.organizations` | Organization admin. |
171
+ | `client.teams` | Team membership management. |
172
+ | `client.projects` | Project scoping for shared work. |
173
+ | `client.roles` | RBAC role and permission queries. |
174
+
175
+ ## Error handling
176
+
177
+ All SDK errors inherit from `waveium.WaveiumError`:
178
+
179
+ ```python
180
+ from waveium import (
181
+ WaveiumError, # Base class
182
+ AuthenticationError, # 401
183
+ AuthorizationError, # 403
184
+ NotFoundError, # 404
185
+ ConflictError, # 409 (e.g. scene not completed yet)
186
+ ValidationError, # 400
187
+ RateLimitError, # 429
188
+ APIError, # 5xx
189
+ NetworkError, # connection / DNS / timeout
190
+ FileUploadError,
191
+ FileDownloadError,
192
+ ConfigurationError,
193
+ StreamError,
194
+ StreamTimeoutError,
195
+ StreamReconnectError,
196
+ ExecutionFailedError, # terminal "failed" SSE event
197
+ )
198
+
199
+ try:
200
+ client.scenes.create_and_run(scene_id, timeout=3600)
201
+ except ExecutionFailedError as e:
202
+ print(f"Execution {e.execution_id} failed: {e.error_message}")
203
+ except ConflictError:
204
+ print("Scene is not in completed state yet")
205
+ except WaveiumError as e:
206
+ print(f"SDK error: {e}")
207
+ ```
208
+
209
+ ## Configuration
210
+
211
+ | Variable | Meaning | Default |
212
+ |---|---|---|
213
+ | `WAVEIUM_API_KEY` | API key (starts with `wvx_`) | -- |
214
+ | `WAVEIUM_SDK_TOKEN` | Alias for `WAVEIUM_API_KEY` | -- |
215
+ | `FIREBASE_TOKEN` | Firebase JWT for `waveium.auth()` | -- |
216
+ | `WAVEIUM_BASE_URL` | API base URL | `https://api.waveium.io` |
217
+ | `WAVEIUM_TIMEOUT` | HTTP timeout in seconds | `60` |
218
+ | `WAVEIUM_MAX_RETRIES` | Retry budget for idempotent requests | `3` |
219
+
220
+ Override programmatically:
221
+
222
+ ```python
223
+ client = waveium.init(
224
+ api_key="wvx_...",
225
+ base_url="https://api.waveium.io",
226
+ timeout=120.0,
227
+ max_retries=5,
228
+ )
229
+ ```
230
+
231
+ ## Examples
232
+
233
+ See the `examples/` directory for runnable scripts. Each reads
234
+ `WAVEIUM_API_KEY` from the environment (or a `.env` file).
235
+
236
+ | Script | What it shows |
237
+ |---|---|
238
+ | `basic_usage.py` | Full Env -> Scene -> Execution pipeline, synchronous. |
239
+ | `streaming_usage.py` | Four tiers of SSE streaming (raw stream, blocking wait, create-and-run, environment wait). |
240
+ | `async_usage.py` | Async environment creation. |
241
+ | `async_streaming_usage.py` | Async counterparts of the streaming tiers. |
242
+ | `load_and_plot.py` | Minimal results-only script: point it at an `execution_id` to plot channel power and CIR. |
243
+ | `helsinki_usage.py` | Full outdoor Helsinki route: run the sim, download the parquet, and plot a 2D PDP. |
244
+ | `helsinki_track_usage.py` | Outdoor bbox variant: generate/upload a GPX track with timestamps as the UE trajectory, run the sim, download the parquet, and plot a 2D PDP. |
245
+ | `upload_download.py` | Scene file upload and execution result download. |
246
+ | `underground_workflow.py` | Complex underground scenario driven by JSON tunnel graph and CSV stations/UE, using polling. |
247
+ | `underground_workflow_sse.py` | Same scenario using SSE streaming instead of polling. |
248
+ | `verify_sdk.py` | Quick smoke test: init, `users.me()`, list environments. |
249
+
250
+ ## Development
251
+
252
+ ```bash
253
+ cd python313
254
+ pip install -e ".[dev,parquet]"
255
+ ```
256
+
257
+ Code quality gates (match the project's `pyproject.toml` and
258
+ `setup.cfg`):
259
+
260
+ ```bash
261
+ black src/ examples/ tests/
262
+ flake8 src/ examples/ tests/
263
+ mypy --strict src/
264
+ pytest
265
+ ```
266
+
267
+ ## License
268
+
269
+ MIT -- see [`LICENSE`](LICENSE).
270
+
271
+ ## Support
272
+
273
+ - Dashboard: <https://app.waveium.io>
274
+ - Documentation: <https://docs.waveium.io>
275
+ - Issues: <https://github.com/waveium/waveium-sdk/issues>
276
+ - Email: <support@waveium.io>