Skip to content

Commit 01d9509

Browse files
committed
Make error.pyx pure python
1 parent ec7a9d9 commit 01d9509

6 files changed

Lines changed: 122 additions & 140 deletions

File tree

av/error.pxd

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
21
cdef int stash_exception(exc_info=*)
32
cpdef int err_check(int res, filename=*) except -1

av/error.pyx renamed to av/error.py

Lines changed: 115 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,47 @@
1-
cimport libav as lib
2-
from libc.stdio cimport fprintf, stderr
3-
from libc.stdlib cimport free, malloc
4-
5-
from av.logging cimport get_last_error
6-
71
import errno
82
import os
93
import sys
104
import traceback
115
from threading import local
126

7+
import cython
8+
from cython.cimports import libav as lib
9+
from cython.cimports.av.logging import get_last_error
10+
from cython.cimports.libc.stdio import fprintf, stderr
11+
from cython.cimports.libc.stdlib import free, malloc
12+
1313
# Will get extended with all of the exceptions.
1414
__all__ = [
15-
"ErrorType", "FFmpegError", "LookupError", "HTTPError", "HTTPClientError",
15+
"ErrorType",
16+
"FFmpegError",
17+
"LookupError",
18+
"HTTPError",
19+
"HTTPClientError",
1620
"UndefinedError",
1721
]
22+
sentinel = cython.declare(object, object())
1823

1924

20-
cpdef code_to_tag(int code):
25+
@cython.ccall
26+
def code_to_tag(code: cython.int) -> bytes:
2127
"""Convert an integer error code into 4-byte tag.
2228
2329
>>> code_to_tag(1953719668)
2430
b'test'
2531
2632
"""
27-
return bytes((
28-
code & 0xff,
29-
(code >> 8) & 0xff,
30-
(code >> 16) & 0xff,
31-
(code >> 24) & 0xff,
32-
))
33-
34-
cpdef tag_to_code(bytes tag):
33+
return bytes(
34+
(
35+
code & 0xFF,
36+
(code >> 8) & 0xFF,
37+
(code >> 16) & 0xFF,
38+
(code >> 24) & 0xFF,
39+
)
40+
)
41+
42+
43+
@cython.ccall
44+
def tag_to_code(tag: bytes) -> cython.int:
3545
"""Convert a 4-byte error tag into an integer code.
3646
3747
>>> tag_to_code(b'test')
@@ -40,12 +50,7 @@
4050
"""
4151
if len(tag) != 4:
4252
raise ValueError("Error tags are 4 bytes.")
43-
return (
44-
(tag[0]) +
45-
(tag[1] << 8) +
46-
(tag[2] << 16) +
47-
(tag[3] << 24)
48-
)
53+
return (tag[0]) + (tag[1] << 8) + (tag[2] << 16) + (tag[3] << 24)
4954

5055

5156
class FFmpegError(Exception):
@@ -78,8 +83,8 @@ def __init__(self, code, message, filename=None, log=None):
7883
args.append(filename)
7984
if log:
8085
args.append(log)
81-
super(FFmpegError, self).__init__(*args)
82-
self.args = tuple(args) # FileNotFoundError/etc. only pulls 2 args.
86+
super().__init__(*args)
87+
self.args = tuple(args)
8388

8489
@property
8590
def filename(self):
@@ -104,14 +109,16 @@ def __str__(self):
104109
if self.filename:
105110
msg = f"{msg}: {self.filename!r}"
106111
if self.log:
107-
msg = f"{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}"
112+
msg = (
113+
f"{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}"
114+
)
108115

109116
return msg
110117

111118

112119
# Our custom error, used in callbacks.
113-
cdef int c_PYAV_STASHED_ERROR = tag_to_code(b"PyAV")
114-
cdef str PYAV_STASHED_ERROR_message = "Error in PyAV callback"
120+
c_PYAV_STASHED_ERROR: cython.int = tag_to_code(b"PyAV")
121+
PYAV_STASHED_ERROR_message: str = "Error in PyAV callback"
115122

116123

117124
# Bases for the FFmpeg-based exceptions.
@@ -128,6 +135,8 @@ class HTTPClientError(FFmpegError):
128135

