##// END OF EJS Templates
support non-modules in @require...
MinRK -
Show More
@@ -1,203 +1,228 b''
1 """Dependency utilities
1 """Dependency utilities
2
2
3 Authors:
3 Authors:
4
4
5 * Min RK
5 * Min RK
6 """
6 """
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010-2011 The IPython Development Team
8 # Copyright (C) 2013 The IPython Development Team
9 #
9 #
10 # Distributed under the terms of the BSD License. The full license is in
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 import sys
15
14 from types import ModuleType
16 from types import ModuleType
15
17
16 from IPython.parallel.client.asyncresult import AsyncResult
18 from IPython.parallel.client.asyncresult import AsyncResult
17 from IPython.parallel.error import UnmetDependency
19 from IPython.parallel.error import UnmetDependency
18 from IPython.parallel.util import interactive
20 from IPython.parallel.util import interactive
19 from IPython.utils import py3compat
21 from IPython.utils import py3compat
22 from IPython.utils.pickleutil import can, uncan
20
23
21 class depend(object):
24 class depend(object):
22 """Dependency decorator, for use with tasks.
25 """Dependency decorator, for use with tasks.
23
26
24 `@depend` lets you define a function for engine dependencies
27 `@depend` lets you define a function for engine dependencies
25 just like you use `apply` for tasks.
28 just like you use `apply` for tasks.
26
29
27
30
28 Examples
31 Examples
29 --------
32 --------
30 ::
33 ::
31
34
32 @depend(df, a,b, c=5)
35 @depend(df, a,b, c=5)
33 def f(m,n,p)
36 def f(m,n,p)
34
37
35 view.apply(f, 1,2,3)
38 view.apply(f, 1,2,3)
36
39
37 will call df(a,b,c=5) on the engine, and if it returns False or
40 will call df(a,b,c=5) on the engine, and if it returns False or
38 raises an UnmetDependency error, then the task will not be run
41 raises an UnmetDependency error, then the task will not be run
39 and another engine will be tried.
42 and another engine will be tried.
40 """
43 """
41 def __init__(self, f, *args, **kwargs):
44 def __init__(self, f, *args, **kwargs):
42 self.f = f
45 self.f = f
43 self.args = args
46 self.args = args
44 self.kwargs = kwargs
47 self.kwargs = kwargs
45
48
46 def __call__(self, f):
49 def __call__(self, f):
47 return dependent(f, self.f, *self.args, **self.kwargs)
50 return dependent(f, self.f, *self.args, **self.kwargs)
48
51
49 class dependent(object):
52 class dependent(object):
50 """A function that depends on another function.
53 """A function that depends on another function.
51 This is an object to prevent the closure used
54 This is an object to prevent the closure used
52 in traditional decorators, which are not picklable.
55 in traditional decorators, which are not picklable.
53 """
56 """
54
57
55 def __init__(self, f, df, *dargs, **dkwargs):
58 def __init__(self, f, df, *dargs, **dkwargs):
56 self.f = f
59 self.f = f
57 self.func_name = getattr(f, '__name__', 'f')
60 self.func_name = getattr(f, '__name__', 'f')
58 self.df = df
61 self.df = df
59 self.dargs = dargs
62 self.dargs = dargs
60 self.dkwargs = dkwargs
63 self.dkwargs = dkwargs
61
64
62 def __call__(self, *args, **kwargs):
65 def __call__(self, *args, **kwargs):
63 # if hasattr(self.f, 'func_globals') and hasattr(self.df, 'func_globals'):
66 user_ns = sys.modules['__main__'].__dict__
64 # self.df.func_globals = self.f.func_globals
67 for key, value in self.dkwargs.items():
68 self.dkwargs[key] = uncan(value, user_ns)
65 if self.df(*self.dargs, **self.dkwargs) is False:
69 if self.df(*self.dargs, **self.dkwargs) is False:
66 raise UnmetDependency()
70 raise UnmetDependency()
67 return self.f(*args, **kwargs)
71 return self.f(*args, **kwargs)
68
72
69 if not py3compat.PY3:
73 if not py3compat.PY3:
70 @property
74 @property
71 def __name__(self):
75 def __name__(self):
72 return self.func_name
76 return self.func_name
73
77
74 @interactive
78 @interactive
75 def _require(*names):
79 def _require(*modules, **mapping):
76 """Helper for @require decorator."""
80 """Helper for @require decorator."""
77 from IPython.parallel.error import UnmetDependency
81 from IPython.parallel.error import UnmetDependency
82 from IPython.utils.pickleutil import uncan
78 user_ns = globals()
83 user_ns = globals()
79 for name in names:
84 for name in modules:
80 if name in user_ns:
81 continue
82 try:
85 try:
83 exec 'import %s'%name in user_ns
86 exec 'import %s' % name in user_ns
84 except ImportError:
87 except ImportError:
85 raise UnmetDependency(name)
88 raise UnmetDependency(name)
89
90 for name, cobj in mapping.items():
91 user_ns[name] = uncan(cobj, user_ns)
86 return True
92 return True
87
93
88 def require(*mods):
94 def require(*objects, **mapping):
89 """Simple decorator for requiring names to be importable.
95 """Simple decorator for requiring local objects and modules to be available
96 when the decorated function is called on the engine.
97
98 Modules specified by name or passed directly will be imported
99 prior to calling the decorated function.
100
101 Objects other than modules will be pushed as a part of the task.
102 Functions can be passed positionally,
103 and will be pushed to the engine with their __name__.
104 Other objects can be passed by keyword arg.
90
105
91 Examples
106 Examples
92 --------
107 --------
93
108
94 In [1]: @require('numpy')
109 In [1]: @require('numpy')
95 ...: def norm(a):
110 ...: def norm(a):
96 ...: import numpy
97 ...: return numpy.linalg.norm(a,2)
111 ...: return numpy.linalg.norm(a,2)
112
113 In [2]: foo = lambda x: x*x
114 In [3]: @require(foo)
115 ...: def bar(a):
116 ...: return foo(1-a)
98 """
117 """
99 names = []
118 names = []
100 for mod in mods:
119 for obj in objects:
101 if isinstance(mod, ModuleType):
120 if isinstance(obj, ModuleType):
102 mod = mod.__name__
121 obj = obj.__name__
103
122
104 if isinstance(mod, basestring):
123 if isinstance(obj, basestring):
105 names.append(mod)
124 names.append(obj)
125 elif hasattr(obj, '__name__'):
126 mapping[obj.__name__] = obj
106 else:
127 else:
107 raise TypeError("names must be modules or module names, not %s"%type(mod))
128 raise TypeError("Objects other than modules and functions "
129 "must be passed by kwarg, but got: %s" % type(obj)
130 )
108
131
109 return depend(_require, *names)
132 for name, obj in mapping.items():
133 mapping[name] = can(obj)
134 return depend(_require, *names, **mapping)
110
135
111 class Dependency(set):
136 class Dependency(set):
112 """An object for representing a set of msg_id dependencies.
137 """An object for representing a set of msg_id dependencies.
113
138
114 Subclassed from set().
139 Subclassed from set().
115
140
116 Parameters
141 Parameters
117 ----------
142 ----------
118 dependencies: list/set of msg_ids or AsyncResult objects or output of Dependency.as_dict()
143 dependencies: list/set of msg_ids or AsyncResult objects or output of Dependency.as_dict()
119 The msg_ids to depend on
144 The msg_ids to depend on
120 all : bool [default True]
145 all : bool [default True]
121 Whether the dependency should be considered met when *all* depending tasks have completed
146 Whether the dependency should be considered met when *all* depending tasks have completed
122 or only when *any* have been completed.
147 or only when *any* have been completed.
123 success : bool [default True]
148 success : bool [default True]
124 Whether to consider successes as fulfilling dependencies.
149 Whether to consider successes as fulfilling dependencies.
125 failure : bool [default False]
150 failure : bool [default False]
126 Whether to consider failures as fulfilling dependencies.
151 Whether to consider failures as fulfilling dependencies.
127
152
128 If `all=success=True` and `failure=False`, then the task will fail with an ImpossibleDependency
153 If `all=success=True` and `failure=False`, then the task will fail with an ImpossibleDependency
129 as soon as the first depended-upon task fails.
154 as soon as the first depended-upon task fails.
130 """
155 """
131
156
132 all=True
157 all=True
133 success=True
158 success=True
134 failure=True
159 failure=True
135
160
136 def __init__(self, dependencies=[], all=True, success=True, failure=False):
161 def __init__(self, dependencies=[], all=True, success=True, failure=False):
137 if isinstance(dependencies, dict):
162 if isinstance(dependencies, dict):
138 # load from dict
163 # load from dict
139 all = dependencies.get('all', True)
164 all = dependencies.get('all', True)
140 success = dependencies.get('success', success)
165 success = dependencies.get('success', success)
141 failure = dependencies.get('failure', failure)
166 failure = dependencies.get('failure', failure)
142 dependencies = dependencies.get('dependencies', [])
167 dependencies = dependencies.get('dependencies', [])
143 ids = []
168 ids = []
144
169
145 # extract ids from various sources:
170 # extract ids from various sources:
146 if isinstance(dependencies, (basestring, AsyncResult)):
171 if isinstance(dependencies, (basestring, AsyncResult)):
147 dependencies = [dependencies]
172 dependencies = [dependencies]
148 for d in dependencies:
173 for d in dependencies:
149 if isinstance(d, basestring):
174 if isinstance(d, basestring):
150 ids.append(d)
175 ids.append(d)
151 elif isinstance(d, AsyncResult):
176 elif isinstance(d, AsyncResult):
152 ids.extend(d.msg_ids)
177 ids.extend(d.msg_ids)
153 else:
178 else:
154 raise TypeError("invalid dependency type: %r"%type(d))
179 raise TypeError("invalid dependency type: %r"%type(d))
155
180
156 set.__init__(self, ids)
181 set.__init__(self, ids)
157 self.all = all
182 self.all = all
158 if not (success or failure):
183 if not (success or failure):
159 raise ValueError("Must depend on at least one of successes or failures!")
184 raise ValueError("Must depend on at least one of successes or failures!")
160 self.success=success
185 self.success=success
161 self.failure = failure
186 self.failure = failure
162
187
163 def check(self, completed, failed=None):
188 def check(self, completed, failed=None):
164 """check whether our dependencies have been met."""
189 """check whether our dependencies have been met."""
165 if len(self) == 0:
190 if len(self) == 0:
166 return True
191 return True
167 against = set()
192 against = set()
168 if self.success:
193 if self.success:
169 against = completed
194 against = completed
170 if failed is not None and self.failure:
195 if failed is not None and self.failure:
171 against = against.union(failed)
196 against = against.union(failed)
172 if self.all:
197 if self.all:
173 return self.issubset(against)
198 return self.issubset(against)
174 else:
199 else:
175 return not self.isdisjoint(against)
200 return not self.isdisjoint(against)
176
201
177 def unreachable(self, completed, failed=None):
202 def unreachable(self, completed, failed=None):
178 """return whether this dependency has become impossible."""
203 """return whether this dependency has become impossible."""
179 if len(self) == 0:
204 if len(self) == 0:
180 return False
205 return False
181 against = set()
206 against = set()
182 if not self.success:
207 if not self.success:
183 against = completed
208 against = completed
184 if failed is not None and not self.failure:
209 if failed is not None and not self.failure:
185 against = against.union(failed)
210 against = against.union(failed)
186 if self.all:
211 if self.all:
187 return not self.isdisjoint(against)
212 return not self.isdisjoint(against)
188 else:
213 else:
189 return self.issubset(against)
214 return self.issubset(against)
190
215
191
216
192 def as_dict(self):
217 def as_dict(self):
193 """Represent this dependency as a dict. For json compatibility."""
218 """Represent this dependency as a dict. For json compatibility."""
194 return dict(
219 return dict(
195 dependencies=list(self),
220 dependencies=list(self),
196 all=self.all,
221 all=self.all,
197 success=self.success,
222 success=self.success,
198 failure=self.failure
223 failure=self.failure
199 )
224 )
200
225
201
226
202 __all__ = ['depend', 'require', 'dependent', 'Dependency']
227 __all__ = ['depend', 'require', 'dependent', 'Dependency']
203
228
General Comments 0
You need to be logged in to leave comments. Login now