Skip to content

Commit

Permalink
Add hashing on TrackedModel user-editable content.
Browse files Browse the repository at this point in the history
  • Loading branch information
stuaxo committed Jul 27, 2022
1 parent fafaf84 commit 14b0920
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 3 deletions.
8 changes: 5 additions & 3 deletions common/business_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from typing import Iterator
from typing import Mapping
from typing import Optional
from typing import Set
from typing import Type
from typing import Union

Expand Down Expand Up @@ -57,7 +56,7 @@ def __init__(self, model: TrackedModel, message: Optional[str] = None):
super().__init__(message, model)


ALL_RULES: Set[Type[BusinessRule]] = set()
ALL_RULES: Dict[str, Type[BusinessRule]] = {}


class BusinessRuleBase(type):
Expand Down Expand Up @@ -106,7 +105,10 @@ def __new__(cls, name, bases, attrs, **kwargs):
getattr(new_class, "__doc__", None),
)

ALL_RULES.add(new_class)
assert (
name not in ALL_RULES
), f"Business Rules Must Have Unique Names: {new_class} == {ALL_RULES[name]}"
ALL_RULES[name] = new_class

return new_class

Expand Down
10 changes: 10 additions & 0 deletions common/models/tracked_qs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

from hashlib import sha256
from typing import List

from django.db.models import Case
Expand Down Expand Up @@ -350,3 +351,12 @@ def follow_path(self, path: str) -> TrackedModelQuerySet:
qs = model_type.objects.none()

return qs.distinct()

def content_hash(self):
"""
:return: Combined sha256 hash for all contained TrackedModels.
"""
sha = sha256()
for o in self:
sha.update(o.content_hash())
return sha.digest()
21 changes: 21 additions & 0 deletions common/models/trackedmodel.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

from hashlib import sha256
from json import dumps
from typing import Any
from typing import Dict
from typing import Iterable
Expand Down Expand Up @@ -658,3 +660,22 @@ def get_url_pattern_name_prefix(cls):
if not prefix:
prefix = cls._meta.verbose_name.replace(" ", "_")
return prefix

def content_hash(self):
"""
Hash of the user editable content, used by business rule checks for
result caching.
:return: 32 character sha256 'digest', see hashlib.sha256.
"""
content = {
field.name: str(getattr(self, field.name)) for field in self.copyable_fields
}

# The json encoder ensures a somewhat regular format and everything
# passed to it must be hashable.
hashable = dumps(content).encode("utf-8")

sha = sha256()
sha.update(hashable)
return sha.digest()
2 changes: 2 additions & 0 deletions pii-ner-exclude.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1148,3 +1148,5 @@ self.linked_model
WorkBasketOutputFormat
param kwargs:
Enum
sha256
hashlib.sha256

0 comments on commit 14b0920

Please sign in to comment.