Comments (17)
Hey, this is great. Thanks.
It had recently crossed my mind to see if I could support asyncio
but I hadn't put any real thought into it. I'm by no means an expert in asynchronous python. It is great that you have put some real thought into how it might work.
I'm inclined to explore if we could roll async support into the core library (option 1. above) but I don't yet have a clear sense of what that entails. I'm quite fond of the current API and I wouldn't want to do it if it ended up polluting it with clunky options or an undue expansion of its surface.
A few questions off the top of my head:
- Could the API potentially be exactly the same and the implementation switching based only on whether the the target and handlers functions are coroutines? Or would we need something more explicit?
- Do you know of any other libraries that support something similar? i.e. is there a best practice out there already for this sort of thing?
- If the decorated target is a coroutine, does that imply that the handlers have to be too? Can those be mix and matched?
- Some keywords args can also be callable so their values can be evaluated at runtime. Would we have to or want to support async for those as well?
from backoff.
Could the API potentially be exactly the same and the implementation switching based only on whether the the target and handlers functions are coroutines? Or would we need something more explicit?
Yes, API can left exactly the same. You can see in #23 that API of on_predicate
/on_exception
hasn't changed (they are still normal decorators with the same parameters as in backoff
).
Do you know of any other libraries that support something similar? i.e. is there a best practice out there already for this sort of thing?
Other libraries that implements exponential and other backoffs functionality? I haven't found any compatible with asyncio.
If the decorated target is a coroutine, does that imply that the handlers have to be too? Can those be mix and matched?
Handlers can be regular functions even if target is coroutine.
From coroutines you can freely call regular functions with the exception that those functions must be non-blocking (i.e. time.sleep()
, long computations, blocking networking are not allowed).
In #23 I explicitly convert handlers to coroutines, which should work assuming that handler doesn't do any blocking operations.
Some keywords args can also be callable so their values can be evaluated at runtime. Would we have to or want to support async for those as well?
It depends. Support both sync and async versions of them is easy, but I don't think it's feasible to have them async --- is meaningful for them to read/compute value from network/database (i.e. value obtaining may require async operation)?
I'm inclined to explore if we could roll async support into the core library (option 1. above) but I don't yet have a clear sense of what that entails. I'm quite fond of the current API and I wouldn't want to do it if it ended up polluting it with clunky options or an undue expansion of its surface.
External interface can be left the same, as seen in #23 . But implementation of on_predicate
/on_exception
will be either messy with conditions/switches, or be duplicated.
I see that on_predicate
and on_exception
shares a lot in common, but you haven't merged their implementations (probably because it will lead to messy code).
If we decide to left sync and async implementations of on_predicate
/on_exception
in separate function it should be quite easy to implement. Something like:
def on_predicate(...):
...
def decorate(target):
@functools.wraps(target)
def retry_sync(*args, **kwargs):
...
@functools.wraps(target)
@asyncio.coroutine
def retry_async(*args, **kwargs):
...
if asyncio.iscouroutinefunction(target):
return retry_async
else:
return retry_sync
IMO I see two things relatively complicated:
-
Trying to extract common functionality from sync/async versions of
on_predicate
/on_exception
. Or forget about it and accurately maintain four versions ofretry
. -
Properly handle old Pythons, where asyncio is not available in
backoff.py
, tests, CI, etc.
from backoff.
External interface can be left the same, as seen in #23 . But implementation of on_predicate/on_exception will be either messy with conditions/switches, or be duplicated.
I see that on_predicate and on_exception shares a lot in common, but you haven't merged their implementations (probably because it will lead to messy code).
Ok, great. I think you're exactly right about where the difficulties will lie.
I'm most concerned with keeping the API clean and slightly less concerned with what the implementation looks like. I think that's the best policy for library code. However, of course maintenance is a concern, and you're exactly right the 2 current implementations are similar but mostly don't share code. The reason is these were once much simpler and grew up to their present state as features were added. Looking now with a critical eye, we could at minimum factor out a few helper functions. This might help to make the 4 implementations we end up with a little less unwieldy. Maybe I'll see how far I get doing this as a first step, since it probably makes sense anyway.
- Properly handle old Pythons, where asyncio is not available in backoff.py, tests, CI, etc.
I did a little exploratory coding for this, and I think the biggest problem here is going to be with new Python 3 syntax. If the asyncio
library isn't available in an python 2.7, that's not a problem because we can just conditionally not import it at runtime, and conditionally not define the retry_async
version of the retry function. But if the code fails to compile because of new syntax e.g. yield from
then it won't work and we are stuck. So the question is: is that new syntax required for the async path to work?
from backoff.
- Properly handle old Pythons, where asyncio is not available in backoff.py, tests, CI, etc.
I did a little exploratory coding for this, and I think the biggest problem here is going to be with new python 3 syntax. If the asyncio library isn't available in an python 2.7, that's not a problem because we can just conditionally not import it at runtime, and conditionally not define the retry_async version of the retry function. But if the code failed to compile because of new syntax e.g. yield from then it won't work and we are stuck. So the question is: is that new syntax required for the async path to work?
Uh, I forgot that yield from
appeared only in Python 3.3. Yes, new syntax is required to use asyncio's coroutines (yield from
and asyncio.coroutine
are used in Python >=3.4, await
and async def
keywords may be used in Python >= 3.5).
We can move all async stuff to separate module, like _backoff_async.py
, and import it only on Python >= 3.4.
from backoff.
A separate _backoff_async
module which is only imported in Python >=3.4 sounds like a good idea if we can make it work. I know that 3.5 has those new keywords, but I believe we could stick with a 3.4 style implementation without keywords that would still work in 3.5. Is that true?
I suspect we're going to end up with the four different implementations, so as a first step, I did some simple refactoring to share more code in the form of helper functions. A PR for it is here: #24 I think at at minimum it makes the two current implementations a little easier to follow. The async implementations should be able to be modified to use those as well.
from backoff.
A separate _backoff_async module which is only imported in Python >=3.4 sounds like a good idea if we can make it work. I know that 3.5 has those new keywords, but I believe we could stick with a 3.4 style implementation without keywords that would still work in 3.5. Is that true?
Yes, it should be enough to support Python 3.4 style only --- it works under Python >= 3.4, and we don't need at this moment syntax features appeared in Python 3.5 and 3.6.
I suspect we're going to end up with the four different implementations, so as a first step, I did some simple refactoring to share more code in the form of helper functions. A PR for it is here: #24 I think at at minimum it makes the two current implementations a little easier to follow. The async implementations should be able to be modified to use those as well.
Looks good to me. Would you like to convert single-file backoff.py
to package with set of modules?
This way we can create _common.py
and move to it common functions for sync and async implementations (_maybe_call
, _init_wait_gen
, _next_wait
).
Create _wait_gens
(or any other name) to put expo
, fibo
, constant
; and probably other private modules.
backoff.__init__.py
will import _backoff_async.py
and _backoff_async.py
should not import backoff.__init__.py
to prevent circular dependencies, so we need to place all common functions in separate modules.
from backoff.
Looks good to me. Would you like to convert single-file backoff.py to package with set of modules? This way we can create _common.py and move to it common functions for sync and async implementations (_maybe_call, _init_wait_gen, _next_wait). Create _wait_gens (or any other name) to put expo, fibo, constant; and probably other private modules.
Yes, this sounds good to me.
backoff.init.py will import _backoff_async.py and _backoff_async.py should not import backoff.init.py to prevent circular dependencies, so we need to place all common functions in separate modules.
Yes, sounds right.
Do you want to put a PR together for this? If not, I can see how far I get as I have time.
from backoff.
Do you want to put a PR together for this? If not, I can see how far I get as I have time.
If you can do initial split --- this would be great, then I can do PR adding _backoff_async.py
and tests for it (in separate file too, so probably it is a good idea to move backoff_tests.py
to tests
subdirectory; I would also rename it to test_backoff.py
and run pytest
on that tests
directory; async tests may be in test_backoff_async.py
).
I'm limited in time too, but if you won't be able to do splitting, I can try to do PR for that.
from backoff.
Here's my pass at making backoff into a package with private submodules. The idea is that you would just need to implement the two functions in _async.py
Let me know if this looks like what you need. #25
from backoff.
#25 is merged to master!
from backoff.
#26 is merged to master! I also sorted out the pep8, tests, and coverage across platforms so all Travis builds pass 'make check' now.
I'll need to work on some documentation next. Let me know if you have any compelling simple examples which you think would be good in the docs.
Thanks for your work on this!
from backoff.
@bgreen-litl your changes for checking code LGTM, thanks!
I opened #28 with README update, which should complete this issue (only changelog update and release publishing will be left).
Here is complete example of async usage of backoff parts of which I used in README, in case you would like to experiment with it: https://gist.github.com/rutsky/6b2b891dff399536ce98c3d7445e39c7
from backoff.
@rutsky 1.4.0 with async support is released!
from backoff.
Great, thanks!
Would you like to make announcement on the aio-libs mailing list?
I can do this for you, if you want.
from backoff.
from backoff.
I wrote release e-mail here:
https://groups.google.com/forum/#!topic/aio-libs/eR1pmAwbcTk
from backoff.
@bgreen-litl just FYI, there is also riprova library that implements backoff, it supports asyncio
too.
from backoff.
Related Issues (20)
- 2.2.0 release gives type errors for previously valid usage? HOT 1
- Max tries
- Duplicate logging in some special cases HOT 1
- How do I use this?
- Obtain value returned by backoff_handler function after maximum tries is reached
- max_retries HOT 1
- max_time only applies _after_ first retry?
- Canonical way to handle error bursting
- Add exponential decay
- How to handle backoff on Facebook cursor? HOT 1
- Improve Typing and Error Handling in `on_exception`
- question about max_time
- Feature Request: add a way to use backoff in a context manager HOT 2
- feature request: toggle decorator on/off
- `backoff.on_predicate` does not work for `staticmethod`s
- Expose typing hints as they are part of the API
- Add support for globally disabling retries
- How to provide arguments to `on_backoff` function HOT 1
- Feature Request: support direct invocation with functools.partial
- Maintenance help? HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from backoff.