magical_py
Native Python bindings for
magical_rs, a zero-dependency
Rust library that identifies files by their magic bytes.
This is not a drop-in replacement for python-magic, and it is not trying to
be one. python-magic returns a string like "image/png" and leaves you to parse
it. This returns a typed value that carries its media type and conventional
extension with it.
Install
Not released to PyPI yet. From a checkout of this repository:
pip install ./bindings/python
Once published this becomes pip install magical-py, and there is a binary
wheel for every platform we publish for. One abi3 wheel covers CPython 3.8
and later, so you will not have to build from source.
FileKind covers all 114 formats the Rust crate detects, and the enum is
declared in Python rather than generated, so it is a real enum.Enum and your
editor completes FileKind. with every member and its docstring.
Use
from magical_py import FileKind, detect
kind = detect("photo.jpg")
kind is FileKind.Jpg # True, a real enum member your editor can complete
kind.value # 'jpg', a stable id for serialising
kind.mime # 'image/jpeg'
kind.extension # 'jpg'
kind.description # 'JPEG'
For data you already have in memory:
from magical_py import detect_bytes
detect_bytes(header) # FileKind.Png | None
Both return None when nothing matches, and raise the usual OSError
subclasses when a file cannot be read:
try:
kind = detect(path)
except FileNotFoundError:
kind = None
What it looks like
Each member carries its display name as its __doc__, which is the attribute
the REPL and help() read. Python 3.9 and later render it for all 114 members;
Python 3.8 omits it from help(), so on 3.8 read __doc__ directly as below.
>>> FileKind.Png.__doc__
'PNG'
>>> FileKind.ISO.__doc__
'ISO 9660'
>>> FileKind.PkgZip.description
'Zip / JAR / APK'
Things worth knowing
Detection reads magic bytes only. The file name is never consulted, so a
.jpg that actually contains PNG data is reported as FileKind.Png. That is
the behaviour you want from a detection library, and it is also why the
extension is advisory rather than something to switch on.
Some media types are None. FileKind.mime and FileKind.extension
return None when no media type is registered or verified for that format.
None means "there isn't one", never "we guessed". A wrong media type would be
a documentation bug of the same kind as a wrong magic byte, and this project
does not ship those.
detect reads 36,870 bytes. ISO 9660 stores its magic at offset 36,865, so
detecting it means reading that far into the file. detect does that for you;
magical_py.bytes_read() reports the figure if you are reading headers
yourself. Do not hardcode 2048 and assume you are done.
Some signatures are only two bytes. 9 formats match on nothing but a
two-byte prefix: Arj, Bitmap, Gzip, MP3, MSDOS, Pcx, Pickle,
SerializedJavaData and Zlib. Those are the values the formats themselves
specify, so a file beginning with those bytes is reported as that format even
if it is not really one. Each one is listed with its bytes in the
magical_rs README.
Two rules look at more than a fixed pattern. FileKind.ScriptExecute
requires a / later on the first line, so a bare #! does not count;
FileKind.WEBP requires the full RIFF....WEBP layout. The shebang rule
exists because #!AMR is the literal magic of AMR audio: without the /
check, the script rule claimed those files and they became undetectable.
Type checking
The package ships a py.typed marker and stubs for the compiled module, and it
is checked with pyright in strict mode. pythonVersion is pinned to 3.8 so the
checker enforces the same floor as the wheel.
from magical_py import FileKind
# Narrowed to FileKind after the None check.
kind: FileKind | None = detect("photo.jpg")
if kind is not None:
reveal_type(kind) # FileKind
reveal_type(kind.mime) # str | None
Development
Managed with uv.
uv sync # create the venv and install maturin, pytest, pyright
uv run maturin develop # build the extension into the venv
uv run pytest # run the tests
uv run pyright # type check in strict mode
uv build # build the wheel and the sdist
python/magical_py/_kinds.py is generated. Edit the metadata table in
gen_kinds.ps1 and re-run it; tests/test_drift.py fails if the enum and the
Rust detection table disagree.
License
MIT, the same as magical_rs.
Metadata
Release files for magical-py 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| magical_py-0.1.0.tar.gz | 69.8 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| magical_py-0.1.0-cp38-abi3-win_amd64.whl | CPython 3.8 | abi3 | Windows x86-64 | Details |
| magical_py-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| magical_py-0.1.0-cp38-abi3-macosx_11_0_arm64.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64 | Details |
| magical_py-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl | CPython 3.8 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 3.2 MB
Release files / magical_py-0.1.0.tar.gz
| Download URL | magical_py-0.1.0.tar.gz |
|---|---|
| Size | 69.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
550ff0def0aab6026a512f6a42180c534a6ae8ca81de577808db50d1f3f71a8d
|
|
BLAKE2b-256 checksum How to use checksums |
e0829e79413259f6c0a2a63847d7e6242531a379f0632b60874a8a7db68dbecf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / magical_py-0.1.0-cp38-abi3-win_amd64.whl
| Download URL | magical_py-0.1.0-cp38-abi3-win_amd64.whl |
|---|---|
| Size | 187.9 kB |
| Tags | CPython 3.8 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
604f3a49dca667d11cedfa32b8c83458e33e48a2fe1eced1dbd6044daf214c8f
|
|
BLAKE2b-256 checksum How to use checksums |
4ebd79a6755b9442faa19be250862c18f57f37b349ce959cc0f4c9cf5552a532
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / magical_py-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | magical_py-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 2.2 MB |
| Tags | CPython 3.8 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
ba50027074db8d87b4fd25ff4f977181d8c0ade7438c77a6a3707648084eee1d
|
|
BLAKE2b-256 checksum How to use checksums |
aa98c6e0d433ffa5e1b87edebf9a75b7fa2c4bdab105e0e274bf9ace835aabc4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / magical_py-0.1.0-cp38-abi3-macosx_11_0_arm64.whl
| Download URL | magical_py-0.1.0-cp38-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 348.1 kB |
| Tags | CPython 3.8 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e5dddc53e6311b978902bc19f995e61fd8596412ae869b320d4eb44332303cd0
|
|
BLAKE2b-256 checksum How to use checksums |
33bde52a560257e27e82539c374fb125af070b36c4f7b3ef728dc62c42bf2fd5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / magical_py-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl
| Download URL | magical_py-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 325.5 kB |
| Tags | CPython 3.8 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
8112f31c7751fc9bbda0b39d6b611c8cd8bdaad02815999e975126648694b5f0
|
|
BLAKE2b-256 checksum How to use checksums |
fdaa8fad39c16859cb6af0a9a7275641cd0bd350ddc88d16f2ba20085437aff1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.20 {"installer":{"name":"uv","version":"0.12.20","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|