Repository navigation
add stdlib queue.Queue compatible API - #7
Conversation
|
Why did you switch to py.test? |
|
You already invoked the tests using py.test: python-persistent-queue/.travis.yml Line 10 in e319e5e So I though it would be nice to fully embrace the py.test goodness(tm). 😄 What do you think in general of the proposed API additions? |
|
The API changes seem good! I think we could remove some redundant methods. For example, instead of having a |
|
Could you separate the changes to the tests into another PR? |
|
Sure! I didn't want to mess with your existing API too much (semver and all...) |
Done. |
|
Yeah, let's bump it to version 2. I think it would be better to have the API resemble something that people are familiar with. You can make those changes as part of this PR. What ever is easiest. |
|
Ok - I just pushed most of the refactoring into this PR. I might have gone overboard and changed a lot of things. If you have any objections to the specifics parts, I will try to extract it.
Two tests are still missing - then we have 100% coverage again. |
|
@philipbl all done! |
|
Sorry for the slow response. I really like it! I have a few comments, but it's too late tonight and I want to look at the code again in the morning. Thanks for all of your work on this! |
philipbl
left a comment
There was a problem hiding this comment.
I left a few comments for you. Let me know what you think.
| if timeout <= 0: | ||
| raise queue.Empty | ||
| self._put_event.clear() | ||
| elif not partial and items == 1: |
There was a problem hiding this comment.
What should happen if items > 1? For example, queue.get(block=False, items=2).
There was a problem hiding this comment.
I guess this part is undefined at the moment. What would you expect it to do? Should it raise if we try to get more items than are available, or just return everything that is available?
There was a problem hiding this comment.
It probably doesn't matter as long as it is consistent and documented.
I think when you peek (partial=True), the data that is available should be given, even if more was requested. When you get (partial=False), an exception should be raised if there is not enough items in the queue, no matter how many items are being requested.
So I would change lines 130-132 to be:
elif not partial and self._length < items:
raise queue.Empty| if timeout is not None: | ||
| target = time.time() + timeout | ||
| while self._length < items: | ||
| if self._put_event.wait(timeout) is not True: |
There was a problem hiding this comment.
Maybe I don't completely understand, but the logic doesn't seem to be right. First, shouldn't the not True be changed to True? Second, if wait returns True, then we need to check the length again before we see if the timeout has expired and raise an exception. Something like:
while self._length < items:
if self._put_event.wait(timeout) is True:
if timeout is not None:
timeout = target - time.time()
if self._length < items and timeout <= 0:
raise queue.Empty
self._put_event.clear()Third, we need something to handle the case where wait returns False:
if self._put_event.wait(timeout) is True:
...
else:
raise queue.EmptyThere was a problem hiding this comment.
This part is within the if block: condition - so we should not raise a queue.Empty unless a timeout value is given.
If timeout == None, the code looks like this (simplified):
while self._length < items:
self._put_event.wait(None) # only returns True
self._put_event.clear()If timeout is a number, the code looks like this (simplified)
target is going to be e.g. 5 seconds in the future.
while self._length < items:
if self._put_event.wait(5) is not True:
timeout = target - time.time()
if timeout <= 0: # only raise if timeout was set and has expired
raise queue.Empty
# else: the event obviously returned True, which means a new item was pushed
# so we just clear the event, and let the while-condition decode what to do next
self._put_event.clear()There was a problem hiding this comment.
Can't
if self._put_event.wait(timeout) is not True:be simplified to
if self._put_event.wait(timeout) is False:?
Maybe I am understanding Event.wait incorrectly. Doesn't it return False if the wait call times out? Assuming that a timeout value is given, this branch
if self._put_event.wait(timeout) is not True:will only be taken if wait has timed out. Since wait has timed out, we can just raise queue.Empty, we don't need to do the rest of the logic you have. It is only when wait returns True (because something was added the the queue so the wait didn't time out) that you need to adjust timeout to a new value, as long as there's still not enough items in the queue.
There was a problem hiding this comment.
Yes - this is probably a bit weirdly written. As far as I understood the API docs, it returns True if the event got set. But in the case of a timeout, the return value is not defined (I assume it is and will be False forever. Do you want me to clean that up and make it more intuitive?
There was a problem hiding this comment.
Well, I think the behavior is incorrect, not unintuitive. Here is what I think the behavior should be
if block:
if timeout is not None:
target = time.time() + timeout
while self._length < items:
if self._put_event.wait(timeout) is False:
# Nothing was added to the queue and timeout expired
# This will never happen if timeout is None
raise queue.Empty
# Something was added to the queue
# Update timeout, if necessary
if timeout is not None:
timeout = target - time.time()
if timeout <= 0:
raise queue.Empty
self._put_event.clear()
elif not partial and items == 1:
if self._length < items:
raise queue.EmptyWhat do you think?
There was a problem hiding this comment.
Yes - you are right!
One minor thing:
I think we can even remove this block - since if the timeout would be negative, the wait should have already returned False.
if timeout <= 0:
raise queue.Empty
What do you think?
| with self._all_tasks_done: | ||
| unfinished = self._unfinished_tasks - items | ||
| if unfinished <= 0: | ||
| if unfinished < 0: |
There was a problem hiding this comment.
It's a style thing, but I don't like the nested checks on unfinished. How about something like:
if unfinished < 0:
raise ValueError('task_done() called too many times')
elif unfinished == 0:
self._all_tasks_done.notify_all()There was a problem hiding this comment.
Ok - I will change it.
FYI: I just copied this block of code straight out of lib/python3.5/queue.py from the Python stdlib.
| flush_limit: below this filesize, flush() is a no-op. | ||
| """ | ||
| if filename is None: | ||
| raise ValueError("filename must be set, e.g. '/my/path/important.queue'") |
There was a problem hiding this comment.
Any reason why you didn't make filename a required parameter? I know it is a break from the Queue API, but isn't this practically the same thing?
There was a problem hiding this comment.
I think you are right, this is probably a valid point to break API-compatibility.
However, I'm not sure how to implement this - because putting filename as a first argument might lead to confusion, since the std-API expects an integer as API...
Or should we just verify that filename is a str?
There was a problem hiding this comment.
In my opinion, we don't need to check to make sure filename is a str. We don't do that for the rest of the parameters and os.path.abspath(filename) will fail if filename is an int. I don't feel strongly about this though, so if you want to add the check, go ahead.
Here is what I think the signature should be:
def __init__(self, filename, maxsize=0, dumps=pickle.dumps, loads=pickle.loads, flush_limit=1048576):| if timeout is not None: | ||
| target = time.time() + timeout | ||
| while self._length + len(items) > self.maxsize: | ||
| if self._get_event.wait(timeout) is not True: |
There was a problem hiding this comment.
See my comments for _peek. I think they apply here as well.
|
I just pushed a few new commits that should address all your comments. |
|
Yeah! Thanks! 👍 🎉 👏 |
This PR adds new functions to reach API-compatibility with the Python stdlib
queue.Queue.I'll leave bumping the major version number to @philipbl.
fixes #2
ToDo:
getandputmaxsizechecks to handle full queuesqueue.Emptyandqueue.Fullexceptions