From f00facb88df686f6ad6c076be40e055e8ed9dd5c Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Sun, 1 Dec 2024 15:45:39 -0500 Subject: [PATCH] Remove our custom Enum class --- av/codec/codec.pyi | 1 - av/enum.pxd | 4 - av/enum.pyi | 68 --------- av/enum.pyx | 296 --------------------------------------- av/error.pxd | 2 - av/error.pyi | 41 ------ av/error.pyx | 164 ++++++++++++++++------ av/video/reformatter.pyi | 8 +- docs/api/codec.rst | 12 +- docs/api/enum.rst | 24 ---- docs/api/error.rst | 38 +---- docs/api/error_table.py | 25 ---- tests/test_enums.py | 223 ----------------------------- tests/test_errors.py | 6 +- 14 files changed, 136 insertions(+), 776 deletions(-) delete mode 100644 av/enum.pxd delete mode 100644 av/enum.pyi delete mode 100644 av/enum.pyx delete mode 100644 docs/api/enum.rst delete mode 100644 docs/api/error_table.py delete mode 100644 tests/test_enums.py diff --git a/av/codec/codec.pyi b/av/codec/codec.pyi index 3323895ed..32736c080 100644 --- a/av/codec/codec.pyi +++ b/av/codec/codec.pyi @@ -5,7 +5,6 @@ from typing import ClassVar, Literal, overload from av.audio.codeccontext import AudioCodecContext from av.audio.format import AudioFormat from av.descriptor import Descriptor -from av.enum import EnumFlag from av.subtitles.codeccontext import SubtitleCodecContext from av.video.codeccontext import VideoCodecContext from av.video.format import VideoFormat diff --git a/av/enum.pxd b/av/enum.pxd deleted file mode 100644 index 884fdd961..000000000 --- a/av/enum.pxd +++ /dev/null @@ -1,4 +0,0 @@ -cpdef define_enum( - name, module, items, - bint is_flags=* -) diff --git a/av/enum.pyi b/av/enum.pyi deleted file mode 100644 index 026a176b3..000000000 --- a/av/enum.pyi +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Any, Callable, Iterable, Literal, Sequence, overload - -class EnumType(type): - def __init__( - self, - name: str, - bases: tuple[type, ...], - attrs: dict[str, Any], - items: Iterable[tuple[str, Any, str | None, bool]], - ) -> None: ... - def _create( - self, name: str, value: int, doc: str | None = None, by_value_only: bool = False - ) -> None: ... - def __len__(self) -> None: ... - def __iter__(self) -> None: ... - def __getitem__(self, key: str | int | EnumType) -> None: ... - def _get(self, value: int, create: bool = False) -> None: ... - def _get_multi_flags(self, value: int) -> None: ... - def get( - self, - key: str | int | EnumType, - default: int | None = None, - create: bool = False, - ) -> int | None: ... - -class EnumItem: - @property - def name(self) -> str: ... - @property - def value(self) -> int: ... - def __int__(self) -> int: ... - def __hash__(self) -> int: ... - def __reduce__( - self, - ) -> tuple[Callable[[str, str, str], EnumItem], tuple[str, str, str]]: ... - def __eq__(self, other: object) -> bool: ... - def __ne__(self, other: object) -> bool: ... - -class EnumFlag(EnumItem): - flags: tuple[EnumFlag] - - def __and__(self, other: object) -> EnumFlag: ... - def __or__(self, other: object) -> EnumFlag: ... - def __xor__(self, other: object) -> EnumFlag: ... - def __invert__(self) -> bool: ... - def __nonzero__(self) -> bool: ... - -@overload -def define_enum( - name: str, - module: str, - items: Sequence[tuple[str, int] | None], - is_flags: Literal[True], -) -> EnumFlag: ... -@overload -def define_enum( - name: str, - module: str, - items: Sequence[tuple[str, int] | None], - is_flags: Literal[False], -) -> EnumItem: ... -@overload -def define_enum( - name: str, - module: str, - items: Sequence[tuple[str, int] | None], - is_flags: bool = False, -) -> EnumItem | EnumFlag: ... diff --git a/av/enum.pyx b/av/enum.pyx deleted file mode 100644 index 802a731ff..000000000 --- a/av/enum.pyx +++ /dev/null @@ -1,296 +0,0 @@ -""" - -PyAV provides enumeration and flag classes that are similar to the stdlib ``enum`` -module that shipped with Python 3.4. - -PyAV's enums are a little more forgiving to preserve backwards compatibility -with earlier PyAV patterns. e.g., they can be freely compared to strings or -integers for names and values respectively. - -""" - -import copyreg - - -cdef sentinel = object() - - -class EnumType(type): - def __new__(mcl, name, bases, attrs, *args): - # Just adapting the method signature. - return super().__new__(mcl, name, bases, attrs) - - def __init__(self, name, bases, attrs, items): - self._by_name = {} - self._by_value = {} - self._all = [] - - for spec in items: - self._create(*spec) - - def _create(self, name, value, doc=None, by_value_only=False): - # We only have one instance per value. - try: - item = self._by_value[value] - except KeyError: - item = self(sentinel, name, value, doc) - self._by_value[value] = item - - if not by_value_only: - setattr(self, name, item) - self._all.append(item) - self._by_name[name] = item - - return item - - def __len__(self): - return len(self._all) - - def __iter__(self): - return iter(self._all) - - def __getitem__(self, key): - if isinstance(key, str): - return self._by_name[key] - - if isinstance(key, int): - try: - return self._by_value[key] - except KeyError: - pass - - if issubclass(self, EnumFlag): - return self._get_multi_flags(key) - - raise KeyError(key) - - if isinstance(key, self): - return key - - raise TypeError(f"{self.__name__} indices must be str, int, or itself") - - def _get(self, long value, bint create=False): - try: - return self._by_value[value] - except KeyError: - pass - - if not create: - return - - return self._create(f"{self.__name__.upper()}_{value}", value, by_value_only=True) - - def _get_multi_flags(self, long value): - try: - return self._by_value[value] - except KeyError: - pass - - flags = [] - cdef long to_find = value - for item in self: - if item.value & to_find: - flags.append(item) - to_find = to_find ^ item.value - if not to_find: - break - - if to_find: - raise KeyError(value) - - name = "|".join(f.name for f in flags) - cdef EnumFlag combo = self._create(name, value, by_value_only=True) - combo.flags = tuple(flags) - - return combo - - def get(self, key, default=None, create=False): - try: - return self[key] - except KeyError: - if create: - return self._get(key, create=True) - return default - - def property(self, *args, **kwargs): - return EnumProperty(self, *args, **kwargs) - - -def _unpickle(mod_name, cls_name, item_name): - mod = __import__(mod_name, fromlist=["."]) - cls = getattr(mod, cls_name) - return cls[item_name] - - -copyreg.constructor(_unpickle) - - -cdef class EnumItem: - cdef readonly str name - cdef readonly int value - cdef Py_hash_t _hash - - def __cinit__(self, sentinel_, str name, int value, doc=None): - if sentinel_ is not sentinel: - raise RuntimeError(f"Cannot instantiate {self.__class__.__name__}.") - - self.name = name - self.value = value - self.__doc__ = doc - - # We need to establish a hash that doesn't collide with anything that - # would return true from `__eq__`. This is because these enums (vs - # the stdlib ones) are weakly typed (they will compare against string - # names and int values), and if we have the same hash AND are equal, - # then they will be equivalent as keys in a dictionary, which is wierd. - cdef Py_hash_t hash_ = value + 1 - if hash_ == hash(name): - hash_ += 1 - self._hash = hash_ - - def __repr__(self): - return f"<{self.__class__.__module__}.{self.__class__.__name__}:{self.name}(0x{self.value:x})>" - - def __str__(self): - return self.name - - def __int__(self): - return self.value - - def __hash__(self): - return self._hash - - def __reduce__(self): - return (_unpickle, (self.__class__.__module__, self.__class__.__name__, self.name)) - - def __eq__(self, other): - if isinstance(other, str): - if self.name == other: - return True - - try: - other_inst = self.__class__._by_name[other] - except KeyError: - raise ValueError( - f"{self.__class__.__name__} does not have item named {other!r}" - ) - else: - return self is other_inst - - if isinstance(other, int): - if self.value == other: - return True - if other in self.__class__._by_value: - return False - raise ValueError( - f"{self.__class__.__name__} does not have item valued {other}" - ) - - if isinstance(other, self.__class__): - return self is other - - raise TypeError( - f"'==' not supported between {self.__class__.__name__} and {type(other).__name__}" - ) - - def __ne__(self, other): - return not (self == other) - - -cdef class EnumFlag(EnumItem): - cdef readonly tuple flags - - def __cinit__(self, sentinel, name, value, doc=None): - self.flags = (self, ) - - def __and__(self, other): - if not isinstance(other, int): - other = self.__class__[other].value - value = self.value & other - return self.__class__._get_multi_flags(value) - - def __or__(self, other): - if not isinstance(other, int): - other = self.__class__[other].value - value = self.value | other - return self.__class__._get_multi_flags(value) - - def __xor__(self, other): - if not isinstance(other, int): - other = self.__class__[other].value - value = self.value ^ other - return self.__class__._get_multi_flags(value) - - def __invert__(self): - # This can't result in a flag, but is helpful. - return ~self.value - - def __nonzero__(self): - return bool(self.value) - - -cdef class EnumProperty: - cdef object enum - cdef object fget - cdef object fset - cdef public __doc__ - - def __init__(self, enum, fget, fset=None, doc=None): - self.enum = enum - self.fget = fget - self.fset = fset - self.__doc__ = doc or fget.__doc__ - - def setter(self, fset): - self.fset = fset - return self - - def __get__(self, inst, owner): - if inst is not None: - value = self.fget(inst) - return self.enum.get(value, create=True) - else: - return self - - def __set__(self, inst, value): - item = self.enum.get(value) - self.fset(inst, item.value) - - def flag_property(self, name, doc=None): - - item = self.enum[name] - cdef int item_value = item.value - - class Property(property): - pass - - @Property - def _property(inst): - return bool(self.fget(inst) & item_value) - - if self.fset: - @_property.setter - def _property(inst, value): - if value: - flags = self.fget(inst) | item_value - else: - flags = self.fget(inst) & ~item_value - self.fset(inst, flags) - - _property.__doc__ = doc or item.__doc__ - _property._enum_item = item - - return _property - - -cpdef define_enum(name, module, items, bint is_flags=False): - - if is_flags: - base_cls = EnumFlag - else: - base_cls = EnumItem - - # Some items may be None if they correspond to an unsupported FFmpeg feature - cls = EnumType(name, (base_cls, ), {"__module__": module}, [i for i in items if i is not None]) - - return cls diff --git a/av/error.pxd b/av/error.pxd index 836300d22..d9a542a36 100644 --- a/av/error.pxd +++ b/av/error.pxd @@ -1,5 +1,3 @@ cdef int stash_exception(exc_info=*) - cpdef int err_check(int res, filename=*) except -1 -cpdef make_error(int res, filename=*, log=*) diff --git a/av/error.pyi b/av/error.pyi index 4c41f35e7..abbe2188c 100644 --- a/av/error.pyi +++ b/av/error.pyi @@ -1,58 +1,17 @@ import builtins from enum import Enum -from .enum import EnumItem - classes: dict[int, Exception] def code_to_tag(code: int) -> bytes: ... def tag_to_code(tag: bytes) -> int: ... -def make_error( - res: int, - filename: str | None = None, - log: tuple[int, tuple[int, str, str] | None] | None = None, -) -> None: ... def err_check(res: int, filename: str | None = None) -> int: ... -BUFFER_TOO_SMALL: EnumItem - -class ErrorType(EnumItem, Enum): - BSF_NOT_FOUND: EnumItem - BUG: EnumItem - BUFFER_TOO_SMALL: EnumItem - DECODER_NOT_FOUND: EnumItem - DEMUXER_NOT_FOUND: EnumItem - ENCODER_NOT_FOUND: EnumItem - EOF: EnumItem - EXIT: EnumItem - EXTERNAL: EnumItem - FILTER_NOT_FOUND: EnumItem - INVALIDDATA: EnumItem - MUXER_NOT_FOUND: EnumItem - OPTION_NOT_FOUND: EnumItem - PATCHWELCOME: EnumItem - PROTOCOL_NOT_FOUND: EnumItem - UNKNOWN: EnumItem - EXPERIMENTAL: EnumItem - INPUT_CHANGED: EnumItem - OUTPUT_CHANGED: EnumItem - HTTP_BAD_REQUEST: EnumItem - HTTP_UNAUTHORIZED: EnumItem - HTTP_FORBIDDEN: EnumItem - HTTP_NOT_FOUND: EnumItem - HTTP_OTHER_4XX: EnumItem - HTTP_SERVER_ERROR: EnumItem - PYAV_CALLBACK: EnumItem - - @property - def tag(self) -> bytes: ... - class FFmpegError(Exception): errno: int strerror: str filename: str log: tuple[int, tuple[int, str, str] | None] - type: ErrorType def __init__( self, diff --git a/av/error.pyx b/av/error.pyx index 97fab0e41..426d01ed0 100644 --- a/av/error.pyx +++ b/av/error.pyx @@ -1,4 +1,5 @@ cimport libav as lib +from libc.stdlib cimport free, malloc from av.logging cimport get_last_error @@ -8,8 +9,6 @@ import sys import traceback from threading import local -from av.enum import define_enum - # Will get extended with all of the exceptions. __all__ = [ "ErrorType", "FFmpegError", "LookupError", "HTTPError", "HTTPClientError", @@ -63,10 +62,6 @@ class FFmpegError(Exception): The filename that was being operated on (if available). - .. attribute:: type - - The :class:`av.error.ErrorType` enum value for the error type. - .. attribute:: log The tuple from :func:`av.logging.get_last_log`, or ``None``. @@ -74,6 +69,9 @@ class FFmpegError(Exception): """ def __init__(self, code, message, filename=None, log=None): + self.errno = code + self.strerror = message + args = [code, message] if filename or log: args.append(filename) @@ -81,15 +79,6 @@ class FFmpegError(Exception): args.append(log) super(FFmpegError, self).__init__(*args) self.args = tuple(args) # FileNotFoundError/etc. only pulls 2 args. - self.type = ErrorType.get(code, create=True) - - @property - def errno(self): - return self.args[0] - - @property - def strerror(self): - return self.args[1] @property def filename(self): @@ -165,12 +154,77 @@ _ffmpeg_specs = ( ("PYAV_CALLBACK", c_PYAV_STASHED_ERROR, "PyAVCallbackError", RuntimeError), ) +cdef sentinel = object() + + +class EnumType(type): + def __new__(mcl, name, bases, attrs, *args): + # Just adapting the method signature. + return super().__new__(mcl, name, bases, attrs) + + def __init__(self, name, bases, attrs, items): + self._by_name = {} + self._by_value = {} + self._all = [] + + for spec in items: + self._create(*spec) + + def _create(self, name, value, doc=None, by_value_only=False): + # We only have one instance per value. + try: + item = self._by_value[value] + except KeyError: + item = self(sentinel, name, value, doc) + self._by_value[value] = item -# The actual enum. -ErrorType = define_enum("ErrorType", __name__, [x[:2] for x in _ffmpeg_specs]) + return item -# It has to be monkey-patched. -ErrorType.__doc__ = """An enumeration of FFmpeg's error types. + def __len__(self): + return len(self._all) + + def __iter__(self): + return iter(self._all) + + def __getitem__(self, key): + if isinstance(key, str): + return self._by_name[key] + + if isinstance(key, int): + try: + return self._by_value[key] + except KeyError: + pass + + raise KeyError(key) + + if isinstance(key, self): + return key + + raise TypeError(f"{self.__name__} indices must be str, int, or itself") + + def _get(self, long value, bint create=False): + try: + return self._by_value[value] + except KeyError: + pass + + if not create: + return + + return self._create(f"{self.__name__.upper()}_{value}", value, by_value_only=True) + + def get(self, key, default=None, create=False): + try: + return self[key] + except KeyError: + if create: + return self._get(key, create=True) + return default + + +cdef class EnumItem: + """An enumeration of FFmpeg's error types. .. attribute:: tag @@ -181,7 +235,32 @@ ErrorType.__doc__ = """An enumeration of FFmpeg's error types. The error message that would be returned. """ -ErrorType.tag = property(lambda self: code_to_tag(self.value)) + cdef readonly str name + cdef readonly int value + + def __cinit__(self, sentinel_, str name, int value, doc=None): + if sentinel_ is not sentinel: + raise RuntimeError(f"Cannot instantiate {self.__class__.__name__}.") + + self.name = name + self.value = value + self.__doc__ = doc + + def __repr__(self): + return f"<{self.__class__.__module__}.{self.__class__.__name__}:{self.name}(0x{self.value:x})>" + + def __str__(self): + return self.name + + def __int__(self): + return self.value + + @property + def tag(self): + return code_to_tag(self.value) + + +ErrorType = EnumType("ErrorType", (EnumItem, ), {"__module__": __name__}, [x[:2] for x in _ffmpeg_specs]) for enum in ErrorType: @@ -274,6 +353,8 @@ for enum_name, code, name, base in _ffmpeg_specs: globals()[name] = cls __all__.append(name) +del _ffmpeg_specs + # Storage for stashing. cdef object _local = local() @@ -298,6 +379,27 @@ cdef int stash_exception(exc_info=None): cdef int _last_log_count = 0 + +cpdef make_error(int res, filename=None, log=None): + cdef int code = -res + cdef char* error_buffer = malloc(lib.AV_ERROR_MAX_STRING_SIZE * sizeof(char)) + if error_buffer == NULL: + raise MemoryError() + + try: + if code == c_PYAV_STASHED_ERROR: + message = PYAV_STASHED_ERROR_message + else: + lib.av_strerror(res, error_buffer, lib.AV_ERROR_MAX_STRING_SIZE) + # Fallback to OS error string if no message + message = error_buffer or os.strerror(code) + + cls = classes.get(code, UndefinedError) + return cls(code, message, filename, log) + finally: + free(error_buffer) + + cpdef int err_check(int res, filename=None) except -1: """Raise appropriate exceptions from library return code.""" @@ -329,25 +431,3 @@ cpdef int err_check(int res, filename=None) except -1: class UndefinedError(FFmpegError): """Fallback exception type in case FFmpeg returns an error we don't know about.""" pass - - -cpdef make_error(int res, filename=None, log=None): - cdef int code = -res - cdef bytes py_buffer - cdef char *c_buffer - - if code == c_PYAV_STASHED_ERROR: - message = PYAV_STASHED_ERROR_message - else: - # Jump through some hoops due to Python 2 in same codebase. - py_buffer = b"\0" * lib.AV_ERROR_MAX_STRING_SIZE - c_buffer = py_buffer - lib.av_strerror(res, c_buffer, lib.AV_ERROR_MAX_STRING_SIZE) - py_buffer = c_buffer - message = py_buffer.decode("latin1") - - # Default to the OS if we have no message; this should not get called. - message = message or os.strerror(code) - - cls = classes.get(code, UndefinedError) - return cls(code, message, filename, log) diff --git a/av/video/reformatter.pyi b/av/video/reformatter.pyi index abd545332..a601dd335 100644 --- a/av/video/reformatter.pyi +++ b/av/video/reformatter.pyi @@ -1,8 +1,8 @@ -from av.enum import EnumItem +from enum import IntEnum from .frame import VideoFrame -class Interpolation(EnumItem): +class Interpolation(IntEnum): FAST_BILINEAER: int BILINEAR: int BICUBIC: int @@ -15,7 +15,7 @@ class Interpolation(EnumItem): LANCZOS: int SPLINE: int -class Colorspace(EnumItem): +class Colorspace(IntEnum): ITU709: int FCC: int ITU601: int @@ -30,7 +30,7 @@ class Colorspace(EnumItem): smpte240: int default: int -class ColorRange(EnumItem): +class ColorRange(IntEnum): UNSPECIFIED: int MPEG: int JPEG: int diff --git a/docs/api/codec.rst b/docs/api/codec.rst index d4a241425..7e06ff8cc 100644 --- a/docs/api/codec.rst +++ b/docs/api/codec.rst @@ -90,8 +90,8 @@ Transcoding .. automethod:: CodecContext.flush_buffers -Flags -~~~~~ +Enums and Flags +~~~~~~~~~~~~~~~ .. autoattribute:: CodecContext.flags @@ -107,10 +107,6 @@ Flags .. enumtable:: av.codec.context:Flags2 :class: av.codec.context:CodecContext - -Enums -~~~~~ - .. autoclass:: av.codec.context.ThreadType Which multithreading methods to use. @@ -119,8 +115,4 @@ Enums .. enumtable:: av.codec.context.ThreadType -.. autoclass:: av.codec.context.SkipType - - .. enumtable:: av.codec.context.SkipType - diff --git a/docs/api/enum.rst b/docs/api/enum.rst deleted file mode 100644 index 5fdcec4f7..000000000 --- a/docs/api/enum.rst +++ /dev/null @@ -1,24 +0,0 @@ - -Enumerations and Flags -====================== - -.. currentmodule:: av.enum - -.. automodule:: av.enum - - -.. _enums: - -Enumerations ------------- - -.. autoclass:: EnumItem - - -.. _flags: - -Flags ------ - -.. autoclass:: EnumFlag - diff --git a/docs/api/error.rst b/docs/api/error.rst index 3f82f4ec2..557ab188e 100644 --- a/docs/api/error.rst +++ b/docs/api/error.rst @@ -5,39 +5,18 @@ Errors .. _error_behaviour: -General Behaviour +General Behavior ----------------- When PyAV encounters an FFmpeg error, it raises an appropriate exception. FFmpeg has a couple dozen of its own error types which we represent via -:ref:`error_classes` and at a lower level via :ref:`error_types`. +:ref:`error_classes`. FFmpeg will also return more typical errors such as ``ENOENT`` or ``EAGAIN``, which we do our best to translate to extensions of the builtin exceptions as defined by -`PEP 3151 `_ -(and fall back onto ``OSError`` if using Python < 3.3). - - -.. _error_types: - -Error Type Enumerations ------------------------ - -We provide :class:`av.error.ErrorType` as an enumeration of the various FFmpeg errors. -To mimick the stdlib ``errno`` module, all enumeration values are available in -the ``av.error`` module, e.g.:: - - try: - do_something() - except OSError as e: - if e.errno != av.error.FILTER_NOT_FOUND: - raise - handle_error() - - -.. autoclass:: av.error.ErrorType +`PEP 3151 `_. .. _error_classes: @@ -55,7 +34,7 @@ There are two competing ideas that have influenced the final design: 2. We want to use the builtin exceptions whenever possible. -As such, PyAV effectivly shadows as much of the builtin exception heirarchy as +As such, PyAV effectively shadows as much of the builtin exception hierarchy as it requires, extending from both the builtins and from :class:`FFmpegError`. Therefore, an argument error within FFmpeg will raise a ``av.error.ValueError``, which @@ -74,12 +53,3 @@ All of these exceptions are available on the top-level ``av`` package, e.g.:: .. autoclass:: av.FFmpegError - -Mapping Codes and Classes -------------------------- - -Here is how the classes line up with the error codes/enumerations: - -.. include:: ../_build/rst/api/error_table.rst - - diff --git a/docs/api/error_table.py b/docs/api/error_table.py deleted file mode 100644 index 3a1b5f219..000000000 --- a/docs/api/error_table.py +++ /dev/null @@ -1,25 +0,0 @@ -import av - - -rows = [("Exception Class", "Code/Enum Name", "FFmpeg Error Message")] - -for code, cls in av.error.classes.items(): - enum = av.error.ErrorType.get(code) - - if not enum: - continue - - if enum.tag == b"PyAV": - continue - - rows.append((f"``av.{cls.__name__}``", f"``av.error.{enum.name}``", enum.strerror)) - -lens = [max(len(row[i]) for row in rows) for i in range(len(rows[0]))] - -header = tuple("=" * x for x in lens) -rows.insert(0, header) -rows.insert(2, header) -rows.append(header) - -for row in rows: - print(" ".join("{:{}s}".format(cell, len_) for cell, len_ in zip(row, lens))) diff --git a/tests/test_enums.py b/tests/test_enums.py deleted file mode 100644 index 64b76d9eb..000000000 --- a/tests/test_enums.py +++ /dev/null @@ -1,223 +0,0 @@ -import pickle - -import pytest - -from av.enum import EnumType, define_enum - -PickleableFooBar = define_enum("PickleableFooBar", __name__, [("FOO", 1)]) - - -def define_foobar(is_flags: bool = False): - return define_enum("Foobar", __name__, (("FOO", 1), ("BAR", 2)), is_flags=is_flags) - - -def test_basics(): - cls = define_foobar() - - assert isinstance(cls, EnumType) - - foo = cls.FOO - - assert isinstance(foo, cls) - assert foo.name == "FOO" and foo.value == 1 - assert not isinstance(foo, PickleableFooBar) - - -def test_access(): - cls = define_foobar() - foo1 = cls.FOO - foo2 = cls["FOO"] - foo3 = cls[1] - foo4 = cls[foo1] - assert foo1 is foo2 - assert foo1 is foo3 - assert foo1 is foo4 - - assert foo1 in cls and "FOO" in cls and 1 in cls - - pytest.raises(KeyError, lambda: cls["not a foo"]) - pytest.raises(KeyError, lambda: cls[10]) - pytest.raises(TypeError, lambda: cls[()]) - - assert cls.get("FOO") == foo1 - assert cls.get("not a foo") is None - - -def test_casting(): - cls = define_foobar() - foo = cls.FOO - - assert repr(foo) == "" - - str_foo = str(foo) - assert isinstance(str_foo, str) and str_foo == "FOO" - - int_foo = int(foo) - assert isinstance(int_foo, int) and int_foo == 1 - - -def test_iteration(): - cls = define_foobar() - assert list(cls) == [cls.FOO, cls.BAR] - - -def test_equality(): - cls = define_foobar() - foo = cls.FOO - bar = cls.BAR - - assert foo == "FOO" and foo == 1 and foo == foo - assert foo != "BAR" and foo != 2 and foo != bar - - pytest.raises(ValueError, lambda: foo == "not a foo") - pytest.raises(ValueError, lambda: foo == 10) - pytest.raises(TypeError, lambda: foo == ()) - - -def test_as_key(): - cls = define_foobar() - foo = cls.FOO - - d = {foo: "value"} - assert d[foo] == "value" - assert d.get("FOO") is None - assert d.get(1) is None - - -def test_pickleable(): - cls = PickleableFooBar - foo = cls.FOO - - enc = pickle.dumps(foo) - - foo2 = pickle.loads(enc) - - assert foo is foo2 - - -def test_create_unknown(): - cls = define_foobar() - baz = cls.get(3, create=True) - - assert baz.name == "FOOBAR_3" - assert baz.value == 3 - - -def test_multiple_names(): - cls = define_enum( - "FFooBBar", - __name__, - (("FOO", 1), ("F", 1), ("BAR", 2), ("B", 2)), - ) - - assert cls.F is cls.FOO - - assert cls.F.name == "FOO" - assert cls.F.name != "F" # This is actually the string. - - assert cls.F == "FOO" - assert cls.F == "F" - assert cls.F != "BAR" - assert cls.F != "B" - pytest.raises(ValueError, lambda: cls.F == "x") - - -def test_flag_basics(): - cls = define_enum( - "FoobarAllFlags", - __name__, - {"FOO": 1, "BAR": 2, "FOOBAR": 3}.items(), - is_flags=True, - ) - foo = cls.FOO - bar = cls.BAR - - foobar = foo | bar - assert foobar is cls.FOOBAR - - foo2 = foobar & foo - assert foo2 is foo - - bar2 = foobar ^ foo - assert bar2 is bar - - bar3 = foobar & ~foo - assert bar3 is bar - - x = cls.FOO - x |= cls.BAR - assert x is cls.FOOBAR - - x = cls.FOOBAR - x &= cls.FOO - assert x is cls.FOO - - -def test_multi_flags_basics(): - cls = define_foobar(is_flags=True) - - foo = cls.FOO - bar = cls.BAR - foobar = foo | bar - assert foobar.name == "FOO|BAR" - assert foobar.value == 3 - assert foobar.flags == (foo, bar) - - foobar2 = foo | bar - foobar3 = cls[3] - foobar4 = cls[foobar] - - assert foobar is foobar2 - assert foobar is foobar3 - assert foobar is foobar4 - - pytest.raises(KeyError, lambda: cls["FOO|BAR"]) - - assert len(cls) == 2 # It didn't get bigger - assert list(cls) == [foo, bar] - - -def test_multi_flags_create_missing(): - cls = define_foobar(is_flags=True) - - foobar = cls[3] - assert foobar is cls.FOO | cls.BAR - - pytest.raises(KeyError, lambda: cls[4]) # Not FOO or BAR - pytest.raises(KeyError, lambda: cls[7]) # FOO and BAR and missing flag. - - -def test_properties(): - Flags = define_foobar(is_flags=True) - foobar = Flags.FOO | Flags.BAR - - class Class: - def __init__(self, value): - self.value = Flags[value].value - - @Flags.property - def flags(self): - return self.value - - @flags.setter - def flags(self, value): - self.value = value - - foo = flags.flag_property("FOO") - bar = flags.flag_property("BAR") - - obj = Class("FOO") - - assert obj.flags is Flags.FOO - assert obj.foo - assert not obj.bar - - obj.bar = True - assert obj.flags is foobar - assert obj.foo - assert obj.bar - - obj.foo = False - assert obj.flags is Flags.BAR - assert not obj.foo - assert obj.bar diff --git a/tests/test_errors.py b/tests/test_errors.py index 1cad5d086..f654c6b2e 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -55,9 +55,11 @@ def test_filenotfound(): def test_buffertoosmall() -> None: """Throw an exception from an enum.""" + + BUFFER_TOO_SMALL = 1397118274 try: - av.error.err_check(-av.error.BUFFER_TOO_SMALL.value) + av.error.err_check(-BUFFER_TOO_SMALL) except av.error.BufferTooSmallError as e: - assert e.errno == av.error.BUFFER_TOO_SMALL.value + assert e.errno == BUFFER_TOO_SMALL else: assert False, "No exception raised!"