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.
- {waveium-0.2.4 → waveium-0.4.0}/.gitignore +3 -0
- waveium-0.4.0/CHANGELOG.md +109 -0
- waveium-0.4.0/PKG-INFO +314 -0
- waveium-0.4.0/README.md +276 -0
- waveium-0.4.0/data/helsinki/track.gpx +38 -0
- waveium-0.4.0/docs/getting_started.md +381 -0
- {waveium-0.2.4 → waveium-0.4.0}/examples/basic_usage.py +12 -6
- waveium-0.4.0/examples/helsinki_track_usage.py +213 -0
- waveium-0.4.0/examples/helsinki_usage.py +139 -0
- waveium-0.4.0/examples/inspect_scene.py +58 -0
- {waveium-0.2.4 → waveium-0.4.0}/examples/load_and_plot.py +2 -2
- {waveium-0.2.4 → waveium-0.4.0}/examples/underground_workflow.py +4 -4
- {waveium-0.2.4 → waveium-0.4.0}/examples/underground_workflow_sse.py +3 -3
- {waveium-0.2.4 → waveium-0.4.0}/pyproject.toml +1 -1
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/__init__.py +17 -1
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/_http.py +108 -88
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/exceptions.py +10 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/__init__.py +14 -4
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/common.py +23 -11
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/environments.py +26 -2
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/executions.py +67 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/post_processing.py +18 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/scenes.py +10 -10
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/shares.py +10 -3
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/teams.py +17 -1
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_file_ops.py +2 -2
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_share_ops.py +5 -13
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_upload_ops.py +19 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/antenna_patterns.py +38 -17
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/auth.py +4 -13
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/environments.py +129 -84
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/executions.py +46 -31
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/organizations.py +6 -15
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/projects.py +36 -42
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/scenes.py +181 -72
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/teams.py +10 -27
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/users.py +12 -29
- {waveium-0.2.4 → waveium-0.4.0}/tests/test_exceptions.py +5 -0
- waveium-0.4.0/tests/test_http_204.py +111 -0
- waveium-0.4.0/tests/test_http_errors.py +188 -0
- waveium-0.4.0/tests/test_idempotency.py +137 -0
- waveium-0.4.0/tests/test_typed_inputs.py +232 -0
- waveium-0.2.4/PKG-INFO +0 -430
- waveium-0.2.4/README.md +0 -392
- waveium-0.2.4/examples/openapi.json +0 -1
- {waveium-0.2.4 → waveium-0.4.0}/.python-version +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/LICENSE +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/examples/.env.example +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/examples/async_streaming_usage.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/examples/async_usage.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/examples/streaming_usage.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/examples/upload_download.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/examples/verify_sdk.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/setup.cfg +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/_config.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/_sse.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/async_client.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/client.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/antenna_patterns.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/auth.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/events.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/files.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/organizations.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/projects.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/roles.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/models/users.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/__init__.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_base.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/_stream_ops.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/src/waveium/resources/roles.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/tests/__init__.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/tests/test_events.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/tests/test_parse_stations.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/tests/test_repr.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/tests/test_sse_connection.py +0 -0
- {waveium-0.2.4 → waveium-0.4.0}/tests/test_sse_parser.py +0 -0
|
@@ -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>
|
waveium-0.4.0/README.md
ADDED
|
@@ -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>
|