Skip to content

Commit 4e6c334

Browse files
committed
fs: replace fs.path with plain fs methods
After such a long time using it, it is clear that it wasn't the right decision and path manipulation methods should be right in the class. This also makes it possible to use most of methods as classmethods without having to initialize the filesystem at all. Per fsspec/filesystem_spec#747 (comment) and also a pre-requisite for it.
1 parent c01921e commit 4e6c334

File tree

7 files changed

+172
-199
lines changed

7 files changed

+172
-199
lines changed

src/dvc_objects/db.py

+7-8
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,13 @@ def _init(self, dname: str) -> None:
6363
self._dirs = set()
6464
with suppress(FileNotFoundError, NotImplementedError):
6565
self._dirs = {
66-
self.fs.path.name(path)
67-
for path in self.fs.ls(self.path, detail=False)
66+
self.fs.name(path) for path in self.fs.ls(self.path, detail=False)
6867
}
6968

7069
if dname in self._dirs:
7170
return
7271

73-
self.makedirs(self.fs.path.join(self.path, dname))
72+
self.makedirs(self.fs.join(self.path, dname))
7473
self._dirs.add(dname)
7574

7675
def exists(self, oid: str) -> bool:
@@ -198,7 +197,7 @@ def _oid_parts(self, oid: str) -> Tuple[str, str]:
198197
return oid[:2], oid[2:]
199198

200199
def oid_to_path(self, oid) -> str:
201-
return self.fs.path.join(self.path, *self._oid_parts(oid))
200+
return self.fs.join(self.path, *self._oid_parts(oid))
202201

