Add docstrings to live-runner and media SDK modules#48
Conversation
Document the public API with Google-style docstrings across live_runner, media_decode, media_output, media_publish, selection, and errors, covering Attributes, Args, Returns, and Raises for the exported classes and functions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughChangesLive runner platform
Estimated code review effort: 5 (Critical) | ~90 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
src/livepeer_gateway/scope.py (1)
82-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd the return type annotation for consistency. This helper returns
LiveVideoToVideo; annotating it keeps the type-from-annotations convention (the PR relies on annotations rather than docstring types) and helps static checkers.♻️ Proposed change
orch_url: Optional[Sequence[str] | str], timeout: float, -): +) -> LiveVideoToVideo:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/livepeer_gateway/scope.py` around lines 82 - 91, Add the return type annotation to the _start_scope_with_runner function, specifying that it returns LiveVideoToVideo, while preserving its existing parameters and behavior.src/livepeer_gateway/async_cache.py (1)
17-29: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAsync cache does not dedupe in-flight calls and cannot cache
None.Two robustness gaps for a decorator whose stated purpose is memoizing signer network calls:
- No in-flight dedupe: because
await func(...)yields, concurrent callers with the same key all miss and each perform the underlying network fetch (cache stampede). Caching the pending awaitable/task instead of the resolved value would coalesce concurrent callers.if cached is not Nonetreats a legitimately cachedNoneas a miss, soNoneresults are never memoized. A sentinel avoids this. Currently latent (get_signer_inforeturns a non-NoneSignerMaterial), but fragile if reused.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/livepeer_gateway/async_cache.py` around lines 17 - 29, Update the async cache wrapper to deduplicate concurrent calls by storing a shared pending task/awaitable for each key before yielding to func, then replace it with the resolved value while preserving the existing LRU eviction behavior. Use a distinct missing-value sentinel in place of cached is not None so legitimately cached None results are returned as hits. Anchor both changes in wrapper and ensure failures remove the pending entry rather than caching the exception.examples/echo/client.py (1)
78-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winUnguarded
/updatecall can abort the whole publish.If
post_json(Line 85) raises (e.g., transient network error), the exception propagates out of_publish_video, aborting the entire video publish rather than just skipping one blur update.🛡️ Proposed fix
): - await post_json(f"{app_url.rstrip('/')}/update", {"mode": "blur", "radius": blur_radius}) + try: + await post_json(f"{app_url.rstrip('/')}/update", {"mode": "blur", "radius": blur_radius}) + except Exception: + _log("blur update failed; continuing") if blur_radius == MAX_BLUR_RADIUS:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/echo/client.py` around lines 78 - 91, Update the blur-update loop in _publish_video around the post_json call so a transient exception from the /update request is caught and does not propagate to abort video publishing. Skip the failed blur update while preserving the existing blur radius and next_update_pts_time progression.examples/text/runner.py (1)
21-21: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winResolve
story.txtrelative to the script, not the CWD.Both handlers open
"story.txt"via a bare relative path, which only works if the process is launched withexamples/text/as the working directory. Other examples in this stack (e.g. echo's README) invoke scripts with full paths from the repo root, so this convention is fragile if a reader applies that pattern here.♻️ Proposed fix
+from pathlib import Path + +STORY_PATH = Path(__file__).parent / "story.txt" + async def _handle_sse(request: web.Request) -> web.StreamResponse: ... - with open("story.txt", encoding="utf-8", errors="replace") as lines: + with open(STORY_PATH, encoding="utf-8", errors="replace") as lines:(apply the same substitution at the
_handle_textopen call)Also applies to: 41-41
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/text/runner.py` at line 21, Update both story-file open calls in the handlers, including `_handle_text`, to build the path relative to `runner.py` rather than relying on the current working directory. Reuse the script’s directory path for both opens while preserving the existing encoding and error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/echo/README.md`:
- Line 32: Update the command in the README to invoke client.py with the
examples/echo/ path prefix, matching the earlier runner.py and client.py
commands while preserving all existing arguments and piping.
- Line 19: Update the documented runner command in README.md to use the plain
HTTP orchestrator URL, matching the go-livepeer startup configuration and runner
default; retain the existing host, port, and secret arguments.
In `@examples/echo/runner.py`:
- Around line 128-170: Protect the active-session check and initialization in
_handle_echo with a shared asyncio lock so concurrent requests cannot both pass
the state guard while create_trickle_channels yields. Acquire the lock before
checking state and hold it through creation of the channels, output, publisher,
and assignment to state; preserve the existing conflict response for a different
session_id and the existing response for the active session.
In `@examples/text/README.md`:
- Line 3: Correct the product name typo in the README description by replacing
“Livpeeer” with “Livepeer” while preserving the rest of the text.
In `@src/livepeer_gateway/remote_signer.py`:
- Around line 271-274: Update the header construction in the asynchronous
payment POST flow to normalize optional payment.seg_creds to an empty string
when it is None, matching PaymentSession.send_payment, while leaving the
Livepeer-Payment header unchanged.
---
Nitpick comments:
In `@examples/echo/client.py`:
- Around line 78-91: Update the blur-update loop in _publish_video around the
post_json call so a transient exception from the /update request is caught and
does not propagate to abort video publishing. Skip the failed blur update while
preserving the existing blur radius and next_update_pts_time progression.
In `@examples/text/runner.py`:
- Line 21: Update both story-file open calls in the handlers, including
`_handle_text`, to build the path relative to `runner.py` rather than relying on
the current working directory. Reuse the script’s directory path for both opens
while preserving the existing encoding and error handling.
In `@src/livepeer_gateway/async_cache.py`:
- Around line 17-29: Update the async cache wrapper to deduplicate concurrent
calls by storing a shared pending task/awaitable for each key before yielding to
func, then replace it with the resolved value while preserving the existing LRU
eviction behavior. Use a distinct missing-value sentinel in place of cached is
not None so legitimately cached None results are returned as hits. Anchor both
changes in wrapper and ensure failures remove the pending entry rather than
caching the exception.
In `@src/livepeer_gateway/scope.py`:
- Around line 82-91: Add the return type annotation to the
_start_scope_with_runner function, specifying that it returns LiveVideoToVideo,
while preserving its existing parameters and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5391aa42-6489-4368-b7a3-c45726c063e0
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (31)
examples/echo/README.mdexamples/echo/client.pyexamples/echo/runner.pyexamples/get_orchestrator_info.pyexamples/ping-pong/README.mdexamples/ping-pong/client.pyexamples/ping-pong/runner.pyexamples/text/README.mdexamples/text/go-livepeer.confexamples/text/runner.pyexamples/text/runners.jsonexamples/text/story.txtpyproject.tomlsrc/livepeer_gateway/__init__.pysrc/livepeer_gateway/async_cache.pysrc/livepeer_gateway/byoc.pysrc/livepeer_gateway/capabilities.pysrc/livepeer_gateway/channel_reader.pysrc/livepeer_gateway/discovery.pysrc/livepeer_gateway/errors.pysrc/livepeer_gateway/http.pysrc/livepeer_gateway/live_runner.pysrc/livepeer_gateway/lv2v.pysrc/livepeer_gateway/media_decode.pysrc/livepeer_gateway/media_output.pysrc/livepeer_gateway/media_publish.pysrc/livepeer_gateway/orch_info.pysrc/livepeer_gateway/orchestrator.pysrc/livepeer_gateway/remote_signer.pysrc/livepeer_gateway/scope.pysrc/livepeer_gateway/selection.py
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 5
🧹 Nitpick comments (4)
src/livepeer_gateway/scope.py (1)
82-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd the return type annotation for consistency. This helper returns
LiveVideoToVideo; annotating it keeps the type-from-annotations convention (the PR relies on annotations rather than docstring types) and helps static checkers.♻️ Proposed change
orch_url: Optional[Sequence[str] | str], timeout: float, -): +) -> LiveVideoToVideo:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/livepeer_gateway/scope.py` around lines 82 - 91, Add the return type annotation to the _start_scope_with_runner function, specifying that it returns LiveVideoToVideo, while preserving its existing parameters and behavior.src/livepeer_gateway/async_cache.py (1)
17-29: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winAsync cache does not dedupe in-flight calls and cannot cache
None.Two robustness gaps for a decorator whose stated purpose is memoizing signer network calls:
- No in-flight dedupe: because
await func(...)yields, concurrent callers with the same key all miss and each perform the underlying network fetch (cache stampede). Caching the pending awaitable/task instead of the resolved value would coalesce concurrent callers.if cached is not Nonetreats a legitimately cachedNoneas a miss, soNoneresults are never memoized. A sentinel avoids this. Currently latent (get_signer_inforeturns a non-NoneSignerMaterial), but fragile if reused.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/livepeer_gateway/async_cache.py` around lines 17 - 29, Update the async cache wrapper to deduplicate concurrent calls by storing a shared pending task/awaitable for each key before yielding to func, then replace it with the resolved value while preserving the existing LRU eviction behavior. Use a distinct missing-value sentinel in place of cached is not None so legitimately cached None results are returned as hits. Anchor both changes in wrapper and ensure failures remove the pending entry rather than caching the exception.examples/echo/client.py (1)
78-91: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winUnguarded
/updatecall can abort the whole publish.If
post_json(Line 85) raises (e.g., transient network error), the exception propagates out of_publish_video, aborting the entire video publish rather than just skipping one blur update.🛡️ Proposed fix
): - await post_json(f"{app_url.rstrip('/')}/update", {"mode": "blur", "radius": blur_radius}) + try: + await post_json(f"{app_url.rstrip('/')}/update", {"mode": "blur", "radius": blur_radius}) + except Exception: + _log("blur update failed; continuing") if blur_radius == MAX_BLUR_RADIUS:🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/echo/client.py` around lines 78 - 91, Update the blur-update loop in _publish_video around the post_json call so a transient exception from the /update request is caught and does not propagate to abort video publishing. Skip the failed blur update while preserving the existing blur radius and next_update_pts_time progression.examples/text/runner.py (1)
21-21: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winResolve
story.txtrelative to the script, not the CWD.Both handlers open
"story.txt"via a bare relative path, which only works if the process is launched withexamples/text/as the working directory. Other examples in this stack (e.g. echo's README) invoke scripts with full paths from the repo root, so this convention is fragile if a reader applies that pattern here.♻️ Proposed fix
+from pathlib import Path + +STORY_PATH = Path(__file__).parent / "story.txt" + async def _handle_sse(request: web.Request) -> web.StreamResponse: ... - with open("story.txt", encoding="utf-8", errors="replace") as lines: + with open(STORY_PATH, encoding="utf-8", errors="replace") as lines:(apply the same substitution at the
_handle_textopen call)Also applies to: 41-41
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/text/runner.py` at line 21, Update both story-file open calls in the handlers, including `_handle_text`, to build the path relative to `runner.py` rather than relying on the current working directory. Reuse the script’s directory path for both opens while preserving the existing encoding and error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/echo/README.md`:
- Line 32: Update the command in the README to invoke client.py with the
examples/echo/ path prefix, matching the earlier runner.py and client.py
commands while preserving all existing arguments and piping.
- Line 19: Update the documented runner command in README.md to use the plain
HTTP orchestrator URL, matching the go-livepeer startup configuration and runner
default; retain the existing host, port, and secret arguments.
In `@examples/echo/runner.py`:
- Around line 128-170: Protect the active-session check and initialization in
_handle_echo with a shared asyncio lock so concurrent requests cannot both pass
the state guard while create_trickle_channels yields. Acquire the lock before
checking state and hold it through creation of the channels, output, publisher,
and assignment to state; preserve the existing conflict response for a different
session_id and the existing response for the active session.
In `@examples/text/README.md`:
- Line 3: Correct the product name typo in the README description by replacing
“Livpeeer” with “Livepeer” while preserving the rest of the text.
In `@src/livepeer_gateway/remote_signer.py`:
- Around line 271-274: Update the header construction in the asynchronous
payment POST flow to normalize optional payment.seg_creds to an empty string
when it is None, matching PaymentSession.send_payment, while leaving the
Livepeer-Payment header unchanged.
---
Nitpick comments:
In `@examples/echo/client.py`:
- Around line 78-91: Update the blur-update loop in _publish_video around the
post_json call so a transient exception from the /update request is caught and
does not propagate to abort video publishing. Skip the failed blur update while
preserving the existing blur radius and next_update_pts_time progression.
In `@examples/text/runner.py`:
- Line 21: Update both story-file open calls in the handlers, including
`_handle_text`, to build the path relative to `runner.py` rather than relying on
the current working directory. Reuse the script’s directory path for both opens
while preserving the existing encoding and error handling.
In `@src/livepeer_gateway/async_cache.py`:
- Around line 17-29: Update the async cache wrapper to deduplicate concurrent
calls by storing a shared pending task/awaitable for each key before yielding to
func, then replace it with the resolved value while preserving the existing LRU
eviction behavior. Use a distinct missing-value sentinel in place of cached is
not None so legitimately cached None results are returned as hits. Anchor both
changes in wrapper and ensure failures remove the pending entry rather than
caching the exception.
In `@src/livepeer_gateway/scope.py`:
- Around line 82-91: Add the return type annotation to the
_start_scope_with_runner function, specifying that it returns LiveVideoToVideo,
while preserving its existing parameters and behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5391aa42-6489-4368-b7a3-c45726c063e0
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (31)
examples/echo/README.mdexamples/echo/client.pyexamples/echo/runner.pyexamples/get_orchestrator_info.pyexamples/ping-pong/README.mdexamples/ping-pong/client.pyexamples/ping-pong/runner.pyexamples/text/README.mdexamples/text/go-livepeer.confexamples/text/runner.pyexamples/text/runners.jsonexamples/text/story.txtpyproject.tomlsrc/livepeer_gateway/__init__.pysrc/livepeer_gateway/async_cache.pysrc/livepeer_gateway/byoc.pysrc/livepeer_gateway/capabilities.pysrc/livepeer_gateway/channel_reader.pysrc/livepeer_gateway/discovery.pysrc/livepeer_gateway/errors.pysrc/livepeer_gateway/http.pysrc/livepeer_gateway/live_runner.pysrc/livepeer_gateway/lv2v.pysrc/livepeer_gateway/media_decode.pysrc/livepeer_gateway/media_output.pysrc/livepeer_gateway/media_publish.pysrc/livepeer_gateway/orch_info.pysrc/livepeer_gateway/orchestrator.pysrc/livepeer_gateway/remote_signer.pysrc/livepeer_gateway/scope.pysrc/livepeer_gateway/selection.py
🛑 Comments failed to post (5)
examples/echo/README.md (2)
19-19: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Protocol mismatch: use
http, nothttps.Nothing in this demo configures TLS; go-livepeer is started with plain HTTP (Line 13) and the runner's own default matches (
http://localhost:8935). Thehttps://here will fail to connect.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/echo/README.md` at line 19, Update the documented runner command in README.md to use the plain HTTP orchestrator URL, matching the go-livepeer startup configuration and runner default; retain the existing host, port, and secret arguments.
32-32: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Missing
examples/echo/path prefix.Earlier commands in this doc use
examples/echo/runner.py/examples/echo/client.py(Line 19, 25); this one drops the prefix and will fail to resolveclient.pyunless the reader has alreadycd'd intoexamples/echo.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/echo/README.md` at line 32, Update the command in the README to invoke client.py with the examples/echo/ path prefix, matching the earlier runner.py and client.py commands while preserving all existing arguments and piping.examples/echo/runner.py (1)
128-170: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win
Race condition on global
state(TOCTOU).The active-session guard at Line 132-134 is checked, then
await create_trickle_channels(...)(Line 137-143) yields control beforestateis set (Line 159-166). Two concurrent/echorequests can both pass thestate is None/session-id check before either assignsstate, resulting in one session'soutput/publisherbeing silently overwritten and leaked (never closed).🔒️ Proposed fix using a guard lock
state: "EchoSession | None" = None +_state_lock = asyncio.Lock() ... async def _handle_echo(request: web.Request) -> web.Response: global state session_id = _session_id(request) - if state is not None: - if state.session_id != session_id: - raise web.HTTPConflict(text="echo runner already has an active session") - return web.json_response(state.to_json()) - - channels = await create_trickle_channels( - request, - [ - {"name": "in", "mime_type": "video/mp2t"}, - {"name": "out", "mime_type": "video/mp2t"}, - ], - ) + async with _state_lock: + if state is not None: + if state.session_id != session_id: + raise web.HTTPConflict(text="echo runner already has an active session") + return web.json_response(state.to_json()) + # Reserve the slot before doing any awaiting I/O below. + state = _RESERVED # sentinel, replaced once channels are ready + + channels = await create_trickle_channels( + request, + [ + {"name": "in", "mime_type": "video/mp2t"}, + {"name": "out", "mime_type": "video/mp2t"}, + ], + )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/echo/runner.py` around lines 128 - 170, Protect the active-session check and initialization in _handle_echo with a shared asyncio lock so concurrent requests cannot both pass the state guard while create_trickle_channels yields. Acquire the lock before checking state and hold it through creation of the channels, output, publisher, and assignment to state; preserve the existing conflict response for a different session_id and the existing response for the active session.examples/text/README.md (1)
3-3: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Typo: "Livpeeer" → "Livepeer".
🧰 Tools
🪛 LanguageTool
[grammar] ~3-~3: Ensure spelling is correct
Context: ...eam Demo Single-shot text streaming on Livpeeer with static configuration. This demo e...(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/text/README.md` at line 3, Correct the product name typo in the README description by replacing “Livpeeer” with “Livepeer” while preserving the rest of the text.src/livepeer_gateway/remote_signer.py (1)
271-274: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Livepeer-Segmentheader can beNone, crashing the payment POST.
payment.seg_credsis optional (GetPaymentResponse(payment="", seg_creds=None)and_payment_requestmay returnseg_creds=None), and aiohttp raises when a header value isNone. The synchronousPaymentSession.send_paymentguards this withp.seg_creds or ""; this async path does not.🛡️ Proposed fix
headers = { "Livepeer-Payment": payment.payment, - "Livepeer-Segment": payment.seg_creds, + "Livepeer-Segment": payment.seg_creds or "", }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.headers = { "Livepeer-Payment": payment.payment, "Livepeer-Segment": payment.seg_creds or "", }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/livepeer_gateway/remote_signer.py` around lines 271 - 274, Update the header construction in the asynchronous payment POST flow to normalize optional payment.seg_creds to an empty string when it is None, matching PaymentSession.send_payment, while leaving the Livepeer-Payment header unchanged.
- PEP 257: one-line summary on the opening """ line - Exceptions describe what they represent, not "Raised when …" (Google §3.8.4) - Code refs use reST double-backticks; drop Sphinx :func:/:class:/:meth: roles - Add module docstrings; document remaining public classes/methods (selection cursors, orchestrator_selector, media_decode workers, restored LiveRunnerSessionHeaders lost in an earlier rebase) - NoOrchestratorAvailableError.__str__ now mirrors NoRunnerAvailableError Docstrings only — no behavior changes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@j0sh just some small docstring updated to ensure that when people import the package the immediately can see what the public methods do. |
Summary
Adds docstrings across the SDK's public API so consumers installing from PyPI get useful
help()and IDE hovers:live_runner— registration, session, trickle-channel, proxy, and call APIsmedia_decode— decoded frame typesmedia_output/media_publish— output/publish classes and stats snapshotsselection—runner_selector,reserve_session, and the selection cursorserrors— HTTP and selection-rejection error typesDocstring conventions
PEP 257 + Google style, adapted for statically-typed Python:
Args:/Attributes:describe parameters and fields but never restate their types; the annotation is the single source of truth.Args:/Returns:/Raises:on functions;Attributes:on classes and dataclasses; constructorArgs:live on__init__, and only where a class is constructed directly (not for exceptions or factory-built types)."""(PEP 257), never on the following line.name) — reStructuredText inline literal; renders as code in IDEs andhelp()with no docs-tooling dependency.Docstrings only — no behavior changes.