Skip to content

Commit

Permalink
Merge pull request #69 from ikalnytskyi/bug/box-put-no-value-no-factory
Browse files Browse the repository at this point in the history
Require either 'value' or 'factory' in Box.put()
  • Loading branch information
ikalnytskyi committed Nov 19, 2023
2 parents 49607f2 + d7459da commit 61cae69
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 12 deletions.
4 changes: 4 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,10 @@ Release Notes
properly its return type breaking code completion in some LSP servers for the
returned object.

* Fix ``Box.put()`` and ``picobox.put()`` to require either ``value``
or ``factory`` argument. Previously, they could have been invoked with ``key``
argument only, which makes no sense and causes runtime issues later on.

3.0.0
`````

Expand Down
10 changes: 8 additions & 2 deletions src/picobox/_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,14 @@ def put(
a class that implements :class:`Scope` interface.
:raises ValueError: If both `value` and `factory` are passed.
"""
if value is not _unset and (factory is not _unset or scope is not _unset):
raise ValueError("either 'value' or 'factory'/'scope' pair must be passed")
if value is _unset and factory is _unset:
raise TypeError("Box.put() missing 1 required argument: either 'value' or 'factory'")

if value is not _unset and factory is not _unset:
raise TypeError("Box.put() takes either 'value' or 'factory', not both")

if value is not _unset and scope is not _unset:
raise TypeError("Box.put() takes 'scope' when 'factory' provided")

# Value is a syntax sugar Box supports to store objects "As Is"
# with singleton scope. In other words it's essentially the same
Expand Down
27 changes: 21 additions & 6 deletions tests/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,28 +96,43 @@ def fn(a):
assert testbox.get("b") == 14


def test_box_put_value_factory_required(boxclass):
testbox = boxclass()

with pytest.raises(TypeError) as excinfo:
testbox.put("the-key")

assert str(excinfo.value) == (
"Box.put() missing 1 required argument: either 'value' or 'factory'"
)


def test_box_put_value_and_factory(boxclass):
testbox = boxclass()

with pytest.raises(ValueError) as excinfo:
with pytest.raises(TypeError) as excinfo:
testbox.put("the-key", 42, factory=object)
excinfo.match("either 'value' or 'factory'/'scope' pair must be passed")

assert str(excinfo.value) == "Box.put() takes either 'value' or 'factory', not both"


def test_box_put_value_and_scope(boxclass):
testbox = boxclass()

with pytest.raises(ValueError) as excinfo:
with pytest.raises(TypeError) as excinfo:
testbox.put("the-key", 42, scope=picobox.threadlocal)
excinfo.match("either 'value' or 'factory'/'scope' pair must be passed")

assert str(excinfo.value) == "Box.put() takes 'scope' when 'factory' provided"


def test_box_get_keyerror(boxclass):
testbox = boxclass()

with pytest.raises(KeyError, match="the-key"):
with pytest.raises(KeyError) as excinfo:
testbox.get("the-key")

assert str(excinfo.value) == "'the-key'"


def test_box_get_default(boxclass):
testbox = boxclass()
Expand Down Expand Up @@ -421,7 +436,7 @@ def fn(a, b):
with pytest.raises(KeyError) as excinfo:
fn(1)

excinfo.match("b")
assert str(excinfo.value) == "'b'"


def test_box_pass_optimization(boxclass, request):
Expand Down
22 changes: 18 additions & 4 deletions tests/test_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,36 @@ def fn(a):
assert teststack.get("b") == 14


def test_box_put_value_factory_required(boxclass, teststack):
testbox = boxclass()

with teststack.push(testbox):
with pytest.raises(TypeError) as excinfo:
teststack.put("the-key")

assert str(excinfo.value) == (
"Box.put() missing 1 required argument: either 'value' or 'factory'"
)


def test_box_put_value_and_factory(boxclass, teststack):
testbox = boxclass()

with teststack.push(testbox):
with pytest.raises(ValueError) as excinfo:
with pytest.raises(TypeError) as excinfo:
teststack.put("the-key", 42, factory=object)
excinfo.match("either 'value' or 'factory'/'scope' pair must be passed")

assert str(excinfo.value) == "Box.put() takes either 'value' or 'factory', not both"


def test_box_put_value_and_scope(boxclass, teststack):
testbox = boxclass()

with teststack.push(testbox):
with pytest.raises(ValueError) as excinfo:
with pytest.raises(TypeError) as excinfo:
teststack.put("the-key", 42, scope=picobox.threadlocal)
excinfo.match("either 'value' or 'factory'/'scope' pair must be passed")

assert str(excinfo.value) == "Box.put() takes 'scope' when 'factory' provided"


def test_box_put_runtimeerror(boxclass, teststack):
Expand Down

0 comments on commit 61cae69

Please sign in to comment.