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/>.
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.
40 def __get__(self
, obj
, type=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
__:
53 value
= object.__getattribute
__(mrotype
, name
)
54 except AttributeError:
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
)
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
)
76 def __set__(self
, obj
, value
):
77 property.__set
__(self
, obj
, value
)
79 setattr(obj
, cache_attr
, value
)
82 delattr(obj
, cache_attr
)
83 except AttributeError:
86 def __delete__(self
, obj
):
87 property.__delete
__(self
, obj
)
89 delattr(obj
, cache_attr
)
90 except AttributeError:
93 return _cached_writable_property
96 def jsonify_error(function
=None, logger_name
=None, logger_message
=None, logging_level
='info', status
=200):
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
):
107 def wrapper(*args
, **kw
):
108 for e
in list(args
) + kw
.values():
109 if isinstance(e
, Exception):
113 raise IndicoError('Wrong usage of jsonify_error: No error found in params')
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
124 if request
.is_xhr
or request
.headers
.get('Content-Type') == 'application/json':
125 return create_json_error_answer(exception
, status
=status
)
127 args
[0]._responseUtil
.status
= status
128 return f(*args
, **kw
)
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::
144 And also with arguments::
146 @fancy_decorator(123, foo='bar')
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::
157 def requires_location(f, some='args', are='here'):
159 def wrapper(*args, **kwargs):
160 return f(*args, **kwargs)
165 def wrapper(*args
, **kw
):
166 if len(args
) == 1 and not kw
and callable(args
[0]):
169 return lambda original
: f(original
, *args
, **kw
)