mrkeyoor.com_
Sun 20 Sept 17:49 UTC
PyPIUtilsupdated 20 Sept 2026

filetype review

filetype 1.2.0 identifies a binary format from the leading bytes of a file path, bytes object, memoryview, or readable stream. A match returns an object with extension and MIME properties; an unrecognized header returns None. The released matcher table covers common images, audio, video, archives, Office and OpenDocument files, fonts, PDF, SQLite, WebAssembly, and several executables. Version 1.2.0 added AVIF plus nine document formats, fixed DOC and DOCX recognition, moved audio checks ahead of video for M4A, and repaired stream-position restoration. Our import took 0.09 seconds, and the pure Python package had no direct dependencies.

Verdict

filetype 1.2.0 installed in 0.2 seconds and occupied 1 MB in our sandbox, with 0 dependencies and 0 audit findings; it is cheap enough for coarse binary allowlists when its fixed signature table covers the inputs. Do not install it for text detection, content safety, tenant-specific matcher state, or a project that requires current Python support metadata.

We installed it

Lab card: what happened when we installed filetypeScreenshot of filetype documentation
Install✓ · 0.2s1 package on disk · 1 MB
Importimport filetype in 0.09s · pure Python
Known vulns0(pip-audit)

Answers from our run

Does filetype install cleanly?

Yes. In a fresh container with an empty cache, pip install filetype finished in 0.2s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does filetype need to run?

Python 3.x, and nothing compiled: it is pure Python. In our run import filetype succeeded in 0.09s.

filetype or python-magic: which should you use?

python-magic: Use it when libmagic is available and its larger system signature database or encoding output is required. filetype 1.2.0 installed in 0.2 seconds and occupied 1 MB in our sandbox, with 0 dependencies and 0 audit findings; it is cheap enough for coarse binary allowlists when its fixed signature table covers the inputs.

When should you not use filetype?

Most inputs are JSON, CSV, XML, HTML, SVG, YAML, or source code. The built-in table targets binary signatures and generally returns None for those text formats.

API stability4/5The 1.2.0 API is small: guess, MIME and extension helpers, family-specific matchers, supported-type lookup, and add_type. Its release fixed readable-stream behavior and changed matcher precedence without replacing those entry points. That makes integrations easy to pin. Ambiguous signatures remain order-dependent, and a newly inserted custom matcher can alter answers process-wide even when callers did not change.
Docs3/5The README lists every released extension and MIME pair, shows path-based guessing, describes custom matchers, and links to an annotated API site that returns HTTP 200. One operational detail is wrong: the README states that 261 bytes are enough, while released utils.py reads 8,192 bytes. The docs also leave empty-input handling, non-seekable stream consumption, and global matcher priority for readers to infer from source.
Maintenance2/5PyPI's latest artifact is 1.2.0 from November 2, 2022. GitHub is unarchived and shows a May 2, 2025 push, 770 stars, and 65 open issues and pull requests. Commits after the release add or fix formats such as QOI, JPEG XL, DDS, and animated AVIF, but none of that work is in the current PyPI wheel. Source activity exists; delivery to package users has stalled.
Ecosystem4/5PyPI Stats counted 10,161,209 downloads in the latest week, and GitHub shows 770 stars. One pure Python package with no direct dependencies is simple to embed in upload handlers and document queues. The tradeoff is a closed built-in table: downstream users either accept its coverage, mutate the global list, or move to libmagic when they need a broader shared signature database.

Use it if

  • You need a quick MIME-family check on an uploaded binary without trusting its filename or client-supplied Content-Type.
  • The deployment cannot provide libmagic, and a pure Python signature table covers every accepted format.
  • You can read only the opening range of an object-store file and want a cheap first classification.
  • A private binary format has a precise header and can be added through one custom Type matcher at process startup.
Skip it if

Setup reality

Our filetype 1.2.0 install finished in 0.2 seconds inside a fresh Python 3.12 Bookworm container. It left one package and 1 MB on disk, with 0 direct dependencies and 0 known vulnerabilities from pip-audit. import filetype succeeded in 0.09 seconds. The wheel is pure Python and MIT licensed. PyPI leaves the supported Python version unspecified, and the distribution has no py.typed marker.

No service, credential, or config file is involved. Pass guess() a path, bytes, bytearray, memoryview, or an object with read(). It returns the first matching Type or None. Empty input also returns None, so callers that care about zero-byte uploads must distinguish them before guessing. A nonexistent path raises its normal I/O exception. This is a classifier, not a parser or malware check.

The README says the first 261 bytes are sufficient, but version 1.2.0's released utils.py reads up to 8,192 bytes. Use bytes 0 through 8191 for an HTTP or object-store range request if you want behavior consistent with a local path. For a seekable stream, the release records its current offset, seeks to the start, reads the signature window, and then seeks back. Non-seekable streams are consumed.

