def test_arg_replacer_method(self): replacer = ArgReplacer(cythonapp.AClass().method_with_args, 'two') args = (1, 'old', 3) kwargs = {} self.assertEqual(replacer.get_old_value(args, kwargs), 'old') self.assertEqual(replacer.replace('new', args, kwargs), ('old', [1, 'new', 3], {}))
def _auth_return_future(f): """Similar to tornado.concurrent.return_future, but uses the auth module's legacy callback interface. Note that when using this decorator the ``callback`` parameter inside the function will actually be a future. """ replacer = ArgReplacer(f, 'callback') @functools.wraps(f) def wrapper(*args, **kwargs): future = TracebackFuture() callback, args, kwargs = replacer.replace(future, args, kwargs) if callback is not None: future.add_done_callback( functools.partial(_auth_future_to_callback, callback)) def handle_exception(typ, value, tb): if future.done(): return False else: future.set_exc_info((typ, value, tb)) return True with ExceptionStackContext(handle_exception): f(*args, **kwargs) return future return wrapper
def _iostream_return_future(f): """Similar to tornado.concurrent.return_future, but the Future will also raise a StreamClosedError if the stream is closed before it resolves. Unlike return_future (and _auth_return_future), no Future will be returned if a callback is given. """ replacer = ArgReplacer(f, 'callback') @functools.wraps(f) def wrapper(*args, **kwargs): if replacer.get_old_value(args, kwargs) is not None: # If a callaback is present, just call in to the decorated # function. This is a slight optimization (by not creating a # Future that is unlikely to be used), but mainly avoids the # complexity of running the callback in the expected way. return f(*args, **kwargs) future = TracebackFuture() callback, args, kwargs = replacer.replace( lambda value=None: future.set_result(value), args, kwargs) f(*args, **kwargs) stream = args[0] stream._pending_futures.add(future) future.add_done_callback( lambda fut: stream._pending_futures.discard(fut)) return future return wrapper
def _non_deprecated_return_future(f, warn=False): # Allow auth.py to use this decorator without triggering # deprecation warnings. This will go away once auth.py has removed # its legacy interfaces in 6.0. replacer = ArgReplacer(f, 'callback') @functools.wraps(f) def wrapper(*args, **kwargs): future = Future() callback, args, kwargs = replacer.replace( lambda value=_NO_RESULT: future_set_result_unless_cancelled(future, value), args, kwargs) def handle_error(typ, value, tb): future_set_exc_info(future, (typ, value, tb)) return True exc_info = None esc = ExceptionStackContext(handle_error, delay_warning=True) with esc: if not warn: # HACK: In non-deprecated mode (only used in auth.py), # suppress the warning entirely. Since this is added # in a 5.1 patch release and already removed in 6.0 # I'm prioritizing a minimial change instead of a # clean solution. esc.delay_warning = False try: result = f(*args, **kwargs) if result is not None: raise ReturnValueIgnoredError( "@return_future should not be used with functions " "that return values") except: exc_info = sys.exc_info() raise if exc_info is not None: # If the initial synchronous part of f() raised an exception, # go ahead and raise it to the caller directly without waiting # for them to inspect the Future. future.result() # If the caller passed in a callback, schedule it to be called # when the future resolves. It is important that this happens # just before we return the future, or else we risk confusing # stack contexts with multiple exceptions (one here with the # immediate exception, and again when the future resolves and # the callback triggers its exception by calling future.result()). if callback is not None: warnings.warn("callback arguments are deprecated, use the returned Future instead", DeprecationWarning) def run_callback(future): result = future.result() if result is _NO_RESULT: callback() else: callback(future.result()) future_add_done_callback(future, wrap(run_callback)) return future return wrapper
class ArgReplacerTest(unittest.TestCase): def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, 'callback') def test_omitted(self): self.assertEqual(self.replacer.replace('new', (1, 2), dict()), (None, (1, 2), dict(callback='new'))) def test_position(self): self.assertEqual(self.replacer.replace('new', (1, 2, 'old', 3), dict()), ('old', [1, 2, 'new', 3], dict())) def test_keyword(self): self.assertEqual(self.replacer.replace('new', (1,), dict(y=2, callback='old', z=3)), ('old', (1,), dict(y=2, callback='new', z=3)))
class ArgReplacerTest(unittest.TestCase): def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, 'callback') def test_omitted(self): self.assertEqual(self.replacer.replace('new', (1, 2), dict()), (None, (1, 2), dict(callback='new'))) def test_position(self): self.assertEqual( self.replacer.replace('new', (1, 2, 'old', 3), dict()), ('old', [1, 2, 'new', 3], dict())) def test_keyword(self): self.assertEqual( self.replacer.replace('new', (1, ), dict(y=2, callback='old', z=3)), ('old', (1, ), dict(y=2, callback='new', z=3)))
class ArgReplacerTest(unittest.TestCase): def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, 'callback') def test_omitted(self): args = (1, 2) kwargs = dict() self.assertIs(self.replacer.get_old_value(args, kwargs), None) self.assertEqual(self.replacer.replace('new', args, kwargs), (None, (1, 2), dict(callback='new'))) def test_position(self): args = (1, 2, 'old', 3) kwargs = dict() self.assertEqual(self.replacer.get_old_value(args, kwargs), 'old') self.assertEqual(self.replacer.replace('new', args, kwargs), ('old', [1, 2, 'new', 3], dict())) def test_keyword(self): args = (1,) kwargs = dict(y=2, callback='old', z=3) self.assertEqual(self.replacer.get_old_value(args, kwargs), 'old') self.assertEqual(self.replacer.replace('new', args, kwargs), ('old', (1,), dict(y=2, callback='new', z=3)))
class ArgReplacerTest(unittest.TestCase): def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, "callback") def test_omitted(self): args = (1, 2) kwargs = dict() # type: Dict[str, Any] self.assertIs(self.replacer.get_old_value(args, kwargs), None) self.assertEqual( self.replacer.replace("new", args, kwargs), (None, (1, 2), dict(callback="new")), ) def test_position(self): args = (1, 2, "old", 3) kwargs = dict() # type: Dict[str, Any] self.assertEqual(self.replacer.get_old_value(args, kwargs), "old") self.assertEqual( self.replacer.replace("new", args, kwargs), ("old", [1, 2, "new", 3], dict()), ) def test_keyword(self): args = (1, ) kwargs = dict(y=2, callback="old", z=3) self.assertEqual(self.replacer.get_old_value(args, kwargs), "old") self.assertEqual( self.replacer.replace("new", args, kwargs), ("old", (1, ), dict(y=2, callback="new", z=3)), )
class ArgReplacerTest(unittest.TestCase): def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, 'callback') def test_omitted(self): args = (1, 2) kwargs = dict() self.assertIs(self.replacer.get_old_value(args, kwargs), None) self.assertEqual(self.replacer.replace('new', args, kwargs), (None, (1, 2), dict(callback='new'))) def test_position(self): args = (1, 2, 'old', 3) kwargs = dict() self.assertEqual(self.replacer.get_old_value(args, kwargs), 'old') self.assertEqual(self.replacer.replace('new', args, kwargs), ('old', [1, 2, 'new', 3], dict())) def test_keyword(self): args = (1, ) kwargs = dict(y=2, callback='old', z=3) self.assertEqual(self.replacer.get_old_value(args, kwargs), 'old') self.assertEqual(self.replacer.replace('new', args, kwargs), ('old', (1, ), dict(y=2, callback='new', z=3)))
class ArgReplacerTest(unittest.TestCase): def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, "callback") def test_omitted(self): args = (1, 2) kwargs = dict() # type: Dict[str, Any] self.assertIs(self.replacer.get_old_value(args, kwargs), None) self.assertEqual( self.replacer.replace("new", args, kwargs), (None, (1, 2), dict(callback="new")), ) def test_position(self): args = (1, 2, "old", 3) kwargs = dict() # type: Dict[str, Any] self.assertEqual(self.replacer.get_old_value(args, kwargs), "old") self.assertEqual( self.replacer.replace("new", args, kwargs), ("old", [1, 2, "new", 3], dict()), ) def test_keyword(self): args = (1,) kwargs = dict(y=2, callback="old", z=3) self.assertEqual(self.replacer.get_old_value(args, kwargs), "old") self.assertEqual( self.replacer.replace("new", args, kwargs), ("old", (1,), dict(y=2, callback="new", z=3)), )
def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, 'callback')
def return_future(f): """Decorator to make a function that returns via callback return a `Future`. The wrapped function should take a ``callback`` keyword argument and invoke it with one argument when it has finished. To signal failure, the function can simply raise an exception (which will be captured by the `.StackContext` and passed along to the ``Future``). From the caller's perspective, the callback argument is optional. If one is given, it will be invoked when the function is complete with `Future.result()` as an argument. If the function fails, the callback will not be run and an exception will be raised into the surrounding `.StackContext`. If no callback is given, the caller should use the ``Future`` to wait for the function to complete (perhaps by yielding it in a `.gen.engine` function, or passing it to `.IOLoop.add_future`). Usage: .. testcode:: @return_future def future_func(arg1, arg2, callback): # Do stuff (possibly asynchronous) callback(result) @gen.engine def caller(callback): yield future_func(arg1, arg2) callback() .. Note that ``@return_future`` and ``@gen.engine`` can be applied to the same function, provided ``@return_future`` appears first. However, consider using ``@gen.coroutine`` instead of this combination. """ replacer = ArgReplacer(f, 'callback') @functools.wraps(f) def wrapper(*args, **kwargs): future = TracebackFuture() callback, args, kwargs = replacer.replace( lambda value=_NO_RESULT: future.set_result(value), args, kwargs) def handle_error(typ, value, tb): future.set_exc_info((typ, value, tb)) return True exc_info = None with ExceptionStackContext(handle_error): try: result = f(*args, **kwargs) if result is not None: raise ReturnValueIgnoredError( "@return_future should not be used with functions " "that return values") except: exc_info = sys.exc_info() raise if exc_info is not None: # If the initial synchronous part of f() raised an exception, # go ahead and raise it to the caller directly without waiting # for them to inspect the Future. future.result() # If the caller passed in a callback, schedule it to be called # when the future resolves. It is important that this happens # just before we return the future, or else we risk confusing # stack contexts with multiple exceptions (one here with the # immediate exception, and again when the future resolves and # the callback triggers its exception by calling future.result()). if callback is not None: def run_callback(future): result = future.result() if result is _NO_RESULT: callback() else: callback(future.result()) future.add_done_callback(wrap(run_callback)) return future return wrapper
def setUp(self): def function(x, y, callback=None, z=None): pass self.replacer = ArgReplacer(function, "callback")
def return_future(f): """Decorator to make a function that returns via callback return a `Future`. 装饰器, 通过 callback, 使 function 返回 一个 Future The wrapped function should take a ``callback`` keyword argument and invoke it with one argument when it has finished. To signal failure, the function can simply raise an exception (which will be captured by the `.StackContext` and passed along to the ``Future``). 包装的 function 必须有一个 callback 关键字参数 而且在 function 结束时 传入一个参数调用 callback. 为了通知 failure, 该 function 简单地 raise 一个 exception (它会被 .StackContext 捕获, 并且被传递到 Future ). From the caller's perspective, the callback argument is optional. If one is given, it will be invoked when the function is complete with `Future.result()` as an argument. If the function fails, the callback will not be run and an exception will be raised into the surrounding `.StackContext`. 从 caller 的角度看, callback 参数是可选的. 如果给定, callback 会在 function 完成时执行 Future.result() 作为参数被调用. 如果 function 执行失败, callback 不会执行, 而且 exception 被 raised 进 .StackContext If no callback is given, the caller should use the ``Future`` to wait for the function to complete (perhaps by yielding it in a `.gen.engine` function, or passing it to `.IOLoop.add_future`). 如果 没有 callback 指定, caller 调用者会使用 Future 等待 function 完成 (可能被 yielding 在 .gen.engine function, 或者传递到 .IOLoop.add_future) Usage:: @return_future def future_func(arg1, arg2, callback): # Do stuff (possibly asynchronous) callback(result) @gen.engine def caller(callback): yield future_func(arg1, arg2) callback() Note that ``@return_future`` and ``@gen.engine`` can be applied to the same function, provided ``@return_future`` appears first. However, consider using ``@gen.coroutine`` instead of this combination. 注意: @return_future 和 @gen.engine 可以应用于相同的 function, 先使用 @return_future. 然而, 可以考虑使用 @gen.coroutine 替代以上组合. """ replacer = ArgReplacer(f, 'callback') @functools.wraps(f) def wrapper(*args, **kwargs): future = TracebackFuture() callback, args, kwargs = replacer.replace( lambda value=_NO_RESULT: future.set_result(value), args, kwargs) def handle_error(typ, value, tb): future.set_exc_info((typ, value, tb)) return True exc_info = None with ExceptionStackContext(handle_error): try: result = f(*args, **kwargs) if result is not None: raise ReturnValueIgnoredError( "@return_future should not be used with functions " "that return values") except: exc_info = sys.exc_info() raise # with 语句抛出的异常不会直接继续抛出, 因为 handle_error 一直返回 True, # ExceptionStackContext 类里的 __exit__ 函数处理异常时, 调用的是 handle_error if exc_info is not None: # 这里才真正处理异常 # If the initial synchronous part of f() raised an exception, # go ahead and raise it to the caller directly without waiting # for them to inspect the Future. # 4.1 继续执行 future.result() future.result() # If the caller passed in a callback, schedule it to be called # when the future resolves. It is important that this happens # just before we return the future, or else we risk confusing # stack contexts with multiple exceptions (one here with the # immediate exception, and again when the future resolves and # the callback triggers its exception by calling future.result()). """ 如果 caller 传递进一个 callback, 另它在 future 解析时被调用. 在我们返回 future 之前, 这是非常重要, 否则我们冒险地将多个 exception 混淆了 stack context(这里一个紧急的 exception, 而 future 解析时又一个, 且 callback 在调用 future.result() 时出发了它自己的 exception ) """ if callback is not None: def run_callback(future): result = future.result() if result is _NO_RESULT: callback() else: callback(future.result()) future.add_done_callback(wrap(run_callback)) return future return wrapper
def return_future(f): """Decorator to make a function that returns via callback return a `Future`. The wrapped function should take a ``callback`` keyword argument and invoke it with one argument when it has finished. To signal failure, the function can simply raise an exception (which will be captured by the `stack_context` and passed along to the `Future`). From the caller's perspective, the callback argument is optional. If one is given, it will be invoked when the function is complete with the `Future` as an argument. If no callback is given, the caller should use the `Future` to wait for the function to complete (perhaps by yielding it in a `gen.engine` function, or passing it to `IOLoop.add_future`). Usage:: @return_future def future_func(arg1, arg2, callback): # Do stuff (possibly asynchronous) callback(result) @gen.engine def caller(callback): yield future_func(arg1, arg2) callback() Note that ``@return_future`` and ``@gen.engine`` can be applied to the same function, provided ``@return_future`` appears first. """ replacer = ArgReplacer(f, 'callback') @functools.wraps(f) def wrapper(*args, **kwargs): future = Future() callback, args, kwargs = replacer.replace(future.set_result, args, kwargs) if callback is not None: future.add_done_callback(callback) def handle_error(typ, value, tb): future.set_exception(value) return True exc_info = None with ExceptionStackContext(handle_error): try: result = f(*args, **kwargs) if result is not None: raise ReturnValueIgnoredError( "@return_future should not be used with functions " "that return values") except: exc_info = sys.exc_info() raise if exc_info is not None: # If the initial synchronous part of f() raised an exception, # go ahead and raise it to the caller directly without waiting # for them to inspect the Future. raise_exc_info(exc_info) return future return wrapper