Python's missing debug print command and development tools with enhanced debugging, pretty printing, timing, and ANSI styling capabilities.
—
Pytest integration for enhanced testing workflows with automatic assert statement insertion, test failure reporting, and development-time debugging utilities. The plugin provides insert_assert() functionality for dynamic test generation and integrates devtools debugging capabilities into the pytest environment.
Dynamically insert assert statements during test development for rapid test creation and debugging.
def insert_assert(value) -> int:
"""
Insert assert statement for testing (requires Python 3.8+).
Analyzes the calling code and generates an assert statement comparing
the provided value with its current runtime value. The generated assert
is formatted and can be written to the source file.
Parameters:
- value: Value to create assert statement for
Returns:
Number of insert_assert calls in the current test
Raises:
RuntimeError: If Python version < 3.8 or code inspection fails
"""Usage examples:
def test_calculation():
result = calculate_something(10, 20)
# During development, use insert_assert to capture expected values
insert_assert(result) # Generates: assert result == 42
# After running tests, the insert_assert calls are replaced with actual asserts
assert result == 42
def test_data_processing():
data = process_data([1, 2, 3, 4, 5])
# Multiple insert_assert calls in one test
insert_assert(len(data)) # Generates: assert len(data) == 5
insert_assert(data[0]) # Generates: assert data[0] == 2
insert_assert(sum(data)) # Generates: assert sum(data) == 15
# After processing:
assert len(data) == 5
assert data[0] == 2
assert sum(data) == 15
def test_complex_object():
obj = create_complex_object()
# Works with complex expressions
insert_assert(obj.property.method())
# Generates: assert obj.property.method() == expected_valueCommand-line options for controlling insert_assert behavior.
# Print generated assert statements instead of writing to files
pytest --insert-assert-print
# Fail tests that contain insert_assert calls (for CI/CD)
pytest --insert-assert-failBuilt-in fixtures for accessing insert_assert functionality and integration.
@pytest.fixture(name='insert_assert')
def insert_assert_fixture() -> Callable[[Any], int]:
"""
Pytest fixture providing access to insert_assert function.
Returns:
insert_assert function for use in test functions
"""
@pytest.fixture(scope='session', autouse=True)
def insert_assert_add_to_builtins() -> None:
"""
Automatically add insert_assert and debug to builtins for all tests.
Makes insert_assert and debug available without imports in test files.
"""
@pytest.fixture(autouse=True)
def insert_assert_maybe_fail(pytestconfig: pytest.Config) -> Generator[None, None, None]:
"""
Auto-use fixture that optionally fails tests containing insert_assert calls.
Controlled by --insert-assert-fail command line option.
"""
@pytest.fixture(scope='session', autouse=True)
def insert_assert_session(pytestconfig: pytest.Config) -> Generator[None, None, None]:
"""
Session-level fixture handling insert_assert code generation and file writing.
Manages the actual insertion of assert statements into source files.
"""Usage with fixtures:
def test_with_fixture(insert_assert):
# Use insert_assert fixture explicitly
result = some_calculation()
count = insert_assert(result)
assert count == 1 # First insert_assert call in this test
def test_builtin_access():
# insert_assert available without imports due to auto-fixture
data = get_test_data()
insert_assert(data['status']) # Works without explicit import
# debug also available
debug(data) # Works without explicit importPlugin hooks for customizing test behavior and reporting.
def pytest_addoption(parser) -> None:
"""
Add command-line options for insert_assert functionality.
Adds:
- --insert-assert-print: Print statements instead of writing files
- --insert-assert-fail: Fail tests with insert_assert calls
"""
def pytest_report_teststatus(report: pytest.TestReport, config: pytest.Config):
"""
Custom test status reporting for insert_assert failures.
Shows 'INSERT ASSERT' status for tests failed due to insert_assert calls.
Returns:
Tuple of (outcome, letter, verbose) for insert_assert failures
"""
def pytest_terminal_summary() -> None:
"""
Print summary of insert_assert operations at end of test session.
Shows statistics about generated assert statements and file modifications.
"""Typical workflow for using insert_assert in test development:
# 1. Initial test development
def test_new_feature():
result = new_feature_function()
# Use insert_assert to capture actual values during development
insert_assert(result.status)
insert_assert(result.data)
insert_assert(len(result.items))
# 2. Run tests with print mode to see generated asserts
# pytest test_file.py --insert-assert-print
# 3. Review printed assert statements:
# --------------------------------------------------------------------------------
# test_file.py - 5:
# --------------------------------------------------------------------------------
# # insert_assert(result.status)
# assert result.status == 'success'
# --------------------------------------------------------------------------------
# 4. Run tests normally to write asserts to file
# pytest test_file.py
# 5. Final test after insert_assert replacement:
def test_new_feature():
result = new_feature_function()
# insert_assert calls replaced with actual assertions
assert result.status == 'success'
assert result.data == {'key': 'value'}
assert len(result.items) == 3The plugin handles various edge cases gracefully:
def test_error_handling():
# Works with complex expressions
obj = get_complex_object()
insert_assert(obj.nested.property[0].method())
# Handles exceptions during formatting
problematic_value = get_problematic_value()
insert_assert(problematic_value) # Won't crash even if repr() fails
# Works with custom objects
custom = CustomClass()
insert_assert(custom) # Uses appropriate representation
def test_multiple_calls_same_line():
x, y = 1, 2
# Multiple insert_assert calls - handles duplicates
insert_assert(x); insert_assert(y)
# Second call on same line will be skipped in file writingSeamless integration with devtools debug functionality:
def test_with_debug():
# Both debug and insert_assert available in test environment
data = process_test_data()
# Debug for inspection during development
debug(data)
# insert_assert for generating test assertions
insert_assert(data['result'])
insert_assert(data['error_count'])
def test_debug_in_fixtures(insert_assert):
# Debug works in pytest fixtures and test functions
@pytest.fixture
def test_data():
data = create_test_data()
debug(data) # Available without import
return dataThe plugin automatically handles source file modification:
pyproject.tomlinsert_assert calls are replaced, not deleted# Configuration via pyproject.toml affects generated code formatting
[tool.black]
line-length = 88
target-version = ['py38']
# Generated assert statements respect Black configuration:
# assert very_long_variable_name_here == {
# "key1": "value1",
# "key2": "value2"
# }For testing the insert_assert functionality:
def test_insert_assert_behavior():
# Test that insert_assert returns correct count
count1 = insert_assert(42)
count2 = insert_assert("hello")
assert count1 == 1
assert count2 == 2
def test_insert_assert_with_expressions():
data = [1, 2, 3]
# Test with expressions
insert_assert(len(data))
insert_assert(sum(data))
insert_assert(data[0])
# These will generate appropriate assert statements@dataclass
class ToReplace:
"""
Represents a code replacement operation for insert_assert.
"""
file: Path # Source file to modify
start_line: int # Starting line number
end_line: int | None # Ending line number
code: str # Replacement code
class PlainRepr(str):
"""
String class where repr() doesn't include quotes.
Used for custom representation in generated assert statements.
"""
def __repr__(self) -> str: ...
# Context variables for tracking insert_assert state
insert_assert_calls: ContextVar[int] # Count of calls in current test
insert_assert_summary: ContextVar[list[str]] # Session summary information
# Global state
to_replace: list[ToReplace] # List of pending code replacementsInstall with Tessl CLI
npx tessl i tessl/pypi-devtools