[File] Python bindings to libmagic

Hoël Bézier hoelbezier at leanco.fr
Tue Jul 19 08:48:46 UTC 2022


Hi, this is a follow-up to my previous email.
>So it seems to me that the `open` routine always returns a Magic 
>object and cannot fail. The `_open` routine on the other hand might 
>fail, and thus the returned Magic object would have a _magic_t 
>attribute set to None.
>
>If I understood things properly, maybe the documentation would need to 
>be updated so that users may handle failures properly.
I figured it would be best to update the code to reflect the documentation, so 
I’ve attached a patch that should do exactly this. I’ve also added a bit of 
error handling for the detect_from_* routines, using a magic.error exception.

Since I’m also trying to type my code whenever possible, the patch includes a 
brand new magic.pyi file which contains type annotations for all public 
routines and constants of the module. However, as I have no experience with 
python packaging, I’m not sure this file would be included by the setup.py.

Feedback is of course welcome. :)
Hoël
-------------- next part --------------
diff --git a/python/magic.py b/python/magic.py
--- a/python/magic.py
+++ b/python/magic.py
@@ -272,16 +272,29 @@ def open(flags):
     Returns a magic object on success and None on failure.
     Flags argument as for setflags.
     """
-    return Magic(_open(flags))
+    magic_t = _open(flags)
+    if magic_t is None:
+        return None
+    else:
+        return Magic(magic_t)
 
 
 # Objects used by `detect_from_` functions
+class error(Exception):
+    pass
+
 class MagicDetect(object):
     def __init__(self):
-        self.mime_magic = Magic(_open(MAGIC_MIME))
-        self.mime_magic.load()
-        self.none_magic = Magic(_open(MAGIC_NONE))
-        self.none_magic.load()
+        self.mime_magic = open(MAGIC_MIME)
+        if self.mime_magic is None:
+            raise error
+        if self.mime_magic.load() == -1:
+            raise error
+        self.none_magic = open(MAGIC_NONE)
+        if self.none_magic is None:
+            raise error
+        if self.none_magic.load() == -1:
+            raise error
 
     def __del__(self):
         self.mime_magic.close()
diff --git a/python/magic.pyi b/python/magic.pyi
new file mode 100644
--- /dev/null
+++ b/python/magic.pyi
@@ -0,0 +1,93 @@
+from typing import NamedTuple
+from io import IOBase
+
+MAGIC_NONE: int
+NONE: int
+MAGIC_DEBUG: int
+DEBUG: int
+MAGIC_SYMLINK: int
+SYMLINK: int
+MAGIC_COMPRESS: int
+COMPRESS: int
+MAGIC_DEVICES: int
+DEVICES: int
+MAGIC_MIME_TYPE: int
+MIME_TYPE: int
+MAGIC_CONTINUE: int
+CONTINUE: int
+MAGIC_CHECK: int
+CHECK: int
+MAGIC_PRESERVE_ATIME: int
+PRESERVE_ATIME: int
+MAGIC_RAW: int
+RAW: int
+MAGIC_ERROR: int
+ERROR: int
+MAGIC_MIME_ENCODING: int
+MIME_ENCODING: int
+MAGIC_MIME: int
+MIME: int
+MAGIC_APPLE: int
+APPLE: int
+MAGIC_NO_CHECK_COMPRESS: int
+NO_CHECK_COMPRESS: int
+MAGIC_NO_CHECK_TAR: int
+NO_CHECK_TAR: int
+MAGIC_NO_CHECK_SOFT: int
+NO_CHECK_SOFT: int
+MAGIC_NO_CHECK_APPTYPE: int
+NO_CHECK_APPTYPE: int
+MAGIC_NO_CHECK_ELF: int
+NO_CHECK_ELF: int
+MAGIC_NO_CHECK_TEXT: int
+NO_CHECK_TEXT: int
+MAGIC_NO_CHECK_CDF: int
+NO_CHECK_CDF: int
+MAGIC_NO_CHECK_TOKENS: int
+NO_CHECK_TOKENS: int
+MAGIC_NO_CHECK_ENCODING: int
+NO_CHECK_ENCODING: int
+MAGIC_NO_CHECK_BUILTIN: int
+NO_CHECK_BUILTIN: int
+MAGIC_PARAM_INDIR_MAX: int
+PARAM_INDIR_MAX: int
+MAGIC_PARAM_NAME_MAX: int
+PARAM_NAME_MAX: int
+MAGIC_PARAM_ELF_PHNUM_MAX: int
+PARAM_ELF_PHNUM_MAX: int
+MAGIC_PARAM_ELF_SHNUM_MAX: int
+PARAM_ELF_SHNUM_MAX: int
+MAGIC_PARAM_ELF_NOTES_MAX: int
+PARAM_ELF_NOTES_MAX: int
+MAGIC_PARAM_REGEX_MAX: int
+PARAM_REGEX_MAX: int
+MAGIC_PARAM_BYTES_MAX: int
+PARAM_BYTES_MAX: int
+
+class FileMagic(NamedTuple):
+    mime_type: str
+    encoding: str
+    name: str
+
+class Magic:
+    def close(self) -> None: ...
+    def file(self, filename: str | bytes) -> str | None: ...
+    def descriptor(self, fd: int) -> str | None: ...
+    def buffer(self, buf: str | bytes) -> str | None: ...
+    def error(self) -> str | None: ...
+    def setflags(self, flags: int) -> int: ...
+    def load(self, filename: str | bytes | None = ...) -> int: ...
+    def compile(self, dbs: str | bytes) -> int: ...
+    def check(self, dbs: str | bytes) -> int: ...
+    def list(self, dbs: str | bytes) -> int: ...
+    def errno(self) -> int: ...
+    def getparam(self, param: int) -> int: ...
+    def setparam(self, param: int, value: int) -> int: ...
+
+def open(flags: int) -> Magic | None: ...
+
+class error(Exception): ...
+
+def detect_from_filename(filename: str | bytes) -> FileMagic: ...
+def detect_from_fobj(fobj: IOBase) -> FileMagic: ...
+def detect_from_content(byte_content: str | bytes) -> FileMagic: ...
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://mailman.astron.com/pipermail/file/attachments/20220719/89fc29f3/attachment.asc>


More information about the File mailing list