Skip to content

Commit 8b30d9c

Browse files
committed
fix: processing of pathlib.Path argument for writing (#1031)
* correctly process pathlib.Path * test issue * add explanation for new check * modify defaults of new helper * handle new type of network error
1 parent adeda66 commit 8b30d9c

File tree

5 files changed

+41
-17
lines changed

5 files changed

+41
-17
lines changed

.github/workflows/build-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767

6868
- name: Run pytest
6969
run: |
70-
python -m pytest -vv tests --reruns 3 --reruns-delay 30 --only-rerun "(?i)http|timeout"
70+
python -m pytest -vv tests --reruns 3 --reruns-delay 30 --only-rerun "(?i)http|timeout|connection"
7171
7272
vanilla-build:
7373
strategy:
@@ -91,4 +91,4 @@ jobs:
9191

9292
- name: Run pytest
9393
run: |
94-
python -m pytest -vv tests --reruns 3 --reruns-delay 30 --only-rerun "(?i)http|timeout"
94+
python -m pytest -vv tests --reruns 3 --reruns-delay 30 --only-rerun "(?i)http|timeout|connection"

src/uproot/_util.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@ def ensure_numpy(array, types=(numpy.bool_, numpy.integer, numpy.floating)):
9090
return out
9191

9292

93+
def is_file_like(
94+
obj, readable: bool = False, writable: bool = False, seekable: bool = False
95+
) -> bool:
96+
return (
97+
callable(getattr(obj, "read", None))
98+
and callable(getattr(obj, "write", None))
99+
and callable(getattr(obj, "seek", None))
100+
and callable(getattr(obj, "tell", None))
101+
and callable(getattr(obj, "flush", None))
102+
and (not readable or not hasattr(obj, "readable") or obj.readable())
103+
and (not writable or not hasattr(obj, "writable") or obj.writable())
104+
and (not seekable or not hasattr(obj, "seekable") or obj.seekable())
105+
)
106+
107+
93108
def parse_version(version):
94109
"""
95110
Converts a semver string into a Version object that can be compared with

src/uproot/sink/file.py

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import numbers
1515
import os
1616

17+
import uproot._util
18+
1719

1820
class FileSink:
1921
"""
@@ -39,16 +41,7 @@ def from_object(cls, obj) -> FileSink:
3941
as ``io.BytesIO``. The object must be readable, writable, and seekable
4042
with ``"r+b"`` mode semantics.
4143
"""
42-
if (
43-
callable(getattr(obj, "read", None))
44-
and callable(getattr(obj, "write", None))
45-
and callable(getattr(obj, "seek", None))
46-
and callable(getattr(obj, "tell", None))
47-
and callable(getattr(obj, "flush", None))
48-
and (not hasattr(obj, "readable") or obj.readable())
49-
and (not hasattr(obj, "writable") or obj.writable())
50-
and (not hasattr(obj, "seekable") or obj.seekable())
51-
):
44+
if uproot._util.is_file_like(obj, readable=True, writable=True, seekable=True):
5245
self = cls(None)
5346
self._file = obj
5447
else:

src/uproot/writing/writable.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,9 @@ def create(file_path: str | IO, **options):
7373

7474

7575
def _sink_from_path(
76-
file_path_or_object: str | IO, **storage_options
76+
file_path_or_object: str | Path | IO, **storage_options
7777
) -> uproot.sink.file.FileSink:
78-
if not isinstance(file_path_or_object, str):
79-
# assume it's a file-like object
78+
if uproot._util.is_file_like(file_path_or_object):
8079
return uproot.sink.file.FileSink.from_object(file_path_or_object)
8180

8281
file_path = uproot._util.regularize_path(file_path_or_object)
@@ -118,7 +117,7 @@ def _sink_from_path(
118117
) from None
119118

120119

121-
def recreate(file_path: str | IO, **options):
120+
def recreate(file_path: str | Path | IO, **options):
122121
"""
123122
Args:
124123
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):
174173
).root_directory
175174

176175

177-
def update(file_path: str | IO, **options):
176+
def update(file_path: str | Path | IO, **options):
178177
"""
179178
Args:
180179
file_path (str, ``pathlib.Path`` or file-like object): The filesystem path of the

tests/test_0692_fsspec_writing.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import uproot.source.fsspec
66

77
import os
8+
import pathlib
89
import fsspec
910
import numpy as np
1011

@@ -30,6 +31,22 @@ def test_fsspec_writing_local(tmp_path, scheme):
3031
assert f["tree"]["x"].array().tolist() == [1, 2, 3]
3132

3233

34+
def test_issue_1029(tmp_path):
35+
# https://github.com/scikit-hep/uproot5/issues/1029
36+
urlpath = os.path.join(tmp_path, "some", "path", "file.root")
37+
urlpath = pathlib.Path(urlpath)
38+
39+
with uproot.recreate(urlpath) as f:
40+
f["tree_1"] = {"x": np.array([1, 2, 3])}
41+
42+
with uproot.update(urlpath) as f:
43+
f["tree_2"] = {"y": np.array([4, 5, 6])}
44+
45+
with uproot.open(urlpath) as f:
46+
assert f["tree_1"]["x"].array().tolist() == [1, 2, 3]
47+
assert f["tree_2"]["y"].array().tolist() == [4, 5, 6]
48+
49+
3350
def test_fsspec_writing_http(server):
3451
pytest.importorskip("aiohttp")
3552

0 commit comments

Comments
 (0)