Skip to content

Commit 81606f4

Browse files
authored
Merge pull request #1021 from compas-dev/data-hash
Data hash value
2 parents 8b52dd8 + b014ad7 commit 81606f4

File tree

2 files changed

+52
-9
lines changed

2 files changed

+52
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
### Added
1212

1313
* Added `Polyline.extend`, `Polyline.extended`, `Polyline.shorten`, `Polyline.shortened`.
14+
* Added `Data.sha256` for computing a hash value of data objects, for example for comparisons during version control.
1415

1516
### Changed
1617

src/compas/data/data.py

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import json
7+
import hashlib
78
from uuid import uuid4
89
from copy import deepcopy
910

@@ -112,12 +113,12 @@ def __init__(self, name=None):
112113

113114
def __getstate__(self):
114115
"""Return the object data for state serialization with older pickle protocols."""
115-
return {'__dict__': self.__dict__, 'dtype': self.dtype, 'data': self.data}
116+
return {"__dict__": self.__dict__, "dtype": self.dtype, "data": self.data}
116117

117118
def __setstate__(self, state):
118119
"""Assign a deserialized state to the object data to support older pickle protocols."""
119-
self.__dict__.update(state['__dict__'])
120-
self.data = state['data']
120+
self.__dict__.update(state["__dict__"])
121+
self.data = state["data"]
121122

122123
@property
123124
def DATASCHEMA(self):
@@ -133,18 +134,22 @@ def JSONSCHEMANAME(self):
133134
def JSONSCHEMA(self):
134135
"""dict : The schema of the JSON representation of the data of this object."""
135136
if not self._JSONSCHEMA:
136-
schema_filename = '{}.json'.format(self.JSONSCHEMANAME.lower())
137-
schema_path = os.path.join(os.path.dirname(__file__), 'schemas', schema_filename)
138-
with open(schema_path, 'r') as fp:
137+
schema_filename = "{}.json".format(self.JSONSCHEMANAME.lower())
138+
schema_path = os.path.join(
139+
os.path.dirname(__file__), "schemas", schema_filename
140+
)
141+
with open(schema_path, "r") as fp:
139142
self._JSONSCHEMA = json.load(fp)
140143
return self._JSONSCHEMA
141144

142145
@property
143146
def jsondefinitions(self):
144147
"""dict : Reusable schema definitions."""
145148
if not self._jsondefinitions:
146-
schema_path = os.path.join(os.path.dirname(__file__), 'schemas', 'compas.json')
147-
with open(schema_path, 'r') as fp:
149+
schema_path = os.path.join(
150+
os.path.dirname(__file__), "schemas", "compas.json"
151+
)
152+
with open(schema_path, "r") as fp:
148153
self._jsondefinitions = json.load(fp)
149154
return self._jsondefinitions
150155

@@ -153,13 +158,16 @@ def jsonvalidator(self):
153158
"""jsonschema.Draft7Validator : JSON schema validator for draft 7."""
154159
if not self._jsonvalidator:
155160
from jsonschema import RefResolver, Draft7Validator
161+
156162
resolver = RefResolver.from_schema(self.jsondefinitions)
157163
self._jsonvalidator = Draft7Validator(self.JSONSCHEMA, resolver=resolver)
158164
return self._jsonvalidator
159165

160166
@property
161167
def dtype(self):
162-
return '{}/{}'.format('.'.join(self.__class__.__module__.split('.')[:2]), self.__class__.__name__)
168+
return "{}/{}".format(
169+
".".join(self.__class__.__module__.split(".")[:2]), self.__class__.__name__
170+
)
163171

164172
@property
165173
def data(self):
@@ -321,6 +329,7 @@ def validate_data(self):
321329
322330
"""
323331
import schema
332+
324333
try:
325334
data = self.DATASCHEMA.validate(self.data)
326335
except schema.SchemaError as e:
@@ -342,6 +351,7 @@ def validate_json(self):
342351
343352
"""
344353
import jsonschema
354+
345355
jsonstring = json.dumps(self.data, cls=DataEncoder)
346356
jsondata = json.loads(jsonstring, cls=DataDecoder)
347357
try:
@@ -350,3 +360,35 @@ def validate_json(self):
350360
print("Validation against the JSON schema of this object failed.")
351361
raise e
352362
return jsonstring
363+
364+
def sha256(self, as_string=False):
365+
"""Compute a hash of the data for comparison during version control using the sha256 algorithm.
366+
367+
Parameters
368+
----------
369+
as_string : bool, optional
370+
If True, return the digest in hexadecimal format rather than as bytes.
371+
372+
Returns
373+
-------
374+
bytes | str
375+
376+
Examples
377+
--------
378+
>>> from compas.datastructures import Mesh
379+
>>> mesh = Mesh.from_obj(compas.get('faces.obj'))
380+
>>> v1 = mesh.sha256()
381+
>>> v2 = mesh.sha256()
382+
>>> mesh.vertex_attribute(mesh.vertex_sample(1)[0], 'z', 1)
383+
>>> v3 = mesh.sha256()
384+
>>> v1 == v2
385+
True
386+
>>> v2 == v3
387+
False
388+
389+
"""
390+
h = hashlib.sha256()
391+
h.update(self.jsonstring.encode())
392+
if as_string:
393+
return h.hexdigest()
394+
return h.digest()

0 commit comments

Comments
 (0)