Skip to content

Stream.disposition setter rejects Disposition enum members at runtime (type/runtime contract mismatch) #2256

@HotariTobu

Description

@HotariTobu

Summary

Stream.disposition is typed as Disposition in av/stream.pyi, but assigning a Disposition member at runtime raises TypeError. The user must assign Disposition.X.value (an int) to make the runtime accept it, which then fails static type-checking against the stub. There is no value that satisfies both the static type and the runtime.

Root cause: Disposition extends enum.Flag (not enum.IntFlag), so its members are not int subclasses; the Cython setter assigns to a C int field, and the auto-conversion int(value) fails for plain Flag members.

Environment

  • PyAV: 17.0.1 (latest on PyPI)
  • ffmpeg: 8.0.1 (Homebrew)
  • Python: 3.12.11
  • OS: macOS 26.4.1 (Darwin 25)

Reproducer

import av
from av.stream import Disposition

w = av.open("/tmp/repro.mp3", mode="w", format="mp3")
w.add_stream("mp3", rate=44100)
video = w.add_stream("png", rate=1)
video.width = 1
video.height = 1
video.pix_fmt = "rgba"

# A) Assign the enum member (matches the stub `disposition: Disposition`).
# Runtime: TypeError.
video.disposition = Disposition.attached_pic
# TypeError: int() argument must be a string, a bytes-like object or a real
# number, not 'Disposition'

# B) Assign the underlying int via .value (runtime works, static check fails).
video.disposition = Disposition.attached_pic.value
Assignment Runtime Static type-check
video.disposition = Disposition.attached_pic TypeError OK
video.disposition = Disposition.attached_pic.value OK error: int is not assignable to Disposition

Expected

Assigning the value declared by the stub (Disposition.attached_pic) should succeed at runtime.

Root cause

Disposition is declared as a plain Flag, whose members are not int subclasses:

>>> from av.stream import Disposition
>>> [c.__name__ for c in type(Disposition.attached_pic).__mro__]
['Disposition', 'Flag', 'Enum', 'object']
>>> isinstance(Disposition.attached_pic, int)
False
>>> int(Disposition.attached_pic)
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'Disposition'

The setter for disposition in av/stream.py assigns to a C int field on the underlying AVStream:

def __setattr__(self, name, value):
    ...
    if name == "disposition":
        self.ptr.disposition = value
        return

Cython auto-converts the right-hand side via int(value) for the C int target. Since plain Flag does not implement __int__, this raises TypeError. The user must pass .value (an int) for the conversion to succeed — but that contradicts the stub, which declares disposition: Disposition.

Suggested fix

Change Disposition to inherit from enum.IntFlag instead of enum.Flag. IntFlag members are int subclasses, so int(member) works and the Cython setter's conversion succeeds. Bitwise operations and membership behavior are preserved. Two lines in av/stream.py (and the matching two in av/stream.pyi).

I'm happy to send a PR if helpful.

Observed downstream impact

External code that sets disposition = Disposition.attached_pic.value (the only form runtime accepts) cannot pass a static type check without a suppression marker (# type: ignore[invalid-assignment], an explicit cast, or a rule downgrade in the type-checker config).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions