-
Notifications
You must be signed in to change notification settings - Fork 92
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
Asyncio support #311
Comments
Okay, I found #53. However, I'm not planning to use ZODB in a Zope project but something completely unrelated, which uses asyncio. From my understanding, in the current state this would break:
|
#53 discusses some of the practical issues with asyncio. More fundamentally, ZODB's programming model of transparent demand-paged objects simply does not fit well with asyncio. In ZODB, any attribute access anywhere on any persistent object could potentially lead to (blocking) database calls. That doesn't work well with asycio's "all yield points must be excruciatingly annotated as such" model. For asynchronous programming, gevent does work extremely well with ZODB (when backed by a storage like RelStorage and at least at one point, ZEO). |
Perhaps when accessing these attributes, an await can be applied to the actual attribute - as though the attribute is a coroutine (using an @Property, and ignored when not a ghost), and similarly awaiting on transaction saves? Without asyncio, ZODB is useless to me, as I'm already highly invested in it. |
Persistent ZODB objects work by overridding EDITED to add: The link to |
Yep, was reading the source and saw that. Surely we can return a coroutine from |
Even if that were possible (I'm not sure it is) it would be extremely unpleasant because you'd have to do that for every attribute access. And like a disease, it would spread, to every consumer of any persistent object ( Hypothetical sketch of what that would look like: async def debit(account_number, amount):
p_bank_account = connection.root()[account_number]
can_debit = await p_bank_account.can_debit
if not can_debit:
frozen = await p_bank_account.frozen
act_type = await p_bank_account.type
throw AccountError(
"Cannot debit. Account might be frozen (%s) or wrong type (%s)" % (frozen, act_type))
# Calling a method first gets the attribute so we have to wait for that
has_enough_funds = await p_bank_account.has_enough_funds
# The method itself probably uses other attributes, including methods,
# so it must be declared async too...
has_enough_funds = await has_enough_funds(amount)
if not has_enough_funds:
throw AccountError("Cannot debit, insufficient funds")
# Logging is a problem. We can't just pass the object there, logging won't wait for
# anything.
logging.info("Debiting %s from account %s", amount, p_bank_account) # WRONG
# etc… |
Regarding the way you fetch has_enough_funds, I would assume that methods aren't saved to the db (are transient) by default, so that could be safely removed. Regarding your complaint that asyncio spreads like a disease, that's sort of the whole point of it. I'd say that it should be optional - there could he an AIODB class, perhaps? About logging, you can probably just override |
Nope. Methods go through the standard attribute lookup process, and that includes invoking >>> class Persistent(object):
... def method(self):
... return 1
... def __getattribute__(self, name):
... print("Getting attribute", name)
... return object.__getattribute__(self, name)
...
>>> p = Persistent()
>>> p.method()
Getting attribute method
1 |
Individual persistent objects talk to an Or perhaps everything async could be hidden inside the Happy hacking! |
Of course they go through getattribute, just don't know whether ZODB pickles them... It seems wasteful to pickle the functions when they are provided by the code |
Oh, okay. See you next week, when I try to start and utterly fail :P |
Sure, usually, they're not. Usually >>> p.method = lambda: 42
>>> p.method()
Getting attribute method
42 |
That's true. I guess it will need an await then unless they are annotated @classmethod perhaps. I guess it would still go through getattribute, but we can detect a classmethod and skip it, perhaps? I think that wouldn't work because the classmethod still can be overridden in instances, but perhaps a similar solution could work (applying a marker decorator to class-level functions we want to use, regardless of what's in the db) |
They're you're getting into the descriptor protocol, which is somewhat complex (functions themselves are non-data descriptors, which is how they implement binding; a Fortunately, that complexity is not the responsibility of Doing anything else would require a different implementation of |
From my understanding of Descriptors are searched for in the instance before the class, so assuming that all class-level descriptors are not replaceable on instances is false. I think descriptors don't really solve the problem, the best way to avoid it would surely be an annotation applied from Persistent to mark that the class-level attribute never requires lookup (this would presumably make the library faster too) |
Are there any plans for ZODB to support asynchronous reads and writes from the database with asyncio?
The text was updated successfully, but these errors were encountered: