Snippets of my code for the Python PaaS I/O exercise are shown below.
class MeteredFile(io.BufferedRandom):
"""Implement using a subclassing model."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._read_bytes = self._write_bytes = self._read_ops = self._write_ops = 0
def __enter__(self) -> Self:
return self
def __exit__(self, exc_type: Type[BaseException] | None,
exc_val: BaseException | None, exc_tb: TracebackType | None) -> bool | None:
return super().__exit__(exc_type, exc_val, exc_tb)
# Other methods not shown
class MeteredSocket:
"""Implement using a delegation model."""
def __init__(self, s: socket):
self._socket = s
self._recv_bytes = self._send_bytes = self._recv_ops = self._send_ops = 0
def __enter__(self) -> Self:
return self
def __exit__(self, exc_type: Type[BaseException] | None,
exc_val: BaseException | None, exc_tb: TracebackType | None) -> bool | None:
return self._socket.__exit__(exc_type, exc_val, exc_tb)
# Other methods not shown
If I call super().__enter__()
from MeteredFile.__enter__
or self._socket.__enter__()
from MeteredSocket.__enter__
, some tests fail, as those do not expect those methods to be called. Returning self
keeps those tests happy.
@patch("paasio.super", create=True, new_callable=SuperMock)
def test_meteredfile_context_manager(self, super_mock):
wrapped = MockFile(ZEN)
mock = NonCallableMagicMock(wraps=wrapped, autospec=True)
mock.__exit__.side_effect = wrapped.__exit__
super_mock.mock_object = mock
with MeteredFile() as file:
self.assertEqual(1, super_mock.init_called)
self.assertFalse(mock.__enter__.called)
file.read()
self.assertFalse(mock.__enter__.called)
mock.__exit__.assert_called_once_with(None, None, None)
self.assertEqual(2, len(mock.mock_calls))
with self.assertRaisesRegex(ValueError, "I/O operation on closed file."):
file.read()
with self.assertRaisesRegex(ValueError, "I/O operation on closed file."):
file.write(b"data")
@patch("paasio.super", create=True, new_callable=SuperMock)
def test_meteredfile_context_manager_exception_raise(self, super_mock):
exception = MockException("Should raise")
wrapped = MockFile(ZEN, exception=exception)
mock = NonCallableMagicMock(wraps=wrapped, autospec=True)
mock.__exit__.side_effect = wrapped.__exit__
super_mock.mock_object = mock
with self.assertRaisesRegex(MockException, "Should raise") as err:
with MeteredFile() as file:
self.assertFalse(mock.__enter__.called)
file.read()
self.assertFalse(mock.__enter__.called)
mock.__exit__.assert_called_once_with(
MockException,
err.exception,
ANY,
)
self.assertEqual(exception, err.exception)
However, the __exit__
methods are called as expected. I don’t understand why the tests are designed this way, can someone explain?