diff options
Diffstat (limited to 'src/leap/common')
-rw-r--r-- | src/leap/common/decorators.py | 160 | ||||
-rw-r--r-- | src/leap/common/events/events.proto | 1 | ||||
-rw-r--r-- | src/leap/common/events/events_pb2.py | 17 | ||||
-rw-r--r-- | src/leap/common/mail.py | 9 | ||||
-rw-r--r-- | src/leap/common/tests/test_check.py | 1 | ||||
-rw-r--r-- | src/leap/common/tests/test_memoize.py | 76 |
6 files changed, 253 insertions, 11 deletions
diff --git a/src/leap/common/decorators.py b/src/leap/common/decorators.py new file mode 100644 index 0000000..2ef6711 --- /dev/null +++ b/src/leap/common/decorators.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- +# decorators.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Useful decorators. +""" +import collections +import datetime +import functools +import logging + +logger = logging.getLogger(__name__) + + +class _memoized(object): + """ + Decorator. + + Caches a function's return value each time it is called. + If called later with the same arguments, the cached value is returned + (not reevaluated). + """ + + # cache invalidation time, in seconds + CACHE_INVALIDATION_DELTA = 1800 + + def __init__(self, func, ignore_kwargs=None, is_method=False, + invalidation=None): + + """ + :param ignore_kwargs: If True, ignore all kwargs. + If tuple, ignore those kwargs. + :type ignore_kwargs: bool, tuple or None + :param is_method: whether the decorated function is a method. + (ignores the self argument if so). + :type is_method: True + """ + self.ignore_kwargs = ignore_kwargs if ignore_kwargs else [] + self.is_method = is_method + self.func = func + + if invalidation: + self.CACHE_INVALIDATION_DELTA = invalidation + + # TODO should put bounds to the cache dict so we do not + # consume a huge amount of memory. + self.cache = {} + self.cache_ts = {} + + def __call__(self, *args, **kwargs): + """ + Executes the call. + + :tyoe args: tuple + :type kwargs: dict + """ + def ret_or_raise(value): + """ + Returns the value except if it is an exception, + in which case it's raised. + """ + if isinstance(value, Exception): + raise value + return value + + if self.is_method: + # forget about `self` as key + key_args = args[1:] + else: + key_args = args + + if self.ignore_kwargs is True: + key = key_args + else: + key = (key_args, frozenset( + [(k, v) for k, v in kwargs.items() + if k not in self.ignore_kwargs])) + + if not isinstance(key, collections.Hashable): + # uncacheable. a list, for instance. + # better to not cache than blow up. + logger.warning("Key is not hashable, bailing out!") + return self.func(*args, **kwargs) + + if key in self.cache: + if self._is_cache_still_valid(key): + value = self.cache[key] + logger.debug("Got value from cache...") + return ret_or_raise(value) + else: + logger.debug("Cache is invalid, evaluating again...") + + # no cache, or cache invalid + try: + value = self.func(*args, **kwargs) + except Exception as exc: + logger.error("Exception while calling function: %r" % (exc,)) + value = exc + self.cache[key] = value + self.cache_ts[key] = datetime.datetime.now() + return ret_or_raise(value) + + def _is_cache_still_valid(self, key, now=datetime.datetime.now): + """ + Returns True if the cache value is still valid, False otherwise. + + For now, this happen if less than CACHE_INVALIDATION_DELTA seconds + have passed from the time in which we recorded the cached value. + + :param key: the key to lookup in the cache + :type key: hashable + :param now: a callable that returns a datetime object. override + for dependency injection during testing. + :type now: callable + :rtype: bool + """ + cached_ts = self.cache_ts[key] + delta = datetime.timedelta(seconds=self.CACHE_INVALIDATION_DELTA) + return (now() - cached_ts) < delta + + def __repr__(self): + """ + Return the function's docstring. + """ + return self.func.__doc__ + + def __get__(self, obj, objtype): + """ + Support instance methods. + """ + return functools.partial(self.__call__, obj) + + +def memoized_method(function=None, ignore_kwargs=None): + """ + Wrap _memoized to allow for deferred calling + + :type function: callable, or None. + :type ignore_kwargs: None, True or tuple. + """ + if function: + return _memoized(function, is_method=True) + else: + def wrapper(function): + return _memoized( + function, ignore_kwargs=ignore_kwargs, is_method=True) + return wrapper diff --git a/src/leap/common/events/events.proto b/src/leap/common/events/events.proto index 2708b93..a8eb82a 100644 --- a/src/leap/common/events/events.proto +++ b/src/leap/common/events/events.proto @@ -63,6 +63,7 @@ enum Event { KEYMANAGER_STARTED_KEY_GENERATION = 43; KEYMANAGER_FINISHED_KEY_GENERATION = 44; KEYMANAGER_DONE_UPLOADING_KEYS = 45; + SOLEDAD_INVALID_AUTH_TOKEN = 46; } diff --git a/src/leap/common/events/events_pb2.py b/src/leap/common/events/events_pb2.py index 93f6f0b..f0b05f7 100644 --- a/src/leap/common/events/events_pb2.py +++ b/src/leap/common/events/events_pb2.py @@ -13,7 +13,7 @@ from google.protobuf import descriptor_pb2 DESCRIPTOR = descriptor.FileDescriptor( name='events.proto', package='leap.common.events', - serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"l\n\x11UnregisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\r\n\x0bPingRequest\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xc0\t\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x12\x10\n\x0cRAISE_WINDOW\x10\r\x12\x18\n\x14SMTP_SERVICE_STARTED\x10\x0e\x12 \n\x1cSMTP_SERVICE_FAILED_TO_START\x10\x0f\x12%\n!SMTP_RECIPIENT_ACCEPTED_ENCRYPTED\x10\x10\x12\'\n#SMTP_RECIPIENT_ACCEPTED_UNENCRYPTED\x10\x11\x12\x1b\n\x17SMTP_RECIPIENT_REJECTED\x10\x12\x12\x1f\n\x1bSMTP_START_ENCRYPT_AND_SIGN\x10\x13\x12\x1d\n\x19SMTP_END_ENCRYPT_AND_SIGN\x10\x14\x12\x13\n\x0fSMTP_START_SIGN\x10\x15\x12\x11\n\rSMTP_END_SIGN\x10\x16\x12\x1b\n\x17SMTP_SEND_MESSAGE_START\x10\x17\x12\x1d\n\x19SMTP_SEND_MESSAGE_SUCCESS\x10\x18\x12\x1b\n\x17SMTP_SEND_MESSAGE_ERROR\x10\x19\x12\x18\n\x14SMTP_CONNECTION_LOST\x10\x1a\x12\x18\n\x14IMAP_SERVICE_STARTED\x10\x1e\x12 \n\x1cIMAP_SERVICE_FAILED_TO_START\x10\x1f\x12\x15\n\x11IMAP_CLIENT_LOGIN\x10 \x12\x19\n\x15IMAP_FETCHED_INCOMING\x10!\x12\x17\n\x13IMAP_MSG_PROCESSING\x10\"\x12\x16\n\x12IMAP_MSG_DECRYPTED\x10#\x12\x1a\n\x16IMAP_MSG_SAVED_LOCALLY\x10$\x12\x1d\n\x19IMAP_MSG_DELETED_INCOMING\x10%\x12\x18\n\x14IMAP_UNHANDLED_ERROR\x10&\x12\x14\n\x10IMAP_UNREAD_MAIL\x10\'\x12\x1e\n\x1aKEYMANAGER_LOOKING_FOR_KEY\x10(\x12\x18\n\x14KEYMANAGER_KEY_FOUND\x10)\x12\x1c\n\x18KEYMANAGER_KEY_NOT_FOUND\x10*\x12%\n!KEYMANAGER_STARTED_KEY_GENERATION\x10+\x12&\n\"KEYMANAGER_FINISHED_KEY_GENERATION\x10,\x12\"\n\x1eKEYMANAGER_DONE_UPLOADING_KEYS\x10-2\xdd\x02\n\x13\x45ventsServerService\x12J\n\x04ping\x12\x1f.leap.common.events.PingRequest\x1a!.leap.common.events.EventResponse\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12V\n\nunregister\x12%.leap.common.events.UnregisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2\xb1\x01\n\x13\x45ventsClientService\x12J\n\x04ping\x12\x1f.leap.common.events.PingRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponseB\x03\x90\x01\x01') + serialized_pb='\n\x0c\x65vents.proto\x12\x12leap.common.events\"\x97\x01\n\rSignalRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0f\n\x07\x63ontent\x18\x02 \x02(\t\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\x12\x12\n\nenc_method\x18\x05 \x01(\t\x12\x16\n\x0e\x65rror_occurred\x18\x06 \x01(\x08\"j\n\x0fRegisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"l\n\x11UnregisterRequest\x12(\n\x05\x65vent\x18\x01 \x02(\x0e\x32\x19.leap.common.events.Event\x12\x0c\n\x04port\x18\x02 \x02(\x05\x12\x12\n\nmac_method\x18\x03 \x02(\t\x12\x0b\n\x03mac\x18\x04 \x02(\x0c\"\r\n\x0bPingRequest\"\x82\x01\n\rEventResponse\x12\x38\n\x06status\x18\x01 \x02(\x0e\x32(.leap.common.events.EventResponse.Status\x12\x0e\n\x06result\x18\x02 \x01(\t\"\'\n\x06Status\x12\x06\n\x02OK\x10\x01\x12\n\n\x06UNAUTH\x10\x02\x12\t\n\x05\x45RROR\x10\x03*\xe0\t\n\x05\x45vent\x12\x15\n\x11\x43LIENT_SESSION_ID\x10\x01\x12\x0e\n\nCLIENT_UID\x10\x02\x12\x19\n\x15SOLEDAD_CREATING_KEYS\x10\x03\x12\x1e\n\x1aSOLEDAD_DONE_CREATING_KEYS\x10\x04\x12\x1a\n\x16SOLEDAD_UPLOADING_KEYS\x10\x05\x12\x1f\n\x1bSOLEDAD_DONE_UPLOADING_KEYS\x10\x06\x12\x1c\n\x18SOLEDAD_DOWNLOADING_KEYS\x10\x07\x12!\n\x1dSOLEDAD_DONE_DOWNLOADING_KEYS\x10\x08\x12\x1c\n\x18SOLEDAD_NEW_DATA_TO_SYNC\x10\t\x12\x1a\n\x16SOLEDAD_DONE_DATA_SYNC\x10\n\x12\x17\n\x13UPDATER_NEW_UPDATES\x10\x0b\x12\x19\n\x15UPDATER_DONE_UPDATING\x10\x0c\x12\x10\n\x0cRAISE_WINDOW\x10\r\x12\x18\n\x14SMTP_SERVICE_STARTED\x10\x0e\x12 \n\x1cSMTP_SERVICE_FAILED_TO_START\x10\x0f\x12%\n!SMTP_RECIPIENT_ACCEPTED_ENCRYPTED\x10\x10\x12\'\n#SMTP_RECIPIENT_ACCEPTED_UNENCRYPTED\x10\x11\x12\x1b\n\x17SMTP_RECIPIENT_REJECTED\x10\x12\x12\x1f\n\x1bSMTP_START_ENCRYPT_AND_SIGN\x10\x13\x12\x1d\n\x19SMTP_END_ENCRYPT_AND_SIGN\x10\x14\x12\x13\n\x0fSMTP_START_SIGN\x10\x15\x12\x11\n\rSMTP_END_SIGN\x10\x16\x12\x1b\n\x17SMTP_SEND_MESSAGE_START\x10\x17\x12\x1d\n\x19SMTP_SEND_MESSAGE_SUCCESS\x10\x18\x12\x1b\n\x17SMTP_SEND_MESSAGE_ERROR\x10\x19\x12\x18\n\x14SMTP_CONNECTION_LOST\x10\x1a\x12\x18\n\x14IMAP_SERVICE_STARTED\x10\x1e\x12 \n\x1cIMAP_SERVICE_FAILED_TO_START\x10\x1f\x12\x15\n\x11IMAP_CLIENT_LOGIN\x10 \x12\x19\n\x15IMAP_FETCHED_INCOMING\x10!\x12\x17\n\x13IMAP_MSG_PROCESSING\x10\"\x12\x16\n\x12IMAP_MSG_DECRYPTED\x10#\x12\x1a\n\x16IMAP_MSG_SAVED_LOCALLY\x10$\x12\x1d\n\x19IMAP_MSG_DELETED_INCOMING\x10%\x12\x18\n\x14IMAP_UNHANDLED_ERROR\x10&\x12\x14\n\x10IMAP_UNREAD_MAIL\x10\'\x12\x1e\n\x1aKEYMANAGER_LOOKING_FOR_KEY\x10(\x12\x18\n\x14KEYMANAGER_KEY_FOUND\x10)\x12\x1c\n\x18KEYMANAGER_KEY_NOT_FOUND\x10*\x12%\n!KEYMANAGER_STARTED_KEY_GENERATION\x10+\x12&\n\"KEYMANAGER_FINISHED_KEY_GENERATION\x10,\x12\"\n\x1eKEYMANAGER_DONE_UPLOADING_KEYS\x10-\x12\x1e\n\x1aSOLEDAD_INVALID_AUTH_TOKEN\x10.2\xdd\x02\n\x13\x45ventsServerService\x12J\n\x04ping\x12\x1f.leap.common.events.PingRequest\x1a!.leap.common.events.EventResponse\x12R\n\x08register\x12#.leap.common.events.RegisterRequest\x1a!.leap.common.events.EventResponse\x12V\n\nunregister\x12%.leap.common.events.UnregisterRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponse2\xb1\x01\n\x13\x45ventsClientService\x12J\n\x04ping\x12\x1f.leap.common.events.PingRequest\x1a!.leap.common.events.EventResponse\x12N\n\x06signal\x12!.leap.common.events.SignalRequest\x1a!.leap.common.events.EventResponseB\x03\x90\x01\x01') _EVENT = descriptor.EnumDescriptor( name='Event', @@ -189,11 +189,15 @@ _EVENT = descriptor.EnumDescriptor( name='KEYMANAGER_DONE_UPLOADING_KEYS', index=41, number=45, options=None, type=None), + descriptor.EnumValueDescriptor( + name='SOLEDAD_INVALID_AUTH_TOKEN', index=42, number=46, + options=None, + type=None), ], containing_type=None, options=None, serialized_start=557, - serialized_end=1773, + serialized_end=1805, ) @@ -239,6 +243,7 @@ KEYMANAGER_KEY_NOT_FOUND = 42 KEYMANAGER_STARTED_KEY_GENERATION = 43 KEYMANAGER_FINISHED_KEY_GENERATION = 44 KEYMANAGER_DONE_UPLOADING_KEYS = 45 +SOLEDAD_INVALID_AUTH_TOKEN = 46 _EVENTRESPONSE_STATUS = descriptor.EnumDescriptor( @@ -532,8 +537,8 @@ _EVENTSSERVERSERVICE = descriptor.ServiceDescriptor( file=DESCRIPTOR, index=0, options=None, - serialized_start=1776, - serialized_end=2125, + serialized_start=1808, + serialized_end=2157, methods=[ descriptor.MethodDescriptor( name='ping', @@ -587,8 +592,8 @@ _EVENTSCLIENTSERVICE = descriptor.ServiceDescriptor( file=DESCRIPTOR, index=1, options=None, - serialized_start=2128, - serialized_end=2305, + serialized_start=2160, + serialized_end=2337, methods=[ descriptor.MethodDescriptor( name='ping', diff --git a/src/leap/common/mail.py b/src/leap/common/mail.py index 2f2146d..b630c90 100644 --- a/src/leap/common/mail.py +++ b/src/leap/common/mail.py @@ -20,26 +20,25 @@ Utility functions for email. import email import re -from leap.common.check import leap_assert_type - def get_email_charset(content, default="utf-8"): """ Mini parser to retrieve the charset of an email. :param content: mail contents - :type content: unicode + :type content: unicode or str :param default: optional default value for encoding :type default: str or None :returns: the charset as parsed from the contents :rtype: str """ - leap_assert_type(content, unicode) + if isinstance(content, unicode): + content.encode("utf-8", "replace") charset = default try: - em = email.message_from_string(content.encode("utf-8", "replace")) + em = email.message_from_string(content) # Miniparser for: Content-Type: <something>; charset=<charset> charset_re = r'''charset=(?P<charset>[\w|\d|-]*)''' charset = re.findall(charset_re, em["Content-Type"])[0] diff --git a/src/leap/common/tests/test_check.py b/src/leap/common/tests/test_check.py index 6ce8493..cd488ff 100644 --- a/src/leap/common/tests/test_check.py +++ b/src/leap/common/tests/test_check.py @@ -27,6 +27,7 @@ import mock from leap.common import check + class CheckTests(unittest.TestCase): def test_raises_on_false_condition(self): with self.assertRaises(AssertionError): diff --git a/src/leap/common/tests/test_memoize.py b/src/leap/common/tests/test_memoize.py new file mode 100644 index 0000000..c923fc5 --- /dev/null +++ b/src/leap/common/tests/test_memoize.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# test_check.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +""" +Tests for: + * leap/common/decorators._memoized +""" +try: + import unittest2 as unittest +except ImportError: + import unittest + +from time import sleep + +import mock + +from leap.common.decorators import _memoized + + +class MemoizeTests(unittest.TestCase): + + def test_memoized_call(self): + """ + Test that a memoized function only calls once. + """ + witness = mock.Mock() + + @_memoized + def callmebaby(): + return witness() + + for i in range(10): + callmebaby() + witness.assert_called_once_with() + + def test_cache_invalidation(self): + """ + Test that time makes the cache invalidation expire. + """ + witness = mock.Mock() + + cache_with_alzheimer = _memoized + cache_with_alzheimer.CACHE_INVALIDATION_DELTA = 1 + + @cache_with_alzheimer + def callmebaby(*args): + return witness(*args) + + for i in range(10): + callmebaby() + witness.assert_called_once_with() + + sleep(2) + callmebaby("onemoretime") + + expected = [mock.call(), mock.call("onemoretime")] + self.assertEqual( + witness.call_args_list, + expected) + + +if __name__ == "__main__": + unittest.main() |