From 62ce73947e32a5f2d0069d97cff04445e4fef94d Mon Sep 17 00:00:00 2001 From: "Ilya (Marshal)" Date: Sat, 31 Oct 2026 14:24:09 +0100 Subject: [PATCH] Fix docs contrast, mobile overflow and dead API links --- .github/workflows/ci.yml | 2 +- benchmarks/drivers/webrtc-rs/Cargo.lock | 4 +- docs/source/_static/css/custom.css | 32 +++++++++++- docs/source/conf.py | 51 ++++++++++++++----- docs/source/guides/comparison.md | 2 +- docs/source/index.md | 2 +- .../python/webrtc/interfaces/field_trials.py | 1 + .../webrtc/interfaces/rtc_peer_connection.py | 6 +-- .../webrtc/interfaces/sframe_transform.py | 6 +-- .../webrtc/models/media_track_constraints.py | 6 +-- .../python/webrtc/models/rtc_certificate.py | 2 +- .../python/webrtc/models/rtc_encoded_frame.py | 4 +- .../python/webrtc/models/rtc_ice_candidate.py | 4 +- .../python/webrtc/models/rtp_parameters.py | 2 +- .../python/webrtc/models/video_frame.py | 10 ++-- python-webrtc/python/webrtc/streams.py | 4 +- uv.lock | 6 +-- 17 files changed, 101 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a4d1df1..0e3f7b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,6 +182,6 @@ jobs: with: path: dist merge-multiple: true - - uses: pypa/gh-action-pypi-publish@v1.14.2 + - uses: pypa/gh-action-pypi-publish@v1.14.3 with: skip-existing: true diff --git a/benchmarks/drivers/webrtc-rs/Cargo.lock b/benchmarks/drivers/webrtc-rs/Cargo.lock index 1e0ef69..cbf5c86 100644 --- a/benchmarks/drivers/webrtc-rs/Cargo.lock +++ b/benchmarks/drivers/webrtc-rs/Cargo.lock @@ -1396,9 +1396,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.45" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "once_cell", "ring", diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index dcd4ad4..f43c2f2 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -11,6 +11,36 @@ dl.py > dt .sig-prename { overflow-wrap: anywhere; } +/* Long names in prose would scroll the page sideways on phones. */ +#content { + overflow-wrap: anywhere; +} + +#content code.literal .pre { + white-space: pre-wrap; +} + +/* Tables scroll instead. */ +#content table { + overflow-wrap: normal; +} + +/* The previous and next links: long titles wrap instead of widening the page. */ +main .border-t.justify-between > div { + min-width: 0; + overflow-wrap: anywhere; +} + +/* The muted link color is too faint on the code background. */ +#right-sidebar a code { + background: transparent; +} + +/* Inactive tab labels are too faint by default. */ +.sd-tab-set { + --sd-color-tabs-label-inactive: hsl(var(--muted-foreground)); +} + /* API signatures. */ :root { --sig-accent: #b3123c; @@ -54,7 +84,7 @@ dl.py a.reference .pre { dl.py > dt .sig-prename { font-size: 0.85em; font-weight: 400; - opacity: 0.6; + color: hsl(var(--muted-foreground)); } dl.py.class > dd { diff --git a/docs/source/conf.py b/docs/source/conf.py index 60d5a26..9066e53 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -84,7 +84,7 @@ suppress_warnings = ['myst.header', 'ref.python'] # -- Pygments --------------------------------------------------- -pygments_style = 'friendly' +pygments_style = 'xcode' pygments_style_dark = 'monokai' # -- MyST-Parser --------------------------------------------------- @@ -205,7 +205,7 @@ # -- Sitemap --------------------------------------------------- sitemap_locales = [None] sitemap_url_scheme = '{link}' -sitemap_excludes = ['llms-txt/'] +sitemap_excludes = ['llms-txt/', 'search/', 'genindex/'] # -- llms.txt --------------------------------------------------- # a Markdown copy of every page, e.g. /quickstart.md, for agents @@ -339,8 +339,35 @@ def hide_enum_signatures( return None +def _add_alias(py_objects: dict[str, Any], name: str, target: str) -> None: + """Make ``name`` resolve to the documented ``target``, unless it's documented itself.""" + if name not in py_objects: + entry = py_objects[target] + py_objects[name] = entry.__class__(entry.docname, entry.node_id, entry.objtype, aliased=True) + + +def _alias_class(py_objects: dict[str, Any], short: str, cls: type) -> None: + """Alias a class and its members to ``short``, and the documented members of its bases to both names.""" + canonical = f'{cls.__module__}.{cls.__qualname__}' + if canonical not in py_objects: + return + + _add_alias(py_objects, short, canonical) + + # the nearest definition wins, as in the class + for owner in inspect.getmro(cls): + owner_canonical = f'{owner.__module__}.{owner.__qualname__}' + members = [key[len(owner_canonical) :] for key in py_objects if key.startswith(f'{owner_canonical}.')] + for member in members: + _add_alias(py_objects, f'{canonical}{member}', f'{owner_canonical}{member}') + _add_alias(py_objects, f'{short}{member}', f'{owner_canonical}{member}') + + def register_short_aliases(app: Sphinx, env: BuildEnvironment) -> None: - """Resolve ``webrtc.X`` references to the module the object is documented in.""" + """Resolve ``webrtc.X`` references to the module the object is documented in. + + A class also gets the documented members of its bases, like ``from_json`` of every dictionary. + """ # parallel builds import the package in the workers only with mock(app.config.autodoc_mock_imports): import webrtc # ruff: ignore[import-outside-top-level] @@ -349,17 +376,17 @@ def register_short_aliases(app: Sphinx, env: BuildEnvironment) -> None: for name in webrtc.__all__: obj = getattr(webrtc, name, None) module = getattr(obj, '__module__', None) - if not module or module == 'webrtc' or not (inspect.isclass(obj) or inspect.isfunction(obj)): + if not module or module == 'webrtc': + continue + + if inspect.isclass(obj): + _alias_class(py_objects, f'webrtc.{name}', obj) continue - canonical = f'{module}.{obj.__qualname__}' - # the object and its members - targets = [key for key in py_objects if key == canonical or key.startswith(f'{canonical}.')] - for target in targets: - short = f'webrtc.{name}{target[len(canonical) :]}' - if short not in py_objects: - entry = py_objects[target] - py_objects[short] = entry.__class__(entry.docname, entry.node_id, entry.objtype, aliased=True) + # a function, or a module-level instance like field_trials + canonical = f'{module}.{obj.__qualname__}' if inspect.isfunction(obj) else f'{module}.{name}' + if canonical in py_objects: + _add_alias(py_objects, f'webrtc.{name}', canonical) def disable_search_index(app: Sphinx) -> None: diff --git a/docs/source/guides/comparison.md b/docs/source/guides/comparison.md index 9706b68..d480190 100644 --- a/docs/source/guides/comparison.md +++ b/docs/source/guides/comparison.md @@ -24,7 +24,7 @@ webrtcbin. This page compares them as of 24 October 2026, with a source for ever ## Features -| | **Python WebRTC** | aiortc | LiveKit SDK | GStreamer webrtcbin | +| Feature | **Python WebRTC** | aiortc | LiveKit SDK | GStreamer webrtcbin | |---------------------------|----------------------------|--------------------------|------------------------------|-----------------------| | Engine | libwebrtc M152 | own, in Python | libwebrtc M150, through Rust | own, in C | | Connects to | any WebRTC endpoint | any WebRTC endpoint | a LiveKit server | any WebRTC endpoint | diff --git a/docs/source/index.md b/docs/source/index.md index 3444525..628c98a 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -98,7 +98,7 @@ Prebuilt wheels for Linux, macOS and Windows, including free-threaded Python. No ## How it compares -| | **Python WebRTC** | aiortc | LiveKit SDK | GStreamer webrtcbin | +| Feature | **Python WebRTC** | aiortc | LiveKit SDK | GStreamer webrtcbin | |-----------------------------------------------------|----------------------|----------------|---------------------------|---------------------| | Engine | libwebrtc (Chrome's) | own, in Python | libwebrtc | own, in C | | Connects peer-to-peer to any WebRTC endpoint | ✅ | ✅ | ❌ needs a LiveKit server | ✅ | diff --git a/python-webrtc/python/webrtc/interfaces/field_trials.py b/python-webrtc/python/webrtc/interfaces/field_trials.py index 4a1f195..549da27 100644 --- a/python-webrtc/python/webrtc/interfaces/field_trials.py +++ b/python-webrtc/python/webrtc/interfaces/field_trials.py @@ -115,6 +115,7 @@ def update( @override def clear(self) -> None: + """Removes all trials at once.""" _apply({}) @override diff --git a/python-webrtc/python/webrtc/interfaces/rtc_peer_connection.py b/python-webrtc/python/webrtc/interfaces/rtc_peer_connection.py index d053495..b83a449 100644 --- a/python-webrtc/python/webrtc/interfaces/rtc_peer_connection.py +++ b/python-webrtc/python/webrtc/interfaces/rtc_peer_connection.py @@ -800,7 +800,7 @@ async def get_stats(self, selector: webrtc.MediaStreamTrack | None = None) -> we @staticmethod async def generate_certificate(keygen_algorithm: AlgorithmIdentifier) -> webrtc.RTCCertificate: - """Generates a key and a self-signed certificate on a worker thread, for :attr:`RTCConfiguration.certificates`. + """Generates a key and a self-signed certificate on a worker thread, for ``RTCConfiguration.certificates``. See :mdn:`RTCPeerConnection/generateCertificate_static`. @@ -843,8 +843,8 @@ def set_configuration(self, configuration: webrtc.RTCConfiguration | None = None Raises: webrtc.InvalidStateError: If the connection is closed. webrtc.InvalidModificationError: If a member that can't be changed differs, like - :attr:`webrtc.RTCConfiguration.bundle_policy` or - :attr:`webrtc.RTCConfiguration.always_negotiate_data_channels`. + ``webrtc.RTCConfiguration.bundle_policy`` or + ``webrtc.RTCConfiguration.always_negotiate_data_channels``. webrtc.InvalidSyntaxError: If an ICE server URL is invalid. webrtc.InvalidAccessError: If a TURN server has no credentials, or a certificate has expired. ValueError: If a member of the configuration is out of range. diff --git a/python-webrtc/python/webrtc/interfaces/sframe_transform.py b/python-webrtc/python/webrtc/interfaces/sframe_transform.py index e03ec9d..87c8fb3 100644 --- a/python-webrtc/python/webrtc/interfaces/sframe_transform.py +++ b/python-webrtc/python/webrtc/interfaces/sframe_transform.py @@ -290,7 +290,7 @@ def _decrypt(self, data: Buffer, frame: RTCEncodedVideoFrame | RTCEncodedAudioFr class _SFrameStream(WebRTCObject[wrtc.SFrameTransform], Generic[_C]): - """A GenericTransformStream of SFrame. Frames or buffers written to :attr:`writable` are read back transformed. + """A GenericTransformStream of SFrame. Frames or buffers written to ``writable`` are read back transformed. The type parameter is its chunk type. It's encoded frames by default, to match the streams of the transformer it's piped between. Use ``bytes`` for buffers, as in ``SFrameEncryptorStream[bytes]``, or :obj:`SFrameChunk` @@ -328,7 +328,7 @@ def writable(self) -> WritableStream[_C | Buffer]: class SFrameEncryptorStream(_SFrameEncryptorManager, _SFrameStream[_C]): """A transform stream that encrypts the frames or buffers written to it with SFrame. - It has :attr:`readable` and :attr:`writable` like a :obj:`webrtc.TransformStream`, so it can be piped through in + It has ``readable`` and ``writable`` like a :obj:`webrtc.TransformStream`, so it can be piped through in the worker of an :obj:`webrtc.RTCRtpScriptTransform`. A frame comes out with its data encrypted, and a buffer comes out as the :obj:`bytes` of the ciphertext. Until a key is set, chunks are dropped. The type parameter is the output type. It's encoded frames by default, and ``SFrameEncryptorStream[bytes]`` handles buffers. @@ -359,7 +359,7 @@ class SFrameDecryptorStream( ): """A transform stream that decrypts the SFrame frames or buffers written to it. - It has :attr:`readable` and :attr:`writable` like a :obj:`webrtc.TransformStream`. A frame comes out with its + It has ``readable`` and ``writable`` like a :obj:`webrtc.TransformStream`. A frame comes out with its data decrypted, and a buffer comes out as the :obj:`bytes` of the plaintext. A chunk that fails to decrypt is dropped and reported by an ``error`` event. The type parameter is the output type. It's encoded frames by default, and ``SFrameDecryptorStream[bytes]`` handles buffers. diff --git a/python-webrtc/python/webrtc/models/media_track_constraints.py b/python-webrtc/python/webrtc/models/media_track_constraints.py index 6ea3ce1..51c503d 100644 --- a/python-webrtc/python/webrtc/models/media_track_constraints.py +++ b/python-webrtc/python/webrtc/models/media_track_constraints.py @@ -136,7 +136,7 @@ class MediaTrackSupportedConstraints(Dictionary): """The constraints the library recognizes, as :meth:`webrtc.MediaDevices.get_supported_constraints` returns. Every member is :obj:`True`. A recognized constraint that the source has no capability or setting for (like - :attr:`facing_mode` on the synthetic camera) is ignored when ideal and fails when required. + ``facing_mode`` on the synthetic camera) is ignored when ideal and fails when required. See :mdn:`MediaDevices/getSupportedConstraints`. @@ -213,7 +213,7 @@ class MediaTrackSettings(Dictionary): """What a track carries now, as :meth:`webrtc.MediaStreamTrack.get_settings` returns. Members are :obj:`None` when they don't apply to the track or aren't known yet. The library never fills - :attr:`facing_mode` or :attr:`background_blur`. + ``facing_mode`` or ``background_blur``. See :mdn:`MediaTrackSettings`. @@ -471,7 +471,7 @@ class MediaTrackConstraints(MediaTrackConstraintSet): """The constraints of a track, as given to :meth:`webrtc.MediaStreamTrack.apply_constraints`. They're also given per track to :meth:`webrtc.MediaDevices.get_user_media`. The members are the ones of - :obj:`MediaTrackConstraintSet`, plus :attr:`advanced`. + :obj:`MediaTrackConstraintSet`, plus ``advanced``. See :mdn:`MediaTrackConstraints`. diff --git a/python-webrtc/python/webrtc/models/rtc_certificate.py b/python-webrtc/python/webrtc/models/rtc_certificate.py index f174eed..5c5a9a5 100644 --- a/python-webrtc/python/webrtc/models/rtc_certificate.py +++ b/python-webrtc/python/webrtc/models/rtc_certificate.py @@ -183,7 +183,7 @@ class RTCCertificate(WebRTCObject[wrtc.RTCCertificate]): """A certificate and its private key, which a connection authenticates with in DTLS. Only :meth:`webrtc.RTCPeerConnection.generate_certificate` creates it, and a connection uses it through - :attr:`webrtc.RTCConfiguration.certificates`. A connection without one generates its own. + ``webrtc.RTCConfiguration.certificates``. A connection without one generates its own. See :mdn:`RTCCertificate`. """ diff --git a/python-webrtc/python/webrtc/models/rtc_encoded_frame.py b/python-webrtc/python/webrtc/models/rtc_encoded_frame.py index 30eeb04..abf3fea 100644 --- a/python-webrtc/python/webrtc/models/rtc_encoded_frame.py +++ b/python-webrtc/python/webrtc/models/rtc_encoded_frame.py @@ -79,9 +79,9 @@ class RTCEncodedVideoFrameMetadata(RTCEncodedFrameMetadata): See :mdn:`RTCEncodedVideoFrame/getMetadata`. Args: - frame_id (:obj:`int`, optional): The identifier of the frame, which :attr:`dependencies` of later frames + frame_id (:obj:`int`, optional): The identifier of the frame, which ``dependencies`` of later frames refer to. - dependencies (:obj:`list` of :obj:`int`, optional): The :attr:`frame_id` values of the frames this one + dependencies (:obj:`list` of :obj:`int`, optional): The ``frame_id`` values of the frames this one depends on. width (:obj:`int`, optional): The width of the frame, in pixels. height (:obj:`int`, optional): The height of the frame, in pixels. diff --git a/python-webrtc/python/webrtc/models/rtc_ice_candidate.py b/python-webrtc/python/webrtc/models/rtc_ice_candidate.py index d1013c9..c5e4300 100644 --- a/python-webrtc/python/webrtc/models/rtc_ice_candidate.py +++ b/python-webrtc/python/webrtc/models/rtc_ice_candidate.py @@ -282,7 +282,7 @@ class RTCIceCandidate: A :obj:`webrtc.RTCPeerConnection` gathers them (see its ``icecandidate`` event). Each one is sent to the remote peer and added there with :meth:`webrtc.RTCPeerConnection.add_ice_candidate`. The constructor doesn't reject an - unparsable :attr:`candidate`, and the properties parsed from it are :obj:`None` then. Instances are immutable. + unparsable ``candidate``, and the properties parsed from it are :obj:`None` then. Instances are immutable. See :mdn:`RTCIceCandidate`. @@ -414,7 +414,7 @@ def port(self) -> int | None: @property def type(self) -> RTCIceCandidateType | None: - """:obj:`webrtc.RTCIceCandidateType`, optional: How the candidate was found: host, reflexive or relay. + """:obj:`webrtc.RTCIceCandidateType`, optional: How the candidate was found, as host, reflexive or relay. See :mdn:`RTCIceCandidate/type`. """ diff --git a/python-webrtc/python/webrtc/models/rtp_parameters.py b/python-webrtc/python/webrtc/models/rtp_parameters.py index ba1160b..67b1710 100644 --- a/python-webrtc/python/webrtc/models/rtp_parameters.py +++ b/python-webrtc/python/webrtc/models/rtp_parameters.py @@ -418,7 +418,7 @@ def _from_native(cls, native: wrtc.RtpParameters) -> RTCRtpReceiveParameters: class RTCRtpSendParameters(RTCRtpParameters): """The parameters a sender sends with, from :meth:`webrtc.RTCRtpSender.get_parameters`. - Only :attr:`encodings` (all but their ``rid``) and :attr:`degradation_preference` can be changed with + Only ``encodings`` (all but their ``rid``) and ``degradation_preference`` can be changed with :meth:`webrtc.RTCRtpSender.set_parameters`. See :mdn:`RTCRtpSender/getParameters`. diff --git a/python-webrtc/python/webrtc/models/video_frame.py b/python-webrtc/python/webrtc/models/video_frame.py index bc28e40..c8a3cf6 100644 --- a/python-webrtc/python/webrtc/models/video_frame.py +++ b/python-webrtc/python/webrtc/models/video_frame.py @@ -56,7 +56,7 @@ class DOMRectReadOnly: """An immutable rectangle, like the coded or visible part of a frame. - The width and height may be negative. The edges are then measured from :attr:`x` and :attr:`y` backwards. + The width and height may be negative. The edges are then measured from ``x`` and ``y`` backwards. See :mdn:`DOMRectReadOnly`. @@ -74,7 +74,7 @@ class DOMRectReadOnly: @property def top(self) -> float: - """:obj:`float`: The smaller of :attr:`y` and :attr:`y` plus :attr:`height`. + """:obj:`float`: The smaller of ``y`` and ``y`` plus ``height``. See :mdn:`DOMRectReadOnly/top`. """ @@ -82,7 +82,7 @@ def top(self) -> float: @property def right(self) -> float: - """:obj:`float`: The larger of :attr:`x` and :attr:`x` plus :attr:`width`. + """:obj:`float`: The larger of ``x`` and ``x`` plus ``width``. See :mdn:`DOMRectReadOnly/right`. """ @@ -90,7 +90,7 @@ def right(self) -> float: @property def bottom(self) -> float: - """:obj:`float`: The larger of :attr:`y` and :attr:`y` plus :attr:`height`. + """:obj:`float`: The larger of ``y`` and ``y`` plus ``height``. See :mdn:`DOMRectReadOnly/bottom`. """ @@ -98,7 +98,7 @@ def bottom(self) -> float: @property def left(self) -> float: - """:obj:`float`: The smaller of :attr:`x` and :attr:`x` plus :attr:`width`. + """:obj:`float`: The smaller of ``x`` and ``x`` plus ``width``. See :mdn:`DOMRectReadOnly/left`. """ diff --git a/python-webrtc/python/webrtc/streams.py b/python-webrtc/python/webrtc/streams.py index a1b890e..bb37967 100644 --- a/python-webrtc/python/webrtc/streams.py +++ b/python-webrtc/python/webrtc/streams.py @@ -404,7 +404,7 @@ class ReadableStreamReadResult(Dictionary, Generic[_T]): See :mdn:`ReadableStreamDefaultReader/read`. Args: - value (optional): The chunk read. It's :obj:`None` when :attr:`done` is set. + value (optional): The chunk read. It's :obj:`None` when ``done`` is set. done (:obj:`bool`, optional): :obj:`True` once the stream is closed and every chunk was read. """ @@ -696,7 +696,7 @@ def pipe_to( """Reads every chunk and writes it to a writable stream, respecting its backpressure. Both streams are locked while the pipe runs. There's no abort signal, so cancel the returned future to stop - the pipe. The destination is then aborted unless :attr:`StreamPipeOptions.prevent_abort` is set. + the pipe. The destination is then aborted unless ``StreamPipeOptions.prevent_abort`` is set. See :mdn:`ReadableStream/pipeTo`. diff --git a/uv.lock b/uv.lock index ac0e0be..fcfb2dc 100644 --- a/uv.lock +++ b/uv.lock @@ -519,11 +519,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.7.22" +version = "2026.10.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/eb/a2aa1ac3ea30dd3f30ca57f40232138b6b0040719316ecb85daf61167771/certifi-2026.10.18.tar.gz", hash = "sha256:b9995d97b64d0431a5af837d8ca8cd6238104d1f319f04bd93ea43124d6c861e", size = 135227, upload-time = "2026-10-18T16:21:07.655Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, + { url = "https://files.pythonhosted.org/packages/08/ba/95dd616042484f7c83a06974388b37b837732740ee70bc5dc715b5fcd55a/certifi-2026.10.18-py3-none-any.whl", hash = "sha256:73191c47aaa79da394e8fee2dbc2ec827432cab56311122c9e47abf79e390f62", size = 134093, upload-time = "2026-10-18T16:21:06.276Z" }, ] [[package]]