Matcher order affects the answer because match() stops at the first success. Version 1.2.0 moved audio matchers before video to correct M4A detection. add_type() puts a custom Type at index 0 of the shared list, giving it priority over built-ins. Register each custom matcher once during startup. The repository contains newer format work after 1.2.0, but PyPI users do not receive those commits until another package release appears.

Patterns

Classify a file on disk identify-file-path

import filetype

kind = filetype.guess('uploads/item.bin')
if kind is None:
    raise ValueError('unrecognized binary header')

print(kind.extension)
print(kind.mime)

None means that no released matcher accepted the bytes. A missing or unreadable path raises an I/O exception instead.

Classify an in-memory header identify-byte-prefix

import filetype

with open('upload.bin', 'rb') as source:
    prefix = source.read(8192)

kind = filetype.guess(prefix)
mime = kind.mime if kind else None

Version 1.2.0 reads at most 8,192 bytes in its code, despite the README's smaller 261-byte claim.

Apply an upload MIME allowlist allow-image-mimes

import filetype

ALLOWED_MIMES = {'image/jpeg', 'image/png', 'image/webp'}

def accept_image(data):
    kind = filetype.guess(data)
    if kind is None or kind.mime not in ALLOWED_MIMES:
        raise ValueError('unsupported image format')
    return kind

A header match catches simple filename lies. Decode the image with an image library when malformed content is part of the threat model.

Report empty and unknown data separately separate-empty-input

import filetype

def classify(data):
    if len(data) == 0:
        return {'status': 'empty'}
    kind = filetype.guess(data)
    if kind is None:
        return {'status': 'unknown'}
    return {'status': 'matched', 'mime': kind.mime}

guess() returns None for both a falsy input and nonempty bytes that fail every matcher. Check length first when those cases differ.

Search only image signatures match-one-family

from filetype import image_match

kind = image_match(header)
if kind is None:
    raise ValueError('header is not a supported image')
print(kind.extension)

image_match() skips audio, video, archive, document, font, and application matchers but returns the same Type object as guess().

Route one match by MIME prefix route-by-family

import filetype

kind = filetype.guess(header)
if kind is None:
    reject_unknown()
elif kind.mime.startswith('image/'):
    enqueue_thumbnail()
elif kind.mime.startswith('video/'):
    enqueue_transcode()
else:
    store_without_preview()

Call guess() once when you need the MIME value. Repeated is_image() and is_video() calls scan matcher families again.

Classify a remote object from one range read-http-range

from urllib.request import Request, urlopen
import filetype

request = Request(url, headers={'Range': 'bytes=0-8191'})
with urlopen(request) as response:
    header = response.read(8192)

kind = filetype.guess(header)

The server may ignore Range and return a full response. Cap the local read at 8,192 bytes and enforce your own response and timeout limits.

Inspect a stream without changing its offset preserve-seekable-stream

import filetype

upload.seek(128)
before = upload.tell()
kind = filetype.guess(upload)
after = upload.tell()
assert after == before

Version 1.2.0 seeks to byte 0 and restores the saved position when tell() and seek() exist. A non-seekable readable is consumed.

Check whether a MIME is in the table lookup-mime-support

import filetype

kind = filetype.get_type(mime='image/avif')
if kind is None:
    raise RuntimeError('AVIF matcher is unavailable')
print(kind.extension)

get_type() searches declared MIME and extension properties. It does not inspect a file or prove that a supplied MIME is correct.

Audit the active signature table list-built-in-types

import filetype

active = [
    {'extension': kind.extension, 'mime': kind.mime}
    for kind in filetype.types
]
for item in active:
    print(item)

filetype.types is the live module-level list. Custom registrations appear at its front and affect later calls in the process.

Add a private four-byte format register-custom-signature

import filetype
from filetype.types import Type

class AcmeBlob(Type):
    def __init__(self):
        super().__init__('application/x-acme-blob', 'acme')

    def match(self, buf):
        return len(buf) >= 4 and bytes(buf[:4]) == b'ACME'

filetype.add_type(AcmeBlob())

add_type() inserts at position 0, ahead of every built-in matcher. Run this once during startup and make the signature narrow.

Follow signature matching with real decoding verify-image-payload

from io import BytesIO
import filetype
from PIL import Image

kind = filetype.guess(data)
if kind is None or not kind.mime.startswith('image/'):
    raise ValueError('unsupported header')

with Image.open(BytesIO(data)) as image:
    image.verify()

filetype only recognizes the header window. Pillow's verify() checks image structure, though separate resource limits are still needed for hostile uploads.

Alternatives

PackageRegistryPick it when
python-magicPyPIUse it when libmagic is available and its larger system signature database or encoding output is required.
puremagicPyPIUse it for a Python-only detector that can return several candidate matches rather than one first match.
mimetypes-magicPyPIUse it when you need the python-magic API from a separately maintained distribution and can install libmagic.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.