0
# Development Tools
1
2
Integration tools for testing frameworks (pytest, hypothesis), static analysis (mypy), and development workflows with functional containers. These tools enhance the development experience when working with Returns.
3
4
## Capabilities
5
6
### Pytest Integration
7
8
Testing utilities and assertions specifically designed for functional containers.
9
10
```python { .api }
11
# Pytest plugin automatically loaded
12
from returns.contrib.pytest import ReturnsAsserts
13
14
class ReturnsAsserts:
15
"""Custom assertions for Returns containers"""
16
17
def assert_success(self, container: Result[T, E], expected: T) -> None:
18
"""Assert Result is Success with expected value"""
19
20
def assert_failure(self, container: Result[T, E]) -> None:
21
"""Assert Result is Failure"""
22
23
def assert_failure_with(self, container: Result[T, E], expected_error: E) -> None:
24
"""Assert Result is Failure with specific error"""
25
26
def assert_some(self, container: Maybe[T], expected: T) -> None:
27
"""Assert Maybe is Some with expected value"""
28
29
def assert_nothing(self, container: Maybe[T]) -> None:
30
"""Assert Maybe is Nothing"""
31
32
# Additional test utilities
33
def raises_failure(exception_type: type[Exception]) -> Callable:
34
"""Context manager for testing exception-based failures"""
35
36
def returns_success(expected_value: T) -> Callable:
37
"""Decorator to test function returns Success with value"""
38
39
def returns_failure() -> Callable:
40
"""Decorator to test function returns Failure"""
41
```
42
43
Usage examples:
44
45
```python
46
import pytest
47
from returns.contrib.pytest import ReturnsAsserts
48
from returns.result import Success, Failure, safe
49
from returns.maybe import Some, Nothing
50
51
class TestMyFunctions(ReturnsAsserts):
52
"""Test class with Returns assertions"""
53
54
def test_successful_operation(self):
55
result = Success(42)
56
self.assert_success(result, 42)
57
58
def test_failed_operation(self):
59
result = Failure("error occurred")
60
self.assert_failure(result)
61
self.assert_failure_with(result, "error occurred")
62
63
def test_maybe_operations(self):
64
some_value = Some(42)
65
nothing_value = Nothing
66
67
self.assert_some(some_value, 42)
68
self.assert_nothing(nothing_value)
69
70
# Testing with decorators and context managers
71
@safe
72
def divide(a: int, b: int) -> float:
73
return a / b
74
75
def test_safe_division():
76
"""Test safe division function"""
77
# Test successful case
78
result = divide(10, 2)
79
assert isinstance(result, Success)
80
assert result.unwrap() == 5.0
81
82
# Test failure case
83
result = divide(10, 0)
84
assert isinstance(result, Failure)
85
assert isinstance(result.failure(), ZeroDivisionError)
86
87
# Using pytest fixtures
88
@pytest.fixture
89
def sample_data():
90
return [Success(1), Failure("error"), Success(3)]
91
92
def test_with_fixture(sample_data):
93
successes = [r for r in sample_data if isinstance(r, Success)]
94
assert len(successes) == 2
95
```
96
97
### Hypothesis Integration
98
99
Property-based testing strategies for generating test data for functional containers.
100
101
```python { .api }
102
from returns.contrib.hypothesis import get_strategies
103
104
# Strategy generators
105
def get_strategies(container_type: type) -> dict[str, SearchStrategy]:
106
"""Get Hypothesis strategies for container types"""
107
108
# Pre-defined strategies
109
success_strategy = strategies.success(strategies.integers())
110
failure_strategy = strategies.failure(strategies.text())
111
result_strategy = strategies.result(strategies.integers(), strategies.text())
112
113
some_strategy = strategies.some(strategies.integers())
114
nothing_strategy = strategies.nothing()
115
maybe_strategy = strategies.maybe(strategies.integers())
116
117
io_strategy = strategies.io(strategies.integers())
118
future_strategy = strategies.future(strategies.integers())
119
```
120
121
Usage examples:
122
123
```python
124
import pytest
125
from hypothesis import given, strategies as st
126
from returns.contrib.hypothesis import get_strategies
127
from returns.result import Result, Success, Failure
128
from returns.maybe import Maybe, Some, Nothing
129
130
# Get strategies for specific types
131
result_strategies = get_strategies(Result)
132
maybe_strategies = get_strategies(Maybe)
133
134
@given(result_strategies['result'](st.integers(), st.text()))
135
def test_result_properties(result):
136
"""Property-based test for Result containers"""
137
# Test that mapping preserves container type
138
mapped = result.map(lambda x: x * 2)
139
140
if isinstance(result, Success):
141
assert isinstance(mapped, Success)
142
assert mapped.unwrap() == result.unwrap() * 2
143
else:
144
assert isinstance(mapped, Failure)
145
assert mapped.failure() == result.failure()
146
147
@given(maybe_strategies['maybe'](st.integers()))
148
def test_maybe_properties(maybe):
149
"""Property-based test for Maybe containers"""
150
# Test that binding with identity preserves value
151
identity_bound = maybe.bind(lambda x: Some(x))
152
153
if isinstance(maybe, Some):
154
assert isinstance(identity_bound, Some)
155
assert identity_bound.unwrap() == maybe.unwrap()
156
else:
157
assert isinstance(identity_bound, Nothing)
158
159
# Custom property tests
160
@given(st.lists(st.integers(), min_size=1))
161
def test_collection_processing(numbers):
162
"""Test collection processing properties"""
163
from returns.iterables import sequence
164
from returns.result import safe
165
166
@safe
167
def process_number(n: int) -> int:
168
return n * 2
169
170
results = [process_number(n) for n in numbers]
171
sequenced = sequence(results)
172
173
# Property: if all operations succeed, sequence succeeds
174
assert isinstance(sequenced, Success)
175
assert sequenced.unwrap() == [n * 2 for n in numbers]
176
```
177
178
### MyPy Integration
179
180
Static analysis plugin and type checking enhancements for Returns containers.
181
182
```python { .api }
183
# MyPy plugin configuration (in mypy.ini or setup.cfg)
184
"""
185
[mypy]
186
plugins = returns.contrib.mypy.returns_plugin
187
188
[tool:mypy-returns.*]
189
ignore_errors = True
190
"""
191
192
# Type transformation utilities
193
from returns.contrib.mypy import plugin_helpers
194
195
def transform_container_type(container_type: type) -> type:
196
"""Transform container types for better inference"""
197
198
def infer_generic_args(container: Container) -> tuple[type, ...]:
199
"""Infer generic type arguments from container instance"""
200
201
# Enhanced type stubs
202
def bind(self: Container[T], func: Callable[[T], Container[U]]) -> Container[U]: ...
203
def map(self: Container[T], func: Callable[[T], U]) -> Container[U]: ...
204
```
205
206
Configuration example:
207
208
```ini
209
# mypy.ini
210
[mypy]
211
plugins = returns.contrib.mypy.returns_plugin
212
strict_optional = True
213
warn_redundant_casts = True
214
warn_unused_ignores = True
215
disallow_any_generics = True
216
217
# Enable Returns-specific type checking
218
[mypy-returns.*]
219
check_untyped_defs = True
220
disallow_incomplete_defs = True
221
```
222
223
Usage examples:
224
225
```python
226
# Type-safe container usage with mypy checking
227
from returns.result import Result, Success, Failure
228
from returns.maybe import Maybe, Some, Nothing
229
230
def process_data(data: str) -> Result[int, ValueError]:
231
"""MyPy will enforce return type matches Success/Failure types"""
232
try:
233
value = int(data)
234
if value < 0:
235
return Failure(ValueError("Negative values not allowed"))
236
return Success(value)
237
except ValueError as e:
238
return Failure(e)
239
240
# MyPy will catch type mismatches
241
def chain_operations(x: int) -> Result[str, ValueError]:
242
return (
243
process_data(str(x))
244
.bind(lambda n: Success(n * 2)) # Result[int, ValueError]
245
.map(str) # Result[str, ValueError]
246
)
247
248
# Generic type inference
249
def generic_processor[T, E](
250
container: Result[T, E],
251
transform: Callable[[T], str]
252
) -> Result[str, E]:
253
"""MyPy infers correct generic types"""
254
return container.map(transform)
255
```
256
257
### Testing Utilities
258
259
Additional utilities for testing functional code patterns.
260
261
```python { .api }
262
def assert_container_equals(actual: Container[T], expected: Container[T]) -> None:
263
"""Deep equality assertion for containers"""
264
265
def mock_container(container_type: type, value: T) -> Container[T]:
266
"""Create mock container for testing"""
267
268
def container_test_case(container: Container[T]) -> TestCase:
269
"""Generate test cases for container operations"""
270
271
class ContainerTestMixin:
272
"""Mixin class providing container testing utilities"""
273
274
def run_container_laws(self, container: Container[T]) -> None:
275
"""Verify container follows mathematical laws"""
276
277
def test_functor_laws(self, container: Functor[T]) -> None:
278
"""Test functor laws (identity, composition)"""
279
280
def test_monad_laws(self, container: Monad[T]) -> None:
281
"""Test monad laws (left identity, right identity, associativity)"""
282
```
283
284
Usage examples:
285
286
```python
287
import unittest
288
from returns.contrib.pytest import ContainerTestMixin
289
from returns.result import Success, Failure
290
from returns.maybe import Some, Nothing
291
292
class TestContainerLaws(unittest.TestCase, ContainerTestMixin):
293
"""Test that containers follow mathematical laws"""
294
295
def test_result_monad_laws(self):
296
"""Test Result follows monad laws"""
297
success = Success(42)
298
failure = Failure("error")
299
300
self.test_monad_laws(success)
301
self.test_monad_laws(failure)
302
303
def test_maybe_functor_laws(self):
304
"""Test Maybe follows functor laws"""
305
some = Some(42)
306
nothing = Nothing
307
308
self.test_functor_laws(some)
309
self.test_functor_laws(nothing)
310
311
# Mock containers for testing
312
def test_with_mock_containers():
313
"""Test using mock containers"""
314
from unittest.mock import Mock
315
316
# Mock a Result container
317
mock_result = Mock(spec=Success)
318
mock_result.bind.return_value = Success(84)
319
mock_result.map.return_value = Success("42")
320
321
# Test interaction
322
def double(x: int) -> Success[int]:
323
return Success(x * 2)
324
325
result = mock_result.bind(double)
326
assert result.unwrap() == 84
327
```
328
329
### Debugging and Inspection
330
331
Tools for debugging and inspecting functional container operations.
332
333
```python { .api }
334
def trace_container(container: Container[T], label: str = "") -> Container[T]:
335
"""Log container operations for debugging"""
336
337
def inspect_container(container: Container[T]) -> dict[str, Any]:
338
"""Get detailed inspection info about container"""
339
340
def debug_pipeline(*operations: Callable) -> Callable:
341
"""Debug a pipeline of operations"""
342
343
class ContainerDebugger:
344
"""Debugger for container operations"""
345
346
def __init__(self, enabled: bool = True): ...
347
def trace(self, container: Container[T], operation: str) -> Container[T]: ...
348
def dump_trace(self) -> list[dict]: ...
349
```
350
351
Usage examples:
352
353
```python
354
from returns.contrib.debug import trace_container, debug_pipeline
355
from returns.result import Success
356
from returns.pipeline import flow
357
358
# Trace individual operations
359
result = (
360
Success(42)
361
.bind(lambda x: trace_container(Success(x * 2), "double"))
362
.map(lambda x: trace_container(x + 1, "add_one"))
363
)
364
365
# Debug entire pipeline
366
@debug_pipeline
367
def process_data(x: int) -> int:
368
return flow(
369
x,
370
lambda n: n * 2,
371
lambda n: n + 1,
372
lambda n: n / 2
373
)
374
375
result = process_data(10) # Logs each step
376
```
377
378
## Development Workflow Patterns
379
380
### Test-Driven Development
381
382
```python
383
import pytest
384
from returns.contrib.pytest import ReturnsAsserts
385
from returns.result import Result, Success, Failure
386
387
class TestUserService(ReturnsAsserts):
388
"""TDD example with Returns containers"""
389
390
def test_create_user_success(self):
391
# Test first, implement later
392
user_data = {"name": "John", "email": "john@example.com"}
393
result = create_user(user_data)
394
395
self.assert_success(result, User(name="John", email="john@example.com"))
396
397
def test_create_user_invalid_email(self):
398
user_data = {"name": "John", "email": "invalid-email"}
399
result = create_user(user_data)
400
401
self.assert_failure_with(result, ValidationError("Invalid email"))
402
403
# Implementation follows tests
404
def create_user(data: dict) -> Result[User, ValidationError]:
405
# Implementation guided by tests
406
pass
407
```
408
409
### Property-Based Testing
410
411
```python
412
from hypothesis import given, strategies as st
413
from returns.contrib.hypothesis import get_strategies
414
415
@given(st.lists(st.integers()))
416
def test_fold_properties(numbers):
417
"""Property-based testing of fold operations"""
418
from returns.iterables import Fold
419
from returns.result import Success
420
421
def add(a: int, b: int) -> Success[int]:
422
return Success(a + b)
423
424
results = [Success(n) for n in numbers]
425
folded = Fold.loop(results, Success(0), add)
426
427
# Property: folding should equal sum
428
assert folded.unwrap() == sum(numbers)
429
```
430
431
Development tools in Returns provide comprehensive support for testing, type checking, and debugging functional code, enabling robust development workflows while maintaining the benefits of functional programming patterns.