commit fb98a2c044aea25d776002300f72871e408b4394 Author: Damian Johnson atagar@torproject.org Date: Wed Aug 8 15:21:34 2018 -0700
Simplify hash caching
Ahh. On reflection there's no need for each class to handle its own hash caching. There's a couple spots where they should due to being special snowflakes, but generally our hash_attr() can do this. --- stem/__init__.py | 12 ++---------- stem/client/cell.py | 44 ++++++++++++++++++++++++++++++-------------- stem/client/datatype.py | 18 +++--------------- stem/directory.py | 12 ++---------- stem/exit_policy.py | 6 +----- stem/manual.py | 12 ++---------- stem/response/__init__.py | 3 ++- stem/response/events.py | 6 +----- stem/util/__init__.py | 11 ++++++++++- stem/version.py | 6 +----- 10 files changed, 54 insertions(+), 76 deletions(-)
diff --git a/stem/__init__.py b/stem/__init__.py index 1679b0a0..f95d1b5e 100644 --- a/stem/__init__.py +++ b/stem/__init__.py @@ -572,13 +572,9 @@ class Endpoint(object):
self.address = address self.port = int(port) - self._hash = None
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'address', 'port') - - return self._hash + return stem.util._hash_attr(self, 'address', 'port', cache = True)
def __eq__(self, other): return hash(self) == hash(other) if isinstance(other, Endpoint) else False @@ -597,13 +593,9 @@ class ORPort(Endpoint): def __init__(self, address, port, link_protocols = None): super(ORPort, self).__init__(address, port) self.link_protocols = link_protocols - self._hash = None
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'link_protocols', parent = Endpoint) - - return self._hash + return stem.util._hash_attr(self, 'link_protocols', parent = Endpoint, cache = True)
class DirPort(Endpoint): diff --git a/stem/client/cell.py b/stem/client/cell.py index ec214018..12fa994c 100644 --- a/stem/client/cell.py +++ b/stem/client/cell.py @@ -95,7 +95,6 @@ class Cell(object): def __init__(self, unused = b''): super(Cell, self).__init__() self.unused = unused - self._hash = None
@staticmethod def by_name(name): @@ -253,9 +252,6 @@ class Cell(object):
raise NotImplementedError('Unpacking not yet implemented for %s cells' % cls.NAME)
- def __hash__(self): - return self._hash if self._hash else super(type(self), self).__hash__() - def __eq__(self, other): return hash(self) == hash(other) if isinstance(other, Cell) else False
@@ -294,7 +290,6 @@ class PaddingCell(Cell):
super(PaddingCell, self).__init__() self.payload = payload - self._hash = stem.util._hash_attr(self, 'payload')
def pack(self, link_protocol): return PaddingCell._pack(link_protocol, self.payload) @@ -303,6 +298,9 @@ class PaddingCell(Cell): def _unpack(cls, content, circ_id, link_protocol): return PaddingCell(content)
+ def __hash__(self): + return stem.util._hash_attr(self, 'payload', cache = True) +
class CreateCell(CircuitCell): NAME = 'CREATE' @@ -360,7 +358,6 @@ class RelayCell(CircuitCell): self.stream_id = stream_id self.digest = digest self.data = str_tools._to_bytes(data) - self._hash = stem.util._hash_attr(self, 'command_int', 'stream_id', 'digest', 'data')
if digest == 0: if not stream_id and self.command in STREAM_ID_REQUIRED: @@ -393,6 +390,9 @@ class RelayCell(CircuitCell):
return RelayCell(circ_id, command, data, digest, stream_id, recognized, unused)
+ def __hash__(self): + return stem.util._hash_attr(self, 'command_int', 'stream_id', 'digest', 'data', cache = True) +
class DestroyCell(CircuitCell): """ @@ -409,7 +409,6 @@ class DestroyCell(CircuitCell): def __init__(self, circ_id, reason = CloseReason.NONE, unused = b''): super(DestroyCell, self).__init__(circ_id, unused) self.reason, self.reason_int = CloseReason.get(reason) - self._hash = stem.util._hash_attr(self, 'circ_id', 'reason_int')
def pack(self, link_protocol): return DestroyCell._pack(link_protocol, Size.CHAR.pack(self.reason_int), self.unused, self.circ_id) @@ -419,6 +418,9 @@ class DestroyCell(CircuitCell): reason, unused = Size.CHAR.pop(content) return DestroyCell(circ_id, reason, unused)
+ def __hash__(self): + return stem.util._hash_attr(self, 'circ_id', 'reason_int', cache = True) +
class CreateFastCell(CircuitCell): """ @@ -440,7 +442,6 @@ class CreateFastCell(CircuitCell):
super(CreateFastCell, self).__init__(circ_id, unused) self.key_material = key_material - self._hash = stem.util._hash_attr(self, 'circ_id', 'key_material')
def pack(self, link_protocol): return CreateFastCell._pack(link_protocol, self.key_material, self.unused, self.circ_id) @@ -454,6 +455,9 @@ class CreateFastCell(CircuitCell):
return CreateFastCell(circ_id, key_material, unused)
+ def __hash__(self): + return stem.util._hash_attr(self, 'circ_id', 'key_material', cache = True) +
class CreatedFastCell(CircuitCell): """ @@ -479,7 +483,6 @@ class CreatedFastCell(CircuitCell): super(CreatedFastCell, self).__init__(circ_id, unused) self.key_material = key_material self.derivative_key = derivative_key - self._hash = stem.util._hash_attr(self, 'circ_id', 'derivative_key', 'key_material')
def pack(self, link_protocol): return CreatedFastCell._pack(link_protocol, self.key_material + self.derivative_key, self.unused, self.circ_id) @@ -494,6 +497,9 @@ class CreatedFastCell(CircuitCell):
return CreatedFastCell(circ_id, derivative_key, key_material, content)
+ def __hash__(self): + return stem.util._hash_attr(self, 'circ_id', 'derivative_key', 'key_material', cache = True) +
class VersionsCell(Cell): """ @@ -509,7 +515,6 @@ class VersionsCell(Cell): def __init__(self, versions): super(VersionsCell, self).__init__() self.versions = versions - self._hash = stem.util._hash_attr(self, 'versions')
def pack(self, link_protocol): payload = b''.join([Size.SHORT.pack(v) for v in self.versions]) @@ -525,6 +530,9 @@ class VersionsCell(Cell):
return VersionsCell(link_protocols)
+ def __hash__(self): + return stem.util._hash_attr(self, 'versions', cache = True) +
class NetinfoCell(Cell): """ @@ -544,7 +552,6 @@ class NetinfoCell(Cell): self.timestamp = timestamp if timestamp else datetime.datetime.now() self.receiver_address = receiver_address self.sender_addresses = sender_addresses - self._hash = stem.util._hash_attr(self, 'timestamp', 'receiver_address', 'sender_addresses')
def pack(self, link_protocol): payload = bytearray() @@ -571,6 +578,9 @@ class NetinfoCell(Cell):
return NetinfoCell(receiver_address, sender_addresses, datetime.datetime.utcfromtimestamp(timestamp), unused = content)
+ def __hash__(self): + return stem.util._hash_attr(self, 'timestamp', 'receiver_address', 'sender_addresses', cache = True) +
class RelayEarlyCell(CircuitCell): NAME = 'RELAY_EARLY' @@ -629,7 +639,6 @@ class VPaddingCell(Cell):
super(VPaddingCell, self).__init__() self.payload = payload if payload is not None else os.urandom(size) - self._hash = stem.util._hash_attr(self, 'payload')
def pack(self, link_protocol): return VPaddingCell._pack(link_protocol, self.payload) @@ -638,6 +647,9 @@ class VPaddingCell(Cell): def _unpack(cls, content, circ_id, link_protocol): return VPaddingCell(payload = content)
+ def __hash__(self): + return stem.util._hash_attr(self, 'payload', cache = True) +
class CertsCell(Cell): """ @@ -653,7 +665,6 @@ class CertsCell(Cell): def __init__(self, certs, unused = b''): super(CertsCell, self).__init__(unused) self.certificates = certs - self._hash = stem.util._hash_attr(self, 'certificates')
def pack(self, link_protocol): return CertsCell._pack(link_protocol, Size.CHAR.pack(len(self.certificates)) + b''.join([cert.pack() for cert in self.certificates]), self.unused) @@ -672,6 +683,9 @@ class CertsCell(Cell):
return CertsCell(certs, unused = content)
+ def __hash__(self): + return stem.util._hash_attr(self, 'certificates', cache = True) +
class AuthChallengeCell(Cell): """ @@ -695,7 +709,6 @@ class AuthChallengeCell(Cell): super(AuthChallengeCell, self).__init__(unused) self.challenge = challenge self.methods = methods - self._hash = stem.util._hash_attr(self, 'challenge', 'methods')
def pack(self, link_protocol): payload = bytearray() @@ -727,6 +740,9 @@ class AuthChallengeCell(Cell):
return AuthChallengeCell(methods, challenge, unused = content)
+ def __hash__(self): + return stem.util._hash_attr(self, 'challenge', 'methods', cache = True) +
class AuthenticateCell(Cell): NAME = 'AUTHENTICATE' diff --git a/stem/client/datatype.py b/stem/client/datatype.py index 32295317..5ca4e820 100644 --- a/stem/client/datatype.py +++ b/stem/client/datatype.py @@ -349,7 +349,6 @@ class Size(Field): self.name = name self.size = size self.format = pack_format - self._hash = None
@staticmethod def pop(packed): @@ -378,10 +377,7 @@ class Size(Field): return self.unpack(to_unpack), remainder
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'name', 'size', 'format') - - return self._hash + return stem.util._hash_attr(self, 'name', 'size', 'format', cache = True)
class Address(Field): @@ -404,7 +400,6 @@ class Address(Field): raise ValueError("'%s' isn't an IPv4 or IPv6 address" % value)
self.type, self.type_int = AddrType.get(addr_type) - self._hash = None
if self.type == AddrType.IPv4: if stem.util.connection.is_valid_ipv4_address(value): @@ -454,10 +449,7 @@ class Address(Field): return Address(addr_value, addr_type), content
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'type_int', 'value_bin') - - return self._hash + return stem.util._hash_attr(self, 'type_int', 'value_bin', cache = True)
class Certificate(Field): @@ -472,7 +464,6 @@ class Certificate(Field): def __init__(self, cert_type, value): self.type, self.type_int = CertType.get(cert_type) self.value = value - self._hash = None
def pack(self): cell = bytearray() @@ -493,10 +484,7 @@ class Certificate(Field): return Certificate(cert_type, cert_bytes), content
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'type_int', 'value') - - return self._hash + return stem.util._hash_attr(self, 'type_int', 'value')
class KDF(collections.namedtuple('KDF', ['key_hash', 'forward_digest', 'backward_digest', 'forward_key', 'backward_key'])): diff --git a/stem/directory.py b/stem/directory.py index 5d243707..1782fbf1 100644 --- a/stem/directory.py +++ b/stem/directory.py @@ -255,7 +255,6 @@ class Authority(Directory):
self.v3ident = v3ident self.is_bandwidth_authority = is_bandwidth_authority - self._hash = None
@staticmethod def from_cache(): @@ -315,10 +314,7 @@ class Authority(Directory): return section_lines
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'v3ident', 'is_bandwidth_authority', parent = Directory) - - return self._hash + return stem.util._hash_attr(self, 'v3ident', 'is_bandwidth_authority', parent = Directory, cache = True)
def __eq__(self, other): return hash(self) == hash(other) if isinstance(other, Authority) else False @@ -370,7 +366,6 @@ class Fallback(Directory): super(Fallback, self).__init__(address, or_port, dir_port, fingerprint, nickname, orport_v6) self.has_extrainfo = has_extrainfo self.header = OrderedDict(header) if header else OrderedDict() - self._hash = None
@staticmethod def from_cache(path = FALLBACK_CACHE_PATH): @@ -520,10 +515,7 @@ class Fallback(Directory): conf.save(path)
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'has_extrainfo', 'header', parent = Directory) - - return self._hash + return stem.util._hash_attr(self, 'has_extrainfo', 'header', parent = Directory, cache = True)
def __eq__(self, other): return hash(self) == hash(other) if isinstance(other, Fallback) else False diff --git a/stem/exit_policy.py b/stem/exit_policy.py index 0912546b..0ed5e581 100644 --- a/stem/exit_policy.py +++ b/stem/exit_policy.py @@ -1057,7 +1057,6 @@ class MicroExitPolicyRule(ExitPolicyRule): self.address = None # wildcard address self.min_port = min_port self.max_port = max_port - self._hash = None self._skip_rule = False
def is_address_wildcard(self): @@ -1073,10 +1072,7 @@ class MicroExitPolicyRule(ExitPolicyRule): return None
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'is_accept', 'min_port', 'max_port') - - return self._hash + return stem.util._hash_attr(self, 'is_accept', 'min_port', 'max_port', cache = True)
def __eq__(self, other): return hash(self) == hash(other) if isinstance(other, MicroExitPolicyRule) else False diff --git a/stem/manual.py b/stem/manual.py index 3e3d317f..7b956aa5 100644 --- a/stem/manual.py +++ b/stem/manual.py @@ -187,13 +187,9 @@ class ConfigOption(object): self.usage = usage self.summary = summary self.description = description - self._hash = None
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'name', 'category', 'usage', 'summary', 'description') - - return self._hash + return stem.util._hash_attr(self, 'name', 'category', 'usage', 'summary', 'description', cache = True)
def __eq__(self, other): return hash(self) == hash(other) if isinstance(other, ConfigOption) else False @@ -383,7 +379,6 @@ class Manual(object): self.man_commit = None self.stem_commit = None self.schema = None - self._hash = None
@staticmethod def from_cache(path = None): @@ -657,10 +652,7 @@ class Manual(object): conf.save(path)
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'name', 'synopsis', 'description', 'commandline_options', 'signals', 'files', 'config_options') - - return self._hash + return stem.util._hash_attr(self, 'name', 'synopsis', 'description', 'commandline_options', 'signals', 'files', 'config_options', cache = True)
def __eq__(self, other): return hash(self) == hash(other) if isinstance(other, Manual) else False diff --git a/stem/response/__init__.py b/stem/response/__init__.py index 54eb9c44..96436b4d 100644 --- a/stem/response/__init__.py +++ b/stem/response/__init__.py @@ -172,6 +172,7 @@ class ControlMessage(object): self._parsed_content = parsed_content self._raw_content = raw_content self._str = None + self._hash = stem.util._hash_attr(self, '_raw_content')
def is_ok(self): """ @@ -302,7 +303,7 @@ class ControlMessage(object): return ControlLine(content)
def __hash__(self): - return stem.util._hash_attr(self, '_raw_content') + return self._hash
def __eq__(self, other): return hash(self) == hash(other) if isinstance(other, ControlMessage) else False diff --git a/stem/response/events.py b/stem/response/events.py index 37b60b38..64ed250d 100644 --- a/stem/response/events.py +++ b/stem/response/events.py @@ -57,7 +57,6 @@ class Event(stem.response.ControlMessage):
self.type = str(self).split()[0] self.arrived_at = arrived_at - self._hash = None
# if we're a recognized event type then translate ourselves into that subclass
@@ -73,10 +72,7 @@ class Event(stem.response.ControlMessage): self._parse()
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'arrived_at', parent = stem.response.ControlMessage) - - return self._hash + return stem.util._hash_attr(self, 'arrived_at', parent = stem.response.ControlMessage, cache = True)
def _parse_standard_attr(self): """ diff --git a/stem/util/__init__.py b/stem/util/__init__.py index f0c2397b..91d96bcf 100644 --- a/stem/util/__init__.py +++ b/stem/util/__init__.py @@ -130,10 +130,16 @@ def _hash_attr(obj, *attributes, **kwargs):
:param Object obj: object to be hashed :param list attributes: attribute names to take into account + :param bool cache: persists hash in a '_cached_hash' object attribute :param class parent: include parent's hash value """
- parent_class = kwargs.get('parent') + is_cached = kwargs.get('cache', False) + parent_class = kwargs.get('parent', None) + cached_hash = getattr(obj, '_cached_hash', None) + + if is_cached and cached_hash is not None: + return cached_hash
my_hash = parent_class.__hash__(obj) if parent_class else 0 my_hash = my_hash * 1024 + hash(str(type(obj))) @@ -142,4 +148,7 @@ def _hash_attr(obj, *attributes, **kwargs): val = getattr(obj, attr) my_hash = my_hash * 1024 + _hash_value(val)
+ if is_cached: + setattr(obj, '_cached_hash', my_hash) + return my_hash diff --git a/stem/version.py b/stem/version.py index b6a866f0..979bcf95 100644 --- a/stem/version.py +++ b/stem/version.py @@ -182,7 +182,6 @@ class Version(object): def __init__(self, version_str): self.version_str = version_str version_parts = VERSION_PATTERN.match(version_str) - self._hash = None
if version_parts: major, minor, micro, patch, status, extra_str, _ = version_parts.groups() @@ -251,10 +250,7 @@ class Version(object): return method(my_status, other_status)
def __hash__(self): - if self._hash is None: - self._hash = stem.util._hash_attr(self, 'major', 'minor', 'micro', 'patch', 'status') - - return self._hash + return stem.util._hash_attr(self, 'major', 'minor', 'micro', 'patch', 'status', cache = True)
def __eq__(self, other): return self._compare(other, lambda s, o: s == o)