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
KeyErrorunlessignore_unhashableis set.
coola.hashing.BaseHasher ¶
Bases: ABC, Generic[T]
Base class for type-specific hashers.
This abstract base class defines the interface for hashers that compute a hash value for a specific data type. Each concrete hasher implementation handles a specific data type and knows how to hash its nested elements recursively.
Notes
Subclasses must implement the hash method to define how
their specific type should be hashed.
Example
>>> from coola.hashing import StrHasher, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = StrHasher()
>>> hasher
StrHasher()
>>> len(hasher.hash([1, 2, 3], registry=registry))
64
coola.hashing.BaseHasher.hash
abstractmethod
¶
hash(
data: T,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Hash the given data structure recursively.
This method traverses the data structure and computes a hash value for it. The registry is used to resolve appropriate hashers for nested data types encountered during traversal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
T
|
The data structure to hash. Must be of the type |
required |
registry
|
HasherRegistry
|
The hasher registry used to look up hashers for nested data structures of different types. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive, since each byte of the digest encodes as two hex characters. |
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A string representing the hash of the input data. |
Example
>>> from coola.hashing import StrHasher, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = StrHasher()
>>> len(hasher.hash("Meowwww", registry=registry))
64
coola.hashing.BytesHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, BaseHasher[bytes | bytearray]
Hasher for bytes and bytearray objects.
This hasher computes the hash of the bytes namespaced by the type
tag (bytes or bytearray), so a bytes and a bytearray
with the same content hash differently.
Example
>>> from coola.hashing import BytesHasher, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = BytesHasher()
>>> hasher
BytesHasher()
>>> len(hasher.hash(b"hello", registry=registry))
64
>>> len(hasher.hash(b"hello", registry=registry, length=16))
16
coola.hashing.BytesHasher.hash ¶
hash(
data: bytes | bytearray,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of bytes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
bytes | bytearray
|
The input bytes to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry. Unused by this hasher since
bytes are hashed directly with no need to dispatch to
another hasher for nested/composite data; accepted
only to satisfy the common |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
Unused by this hasher; accepted only to
satisfy the common |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.DataclassHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, BaseHasher[Any]
Hasher for dataclass instances.
The hash combines the qualified class name with the hash of a
mapping from field name to field value, with every value hashed
through the registry. Because dataclasses have no common base
class, this hasher is not registered by default: register it for
each dataclass type with register_hashers.
Example
>>> from dataclasses import dataclass
>>> from coola.hashing import DataclassHasher, get_default_registry
>>> @dataclass
... class Point:
... x: int
... y: int
...
>>> registry = get_default_registry()
>>> hasher = DataclassHasher()
>>> hasher
DataclassHasher()
>>> len(hasher.hash(Point(1, 2), registry=registry))
64
coola.hashing.DataclassHasher.hash ¶
hash(
data: Any,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of a dataclass instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The dataclass instance to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry used to hash the field values. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive. Defaults to 64. |
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
KeyError
|
If a field value has a type for which no hasher
is registered in |
ValueError
|
If |
coola.hashing.DatetimeHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, BaseHasher[date | time]
Hasher for :class:datetime.date, :class:datetime.datetime
and :class: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 :class:datetime.datetime is a subclass of :class: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 not normalised: its
offset is part of the ISO string.
Example
>>> from datetime import date
>>> from coola.hashing import DatetimeHasher, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = DatetimeHasher()
>>> hasher
DatetimeHasher()
>>> len(hasher.hash(date(2021, 1, 1), registry=registry))
64
coola.hashing.DatetimeHasher.hash ¶
hash(
data: date | time,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of a date/datetime.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
date | time
|
The date or datetime to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry. Unused by this hasher since
the ISO string is hashed directly with no need to
dispatch to another hasher for nested data; accepted
only to satisfy the common |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
Unused by this hasher; accepted only to
satisfy the common |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.EnumHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, BaseHasher[Enum]
Hasher for :class: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, HasherRegistry
>>> class Color(Enum):
... RED = 1
...
>>> registry = HasherRegistry()
>>> hasher = EnumHasher()
>>> hasher
EnumHasher()
>>> len(hasher.hash(Color.RED, registry=registry))
64
coola.hashing.EnumHasher.hash ¶
hash(
data: Enum,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of an enum member.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Enum
|
The enum member to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry. Unused; accepted only to
satisfy the common |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
Unused; accepted only to satisfy the
common |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.HashableHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, BaseHasher[SupportsHash]
Hasher for objects that implement their own hash method.
This hasher delegates to the object's own hash method, passing
through the registry so the object can recursively hash its
nested data via registry the same way any other hasher would.
It works for any object matching the SupportsHash protocol,
regardless of its concrete type.
Example
>>> from coola.hashing import HashableHasher, get_default_registry
>>> class MyClass:
... def __init__(self, value):
... self._value = value
... def hash(self, registry=None, length=64, ignore_unhashable=False):
... if registry is None:
... registry = get_default_registry()
... return registry.hash(
... self._value, length=length, ignore_unhashable=ignore_unhashable
... )
...
>>> registry = get_default_registry()
>>> hasher = HashableHasher()
>>> hasher
HashableHasher()
>>> len(hasher.hash(MyClass(42), registry=registry))
64
coola.hashing.HashableHasher.hash ¶
hash(
data: SupportsHash,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash by delegating to the object's
own hash method.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
SupportsHash
|
The object to hash. Must implement the
|
required |
registry
|
HasherRegistry
|
The hasher registry, forwarded to |
required |
length
|
int
|
The desired length of the returned hex string.
Forwarded to |
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A string representing the hash of |
str
|
returned by |
str
|
class name of |
Raises:
| Type | Description |
|---|---|
AttributeError
|
If |
coola.hashing.Hasher ¶
Bases: Protocol[T_contra]
Structural interface of a type-specific hasher.
Any object with a compatible hash method can be registered
without subclassing :class:BaseHasher, which remains available as
a convenience base class.
coola.hashing.HasherRegistry ¶
Bases: BaseTypeDispatchRegistry[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, V] | 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(
(state): TypeRegistry(
(<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 :meth:find: find the appropriate
hasher for a given type.
See :meth:BaseTypeDispatchRegistry.find for the full behavior
description (MRO resolution, caching, and the KeyError on no
match).
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 default hasher if no match 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 :meth:has: check if a hasher is
explicitly registered for the given type.
See :meth:BaseTypeDispatchRegistry.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 by recursively traversing its structure.
This is the main entry point for hashing. It automatically:
- Determines the data's type.
- Finds the appropriate hasher via
find_hasher. - Delegates to that hasher's
hashmethod, which recursively processes any nested structures.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
object
|
The data to hash. Can be a nested structure such as
a |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive, since each byte of the digest encodes as two hex characters. |
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A string representing the hash of the input data. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no hasher is registered for |
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.MappingHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Mapping[Any, Any]]
Hasher for mapping types.
This hasher hashes each key and value separately via the registry, concatenates the key and value hashes per item, sorts the items by key hash, concatenates all per-item strings, and hashes the result under a type tag.
Sorting by key hash (not by key) ensures that 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 collections.abc import Mapping
>>> from coola.hashing import MappingHasher, StrHasher, HasherRegistry
>>> registry = HasherRegistry({object: StrHasher(), Mapping: MappingHasher()})
>>> hasher = MappingHasher()
>>> hasher
MappingHasher(tag='mapping', ordered=False)
>>> len(hasher.hash({"a": 1, "b": 2}, registry=registry))
64
coola.hashing.MappingHasher.hash ¶
hash(
data: Mapping[Any, Any],
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of a mapping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Mapping[Any, Any]
|
The mapping to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry used to hash each key and value, and to recurse into any nested data structures. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive. Defaults to 64. |
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If a key or value has a type for which no hasher
is registered in |
ValueError
|
If |
coola.hashing.PathHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, BaseHasher[PurePath]
Hasher for pathlib.PurePath and pathlib.Path objects.
This hasher delegates to hash_path, which hashes 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, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = PathHasher()
>>> hasher
PathHasher()
>>> len(hasher.hash(Path("/tmp/file.txt"), registry=registry))
64
coola.hashing.PathHasher.hash ¶
hash(
data: PurePath,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of a path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
PurePath
|
The path to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry. Unused by this hasher since
the path is hashed directly with no need to dispatch to
another hasher for nested data; accepted only to
satisfy the common |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
Unused by this hasher; accepted only to
satisfy the common |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.PydanticModelHasher ¶
Bases: InlineDisplayMixin, BaseHasher[BaseModel]
Hasher for pydantic BaseModel objects.
This hasher computes a stable content hash of a pydantic model from
its qualified class name and its fields, with every field value
hashed through the registry - see hash_pydantic_model for
details. Nested models found directly in fields or in plain
containers (dict, list, tuple, set, frozenset)
are hashed with the same on_secret policy.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
on_secret
|
Literal['reveal', 'exclude', 'error']
|
How |
'error'
|
Example
>>> from pydantic import BaseModel
>>> from coola.hashing import PydanticModelHasher, get_default_registry
>>> class Point(BaseModel):
... x: int
... y: int
...
>>> registry = get_default_registry()
>>> hasher = PydanticModelHasher()
>>> hasher
PydanticModelHasher(on_secret='error')
>>> len(hasher.hash(Point(x=1, y=2), registry=registry))
64
coola.hashing.PydanticModelHasher.hash ¶
hash(
data: BaseModel,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of a pydantic model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
BaseModel
|
The pydantic model instance to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry used to hash the field values. |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
KeyError
|
If a field value has a type for which no hasher
is registered in |
coola.hashing.ReprHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, 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 such as NumPy scalars) use the
builtin repr and the builtin type name as tag, so they hash like
the equivalent builtin value. Negative zero is normalised to zero,
since -0.0 == 0.0. Enum members are hashed with
hash_enum instead. 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.
Example
>>> from coola.hashing import ReprHasher, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = ReprHasher()
>>> hasher
ReprHasher()
>>> len(hasher.hash(1234, registry=registry))
64
coola.hashing.ReprHasher.hash ¶
hash(
data: Any,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of repr(data).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The object to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry. Unused by this hasher since
the |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
Unused by this hasher; accepted only to
satisfy the common |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.ResolvedPathHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, 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, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = ResolvedPathHasher()
>>> hasher
ResolvedPathHasher()
>>> len(hasher.hash(Path("file.txt"), registry=registry))
64
coola.hashing.ResolvedPathHasher.hash ¶
hash(
data: Path,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of the resolved path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Path
|
The path to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry. Unused; accepted only to
satisfy the common |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
Unused; accepted only to satisfy the
common |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.SequenceHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Sequence[Any]]
Hasher for sequence types.
This hasher computes the hash of each item in the sequence recursively using the registry, concatenates the intermediate hash strings, and then hashes the concatenated result under a type tag. 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, StrHasher, HasherRegistry
>>> registry = HasherRegistry({object: StrHasher(), Sequence: SequenceHasher()})
>>> hasher = SequenceHasher()
>>> hasher
SequenceHasher(tag='sequence')
>>> len(hasher.hash([1, 2, 3], registry=registry))
64
coola.hashing.SequenceHasher.hash ¶
hash(
data: Sequence[Any],
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of a sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Sequence[Any]
|
The sequence to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry used to hash each item, and to recurse into any nested data structures. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive. Defaults to 64. |
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If an item has a type for which no hasher is
registered in |
ValueError
|
If |
coola.hashing.SetHasher ¶
Bases: InlineDisplayMixin, BaseHasher[Set[Any]]
Hasher for set types.
This hasher hashes each item via the registry, sorts the item hashes, concatenates them, and hashes the result under a type tag. Sorting by item hash (not by item) makes the result 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, StrHasher, HasherRegistry
>>> registry = HasherRegistry({object: StrHasher()})
>>> hasher = SetHasher()
>>> hasher
SetHasher(tag='set')
>>> len(hasher.hash({1, 2, 3}, registry=registry))
64
coola.hashing.SetHasher.hash ¶
hash(
data: Set[Any],
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of a set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Set[Any]
|
The set to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry used to hash each item, and to recurse into any nested data structures. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive. Defaults to 64. |
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If an item has a type for which no hasher is
registered in |
ValueError
|
If |
coola.hashing.StrHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, 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.
Example
>>> from coola.hashing import StrHasher, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = StrHasher()
>>> hasher
StrHasher()
>>> len(hasher.hash("hello", registry=registry))
64
coola.hashing.StrHasher.hash ¶
hash(
data: Any,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of str(data).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The object to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry. Unused by this hasher since
the |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
Unused by this hasher; accepted only to
satisfy the common |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.StringHasher ¶
Bases: InlineDisplayMixin, NoArgsDisplayMixin, BaseHasher[str]
Hasher for string objects.
This hasher computes the hash of the string, namespaced by the
str type tag, without any intermediate conversion. str
subclasses hash like plain strings, except enum members, which hash
through hash_enum.
Example
>>> from coola.hashing import StringHasher, HasherRegistry
>>> registry = HasherRegistry()
>>> hasher = StringHasher()
>>> hasher
StringHasher()
>>> len(hasher.hash("Meowwwwww", registry=registry))
64
coola.hashing.StringHasher.hash ¶
hash(
data: str,
registry: HasherRegistry,
*,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Compute a deterministic hash of a string.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
str
|
The input string to hash. |
required |
registry
|
HasherRegistry
|
The hasher registry. Unused by this hasher since
strings are hashed directly with no need to dispatch to
another hasher for nested/composite data; accepted
only to satisfy the common |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
ignore_unhashable
|
bool
|
Unused by this hasher; accepted only to
satisfy the common |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.SupportsHash ¶
Bases: Protocol
Implement a protocol to represent objects with a hash
method.
coola.hashing.SupportsHash.hash ¶
hash(
registry: HasherRegistry | None = None,
length: int = 64,
ignore_unhashable: bool = False,
) -> str
Return a hash uniquely identifying this object.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
HasherRegistry | None
|
The registry used to recursively hash the
object's nested data. If |
None
|
length
|
int
|
The length of the returned hash. |
64
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
The hash of this object. |
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.hash_bytes ¶
hash_bytes(value: bytes, length: int = 64) -> str
Generate a hexadecimal hash of bytes using BLAKE2b.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
bytes
|
The input bytes to hash. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive, since each byte of the digest encodes as two hex characters. Defaults to 64 (32-byte digest). |
64
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
>>> from coola.hashing import hash_bytes
>>> len(hash_bytes(b"hello", length=16))
16
coola.hashing.hash_enum ¶
hash_enum(member: Enum, length: int = 64) -> str
Compute a hash of an enum member.
The hash depends on the qualified name of the enum class and on the
member name, not on the member value, so it is stable and does not
collapse IntEnum/StrEnum members into int/str.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
member
|
Enum
|
The enum member to hash. |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Example
>>> from enum import Enum
>>> from coola.hashing import hash_enum
>>> class Color(Enum):
... RED = 1
...
>>> len(hash_enum(Color.RED))
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.
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 |
|---|---|
KeyError
|
If |
ValueError
|
If |
Example
>>> from coola.hashing import hash_object
>>> len(hash_object({"a": 1, "b": "abc"}))
64
coola.hashing.hash_path ¶
hash_path(
path: PurePath, length: int = 64, resolve: bool = False
) -> str
Compute a stable hash string for a path, based on its POSIX string representation.
By default the path is hashed as written, without any filesystem
access. With resolve=True the path is resolved first (following
symlinks, collapsing ./.. segments, making it absolute), so
that two differently written but logically equivalent paths hash
the same. If resolution fails (e.g. permission error, OS-level
issue), falls back to hashing the unresolved path as given.
Uses as_posix() rather than str(path) so the hash is
consistent across platforms (POSIX vs Windows separators).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PurePath
|
The path to hash. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive. Defaults to 64. |
64
|
resolve
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
coola.hashing.hash_pydantic_model ¶
hash_pydantic_model(
model: BaseModel,
*,
length: int = 64,
on_secret: Literal[
"reveal", "exclude", "error"
] = "error",
registry: HasherRegistry | None = None,
ignore_unhashable: bool = False,
) -> str
Compute a stable content hash of a pydantic model.
The hash combines the qualified class name of the model, so two different model classes with the same field values hash differently, with the hash of a mapping from field name to field value (extra fields included). Every value is hashed through the registry, so it follows the same rules as any other data (type sensitivity, order-insensitive sets, ...), and the hash is stable regardless of field declaration order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
BaseModel
|
The pydantic model instance to hash. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive. Defaults to 64. |
64
|
on_secret
|
Literal['reveal', 'exclude', 'error']
|
How |
'error'
|
registry
|
HasherRegistry | None
|
The registry used to hash the field values. If
|
None
|
ignore_unhashable
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
str
|
The hex digest of the hash, as a string. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
KeyError
|
If a field value has a type for which no hasher is
registered in |
Example
>>> from pydantic import BaseModel, SecretStr
>>> from coola.hashing import hash_pydantic_model
>>> class Creds(BaseModel):
... username: str
... password: SecretStr
...
>>> creds = Creds(username="alice", password="hunter2")
>>> hash_pydantic_model(creds, on_secret="exclude") == hash_pydantic_model(
... Creds(username="alice", password="different"), on_secret="exclude"
... )
True
coola.hashing.hash_string ¶
hash_string(value: str, length: int = 64) -> str
Generate a hexadecimal hash of a string using BLAKE2b.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
The input string to hash. |
required |
length
|
int
|
The desired length of the returned hex string. Must be an even number between 2 and 128 inclusive, since each byte of the digest encodes as two hex characters. Defaults to 64 (32-byte digest). |
64
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Example
>>> from coola.hashing import hash_string
>>> len(hash_string("hello", length=16))
16
coola.hashing.hash_tagged ¶
hash_tagged(tag: str, value: str, length: int = 64) -> str
Compute a hash of value namespaced by a type tag.
The tag is mixed into the digest input so that values of different
types that have the same textual form (e.g. 1 and '1', or
an empty list and an empty string) do not collide.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str
|
The type tag, e.g. |
required |
value
|
str
|
The text to hash. |
required |
length
|
int
|
The desired length of the returned hex string. See
|
64
|
Returns:
| Type | Description |
|---|---|
str
|
A lowercase hexadecimal string of exactly |
str
|
characters. |
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()})