Skip to content

Commit d177d78

Browse files
Merge pull request #8 from Quansight/refactor
refactors code to move implimentations into their own folders
2 parents ea81b51 + 6b04802 commit d177d78

File tree

9 files changed

+186
-102
lines changed

9 files changed

+186
-102
lines changed

upath/core.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import urllib
44

55
from upath.registry import _registry
6-
from upath.universal_path import UniversalPath
76

87

98
class UPath(pathlib.Path):
@@ -30,10 +29,7 @@ def __new__(cls, *args, **kwargs):
3029
)
3130
self._init()
3231
else:
33-
if parsed_url.scheme in _registry:
34-
cls = _registry[parsed_url.scheme]
35-
else:
36-
cls = UniversalPath
32+
cls = _registry[parsed_url.scheme]
3733
kwargs["_url"] = parsed_url
3834
new_args.insert(0, parsed_url.path)
3935
args = tuple(new_args)

upath/implementations/hdfs.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from upath.universal_path import _FSSpecAccessor, UniversalPath
2+
3+
4+
class _HDFSAccessor(_FSSpecAccessor):
5+
def __init__(self, parsed_url, *args, **kwargs):
6+
super().__init__(parsed_url, *args, **kwargs)
7+
self._fs.root_marker = "/"
8+
9+
def argument_upath_self_to_filepath(self, func):
10+
"""if arguments are passed to the wrapped function, and if the first
11+
argument is a UniversalPath instance, that argument is replaced with
12+
the UniversalPath's path attribute
13+
"""
14+
15+
def wrapper(*args, **kwargs):
16+
args, kwargs = self._format_path(args, kwargs)
17+
print(args, kwargs)
18+
if "trunicate" in kwargs:
19+
kwargs.pop("trunicate")
20+
if func.__name__ == "mkdir":
21+
args = args[:1]
22+
return func(*args, **kwargs)
23+
24+
return wrapper
25+
26+
27+
class HDFSPath(UniversalPath):
28+
_default_accessor = _HDFSAccessor

upath/implementations/s3.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from upath.universal_path import _FSSpecAccessor, UniversalPath
2+
3+
4+
class _S3Accessor(_FSSpecAccessor):
5+
def __init__(self, parsed_url, *args, **kwargs):
6+
super().__init__(parsed_url, *args, **kwargs)
7+
8+
9+
class S3Path(UniversalPath):
10+
_default_accessor = _S3Accessor

upath/registry.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1-
from upath.implementations import http
1+
import warnings
22

3-
_registry = {"http": http.HTTPPath}
3+
from upath.universal_path import UniversalPath
4+
from upath.implementations import http, hdfs, s3
5+
6+
7+
class _Registry:
8+
http = http.HTTPPath
9+
hdfs = hdfs.HDFSPath
10+
s3 = s3.S3Path
11+
12+
def __getitem__(self, item):
13+
implimented_path = getattr(self, item, None)
14+
if not implimented_path:
15+
warning_str = (
16+
f"{item} filesystem path not explicitely implimented. "
17+
"falling back to default implimentation UniversalPath. "
18+
"This filesystem may not be tested"
19+
)
20+
warnings.warn(warning_str, UserWarning)
21+
return UniversalPath
22+
return implimented_path
23+
24+
25+
_registry = _Registry()

upath/tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ def local_testdir(tempdir, clear_registry):
7373
shutil.rmtree(tempdir)
7474

7575

