4
__all__ = ['inject', 'import_patched', 'monkey_patch', 'is_monkey_patched']
6
__exclude = set(('__builtins__', '__file__', '__name__'))
8
class SysModulesSaver(object):
9
"""Class that captures some subset of the current state of
10
sys.modules. Pass in an iterator of module names to the
12
def __init__(self, module_names=()):
15
self.save(*module_names)
17
def save(self, *module_names):
18
"""Saves the named modules to the object."""
19
for modname in module_names:
20
self._saved[modname] = sys.modules.get(modname, None)
23
"""Restores the modules that the saver knows about into
27
for modname, mod in self._saved.iteritems():
29
sys.modules[modname] = mod
32
del sys.modules[modname]
39
def inject(module_name, new_globals, *additional_modules):
40
"""Base method for "injecting" greened modules into an imported module. It
41
imports the module specified in *module_name*, arranging things so
42
that the already-imported modules in *additional_modules* are used when
43
*module_name* makes its imports.
45
*new_globals* is either None or a globals dictionary that gets populated
46
with the contents of the *module_name* module. This is useful when creating
47
a "green" version of some other module.
49
*additional_modules* should be a collection of two-element tuples, of the
50
form (<name>, <module>). If it's not specified, a default selection of
51
name/module pairs is used, which should cover all use cases but may be
52
slower because there are inevitably redundant or unnecessary imports.
54
patched_name = '__patched_module_' + module_name
55
if patched_name in sys.modules:
56
# returning already-patched module so as not to destroy existing
57
# references to patched modules
58
return sys.modules[patched_name]
60
if not additional_modules:
61
# supply some defaults
62
additional_modules = (
64
_green_select_modules() +
65
_green_socket_modules() +
66
_green_thread_modules() +
67
_green_time_modules())
68
#_green_MySQLdb()) # enable this after a short baking-in period
70
# after this we are gonna screw with sys.modules, so capture the
71
# state of all the modules we're going to mess with, and lock
72
saver = SysModulesSaver([name for name, m in additional_modules])
73
saver.save(module_name)
75
# Cover the target modules so that when you import the module it
76
# sees only the patched versions
77
for name, mod in additional_modules:
78
sys.modules[name] = mod
80
## Remove the old module from sys.modules and reimport it while
81
## the specified modules are in place
82
sys.modules.pop(module_name, None)
84
module = __import__(module_name, {}, {}, module_name.split('.')[:-1])
86
if new_globals is not None:
87
## Update the given globals dictionary with everything from this new module
88
for name in dir(module):
89
if name not in __exclude:
90
new_globals[name] = getattr(module, name)
92
## Keep a reference to the new module to prevent it from dying
93
sys.modules[patched_name] = module
95
saver.restore() ## Put the original modules back
100
def import_patched(module_name, *additional_modules, **kw_additional_modules):
101
"""Imports a module in a way that ensures that the module uses "green"
102
versions of the standard library modules, so that everything works
105
The only required argument is the name of the module to be imported.
110
*additional_modules + tuple(kw_additional_modules.items()))
113
def patch_function(func, *additional_modules):
114
"""Decorator that returns a version of the function that patches
115
some modules for the duration of the function call. This is
116
deeply gross and should only be used for functions that import
117
network libraries within their function bodies that there is no
118
way of getting around."""
119
if not additional_modules:
120
# supply some defaults
121
additional_modules = (
122
_green_os_modules() +
123
_green_select_modules() +
124
_green_socket_modules() +
125
_green_thread_modules() +
126
_green_time_modules())
128
def patched(*args, **kw):
129
saver = SysModulesSaver()
130
for name, mod in additional_modules:
132
sys.modules[name] = mod
134
return func(*args, **kw)
139
def _original_patch_function(func, *module_names):
140
"""Kind of the contrapositive of patch_function: decorates a
141
function such that when it's called, sys.modules is populated only
142
with the unpatched versions of the specified modules. Unlike
143
patch_function, only the names of the modules need be supplied,
144
and there are no defaults. This is a gross hack; tell your kids not
145
to import inside function bodies!"""
146
def patched(*args, **kw):
147
saver = SysModulesSaver(module_names)
148
for name in module_names:
149
sys.modules[name] = original(name)
151
return func(*args, **kw)
157
def original(modname):
158
""" This returns an unpatched version of a module; this is useful for
159
Eventlet itself (i.e. tpool)."""
160
# note that it's not necessary to temporarily install unpatched
161
# versions of all patchable modules during the import of the
162
# module; this is because none of them import each other, except
163
# for threading which imports thread
164
original_name = '__original_module_' + modname
165
if original_name in sys.modules:
166
return sys.modules.get(original_name)
168
# re-import the "pure" module and store it in the global _originals
169
# dict; be sure to restore whatever module had that name already
170
saver = SysModulesSaver((modname,))
171
sys.modules.pop(modname, None)
172
# some rudimentary dependency checking -- fortunately the modules
173
# we're working on don't have many dependencies so we can just do
174
# some special-casing here
175
deps = {'threading':'thread', 'Queue':'threading'}
177
dependency = deps[modname]
178
saver.save(dependency)
179
sys.modules[dependency] = original(dependency)
181
real_mod = __import__(modname, {}, {}, modname.split('.')[:-1])
182
if modname == 'Queue' and not hasattr(real_mod, '_threading'):
183
# tricky hack: Queue's constructor in <2.7 imports
184
# threading on every instantiation; therefore we wrap
185
# it so that it always gets the original threading
186
real_mod.Queue.__init__ = _original_patch_function(
187
real_mod.Queue.__init__,
189
# save a reference to the unpatched module so it doesn't get lost
190
sys.modules[original_name] = real_mod
194
return sys.modules[original_name]
197
def monkey_patch(**on):
198
"""Globally patches certain system modules to be greenthread-friendly.
200
The keyword arguments afford some control over which modules are patched.
201
If no keyword arguments are supplied, all possible modules are patched.
202
If keywords are set to True, only the specified modules are patched. E.g.,
203
``monkey_patch(socket=True, select=True)`` patches only the select and
204
socket modules. Most arguments patch the single module of the same name
205
(os, time, select). The exceptions are socket, which also patches the ssl
206
module if present; and thread, which patches thread, threading, and Queue.
208
It's safe to call monkey_patch multiple times.
210
accepted_args = set(('os', 'select', 'socket',
211
'thread', 'time', 'psycopg', 'MySQLdb'))
212
default_on = on.pop("all",None)
213
for k in on.iterkeys():
214
if k not in accepted_args:
215
raise TypeError("monkey_patch() got an unexpected "\
216
"keyword argument %r" % k)
217
if default_on is None:
218
default_on = not (True in on.values())
219
for modname in accepted_args:
220
if modname == 'MySQLdb':
221
# MySQLdb is only on when explicitly patched for the moment
222
on.setdefault(modname, False)
223
on.setdefault(modname, default_on)
225
modules_to_patch = []
226
patched_thread = False
227
if on['os'] and not already_patched.get('os'):
228
modules_to_patch += _green_os_modules()
229
already_patched['os'] = True
230
if on['select'] and not already_patched.get('select'):
231
modules_to_patch += _green_select_modules()
232
already_patched['select'] = True
233
if on['socket'] and not already_patched.get('socket'):
234
modules_to_patch += _green_socket_modules()
235
already_patched['socket'] = True
236
if on['thread'] and not already_patched.get('thread'):
237
patched_thread = True
238
modules_to_patch += _green_thread_modules()
239
already_patched['thread'] = True
240
if on['time'] and not already_patched.get('time'):
241
modules_to_patch += _green_time_modules()
242
already_patched['time'] = True
243
if on.get('MySQLdb') and not already_patched.get('MySQLdb'):
244
modules_to_patch += _green_MySQLdb()
245
already_patched['MySQLdb'] = True
246
if on['psycopg'] and not already_patched.get('psycopg'):
248
from eventlet.support import psycopg2_patcher
249
psycopg2_patcher.make_psycopg_green()
250
already_patched['psycopg'] = True
252
# note that if we get an importerror from trying to
253
# monkeypatch psycopg, we will continually retry it
254
# whenever monkey_patch is called; this should not be a
255
# performance problem but it allows is_monkey_patched to
256
# tell us whether or not we succeeded
261
for name, mod in modules_to_patch:
262
orig_mod = sys.modules.get(name)
264
orig_mod = __import__(name)
265
for attr_name in mod.__patched__:
266
patched_attr = getattr(mod, attr_name, None)
267
if patched_attr is not None:
268
setattr(orig_mod, attr_name, patched_attr)
270
# hacks ahead; this is necessary to prevent a KeyError on program exit
272
_patch_main_thread(sys.modules['threading'])
276
def _patch_main_thread(mod):
277
"""This is some gnarly patching specific to the threading module;
278
threading will always be initialized prior to monkeypatching, and
279
its _active dict will have the wrong key (it uses the real thread
280
id but once it's patched it will use the greenlet ids); so what we
281
do is rekey the _active dict so that the main thread's entry uses
282
the greenthread key. Other threads' keys are ignored."""
283
thread = original('thread')
284
curthread = mod._active.pop(thread.get_ident(), None)
286
import eventlet.green.thread
287
mod._active[eventlet.green.thread.get_ident()] = curthread
290
def is_monkey_patched(module):
291
"""Returns True if the given module is monkeypatched currently, False if
292
not. *module* can be either the module itself or its name.
294
Based entirely off the name of the module, so if you import a
295
module some other way than with the import keyword (including
296
import_patched), this might not be correct about that particular
298
return module in already_patched or \
299
getattr(module, '__name__', None) in already_patched
301
def _green_os_modules():
302
from eventlet.green import os
305
def _green_select_modules():
306
from eventlet.green import select
307
return [('select', select)]
309
def _green_socket_modules():
310
from eventlet.green import socket
312
from eventlet.green import ssl
313
return [('socket', socket), ('ssl', ssl)]
315
return [('socket', socket)]
317
def _green_thread_modules():
318
from eventlet.green import Queue
319
from eventlet.green import thread
320
from eventlet.green import threading
321
return [('Queue', Queue), ('thread', thread), ('threading', threading)]
323
def _green_time_modules():
324
from eventlet.green import time
325
return [('time', time)]
327
def _green_MySQLdb():
329
from eventlet.green import MySQLdb
330
return [('MySQLdb', MySQLdb)]
335
def slurp_properties(source, destination, ignore=[], srckeys=None):
336
"""Copy properties from *source* (assumed to be a module) to
337
*destination* (assumed to be a dict).
339
*ignore* lists properties that should not be thusly copied.
340
*srckeys* is a list of keys to copy, if the source's __all__ is
344
srckeys = source.__all__
345
destination.update(dict([(name, getattr(source, name))
348
name.startswith('__') or
353
if __name__ == "__main__":
357
execfile(sys.argv[0])