129136

130137
# Tuples of (enum_name, enum_value, exc_name, exc_base).
138+
# tuple[str, int, str | None, Exception | none]
139+
# fmt: off
131140
_ffmpeg_specs = (
132141
("BSF_NOT_FOUND", -lib.AVERROR_BSF_NOT_FOUND, "BSFNotFoundError", LookupError),
133142
("BUG", -lib.AVERROR_BUG, None, RuntimeError),
@@ -156,8 +165,7 @@ class HTTPClientError(FFmpegError):
156165
("HTTP_SERVER_ERROR", -lib.AVERROR_HTTP_SERVER_ERROR, "HTTPServerError", HTTPError),
157166
("PYAV_CALLBACK", c_PYAV_STASHED_ERROR, "PyAVCallbackError", RuntimeError),
158167
)
159-
160-
cdef sentinel = object()
168+
# fmt: on
161169

162170

163171
class EnumType(type):
@@ -189,59 +197,24 @@ def __len__(self):
189197
def __iter__(self):
190198
return iter(self._all)
191199

192-
def __getitem__(self, key):
193-
if isinstance(key, str):
194-
return self._by_name[key]
195200

196-
if isinstance(key, int):
197-
try:
198-
return self._by_value[key]
199-
except KeyError:
200-
pass
201-
202-
raise KeyError(key)
203-
204-
if isinstance(key, self):
205-
return key
206-
207-
raise TypeError(f"{self.__name__} indices must be str, int, or itself")
208-
209-
def _get(self, long value, bint create=False):
210-
try:
211-
return self._by_value[value]
212-
except KeyError:
213-
pass
214-
215-
if not create:
216-
return
217-
218-
return self._create(f"{self.__name__.upper()}_{value}", value, by_value_only=True)
219-
220-
def get(self, key, default=None, create=False):
221-
try:
222-
return self[key]
223-
except KeyError:
224-
if create:
225-
return self._get(key, create=True)
226-
return default
227-
228-
229-
cdef class EnumItem:
201+
@cython.cclass
202+
class EnumItem:
230203
"""An enumeration of FFmpeg's error types.
231204
232-
.. attribute:: tag
205+
.. attribute:: tag
233206
234-
The FFmpeg byte tag for the error.
207+
The FFmpeg byte tag for the error.
235208
236-
.. attribute:: strerror
209+
.. attribute:: strerror
237210
238-
The error message that would be returned.
211+
The error message that would be returned.
212+
"""
239213