76+
@pytest.fixture()
77+
def pathlib_base(local_testdir):
78+
return Path(local_testdir)
79+
80+
7681
@pytest.fixture(scope="session")
7782
def htcluster():
7883
proc = subprocess.Popen(
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""see upath/tests/conftest.py for fixtures
2+
"""
3+
import pytest # noqa: F401
4+
5+
from upath import UPath
6+
from upath.implementations.hdfs import HDFSPath
7+
from upath.tests.test_core import TestUpath
8+
9+
10+
@pytest.mark.hdfs
11+
class TestUPathHDFS(TestUpath):
12+
@pytest.fixture(autouse=True)
13+
def path(self, local_testdir, hdfs):
14+
host, user, port = hdfs
15+
path = f"hdfs:{local_testdir}"
16+
self.path = UPath(path, host=host, user=user, port=port)
17+
18+
def test_is_HDFSPath(self):
19+
assert isinstance(self.path, HDFSPath)
20+
21+
def test_chmod(self):
22+
# todo
23+
pass
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""see upath/tests/conftest.py for fixtures
2+
"""
3+
import pytest # noqa: F401
4+
5+
from upath import UPath
6+
from upath.errors import NotDirectoryError
7+
from upath.implementations.s3 import S3Path
8+
from upath.tests.test_core import TestUpath
9+
10+
11+
class TestUPathS3(TestUpath):
12+
@pytest.fixture(autouse=True)
13+
def path(self, local_testdir, s3):
14+
anon, s3so = s3
15+
path = f"s3:{local_testdir}"
16+
self.path = UPath(path, anon=anon, **s3so)
17+
18+
def test_is_S3Path(self):
19+
assert isinstance(self.path, S3Path)
20+
21+
def test_chmod(self):
22+
# todo
23+
pass
24+
25+
def test_mkdir(self):
26+
new_dir = self.path.joinpath("new_dir")
27+
# new_dir.mkdir()
28+
# mkdir doesnt really do anything. A directory only exists in s3
29+
# if some file or something is written to it
30+
new_dir.joinpath("test.txt").touch()
31+
assert new_dir.exists()
32+
33+
def test_rmdir(self, local_testdir):
34+
dirname = "rmdir_test"
35+
mock_dir = self.path.joinpath(dirname)
36+
mock_dir.joinpath("test.txt").touch()
37+
mock_dir.rmdir()
38+
assert not mock_dir.exists()
39+
with pytest.raises(NotDirectoryError):
40+
self.path.joinpath("file1.txt").rmdir()
41+
42+
def test_touch_unlink(self):
43+
path = self.path.joinpath("test_touch.txt")
44+
path.touch()
45+
assert path.exists()
46+
path.unlink()
47+
assert not path.exists()
48+
49+
# should raise FileNotFoundError since file is missing
50+
with pytest.raises(FileNotFoundError):
51+
path.unlink()
52+
53+
# file doesn't exists, but missing_ok is True
54+
path.unlink(missing_ok=True)

upath/tests/test_core.py

Lines changed: 12 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,32 @@
11
import pathlib
22
from pathlib import Path
3+
import warnings
34

45
import pytest
56

67
from upath import UPath
7-
from upath.errors import NotDirectoryError
8-
9-
10-
@pytest.fixture()
11-
def pathlib_base(local_testdir):
12-
return Path(local_testdir)
138

149

1510
def test_posix_path(local_testdir):
1611
assert isinstance(UPath(local_testdir), pathlib.PosixPath)
1712

1813

14+
def test_UPath_warning():
15+
with warnings.catch_warnings(record=True) as w:
16+
path = UPath("mock:/") # noqa: F841
17+
assert len(w) == 1
18+
assert issubclass(w[-1].category, UserWarning)
19+
assert "mock" in str(w[-1].message)
20+
21+
1922
class TestUpath:
2023
@pytest.fixture(autouse=True)
2124
def path(self, local_testdir):
22-
self.path = UPath(f"mock:{local_testdir}")
23-
print(self.path)
25+
with warnings.catch_warnings():
26+
warnings.simplefilter("ignore")
27+
self.path = UPath(f"mock:{local_testdir}")
2428

2529
def test_cwd(self):
26-
print("test_cwd")
2730
with pytest.raises(NotImplementedError):
2831
self.path.cwd()
2932

@@ -118,7 +121,6 @@ def test_lstat(self):
118121
def test_mkdir(self):
119122
new_dir = self.path.joinpath("new_dir")
120123
new_dir.mkdir()
121-
print(new_dir._accessor.info(new_dir))
122124
assert new_dir.exists()
123125

124126
def test_open(self):
@@ -196,62 +198,6 @@ def test_write_text(self, pathlib_base):
196198
assert path.read_text() == s
197199

198200

199-
@pytest.mark.hdfs
200-
class TestUPathHDFS(TestUpath):
201-
@pytest.fixture(autouse=True)
202-
def path(self, local_testdir, hdfs):
203-
host, user, port = hdfs
204-
path = f"hdfs:{local_testdir}"
205-
self.path = UPath(path, host=host, user=user, port=port)
206-
207-
def test_chmod(self):
208-
# todo
209-
pass
210-
211-
212-
class TestUPathS3(TestUpath):
213-
@pytest.fixture(autouse=True)
214-
def path(self, local_testdir, s3):
215-
anon, s3so = s3
216-
path = f"s3:{local_testdir}"
217-
self.path = UPath(path, anon=anon, **s3so)
218-
219-
def test_chmod(self):
220-
# todo
221-
pass
222-
223-
def test_mkdir(self):
224-
new_dir = self.path.joinpath("new_dir")
225-
# new_dir.mkdir()
226-
# mkdir doesnt really do anything. A directory only exists in s3
227-
# if some file or something is written to it
228-
new_dir.joinpath("test.txt").touch()
229-
assert new_dir.exists()
230-
231-
def test_rmdir(self, local_testdir):
232-
dirname = "rmdir_test"
233-
mock_dir = self.path.joinpath(dirname)
234-
mock_dir.joinpath("test.txt").touch()
235-
mock_dir.rmdir()
236-
assert not mock_dir.exists()
237-
with pytest.raises(NotDirectoryError):
238-
self.path.joinpath("file1.txt").rmdir()
239-
240-
def test_touch_unlink(self):
241-
path = self.path.joinpath("test_touch.txt")
242-
path.touch()
243-
assert path.exists()
244-
path.unlink()
245-
assert not path.exists()
246-
247-
# should raise FileNotFoundError since file is missing
248-
with pytest.raises(FileNotFoundError):
249-
path.unlink()
250-
251-
# file doesn't exists, but missing_ok is True
252-
path.unlink(missing_ok=True)
253-
254-
255201
@pytest.mark.hdfs
256202
def test_multiple_backend_paths(local_testdir, s3, hdfs):
257203
anon, s3so = s3

upath/universal_path.py

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ def __init__(self, parsed_url, *args, **kwargs):
1616
)
1717
url_kwargs.update(kwargs)
1818
self._fs = cls(**url_kwargs)
19-
if self._url.scheme in ["hdfs"]:
20-
self._fs.root_marker = "/"
2119

