avrokit 0.1.2__tar.gz → 0.1.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. {avrokit-0.1.2 → avrokit-0.1.3}/PKG-INFO +1 -1
  2. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/url/google.py +10 -1
  3. {avrokit-0.1.2 → avrokit-0.1.3}/pyproject.toml +1 -1
  4. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_url_google.py +48 -1
  5. {avrokit-0.1.2 → avrokit-0.1.3}/uv.lock +1 -1
  6. {avrokit-0.1.2 → avrokit-0.1.3}/.github/workflows/check.yml +0 -0
  7. {avrokit-0.1.2 → avrokit-0.1.3}/.github/workflows/release.yml +0 -0
  8. {avrokit-0.1.2 → avrokit-0.1.3}/.gitignore +0 -0
  9. {avrokit-0.1.2 → avrokit-0.1.3}/LICENSE +0 -0
  10. {avrokit-0.1.2 → avrokit-0.1.3}/LICENSES/Apache-2.0.txt +0 -0
  11. {avrokit-0.1.2 → avrokit-0.1.3}/Makefile +0 -0
  12. {avrokit-0.1.2 → avrokit-0.1.3}/README.md +0 -0
  13. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/__init__.py +0 -0
  14. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/asyncio/__init__.py +0 -0
  15. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/asyncio/reader.py +0 -0
  16. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/asyncio/writer.py +0 -0
  17. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/io/__init__.py +0 -0
  18. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/io/compact.py +0 -0
  19. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/io/reader.py +0 -0
  20. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/io/schema.py +0 -0
  21. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/io/writer.py +0 -0
  22. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/py.typed +0 -0
  23. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/__init__.py +0 -0
  24. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/__main__.py +0 -0
  25. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/base.py +0 -0
  26. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/cat.py +0 -0
  27. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/concat.py +0 -0
  28. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/count.py +0 -0
  29. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/filesort.py +0 -0
  30. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/fromparquet.py +0 -0
  31. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/getmeta.py +0 -0
  32. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/getschema.py +0 -0
  33. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/httpserver.py +0 -0
  34. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/partition.py +0 -0
  35. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/repair.py +0 -0
  36. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/stats.py +0 -0
  37. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/tojson.py +0 -0
  38. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/tools/toparquet.py +0 -0
  39. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/url/__init__.py +0 -0
  40. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/url/base.py +0 -0
  41. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/url/factory.py +0 -0
  42. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/url/file.py +0 -0
  43. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/url/http.py +0 -0
  44. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/url/s3.py +0 -0
  45. {avrokit-0.1.2 → avrokit-0.1.3}/avrokit/url/utils.py +0 -0
  46. {avrokit-0.1.2 → avrokit-0.1.3}/tests/__init__.py +0 -0
  47. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_additional_coverage.py +0 -0
  48. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_asyncio.py +0 -0
  49. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_exports.py +0 -0
  50. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_io.py +0 -0
  51. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_io_schema.py +0 -0
  52. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_plan_coverage.py +0 -0
  53. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_property_based.py +0 -0
  54. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_schema_advanced.py +0 -0
  55. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_tools_httpserver.py +0 -0
  56. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_tools_integration.py +0 -0
  57. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_tools_repair.py +0 -0
  58. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_url.py +0 -0
  59. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_url_contract.py +0 -0
  60. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_url_file.py +0 -0
  61. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_url_http.py +0 -0
  62. {avrokit-0.1.2 → avrokit-0.1.3}/tests/test_url_s3.py +0 -0
  63. {avrokit-0.1.2 → avrokit-0.1.3}/uv.lock.license +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: avrokit
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Python utilities for working with Avro data files
5
5
  Project-URL: Homepage, https://github.com/brandtg/avrokit
6
6
  Project-URL: Repository, https://github.com/brandtg/avrokit
@@ -152,7 +152,16 @@ class GoogleCloudStorageURL(URL):
152
152
  self._current_remote.upload_from_filename(self._current_local.name)
153
153
  finally:
154
154
  # Cleanup resources
155
- self._current_local.close() # N.b. deletes on close
155
+ local = self._current_local
156
+ name = local.name
157
+ local.close()
158
+ # N.b. the temp file is created with delete=False so it survives an external
159
+ # close (e.g. DataFileWriter closing the stream before URL.close() uploads).
160
+ # Unlink it explicitly here to avoid leaking a file per open/close cycle.
161
+ try:
162
+ os.unlink(name)
163
+ except FileNotFoundError:
164
+ pass
156
165
  self._current_client.close()
157
166
  self._current_local = None
158
167
  self._current_local_stream = None
@@ -4,7 +4,7 @@
4
4
 
5
5
  [project]
6
6
  name = "avrokit"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Python utilities for working with Avro data files"
9
9
  authors = [{ name = "Greg Brandt", email = "brandt.greg@gmail.com" }]
10
10
  license = "Apache-2.0"
@@ -13,7 +13,7 @@ from filelock import FileLock
13
13
 
14
14
  from avrokit.io import avro_reader, avro_schema, avro_writer
15
15
  from avrokit.url.factory import parse_url
16
- from avrokit.url.google import google_cloud_storage_client
16
+ from avrokit.url.google import GoogleCloudStorageURL, google_cloud_storage_client
17
17
 
18
18
  faker = Faker()
19
19
 
@@ -163,3 +163,50 @@ class TestGoogleCloudStorageURL:
163
163
  ]
164
164
  # No matches returns empty list
165
165
  assert parse_url(f"{base}/nonexistent_*.avro").expand() == []
166
+
167
+ def test_no_temp_file_leak(self, monkeypatch):
168
+ class FakeBlob:
169
+ def __init__(self):
170
+ self.content = b""
171
+
172
+ def download_to_file(self, f):
173
+ f.write(self.content)
174
+ f.flush()
175
+
176
+ def upload_from_filename(self, name):
177
+ with open(name, "rb") as f:
178
+ self.content = f.read()
179
+
180
+ class FakeBucket:
181
+ def __init__(self):
182
+ self.blob_obj = FakeBlob()
183
+
184
+ def blob(self, path):
185
+ return self.blob_obj
186
+
187
+ class FakeClient:
188
+ def __init__(self):
189
+ self.bucket_obj = FakeBucket()
190
+
191
+ def bucket(self, name):
192
+ return self.bucket_obj
193
+
194
+ def close(self):
195
+ pass
196
+
197
+ fake_client = FakeClient()
198
+ monkeypatch.setattr("avrokit.url.google._create_client", lambda: fake_client)
199
+
200
+ tmpdir = tempfile.mkdtemp()
201
+ monkeypatch.setattr(tempfile, "tempdir", tmpdir)
202
+
203
+ url = GoogleCloudStorageURL("gs://test-bucket/test/leak.txt", mode="w")
204
+ with url as f:
205
+ f.write("hello")
206
+ assert fake_client.bucket_obj.blob_obj.content == b"hello"
207
+ assert os.listdir(tmpdir) == []
208
+
209
+ url = GoogleCloudStorageURL("gs://test-bucket/test/leak.txt", mode="r")
210
+ with url as f:
211
+ assert f.read() == "hello"
212
+ assert os.listdir(tmpdir) == []
@@ -26,7 +26,7 @@ wheels = [
26
26
 
27
27
  [[package]]
28
28
  name = "avrokit"
29
- version = "0.1.0"
29
+ version = "0.1.2"
30
30
  source = { editable = "." }
31
31
  dependencies = [
32
32
  { name = "avro" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes