diff --git a/.github/workflows/python-lint.yml b/.github/workflows/python-lint.yml new file mode 100644 index 0000000..de4adb1 --- /dev/null +++ b/.github/workflows/python-lint.yml @@ -0,0 +1,32 @@ +name: python lint + +# Ruff lint gate for the published Python SDK. Enforces the E/F/I/N/W/UP/B/C4/SIM +# rule set (config lives in pyproject.toml [tool.ruff.lint]). The SDK's compressed +# one-liner house style (E701/E702) and public re-export imports are blessed there. +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +concurrency: + group: python-lint-${{ github.ref }} + cancel-in-progress: true + +jobs: + ruff: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + - name: Install ruff + run: pip install ruff + - name: Ruff check + run: ruff check diff --git a/pyproject.toml b/pyproject.toml index 3ec3091..28d5997 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,8 +86,20 @@ warn_unused_configs = true [tool.ruff] target-version = "py39" line-length = 100 + +[tool.ruff.lint] select = ["E", "F", "I", "N", "W", "UP", "B", "C4", "SIM"] -ignore = ["E501"] +# E501: line length is left to the formatter, not the linter. +# E701/E702: the SDK deliberately uses a compressed one-liner house style +# (multiple statements per line). Blessed by the maintainers. +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"] +# 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"] [tool.black] line-length = 100 diff --git a/tests/test_sdk_exports.py b/tests/test_sdk_exports.py index b9f3c87..b5197f6 100644 --- a/tests/test_sdk_exports.py +++ b/tests/test_sdk_exports.py @@ -11,14 +11,39 @@ def test_all_modules_import(): """All 33 API modules should be importable from wave package.""" from wave import ( - ClipsAPI, EditorAPI, VoiceAPI, PhoneAPI, CollabAPI, - CaptionsAPI, ChaptersAPI, StudioAIAPI, TranscribeAPI, - SentimentAPI, SearchAPI, SceneAPI, - PipelineAPI, StudioAPI, - FleetAPI, GhostAPI, MeshAPI, EdgeAPI, PulseAPI, PrismAPI, ZoomAPI, - VaultAPI, MarketplaceAPI, ConnectAPI, DistributionAPI, - DesktopAPI, SignageAPI, QrAPI, AudienceAPI, CreatorAPI, - PodcastAPI, SlidesAPI, UsbAPI, + AudienceAPI, + CaptionsAPI, + ChaptersAPI, + ClipsAPI, + CollabAPI, + ConnectAPI, + CreatorAPI, + DesktopAPI, + DistributionAPI, + EdgeAPI, + EditorAPI, + FleetAPI, + GhostAPI, + MarketplaceAPI, + MeshAPI, + PhoneAPI, + PipelineAPI, + PodcastAPI, + PrismAPI, + PulseAPI, + QrAPI, + SceneAPI, + SearchAPI, + SentimentAPI, + SignageAPI, + SlidesAPI, + StudioAIAPI, + StudioAPI, + TranscribeAPI, + UsbAPI, + VaultAPI, + VoiceAPI, + ZoomAPI, ) # All should be classes assert callable(ClipsAPI) @@ -29,7 +54,7 @@ def test_all_modules_import(): def test_wave_client_import(): """Core client classes should import.""" - from wave import WaveClient, WaveError, RateLimitError + from wave import RateLimitError, WaveClient, WaveError assert callable(WaveClient) assert issubclass(WaveError, Exception) assert issubclass(RateLimitError, WaveError) diff --git a/wave/__init__.py b/wave/__init__.py index ce9f482..6357172 100644 --- a/wave/__init__.py +++ b/wave/__init__.py @@ -10,55 +10,55 @@ >>> clips = client.clips.list() """ -from wave.client import WaveClient, WaveError, RateLimitError +from wave.audience import AudienceAPI +from wave.captions import CaptionsAPI +from wave.chapters import ChaptersAPI +from wave.client import RateLimitError, WaveClient, WaveError # Existing P3 modules from wave.clips import ClipsAPI -from wave.editor import EditorAPI -from wave.voice import VoiceAPI -from wave.phone import PhoneAPI from wave.collab import CollabAPI -from wave.captions import CaptionsAPI -from wave.chapters import ChaptersAPI -from wave.studio_ai import StudioAIAPI -from wave.transcribe import TranscribeAPI -from wave.sentiment import SentimentAPI -from wave.search import SearchAPI -from wave.scene import SceneAPI - -# P1 modules -from wave.pipeline import PipelineAPI -from wave.studio import StudioAPI +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 # P2 modules from wave.fleet import FleetAPI from wave.ghost import GhostAPI +from wave.marketplace import MarketplaceAPI from wave.mesh import MeshAPI -from wave.edge import EdgeAPI -from wave.pulse import PulseAPI -from wave.prism import PrismAPI -from wave.zoom import ZoomAPI -# P3 new modules -from wave.vault import VaultAPI -from wave.marketplace import MarketplaceAPI -from wave.connect import ConnectAPI -from wave.distribution import DistributionAPI -from wave.desktop import DesktopAPI -from wave.signage import SignageAPI -from wave.qr import QrAPI -from wave.audience import AudienceAPI -from wave.creator import CreatorAPI +# Cross-cutting +from wave.notifications import NotificationsAPI +from wave.phone import PhoneAPI + +# P1 modules +from wave.pipeline import PipelineAPI # P4 modules from wave.podcast import PodcastAPI +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.usb import UsbAPI -# Cross-cutting -from wave.notifications import NotificationsAPI -from wave.drm import DrmAPI -from wave.realtime import RealtimeAPI, RealtimeChannel +# P3 new modules +from wave.vault import VaultAPI +from wave.voice import VoiceAPI +from wave.zoom import ZoomAPI __version__ = "2.1.0" __all__ = [ diff --git a/wave/agents.py b/wave/agents.py index 1230675..9066458 100644 --- a/wave/agents.py +++ b/wave/agents.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing import Any, Callable, Optional +from typing import Any, Callable + import httpx @@ -64,9 +65,9 @@ def __init__( self, api_key: str, name: str = "stream-monitor", - stream_ids: Optional[list[str]] = None, + stream_ids: list[str] | None = None, auto_remediate: bool = False, - on_quality_drop: Optional[Callable[..., Any]] = None, + on_quality_drop: Callable[..., Any] | None = None, ) -> None: super().__init__(api_key=api_key, name=name, agent_type="stream_monitor") self.stream_ids = stream_ids or [] diff --git a/wave/audience.py b/wave/audience.py index 470725e..435bf3a 100644 --- a/wave/audience.py +++ b/wave/audience.py @@ -1,9 +1,12 @@ """WAVE SDK - Audience API. Polls, Q&A, reactions, and engagement.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/captions.py index 10f4bdf..f5b7f21 100644 --- a/wave/captions.py +++ b/wave/captions.py @@ -1,10 +1,13 @@ """WAVE SDK - Captions API. Auto-generate, translate, burn-in, and manage captions.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/chapters.py index 876ae35..427d7bc 100644 --- a/wave/chapters.py +++ b/wave/chapters.py @@ -1,10 +1,13 @@ """WAVE SDK - Chapters API. Auto-generate and manage video chapters.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/client.py index a2f2e9b..6739708 100644 --- a/wave/client.py +++ b/wave/client.py @@ -6,10 +6,9 @@ from __future__ import annotations -import time import logging -from typing import Any, TypeVar, Generic -from urllib.parse import urlencode +import time +from typing import Any, Generic, TypeVar import httpx from pydantic import BaseModel @@ -43,9 +42,7 @@ def _is_retryable(self, status_code: int, code: str) -> bool: return True if 500 <= status_code < 600: return True - if code in ("TIMEOUT", "NETWORK_ERROR", "SERVICE_UNAVAILABLE"): - return True - return False + return code in ("TIMEOUT", "NETWORK_ERROR", "SERVICE_UNAVAILABLE") def __str__(self) -> str: return f"WaveError({self.code}): {self.message}" @@ -302,7 +299,7 @@ def close(self) -> None: """Close the HTTP client.""" self._client.close() - def __enter__(self) -> "WaveClient": + def __enter__(self) -> WaveClient: return self def __exit__(self, *args: Any) -> None: diff --git a/wave/clips.py b/wave/clips.py index 66f014f..e39a37d 100644 --- a/wave/clips.py +++ b/wave/clips.py @@ -8,10 +8,10 @@ import time from typing import Any, Literal -from pydantic import BaseModel - from wave.client import WaveClient +from pydantic import BaseModel + class ClipSource(BaseModel): """Clip source reference.""" diff --git a/wave/collab.py b/wave/collab.py index 2ab2bf4..f5d458c 100644 --- a/wave/collab.py +++ b/wave/collab.py @@ -1,9 +1,12 @@ """WAVE SDK - Collab API. Real-time collaboration rooms, participants, comments, and annotations.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/connect.py index fa0c164..1b1eed7 100644 --- a/wave/connect.py +++ b/wave/connect.py @@ -1,9 +1,12 @@ """WAVE SDK - Connect API. Third-party integration and webhook management.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/creator.py index 948ab29..fdb8ee0 100644 --- a/wave/creator.py +++ b/wave/creator.py @@ -1,9 +1,12 @@ """WAVE SDK - Creator API. Monetization, subscriptions, tips, and payouts.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/desktop.py index 6d85fa0..7b4b4e1 100644 --- a/wave/desktop.py +++ b/wave/desktop.py @@ -1,8 +1,10 @@ """WAVE SDK - Desktop API. Desktop Node application management.""" from __future__ import annotations + from typing import Any from wave.client import WaveClient + class DesktopAPI: def __init__(self, client: WaveClient): self._client = client; self._base = "/v1/desktop" def get_info(self, node_id: str) -> dict: return self._client.get(f"{self._base}/nodes/{node_id}") diff --git a/wave/distribution.py b/wave/distribution.py index 50193d9..eba165f 100644 --- a/wave/distribution.py +++ b/wave/distribution.py @@ -1,9 +1,12 @@ """WAVE SDK - Distribution API. Social simulcasting and scheduled publishing.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/drm.py index 94534bf..d167305 100644 --- a/wave/drm.py +++ b/wave/drm.py @@ -1,9 +1,12 @@ """WAVE SDK - DRM API. Content protection with Widevine, FairPlay, and PlayReady.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/edge.py index e9dab44..fc9dbaf 100644 --- a/wave/edge.py +++ b/wave/edge.py @@ -1,9 +1,12 @@ """WAVE SDK - Edge API. Edge computing and CDN operations.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/editor.py index b31bb92..283a0d2 100644 --- a/wave/editor.py +++ b/wave/editor.py @@ -1,10 +1,13 @@ """WAVE SDK - Editor API. Video editing with tracks, transitions, effects, and rendering.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/fleet.py index ca7c85f..87ce5d7 100644 --- a/wave/fleet.py +++ b/wave/fleet.py @@ -1,10 +1,13 @@ """WAVE SDK - Fleet API. Desktop Node device fleet management.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/ghost.py index c2960f9..b836d38 100644 --- a/wave/ghost.py +++ b/wave/ghost.py @@ -1,9 +1,12 @@ """WAVE SDK - Autopilot API (formerly Ghost Producer). AI-powered autonomous production directing.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/marketplace.py b/wave/marketplace.py index 9ed724c..607cff1 100644 --- a/wave/marketplace.py +++ b/wave/marketplace.py @@ -1,9 +1,12 @@ """WAVE SDK - Marketplace API. Templates, plugins, and asset marketplace.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/mesh.py index ab447f7..dedb788 100644 --- a/wave/mesh.py +++ b/wave/mesh.py @@ -1,9 +1,12 @@ """WAVE SDK - Mesh API. Multi-region infrastructure failover.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/notifications.py b/wave/notifications.py index 6a2db29..50c7fb5 100644 --- a/wave/notifications.py +++ b/wave/notifications.py @@ -1,9 +1,12 @@ """WAVE SDK - Notifications API. User notifications, preferences, and delivery channels.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/phone.py b/wave/phone.py index 6f2d79e..01c1fd5 100644 --- a/wave/phone.py +++ b/wave/phone.py @@ -1,10 +1,13 @@ """WAVE SDK - Phone API. Voice calls, conferences, numbers, and recordings.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/pipeline.py index f3afcbd..e645794 100644 --- a/wave/pipeline.py +++ b/wave/pipeline.py @@ -1,10 +1,13 @@ """WAVE SDK - Pipeline API. Live stream management across protocols.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/podcast.py index 8577da2..31a3b3e 100644 --- a/wave/podcast.py +++ b/wave/podcast.py @@ -1,9 +1,12 @@ """WAVE SDK - Podcast API. Podcast production, episodes, and distribution.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/prism.py b/wave/prism.py index 6c4c42d..06f1690 100644 --- a/wave/prism.py +++ b/wave/prism.py @@ -1,9 +1,12 @@ """WAVE SDK - Prism API. Virtual Device Bridge for network AV to USB.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/pulse.py index 7562bb2..4548582 100644 --- a/wave/pulse.py +++ b/wave/pulse.py @@ -1,8 +1,10 @@ """WAVE SDK - Pulse Analytics API. Streaming analytics and BI.""" from __future__ import annotations + from typing import Any from wave.client import WaveClient + class PulseAPI: def __init__(self, client: WaveClient): self._client = client; self._base = "/v1/analytics" def get_stream_analytics(self, stream_id: str, **params: Any) -> dict: return self._client.get(f"{self._base}/streams/{stream_id}", params={k: v for k, v in params.items() if v is not None}) diff --git a/wave/qr.py b/wave/qr.py index e776e40..099ac40 100644 --- a/wave/qr.py +++ b/wave/qr.py @@ -1,9 +1,12 @@ """WAVE SDK - QR API. Dynamic QR code generation and analytics.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/realtime.py index bea8b92..3f3de2d 100644 --- a/wave/realtime.py +++ b/wave/realtime.py @@ -10,13 +10,14 @@ """ from __future__ import annotations +import contextlib import json -from typing import Any, Callable, Iterator +from collections.abc import Iterator +from typing import Any, Callable +from wave.client import WaveClient import httpx -from wave.client import WaveClient - _DEFAULT_WS = "wss://realtime.wave.online" @@ -63,7 +64,7 @@ def __iter__(self) -> Iterator[dict]: except Exception: return - def on(self, event: str, callback: Callable[[Any], None]) -> "RealtimeChannel": + def on(self, event: str, callback: Callable[[Any], None]) -> RealtimeChannel: """Register a handler. ``event`` is a frame type ('message','join','leave','presence') or a WAVE event name (e.g. 'caption.cue', 'sentiment.tick'). Returns self for chaining.""" self._handlers.setdefault(event, []).append(callback) @@ -89,10 +90,8 @@ def request_presence(self) -> None: self._ws.send(json.dumps({"op": "presence"})) def close(self) -> None: - try: + with contextlib.suppress(Exception): # pragma: no cover self._ws.close() - except Exception: # pragma: no cover - pass class RealtimeAPI: diff --git a/wave/scene.py b/wave/scene.py index 5d32a6c..0498ec7 100644 --- a/wave/scene.py +++ b/wave/scene.py @@ -1,10 +1,13 @@ """WAVE SDK - Scene API. AI scene detection, shot classification, and visual analysis.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/search.py index f453691..c8a54cd 100644 --- a/wave/search.py +++ b/wave/search.py @@ -1,10 +1,13 @@ """WAVE SDK - Search API. Full-text, visual, audio, and semantic search across content.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/sentiment.py index 0135642..a8e21c7 100644 --- a/wave/sentiment.py +++ b/wave/sentiment.py @@ -1,10 +1,13 @@ """WAVE SDK - Sentiment API. Audience sentiment, emotion, and topic analysis.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/signage.py index 0f6ffe6..57c6ecb 100644 --- a/wave/signage.py +++ b/wave/signage.py @@ -1,9 +1,12 @@ """WAVE SDK - Signage API. Digital signage display and playlist management.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/slides.py index 8b70486..ec7bb6c 100644 --- a/wave/slides.py +++ b/wave/slides.py @@ -1,10 +1,13 @@ """WAVE SDK - Slides API. Presentation to video conversion.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/studio.py index 0ba7964..c271078 100644 --- a/wave/studio.py +++ b/wave/studio.py @@ -1,9 +1,12 @@ """WAVE SDK - Studio API. Multi-camera broadcast production.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/studio_ai.py index 997bf4f..ab9188f 100644 --- a/wave/studio_ai.py +++ b/wave/studio_ai.py @@ -1,9 +1,12 @@ """WAVE SDK - Studio AI API. AI-powered production assistance, directing, and moderation.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/transcribe.py index f86a1ff..2415fee 100644 --- a/wave/transcribe.py +++ b/wave/transcribe.py @@ -1,10 +1,13 @@ """WAVE SDK - Transcribe API. Audio/video transcription with speaker diarization.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + class Speaker(BaseModel): id: str; label: str; segments_count: int = 0; total_duration: float = 0 diff --git a/wave/usb.py b/wave/usb.py index bbf6c25..4510fb7 100644 --- a/wave/usb.py +++ b/wave/usb.py @@ -1,9 +1,12 @@ """WAVE SDK - USB API. USB device relay and management.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/vault.py index 0af3416..672301c 100644 --- a/wave/vault.py +++ b/wave/vault.py @@ -1,9 +1,12 @@ """WAVE SDK - Vault API. Recording storage, VOD management, and archival.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/voice.py index 6986e5b..bc4ccd3 100644 --- a/wave/voice.py +++ b/wave/voice.py @@ -1,10 +1,13 @@ """WAVE SDK - Voice API. Text-to-speech synthesis and voice cloning.""" from __future__ import annotations + import time from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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/zoom.py b/wave/zoom.py index de128c2..b7d1d72 100644 --- a/wave/zoom.py +++ b/wave/zoom.py @@ -1,9 +1,12 @@ """WAVE SDK - Zoom API. Zoom meetings, rooms, recordings, and RTMS.""" from __future__ import annotations + from typing import Any -from pydantic import BaseModel from wave.client import WaveClient +from pydantic import BaseModel + + 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