203202
def _list_prefixes(
204203
self,
@@ -216,12 +215,12 @@ def _list_prefixes(
216215
yield from self.fs.find(paths, batch_size=jobs, prefix=prefix)
217216

218217
def path_to_oid(self, path) -> str:
219-
if self.fs.path.isabs(path):
220-
self_path = self.fs.path.abspath(self.path)
218+
if self.fs.isabs(path):
219+
self_path = self.fs.abspath(self.path)
221220
else:
222221
self_path = self.path
223-
self_parts = self.fs.path.parts(self_path)
224-
parts = self.fs.path.parts(path)[len(self_parts) :]
222+
self_parts = self.fs.parts(self_path)
223+
parts = self.fs.parts(path)[len(self_parts) :]
225224

226225
if not (len(parts) == 2 and parts[0] and len(parts[0]) == 2):
227226
raise ValueError(f"Bad cache file path '{path}'")

src/dvc_objects/fs/base.py

+144-16
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import asyncio
22
import datetime
33
import logging
4+
import ntpath
45
import os
6+
import posixpath
57
import shutil
68
from functools import partial
79
from multiprocessing import cpu_count
@@ -11,15 +13,18 @@
1113
Any,
1214
ClassVar,
1315
Dict,
16+
Iterable,
1417
Iterator,
1518
List,
1619
Literal,
1720
Optional,
21+
Sequence,
1822
Tuple,
1923
Union,
2024
cast,
2125
overload,
2226
)
27+
from urllib.parse import urlsplit, urlunsplit
2328

2429
from fsspec.asyn import get_loop
2530

@@ -40,8 +45,6 @@
4045

4146
from fsspec.spec import AbstractFileSystem
4247

43-
from .path import Path
44-
4548

4649
logger = logging.getLogger(__name__)
4750

@@ -68,6 +71,7 @@ def __init__(self, link: str, fs: "FileSystem", path: str) -> None:
6871
class FileSystem:
6972
sep = "/"
7073

74+
flavour = posixpath
7175
protocol = "base"
7276
REQUIRES: ClassVar[Dict[str, str]] = {}
7377
_JOBS = 4 * cpu_count()
@@ -104,14 +108,138 @@ def config(self) -> Dict[str, Any]:
104108
def root_marker(self) -> str:
105109
return self.fs.root_marker
106110

107-
@cached_property
108-
def path(self) -> "Path":
109-
from .path import Path
111+
def getcwd(self) -> str:
112+
return ""
113+
114+
def chdir(self, path: str):
115+
raise NotImplementedError
116+
117+
@classmethod
118+
def join(cls, *parts: str) -> str:
119+
return cls.flavour.join(*parts)
120+
121+
@classmethod
122+
def split(cls, path: str) -> Tuple[str, str]:
123+
return cls.flavour.split(path)
124+
125+
@classmethod
126+
def splitext(cls, path: str) -> Tuple[str, str]:
127+
return cls.flavour.splitext(path)
128+
129+
def normpath(self, path: str) -> str:
130+
if self.flavour == ntpath:
131+
return self.flavour.normpath(path)
132+
133+
parts = list(urlsplit(path))
134+
parts[2] = self.flavour.normpath(parts[2])
135+
return urlunsplit(parts)
136+
137+
@classmethod
138+
def isabs(cls, path: str) -> bool:
139+
return cls.flavour.isabs(path)
140+
141+
def abspath(self, path: str) -> str:
142+
if not self.isabs(path):
143+
path = self.join(self.getcwd(), path)
144+
return self.normpath(path)
145+
146+
@classmethod
147+
def commonprefix(cls, paths: Sequence[str]) -> str:
148+
return cls.flavour.commonprefix(paths)
149+
150+
@classmethod
151+
def commonpath(cls, paths: Iterable[str]) -> str:
152+
return cls.flavour.commonpath(list(paths))
153+
154+
@classmethod
155+
def parts(cls, path: str) -> Tuple[str, ...]:
156+
drive, path = cls.flavour.splitdrive(path.rstrip(cls.flavour.sep))
157+
158+
ret = []
159+
while True:
160+
path, part = cls.flavour.split(path)
161+
162+
if part:
163+
ret.append(part)
164+
continue
165+
166+
if path:
167+
ret.append(path)
168+
169+
break
170+
171+
ret.reverse()
172+
173+
if drive:
174+
ret = [drive, *ret]
110175

111-
def _getcwd():
112-
return self.fs.root_marker
176+
return tuple(ret)
113177

114-
return Path(self.sep, getcwd=_getcwd)
178+
@classmethod
179+
def parent(cls, path: str) -> str:
180+
return cls.flavour.dirname(path)
181+
182+
@classmethod
183+
def dirname(cls, path: str) -> str:
184+
return cls.parent(path)
185+
186+
@classmethod
187+
def parents(cls, path: str) -> Iterator[str]:
188+
while True:
189+
parent = cls.flavour.dirname(path)
190+
if parent == path:
191+
break
192+
yield parent
193+
path = parent
194+
195+
@classmethod
196+
def name(cls, path: str) -> str:
197+
return cls.flavour.basename(path)
198+
199+
@classmethod
200+
def suffix(cls, path: str) -> str:
201+
name = cls.name(path)
202+
_, dot, suffix = name.partition(".")
203+
return dot + suffix
204+
205+
@classmethod
206+
def with_name(cls, path: str, name: str) -> str:
207+
return cls.join(cls.parent(path), name)
208+
209+
@classmethod
210+
def with_suffix(cls, path: str, suffix: str) -> str:
211+
return cls.splitext(path)[0] + suffix
212+
213+
@classmethod
214+
def isin(cls, left: str, right: str) -> bool:
215+
if left == right:
216+
return False
217+
try:
218+
common = cls.commonpath([left, right])
219+
except ValueError:
220+
# Paths don't have the same drive
221+
return False
222+
return common == right
223+
224+
@classmethod
225+
def isin_or_eq(cls, left: str, right: str) -> bool:
226+
return left == right or cls.isin(left, right)
227+
228+
@classmethod
229+
def overlaps(cls, left: str, right: str) -> bool:
230+
return cls.isin_or_eq(left, right) or cls.isin(right, left)
231+
232+
def relpath(self, path: str, start: Optional[str] = None) -> str:
233+
if start is None:
234+
start = "."
235+
return self.flavour.relpath(self.abspath(path), start=self.abspath(start))
236+
237+
def relparts(self, path: str, start: Optional[str] = None) -> Tuple[str, ...]:
238+
return self.parts(self.relpath(path, start=start))
239+
240+
@classmethod
241+
def as_posix(cls, path: str) -> str:
242+
return path.replace(cls.flavour.sep, posixpath.sep)
115243

116244
@classmethod
117245
def _strip_protocol(cls, path: str) -> str:
@@ -299,7 +427,7 @@ def checksum(self, path: AnyFSPath) -> str:
299427
return self.fs.checksum(path)
300428

301429
def copy(self, from_info: AnyFSPath, to_info: AnyFSPath) -> None:
302-
self.makedirs(self.path.parent(to_info))
430+
self.makedirs(self.parent(to_info))
303431
self.fs.copy(from_info, to_info)
304432

305433
def cp_file(self, from_info: AnyFSPath, to_info: AnyFSPath, **kwargs: Any) -> None:
@@ -515,7 +643,7 @@ def put_file(
515643
else:
516644
assert isinstance(from_file, str)
517645
self.fs.put_file(os.fspath(from_file), to_info, callback=callback, **kwargs)
518-
self.fs.invalidate_cache(self.path.parent(to_info))
646+
self.fs.invalidate_cache(self.parent(to_info))
519647

520648
def get_file(
521649
self,
@@ -527,7 +655,7 @@ def get_file(
527655
self.fs.get_file(from_info, to_info, callback=callback, **kwargs)
528656

529657
def upload_fobj(self, fobj: IO, to_info: AnyFSPath, **kwargs) -> None:
530-
self.makedirs(self.path.parent(to_info))
658+
self.makedirs(self.parent(to_info))
531659
with self.open(to_info, "wb") as fdest:
532660
shutil.copyfileobj(
533661
fobj,
@@ -597,7 +725,7 @@ def get(
597725
from .local import localfs
598726

599727
def get_file(rpath, lpath, **kwargs):
600-
localfs.makedirs(localfs.path.parent(lpath), exist_ok=True)
728+
localfs.makedirs(localfs.parent(lpath), exist_ok=True)
601729
self.fs.get_file(rpath, lpath, **kwargs)
602730

603731
get_file = wrap_and_branch_callback(callback, get_file)
@@ -618,7 +746,7 @@ def get_file(rpath, lpath, **kwargs):
618746
return localfs.makedirs(to_info, exist_ok=True)
619747

620748
to_infos = [
621-
localfs.path.join(to_info, *self.path.relparts(info, from_info))
749+
localfs.join(to_info, *self.relparts(info, from_info))
622750
for info in from_infos
623751
]
624752

@@ -679,9 +807,9 @@ def find(
679807

680808
def _make_args(paths: List[AnyFSPath]) -> Iterator[Tuple[str, str]]:
681809
for path in paths:
682-
if prefix and not path.endswith(self.path.flavour.sep):
683-
parent = self.path.parent(path)
684-
yield parent, self.path.parts(path)[-1]
810+
if prefix and not path.endswith(self.flavour.sep):
811+
parent = self.parent(path)
812+
yield parent, self.parts(path)[-1]
685813
else:
686814
yield path, ""
687815

src/dvc_objects/fs/generic.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -394,16 +394,16 @@ def test_links(
394394
) -> List["AnyFSPath"]:
395395
from .utils import tmp_fname
396396

397-
from_file = from_fs.path.join(from_path, tmp_fname())
398-
to_file = to_fs.path.join(
399-
to_fs.path.parent(to_path),
397+
from_file = from_fs.join(from_path, tmp_fname())
398+
to_file = to_fs.join(
399+
to_fs.parent(to_path),
400400
tmp_fname(),
401401
)
402402

403-
from_fs.makedirs(from_fs.path.parent(from_file))
403+
from_fs.makedirs(from_fs.parent(from_file))
404404
with from_fs.open(from_file, "wb") as fobj:
405405
fobj.write(b"test")
406-
to_fs.makedirs(to_fs.path.parent(to_file))
406+
to_fs.makedirs(to_fs.parent(to_file))
407407

408408
ret = []
409409
try:

src/dvc_objects/fs/local.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ def modified(self, path):
179179
class LocalFileSystem(FileSystem):
180180
sep = os.sep
181181

182+
flavour = os.path
182183
protocol = "local"
183184
PARAM_CHECKSUM = "md5"
184185
PARAM_PATH = "path"
@@ -189,17 +190,18 @@ class LocalFileSystem(FileSystem):
189190
def fs(self):
190191
return FsspecLocalFileSystem(**self.config)
191192

192-
@cached_property
193-
def path(self):
194-
from .path import LocalFileSystemPath
193+
def getcwd(self):
194+
return os.getcwd()
195+
196+
def normpath(self, path: str) -> str:
197+
return self.flavour.normpath(path)
195198

196-
return LocalFileSystemPath(
197-
self.sep, getcwd=os.getcwd, realpath=os.path.realpath
198-
)
199+
def realpath(self, path: str) -> str:
200+
return self.flavour.realpath(path)
199201

200202
def upload_fobj(self, fobj, to_info, **kwargs):
201-
self.makedirs(self.path.parent(to_info))
202-
tmp_info = self.path.join(self.path.parent(to_info), tmp_fname(""))
203+
self.makedirs(self.parent(to_info))
204+
tmp_info = self.join(self.parent(to_info), tmp_fname(""))
203205
try:
204206
with open(tmp_info, "wb+") as fdest:
205207
shutil.copyfileobj(fobj, fdest)

0 commit comments

Comments
 (0)