240-
"""
241-
cdef readonly str name
242-
cdef readonly int value
214+
name = cython.declare(str, visibility="readonly")
215+
value = cython.declare(cython.int, visibility="readonly")
243216

244-
def __cinit__(self, sentinel_, str name, int value, doc=None):
217+
def __cinit__(self, sentinel_, name: str, value: cython.int, doc=None):
245218
if sentinel_ is not sentinel:
246219
raise RuntimeError(f"Cannot instantiate {self.__class__.__name__}.")
247220

@@ -263,42 +236,25 @@ def tag(self):
263236
return code_to_tag(self.value)
264237

265238

266-
ErrorType = EnumType("ErrorType", (EnumItem, ), {"__module__": __name__}, [x[:2] for x in _ffmpeg_specs])
239+
ErrorType = EnumType(
240+
"ErrorType", (EnumItem,), {"__module__": __name__}, [x[:2] for x in _ffmpeg_specs]
241+
)
267242

268243

269244
for enum in ErrorType:
270-
# Mimic the errno module.
245+
# Mimick the errno module.
271246
globals()[enum.name] = enum
272247
if enum.value == c_PYAV_STASHED_ERROR:
273248
enum.strerror = PYAV_STASHED_ERROR_message
274249
else:
275250
enum.strerror = lib.av_err2str(-enum.value)
276251

277-
278-
# Mimic the builtin exception types.
279-
# See https://www.python.org/dev/peps/pep-3151/#new-exception-classes
280-
# Use the named ones we have, otherwise default to OSError for anything in errno.
281-
282-
# See this command for the count of POSIX codes used:
283-
#
284-
# egrep -IR 'AVERROR\(E[A-Z]+\)' vendor/ffmpeg-4.2 |\
285-
# sed -E 's/.*AVERROR\((E[A-Z]+)\).*/\1/' | \
286-
# sort | uniq -c
287-
#
288-
# The biggest ones that don't map to PEP 3151 builtins:
289-
#
290-
# 2106 EINVAL -> ValueError
291-
# 649 EIO -> IOError (if it is distinct from OSError)
292-
# 4080 ENOMEM -> MemoryError
293-
# 340 ENOSYS -> NotImplementedError
294-
# 35 ERANGE -> OverflowError
295-
296-
classes = {}
252+
classes: dict = {}
297253

298254

299255
def _extend_builtin(name, codes):
300256
base = getattr(__builtins__, name, OSError)
301-
cls = type(name, (FFmpegError, base), dict(__module__=__name__))
257+
cls = type(name, (FFmpegError, base), {"__module__": __name__})
302258

303259
# Register in builder.
304260
for code in codes:
@@ -311,30 +267,56 @@ def _extend_builtin(name, codes):
311267
return cls
312268

313269

314-
# PEP 3151 builtins.
315270
_extend_builtin("PermissionError", (errno.EACCES, errno.EPERM))
316-
_extend_builtin("BlockingIOError", (errno.EAGAIN, errno.EALREADY, errno.EINPROGRESS, errno.EWOULDBLOCK))
317-
_extend_builtin("ChildProcessError", (errno.ECHILD, ))
318-
_extend_builtin("ConnectionAbortedError", (errno.ECONNABORTED, ))
319-
_extend_builtin("ConnectionRefusedError", (errno.ECONNREFUSED, ))
320-
_extend_builtin("ConnectionResetError", (errno.ECONNRESET, ))
321-
_extend_builtin("FileExistsError", (errno.EEXIST, ))
322-
_extend_builtin("InterruptedError", (errno.EINTR, ))
323-
_extend_builtin("IsADirectoryError", (errno.EISDIR, ))
324-
_extend_builtin("FileNotFoundError", (errno.ENOENT, ))
325-
_extend_builtin("NotADirectoryError", (errno.ENOTDIR, ))
271+
_extend_builtin(
272+
"BlockingIOError",
273+
(errno.EAGAIN, errno.EALREADY, errno.EINPROGRESS, errno.EWOULDBLOCK),
274+
)
275+
_extend_builtin("ChildProcessError", (errno.ECHILD,))
276+
_extend_builtin("ConnectionAbortedError", (errno.ECONNABORTED,))
277+
_extend_builtin("ConnectionRefusedError", (errno.ECONNREFUSED,))
278+
_extend_builtin("ConnectionResetError", (errno.ECONNRESET,))
279+
_extend_builtin("FileExistsError", (errno.EEXIST,))
280+
_extend_builtin("InterruptedError", (errno.EINTR,))
281+
_extend_builtin("IsADirectoryError", (errno.EISDIR,))
282+
_extend_builtin("FileNotFoundError", (errno.ENOENT,))
283+
_extend_builtin("NotADirectoryError", (errno.ENOTDIR,))
326284
_extend_builtin("BrokenPipeError", (errno.EPIPE, errno.ESHUTDOWN))
327-
_extend_builtin("ProcessLookupError", (errno.ESRCH, ))
328-
_extend_builtin("TimeoutError", (errno.ETIMEDOUT, ))
285+
_extend_builtin("ProcessLookupError", (errno.ESRCH,))
286+
_extend_builtin("TimeoutError", (errno.ETIMEDOUT,))
287+
_extend_builtin("MemoryError", (errno.ENOMEM,))
288+
_extend_builtin("NotImplementedError", (errno.ENOSYS,))
289+
_extend_builtin("OverflowError", (errno.ERANGE,))
290+
_extend_builtin("OSError", [code for code in errno.errorcode if code not in classes])
329291

330-
# Other obvious ones.
331-
_extend_builtin("ValueError", (errno.EINVAL, ))
332-
_extend_builtin("MemoryError", (errno.ENOMEM, ))
333-
_extend_builtin("NotImplementedError", (errno.ENOSYS, ))
334-
_extend_builtin("OverflowError", (errno.ERANGE, ))
335292

336-
# The rest of them (for now)
337-
_extend_builtin("OSError", [code for code in errno.errorcode if code not in classes])
293+
class ArgumentError(FFmpegError):
294+
def __str__(self):
295+
msg = ""
296+
if self.strerror is not None:
297+
msg = f"{msg}{self.strerror}"
298+
if self.filename:
299+
msg = f"{msg}: {self.filename!r}"
300+
if self.errno is not None:
301+
msg = f"{msg} returned {self.errno}"
302+
if self.log:
303+
msg = (
304+
f"{msg}; last error log: [{self.log[1].strip()}] {self.log[2].strip()}"
305+
)
306+
307+
return msg
308+
309+
310+
class UndefinedError(FFmpegError):
311+
"""Fallback exception type in case FFmpeg returns an error we don't know about."""
312+
313+
pass
314+
315+
316+
classes[errno.EINVAL] = ArgumentError
317+
globals()["ArgumentError"] = ArgumentError
318+
__all__.append("ArgumentError")
319+
338320

