Преглед изворни кода

Add cleanup for Pack and PackData objects to fix resource warnings

- Add __del__ methods to Pack and PackData that raise ResourceWarning if not explicitly closed
- Fix test_bundle to properly close PackData objects using addCleanup
- Set _file/_data/_idx to None after closing to prevent double-close
Jelmer Vernooij пре 3 недеља
родитељ
комит
03e40fcbc9
2 измењених фајлова са 42 додато и 1 уклоњено
  1. 36 1
      dulwich/pack.py
  2. 6 0
      tests/test_bundle.py

+ 36 - 1
dulwich/pack.py

@@ -1813,7 +1813,26 @@ class PackData:
 
     def close(self) -> None:
         """Close the underlying pack file."""
-        self._file.close()
+        if self._file is not None:
+            self._file.close()
+            self._file = None  # type: ignore
+
+    def __del__(self) -> None:
+        """Ensure pack file is closed when PackData is garbage collected."""
+        if self._file is not None:
+            import warnings
+
+            warnings.warn(
+                f"unclosed PackData {self!r}",
+                ResourceWarning,
+                stacklevel=2,
+                source=self,
+            )
+            try:
+                self.close()
+            except Exception:
+                # Ignore errors during cleanup
+                pass
 
     def __enter__(self) -> "PackData":
         """Enter context manager."""
@@ -4066,8 +4085,24 @@ class Pack:
         """Close the pack file and index."""
         if self._data is not None:
             self._data.close()
+            self._data = None  # type: ignore
         if self._idx is not None:
             self._idx.close()
+            self._idx = None  # type: ignore
+
+    def __del__(self) -> None:
+        """Ensure pack file is closed when Pack is garbage collected."""
+        if self._data is not None or self._idx is not None:
+            import warnings
+
+            warnings.warn(
+                f"unclosed Pack {self!r}", ResourceWarning, stacklevel=2, source=self
+            )
+            try:
+                self.close()
+            except Exception:
+                # Ignore errors during cleanup
+                pass
 
     def __enter__(self) -> "Pack":
         """Enter context manager."""

+ 6 - 0
tests/test_bundle.py

@@ -74,6 +74,7 @@ class BundleTests(TestCase):
         write_pack_objects(b1.write, [], object_format=DEFAULT_OBJECT_FORMAT)
         b1.seek(0)
         bundle1.pack_data = PackData.from_file(b1, object_format=DEFAULT_OBJECT_FORMAT)
+        self.addCleanup(bundle1.pack_data.close)
 
         bundle2 = Bundle()
         bundle2.version = 3
@@ -85,6 +86,7 @@ class BundleTests(TestCase):
         write_pack_objects(b2.write, [], object_format=DEFAULT_OBJECT_FORMAT)
         b2.seek(0)
         bundle2.pack_data = PackData.from_file(b2, object_format=DEFAULT_OBJECT_FORMAT)
+        self.addCleanup(bundle2.pack_data.close)
 
         # Test equality
         self.assertEqual(bundle1, bundle2)
@@ -99,6 +101,7 @@ class BundleTests(TestCase):
         write_pack_objects(b3.write, [], object_format=DEFAULT_OBJECT_FORMAT)
         b3.seek(0)
         bundle3.pack_data = PackData.from_file(b3, object_format=DEFAULT_OBJECT_FORMAT)
+        self.addCleanup(bundle3.pack_data.close)
         self.assertNotEqual(bundle1, bundle3)
 
         bundle4 = Bundle()
@@ -110,6 +113,7 @@ class BundleTests(TestCase):
         write_pack_objects(b4.write, [], object_format=DEFAULT_OBJECT_FORMAT)
         b4.seek(0)
         bundle4.pack_data = PackData.from_file(b4, object_format=DEFAULT_OBJECT_FORMAT)
+        self.addCleanup(bundle4.pack_data.close)
         self.assertNotEqual(bundle1, bundle4)
 
         bundle5 = Bundle()
@@ -121,6 +125,7 @@ class BundleTests(TestCase):
         write_pack_objects(b5.write, [], object_format=DEFAULT_OBJECT_FORMAT)
         b5.seek(0)
         bundle5.pack_data = PackData.from_file(b5, object_format=DEFAULT_OBJECT_FORMAT)
+        self.addCleanup(bundle5.pack_data.close)
         self.assertNotEqual(bundle1, bundle5)
 
         bundle6 = Bundle()
@@ -134,6 +139,7 @@ class BundleTests(TestCase):
         write_pack_objects(b6.write, [], object_format=DEFAULT_OBJECT_FORMAT)
         b6.seek(0)
         bundle6.pack_data = PackData.from_file(b6, object_format=DEFAULT_OBJECT_FORMAT)
+        self.addCleanup(bundle6.pack_data.close)
         self.assertNotEqual(bundle1, bundle6)
 
         # Test inequality with different type