etiquette/etiquette/decorators.py
Ethan Dalool adb1d0ef39 Replace all double blank lines with single, improve hash headers.
There was always some semblance that two blank lines has some kind of
meaning or structure that's different from single blank lines, but
in reality it was mostly arbitrary and I can't stand to look at it
any more.
2020-09-19 03:13:23 -07:00

99 lines
2.9 KiB
Python

import functools
import time
import warnings
from . import exceptions
def _get_relevant_photodb(instance):
from . import objects
if isinstance(instance, objects.ObjectBase):
photodb = instance.photodb
else:
photodb = instance
return photodb
def required_feature(features):
'''
Declare that the photodb or object method requires certain 'enable_*'
fields in the config.
'''
if isinstance(features, str):
features = [features]
def wrapper(method):
@functools.wraps(method)
def wrapped_required_feature(self, *args, **kwargs):
photodb = _get_relevant_photodb(self)
config = photodb.config['enable_feature']
# Using the received string like "photo.new", try to navigate the
# config and wind up at a True or False. All other values invalid.
# Allow KeyErrors to raise themselves.
for feature in features:
cfg = config
pieces = feature.split('.')
for piece in pieces:
cfg = cfg[piece]
if cfg is True:
pass
elif cfg is False:
raise exceptions.FeatureDisabled(feature)
else:
raise ValueError(f'Bad required_feature: "{feature}" led to {cfg}')
return method(self, *args, **kwargs)
return wrapped_required_feature
return wrapper
def not_implemented(function):
'''
Decorator to remember what needs doing.
'''
warnings.warn(f'{function.__name__} is not implemented')
return function
def time_me(function):
'''
After the function is run, print the elapsed time.
'''
@functools.wraps(function)
def timed_function(*args, **kwargs):
start = time.time()
result = function(*args, **kwargs)
end = time.time()
duration = end - start
print(f'{function.__name__}: {duration:0.8f}')
return result
return timed_function
def transaction(method):
'''
Open a savepoint before running the method.
If the method fails, roll back to that savepoint.
'''
@functools.wraps(method)
def wrapped_transaction(self, *args, **kwargs):
photodb = _get_relevant_photodb(self)
commit = kwargs.pop('commit', False)
is_root = len(photodb.savepoints) == 0
savepoint_id = photodb.savepoint(message=method.__qualname__)
try:
result = method(self, *args, **kwargs)
except Exception as exc:
photodb.log.debug(f'{method} raised {repr(exc)}')
photodb.rollback(savepoint=savepoint_id)
raise
if commit:
photodb.commit(message=method.__qualname__)
elif not is_root:
photodb.release_savepoint(savepoint=savepoint_id)
return result
return wrapped_transaction