# -*- coding: utf-8 -*-
"""
S3Path property methods.
"""
import typing as T
from .filterable_property import FilterableProperty
if T.TYPE_CHECKING: # pragma: no cover
from .s3path import S3Path
[docs]
class AttributeAPIMixin:
"""
A mixin class that implements the property methods.
"""
@property
def parent(self: "S3Path") -> T.Optional["S3Path"]:
"""
Return the parent s3 directory.
- if current object is on s3 bucket root directory, it returns bucket
root directory
- it is always a directory (``s3path.is_dir() is True``)
- if it is already s3 bucket root directory, it returns ``None``
Examples::
>>> S3Path("my-bucket", "my-folder", "my-file.json").parent.uri
s3://my-bucket/my-folder/
>>> S3Path("my-bucket", "my-folder", "my-subfolder/").parent.uri
s3://my-bucket/my-folder/
>>> S3Path("my-bucket", "my-folder").parent.uri
s3://my-bucket/
>>> S3Path("my-bucket", "my-file.json").parent.uri
s3://my-bucket/
.. versionadded:: 1.0.1
"""
if len(self._parts) == 0:
return self
else:
return self._from_parsed_parts(
bucket=self._bucket,
parts=self._parts[:-1],
is_dir=True,
)
@property
def parents(self: "S3Path") -> T.List["S3Path"]:
"""
An immutable sequence providing access to the logical ancestors of
the path.
Examples::
>>> S3Path("my-bucket", "my-folder", "my-file.json").parents
s3://my-bucket/my-folder/
.. versionadded:: 1.0.6
"""
if self.is_void():
raise ValueError(f"void S3path doesn't support .parents method!")
if self.is_relpath():
raise ValueError(f"relative S3path doesn't support .parents method!")
l = list()
parent = self
while 1:
new_parent = parent.parent
if parent.uri == new_parent.uri:
break
else:
l.append(new_parent)
parent = new_parent
return l
[docs]
def is_parent_of(self: "S3Path", other: "S3Path") -> bool:
"""
Test if it is the parent directory or grand-grand-... parent directory
of another one.
Examples::
# is parent
>>> S3Path("bucket").is_parent_of(S3Path("bucket/folder/"))
True
# is grand parent
>>> S3Path("bucket").is_parent_of(S3Path("bucket/folder/file.txt"))
True
# the root bucket's parent is itself
>>> S3Path("bucket").is_parent_of(S3Path("bucket"))
True
# the 'self' has to be a directory
>>> S3Path("bucket/a").is_parent_of(S3Path("bucket/a/b/c"))
TypeError: S3Path('s3://bucket/a') is not a valid directory!
# the 'self' and 'other' has to be concrete S3Path
>>> S3Path().is_parent_of(S3Path())
TypeError: both S3Path(), S3Path() has to be a concrete S3Path!
.. versionadded:: 1.0.2
"""
if self._bucket is None or other._bucket is None:
raise TypeError(f"both {self}, {other} has to be a concrete S3Path!")
if self._bucket != other._bucket:
return False
if self.is_dir() is False:
raise TypeError(f"{self} is not a valid directory!")
n_parts_other = len(other.parts)
if n_parts_other == 0:
return len(self._parts) == 0
else:
return (
self._parts[: (n_parts_other - 1)] == other._parts[:-1]
and len(self._parts) < n_parts_other
)
[docs]
def is_prefix_of(self: "S3Path", other: "S3Path") -> bool:
"""
Test if it is a prefix of another one.
Example::
>>> S3Path("bucket/folder/").is_prefix_of(S3Path("bucket/folder/file.txt"))
True
>>> S3Path("bucket/folder/").is_prefix_of(S3Path("bucket/folder/"))
True
.. versionadded:: 1.0.2
"""
if self._bucket is None or other._bucket is None:
raise TypeError(f"both {self}, {other} has to be a concrete S3Path!")
if self._bucket != other._bucket:
return False
return self.uri <= other.uri
[docs]
@FilterableProperty
def basename(self: "S3Path") -> T.Optional[str]:
"""
The file name with extension, or the last folder name if it is a
directory. If not available, it returns None. For example it doesn't
make sence for s3 bucket.
Logically: dirname + basename = abspath
Example::
# s3 object
>>> S3Path("bucket", "folder", "file.txt").basename
'file.txt'
# s3 directory
>>> S3Path("bucket", "folder/").basename
'folder'
# s3 bucket
>>> S3Path("bucket").basename
None
# void path
>>> S3Path().basename
''
# relative path
>>> S3Path.make_relpath("folder", "file.txt").basename
None
.. versionadded:: 1.0.1
"""
if len(self._parts):
return self._parts[-1]
else:
return ""
[docs]
@FilterableProperty
def dirname(self: "S3Path") -> T.Optional[str]:
"""
The basename of it's parent directory.
Example::
>>> S3Path("bucket", "folder", "file.txt").dirname
'folder'
# root dir name is ''
>>> S3Path("bucket", "folder").dirname
''
.. versionadded:: 1.0.1
"""
return self.parent.basename
[docs]
@FilterableProperty
def fname(self: "S3Path") -> str:
"""
The final path component, minus its last suffix (file extension).
Only if it is not a directory.
Example::
>>> S3Path("bucket", "folder", "file.txt").fname
'file'
.. versionadded:: 1.0.1
"""
if self.is_dir():
raise TypeError
basename: str = self.basename
if not basename:
raise ValueError
i = basename.rfind(".")
if 0 < i < len(basename) - 1:
return basename[:i]
else:
return basename
[docs]
@FilterableProperty
def ext(self: "S3Path") -> str:
"""
The final component's last suffix, if any. Usually it is the file
extension. Only if it is not a directory.
Example::
>>> S3Path("bucket", "folder", "file.txt").fname
'.txt'
.. versionadded:: 1.0.1
"""
if self.is_dir():
raise TypeError
basename: str = self.basename
if not basename:
raise ValueError
i = basename.rfind(".")
if 0 < i < len(basename) - 1:
return basename[i:]
else:
return ""
[docs]
@FilterableProperty
def abspath(self: "S3Path") -> str:
"""
The Unix styled absolute path from the bucket. You can think of the
bucket as a root drive.
Example::
# s3 object
>>> S3Path("bucket", "folder", "file.txt").abspath
'/folder/file.txt'
# s3 directory
>>> S3Path("bucket", "folder/").abspath
'/folder/'
# s3 bucket
>>> S3Path("bucket").abspath
'/'
# void path
>>> S3Path().abspath
TypeError: relative path doesn't have absolute path!
# relative path
>>> S3Path.make_relpath("folder", "file.txt").abspath
TypeError: relative path doesn't have absolute path!
.. versionadded:: 1.0.1
"""
if self._bucket is None:
raise TypeError("relative path doesn't have absolute path!")
if self.is_bucket():
return "/"
elif self.is_dir():
return "/" + "/".join(self._parts) + "/"
elif self.is_file():
return "/" + "/".join(self._parts)
else: # pragma: no cover
raise TypeError
[docs]
@FilterableProperty
def dirpath(self: "S3Path"):
"""
The Unix styled absolute path from the bucket of the **parent directory**.
Example::
# s3 object
>>> S3Path("bucket", "folder", "file.txt").dirpath
'/folder/'
# s3 directory
>>> S3Path("bucket", "folder/").dirpath
'/'
# s3 bucket
>>> S3Path("bucket").dirpath
'/'
# void path
>>> S3Path().dirpath
TypeError: relative path doesn't have absolute path!
# relative path
>>> S3Path.make_relpath("folder", "file.txt").dirpath
TypeError: relative path doesn't have absolute path!
.. versionadded:: 1.0.2
"""
return self.parent.abspath
@property
def root(self: "S3Path") -> "S3Path":
"""
Return the S3 Bucket root folder S3Path object.
Example::
# s3 object
>>> S3Path("bucket", "folder", "file.txt").root
'/folder/'
"""
if self.is_relpath() or self.is_void():
raise TypeError("only concrete File or Directory has a bucket root!")
else:
return self._from_parsed_parts(
bucket=self.bucket,
parts=[],
is_dir=True,
)
def __repr__(self: "S3Path"):
"""
You cannot use the returned string of __repr__ to recover the original
S3Path method.
"""
classname = self.__class__.__name__
if self.is_void():
return "S3VoidPath()"
elif self.is_relpath():
key = self.key
if len(key):
return f"S3RelPath({key!r})"
else: # pragma: no cover
return "S3RelPath()"
else: # bucket, folder or object
uri = self.uri
return "{}('{}')".format(classname, uri)
def __str__(self: "S3Path"):
return self.__repr__()