Hashing
coola.hashing ¶
Provide deterministic hashing functions.
These are content hashes for identity and caching, not for security: do not use them for passwords, signatures or integrity against an attacker.
Hashes are deterministic across calls, processes and machines, and follow these rules:
- Every hash is domain-separated by a type tag, so values of different
types never share a hash even if their text form is the same
(
1,1.0,Trueand"1"are all distinct). - Ordered containers (
list,tuple,OrderedDict) are order-sensitive and unordered containers (dict,set) are not, like incoolaequality. Each container family has its own tag, so alistand atuplewith the same items hash differently. - Container hashes are built only from the hashes of their children,
resolved through a
HasherRegistry. - Unsupported types raise
TypeNotRegisteredError(aKeyError) unlessignore_unhashableis set. - A container that contains itself raises
ValueError. - The output is a lowercase hex string of
lengthcharacters (an even number from 2 to 128, default 64).
coola.hashing.BaseHasher ¶
Bases: ABC, Generic[T]
Base class for type-specific hashers.
A hasher describes the content of a type, it does not hash. The rules of the contract are:
encodeis pure and cheap: it does not call the registry, does not recurse, does not modifydataand does not depend onid(), onhash()or on the iteration order of a set or of a mapping (an unorderedNodesays so instead).- It returns
bytes(a leaf, seetagged) or aNode. - The children of a
Nodeare encoded by their own hasher. - The tag is a constant of the hasher, except for the user-defined classes whose qualified name is the identity. A subclass therefore hashes like its base.
- An exception propagates unchanged.
Example
>>> from coola.hashing import BaseHasher, Node, hash_object, register_hashers
>>> class Point:
... def __init__(self, x, y):
... self.x, self.y = x, y
...
>>> class PointHasher(BaseHasher[Point]):
... def encode(self, data):
... return Node("Point", [data.x, data.y])
...
>>> register_hashers({Point: PointHasher()})
>>> hash_object(Point(1, 2)) == hash_object(Point(1, 2))
True
>>> hash_object(Point(1, 2)) == hash_object(Point(2, 1))
False
coola.hashing.BaseHasher.encode
abstractmethod
¶
encode(data: T) -> Encoding
Describe the content of data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
T
|
The value to describe. Must be of the type |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The bytes of a leaf ( |
Encoding
|
|
Encoding
|
container. |
Example
>>> from coola.hashing import StringHasher
>>> StringHasher().encode("abc")
b'str\x00abc'
coola.hashing.BytesHasher ¶
Bases: InlineDisplayMixin, BaseHasher[bytes | bytearray | memoryview]
Hasher for bytes, bytearray and memoryview objects.
This hasher computes the hash of the bytes namespaced by the type
tag (bytes, bytearray or memoryview), so a bytes, a
bytearray and a memoryview with the same content hash
differently.
Example
>>> from coola.hashing import BytesHasher
>>> hasher = BytesHasher()
>>> hasher
BytesHasher()
>>> len(hasher.encode(b"hello"))
70
coola.hashing.BytesHasher.encode ¶
encode(data: bytes | bytearray | memoryview) -> Encoding
Describe a bytes, a bytearray or a memoryview.
The content is reduced with digest so that the encoding is
small, whatever the size of the buffer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes | bytearray | memoryview
|
The bytes to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf |
coola.hashing.ContentHasher ¶
Bases: InlineDisplayMixin, BaseHasher[SupportsCoolaHash]
Hasher for objects that implement __coola_hash__.
The object is described by the tag custom:module.Class and the
value of __coola_hash__, which the engine hashes through the
registry. The qualified class name is part of the tag, so two
classes with the same content hash differently. The default registry
uses it as a fallback for every class that defines the method and has
no hasher of its own.
Example
>>> from coola.hashing import ContentHasher, hash_object
>>> class Point:
... def __init__(self, x, y):
... self.x, self.y = x, y
... def __coola_hash__(self):
... return (self.x, self.y)
...
>>> hasher = ContentHasher()
>>> hasher
ContentHasher()
>>> hasher.encode(Point(1, 2)).children
[(1, 2)]
>>> hash_object(Point(1, 2)) == hash_object(Point(1, 2))
True
coola.hashing.ContentHasher.encode ¶
encode(data: SupportsCoolaHash) -> Encoding
Describe an object by its content.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
SupportsCoolaHash
|
The object to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
Encoding
|
value of |
coola.hashing.DataclassHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Any]
Hasher for dataclass instances.
The hash combines the qualified class name with a mapping from
field name to field value, with every value hashed through the
registry. Only the fields with compare=True are
hashed, like in objects_are_equal, so two equal dataclasses have
the same hash. Because dataclasses have no common base
class, it cannot be registered for a type: the default registry
uses it as a fallback (see HasherRegistry.register_fallback)
for every dataclass that has no hasher of its own.
Example
>>> from dataclasses import dataclass
>>> from coola.hashing import DataclassHasher
>>> @dataclass
... class Point:
... x: int
... y: int
...
>>> hasher = DataclassHasher()
>>> hasher
DataclassHasher()
>>> hasher.encode(Point(1, 2)).ordered
False
coola.hashing.DataclassHasher.encode ¶
encode(data: Any) -> Encoding
Describe a dataclass instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The dataclass instance to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The unordered |
Encoding
|
fields with |
Encoding
|
in the tag. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
coola.hashing.DatetimeHasher ¶
Bases: InlineDisplayMixin, BaseHasher[date | time]
Hasher for datetime.date, datetime.datetime
and datetime.time objects.
This hasher converts the object to its ISO 8601 string representation
via isoformat() and hashes that string under a tag made of the
concrete class name.
Since datetime.datetime is a subclass of datetime.date,
both types are handled by this hasher. The class name is part of the
tag, so a date and a datetime never produce the same hash,
even for the same calendar day.
Aware datetimes are converted to UTC first, so equal instants in
different time zones hash the same, like they compare equal. Naive
and aware datetimes differ. An aware time is normalised the same
way, like time.__eq__ does: the offset is subtracted from the time
of day (without wrapping around midnight) and the payload is the
resulting number of microseconds. A naive time uses its ISO string.
Example
>>> from datetime import date
>>> from coola.hashing import DatetimeHasher
>>> hasher = DatetimeHasher()
>>> hasher
DatetimeHasher()
>>> hasher.encode(date(2021, 1, 1))
b'date\x002021-01-01'
coola.hashing.DatetimeHasher.encode ¶
encode(data: date | time) -> Encoding
Describe a date, a datetime or a time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
date | time
|
The value to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf |
Encoding
|
|
Encoding
|
microseconds in UTC for an aware time). |
coola.hashing.EnumHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Enum]
Hasher for enum.Enum members.
The hash depends on the qualified class name and the member name,
not on the member value. IntEnum and StrEnum members are
therefore not collapsed into int and str.
Example
>>> from enum import Enum
>>> from coola.hashing import EnumHasher
>>> class Color(Enum):
... RED = 1
...
>>> hasher = EnumHasher()
>>> hasher
EnumHasher()
>>> hasher.encode(Color.RED).startswith(b"Enum")
True
coola.hashing.EnumHasher.encode ¶
encode(data: Enum) -> Encoding
Describe an enum member.
The encoding depends on the qualified name of the enum class and
on the member name, not on the member value, so it does not
collapse IntEnum/StrEnum members into int/str.
A combined flag has no name: its value is used instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Enum
|
The enum member to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf |
coola.hashing.Hasher ¶
Bases: Protocol[T_contra]
Structural interface of a type-specific hasher.
Any object with a compatible encode method can be registered
without subclassing BaseHasher, which remains available as a
convenience base class.
coola.hashing.Hasher.encode ¶
encode(data: T_contra) -> Encoding
Describe an object for hashing.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
T_contra
|
The object to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
coola.hashing.HasherRegistry ¶
Bases: TypeRegistry[Hasher[Any]]
Registry that manages and dispatches hashers based on data type.
This registry maintains a mapping from Python types to hasher instances and uses the Method Resolution Order (MRO) for type lookup. When hashing data, it automatically selects the most specific registered hasher for the data's type, falling back to parent types or a default hasher if needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_state
|
dict[type, T] | None
|
Optional initial mapping of types to hashers. If provided, the state is copied to prevent external mutations. |
None
|
Example
Basic usage with a sequence hasher:
>>> from coola.hashing import HasherRegistry, SequenceHasher, StrHasher
>>> registry = HasherRegistry({object: StrHasher(), list: SequenceHasher()})
>>> registry
HasherRegistry(
(<class 'object'>): StrHasher()
(<class 'list'>): SequenceHasher(tag='sequence')
)
>>> len(registry.hash([1, 2, 3]))
64
Registering custom hashers:
>>> from coola.hashing import HasherRegistry, SequenceHasher, StrHasher
>>> registry = HasherRegistry({object: StrHasher()})
>>> registry.register(list, SequenceHasher())
>>> len(registry.hash([1, 2, 3]))
64
Working with nested structures:
>>> from coola.hashing import get_default_registry
>>> registry = get_default_registry()
>>> data = {"a": [1, 2], "b": [3, 4]}
>>> len(registry.hash(data))
64
coola.hashing.HasherRegistry.find_hasher ¶
Type-specific alias for find: find the appropriate
hasher for a given type.
See TypeRegistry.resolve for the full behavior
description (MRO resolution, caching, and the KeyError on no
match). If no hasher is registered for the type or its parent
types, the fallbacks (see register_fallback) are tried.
A subclass of Enum resolves to the hasher of Enum unless
a hasher is registered for the enum class itself (or for a
parent that is not a builtin base of the mixin). A member of
class C(bytes, Enum) would otherwise hash as the plain
value, like a member of a different enum with the same value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_type
|
type
|
The Python type to find a hasher for. |
required |
Returns:
| Type | Description |
|---|---|
Hasher[Any]
|
The most specific registered hasher for this type, resolved |
Hasher[Any]
|
via MRO, or the hasher of the first matching fallback. |
Raises:
| Type | Description |
|---|---|
TypeNotRegisteredError
|
If no hasher is found. |
Example
>>> from collections.abc import Sequence
>>> from coola.hashing import HasherRegistry, SequenceHasher, StrHasher
>>> registry = HasherRegistry({object: StrHasher()})
>>> registry.register(Sequence, SequenceHasher())
>>> hasher = registry.find_hasher(list)
>>> hasher
StrHasher()
coola.hashing.HasherRegistry.has_hasher ¶
has_hasher(data_type: type) -> bool
Type-specific alias for has: check if a hasher is
explicitly registered for the given type.
See Registry.has for the full behavior
description.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data_type
|
type
|
The type to check. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
Example
>>> from coola.hashing import HasherRegistry, SequenceHasher
>>> registry = HasherRegistry()
>>> registry.register(list, SequenceHasher())
>>> registry.has_hasher(list)
True
>>> registry.has_hasher(tuple)
False
coola.hashing.HasherRegistry.hash ¶
hash(
data: object,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Hash the given data.
This is the registry-level entry point for hashing. It finds the
hasher of each object with find_hasher and walks the nested
structure with the engine (see coola.hashing.engine). The
hash is the first length characters of the 128-character
hex digest, so a hash is the start of the longer ones.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
The data to hash. Can be a nested structure such as
a |
required |
length
|
int
|
The length of the returned hex string: an even
|
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A string representing the hash of the input data. |
Raises:
| Type | Description |
|---|---|
TypeNotRegisteredError
|
If no hasher is registered for
the type of |
ValueError
|
If |
Example
>>> from coola.hashing import get_default_registry
>>> registry = get_default_registry()
>>> len(registry.hash({"scores": [95, 87, 92], "name": "test"}))
64
coola.hashing.JaxArrayHasher ¶
Bases: InlineDisplayMixin, BaseHasher['jax.Array']
Hasher for jax.Array objects.
The hash depends on the dtype, the shape and the values, like
NumpyArrayHasher, but a JAX array and a NumPy array with the
same content hash differently. -0.0 hashes like 0.0.
Example
>>> import jax.numpy as jnp
>>> from coola.hashing import HasherRegistry, JaxArrayHasher
>>> hasher = JaxArrayHasher()
>>> hasher
JaxArrayHasher()
>>> hasher.encode(jnp.arange(3)).startswith(b"jax.Array")
True
coola.hashing.JaxArrayHasher.encode ¶
encode(data: Array) -> Encoding
Describe a JAX array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Array
|
The array to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf that describes |
Encoding
|
the digest of the canonical values. |
coola.hashing.MappingHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Mapping[Any, Any]]
Hasher for mapping types.
This hasher describes a mapping as a Node whose children are
the keys and the values, which the engine hashes through the
registry. The entries (a key and its value) are sorted by digest,
not by key, so mappings with the same key-value pairs but different
insertion orders produce the same hash, without requiring keys to be
orderable. This matches
coola equality, which ignores the order of the keys of a
dict. Register the hasher with ordered=True for a mapping
type whose order is meaningful, like OrderedDict: its equality
is order-sensitive too.
The tag identifies the mapping family, so a dict and an
OrderedDict with the same items hash differently.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str
|
The type tag mixed into the hash. Defaults to
|
'mapping'
|
ordered
|
bool
|
If |
False
|
Example
>>> from coola.hashing import MappingHasher
>>> hasher = MappingHasher()
>>> hasher
MappingHasher(tag='mapping', ordered=False)
>>> hasher.encode({"a": 1}).tag
'mapping'
coola.hashing.MappingHasher.encode ¶
encode(data: Mapping[Any, Any]) -> Encoding
Describe a mapping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Mapping[Any, Any]
|
The mapping to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
Encoding
|
...) with |
Encoding
|
built with |
coola.hashing.Node
dataclass
¶
Describe a container: a tag and the children to hash.
Each child is encoded by its own hasher, found by the registry, so
the children can be any value, including values built on the spot
(a list of field names, a NumPy array, a str dtype).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str
|
The constant tag of the hasher. |
required |
children
|
Iterable[Any]
|
The values to hash. They are consumed once, lazily. |
required |
ordered
|
bool
|
If |
True
|
group
|
int
|
The number of consecutive children that form one entry
of an unordered node (a mapping uses |
1
|
Example
>>> from coola.hashing import Node
>>> node = Node("Point", [1, 2])
>>> node.tag
'Point'
coola.hashing.NumpyArrayHasher ¶
Bases: InlineDisplayMixin, BaseHasher['np.ndarray']
Hasher for numpy.ndarray objects.
The hash depends on the dtype (with its byte order), the shape and
the values, not on the memory layout: a non-contiguous view hashes
like its contiguous copy. -0.0 hashes like 0.0, like in the
equality. The items of an object array are hashed through the
registry. For a masked array, the mask is part of the hash and the
masked values are not.
Example
>>> import numpy as np
>>> from coola.hashing import HasherRegistry, NumpyArrayHasher
>>> hasher = NumpyArrayHasher()
>>> hasher
NumpyArrayHasher()
>>> hasher.encode(np.arange(3)).startswith(b"numpy.ndarray")
True
coola.hashing.NumpyArrayHasher.encode ¶
encode(data: ndarray) -> Encoding
Describe a NumPy array.
A numeric array is a leaf: the dtype, the shape and the digest of
the canonical values (and of the mask for a masked array). An
object array is a Node whose children are the dtype, the
shape and the items, which the engine hashes through the registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
ndarray
|
The array to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf or the |
coola.hashing.PandasDataFrameHasher ¶
Bases: InlineDisplayMixin, BaseHasher['pd.DataFrame']
Hasher for pandas.DataFrame objects.
The hash depends on the column labels (and the name of the columns
index), the index (its names, its dtype and its values) and, for
each column, its dtype and its values (see PandasSeriesHasher).
Example
>>> import pandas as pd
>>> from coola.hashing import PandasDataFrameHasher
>>> hasher = PandasDataFrameHasher()
>>> hasher
PandasDataFrameHasher()
>>> hasher.encode(pd.DataFrame({"a": [1, 2]})).tag
'pandas.DataFrame'
coola.hashing.PandasDataFrameHasher.encode ¶
encode(data: DataFrame) -> Encoding
Describe a pandas data frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data frame to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
Encoding
|
column, its dtype and its values. |
coola.hashing.PandasSeriesHasher ¶
Bases: InlineDisplayMixin, BaseHasher['pd.Series']
Hasher for pandas.Series objects.
The hash depends on the name, the dtype, the index (its names, its
dtype and its values) and the values. For a categorical dtype, the
categories and the ordered flag are part of the dtype.
The values are hashed as a NumPy array, so the elements of an
object column are hashed through the registry. The values of
the other extension dtypes are converted to an object array in
which a missing value is None.
Example
>>> import pandas as pd
>>> from coola.hashing import PandasSeriesHasher
>>> hasher = PandasSeriesHasher()
>>> hasher
PandasSeriesHasher()
>>> hasher.encode(pd.Series([1, 2, 3])).tag
'pandas.Series'
coola.hashing.PandasSeriesHasher.encode ¶
encode(data: Series) -> Encoding
Describe a pandas series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Series
|
The series to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
Encoding
|
values. |
coola.hashing.PathHasher ¶
Bases: InlineDisplayMixin, BaseHasher[PurePath]
Hasher for pathlib.PurePath and pathlib.Path objects.
This hasher describes the POSIX string representation of the path
as written. It never touches the
filesystem and does not depend on the current working directory, so
hashes are portable across machines. A relative path and the
absolute path of the same location therefore hash differently; use
ResolvedPathHasher to hash the real location instead.
Example
>>> from pathlib import Path
>>> from coola.hashing import PathHasher
>>> hasher = PathHasher()
>>> hasher
PathHasher()
>>> hasher.encode(Path("/tmp/file.txt"))
b'Path\x00/tmp/file.txt'
coola.hashing.PathHasher.encode ¶
encode(data: PurePath) -> Encoding
Describe a path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
PurePath
|
The path to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf ``tagged("Path", posix_form). |
coola.hashing.PolarsDataFrameHasher ¶
Bases: InlineDisplayMixin, BaseHasher['pl.DataFrame']
Hasher for polars.DataFrame objects.
The hash depends on the column names, the dtypes and the values.
Example
>>> import polars as pl
>>> from coola.hashing import PolarsDataFrameHasher
>>> hasher = PolarsDataFrameHasher()
>>> hasher
PolarsDataFrameHasher()
>>> hasher.encode(pl.DataFrame({"a": [1, 2]})).tag
'polars.DataFrame'
coola.hashing.PolarsDataFrameHasher.encode ¶
encode(data: DataFrame) -> Encoding
Describe a polars data frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
The data frame to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
coola.hashing.PolarsSeriesHasher ¶
Bases: InlineDisplayMixin, BaseHasher['pl.Series']
Hasher for polars.Series objects.
The hash depends on the name, the dtype and the values, null
included.
Example
>>> import polars as pl
>>> from coola.hashing import PolarsSeriesHasher
>>> hasher = PolarsSeriesHasher()
>>> hasher
PolarsSeriesHasher()
>>> hasher.encode(pl.Series("a", [1, 2, 3])).tag
'polars.Series'
coola.hashing.PolarsSeriesHasher.encode ¶
encode(data: Series) -> Encoding
Describe a polars series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Series
|
The series to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
Raises:
| Type | Description |
|---|---|
MissingPackageError
|
If |
coola.hashing.PyarrowArrayHasher ¶
Bases: InlineDisplayMixin, BaseHasher['pa.Array | pa.ChunkedArray']
Hasher for pyarrow.Array and pyarrow.ChunkedArray
objects.
The hash depends on the type and the values, null included. The
chunk layout of a ChunkedArray is not part of the hash. The
integer, floating-point and boolean arrays are hashed from their
NumPy form (-0.0 and NaN are canonicalized like in NumPy) when
NumPy is installed, so they are as fast as NumPy arrays; the other
types are hashed through Python objects.
Example
>>> import pyarrow as pa
>>> from coola.hashing import PyarrowArrayHasher
>>> hasher = PyarrowArrayHasher()
>>> hasher
PyarrowArrayHasher()
>>> hasher.encode(pa.array([1, 2, 3])).startswith(b'pyarrow.Array')
True
coola.hashing.PyarrowArrayHasher.encode ¶
encode(data: Array | ChunkedArray) -> Encoding
Describe an array.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Array | ChunkedArray
|
The array to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
A leaf (the type, the digest of the values and of the |
Encoding
|
validity) for the fixed-width types, and a |
Encoding
|
type and the Python values for the other types. |
coola.hashing.PyarrowTableHasher ¶
Bases: InlineDisplayMixin, BaseHasher['pa.Table | pa.RecordBatch']
Hasher for pyarrow.Table and pyarrow.RecordBatch
objects.
The hash depends on the column names, the types and the values.
Example
>>> import pyarrow as pa
>>> from coola.hashing import PyarrowTableHasher
>>> hasher = PyarrowTableHasher()
>>> hasher
PyarrowTableHasher()
>>> hasher.encode(pa.table({"a": [1, 2]})).tag
'pyarrow.Table'
coola.hashing.PyarrowTableHasher.encode ¶
encode(data: Table | RecordBatch) -> Encoding
Describe a table or a record batch.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Table | RecordBatch
|
The table to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
coola.hashing.PydanticModelHasher ¶
Bases: InlineDisplayMixin, BaseHasher[BaseModel]
Hasher for pydantic BaseModel objects.
The model is described by its qualified class name and its fields
(extra fields included), with every field value hashed through the
registry. The fields are unordered, so the hash does not depend on
the order of the declaration. The secrets (SecretStr and
SecretBytes) are values like the others: their hasher decides
what happens (see SecretHasher), wherever the secret is.
Example
>>> from pydantic import BaseModel
>>> from coola.hashing import PydanticModelHasher
>>> class Point(BaseModel):
... x: int
... y: int
...
>>> hasher = PydanticModelHasher()
>>> hasher
PydanticModelHasher()
>>> hasher.encode(Point(x=1, y=2)).ordered
False
coola.hashing.PydanticModelHasher.encode ¶
encode(data: BaseModel) -> Encoding
Describe a pydantic model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
BaseModel
|
The pydantic model instance to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The unordered |
Encoding
|
fields included), with the qualified class name in the tag. |
coola.hashing.ReprHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Any]
Hasher for objects whose repr() is a reliable canonical
representation.
This hasher converts the object to its repr() string and hashes
it under a type tag. Instances of bool, int, float and
complex (including subclasses) and the NumPy scalars use the
builtin repr and the builtin type name as tag, so they hash like
the equivalent builtin value (np.float32(1.5) hashes like 1.5,
np.int64(3) like 3). Negative zero is normalised to zero,
since -0.0 == 0.0. It is preferable to
StrHasher for numeric types (int, float, complex,
bool) because repr() guarantees round-trip accuracy for
floating point values, whereas str() may lose precision on some
platforms.
For the other types, the tag is the module and the qualified name of
the class by default, so a user class never shares the tag of
another class with the same name. Pass tag to register a
constant tag for a type (ReprHasher("timedelta")): a subclass
then hashes like its base.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str | None
|
The type tag used for the values that are not a |
None
|
Example
>>> from coola.hashing import ReprHasher
>>> hasher = ReprHasher()
>>> hasher
ReprHasher()
>>> hasher.encode(1234)
b'int\x001234'
coola.hashing.ReprHasher.encode ¶
encode(data: Any) -> Encoding
Describe an object by its repr().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The object to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf |
Encoding
|
|
Encoding
|
scalars) use the builtin type name as tag and the builtin |
Encoding
|
|
coola.hashing.ResolvedPathHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Path]
Hasher for pathlib.Path objects that hashes the resolved
location.
The path is resolved (following symlinks, collapsing ./..
segments, making it absolute) before hashing, so that two
differently written but logically equivalent paths hash the same.
The result depends on the filesystem and on the current working
directory, so it is not portable across machines. If resolution
fails, the unresolved path is hashed instead.
Example
>>> from pathlib import Path
>>> from coola.hashing import ResolvedPathHasher
>>> hasher = ResolvedPathHasher()
>>> hasher
ResolvedPathHasher()
>>> hasher.encode(Path("file.txt")).startswith(b"Path")
True
coola.hashing.ResolvedPathHasher.encode ¶
encode(data: Path) -> Encoding
Describe a path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Path
|
The path to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf ``tagged("Path", posix_form) of the resolved path. |
coola.hashing.SecretHasher ¶
Bases: InlineDisplayMixin, BaseHasher['pydantic.SecretStr | pydantic.SecretBytes']
Hasher for SecretStr and SecretBytes values.
A secret is a value with a hasher, so the policy is the one of the
registry and applies wherever the secret is (in a model, a dict,
a list, a dataclass...).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
on_secret
|
Literal['reveal', 'exclude', 'error']
|
What to do with a secret:
Defaults to |
_ERROR
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
>>> from pydantic import SecretStr
>>> from coola.hashing import SecretHasher
>>> hasher = SecretHasher("exclude")
>>> hasher
SecretHasher(on_secret='exclude')
>>> hasher.encode(SecretStr("a")) == hasher.encode(SecretStr("b"))
True
coola.hashing.SecretHasher.encode ¶
encode(data: SecretStr | SecretBytes) -> Encoding
Describe a secret.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
SecretStr | SecretBytes
|
The secret to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf |
Encoding
|
|
Encoding
|
|
Encoding
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.SequenceHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Sequence[Any]]
Hasher for sequence types.
This hasher describes a sequence as an ordered Node whose
children are the items, which the engine hashes through the
registry. Item order matters.
The tag identifies the sequence family, so sequences of different
families with the same items (e.g. a list and a tuple) hash
differently. Register one instance per family, e.g.
SequenceHasher("list") for list. A named tuple is tagged
with its class name and field names, so it differs from a plain
tuple and from other named tuples.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str
|
The type tag mixed into the hash. Defaults to
|
'sequence'
|
Example
>>> from coola.hashing import SequenceHasher
>>> hasher = SequenceHasher()
>>> hasher
SequenceHasher(tag='sequence')
>>> hasher.encode([1, 2, 3])
Node(tag='sequence', children=[1, 2, 3], ordered=True, group=1)
coola.hashing.SequenceHasher.encode ¶
encode(data: Sequence[Any]) -> Encoding
Describe a sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Sequence[Any]
|
The sequence to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The ordered |
Encoding
|
|
coola.hashing.SetHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Set[Any]]
Hasher for set types.
This hasher describes a set as an unordered Node whose children
are the items, which the engine hashes through the registry. The
digests are sorted, not the items, so the result is independent of
iteration order without requiring items to be orderable.
The tag identifies the set family, so a set and a frozenset
with the same items hash differently, and neither collides with a
list of the same items.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str
|
The type tag mixed into the hash. Defaults to |
'set'
|
Example
>>> from coola.hashing import SetHasher
>>> hasher = SetHasher()
>>> hasher
SetHasher(tag='set')
>>> hasher.encode({1})
Node(tag='set', children={1}, ordered=False, group=1)
coola.hashing.SetHasher.encode ¶
encode(data: Set[Any]) -> Encoding
Describe a set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Set[Any]
|
The set to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The unordered |
coola.hashing.StrHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Any]
Hasher for objects whose str() is a reliable canonical
representation.
This hasher converts the object to its str() string and then
computes the hash of that string. Unlike ReprHasher, which uses
repr(), this hasher uses str(), which is more human-readable
but does not guarantee round-trip accuracy for floating point values.
The tag identifies the type family. By default it is str: followed
by the module and the qualified name of the class, so a user class
never shares the tag of another class with the same name. Pass
tag to register a constant tag for a type (StrHasher("UUID")):
a subclass then hashes like its base.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str | None
|
The type tag mixed into the hash. If |
None
|
Example
>>> from coola.hashing import StrHasher
>>> hasher = StrHasher()
>>> hasher
StrHasher()
>>> hasher.encode("hello")
b'str:str\x00hello'
coola.hashing.StrHasher.encode ¶
encode(data: Any) -> Encoding
Describe an object by its str().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The object to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf |
coola.hashing.StringHasher ¶
Bases: InlineDisplayMixin, BaseHasher[str | UserString]
Hasher for string objects.
This hasher describes the string under the str type tag, without
any intermediate conversion. str subclasses hash like plain
strings (the enum members are dispatched to EnumHasher by the
registry). A UserString is described by its text under the
UserString tag, so it differs from a str.
Example
>>> from coola.hashing import StringHasher
>>> hasher = StringHasher()
>>> hasher
StringHasher()
>>> hasher.encode("Meowwwwww")
b'str\x00Meowwwwww'
coola.hashing.StringHasher.encode ¶
encode(data: str | UserString) -> Encoding
Describe a string.
A lone surrogate (for example in the result of os.fsdecode
for a file name that is not valid UTF-8) is encoded with the
surrogatepass error handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
str | UserString
|
The string to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf |
Encoding
|
|
coola.hashing.SupportsCoolaHash ¶
Bases: Protocol
Implement a protocol to represent objects that say what their content is.
The method returns the content, not a hash: the engine hashes it through the registry, so the object cannot bypass the protections of the engine and does not have to forward any option.
Example
>>> from coola.hashing import SupportsCoolaHash
>>> class Point:
... def __init__(self, x, y):
... self.x, self.y = x, y
... def __coola_hash__(self):
... return (self.x, self.y)
...
>>> isinstance(Point(1, 2), SupportsCoolaHash)
True
coola.hashing.SupportsCoolaHash.__coola_hash__ ¶
__coola_hash__() -> object
Return the content of this object.
Returns:
| Type | Description |
|---|---|
object
|
Any value that can be hashed, for example a tuple of the |
object
|
attributes that identify the object. |
coola.hashing.TorchTensorHasher ¶
Bases: InlineDisplayMixin, BaseHasher['torch.Tensor']
Hasher for torch.Tensor objects.
The hash depends on the dtype, the shape and the values. The device
and requires_grad are not part of the hash, so a tensor and its
copy on another device hash the same. -0.0 hashes like 0.0.
A sparse tensor is hashed from its layout, its positions and its
stored values, without a dense copy, so it does not hash like the
dense tensor with the same values. A meta tensor has no values
and raises a TypeError.
Example
>>> import torch
>>> from coola.hashing import HasherRegistry, TorchTensorHasher
>>> hasher = TorchTensorHasher()
>>> hasher
TorchTensorHasher()
>>> hasher.encode(torch.arange(3)).startswith(b"torch.Tensor")
True
coola.hashing.TorchTensorHasher.encode ¶
encode(data: Tensor) -> Encoding
Describe a PyTorch tensor.
The dense values on the CPU are described: the device, the
layout and requires_grad are not part of the encoding.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Tensor
|
The tensor to describe. |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The leaf that describes |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
coola.hashing.XarrayHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Any]
Hasher for xarray.DataArray, xarray.Dataset and
xarray.Variable objects.
The hash depends on the dimensions, the values, the coordinates and
the attributes (and on the name of a DataArray). The values are
hashed as NumPy arrays.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str
|
The type tag mixed into the hash, e.g. |
required |
Example
>>> import xarray as xr
>>> from coola.hashing import XarrayHasher
>>> hasher = XarrayHasher("DataArray")
>>> hasher
XarrayHasher(tag='DataArray')
>>> hasher.encode(xr.DataArray([1, 2], dims=["x"])).tag
'xarray.DataArray'
coola.hashing.XarrayHasher.encode ¶
encode(data: Any) -> Encoding
Describe an xarray object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The |
required |
Returns:
| Type | Description |
|---|---|
Encoding
|
The |
Encoding
|
values and the attributes for a variable; the name, the |
Encoding
|
variable and the coordinates for a data array; the |
Encoding
|
variables, the coordinates and the attributes for a dataset. |
coola.hashing.digest ¶
digest(data: bytes) -> bytes
Return the 64-byte BLAKE2b digest of the package.
A hasher uses it to reduce a big buffer (the values of an array, the
content of a bytes) to a fixed-width value before it enters the
encoding, so the engine never copies a big preimage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes
|
The bytes to digest. |
required |
Returns:
| Type | Description |
|---|---|
bytes
|
The 64-byte digest. |
Example
>>> from coola.hashing import digest
>>> len(digest(b"abc"))
64
coola.hashing.get_default_registry ¶
get_default_registry() -> HasherRegistry
Return the default global hasher registry.
The registry is created on the first call and reused on all subsequent calls (singleton pattern). It is pre-configured with hashers for common Python built-in types.
Returns:
| Type | Description |
|---|---|
HasherRegistry
|
A singleton |
HasherRegistry
|
built-in types. |
Notes
Because the registry is a singleton, modifications to it
(e.g. via register_hashers) affect all future calls to
this function. If you need an isolated registry, create a new
HasherRegistry instance directly.
Example
>>> from coola.hashing import get_default_registry
>>> registry = get_default_registry()
>>> len(registry.hash("meowwwwww"))
64
coola.hashing.get_secret_hashers ¶
get_secret_hashers(
on_secret: Literal[
"reveal", "exclude", "error"
] = _ERROR,
) -> dict[type, Hasher[Any]]
Get the hashers of SecretStr and SecretBytes for a
policy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
on_secret
|
Literal['reveal', 'exclude', 'error']
|
The policy of the hashers, see |
_ERROR
|
Returns:
| Type | Description |
|---|---|
dict[type, Hasher[Any]]
|
The mapping from |
dict[type, Hasher[Any]]
|
|
Example
>>> from pydantic import BaseModel, SecretStr
>>> from coola.hashing import get_default_registry, get_secret_hashers
>>> class Creds(BaseModel):
... user: str
... password: SecretStr
...
>>> registry = get_default_registry().copy()
>>> registry.register_many(get_secret_hashers("exclude"), exist_ok=True)
>>> registry.hash(Creds(user="a", password="x")) == registry.hash(
... Creds(user="a", password="y")
... )
True
coola.hashing.get_stable_registry ¶
get_stable_registry() -> HasherRegistry
Return the registry of the stable identifiers.
This registry only has the hashers that are built in coola. Unlike
the default registry, it does not load the coola.hashing.hashers
entry points of the installed packages and it is not changed by
register_hashers, so the identifiers computed with it depend on the
version of coola and not on the installed packages or on the code
that runs in the process. It is the registry used by
generate_stable_content_id and generate_stable_uuid5 when no
registry is given.
Returns:
| Type | Description |
|---|---|
HasherRegistry
|
A singleton |
Notes
The registry is shared by the process: do not register hashers in it. Pass your own registry to the functions that accept one to opt out of the stability.
Example
>>> from coola.hashing import get_stable_registry
>>> registry = get_stable_registry()
>>> len(registry.hash("meowwwwww"))
64
coola.hashing.hash_object ¶
hash_object(
data: object,
*,
registry: HasherRegistry | None = None,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a hash of a nested data structure.
The hash is deterministic and depends on the type of every value, on
the order of ordered containers, and not on the order of unordered
ones. For example [1, 2], (1, 2) and {1, 2} all hash
differently, while {"a": 1, "b": 2} and {"b": 2, "a": 1}
hash the same. An OrderedDict is the exception among mappings:
the order of its keys matters, so two OrderedDict with the same
items in a different order hash differently.
Note
The hash of a dataclass, a pydantic model, an enum member or an
object with a __coola_hash__ method includes the module and the
qualified name of its class. The hash changes when the class is
renamed or moved to another module, and a class defined in a
script run as __main__ hashes differently from the same
class imported. Do not persist these hashes (or the IDs derived
from them, such as generate_stable_uuid5) across such a
change.
The options are keyword-only: it was easy to write
hash_object(data, 32) and pass 32 as the registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
The data to hash. Can be a nested structure such as a
|
required |
registry
|
HasherRegistry | None
|
The registry used to resolve hashers for each data
type. If |
None
|
length
|
int
|
The desired length of the returned hex string. Must be
an even number between 2 and 128 inclusive. Defaults to
|
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A string representing the hash of the input data. |
Raises:
| Type | Description |
|---|---|
TypeNotRegisteredError
|
If |
TypeError
|
If |
ValueError
|
If |
Example
>>> from coola.hashing import hash_object
>>> len(hash_object({"a": 1, "b": "abc"}))
64
>>> len(hash_object({"a": 1, "b": "abc"}, length=16))
16
coola.hashing.register_hashers ¶
Register custom hashers into the default global registry.
This function extends the singleton registry returned by
get_default_registry with additional type-to-hasher mappings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mapping
|
Mapping[type, Hasher[Any]]
|
Mapping of Python types to hasher instances. |
required |
exist_ok
|
bool
|
If |
False
|
Raises:
| Type | Description |
|---|---|
AlreadyRegisteredError
|
If any type is already registered and |
Example
>>> from coola.hashing import register_hashers, StrHasher
>>> class MyType:
... def __init__(self, value):
... self.value = value
...
>>> register_hashers({MyType: StrHasher()})
coola.hashing.tagged ¶
tagged(tag: str, payload: str | bytes = b'') -> bytes
Return the encoding of a leaf: the tag, a NUL byte and the payload.
The text is encoded in UTF-8 with surrogatepass, so a lone
surrogate never collides with a valid string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str
|
The constant tag of the type. |
required |
payload
|
str | bytes
|
The content of the value. |
b''
|
Returns:
| Type | Description |
|---|---|
bytes
|
The bytes of the leaf. |
Example
>>> from coola.hashing import tagged
>>> tagged("int", "1")
b'int\x001'