Skip to content

Commit 39bedc2

Browse files
Add hexhash and binhash
1 parent b294339 commit 39bedc2

File tree

5 files changed

+93
-0
lines changed

5 files changed

+93
-0
lines changed

.pylintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
docstring-min-length = 4
44
# Let us name parameters of function type ``f`` and type parameters ``T``.
55
variable-rgx = [a-z_][a-z0-9_]*$
6+
argument-rgx = [a-z_][a-z0-9_]*$
67
# fixme: Warns if we leave a TODO or FIXME in the code. These are tech debt
78
# markers that may not have urgent priority.
89
# redefined-outer-name: Warns if a local variable shadows a global variable.

.style.yapf

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[style]
2+
based_on_style = google
3+
dedent_closing_brackets = true

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ toml = "^0.10.0"
3232
yapf = "^0.27.0"
3333
dataclasses = "^0.6.0"
3434
pytest-asyncio = "^0.10.0"
35+
hypothesis = "^4.24"
3536

3637
[build-system]
3738
requires = ["poetry>=0.12"]

tests/test_hash.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# pylint: disable=missing-docstring
2+
3+
from string import printable
4+
5+
from hypothesis import assume, given
6+
from hypothesis.strategies import (
7+
none,
8+
booleans,
9+
floats,
10+
text,
11+
lists,
12+
dictionaries,
13+
recursive,
14+
)
15+
16+
from typeclasses.hash import hexhash
17+
18+
json = recursive( # pylint: disable=invalid-name
19+
none() | booleans() | floats() | text(printable),
20+
lambda children: lists(children, 1) |
21+
dictionaries(text(printable), children, min_size=1),
22+
)
23+
24+
25+
@given(json)
26+
def test_reflexive(value):
27+
assert hexhash(value) == hexhash(value)
28+
29+
30+
@given(json, json)
31+
def test_injective(a, b):
32+
assume(a != b)
33+
assert hexhash(a) != hexhash(b)
34+
35+
36+
def test_dict_literal_order():
37+
a = {'a': 1, 'b': 2}
38+
b = {'b': 2, 'a': 1}
39+
assert hexhash(a) == hexhash(b)
40+
41+
42+
def test_dict_insert_order():
43+
a = {}
44+
a['a'] = 1
45+
a['b'] = 2
46+
47+
b = {}
48+
b['b'] = 2
49+
b['a'] = 1
50+
51+
assert hexhash(a) == hexhash(b)

typeclasses/hash.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""A method for content hashing values."""
2+
3+
import hashlib
4+
import operator
5+
import pickle
6+
import typing as t
7+
8+
from typeclasses import typeclass
9+
10+
T = t.TypeVar('T') # pylint: disable=invalid-name
11+
12+
13+
@typeclass(T)
14+
def add_bytes(stream, value: T):
15+
"""Serialize a value to a byte stream."""
16+
stream.update(pickle.dumps(value))
17+
18+
19+
@add_bytes.instance(t.Mapping, protocol=True)
20+
def _add_bytes_mapping(stream, mapping):
21+
# We have to canonicalize the order of keys.
22+
items = sorted(mapping.items(), key=operator.itemgetter(0))
23+
for k, v in items:
24+
add_bytes(stream, k)
25+
add_bytes(stream, v)
26+
27+
28+
def binhash(value, algorithm=hashlib.sha256):
29+
stream = algorithm()
30+
add_bytes(stream, value)
31+
return stream.digest()
32+
33+
34+
def hexhash(value, algorithm=hashlib.sha256):
35+
stream = algorithm()
36+
add_bytes(stream, value)
37+
return stream.hexdigest()

0 commit comments

Comments
 (0)