Fix day filter
[cds-indico.git] / indico / util / decorators.py
blob75554cf65d544940873bff9cd78ad9c32854e014
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 import traceback
18 from functools import wraps
20 from flask import request
21 from werkzeug.exceptions import NotFound
23 from indico.util.json import create_json_error_answer
26 class classproperty(property):
27 def __get__(self, obj, type=None):
28 return self.fget.__get__(None, type)()
31 class strict_classproperty(classproperty):
32 """A classproperty that does not work on instances.
34 This is useful for properties which would be confusing when
35 accessed through an instance. However, using this property
36 still won't allow you to set the attribute on the instance
37 itself, so it's really just to stop people from accessing
38 the property in an inappropriate way.
39 """
40 def __get__(self, obj, type=None):
41 if obj is not None:
42 raise AttributeError('Attribute is not available on instances of {}'.format(type.__name__))
43 return super(strict_classproperty, self).__get__(obj, type)
46 class cached_classproperty(property):
47 def __get__(self, obj, objtype=None):
48 # The property name is the function's name
49 name = self.fget.__get__(True).im_func.__name__
50 # In case of inheritance the attribute might be defined in a superclass
51 for mrotype in objtype.__mro__:
52 try:
53 value = object.__getattribute__(mrotype, name)
54 except AttributeError:
55 pass
56 else:
57 break
58 else:
59 raise AttributeError(name)
60 # We we have a cached_classproperty, the value has not been resolved yet
61 if isinstance(value, cached_classproperty):
62 value = self.fget.__get__(None, objtype)()
63 setattr(objtype, name, value)
64 return value
67 def cached_writable_property(cache_attr, cache_on_set=True):
68 class _cached_writable_property(property):
69 def __get__(self, obj, objtype=None):
70 if obj is not None and self.fget and hasattr(obj, cache_attr):
71 return getattr(obj, cache_attr)
72 value = property.__get__(self, obj, objtype)
73 setattr(obj, cache_attr, value)
74 return value
76 def __set__(self, obj, value):
77 property.__set__(self, obj, value)
78 if cache_on_set:
79 setattr(obj, cache_attr, value)
80 else:
81 try:
82 delattr(obj, cache_attr)
83 except AttributeError:
84 pass
86 def __delete__(self, obj):
87 property.__delete__(self, obj)
88 try:
89 delattr(obj, cache_attr)
90 except AttributeError:
91 pass
93 return _cached_writable_property
96 def jsonify_error(function=None, logger_name=None, logger_message=None, logging_level='info', status=200):
97 """
98 Returns response of error handlers in JSON if requested in JSON
99 and logs the exception that ended the request.
101 from indico.core.errors import IndicoError, NotFoundError
102 from indico.core.logger import Logger
103 no_tb_exceptions = (NotFound, NotFoundError)
105 def _jsonify_error(f):
106 @wraps(f)
107 def wrapper(*args, **kw):
108 for e in list(args) + kw.values():
109 if isinstance(e, Exception):
110 exception = e
111 break
112 else:
113 raise IndicoError('Wrong usage of jsonify_error: No error found in params')
115 tb = ''
116 if logging_level != 'exception' and not isinstance(exception, no_tb_exceptions):
117 tb = traceback.format_exc()
118 getattr(Logger.get(logger_name), logging_level)(
119 logger_message if logger_message else
120 'Request {0} finished with {1}: {2}\n{3}'.format(
121 request, exception.__class__.__name__, exception, tb
122 ).rstrip())
124 if request.is_xhr or request.headers.get('Content-Type') == 'application/json':
125 return create_json_error_answer(exception, status=status)
126 else:
127 args[0]._responseUtil.status = status
128 return f(*args, **kw)
129 return wrapper
130 if function:
131 return _jsonify_error(function)
132 return _jsonify_error
135 def smart_decorator(f):
136 """Decorator to make decorators work both with and without arguments.
138 This decorator allows you to use a decorator both without arguments::
140 @fancy_decorator
141 def function():
142 pass
144 And also with arguments::
146 @fancy_decorator(123, foo='bar')
147 def function():
148 pass
150 The only limitation is that the decorator itself MUST NOT allow a callable object
151 as the first positional argument, unless there is at least one other mandatory argument.
153 The decorator decorated with `smart_decorator` obviously needs to have default values for
154 all arguments but the first one::
156 @smart_decorator
157 def requires_location(f, some='args', are='here'):
158 @wraps(f)
159 def wrapper(*args, **kwargs):
160 return f(*args, **kwargs)
162 return wrapper
164 @wraps(f)
165 def wrapper(*args, **kw):
166 if len(args) == 1 and not kw and callable(args[0]):
167 return f(args[0])
168 else:
169 return lambda original: f(original, *args, **kw)
171 return wrapper