Skip to content

Commit 118084c

Browse files
Adrian AcalaAdrian Acala
Adrian Acala
authored and
Adrian Acala
committed
Enhance documentation and tests for __replace__ method in BaseContainer
- Updated the documentation to clarify the usage of the __replace__ method and introduced examples for both pre- and post-Python 3.13 approaches. - Refactored test cases to improve clarity and ensure they accurately validate the behavior of the __replace__ method and its integration with the copy module. - Renamed test functions for consistency and better readability.
1 parent 50fcf82 commit 118084c

File tree

2 files changed

+32
-41
lines changed

2 files changed

+32
-41
lines changed

docs/pages/container.rst

+16-4
Original file line numberDiff line numberDiff line change
@@ -186,17 +186,29 @@ the ``__replace__`` protocol. All containers in ``returns`` implement this proto
186186
This allows creating new container instances with modified internal values:
187187

188188
.. doctest::
189-
:skipif: import sys; sys.version_info < (3, 13)
190189

191-
>>> # The following example requires Python 3.13+
192-
>>> from copy import replace
190+
>>> # This is a compatible way to create a new container with modified inner value
193191
>>> from returns.result import Success
194192
>>>
195193
>>> value = Success(1)
196-
>>> new_value = replace(value, _inner_value=2)
194+
>>> # We can use map to effectively replace the inner value
195+
>>> new_value = value.map(lambda _: 2)
197196
>>> assert new_value == Success(2)
198197
>>> assert value != new_value
199198

199+
For Python 3.13+, a more direct approach will be available using the ``copy.replace()`` function:
200+
201+
.. code-block:: python
202+
203+
# The following example requires Python 3.13+
204+
from copy import replace
205+
from returns.result import Success
206+
207+
value = Success(1)
208+
new_value = replace(value, 2)
209+
assert new_value == Success(2)
210+
assert value != new_value
211+
200212
This is particularly useful when you need to modify the inner value of a container
201213
without using the regular container methods like ``map`` or ``bind``.
202214

tests/test_primitives/test_container/test_base_container/test_replace.py

+16-37
Original file line numberDiff line numberDiff line change
@@ -86,25 +86,10 @@ def test_base_container_replace_direct_call():
8686
assert type(new_container) is type(container) # noqa: WPS516
8787

8888

89-
def test_base_container_replace_direct_call_invalid_args():
90-
"""Test direct call with invalid arguments."""
89+
def test_replace_direct_call_invalid_args():
90+
"""Ensures calling replace directly with invalid arguments raises error."""
9191
container = BaseContainer(1) # Create instance directly
92-
# Direct call with no args should fail
93-
with pytest.raises(TypeError):
94-
container.__replace__() # noqa: PLC2801
95-
96-
# Direct call with keyword args matching the name is allowed by Python,
97-
# even with /.
98-
# If uncommented, it should pass as Python allows this.
99-
# Removing commented test case for
100-
# `container.__replace__(inner_value='new')`
101-
102-
# Direct call with extra positional args should fail
103-
with pytest.raises(TypeError):
104-
container.__replace__('new', 'extra') # noqa: PLC2801
105-
106-
# Direct call with unexpected keyword args should fail
107-
with pytest.raises(TypeError):
92+
with pytest.raises(TypeError, match='unexpected keyword argument'):
10893
container.__replace__(other_kwarg='value') # type: ignore[attr-defined]
10994

11095

@@ -148,33 +133,27 @@ def test_copy_replace(container_value: Any) -> None:
148133
sys.version_info < (3, 13),
149134
reason='copy.replace requires Python 3.13+',
150135
)
151-
def test_base_container_replace_via_copy_no_changes(container_value):
152-
"""Test copy.replace with no actual change in value."""
153-
container = BaseContainer(container_value)
136+
def test_replace_copy_no_changes(container_value):
137+
"""
138+
Ensures calling copy.replace without changes yields a different container.
154139
155-
# Test with no changes is not directly possible via copy.replace with this
156-
# __replace__ implementation.
157-
# The copy.replace function itself handles the no-change case if the
158-
# object supports it, but our __replace__ requires a value.
159-
# If copy.replace is called with the same value, it should work.
160-
new_container = copy.replace(container, inner_value=container_value)
140+
With the same inner value.
141+
"""
142+
container = BaseContainer(container_value)
143+
original_value = container._inner_value # noqa: SLF001
144+
new_container = copy.replace(container, container_value)
161145

162-
assert new_container is not container # A new instance should be created
146+
assert new_container is not container
147+
assert new_container._inner_value == original_value # noqa: SLF001
163148

164149

165150
@pytest.mark.skipif(
166151
sys.version_info < (3, 13),
167152
reason='copy.replace requires Python 3.13+',
168153
)
169-
def test_base_container_replace_via_copy_invalid_args(container):
170-
"""Test copy.replace with invalid arguments."""
171-
# copy.replace converts the keyword 'inner_value' to a positional arg
172-
# for __replace__(self, /, inner_value), so this is valid.
173-
# Removing commented out test case for copy.replace with inner_value kwarg
174-
175-
# However, passing other keyword arguments will fail because __replace__
176-
# doesn't accept them.
177-
with pytest.raises(TypeError):
154+
def test_replace_copy_invalid_args(container):
155+
"""Ensures calling copy.replace with invalid arguments raises error."""
156+
with pytest.raises(TypeError, match='unexpected keyword argument'):
178157
copy.replace(container, other_kwarg='value') # type: ignore[attr-defined]
179158

180159
# copy.replace should raise TypeError if extra positional arguments

0 commit comments

Comments
 (0)