2220
def argument_upath_self_to_filepath(self, func):
2321
"""if arguments are passed to the wrapped function, and if the first
@@ -26,33 +24,33 @@ def argument_upath_self_to_filepath(self, func):
2624
"""
2725

2826
def wrapper(*args, **kwargs):
29-
if args:
30-
args = list(args)
31-
first_arg = args.pop(0)
32-
if not kwargs.get("path"):
33-
if isinstance(first_arg, UniversalPath):
34-
first_arg = first_arg.path
35-
if not self._fs.root_marker and first_arg.startswith(
36-
"/"
37-
):
38-
first_arg = first_arg[1:]
39-
args.insert(0, first_arg)
40-
args = tuple(args)
41-
else:
42-
if not self._fs.root_marker and kwargs["path"].startswith(
43-
"/"
44-
):
45-
kwargs["path"] = kwargs["path"][1:]
46-
if self._url.scheme == "hdfs":
47-
if "trunicate" in kwargs:
48-
kwargs.pop("trunicate")
49-
if func.__name__ == "mkdir":
50-
args = args[:1]
51-
27+
args, kwargs = self._format_path(args, kwargs)
5228
return func(*args, **kwargs)
5329

5430
return wrapper
5531

32+
def _format_path(self, args, kwargs):
33+
"""formats the path properly for the filesystem backend."""
34+
args = list(args)
35+
first_arg = args.pop(0)
36+
if not kwargs.get("path"):
37+
if isinstance(first_arg, UniversalPath):
38+
first_arg = self._remove_root_slash(first_arg.path)
39+
args.insert(0, first_arg)
40+
args = tuple(args)
41+
else:
42+
kwargs["path"] = self._remove_root_slash(kwargs["path"])
43+
return args, kwargs
44+
45+
def _remove_root_slash(self, s):
46+
"""If the filesystem backend doesn't have a root_marker, strip the
47+
leading slash of a path
48+
"""
49+
if not self._fs.root_marker and s.startswith("/"):
50+
return s[1:]
51+
else:
52+
return s
53+
5654
def __getattribute__(self, item):
5755
class_attrs = ["_url", "_fs", "__class__"]
5856
if item in class_attrs:
@@ -62,6 +60,8 @@ def __getattribute__(self, item):
6260
"__init__",
6361
"__getattribute__",
6462
"argument_upath_self_to_filepath",
63+
"_format_path",
64+
"_remove_root_slash",
6565
]
6666
if item in class_methods:
6767
return lambda *args, **kwargs: getattr(self.__class__, item)(
@@ -134,8 +134,8 @@ def _init(self, *args, template=None, **kwargs):
134134

135135
def __getattribute__(self, item):
136136
if item == "__class__":
137-
return UniversalPath
138-
if item in getattr(UniversalPath, "not_implemented"):
137+
return super().__getattribute__("__class__")
138+
if item in getattr(self.__class__, "not_implemented"):
139139
raise NotImplementedError(f"UniversalPath has no attribute {item}")
140140
else:
141141
return super().__getattribute__(item)
@@ -254,7 +254,7 @@ def _from_parts_init(cls, args, init=False):
254254
def _from_parts(self, args, init=True):
255255
# We need to call _parse_args on the instance, so as to get the
256256
# right flavour.
257-
obj = object.__new__(UniversalPath)
257+
obj = object.__new__(self.__class__)
258258
drv, root, parts = self._parse_args(args)
259259
obj._drv = drv
260260
obj._root = root
@@ -264,7 +264,7 @@ def _from_parts(self, args, init=True):
264264
return obj
265265

266266
def _from_parsed_parts(self, drv, root, parts, init=True):
267-
obj = object.__new__(UniversalPath)
267+
obj = object.__new__(self.__class__)
268268
obj._drv = drv
269269
obj._root = root
270270
obj._parts = parts

0 commit comments

Comments
 (0)