diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index ef4277174..219d5c3ce 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -67,7 +67,7 @@ jobs: - name: Run pytest run: | - python -m pytest -vv tests --reruns 3 --reruns-delay 30 --only-rerun "(?i)http|timeout" + python -m pytest -vv tests --reruns 3 --reruns-delay 30 --only-rerun "(?i)http|timeout|connection" vanilla-build: strategy: @@ -91,4 +91,4 @@ jobs: - name: Run pytest run: | - python -m pytest -vv tests --reruns 3 --reruns-delay 30 --only-rerun "(?i)http|timeout" + python -m pytest -vv tests --reruns 3 --reruns-delay 30 --only-rerun "(?i)http|timeout|connection" diff --git a/src/uproot/_util.py b/src/uproot/_util.py index 2e4d1331f..61c7f1f51 100644 --- a/src/uproot/_util.py +++ b/src/uproot/_util.py @@ -90,6 +90,21 @@ def ensure_numpy(array, types=(numpy.bool_, numpy.integer, numpy.floating)): return out +def is_file_like( + obj, readable: bool = False, writable: bool = False, seekable: bool = False +) -> bool: + return ( + callable(getattr(obj, "read", None)) + and callable(getattr(obj, "write", None)) + and callable(getattr(obj, "seek", None)) + and callable(getattr(obj, "tell", None)) + and callable(getattr(obj, "flush", None)) + and (not readable or not hasattr(obj, "readable") or obj.readable()) + and (not writable or not hasattr(obj, "writable") or obj.writable()) + and (not seekable or not hasattr(obj, "seekable") or obj.seekable()) + ) + + def parse_version(version): """ Converts a semver string into a Version object that can be compared with diff --git a/src/uproot/sink/file.py b/src/uproot/sink/file.py index 14c582364..167c8da17 100644 --- a/src/uproot/sink/file.py +++ b/src/uproot/sink/file.py @@ -14,6 +14,8 @@ import numbers import os +import uproot._util + class FileSink: """ @@ -39,16 +41,7 @@ def from_object(cls, obj) -> FileSink: as ``io.BytesIO``. The object must be readable, writable, and seekable with ``"r+b"`` mode semantics. """ - if ( - callable(getattr(obj, "read", None)) - and callable(getattr(obj, "write", None)) - and callable(getattr(obj, "seek", None)) - and callable(getattr(obj, "tell", None)) - and callable(getattr(obj, "flush", None)) - and (not hasattr(obj, "readable") or obj.readable()) - and (not hasattr(obj, "writable") or obj.writable()) - and (not hasattr(obj, "seekable") or obj.seekable()) - ): + if uproot._util.is_file_like(obj, readable=True, writable=True, seekable=True): self = cls(None) self._file = obj else: diff --git a/src/uproot/writing/writable.py b/src/uproot/writing/writable.py index e5eb934a7..870e50669 100644 --- a/src/uproot/writing/writable.py +++ b/src/uproot/writing/writable.py @@ -73,10 +73,9 @@ def create(file_path: str | IO, **options): def _sink_from_path( - file_path_or_object: str | IO, **storage_options + file_path_or_object: str | Path | IO, **storage_options ) -> uproot.sink.file.FileSink: - if not isinstance(file_path_or_object, str): - # assume it's a file-like object + if uproot._util.is_file_like(file_path_or_object): return uproot.sink.file.FileSink.from_object(file_path_or_object) file_path = uproot._util.regularize_path(file_path_or_object) @@ -118,7 +117,7 @@ def _sink_from_path( ) from None -def recreate(file_path: str | IO, **options): +def recreate(file_path: str | Path | IO, **options): """ Args: file_path (str, ``pathlib.Path`` or file-like object): The filesystem path of the @@ -174,7 +173,7 @@ def recreate(file_path: str | IO, **options): ).root_directory -def update(file_path: str | IO, **options): +def update(file_path: str | Path | IO, **options): """ Args: file_path (str, ``pathlib.Path`` or file-like object): The filesystem path of the diff --git a/tests/test_0692_fsspec_writing.py b/tests/test_0692_fsspec_writing.py index 2f5dc483c..9e089d56b 100644 --- a/tests/test_0692_fsspec_writing.py +++ b/tests/test_0692_fsspec_writing.py @@ -5,6 +5,7 @@ import uproot.source.fsspec import os +import pathlib import fsspec import numpy as np @@ -30,6 +31,22 @@ def test_fsspec_writing_local(tmp_path, scheme): assert f["tree"]["x"].array().tolist() == [1, 2, 3] +def test_issue_1029(tmp_path): + # https://github.com/scikit-hep/uproot5/issues/1029 + urlpath = os.path.join(tmp_path, "some", "path", "file.root") + urlpath = pathlib.Path(urlpath) + + with uproot.recreate(urlpath) as f: + f["tree_1"] = {"x": np.array([1, 2, 3])} + + with uproot.update(urlpath) as f: + f["tree_2"] = {"y": np.array([4, 5, 6])} + + with uproot.open(urlpath) as f: + assert f["tree_1"]["x"].array().tolist() == [1, 2, 3] + assert f["tree_2"]["y"].array().tolist() == [4, 5, 6] + + def test_fsspec_writing_http(server): pytest.importorskip("aiohttp")