-
Notifications
You must be signed in to change notification settings - Fork 2k
/
maintain.py
154 lines (129 loc) · 5.91 KB
/
maintain.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
''' This module contains code that helps in maintaining the Ckan codebase. '''
import inspect
import time
import logging
import re
from pylons import c
log = logging.getLogger(__name__)
def deprecated(message=''):
''' This is a decorator used to mark functions as deprecated.
It logs a warning when the function is called. If a message is
passed it is also logged, this can be useful to indicate for example
that a different function should be used instead.
Additionally an exception is raised if the functions docstring does
not contain the word `deprecated`.'''
def decorator(fn):
# When decorating a function check that the docstring is correct.
if not fn.__doc__ or not re.search(r'\bdeprecated\b',
fn.__doc__, re.IGNORECASE):
raise Exception('Function %s() in module %s has been deprecated '
'but this is not mentioned in the docstring. '
'Please update the docstring for the function. '
'It must include the word `deprecated`.'
% (fn.__name__, fn.__module__))
# Log deprecated functions
log.info('Function %s() in module %s has been deprecated. %s'
% (fn.__name__, fn.__module__, message))
def wrapped(*args, **kw):
log.warning('Function %s() in module %s has been deprecated '
'and will be removed in a later release of ckan. %s'
% (fn.__name__, fn.__module__, message))
return fn(*args, **kw)
return wrapped
return decorator
def deprecate_context_item(item_name, message=''):
''' Deprecate a named item in the global context object.
It logs a warning when the item is accessed. If a mesage is passed, it is
also logged. This can be useful to indicate for example that a different
function should be used instead.
No warnings are given when an attempt to change or delete the named item
from the context object.
Example usage:
>>> c.facets = "Foobar"
>>> deprecate_context_item('facets', 'Use `c.search_facets` instead')
>>> print c.facets
2012-07-12 13:27:06,294 WARNI [ckan.lib.maintain] c.facets has been deprecated [...]
Foobar
This function works by attaching a property to the underlying
`pylons.util.AttribSafeContextObj` object which provides the storage of the
context object. ie - it adds a class-level attribute to the
`pylons.util.AttribSafeContextObj` at runtime.
'''
class Fake(object):
''' This is a fake object that calls the methods of the object
contained. '''
def __init__(self, obj):
self._obj = obj
def __getattribute__(self,name):
obj = object.__getattribute__(self, '_obj')
# hack to get the actual object when needed
if name == '_obj':
return obj
return getattr(obj, name)
# store the value in a fake object
setattr(c, item_name, Fake(getattr(c, item_name)))
# we need to store the origional __getattr__ and replace with our own one
if not hasattr(c.__class__, '__old_getattr__'):
def fake_attr(self, name):
obj = self.__class__.__dict__['__old_getattr__'](self, name)
if isinstance(obj, Fake):
return obj._obj
else:
return obj
get_attr = getattr(c.__class__, '__getattr__')
setattr(c.__class__, '__old_getattr__', get_attr)
setattr(c.__class__, '__getattr__', fake_attr)
def defer_context_item(item_name, function):
''' Allows a function to be passed that will be appended to c as a property
so that it is only called if actually used. '''
assert hasattr(function, '__call__'), 'must pass a function'
setattr(c, item_name, property(function))
def timer(params):
''' Decorator function for basic performance testing. It logs the time
taken to call a function. It can either be used as a basic decorator or an
array of parameter names can be passed. If parameter names are passed then
the logging will include the value of the parameter if it is passed to the
function. '''
if hasattr(params, '__call__'):
# this is being used as a simple decorator
fn = params
fn_name = '%s.%s' % (fn.__module__, fn.__name__)
def wrapped(*args, **kw):
start = time.time()
result = fn(*args, **kw)
log.info('Timer: %s %.4f' % (fn_name, time.time() - start))
return result
return wrapped
assert isinstance(params, list)
def decorator(fn):
# we have a list of parameter names so we want to find if the parameter
# is a named one and if so store its position
args_info = inspect.getargspec(fn)
params_data = []
for param in params:
if param in args_info.args:
params_data.append((param, args_info.args.index(param)))
else:
# it could be passed in keywords
params_data.append((param))
fn_name = '%s.%s' % (fn.__module__, fn.__name__)
def wrapped(*args, **kw):
# store parameters being used in the call that we want to record
params = []
for param in params_data:
value = None
if param[0] in kw:
value = kw[param[0]]
elif len(param) != 1 and len(args) >= param[1]:
value = args[param[1]]
else:
continue
params.append(u'%s=%r' % (param[0], value))
p = ', '.join(params)
start = time.time()
# call the function
result = fn(*args, **kw)
log.info('Timer: %s %.4f %s' % (fn_name, time.time() - start, p))
return result
return wrapped
return decorator