1
1
#!/usr/bin/env python
2
2
# -*- coding: utf-8 -*-
3
3
"""Simplified Twisted Deferreds."""
4
# Copyright (C) 2008-2009 Sebastian Heinlein <devel@glatzor.de>
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
# GNU General Public License for more details.
16
# You should have received a copy of the GNU General Public License along
17
# with this program; if not, write to the Free Software Foundation, Inc.,
18
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
4
# Copyright (C) 2008-2010 Sebastian Heinlein <devel@glatzor.de>
5
# Copyright (c) 2001-2010
10
# Apple Computer, Inc.
14
# Christopher Armstrong
19
# Itamar Shtull-Trauring
32
# Massachusetts Institute of Technology
35
# Pavel Pergamenshchik
38
# Software Freedom Conservancy
44
# Permission is hereby granted, free of charge, to any person obtaining
45
# a copy of this software and associated documentation files (the
46
# "Software"), to deal in the Software without restriction, including
47
# without limitation the rights to use, copy, modify, merge, publish,
48
# distribute, sublicense, and/or sell copies of the Software, and to
49
# permit persons to whom the Software is furnished to do so, subject to
50
# the following conditions:
52
# The above copyright notice and this permission notice shall be
53
# included in all copies or substantial portions of the Software.
55
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
56
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
57
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
58
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
59
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
60
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
63
__author__ = "Sebastian Heinlein <devel@glatzor.de>"
65
__all__ = ("Deferred", "AlreadyCalledDeferred", "DeferredException",
66
"defer", "dbus_deferred_method", "inline_callbacks", "return_value")
22
68
from functools import wraps
74
class _DefGen_Return(BaseException):
75
"""Exception to return a result from an inline callback."""
76
def __init__(self, value):
27
80
class AlreadyCalledDeferred(Exception):
28
81
"""The Deferred is already running a callback."""
472
def deferable_function(func):
473
"""Add a defer attribute to the decorated function. If the decorated
474
function has got a reply_handler and error_handler return a Deferred
475
and use its callback and errback as reply_handler and error_handler.
477
This decorator allows to easily make use of Deferreds in a DBus client.
479
def _deferable_function(*args, **kwargs):
480
if kwargs.pop("defer", False):
481
if "reply_handler" in kwargs and "error_handler" in kwargs:
482
deferred = Deferred()
483
kwargs["reply_handler"] = deferred.callback
484
kwargs["error_handler"] = deferred.errback
485
function(*args, **kwargs)
487
deferred = defer(func, *args, **kwargs)
489
return func(*args, **kwargs)
490
return _deferable_function
417
492
def _passthrough(arg):
495
def return_value(val):
497
Return val from a inline_callbacks generator.
499
Note: this is currently implemented by raising an exception
500
derived from BaseException. You might want to change any
501
'except:' clauses to an 'except Exception:' clause so as not to
502
catch this exception.
504
Also: while this function currently will work when called from
505
within arbitrary functions called from within the generator, do
506
not rely upon this behavior.
508
raise _DefGen_Return(val)
510
def _inline_callbacks(result, gen, deferred):
514
# This function is complicated by the need to prevent unbounded recursion
515
# arising from repeatedly yielding immediately ready deferreds. This while
516
# loop and the waiting variable solve that by manually unfolding the
519
waiting = [True, # waiting for result?
524
# Send the last result back as the result of the yield expression.
525
is_failure = isinstance(result, DeferredException)
527
result = gen.throw(result.type, result.value, result.traceback)
529
result = gen.send(result)
530
except StopIteration:
531
# fell off the end, or "return" statement
532
deferred.callback(None)
534
except _DefGen_Return, err:
535
# returnValue() was called; time to give a result to the original
536
# Deferred. First though, let's try to identify the potentially
537
# confusing situation which results when return_value() is
538
# accidentally invoked from a different function, one that wasn't
539
# decorated with @inline_callbacks.
541
# The traceback starts in this frame (the one for
542
# _inline_callbacks); the next one down should be the application
544
appCodeTrace = sys.exc_info()[2].tb_next
546
# If we invoked this generator frame by throwing an exception
547
# into it, then throwExceptionIntoGenerator will consume an
548
# additional stack frame itself, so we need to skip that too.
549
appCodeTrace = appCodeTrace.tb_next
550
# Now that we've identified the frame being exited by the
551
# exception, let's figure out if returnValue was called from it
552
# directly. returnValue itself consumes a stack frame, so the
553
# application code will have a tb_next, but it will *not* have a
555
if appCodeTrace.tb_next.tb_next:
556
# If returnValue was invoked non-local to the frame which it is
557
# exiting, identify the frame that ultimately invoked
558
# returnValue so that we can warn the user, as this behavior is
560
ultimateTrace = appCodeTrace
561
while ultimateTrace.tb_next.tb_next:
562
ultimateTrace = ultimateTrace.tb_next
563
filename = ultimateTrace.tb_frame.f_code.co_filename
564
lineno = ultimateTrace.tb_lineno
565
warnings.warn_explicit(
566
"returnValue() in %r causing %r to exit: "
567
"returnValue should only be invoked by functions decorated "
568
"with inlineCallbacks" % (
569
ultimateTrace.tb_frame.f_code.co_name,
570
appCodeTrace.tb_frame.f_code.co_name),
571
DeprecationWarning, filename, lineno)
572
deferred.callback(err.value)
578
if isinstance(result, Deferred):
579
# a deferred was yielded, get the result.
585
_inline_callbacks(res, gen, deferred)
587
result.add_callbacks(gotResult, gotResult)
589
# Haven't called back yet, set flag so that we get reinvoked
590
# and return from the loop
595
# Reset waiting to initial values for next loop. gotResult uses
596
# waiting, but this isn't a problem because gotResult is only
597
# executed once, and if it hasn't been executed yet, the return
598
# branch above would have been taken.
603
def inline_callbacks(func):
604
"""inline_callbacks helps you write Deferred-using code that looks like a
605
regular sequential function. For example:
608
thing = yield makeSomeRequestResultingInDeferred()
609
print thing #the result! hoorj!
610
thingummy = inline_callbacks(thingummy)
612
When you call anything that results in a Deferred, you can simply yield it;
613
your generator will automatically be resumed when the Deferred's result is
614
available. The generator will be sent the result of the Deferred with the
615
'send' method on generators, or if the result was a failure, 'throw'.
617
Your inline_callbacks-enabled generator will return a Deferred object, which
618
will result in the return value of the generator (or will fail with a
619
failure object if your generator raises an unhandled exception). Note that
620
you can't use return result to return a value; use return_value(result)
621
instead. Falling off the end of the generator, or simply using return
622
will cause the Deferred to have a result of None.
624
The Deferred returned from your deferred generator may errback if your
625
generator raised an exception:
628
thing = yield makeSomeRequestResultingInDeferred()
629
if thing == 'I love Twisted':
630
# will become the result of the Deferred
631
return_value('TWISTED IS GREAT!')
633
# will trigger an errback
634
raise Exception('DESTROY ALL LIFE')
635
thingummy = inline_callbacks(thingummy)
638
def unwind_generator(*args, **kwargs):
639
return _inline_callbacks(None, func(*args, **kwargs), Deferred())
640
return unwind_generator
420
643
# vim:tw=4:sw=4:et