-
Notifications
You must be signed in to change notification settings - Fork 7
Add safe generator #386
base: master
Are you sure you want to change the base?
Add safe generator #386
Conversation
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.
nice! maybe add a test for the decorator too?
also it doesn't matter too much, but the couch.database
module is a bit of a strange place for it. might be better to make this its own module?
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.
Looks great, good solution! Leaving open to give you a chance to respond to Cory's feedback.
Summary of comments I made on slack, plus some new things. FWIW, according to the Python docs
I'm not sure if that matters in practice, and I think the benefits of this probably outweigh the "brokenness." However, it is possible that other code could break if it depended on the documented property that an exhausted iterator will continue to raise Can we avoid putting anything new in dimagi-utils? Seems like this repo should eventually be merged back into HQ since it's so tightly coupled and the submodule workflow only adds overhead. Update: I see you mentioned the brokenness issue in the |
This iterator-like object wraps an iterator. | ||
The SafeIterator will raise a IteratorAlreadyConsumedException if a user attempts to iterate through it a | ||
second time. | ||
This is useful if the wrapped iterator is a one time user generator, which would simply raise StopIteration |
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.
should say "one time use" instead of "one time user"?
This decorator function wraps the return value of the given function in a SafeIterator. | ||
You should only use this decorator on functions that return generators. | ||
The SafeIterator will render the generator "safe" by raising a IteratorAlreadyConsumedException if it is | ||
iterated a second time. |
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 it would be more correct to s/generator/iterator/g
on this docstring. Possibly rename this decorator to safe_iterator
.
Thanks Daniel. I agree that its not ideal to break that protocol, but I think the practical advantages are worth it in this instance. I did consider an alternative implementation that would not break the iterator protocol: class SafeIterable(object):
def __init__(self, iterator):
self._iterator = iterator
self._has_been_iterated = False
def __iter__(self):
if self._has_been_iterated:
raise IteratorAlreadyConsumedException
self._has_been_iterated = True
return self._iterator The downside to this implementation is that there is nothing stopping someone from calling |
Obligatory link to Python-Dev post: The ensuing discussion is a fun read at first, but goes on for much too long and descends into rather time-wasting pedantry at times. Of note, And ultimately shot down by the BDFL. I'll leave it as an exercise for you, dear reader, to discover that bitter end. |
not sure if it's the right mechanism, but would this be a good thing to take to architecture review to try and reach consensus? seems we are making certain tradeoffs either way, and maybe trying to be more inclusive/thoughtful about those in a wider group could help reach consensus. |
also @millerdev re: this point:
I agree with this - think we've said it a few times but maybe not documented formally anywhere. I think it was put here because @NoahCarnahan wants to patch code in dimagi utils to use this. so seems the options would be a library that both dimagi-utils and HQ can depend on, or moving a lot of code from dimagi-utils to HQ, both of which could be a lot of overhead. So it's possible we might decide that this rule can be broken if other code in dimagi-utils needs to depend on new code. |
Good call, I'll send an email to architecture review. Didn't realize that we were trying to avoid adding code to this repo. Once we decide on an approach, I'll look into how difficult it would be to move this code (and the code it needs to patch) into the main repo, but might be inclined to keep it here if it looks like a lot of work. |
Ah, I didn't look closely enough at the usage context. Yes, that makes sense. So possible next steps could be (in order of preference):
|
Quick note on
So we'd need to either merge in those modules as well, or copy/paste the utilities they depend on. There are likely a bunch of utils in dimagi-utils that are only used in commcare-hq, though, which could be moved to I'd support most any such effort, but that does seem pretty out of scope for this PR, so my vote would be to merge |
Ah, didn't realize there were a that many other dependencies outside of commcare-hq. That definitely does increase the scope, so I'm fine with merging this PR and dealing with that issue separately. |
Add a
SafeGenerator
class, which can be used to wrap a normal generator. It will throw an exception if something attempts to iterate over the generator a second time.This PR also adds a decorator that can be used with functions that return generators so that they will return SafeGenerators instead.
@czue @esoergel