| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent dc69411 commit 25e0ccf
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,7 +9,7 @@ | |||
| 9 | 9 | - Barton Cline ([@BartonCline](https://github.com/BartonCline)) | |
| 10 | 10 | - Brian Lloyd ([@brianlloyd](https://github.com/brianlloyd)) | |
| 11 | 11 | - David Anthoff ([@davidanthoff](https://github.com/davidanthoff)) | |
| 12 | - - Denis Akhiyarov ([@denfromufa](https://github.com/denfromufa)) | ||
| 12 | + - Denis Akhiyarov ([@den-run-ai](https://github.com/den-run-ai)) | ||
| 13 | 13 | - Tony Roberts ([@tonyroberts](https://github.com/tonyroberts)) | |
| 14 | 14 | - Victor Uriarte ([@vmuriart](https://github.com/vmuriart)) | |
| 15 | 15 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,6 +11,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. | |||
| 11 | 11 | ||
| 12 | 12 | - Support `del obj[...]` for types derived from `IList<T>` and `IDictionary<K, V>` | |
| 13 | 13 | - Support for .NET Framework 4.6.1 (#2701) | |
| 14 | + - Add context manager protocol for .NET IDisposable types, allowing use of `with` statements | ||
| 15 | + for IDisposable objects (#2568) | ||
| 14 | 16 | ||
| 15 | 17 | ### Changed | |
| 16 | 18 | ### Fixed | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -480,6 +480,34 @@ Python idioms: | |||
| 480 | 480 | for item in domain.GetAssemblies(): | |
| 481 | 481 | name = item.GetName() | |
| 482 | 482 | ||
| 483 | + Using Context Managers (IDisposable) | ||
| 484 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| 485 | + | ||
| 486 | + .NET types that implement ``IDisposable`` can be used with Python's context manager | ||
| 487 | + protocol using the standard ``with`` statement. This automatically calls the object's | ||
| 488 | + ``Dispose()`` method when exiting the ``with`` block: | ||
| 489 | + | ||
| 490 | + .. code:: python | ||
| 491 | + | ||
| 492 | + from System.IO import MemoryStream, StreamWriter | ||
| 493 | + | ||
| 494 | + # Use a MemoryStream as a context manager | ||
| 495 | + with MemoryStream() as stream: | ||
| 496 | + # The stream is automatically disposed when exiting the with block | ||
| 497 | + writer = StreamWriter(stream) | ||
| 498 | + writer.Write("Hello, context manager!") | ||
| 499 | + writer.Flush() | ||
| 500 | + | ||
| 501 | + # Do something with the stream | ||
| 502 | + stream.Position = 0 | ||
| 503 | + # ... | ||
| 504 | + | ||
| 505 | + # After exiting the with block, the stream is disposed | ||
| 506 | + # Attempting to use it here would raise an exception | ||
| 507 | + | ||
| 508 | + This works for any .NET type that implements ``IDisposable``, making resource | ||
| 509 | + management much cleaner and safer in Python code. | ||
| 510 | + | ||
| 483 | 511 | Type Conversion | |
| 484 | 512 | --------------- | |
| 485 | 513 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,6 +63,12 @@ public IEnumerable<PyType> GetBaseTypes(Type type, IList<PyType> existingBases) | |||
| 63 | 63 | newBases.Add(new PyType(this.Mixins.GetAttr("IteratorMixin"))); | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | + // context managers (for IDisposable) | ||
| 67 | + if (interfaces.Contains(typeof(IDisposable))) | ||
| 68 | + { | ||
| 69 | + newBases.Add(new PyType(this.Mixins.GetAttr("ContextManagerMixin"))); | ||
| 70 | + } | ||
| 71 | + | ||
| 66 | 72 | if (newBases.Count == existingBases.Count) | |
| 67 | 73 | { | |
| 68 | 74 | return existingBases; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,6 +5,22 @@ | |||
| 5 | 5 | ||
| 6 | 6 | import collections.abc as col | |
| 7 | 7 | ||
| 8 | + class ContextManagerMixin: | ||
| 9 | + """Implements Python's context manager protocol for .NET IDisposable types""" | ||
| 10 | + def __enter__(self): | ||
| 11 | + """Return self for use in the with block""" | ||
| 12 | + return self | ||
| 13 | + | ||
| 14 | + def __exit__(self, exc_type, exc_val, exc_tb): | ||
| 15 | + """Call Dispose() when exiting the with block""" | ||
| 16 | + if hasattr(self, 'Dispose'): | ||
| 17 | + self.Dispose() | ||
| 18 | + else: | ||
| 19 | + from System import IDisposable | ||
| 20 | + IDisposable(self).Dispose() | ||
| 21 | + # Return False to indicate that exceptions should propagate | ||
| 22 | + return False | ||
| 23 | + | ||
| 8 | 24 | class IteratorMixin(col.Iterator): | |
| 9 | 25 | def close(self): | |
| 10 | 26 | if hasattr(self, 'Dispose'): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,111 @@ | |||
| 1 | + import pytest | ||
| 2 | + | ||
| 3 | + from System.IO import MemoryStream, FileStream, FileMode, File, Path, StreamWriter | ||
| 4 | + | ||
| 5 | + | ||
| 6 | + def test_memory_stream_context_manager(): | ||
| 7 | + """Test that MemoryStream can be used as a context manager""" | ||
| 8 | + data = bytes([1, 2, 3, 4, 5]) | ||
| 9 | + | ||
| 10 | + with MemoryStream() as stream: | ||
| 11 | + # Convert Python bytes to .NET byte array for proper writing | ||
| 12 | + from System import Array, Byte | ||
| 13 | + | ||
| 14 | + dotnet_bytes = Array[Byte](data) | ||
| 15 | + stream.Write(dotnet_bytes, 0, len(dotnet_bytes)) | ||
| 16 | + | ||
| 17 | + assert stream.Length == 5 | ||
| 18 | + stream.Position = 0 | ||
| 19 | + | ||
| 20 | + # Create a .NET byte array to read into | ||
| 21 | + buffer = Array[Byte](5) | ||
| 22 | + stream.Read(buffer, 0, 5) | ||
| 23 | + | ||
| 24 | + # Convert back to Python bytes for comparison | ||
| 25 | + result = bytes(buffer) | ||
| 26 | + assert result == data | ||
| 27 | + | ||
| 28 | + # The stream should be disposed (closed) after the with block | ||
| 29 | + with pytest.raises(Exception): | ||
| 30 | + stream.Position = 0 # This should fail because the stream is closed | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + def test_file_stream_context_manager(tmpdir: str): | ||
| 34 | + """Test that FileStream can be used as a context manager""" | ||
| 35 | + # Create a temporary file path | ||
| 36 | + temp_path = Path.Combine(str(tmpdir), Path.GetRandomFileName()) | ||
| 37 | + | ||
| 38 | + try: | ||
| 39 | + # Write data to the file using with statement | ||
| 40 | + data = "Hello, context manager!" | ||
| 41 | + with FileStream(temp_path, FileMode.Create) as fs: | ||
| 42 | + writer = StreamWriter(fs) | ||
| 43 | + writer.Write(data) | ||
| 44 | + writer.Flush() | ||
| 45 | + | ||
| 46 | + # Verify the file was written and stream was closed | ||
| 47 | + assert File.Exists(temp_path) | ||
| 48 | + content = File.ReadAllText(temp_path) | ||
| 49 | + assert content == data | ||
| 50 | + | ||
| 51 | + # The stream should be disposed after the with block | ||
| 52 | + with pytest.raises(Exception): | ||
| 53 | + fs.Position = 0 # This should fail because the stream is closed | ||
| 54 | + finally: | ||
| 55 | + # Clean up | ||
| 56 | + if File.Exists(temp_path): | ||
| 57 | + File.Delete(temp_path) | ||
| 58 | + | ||
| 59 | + | ||
| 60 | + def test_disposable_in_multiple_contexts(): | ||
| 61 | + """Test that using .NET IDisposable objects in multiple contexts works correctly""" | ||
| 62 | + # Create multiple streams and check that they're all properly disposed | ||
| 63 | + | ||
| 64 | + # Create a list to track if streams were properly disposed | ||
| 65 | + # (we'll check this by trying to access the stream after disposal) | ||
| 66 | + streams_disposed = [False, False] | ||
| 67 | + | ||
| 68 | + # Use nested context managers with .NET IDisposable objects | ||
| 69 | + with MemoryStream() as outer_stream: | ||
| 70 | + # Write some data to the outer stream | ||
| 71 | + from System import Array, Byte | ||
| 72 | + | ||
| 73 | + outer_data = Array[Byte]([10, 20, 30]) | ||
| 74 | + outer_stream.Write(outer_data, 0, len(outer_data)) | ||
| 75 | + | ||
| 76 | + # Check that the outer stream is usable | ||
| 77 | + assert outer_stream.Length == 3 | ||
| 78 | + | ||
| 79 | + with MemoryStream() as inner_stream: | ||
| 80 | + # Write different data to the inner stream | ||
| 81 | + inner_data = Array[Byte]([40, 50, 60, 70]) | ||
| 82 | + inner_stream.Write(inner_data, 0, len(inner_data)) | ||
| 83 | + | ||
| 84 | + # Check that the inner stream is usable | ||
| 85 | + assert inner_stream.Length == 4 | ||
| 86 | + | ||
| 87 | + # Try to use the inner stream - should fail because it's disposed | ||
| 88 | + try: | ||
| 89 | + inner_stream.Position = 0 | ||
| 90 | + except Exception: | ||
| 91 | + streams_disposed[1] = True | ||
| 92 | + | ||
| 93 | + # Try to use the outer stream - should fail because it's disposed | ||
| 94 | + try: | ||
| 95 | + outer_stream.Position = 0 | ||
| 96 | + except Exception: | ||
| 97 | + streams_disposed[0] = True | ||
| 98 | + | ||
| 99 | + # Verify both streams were properly disposed | ||
| 100 | + assert all(streams_disposed) | ||
| 101 | + | ||
| 102 | + | ||
| 103 | + def test_exception_handling(): | ||
| 104 | + """Test that exceptions propagate correctly through the context manager""" | ||
| 105 | + with pytest.raises(ValueError): | ||
| 106 | + with MemoryStream() as stream: | ||
| 107 | + raise ValueError("Test exception") | ||
| 108 | + | ||
| 109 | + # Stream should be disposed despite the exception | ||
| 110 | + with pytest.raises(Exception): | ||
| 111 | + stream.Position = 0 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments