-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add extractCurrentResult() and observeCurrentResult() to synchronously interact with a Deferred's result. #590
Conversation
| @@ -52,6 +52,12 @@ class TimeoutError(Exception): | |||
| """ | |||
|
|
|||
|
|
|||
| class InvalidStateError(Exception): | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should maybe derive from BaseException instead so that a:
try:
undefer(d)
except Exception:
passDoes not catch an invalid undefer() call the same as an exception that came from the callbacks internal to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another thought about why this should come from BaseException:
There's not really much recovering from this. At best maybe you can run it through the event loop or synchronously drive it and then pass it through again... but if you can do that why didn't you do it in the first place?
|
(1) Patch needs a topfile news fragment, as specified here: https://twistedmatrix.com/trac/wiki/TwistedDevelopment#SubmittingaPatch (2) Patch needs to modify the tests to increase coverage. If you install the Codecov browser plugin |
|
@rodrigc Yea I know, I was hoping to get some feedback on the idea and implementation before I did that :) |
67454c0
to
75fac64
Compare
Current coverage is 90.18% (diff: 100%)@@ trunk #590 diff @@
==========================================
Files 843 843
Lines 147134 147265 +131
Methods 0 0
Messages 0 0
Branches 13016 13022 +6
==========================================
- Hits 134280 132817 -1463
- Misses 10621 12177 +1556
- Partials 2233 2271 +38
|
75fac64
to
44ec399
Compare
| @@ -159,6 +165,65 @@ def maybeDeferred(f, *args, **kw): | |||
| return succeed(result) | |||
|
|
|||
|
|
|||
| def unbox(deferred, raises=True, passthrough=True): | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about the passthrough argument here.
Essentially, the problem boils down to if you don't have passthrough=False then when the Deferred gets garbage collected if it was in a failure state then it prints a spurious traceback to stdout. This sees to be a problem that other utilities like gatherResults has had and they have a consumeErrors parameter which defaults to False but is documented that you should almost always use consumeErrors=True.
So I guess the question is, what behavior with results makes sense?
- Always pass through results, success or failure and if someone wants something different then they need to add their own callback that throws away the result.
- Always pass through successful results but consume any failure results under the assumption that if you've unboxed the
Deferredthen you've handled the failure (and if not, you can add your own callback that re-inserts it). - Never pass through any result and "break" the callback chain if someone continues to re-use the
Deferredinstance after you've unboxed it. If you want that to continue working then you'll need to add your own callback that re-inserts the result. - Add a
passthroughparameter that controls in both the success and the failure case whether or not it should be passed through or not.- If this is the answer, should we pass through results (success and failure) by default or not?
- Add a
consumeErrorsparameter that controls whether or notunbox()will consume errors.- Should this default to
Falseas the other utility functions do (though I think they do for backwards compatibility) or should this default toTrueto more likely do the right thing? - Should we always return success results then or should we always consume them? Does it make sense to add a
consumeSuccessesparameter for success results? If so what should it default to?
- Should this default to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After thinking about this more, I think the right answer is one of:
- Always pass through successful responses and always consume errors.
- Always pass through successful responses and add a
consumeErrorsparameter.- Defaulting to
Trueto do the right thing by default. - Defaulting to
Falseto matchgatherResultsand friends.
- Defaulting to
- Always consume everything.
The reasoning for this is that there's very little downside to passing through a successful result. At worst it will just hold a reference to it until the Deferred itself gets garbage collected. So it just always passes that through.
However, if you don't consume errors it prints spurious error results to stdout so there is an incentive to consume those and not pass them through. Adding a consumeErrors parameter aligns with other utilities and allows control on this.
The downside is errors and success ends up being treated differently although that could possibly be solved by consuming everything and treating unbox() as something you should generally only call on a Deferred that is no longer going to be kept around or used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intuition here is that:
def unbox(deferred, raises=True, consumeErrors=True):
...Is the "right" way forward, but I'm not as versed in what is normal in Twisted land.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think the behavior I expected before reading this (even though I'm no deferred expert) is to always raise, and always passthrough.
Someone who wanted raise=False can try/except and someone who wanted specifically the failure and not the exception sounds like they'll be unhappy but I think that's the "fault" of the interaction between failures and exceptions, not of your code here -- i.e., if that was a common use case, I'd expect to see an inverted failure object which is an exception that carries around the original failure.
As for passthrough which it sounds like is more important to address -- I'd have expected always passthrough for a bunch of reasons, some dogmatic and some practical
- I'm discarding the asymmetric options immediately because I'd find it surprising to treat failures differently than successes personally
- no side effects always being better than side effects
- the typical use case for me is like having a webapp which passes deferreds all around, and then I have some way of synchronizing those, but other internals there like "log deferred errors" still might be
addErrbacking the deferred I get my hands on, and I'd still want those to work
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I added raise=False was when I went to implement successResultOf, failureResultOf, and assertNoResult in terms it was hard to replicate the behavior of those functions without it:
successResultOfuses the failure's traceback printing mechanisms which seem to be subtly different than Python's built in ones. If we're happy with the output on failure changing slightly then this one isn't a problem.failureResultOfreturns the failure, which is impossible to do ifunbox()doesn't provide that information to it.failureResultOfwhen there are expected exception types has the same problem assucessResultOfwith the failure message relying on details of failure's traceback formatting.assertNoResultwants to silence failures but not successes, so the currentpassthroughparameter doesn't help it here, but aconsumeErrorswould.
In my own personal use cases I'm hoping to use the unbox() function in functions that can either be driven synchronously or asynchronously depending on what "fetcher" is passed into them (either sync or async). I want to just always return Deferred from these functions but I need a way to hide the fact that Deferreds are involved at all for end users who want to call the code synchronously. The expected way for this to look is something like (simplified):
import requests
import treq
def sync_fetcher(url):
resp = requests.get(url)
resp.raise_for_status()
return resp.content
def async_fetcher(url):
d = treq.get(url)
d.addCallback(treq.content)
return d
def api(thing, fetcher):
d = Deferred()
d.addCallback(fetcher)
d.callback("https://example.com/api/{}".format(thing))
return d
# When being used from async code
d = api("my-thing", fetcher=async_fetcher)
d.addCallback(print)
# When being used from sync code
print(unbox(api("my-thing", fetcher=sync_fetcher)))The goal is to essentially treat the unbox() function sort of like string encoding/decoding in that it turns the code into "normal" sync like code that hides the fact Deferreds are in play. However, if errors don't get consumed then the above code is going to print a spurious error to stdout when the Deferred gets garbage collected. The way to work around this would be:
d = api("my-thing", fetcher=sync_fetcher)
try:
print(unbox(d))
finally:
d.addErrBack(lambda _: None)Which fails at my goal of being able to tell people with "normal" sync looking code to just wrap the Deferred returning functions with an unbox() and forget about it.
| @@ -52,6 +52,12 @@ class TimeoutError(Exception): | |||
| """ | |||
|
|
|||
|
|
|||
| class InvalidStateError(BaseException): | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name here sounds a bit too general / risky to have it start being used by other things if all it currently means is e.g. UnboxingWithoutResult
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll switch it to UnboxingWithoutResult.
| Unbox a L{Deferred} that has already been fully processed. | ||
|
|
||
| Take an already processed (i.e. no longer waiting on any callbacks to be | ||
| fired) and unbox it. If the result is a L{Failure}, then it will re-raise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a word here (deferred), and I think it'd be a bit nicer to flip the order of the english sentence so that you talk about the happy normal case first, and then "it reraises" is your otherwise at the end.
| @@ -159,6 +165,65 @@ def maybeDeferred(f, *args, **kw): | |||
| return succeed(result) | |||
|
|
|||
|
|
|||
| def unbox(deferred, raises=True, passthrough=True): | |||
| """ | |||
| Unbox a L{Deferred} that has already been fully processed. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do other places use the lingo "fully processed", I'm not experienced enough to know, but I think common vernacular if that's what you mean is "that has already fired its result" or the like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't completely happy with the "has already fired its result" because of the behavior of:
d = Deferred()
d2 = Deferred()
d.addCallback(lambda _: d2)
d.callback(None)Has the Deferred d "fired" its result? It's had callback() called on it so it seems to me like it had, but it's been chained to another Deferred that has not yet fired its result. Ultimately I don't really care much what the terminology is, I was just worried about that being confusing.
| B{This function CANNOT take a Deferred with pending asynchronous work to be | ||
| done and make it synchronous. It can ONLY unbox a Deferred which already | ||
| has successfully completed.} | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think even if you don't borrow the existing terminology that at least linking to successResultOf might be a nice crosslink.
| @@ -645,6 +645,121 @@ def test_cancelGatherResultsWithAllDeferredsCallback(self): | |||
| self.assertEqual(callbackResult[1], "Callback Result") | |||
|
|
|||
|
|
|||
| def test_unbox(self): | |||
| """ | |||
| L{defer.unbox} should unbox a Deferred that has already had it's | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its
| @@ -159,6 +165,65 @@ def maybeDeferred(f, *args, **kw): | |||
| return succeed(result) | |||
|
|
|||
|
|
|||
| def unbox(deferred, raises=True, passthrough=True): | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think the behavior I expected before reading this (even though I'm no deferred expert) is to always raise, and always passthrough.
Someone who wanted raise=False can try/except and someone who wanted specifically the failure and not the exception sounds like they'll be unhappy but I think that's the "fault" of the interaction between failures and exceptions, not of your code here -- i.e., if that was a common use case, I'd expect to see an inverted failure object which is an exception that carries around the original failure.
As for passthrough which it sounds like is more important to address -- I'd have expected always passthrough for a bunch of reasons, some dogmatic and some practical
- I'm discarding the asymmetric options immediately because I'd find it surprising to treat failures differently than successes personally
- no side effects always being better than side effects
- the typical use case for me is like having a webapp which passes deferreds all around, and then I have some way of synchronizing those, but other internals there like "log deferred errors" still might be
addErrbacking the deferred I get my hands on, and I'd still want those to work
| else: | ||
| [result] = results | ||
|
|
||
| if raises and isinstance(result, failure.Failure): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you'll run into trouble here if you have a function that callbacks with a failure (but maybe that will already fail in a place you don't control).
But like, doing:
result, failure = [], []
and then addCallback / addErrback would let you distinguish "properly".
Also I think you need tests both for this specifically and for the BaseException vs. Exception distinction (unless I missed an existing one for the latter).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added a test for the BaseException versus Exception distinction. However to the best I can tell Deferred itself treats Deferred().errback() the exact same as Deferred().callback() other than constructing the original Failure instance that will be passed through the chain.
IOW, it's impossible to have a Deferred that returns a Failure.
|
|
||
| def test_unboxRaisesFailureException(self): | ||
| """ | ||
| L{defer.unbox} should raise the original exception if the result of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/should raise/raises (and same with below)
I think generally AIUI we try to follow https://jml.io/pages/test-docstrings.html
| """ | ||
| L{defer.unbox} should return the failure instance if raises=False. | ||
| """ | ||
| d = defer.Deferred() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might solve your initial motivation to add passthrough if you def cbDeferred(self): d = Deferred(); d.callback(1234); return d and def ebDeferred(self, exception): d = Deferred(); d.addErrback(lambda _: None); d.errback(exception); return d and did your lambda _: None there in the latter so you didn't have to keep repeating it.
| @@ -0,0 +1,2 @@ | |||
| Added twisted.internet.defer.unbox function to enable the unboxing of a | |||
| "resolved" Deferred. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like yet a 3rd language here on what we call "a deferred that's already fired".
|
Just putting this here for full context, some stream of consciousness discussion from IRC: |
5104d81
to
b6c5c18
Compare
|
Github hid the original comment, but I think the main open questions are around raising the exception versus returning the failure and how to handle passthrough. You can see the original comments and follow up comments at #590 (review). |
|
This is a very frequently requested feature. It's been turned down many times in the past. It'd be nice to see some discussion here of why it makes sense to add it now when it never did before. |
|
@exarkun Do you have any link to relevant discussions in the past? I tried searching the bug tracker but couldn't find any tickets open or otherwise but I don't think there is an obvious terminology for what this is called to search by. Since I don't have any context about why it was turned down previously I can't make an argument about why it makes sense now when it never did before. I can tell you what my use case is though (and I did earlier, though I'm happy to reiterate), and @Julian had a similar use case. I also see some similar looking code in https://github.com/twisted/tubes/blob/master/tubes/undefer.py though not exactly the same since there is a |
|
I don't have any links, sorry. I'm sure there is some discussion in the twisted-python archives but it's pretty hard to find anything there. The gist of the argument against this functionality is that in a normal program, the only way you know a Deferred has a result for you is if you are a callback on that Deferred and you have just been called. In that case, you don't need this "unbox" function because the result is passed to you as an argument. Any other time you might call such an "unbox" API, you have no idea if the Deferred has a result for you or not. The only way you could use this API with any chance of success is to poll "unbox" and handle the "UnboxingWithoutResult" you get repeatedly. @radix wrote https://github.com/radix/synchronous-deferred to solve the kind of problem issue 8900 described. Apparently from his experience with that he decided any effect-like library is a better approach to the problem. Twisted employs this give-me-the-result pattern in trial to help write tests where you actually can know when there's a result (or if you're wrong, an exception is great, then your test can fail). I don't recall any other places it's used, I'd be interested to look at them if you point them out. The |
|
@exarkun For my use case, I'm using In my use case I can pretty clearly say that either the |
|
If they write code that uses |
|
@exarkun Right, but my library will work both ways. They can either use it synchronously with |
|
It doesn't seem convincing that, because the API can be used with some Deferreds from one library when used in one mode, the API should be added to Twisted so it is available for use with all Deferreds. Maybe you should put this API in your library? Then it's close at hand for the audience to which it is relevant. |
|
@exarkun If I have to do something like that I might, though if this keeps coming up and there are at least two people in the last day or two alone that have need for it, it seems like having multiple copies of this function in different libraries and applications is less than ideal. |
|
I'll admit I haven't yet wrapped my head entirely around @dstufft's use case, mostly because I'm so deep in my own (which only happened to touch on this since I did implement it locally as a one off) but -- @exarkun it does happen to be that my own use case is in testing, but not in a place where I have access to a test case per se (I have a testing helper, and a lack of desire to write code that looks like I do obviously believe your skepticism though and again I haven't properly understood @dstufft's use case myself yet -- does it make you feel any less skeptical if this stayed in |
|
As a test helper, I'm believe it's useful (at least, I don't know of a better approach). I'm pretty sure I'm even the one who committed |
|
@exarkun that was a leading question because I knew you were the one :P And yep full agreement on the unittest thing. @dstufft how dirty does it make you feel to say what we're actually doing is improving the testing helpers, and then if you're really convinced that your API is how it should be, that you just use those in real code illicitly :D? Sorry for the runaround, it's what happens when you have to deal with someone [me] who's not really an expert in twisted lore (cough). |
|
That sounds like a separate question to me -- I don't know what @hawkowl's plans are, but I'd assume / hope that |
|
(FWIW personally I think the right next step rather than yanking your chain and asking for work that may or may not benefit you is for someone to actually understand your use case specifically and then make a call on whether it sounds like this is the right thing to add for it, but I still haven't done so, sorry :/) |
|
This started with me going "Does anyone have any good patterns for making an API that can be used synchronously and asynchronously?". Then @hawkowl suggested just using @attr.s
class URLResponse(object):
url = attr.ib()
response = attr.ib()
headers = attr.ib()
def do_something_with_content(resp):
print("got url", resp.url)
return resp
@inlineCallbacks
def get_async(url):
import treq
resp = yield treq.get(url)
content = yield resp.content()
return URLResponse(url=url, headers=resp.headers, content=content)
def get_sync(url):
import requests
resp = requests.get(url)
return URLResponse(url=url, headers=resp.headers, content=resp.content)
def _api(url, fetcher):
d = Deferred()
d.addCallback(lambda _: fetcher(url))
d.addCallback(do_something_with_content)
d.callback(None)
return d
def async_api(url):
return _api(url, get_async)
def sync_api(url):
return _api(url, get_sync)Which is great, it does basically everything I need, except that it forces every consumer of the library to write their software using the I'm trying to make something that both sync and async users can consume and I'm even willing to add a slight annoyance to sync users to make it happen. Currently though without something like this available (either in my own library or in Twisted or whatever) it feels like the cost of using I'm not sure how else to really describe my use case. To me it feels fairly obvious but I am probably too close to the problem. |
|
|
||
| @raise UnboxingWithoutResult: If C{deferred} has pending work to be done | ||
| and has no result to unbox. | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an @SInCE marker would be useful here.
|
Just a note since @glyph called it out explicitly: This PR already has reimplemented I've also renamed |
|
FWIW, I have seen 'current' result used elsewhere when referring to a Deferred. |
9f4b9b2
to
3f91ec5
Compare
|
@glyph It would probably be good to capture some of that thought process somewhere easier to find. What are the chances future maintainers of this API are going to come read the full PR discussion? |
|
@exarkun Where would that get captured at? I did add a little bit to the doc string but I'm not sure if there is a better place? |
|
@dstufft The docstring seems like it covers the right stuff now. Part of glyph's point was that there's a bigger picture here and that's guiding certain decisions. It seems like that big picture should be capture somewhere (else, it's difficult for people who weren't in the referenced conversations to be guided by it). This feels more like some kind of "vision" doc than anything else. Maybe it's just a blog post on labs, maybe it's a wiki page. |
e0be89f
to
52897b8
Compare
Agreed. I wrote it here first because I don't know where the right place is to get this in front of maintainers. I started thinking about that but figured I'd better just get my thoughts down here given the opportunity, they can be copied and pasted elsewhere without much trouble :) In the future, stuff like this really needs to go on the mailing list as it is discussed. (Which is totally my bad.) Especially if we have an in-person discussion that might affect the project, someone needs to write it up and give everyone a heads up. Generally we need to use the ML more, especially as cross-project discussions fracture across multiple different IRC channels. In the future I'll try to fire off a message when each discussion occurs. I think it might be a little premature to put it somewhere official-looking like labs.tm or the docs, but a wiki page might be appropriate at this point. |
|
As I was writing that, I realized that a simple link might do the trick. I posted to the mailing list here about this thread. |
|
There is a typo in the docstring - look for "originak" |
be666df
to
f939320
Compare
|
Another thought about this PR: With this function avoiding the foot-gun nature of extracting a synchronous result as much as possible, the ill-considered |
|
@glyph presumably that would be another PR? |
|
Oh, yes, absolutely. Just noting it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I've been meaning to get to this for a while now, sorry it took so long. I'll try to be quicker on the next iteration.
| @@ -53,6 +53,13 @@ class TimeoutError(Exception): | |||
|
|
|||
|
|
|||
|
|
|||
| class NoCurrentResult(BaseException): | |||
| """ | |||
| This error is raised when attempting to call extractCurrentResulton a | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could be L{extractCurrentResult}. Also, missing space before the "on" here.
| class NoCurrentResult(BaseException): | ||
| """ | ||
| This error is raised when attempting to call extractCurrentResulton a | ||
| L{Deferred} that has not yet fired all of its callbacks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typically, we say the Deferred fires or is fired. The callbacks are just called, I think. Unless the lingo has moved on while I wasn't paying attention.
|
|
||
| Take a deferred that has already fired all of its callbacks and extract and | ||
| return the result of the L{Deferred}. This process of extracting the result | ||
| will, by default, consume the result, error or not unless the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ... consume the result - error or not - unless the .... Certainly not just a single comma before the "error or not", though. The aside is just "error or not" and it should be delimited on both ends by some punctuation. This could still be commas but there's already a lot of commas here so I think dashes would be less confusing.
| Take a deferred that has already fired all of its callbacks and extract and | ||
| return the result of the L{Deferred}. This process of extracting the result | ||
| will, by default, consume the result, error or not unless the | ||
| L{passthrough} parameter is set to L{True}. If the L{Deferred} is in an |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"has an error result"? "is in an error" seems weird.
|
|
||
| The primary purpose of this function is to allow code to be written that is | ||
| internally asynchronous but which can be consumed without an event loop in | ||
| an synchronous fashion by using L{extractCurrentResult} at the boundaries. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"a synchronous ..."
| d.callback(result) | ||
|
|
||
| self.assertIs(defer.extractCurrentResult(d), result) | ||
| self.assertIsNot(defer.extractCurrentResult(d), result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert that the result becomes None perhaps? Asserting it's not the result leaves the field wide open for what the behavior actually is. I don't see any reason to let the test suite leave the intent this wide.
| result = d.result | ||
|
|
||
| self.assertIs(defer.extractCurrentResult(d, raises=False), result) | ||
| self.assertIsNot(defer.extractCurrentResult(d), result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto comment for the non-error case.
| @@ -0,0 +1,2 @@ | |||
| Added twisted.internet.defer.extractCurrentResult function to enable extracting | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The style guide for news fragments suggests "The new function twisted.internet.defer.extractCurrentResult ..."
| "Success result expected on %r, found no result instead" % ( | ||
| deferred,)) | ||
| elif isinstance(result[0], failure.Failure): | ||
| "Success result expected {0!r}, found no result " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This dropped the "on" which is important to the meaning.
| "found failure result instead:\n%s" % ( | ||
| deferred, result[0].getTraceback())) | ||
| "Success result expected on {0!r}, " | ||
| "found failure result instead:\n{1}".format( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason to use {0} and {1} instead of just {}?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Habit for Python 2.6 compatibility.
Reimplement SynchronousTestCase.successResultOf, SynchronousTestCase.failureResultOf, and SynchronousTestCase.noResult using defer.extractCurrentResult instead of custom one off code.
f939320
to
b2a120c
Compare
b2a120c
to
a4aae36
Compare
|
Just as a note, I've taken the action that @exarkun asked for and I've removed the There are now two functions, |
|
Thanks. See #690. |
Ticket: https://twistedmatrix.com/trac/ticket/8900