python-magic
python-magic is a ctypes wrapper around the operating system's libmagic library, the same file-identification engine used by the Unix `file` command. It identifies content from a path, byte buffer, or file descriptor and can return a human description, MIME type, encoding, likely extensions, or information from inside compressed files. The Python wheel contains the wrapper, not libmagic or its signature database.
Choose python-magic when you specifically want libmagic and can own the native dependency. For portable wheels, reproducible results, or security-sensitive file acceptance, a pure-Python detector or format-specific parser is usually the calmer fit.
Use it if
- You already deploy libmagic and want the same content-based identification as the file command from Python
- You need MIME guesses from file headers rather than trusting a user-supplied filename extension
- You need to inspect buffers, open descriptors, compressed content, or a custom magic database
- You can tolerate platform-dependent descriptions and pin the operating-system libmagic package in deployment
- You need a pure-Python installation: the README states that the libmagic C library must be installed separately, and Windows needs a matching DLL architecture
- You need identical output on every machine: results depend on the installed libmagic version and magic database, so descriptions and MIME choices can vary between images and operating systems
- You treat detected MIME type as a security guarantee: libmagic examines signatures and heuristics, but hostile or polyglot files still need parser-level validation and sandboxing
- You need frequent PyPI releases: 0.4.27 was uploaded in June 2022 even though repository work continued in 2026, leaving published users without those later changes
- You already import the Python bindings shipped by libmagic: the README warns that both packages use the `magic` module name and documents a compatibility layer because the APIs conflict
Setup reality
`pip install python-magic` installs a pure Python wheel, but import still fails unless the native libmagic shared library and its magic database are available. On Debian or Ubuntu the README names `libmagic1`; on macOS it names Homebrew's `libmagic` or MacPorts' `file`. Minimal containers commonly need an extra OS-package layer. Windows is more awkward: the README points to `python-magic-bin` as a bundled alternative and warns that Python and the libmagic DLL must have matching 32-bit or 64-bit architecture. A `WindowsError: [Error 193]` usually means they do not match, while `MagicException: could not find any magic files` means the library cannot locate its database and may need an explicit `magic_file` path. The distribution imports as `magic`, which collides with the different Python bindings shipped by some libmagic packages. Detection quality depends on how many bytes are available; the README recommends at least the first 2,048 bytes for buffer checks because smaller samples can be wrong. Do not read an entire untrusted upload just to classify it. The convenience functions cache Magic instances, while the README cautions against sharing a manually created Magic instance across threads, so use per-thread instances when concurrency matters. `uncompress=True` inspects compressed payloads and increases CPU, memory, and decompression-bomb exposure. Pin both the Python package and OS-level libmagic version if reproducible output matters, and test the exact production image rather than only a developer laptop.
Patterns
Get a human-readable file descriptiondescribe-file
import magic
description = magic.from_file('document.pdf')
print(description)The exact text can vary with the installed libmagic version and signature database; do not persist it as a stable enum.
Detect a file's MIME typedetect-mime-file
import magic
mime_type = magic.from_file('document.pdf', mime=True)
assert mime_type == 'application/pdf'Content detection is stronger than trusting the extension, but it is still heuristic and not sufficient authorization for opening an upload.
Classify an upload from its leading bytesdetect-mime-buffer
import magic
with open('upload.bin', 'rb') as stream:
sample = stream.read(2048)
mime_type = magic.from_buffer(sample, mime=True)The README recommends at least 2,048 bytes because shorter buffers can produce incorrect identification.
Identify an already open filedetect-file-descriptor
import magic
with open('archive.tar', 'rb') as stream:
description = magic.from_descriptor(stream.fileno())Keep the descriptor open for the call. Descriptor behavior depends on what the native libmagic build supports on the platform.
Detect MIME type and character encodingdetect-encoding
import magic
detector = magic.Magic(mime=True, mime_encoding=True)
result = detector.from_file('notes.txt')
print(result)Encoding identification is heuristic. Decode with explicit error handling rather than assuming the reported charset is always correct.
Look inside compressed contentinspect-compressed-file
import magic
detector = magic.Magic(uncompress=True)
description = detector.from_file('payload.gz')Uncompressing hostile input costs extra CPU and memory and can expose decompression-bomb risk; use resource limits around untrusted files.
Load a specific magic databaseuse-custom-database
import magic
detector = magic.Magic(
mime=True,
magic_file='/opt/app/share/misc/magic.mgc',
)
mime_type = detector.from_file('sample.bin')Pinning the database improves reproducibility, but the file must be compatible with the deployed libmagic build and readable by the process.
Ask libmagic for likely extensionslist-likely-extensions
import magic
detector = magic.Magic(extension=True)
extensions = detector.from_file('unknown.bin').split('/')The source raises NotImplementedError when the installed libmagic is too old for extension reporting. Results are suggestions, not a safe rename policy.
Keep more than the first signature matchkeep-all-matches
import magic
detector = magic.Magic(keep_going=True)
print(detector.from_file('ambiguous.bin'))Multiple matches are libmagic-formatted text, not a structured Python list, and formatting can differ by native library version.
Create one detector per worker threadthread-local-detector
from threading import local
import magic
_state = local()
def detector():
if not hasattr(_state, 'magic'):
_state.magic = magic.Magic(mime=True)
return _state.magicThe README cautions that a Magic instance should not be shared across multiple threads; thread-local instances avoid that coupling.
Surface libmagic failures clearlyhandle-native-errors
import magic
try:
mime_type = magic.from_file('/srv/uploads/item')
except (OSError, magic.MagicException) as error:
raise RuntimeError('file identification unavailable') from errorMissing files can raise an OS error, while loading or native identification failures use MagicException. Import itself can fail when libmagic is absent.
Report the native libmagic versioncheck-libmagic-version
import magic
print('libmagic version:', magic.version())Record this alongside the OS image when debugging output differences; the Python package version alone does not identify the detection engine.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| puremagic | PyPI | You need signature-based detection without installing a system C library |
| filetype | PyPI | You want a small pure-Python detector for common binary formats and accept a narrower signature set |
| mimetypes-magic | PyPI | You want a maintained libmagic wrapper distribution with bundled platform-oriented packaging choices |