Skip to content

Commit 82e75e7

Browse files
authored
Fix upath parents (#75)
* upath.core: implement .parents * upath.core: provide kwargs in with_name and with_suffix * tests: test_parents also test names explicitly
1 parent f4de726 commit 82e75e7

File tree

2 files changed

+72
-2
lines changed

2 files changed

+72
-2
lines changed

upath/core.py

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pathlib
22
import re
33
import sys
4+
from typing import Sequence
45
from typing import Union
56
import urllib
67
from urllib.parse import ParseResult
@@ -501,7 +502,11 @@ def with_suffix(self, suffix):
501502
else:
502503
name = name[: -len(old_suffix)] + suffix
503504
return self._from_parsed_parts(
504-
self._drv, self._root, self._parts[:-1] + [name], url=self._url
505+
self._drv,
506+
self._root,
507+
self._parts[:-1] + [name],
508+
url=self._url,
509+
**self._kwargs,
505510
)
506511

507512
def with_name(self, name):
@@ -518,5 +523,62 @@ def with_name(self, name):
518523
):
519524
raise ValueError("Invalid name %r" % (name))
520525
return self._from_parsed_parts(
521-
self._drv, self._root, self._parts[:-1] + [name], url=self._url
526+
self._drv,
527+
self._root,
528+
self._parts[:-1] + [name],
529+
url=self._url,
530+
**self._kwargs,
522531
)
532+
533+
@property
534+
def parents(self):
535+
"""A sequence of this upath's logical parents."""
536+
return _UPathParents(self)
537+
538+
539+
class _UPathParents(Sequence):
540+
"""This object provides sequence-like access to the logical ancestors
541+
of a path. Don't try to construct it yourself."""
542+
543+
__slots__ = (
544+
"_pathcls",
545+
"_drv",
546+
"_root",
547+
"_parts",
548+
"_url",
549+
"_kwargs",
550+
)
551+
552+
def __init__(self, path):
553+
# We don't store the instance to avoid reference cycles
554+
self._pathcls = type(path)
555+
self._drv = path._drv
556+
self._root = path._root
557+
self._parts = path._parts
558+
self._url = path._url
559+
self._kwargs = path._kwargs
560+
561+
def __len__(self):
562+
if self._drv or self._root:
563+
return len(self._parts) - 1
564+
else:
565+
return len(self._parts)
566+
567+
def __getitem__(self, idx):
568+
if isinstance(idx, slice):
569+
return tuple(self[i] for i in range(*idx.indices(len(self))))
570+
571+
if idx >= len(self) or idx < -len(self):
572+
raise IndexError(idx)
573+
if idx < 0:
574+
idx += len(self)
575+
return self._pathcls._from_parsed_parts(
576+
self._drv,
577+
self._root,
578+
self._parts[: -idx - 1],
579+
url=self._url,
580+
**self._kwargs,
581+
)
582+
583+
def __repr__(self):
584+
return "<{}.parents>".format(self._pathcls.__name__)

upath/tests/cases.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ def test_iterdir2(self, local_testdir):
115115
assert set(p.name for p in pl_iter) == set(u.name for u in up_iter)
116116
assert next(self.path.parent.iterdir()).exists()
117117

118+
def test_parents(self):
119+
p = self.path.joinpath("folder1", "file1.txt")
120+
assert p.is_file()
121+
assert p.parents[0] == p.parent
122+
assert p.parents[1] == p.parent.parent
123+
assert p.parents[0].name == "folder1"
124+
assert p.parents[1].name == self.path.name
125+
118126
def test_lchmod(self):
119127
with pytest.raises(NotImplementedError):
120128
self.path.lchmod(mode=77)

0 commit comments

Comments
 (0)