339321
# Classes for the FFmpeg errors.
340322
for enum_name, code, name, base in _ffmpeg_specs:
@@ -360,10 +342,12 @@ def _extend_builtin(name, codes):
360342

361343

362344
# Storage for stashing.
363-
cdef object _local = local()
364-
cdef int _err_count = 0
345+
_local: object = local()
346+
_err_count: cython.int = 0
347+
365348

366-
cdef int stash_exception(exc_info=None):
349+
@cython.cfunc
350+
def stash_exception(exc_info=None) -> cython.int:
367351
global _err_count
368352

369353
existing = getattr(_local, "exc_info", None)
@@ -380,9 +364,12 @@ def _extend_builtin(name, codes):
380364
return -c_PYAV_STASHED_ERROR
381365

382366

383-
cdef int _last_log_count = 0
367+
_last_log_count: cython.int = 0
384368

385-
cpdef int err_check(int res, filename=None) except -1:
369+
370+
@cython.ccall
371+
@cython.exceptval(-1, check=False)
372+
def err_check(res: cython.int, filename=None) -> cython.int:
386373
"""Raise appropriate exceptions from library return code."""
387374

388375
global _err_count
@@ -394,7 +381,7 @@ def _extend_builtin(name, codes):
394381
if exc_info is not None:
395382
_err_count -= 1
396383
_local.exc_info = None
397-
raise exc_info[0], exc_info[1], exc_info[2]
384+
raise exc_info[1].with_traceback(exc_info[2])
398385

399386
if res >= 0:
400387
return res
@@ -407,9 +394,11 @@ def _extend_builtin(name, codes):
407394
else:
408395
log = None
409396

410-
cdef int code = -res
411-
cdef char* error_buffer = <char*>malloc(lib.AV_ERROR_MAX_STRING_SIZE * sizeof(char))
412-
if error_buffer == NULL:
397+
code: cython.int = -res
398+
error_buffer: cython.p_char = cython.cast(
399+
cython.p_char, malloc(lib.AV_ERROR_MAX_STRING_SIZE * cython.sizeof(char))
400+
)
401+
if error_buffer == cython.NULL:
413402
raise MemoryError()
414403

415404
try:
@@ -424,8 +413,3 @@ def _extend_builtin(name, codes):
424413
raise cls(code, message, filename, log)
425414
finally:
426415
free(error_buffer)
427-
428-
429-
class UndefinedError(FFmpegError):
430-
"""Fallback exception type in case FFmpeg returns an error we don't know about."""
431-
pass

av/error.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,4 @@ class OSError(FFmpegError, builtins.OSError): ...
6969
class PermissionError(FFmpegError, builtins.PermissionError): ...
7070
class ProcessLookupError(FFmpegError, builtins.ProcessLookupError): ...
7171
class TimeoutError(FFmpegError, builtins.TimeoutError): ...
72-
class ValueError(FFmpegError, builtins.ValueError): ...
72+
class ArgumentError(FFmpegError): ...

0 commit comments

Comments
 (0)