Source code for pcapkit.utilities.chardet

# -*- coding: utf-8 -*-
"""Character Set Detection
=============================

.. module:: pcapkit.utilities.chardet

:mod:`pcapkit.utilities.chardet` wraps `chardet`_ with a bounded cache, for
turning the bytes of a text field into a :obj:`str`.

.. _chardet: https://chardet.readthedocs.io

"""

import functools

import chardet

__all__ = ['detect']

#: How many distinct bytestrings :func:`detect` will remember. Bounded so that a
#: capture full of never-repeating text cannot retain all of it.
DETECT_CACHE_SIZE = 1024


[docs] @functools.lru_cache(maxsize=DETECT_CACHE_SIZE) def detect(value: 'bytes') -> 'str': """Detect the character set of ``value``. :func:`chardet.detect` is a pure function of the bytes handed to it, and the single most expensive step in turning a text field into a :obj:`str`. The strings a capture presents repeat heavily -- an HTTP-heavy capture asked for the encoding of ``b'Connection'`` once per message and got the same answer every time -- so the verdict is memoised rather than recomputed. The result is by construction the one :func:`chardet.detect` would have returned. Note: The cache is bounded by entry *count*, not by size, and it holds the bytes it was keyed on: :func:`functools.lru_cache` caches an argument's *hash* but still keeps the argument, since a :obj:`dict` needs the key to settle equality on a hash collision. Measured, feeding 20 distinct 1 MB values retains 19.1 MB. :data:`DETECT_CACHE_SIZE` therefore caps the entries rather than the footprint, which matters because :meth:`ProtocolBase.decode <pcapkit.protocols.protocol.ProtocolBase.decode>` is public and a caller may hand it a whole payload. Use :meth:`detect.cache_clear <functools.lru_cache.cache_clear>` to release it in a long-running process. Two alternatives were tried and rejected. Keying on a *prefix* is unsound, since :func:`chardet.detect` is statistical over the whole sequence: an ASCII header followed by a UTF-8, Latin-1 or CP1251 body is detected as ``ascii`` from its first 256 octets and correctly otherwise, three disagreements in six realistic cases. Keying on a digest bounds the footprint exactly and was measured retaining 0.0 MB for the same 19 MB of input, but it cannot be expressed with :func:`~functools.lru_cache` -- which keys on what it is passed -- and hand-rolling the eviction was judged not worth the six lines. Args: value: Bytestring whose encoding is to be detected. Returns: Name of the detected encoding, or ``'utf-8'`` where detection declines to name one. """ return chardet.detect(value)['encoding'] or 'utf-8'