Skip to content

Commit

Permalink
Merge pull request #83 from ikalnytskyi/bug/pop-runtimeerror
Browse files Browse the repository at this point in the history
Throw RuntimeError from .pop() when stack is empty
  • Loading branch information
ikalnytskyi committed Nov 30, 2023
2 parents d160db5 + 770ec90 commit 62666e4
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 9 deletions.
5 changes: 5 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,11 @@ Release Notes
lost its coroutine function marker, i.e. ``inspect.iscoroutinefunction()``
returned ``False``.

* Fix a bug when ``picobox.pop()`` threw a generic ``IndexError`` when there
were no pushed boxes on stack. Now ``RuntimeError`` exception is thrown
instead with a good-looking error message. The behavior is now consistent
with other functions such as ``picobox.put()`` or ``picobox.get()``.

4.0.0
`````

Expand Down
15 changes: 9 additions & 6 deletions src/picobox/_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from ._box import Box, ChainBox

_ERROR_MESSAGE_EMPTY_STACK = "No boxes found on the stack, please `.push()` a box first."


def _copy_signature(method, instance=None):
# This is a workaround to overcome 'sphinx.ext.autodoc' inability to
Expand All @@ -23,15 +25,15 @@ def _copy_signature(method, instance=None):
return functools.wraps(method, (), ())


def _create_stack_proxy(stack, empty_stack_error):
def _create_stack_proxy(stack):
"""Create an object that proxies all calls to the top of the stack."""

class _StackProxy:
def __getattribute__(self, name):
try:
return getattr(stack[-1], name)
except IndexError:
raise RuntimeError(empty_stack_error)
raise RuntimeError(_ERROR_MESSAGE_EMPTY_STACK)

return _StackProxy()

Expand Down Expand Up @@ -103,9 +105,7 @@ def __init__(self, name: t.Optional[str] = None):
# that mimic Box interface but deal with a box on the top instead.
# While it's not completely necessary for `put()` and `get()`, it's
# crucial for `pass_()` due to its laziness and thus late evaluation.
self._topbox = _create_stack_proxy(
self._stack, "No boxes found on the stack, please `.push()` a box first."
)
self._topbox = _create_stack_proxy(self._stack)

def __repr__(self):
name = self._name
Expand Down Expand Up @@ -157,7 +157,10 @@ def pop(self) -> Box:
# ensure the code works properly even when running on alternative
# implementations.
with self._lock:
return self._stack.pop()
try:
return self._stack.pop()
except IndexError:
raise RuntimeError(_ERROR_MESSAGE_EMPTY_STACK)

@_copy_signature(Box.put)
def put(self, *args, **kwargs):
Expand Down
6 changes: 3 additions & 3 deletions tests/test_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,8 +733,8 @@ def fn(magic):
assert teststack.pop() is foobox


def test_stack_pop_indexerror(teststack):
with pytest.raises(IndexError) as excinfo:
def test_stack_pop_runtimeerror(teststack):
with pytest.raises(RuntimeError) as excinfo:
teststack.pop()

assert str(excinfo.value) == "pop from empty list"
assert str(excinfo.value) == "No boxes found on the stack, please `.push()` a box first."

0 comments on commit 62666e4

Please sign in to comment.