diff --git a/.github/workflows/smoke-install.yml b/.github/workflows/smoke-install.yml new file mode 100644 index 0000000..d4a3d05 --- /dev/null +++ b/.github/workflows/smoke-install.yml @@ -0,0 +1,78 @@ +name: smoke install + +# Regression guard for the fresh-install class of bug: builds the wheel from +# this checkout, installs it (no `-e`, no repo on sys.path) into a throwaway +# venv, and proves the installed package imports and reaches the live WAVE +# gateway with the README's own quickstart. Running `pytest` from the repo +# checkout does NOT catch this class of bug (the checkout dir is first on +# sys.path and hides an import collision that only appears once the package +# is actually installed and run from elsewhere) — this workflow is the one +# gate that runs it the way a real `pip install wave-sdk` user does. +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: smoke-install-${{ github.ref }} + cancel-in-progress: true + +jobs: + smoke: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.12", "3.13"] + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: ${{ matrix.python-version }} + + - name: Build wheel + run: | + python -m pip install --upgrade pip + pip install build + python -m build --wheel + + - name: Create fresh venv (no repo on sys.path) + run: python -m venv "$RUNNER_TEMP/smoke" + + - name: Install the built wheel + run: | + WHEEL=$(ls dist/*.whl) + "$RUNNER_TEMP/smoke/bin/pip" install --upgrade pip + "$RUNNER_TEMP/smoke/bin/pip" install "$WHEEL" + + - name: Import check (installed wheel, run away from the repo) + working-directory: ${{ runner.temp }}/smoke + run: | + bin/python -c " + import wave_sdk + print('wave_sdk', wave_sdk.__version__, 'imported from', wave_sdk.__file__) + from wave_sdk import Wave + print('Wave facade OK,', len([n for n in dir(wave_sdk) if n.endswith('API')]), 'API classes') + " + + - name: Copy quickstart into the smoke venv's working directory + run: cp scripts/smoke_quickstart.py "$RUNNER_TEMP/smoke/smoke_quickstart.py" + + - name: README quickstart (live gateway, real credentials) + working-directory: ${{ runner.temp }}/smoke + env: + WAVE_GATEWAY_API_KEY: ${{ secrets.WAVE_GATEWAY_API_KEY }} + run: | + if [ -z "$WAVE_GATEWAY_API_KEY" ]; then + echo "skipped: WAVE_GATEWAY_API_KEY absent (fork or unset)" + exit 0 + fi + bin/python smoke_quickstart.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 211234c..bfc016a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,26 @@ All notable changes to this project are documented here. The format is based on ## [Unreleased] -## [2.1.0] - 2026-09-01 +## [2.1.0] - 2026-09-01 (not yet published to PyPI) + +### Fixed + +- **Critical**: the top-level installable package was named `wave`, which + collides with the Python standard library's own `wave` module (WAV audio + I/O, `Lib/wave.py`, present in every CPython install). Because the stdlib + is earlier on `sys.path` than `site-packages`, a fresh `pip install + wave-sdk` followed by the README's own `from wave import Wave` resolved + to the STDLIB module and raised `ImportError: cannot import name 'Wave' + from 'wave'` — on every supported Python version, in every environment + except the SDK's own repo checkout (where the checkout directory being + first on `sys.path` masked the collision during development and in the + test suite). Verified live against the published 2.0.0 wheel from PyPI in + two isolated interpreters (3.14, 3.12); see the accompanying PR's LIVE + RECEIPTS. The installable package is renamed `wave_sdk` (`pip install + wave-sdk` still works; `from wave_sdk import Wave` now actually resolves + to the SDK). This does not change the 2.0.0 contract on PyPI — 2.0.0 was + never fixable in place and 2.1.0 has not shipped yet, so this lands before + the collision reaches a published release. ### Added @@ -41,7 +60,7 @@ to 42, matching the TS facade 1:1. 2026-09-01) has a corresponding Python method, or is in a justified allowlist (new backend surfaces neither SDK wraps yet, or pre-existing studio-ai drift that predates this release). -- `tests/test_readme_quickstart.py` - asserts every `wave..` +- `tests/test_readme_quickstart.py` - asserts every `client..` call in the README's quickstart resolves to a real SDK method. - Updated `tests/test_sdk_exports.py` for the new API count (42 + client) and version (2.1.0). @@ -50,3 +69,13 @@ to 42, matching the TS facade 1:1. - Bumped to 2.1.0 (additive, semver-minor): no existing method signature changed. + +## [2.0.0] - 2026-04-03 + +Initial public release of the WAVE Python SDK on PyPI as `wave-sdk`: 35 `*API` +classes covering streaming, production, analytics, and content workflows +(verified against the published wheel's `wave/__init__.py`; the PyPI package +`Summary` metadata for this release says "33 API modules", which undercounts +by 2 — a pre-existing metadata typo baked into the immutable 2.0.0 upload, +noted here rather than fixed retroactively since PyPI release metadata for a +published version cannot be edited). diff --git a/LICENSE b/LICENSE index 6b79e20..3345e13 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2026 WAVE, Inc. + Copyright 2026 WAVE Online, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/NOTICE b/NOTICE index 018e56a..0bd6207 100644 --- a/NOTICE +++ b/NOTICE @@ -1,11 +1,11 @@ WAVE -Copyright 2026 WAVE, Inc. +Copyright 2026 WAVE Online, LLC -This product includes software developed at WAVE, Inc. +This product includes software developed at WAVE Online, LLC (https://wave.online). The names "WAVE" and "WAVE Surfer", the WAVE wordmark, and the WAVE logo -are trademarks of WAVE, Inc. and are NOT licensed under the Apache License, +are trademarks of WAVE Online, LLC and are NOT licensed under the Apache License, Version 2.0. The Apache License grants rights to the software in this repository only; it does not grant permission to use the WAVE marks except as required for reasonable and customary use in describing the origin of the diff --git a/README.md b/README.md index e7611fe..b577f9f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # WAVE SDK for Python -Official Python SDK for the WAVE API by WAVE Inc. +Media infrastructure for the agentic internet. Official Python SDK for WAVE, by +WAVE Online, LLC. ## Installation @@ -11,32 +12,19 @@ pip install wave-sdk ## Quick start ```python -from wave import Wave - -wave = Wave(api_key="your-api-key", organization_id="org_123") - -# Create and start a live stream -stream = wave.pipeline.create(title="My Stream", protocol="webrtc") -wave.pipeline.start(stream.id) -health = wave.pipeline.get_health(stream.id) -print(f"Viewers: {health['viewer_count']}") - -# Create a virtual camera from NDI -device = wave.prism.create_device( - name="PTZ Camera 1", - type="camera", - source_protocol="ndi", - source_endpoint="NDI-CAM-1", - node_id="node_abc", - ptz_enabled=True, -) - -# Get analytics -viewers = wave.pulse.get_viewer_analytics(time_range="24h") - -# Send a transcript email (mail:write) and read the usage ledger (meter:read) -wave.mail.transcript_email(to="alice@example.com", transcript="...") -ledger = wave.meter.ledger(channel="mail") +from wave_sdk import Wave + +client = Wave(api_key="your-api-key", organization_id="org_123") + +# Search your organization's indexed media +results = client.search.search(query="product launch") + +# List your org's published pricing tiers (requires the pricing:read scope) +manifests = client.pricing.list_manifests() + +# Transcribe a recording and auto-generate captions for it +transcription = client.transcribe.create(source_url="https://example.com/clip.mp4") +captions = client.captions.generate(media_id=transcription.id, media_type="video") ``` ## All 42 APIs @@ -116,10 +104,10 @@ ledger = wave.meter.ledger(channel="mail") ## Error handling ```python -from wave import WaveError, RateLimitError +from wave_sdk import WaveError, RateLimitError try: - wave.pipeline.get("invalid-id") + client.clips.get("invalid-id") except RateLimitError as e: print(f"Rate limited. Retry after {e.retry_after}s") except WaveError as e: @@ -134,4 +122,4 @@ except WaveError as e: ## License -MIT - WAVE Inc. +MIT - WAVE Online, LLC diff --git a/pyproject.toml b/pyproject.toml index 87eda6d..eaa7500 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ readme = "README.md" license = {text = "MIT"} requires-python = ">=3.9" authors = [ - {name = "WAVE Inc.", email = "sdk@wave.online"} + {name = "WAVE Online, LLC", email = "sdk@wave.online"} ] keywords = [ "wave", @@ -44,6 +44,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Multimedia :: Video", "Topic :: Multimedia :: Sound/Audio", "Topic :: Software Development :: Libraries :: Python Modules", @@ -52,6 +53,11 @@ classifiers = [ dependencies = [ "httpx>=0.25.0", "pydantic>=2.0.0", + # pydantic 2.x needs this to resolve `str | None`-style PEP 604 unions on + # Python 3.9, where `str | None` cannot be `eval()`'d natively (the SDK's + # models use `from __future__ import annotations` + the new union syntax + # for readability; requires-python allows 3.9, so this isn't optional). + "eval-type-backport>=0.2.0; python_version < '3.10'", ] [project.optional-dependencies] @@ -78,7 +84,7 @@ Issues = "https://github.com/wave-av/sdk-python/issues" [tool.setuptools.packages.find] where = ["."] -include = ["wave*"] +include = ["wave_sdk*"] [tool.mypy] python_version = "3.9" @@ -99,7 +105,7 @@ ignore = ["E501", "E701", "E702"] [tool.ruff.lint.per-file-ignores] # Public re-exports: the package __init__ exists to re-export the API surface. -"wave/__init__.py" = ["F401"] +"wave_sdk/__init__.py" = ["F401"] # Export-verification test imports every public symbol to assert it exists # (checked via __all__ / hasattr), so the names are intentionally "unused". "tests/test_sdk_exports.py" = ["F401"] diff --git a/scripts/smoke_quickstart.py b/scripts/smoke_quickstart.py new file mode 100644 index 0000000..faacae7 --- /dev/null +++ b/scripts/smoke_quickstart.py @@ -0,0 +1,74 @@ +"""CI fresh-install smoke: proves the INSTALLED WHEEL imports and reaches the +live WAVE gateway (https://api.wave.online), using the first two calls from +README.md's quickstart (search + pricing) — the ones that resolve to routes +confirmed live in the public OpenAPI spec and that respond deterministically +(200, or an auth/scope error) without depending on a real, fetchable media +URL. Never mocked: this is a real HTTP round trip against production. + +Exit 0 when the SDK reaches the gateway, whether or not the call is fully +authorized (a 402 Payment Required or a 403 SCOPE_INSUFFICIENT both prove the +request landed on a real, authenticating route). Exit 1 on anything that +indicates the *installed package itself* is broken (ImportError, or any +response that is not a recognized "reached the gateway" shape). + +Invoked by .github/workflows/smoke-install.yml against a wheel built from +this checkout, installed into a throwaway venv with no repo source on +sys.path — the class of bug this guards against (the SDK's own top-level +package shadowing Python's stdlib `wave` module) is invisible to `pytest` +run from the repo checkout, because the checkout directory being first on +sys.path masks the collision. Only an install-from-wheel-elsewhere run like +this one, or a real end user's environment, sees it. +""" +from __future__ import annotations + +import os +import sys + + +def main() -> int: + api_key = os.environ.get("WAVE_GATEWAY_API_KEY") + if not api_key: + print("skipped: WAVE_GATEWAY_API_KEY absent (fork or unset)") + return 0 + + # Import happens after the env-var short-circuit so a fork PR (no secret) + # still exercises the import path, which is the cheapest and most common + # way this class of bug shows up. + from wave_sdk import Wave, WaveError + + client = Wave(api_key=api_key, organization_id="org_123") + + reached_gateway = False + + try: + results = client.search.search(query="product launch") + print(f"OK: search.search() -> {len(results.get('results', []))} results") + reached_gateway = True + except WaveError as e: + if e.status_code in (402, 403): + print(f"OK (reached gateway, gated): search.search() -> {e.status_code} {e.code}") + reached_gateway = True + else: + print(f"FAIL: search.search() -> {e.status_code} {e.code}: {e.message}", file=sys.stderr) + + try: + client.pricing.list_manifests() + print("OK: pricing.list_manifests() -> 200") + reached_gateway = True + except WaveError as e: + if e.status_code in (402, 403): + print(f"OK (reached gateway, gated): pricing.list_manifests() -> {e.status_code} {e.code}") + reached_gateway = True + else: + print(f"FAIL: pricing.list_manifests() -> {e.status_code} {e.code}: {e.message}", file=sys.stderr) + + if not reached_gateway: + print("FAIL: neither quickstart call reached the gateway", file=sys.stderr) + return 1 + + print("QUICKSTART OK") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/conftest.py b/tests/conftest.py index 417a394..7c1e788 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,5 +9,5 @@ def api_key(): @pytest.fixture def wave_client(): - from wave import Wave + from wave_sdk import Wave return Wave(api_key="test-api-key", organization_id="org_test") diff --git a/tests/test_contract_coverage.py b/tests/test_contract_coverage.py index 1537d1c..2e793fb 100644 --- a/tests/test_contract_coverage.py +++ b/tests/test_contract_coverage.py @@ -145,7 +145,7 @@ def test_no_stale_mapping_or_allowlist_entries(): def test_mapped_methods_exist_on_wave(): - from wave import Wave + from wave_sdk import Wave w = Wave(api_key="test-key") missing = [] for op_id, (namespace, method) in MAPPING.items(): diff --git a/tests/test_parity_apis.py b/tests/test_parity_apis.py index 4434d99..e7f64b4 100644 --- a/tests/test_parity_apis.py +++ b/tests/test_parity_apis.py @@ -5,16 +5,17 @@ from __future__ import annotations from unittest.mock import MagicMock -from wave.inference import InferenceAPI -from wave.mail import MailAPI -from wave.meter import MeterAPI -from wave.perception import PerceptionAPI -from wave.pricing import ManifestCreateResult, PricingAPI, PricingManifest, PricingTier -from wave.transcripts import TranscriptAPI import httpx import pytest +from wave_sdk.inference import InferenceAPI +from wave_sdk.mail import MailAPI +from wave_sdk.meter import MeterAPI +from wave_sdk.perception import PerceptionAPI +from wave_sdk.pricing import ManifestCreateResult, PricingAPI, PricingManifest, PricingTier +from wave_sdk.transcripts import TranscriptAPI + @pytest.fixture def mock_client(): @@ -223,7 +224,7 @@ def fake_post(url, headers=None, json=None, timeout=None): "usage": {"cost": 0.0001, "total_tokens": 12}, }) - monkeypatch.setattr("wave.inference.httpx.post", fake_post) + monkeypatch.setattr("wave_sdk.inference.httpx.post", fake_post) api = InferenceAPI(FakeClient()) result = api.complete("claude-haiku", [{"role": "user", "content": "hi"}]) assert result.model == "claude-haiku" @@ -238,9 +239,9 @@ class FakeClient: def fake_post(url, headers=None, json=None, timeout=None): return httpx.Response(500, text="funnel down") - monkeypatch.setattr("wave.inference.httpx.post", fake_post) + monkeypatch.setattr("wave_sdk.inference.httpx.post", fake_post) api = InferenceAPI(FakeClient()) - from wave.client import WaveError + from wave_sdk.client import WaveError with pytest.raises(WaveError): api.complete("claude-haiku", [{"role": "user", "content": "hi"}]) @@ -250,7 +251,7 @@ class FakeClient: api_key = "test-key" api = InferenceAPI(FakeClient()) - from wave.client import WaveError + from wave_sdk.client import WaveError with pytest.raises(WaveError, match="registry_url"): api.models() @@ -263,7 +264,7 @@ def fake_get(url, headers=None, timeout=None): assert url.startswith("https://registry.example.com/rest/v1/models") return httpx.Response(200, json=[{"id": "m1", "rail": "openai", "cost_input_per_m": 1.0, "cost_output_per_m": 2.0}]) - monkeypatch.setattr("wave.inference.httpx.get", fake_get) + monkeypatch.setattr("wave_sdk.inference.httpx.get", fake_get) api = InferenceAPI(FakeClient(), registry_url="https://registry.example.com", registry_key="k") models = api.models() assert models[0].id == "m1" diff --git a/tests/test_readme_quickstart.py b/tests/test_readme_quickstart.py index 08b0a42..24a46ab 100644 --- a/tests/test_readme_quickstart.py +++ b/tests/test_readme_quickstart.py @@ -1,4 +1,4 @@ -"""Verifies every `wave..(` call referenced in README.md's +"""Verifies every `client..(` call referenced in README.md's quickstart resolves to a real attribute on the Wave facade — the README is documentation the way the SDK actually behaves, not a wish list.""" from __future__ import annotations @@ -7,19 +7,19 @@ from pathlib import Path README = (Path(__file__).parent.parent / "README.md").read_text() -CALL_RE = re.compile(r"\bwave\.(\w+)\.(\w+)\(") +CALL_RE = re.compile(r"\bclient\.(\w+)\.(\w+)\(") def test_readme_quickstart_calls_are_real(): - from wave import Wave + from wave_sdk import Wave w = Wave(api_key="test-key") calls = sorted(set(CALL_RE.findall(README))) - assert calls, "expected at least one wave..(...) call in README.md" + assert calls, "expected at least one client..(...) call in README.md" missing = [] for namespace, method in calls: ns = getattr(w, namespace, None) if ns is None: - missing.append(f"wave.{namespace} (namespace does not exist)") + missing.append(f"client.{namespace} (namespace does not exist)") elif not hasattr(ns, method): - missing.append(f"wave.{namespace}.{method} (method does not exist)") + missing.append(f"client.{namespace}.{method} (method does not exist)") assert not missing, f"README references methods that don't exist: {missing}" diff --git a/tests/test_sdk_exports.py b/tests/test_sdk_exports.py index 8520b15..42290cc 100644 --- a/tests/test_sdk_exports.py +++ b/tests/test_sdk_exports.py @@ -1,7 +1,7 @@ """ SDK Export Verification Tests -Validates that all 39 SDK modules import correctly, all API classes +Validates that all 42 SDK modules import correctly, all API classes instantiate with WaveClient, and the Wave convenience class wires everything. """ @@ -9,8 +9,8 @@ def test_all_modules_import(): - """All 39 API modules should be importable from wave package.""" - from wave import ( + """All 42 API modules should be importable from the wave_sdk package.""" + from wave_sdk import ( AudienceAPI, CaptionsAPI, ChaptersAPI, @@ -62,7 +62,7 @@ def test_all_modules_import(): def test_wave_client_import(): """Core client classes should import.""" - from wave import RateLimitError, WaveClient, WaveError + from wave_sdk import RateLimitError, WaveClient, WaveError assert callable(WaveClient) assert issubclass(WaveError, Exception) assert issubclass(RateLimitError, WaveError) @@ -70,14 +70,14 @@ def test_wave_client_import(): def test_wave_client_requires_api_key(): """WaveClient should raise ValueError without api_key.""" - from wave import WaveClient + from wave_sdk import WaveClient with pytest.raises(ValueError, match="api_key"): WaveClient(api_key="") def test_wave_convenience_class(): """Wave class should instantiate with all 33 API modules.""" - from wave import Wave + from wave_sdk import Wave w = Wave(api_key="test-key") # Existing P3 @@ -136,7 +136,7 @@ def test_wave_convenience_class(): def test_api_count(): """Wave class should have exactly 42 API bindings (+ client) — parity with the TS SDK's 42 Wave-facade namespaces (43 including the base client).""" - from wave import Wave + from wave_sdk import Wave w = Wave(api_key="test-key") api_attrs = [a for a in dir(w) if not a.startswith('_') and a != 'client'] assert len(api_attrs) == 42, f"Expected 42 APIs, got {len(api_attrs)}: {api_attrs}" @@ -144,7 +144,7 @@ def test_api_count(): def test_pipeline_has_methods(): """PipelineAPI should have expected methods.""" - from wave import Wave + from wave_sdk import Wave w = Wave(api_key="test-key") for method in ['create', 'get', 'list', 'start', 'stop', 'get_health', 'wait_for_live']: assert hasattr(w.pipeline, method), f"PipelineAPI missing {method}" @@ -152,7 +152,7 @@ def test_pipeline_has_methods(): def test_prism_has_methods(): """PrismAPI should have expected methods.""" - from wave import Wave + from wave_sdk import Wave w = Wave(api_key="test-key") for method in ['create_device', 'start_device', 'stop_device', 'discover_sources', 'get_presets', 'set_preset', 'recall_preset']: assert hasattr(w.prism, method), f"PrismAPI missing {method}" @@ -160,7 +160,7 @@ def test_prism_has_methods(): def test_studio_has_methods(): """StudioAPI should have expected methods.""" - from wave import Wave + from wave_sdk import Wave w = Wave(api_key="test-key") for method in ['create', 'start', 'stop', 'add_source', 'activate_scene', 'transition', 'set_program', 'get_audio_mix']: assert hasattr(w.studio, method), f"StudioAPI missing {method}" @@ -168,13 +168,13 @@ def test_studio_has_methods(): def test_version(): """SDK version should be 2.1.0.""" - import wave - assert wave.__version__ == "2.1.0" + import wave_sdk + assert wave_sdk.__version__ == "2.1.0" def test_all_exports(): """__all__ should contain all API classes.""" - import wave + import wave_sdk expected = [ "ClipsAPI", "EditorAPI", "VoiceAPI", "PhoneAPI", "CollabAPI", "CaptionsAPI", "ChaptersAPI", "StudioAIAPI", "TranscribeAPI", @@ -188,4 +188,4 @@ def test_all_exports(): "TranscriptAPI", "MailAPI", "MeterAPI", "PricingAPI", "PerceptionAPI", "InferenceAPI", ] for cls in expected: - assert cls in wave.__all__, f"{cls} missing from __all__" + assert cls in wave_sdk.__all__, f"{cls} missing from __all__" diff --git a/tests/test_x402.py b/tests/test_x402.py index 03a9397..33cd990 100644 --- a/tests/test_x402.py +++ b/tests/test_x402.py @@ -1,4 +1,4 @@ -"""Conformance tests for wave.x402 (EIP-3009 "exact" scheme signing). +"""Conformance tests for wave_sdk.x402 (EIP-3009 "exact" scheme signing). These assert the Python signer is byte-for-byte compatible with WAVE's reference TypeScript x402 signer (the same viem stack the WAVE facilitator verifies against). The expected values live in @@ -14,7 +14,7 @@ pytest.importorskip("eth_account", reason='x402 signing needs the extra: pip install "wave-sdk[x402]"') -from wave.x402 import ( # noqa: E402 (after importorskip) +from wave_sdk.x402 import ( # noqa: E402 (after importorskip) encode_exact_payment_header, get_network_config, random_nonce, diff --git a/wave/__init__.py b/wave_sdk/__init__.py similarity index 66% rename from wave/__init__.py rename to wave_sdk/__init__.py index 13e8a2e..0d9dff4 100644 --- a/wave/__init__.py +++ b/wave_sdk/__init__.py @@ -1,75 +1,76 @@ """ WAVE SDK for Python -Official Python SDK for the WAVE API by WAVE Inc. +Official Python SDK for WAVE, media infrastructure for the agentic internet, by +WAVE Online, LLC. Example: - >>> from wave import Wave + >>> from wave_sdk import Wave >>> client = Wave(api_key="your-api-key") >>> streams = client.pipeline.list() >>> clips = client.clips.list() """ +from __future__ import annotations -from wave.audience import AudienceAPI -from wave.captions import CaptionsAPI -from wave.chapters import ChaptersAPI -from wave.client import RateLimitError, WaveClient, WaveError +from wave_sdk.audience import AudienceAPI +from wave_sdk.captions import CaptionsAPI +from wave_sdk.chapters import ChaptersAPI +from wave_sdk.client import RateLimitError, WaveClient, WaveError, __version__ # Existing P3 modules -from wave.clips import ClipsAPI -from wave.collab import CollabAPI -from wave.connect import ConnectAPI -from wave.creator import CreatorAPI -from wave.desktop import DesktopAPI -from wave.distribution import DistributionAPI -from wave.drm import DrmAPI -from wave.edge import EdgeAPI -from wave.editor import EditorAPI +from wave_sdk.clips import ClipsAPI +from wave_sdk.collab import CollabAPI +from wave_sdk.connect import ConnectAPI +from wave_sdk.creator import CreatorAPI +from wave_sdk.desktop import DesktopAPI +from wave_sdk.distribution import DistributionAPI +from wave_sdk.drm import DrmAPI +from wave_sdk.edge import EdgeAPI +from wave_sdk.editor import EditorAPI # P2 modules -from wave.fleet import FleetAPI -from wave.ghost import GhostAPI -from wave.inference import InferenceAPI -from wave.mail import MailAPI -from wave.marketplace import MarketplaceAPI -from wave.mesh import MeshAPI -from wave.meter import MeterAPI +from wave_sdk.fleet import FleetAPI +from wave_sdk.ghost import GhostAPI +from wave_sdk.inference import InferenceAPI +from wave_sdk.mail import MailAPI +from wave_sdk.marketplace import MarketplaceAPI +from wave_sdk.mesh import MeshAPI +from wave_sdk.meter import MeterAPI # Cross-cutting -from wave.notifications import NotificationsAPI -from wave.perception import PerceptionAPI -from wave.phone import PhoneAPI +from wave_sdk.notifications import NotificationsAPI +from wave_sdk.perception import PerceptionAPI +from wave_sdk.phone import PhoneAPI # P1 modules -from wave.pipeline import PipelineAPI +from wave_sdk.pipeline import PipelineAPI # P4 modules -from wave.podcast import PodcastAPI -from wave.pricing import PricingAPI -from wave.prism import PrismAPI -from wave.pulse import PulseAPI -from wave.qr import QrAPI -from wave.realtime import RealtimeAPI, RealtimeChannel -from wave.scene import SceneAPI -from wave.search import SearchAPI -from wave.sentiment import SentimentAPI -from wave.signage import SignageAPI -from wave.slides import SlidesAPI -from wave.studio import StudioAPI -from wave.studio_ai import StudioAIAPI -from wave.transcribe import TranscribeAPI -from wave.transcripts import TranscriptAPI -from wave.usb import UsbAPI +from wave_sdk.podcast import PodcastAPI +from wave_sdk.pricing import PricingAPI +from wave_sdk.prism import PrismAPI +from wave_sdk.pulse import PulseAPI +from wave_sdk.qr import QrAPI +from wave_sdk.realtime import RealtimeAPI, RealtimeChannel +from wave_sdk.scene import SceneAPI +from wave_sdk.search import SearchAPI +from wave_sdk.sentiment import SentimentAPI +from wave_sdk.signage import SignageAPI +from wave_sdk.slides import SlidesAPI +from wave_sdk.studio import StudioAPI +from wave_sdk.studio_ai import StudioAIAPI +from wave_sdk.transcribe import TranscribeAPI +from wave_sdk.transcripts import TranscriptAPI +from wave_sdk.usb import UsbAPI # P3 new modules -from wave.vault import VaultAPI -from wave.voice import VoiceAPI +from wave_sdk.vault import VaultAPI +from wave_sdk.voice import VoiceAPI # x402 agent payments (signing needs the optional [x402] extra; the import itself is dependency-free) -from wave.x402 import encode_exact_payment_header, sign_exact_authorization -from wave.zoom import ZoomAPI +from wave_sdk.x402 import encode_exact_payment_header, sign_exact_authorization +from wave_sdk.zoom import ZoomAPI -__version__ = "2.1.0" __all__ = [ "Wave", "WaveClient", @@ -105,11 +106,11 @@ class Wave: Full WAVE SDK client with all APIs attached. Example: - >>> from wave import Wave - >>> wave = Wave(api_key="your-api-key", organization_id="org_123") - >>> streams = wave.pipeline.list() - >>> clips = wave.clips.list() - >>> wave.prism.discover_sources() + >>> from wave_sdk import Wave + >>> client = Wave(api_key="your-api-key", organization_id="org_123") + >>> streams = client.pipeline.list() + >>> clips = client.clips.list() + >>> client.prism.discover_sources() """ def __init__( diff --git a/wave/agents.py b/wave_sdk/agents.py similarity index 100% rename from wave/agents.py rename to wave_sdk/agents.py diff --git a/wave/audience.py b/wave_sdk/audience.py similarity index 98% rename from wave/audience.py rename to wave_sdk/audience.py index 435bf3a..5db1d96 100644 --- a/wave/audience.py +++ b/wave_sdk/audience.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Poll(BaseModel): id: str; stream_id: str; question: str; options: list[dict]; status: str; total_votes: int = 0; created_at: str; updated_at: str diff --git a/wave/captions.py b/wave_sdk/captions.py similarity index 99% rename from wave/captions.py rename to wave_sdk/captions.py index f5b7f21..d5dd7f5 100644 --- a/wave/captions.py +++ b/wave_sdk/captions.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class CaptionTrack(BaseModel): id: str; asset_id: str; language: str; status: str; format: str; cue_count: int = 0; download_url: str | None = None; created_at: str; updated_at: str diff --git a/wave/chapters.py b/wave_sdk/chapters.py similarity index 99% rename from wave/chapters.py rename to wave_sdk/chapters.py index 427d7bc..7824a81 100644 --- a/wave/chapters.py +++ b/wave_sdk/chapters.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Chapter(BaseModel): id: str; title: str; start_time: float; end_time: float; thumbnail_url: str | None = None; description: str | None = None diff --git a/wave/client.py b/wave_sdk/client.py similarity index 96% rename from wave/client.py rename to wave_sdk/client.py index 6739708..7f02a27 100644 --- a/wave/client.py +++ b/wave_sdk/client.py @@ -13,7 +13,12 @@ import httpx from pydantic import BaseModel -logger = logging.getLogger("wave") +logger = logging.getLogger("wave_sdk") + +# Single source of truth for the SDK version, used both in the package's +# public __version__ (re-exported from wave_sdk/__init__.py) and here in the +# default User-Agent header, so the two can never drift. +__version__ = "2.1.0" T = TypeVar("T") @@ -126,7 +131,7 @@ def _build_headers(self) -> dict[str, str]: "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", "Accept": "application/json", - "User-Agent": "wave-sdk-python/1.0.0", + "User-Agent": f"wave-sdk-python/{__version__}", } if self.organization_id: headers["X-Organization-Id"] = self.organization_id diff --git a/wave/clips.py b/wave_sdk/clips.py similarity index 99% rename from wave/clips.py rename to wave_sdk/clips.py index e39a37d..6668f36 100644 --- a/wave/clips.py +++ b/wave_sdk/clips.py @@ -8,10 +8,11 @@ import time from typing import Any, Literal -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class ClipSource(BaseModel): """Clip source reference.""" diff --git a/wave/collab.py b/wave_sdk/collab.py similarity index 99% rename from wave/collab.py rename to wave_sdk/collab.py index f5d458c..38dc36f 100644 --- a/wave/collab.py +++ b/wave_sdk/collab.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class CollabRoom(BaseModel): id: str; organization_id: str; name: str; status: str; participant_count: int = 0; max_participants: int = 50; settings: dict | None = None; created_at: str; updated_at: str diff --git a/wave/connect.py b/wave_sdk/connect.py similarity index 98% rename from wave/connect.py rename to wave_sdk/connect.py index 1b1eed7..c90077a 100644 --- a/wave/connect.py +++ b/wave_sdk/connect.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Integration(BaseModel): id: str; organization_id: str; name: str; type: str; provider: str; status: str; scopes: list[str] | None = None; last_sync_at: str | None = None; error_message: str | None = None; created_at: str; updated_at: str diff --git a/wave/creator.py b/wave_sdk/creator.py similarity index 98% rename from wave/creator.py rename to wave_sdk/creator.py index fdb8ee0..d137be0 100644 --- a/wave/creator.py +++ b/wave_sdk/creator.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class CreatorProfile(BaseModel): id: str; user_id: str; display_name: str; subscriber_count: int = 0; total_revenue_cents: int = 0; verified: bool = False; tier: str = "starter"; created_at: str; updated_at: str diff --git a/wave/desktop.py b/wave_sdk/desktop.py similarity index 97% rename from wave/desktop.py rename to wave_sdk/desktop.py index 7b4b4e1..ad18923 100644 --- a/wave/desktop.py +++ b/wave_sdk/desktop.py @@ -2,7 +2,8 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient + +from wave_sdk.client import WaveClient class DesktopAPI: diff --git a/wave/distribution.py b/wave_sdk/distribution.py similarity index 98% rename from wave/distribution.py rename to wave_sdk/distribution.py index eba165f..ce12e75 100644 --- a/wave/distribution.py +++ b/wave_sdk/distribution.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Destination(BaseModel): id: str; organization_id: str; name: str; type: str; status: str; auto_start: bool = False; created_at: str; updated_at: str diff --git a/wave/drm.py b/wave_sdk/drm.py similarity index 98% rename from wave/drm.py rename to wave_sdk/drm.py index d167305..452370f 100644 --- a/wave/drm.py +++ b/wave_sdk/drm.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class DRMPolicy(BaseModel): id: str; organization_id: str; name: str; providers: list[str]; allow_offline: bool = False; max_devices: int = 1; output_protection: str = "none"; persistent_license: bool = False; created_at: str; updated_at: str diff --git a/wave/edge.py b/wave_sdk/edge.py similarity index 98% rename from wave/edge.py rename to wave_sdk/edge.py index fc9dbaf..61bcc3d 100644 --- a/wave/edge.py +++ b/wave_sdk/edge.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class EdgeNode(BaseModel): id: str; name: str; region: str; provider: str; status: str; latency_ms: int; capacity_percent: float; active_workers: int; bandwidth_mbps: float; created_at: str; updated_at: str diff --git a/wave/editor.py b/wave_sdk/editor.py similarity index 99% rename from wave/editor.py rename to wave_sdk/editor.py index 283a0d2..069a4c5 100644 --- a/wave/editor.py +++ b/wave_sdk/editor.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class EditorProject(BaseModel): id: str; organization_id: str; title: str; status: str; duration: float = 0; track_count: int = 0; created_at: str; updated_at: str diff --git a/wave/fleet.py b/wave_sdk/fleet.py similarity index 98% rename from wave/fleet.py rename to wave_sdk/fleet.py index 87ce5d7..eb29f1d 100644 --- a/wave/fleet.py +++ b/wave_sdk/fleet.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class FleetNode(BaseModel): id: str; organization_id: str; name: str; status: str; health: str; ip_address: str | None = None; version: str | None = None; os: str | None = None; cpu_usage: float = 0; memory_usage: float = 0; device_count: int = 0; last_seen_at: str | None = None; created_at: str; updated_at: str diff --git a/wave/ghost.py b/wave_sdk/ghost.py similarity index 97% rename from wave/ghost.py rename to wave_sdk/ghost.py index b836d38..c79dd57 100644 --- a/wave/ghost.py +++ b/wave_sdk/ghost.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class GhostSession(BaseModel): id: str; production_id: str; mode: str; style: str; status: str; confidence_threshold: float = 0.7; created_at: str | None = None diff --git a/wave/inference.py b/wave_sdk/inference.py similarity index 99% rename from wave/inference.py rename to wave_sdk/inference.py index 401d52f..ad2d576 100644 --- a/wave/inference.py +++ b/wave_sdk/inference.py @@ -13,11 +13,12 @@ from __future__ import annotations from typing import Any, Literal -from wave.client import WaveClient, WaveError import httpx from pydantic import BaseModel +from wave_sdk.client import WaveClient, WaveError + class InferenceMessage(BaseModel): role: Literal["system", "user", "assistant", "tool"] diff --git a/wave/mail.py b/wave_sdk/mail.py similarity index 98% rename from wave/mail.py rename to wave_sdk/mail.py index 0fabc95..62241c2 100644 --- a/wave/mail.py +++ b/wave_sdk/mail.py @@ -9,10 +9,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class SendResult(BaseModel): message_id: str | None = None diff --git a/wave/marketplace.py b/wave_sdk/marketplace.py similarity index 97% rename from wave/marketplace.py rename to wave_sdk/marketplace.py index 607cff1..8e6e78f 100644 --- a/wave/marketplace.py +++ b/wave_sdk/marketplace.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class MarketplaceItem(BaseModel): id: str; name: str; description: str; type: str; status: str; author_name: str; version: str; price_cents: int = 0; downloads: int = 0; rating: float = 0; tags: list[str] | None = None; category: str; created_at: str; updated_at: str diff --git a/wave/mesh.py b/wave_sdk/mesh.py similarity index 98% rename from wave/mesh.py rename to wave_sdk/mesh.py index dedb788..221a5a7 100644 --- a/wave/mesh.py +++ b/wave_sdk/mesh.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class MeshRegion(BaseModel): id: str; name: str; provider: str; location: str; status: str; latency_ms: int; capacity_percent: float; stream_count: int; viewer_count: int; is_primary: bool; created_at: str; updated_at: str diff --git a/wave/meter.py b/wave_sdk/meter.py similarity index 98% rename from wave/meter.py rename to wave_sdk/meter.py index a9b86fc..7f41cd9 100644 --- a/wave/meter.py +++ b/wave_sdk/meter.py @@ -7,10 +7,11 @@ from __future__ import annotations from typing import Literal -from wave.client import WaveClient from pydantic import BaseModel, ConfigDict, Field +from wave_sdk.client import WaveClient + class MeterMailChannel(BaseModel): ops: int; usdc: str; errors: int diff --git a/wave/notifications.py b/wave_sdk/notifications.py similarity index 97% rename from wave/notifications.py rename to wave_sdk/notifications.py index 50c7fb5..c118c9a 100644 --- a/wave/notifications.py +++ b/wave_sdk/notifications.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Notification(BaseModel): id: str; user_id: str; type: str; title: str; body: str; status: str; priority: str; channel: str; action_url: str | None = None; read_at: str | None = None; created_at: str; updated_at: str diff --git a/wave/perception.py b/wave_sdk/perception.py similarity index 99% rename from wave/perception.py rename to wave_sdk/perception.py index c8a91d0..805b7eb 100644 --- a/wave/perception.py +++ b/wave_sdk/perception.py @@ -19,10 +19,11 @@ from __future__ import annotations from typing import Any, Literal -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + PerceptionTransport = Literal["whep", "srt"] PerceptionSampleMode = Literal["adaptive", "fixed", "keyframe"] PerceptionAudioMode = Literal["transcribe", "raw", "off"] diff --git a/wave/phone.py b/wave_sdk/phone.py similarity index 99% rename from wave/phone.py rename to wave_sdk/phone.py index 01c1fd5..5c4f1f9 100644 --- a/wave/phone.py +++ b/wave_sdk/phone.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class PhoneNumber(BaseModel): id: str; number: str; type: str; capabilities: list[str] | None = None; status: str; region: str | None = None; created_at: str; updated_at: str diff --git a/wave/pipeline.py b/wave_sdk/pipeline.py similarity index 98% rename from wave/pipeline.py rename to wave_sdk/pipeline.py index e645794..040ecae 100644 --- a/wave/pipeline.py +++ b/wave_sdk/pipeline.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Stream(BaseModel): id: str; organization_id: str; title: str; description: str | None = None; status: str; protocol: str; ingest_url: str | None = None; playback_url: str | None = None; stream_key: str | None = None; resolution: str | None = None; frame_rate: int | None = None; bitrate_kbps: int | None = None; viewer_count: int = 0; recording_enabled: bool = False; tags: list[str] | None = None; metadata: dict[str, Any] | None = None; started_at: str | None = None; ended_at: str | None = None; created_at: str; updated_at: str diff --git a/wave/podcast.py b/wave_sdk/podcast.py similarity index 98% rename from wave/podcast.py rename to wave_sdk/podcast.py index 31a3b3e..9e25b56 100644 --- a/wave/podcast.py +++ b/wave_sdk/podcast.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Podcast(BaseModel): id: str; organization_id: str; title: str; description: str; category: str; language: str = "en"; episode_count: int = 0; subscriber_count: int = 0; rss_url: str | None = None; created_at: str; updated_at: str diff --git a/wave/pricing.py b/wave_sdk/pricing.py similarity index 98% rename from wave/pricing.py rename to wave_sdk/pricing.py index ab1b063..c16b196 100644 --- a/wave/pricing.py +++ b/wave_sdk/pricing.py @@ -9,10 +9,11 @@ from __future__ import annotations from typing import Literal -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class PricingTier(BaseModel): id: str diff --git a/wave/prism.py b/wave_sdk/prism.py similarity index 98% rename from wave/prism.py rename to wave_sdk/prism.py index 06f1690..d599c94 100644 --- a/wave/prism.py +++ b/wave_sdk/prism.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class VirtualDevice(BaseModel): id: str; organization_id: str; name: str; type: str; status: str; source_protocol: str; source_endpoint: str; node_id: str; resolution: dict | None = None; frame_rate: int | None = None; health_score: float = 100; ptz_enabled: bool = False; created_at: str; updated_at: str diff --git a/wave/pulse.py b/wave_sdk/pulse.py similarity index 98% rename from wave/pulse.py rename to wave_sdk/pulse.py index 4548582..34f538d 100644 --- a/wave/pulse.py +++ b/wave_sdk/pulse.py @@ -2,7 +2,8 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient + +from wave_sdk.client import WaveClient class PulseAPI: diff --git a/wave/py.typed b/wave_sdk/py.typed similarity index 100% rename from wave/py.typed rename to wave_sdk/py.typed diff --git a/wave/qr.py b/wave_sdk/qr.py similarity index 97% rename from wave/qr.py rename to wave_sdk/qr.py index 099ac40..ff41e2f 100644 --- a/wave/qr.py +++ b/wave_sdk/qr.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class QRCode(BaseModel): id: str; organization_id: str; type: str; content: str; short_url: str; image_url: str; scan_count: int = 0; status: str = "active"; expires_at: str | None = None; created_at: str; updated_at: str diff --git a/wave/realtime.py b/wave_sdk/realtime.py similarity index 99% rename from wave/realtime.py rename to wave_sdk/realtime.py index 3f3de2d..363b1ca 100644 --- a/wave/realtime.py +++ b/wave_sdk/realtime.py @@ -14,10 +14,11 @@ import json from collections.abc import Iterator from typing import Any, Callable -from wave.client import WaveClient import httpx +from wave_sdk.client import WaveClient + _DEFAULT_WS = "wss://realtime.wave.online" diff --git a/wave/scene.py b/wave_sdk/scene.py similarity index 99% rename from wave/scene.py rename to wave_sdk/scene.py index 0498ec7..682c867 100644 --- a/wave/scene.py +++ b/wave_sdk/scene.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Scene(BaseModel): id: str; start_time: float; end_time: float; scene_type: str; shot_type: str | None = None; confidence: float; labels: list[str] | None = None; thumbnail_url: str | None = None diff --git a/wave/search.py b/wave_sdk/search.py similarity index 98% rename from wave/search.py rename to wave_sdk/search.py index c8a54cd..fbb6f25 100644 --- a/wave/search.py +++ b/wave_sdk/search.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class SearchResult(BaseModel): id: str; type: str; title: str; score: float; highlights: list[dict] | None = None; thumbnail_url: str | None = None; created_at: str diff --git a/wave/sentiment.py b/wave_sdk/sentiment.py similarity index 98% rename from wave/sentiment.py rename to wave_sdk/sentiment.py index a8e21c7..6df80ab 100644 --- a/wave/sentiment.py +++ b/wave_sdk/sentiment.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class SentimentAnalysis(BaseModel): id: str; asset_id: str; status: str; overall_sentiment: str | None = None; overall_score: float = 0; segments_count: int = 0; created_at: str; updated_at: str diff --git a/wave/signage.py b/wave_sdk/signage.py similarity index 98% rename from wave/signage.py rename to wave_sdk/signage.py index 57c6ecb..127e1e6 100644 --- a/wave/signage.py +++ b/wave_sdk/signage.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Display(BaseModel): id: str; organization_id: str; name: str; status: str; resolution: str | None = None; orientation: str = "landscape"; location: str | None = None; current_playlist_id: str | None = None; last_seen_at: str | None = None; created_at: str; updated_at: str diff --git a/wave/slides.py b/wave_sdk/slides.py similarity index 97% rename from wave/slides.py rename to wave_sdk/slides.py index ec7bb6c..c941bdf 100644 --- a/wave/slides.py +++ b/wave_sdk/slides.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Conversion(BaseModel): id: str; organization_id: str; title: str; status: str; input_format: str; input_url: str; output_url: str | None = None; slide_count: int = 0; duration_seconds: float | None = None; progress_percent: int = 0; error: str | None = None; created_at: str; updated_at: str diff --git a/wave/studio.py b/wave_sdk/studio.py similarity index 99% rename from wave/studio.py rename to wave_sdk/studio.py index c271078..b1a2e97 100644 --- a/wave/studio.py +++ b/wave_sdk/studio.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Production(BaseModel): id: str; organization_id: str; title: str; description: str | None = None; status: str; program_source_id: str | None = None; preview_source_id: str | None = None; active_scene_id: str | None = None; recording_enabled: bool = False; streaming_enabled: bool = False; viewer_count: int = 0; started_at: str | None = None; ended_at: str | None = None; created_at: str; updated_at: str diff --git a/wave/studio_ai.py b/wave_sdk/studio_ai.py similarity index 99% rename from wave/studio_ai.py rename to wave_sdk/studio_ai.py index ab9188f..47ba745 100644 --- a/wave/studio_ai.py +++ b/wave_sdk/studio_ai.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class AIAssistant(BaseModel): id: str; production_id: str | None = None; stream_id: str | None = None; mode: str; status: str; config: dict | None = None; stats: dict | None = None; started_at: str | None = None; created_at: str; updated_at: str diff --git a/wave/transcribe.py b/wave_sdk/transcribe.py similarity index 99% rename from wave/transcribe.py rename to wave_sdk/transcribe.py index 2415fee..2995e23 100644 --- a/wave/transcribe.py +++ b/wave_sdk/transcribe.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Speaker(BaseModel): id: str; label: str; segments_count: int = 0; total_duration: float = 0 diff --git a/wave/transcripts.py b/wave_sdk/transcripts.py similarity index 97% rename from wave/transcripts.py rename to wave_sdk/transcripts.py index e1b618f..0030090 100644 --- a/wave/transcripts.py +++ b/wave_sdk/transcripts.py @@ -4,10 +4,11 @@ from __future__ import annotations from typing import Any, Literal -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class TranscriptMessage(BaseModel): role: Literal["system", "user", "assistant"] diff --git a/wave/usb.py b/wave_sdk/usb.py similarity index 97% rename from wave/usb.py rename to wave_sdk/usb.py index 4510fb7..d196e1b 100644 --- a/wave/usb.py +++ b/wave_sdk/usb.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class USBDevice(BaseModel): id: str; node_id: str; name: str; vendor_id: str; product_id: str; device_class: str; status: str; manufacturer: str | None = None; speed: str; capabilities: list[str] | None = None; connected_at: str; updated_at: str diff --git a/wave/vault.py b/wave_sdk/vault.py similarity index 98% rename from wave/vault.py rename to wave_sdk/vault.py index 672301c..d4437af 100644 --- a/wave/vault.py +++ b/wave_sdk/vault.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Recording(BaseModel): id: str; organization_id: str; stream_id: str | None = None; title: str; status: str; duration_seconds: float = 0; file_size_bytes: int = 0; format: str | None = None; storage_tier: str = "hot"; playback_url: str | None = None; download_url: str | None = None; tags: list[str] | None = None; created_at: str; updated_at: str diff --git a/wave/voice.py b/wave_sdk/voice.py similarity index 99% rename from wave/voice.py rename to wave_sdk/voice.py index bc4ccd3..fa7d02d 100644 --- a/wave/voice.py +++ b/wave_sdk/voice.py @@ -3,10 +3,11 @@ import time from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class Voice(BaseModel): id: str; name: str; language: str; gender: str | None = None; model_type: str; preview_url: str | None = None; is_custom: bool = False; created_at: str; updated_at: str diff --git a/wave/x402.py b/wave_sdk/x402.py similarity index 100% rename from wave/x402.py rename to wave_sdk/x402.py diff --git a/wave/zoom.py b/wave_sdk/zoom.py similarity index 98% rename from wave/zoom.py rename to wave_sdk/zoom.py index b7d1d72..f306a2d 100644 --- a/wave/zoom.py +++ b/wave_sdk/zoom.py @@ -2,10 +2,11 @@ from __future__ import annotations from typing import Any -from wave.client import WaveClient from pydantic import BaseModel +from wave_sdk.client import WaveClient + class ZoomMeeting(BaseModel): id: str; topic: str; type: str; status: str; start_url: str; join_url: str; host_id: str; duration_minutes: int; participants_count: int = 0; recording_enabled: bool = False; rtms_enabled: bool = False; created_at: str