Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions typesafety/test_upath_signatures.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
cls: SFTPPath
- module: upath.implementations.smb
cls: SMBPath
- module: upath.implementations.tar
cls: TarPath
- module: upath.implementations.webdav
cls: WebdavPath
- module: upath.implementations.zip
Expand Down Expand Up @@ -271,6 +273,8 @@
cls: SFTPPath
- module: upath.implementations.smb
cls: SMBPath
- module: upath.implementations.tar
cls: TarPath
- module: upath.implementations.webdav
cls: WebdavPath
- module: upath.implementations.zip
Expand Down Expand Up @@ -584,6 +588,8 @@
cls: SFTPPath
- module: upath.implementations.smb
cls: SMBPath
- module: upath.implementations.tar
cls: TarPath
- module: upath.implementations.webdav
cls: WebdavPath
- module: upath.implementations.zip
Expand Down Expand Up @@ -976,6 +982,8 @@
cls: SFTPPath
- module: upath.implementations.smb
cls: SMBPath
- module: upath.implementations.tar
cls: TarPath
- module: upath.implementations.webdav
cls: WebdavPath
- module: upath.implementations.zip
Expand Down
12 changes: 12 additions & 0 deletions typesafety/test_upath_types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
protocol: sftp
- cls_fqn: upath.implementations.smb.SMBPath
protocol: smb
- cls_fqn: upath.implementations.tar.TarPath
protocol: tar
- cls_fqn: upath.implementations.webdav.WebdavPath
protocol: webdav
- cls_fqn: upath.implementations.zip.ZipPath
Expand Down Expand Up @@ -120,6 +122,8 @@
protocol: sftp
- cls_fqn: upath.implementations.smb.SMBPath
protocol: smb
- cls_fqn: upath.implementations.tar.TarPath
protocol: tar
- cls_fqn: upath.implementations.webdav.WebdavPath
protocol: webdav
- cls_fqn: upath.implementations.zip.ZipPath
Expand Down Expand Up @@ -201,6 +205,10 @@
cls: SMBPath
supported_example_name: timeout
supported_example_value: 60
- module: upath.implementations.tar
cls: TarPath
supported_example_name: compression
supported_example_value: '"gzip"'
- module: upath.implementations.webdav
cls: WebdavPath
supported_example_name: base_url
Expand Down Expand Up @@ -266,6 +274,10 @@
cls: SMBPath
supported_example_name: host
unsupported_example_value: 'False'
- module: upath.implementations.tar
cls: TarPath
supported_example_name: compression
unsupported_example_value: '[]'
- module: upath.implementations.webdav
cls: WebdavPath
supported_example_name: base_url
Expand Down
8 changes: 8 additions & 0 deletions upath/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,14 @@ def __new__(
**storage_options: Any,
) -> _uimpl.smb.SMBPath: ...
@overload # noqa: E301
def __new__(
cls,
*args: JoinablePathLike,
protocol: Literal["tar"],
chain_parser: FSSpecChainParser = ...,
**storage_options: Any,
) -> _uimpl.tar.TarPath: ...
@overload # noqa: E301
def __new__(
cls,
*args: JoinablePathLike,
Expand Down
70 changes: 70 additions & 0 deletions upath/implementations/tar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from __future__ import annotations

import stat
import sys
import warnings
from typing import TYPE_CHECKING

from upath._stat import UPathStatResult
from upath.core import UPath
from upath.types import JoinablePathLike

if TYPE_CHECKING:
from collections.abc import Iterator
from typing import Literal

if sys.version_info >= (3, 11):
from typing import Self
from typing import Unpack
else:
from typing_extensions import Self
from typing_extensions import Unpack

from upath._chain import FSSpecChainParser
from upath.types.storage_options import TarStorageOptions


__all__ = ["TarPath"]


class TarPath(UPath):
__slots__ = ()

if TYPE_CHECKING:

def __init__(
self,
*args: JoinablePathLike,
protocol: Literal["zip"] | None = ...,
chain_parser: FSSpecChainParser = ...,
**storage_options: Unpack[TarStorageOptions],
) -> None: ...

def stat(
self,
*,
follow_symlinks: bool = True,
) -> UPathStatResult:
if not follow_symlinks:
warnings.warn(
f"{type(self).__name__}.stat(follow_symlinks=False):"
" is currently ignored.",
UserWarning,
stacklevel=2,
)
info = self.fs.info(self.path).copy()
# convert mode
if info["type"] == "directory":
info["mode"] = stat.S_IFDIR
elif info["type"] == "file":
info["mode"] = stat.S_IFREG
return UPathStatResult.from_info(info)

def iterdir(self) -> Iterator[Self]:
if self.is_file():
raise NotADirectoryError(str(self))
it = iter(super().iterdir())
p0 = next(it)
if p0.name != "":
yield p0
yield from it
4 changes: 4 additions & 0 deletions upath/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
from upath.implementations.memory import MemoryPath as _MemoryPath
from upath.implementations.sftp import SFTPPath as _SFTPPath
from upath.implementations.smb import SMBPath as _SMBPath
from upath.implementations.tar import TarPath as _TarPath
from upath.implementations.webdav import WebdavPath as _WebdavPath
from upath.implementations.zip import ZipPath as _ZipPath

Expand Down Expand Up @@ -102,6 +103,7 @@ class _Registry(MutableMapping[str, "type[upath.UPath]"]):
"simplecache": "upath.implementations.cached.SimpleCachePath",
"sftp": "upath.implementations.sftp.SFTPPath",
"ssh": "upath.implementations.sftp.SFTPPath",
"tar": "upath.implementations.tar.TarPath",
"webdav": "upath.implementations.webdav.WebdavPath",
"webdav+http": "upath.implementations.webdav.WebdavPath",
"webdav+https": "upath.implementations.webdav.WebdavPath",
Expand Down Expand Up @@ -236,6 +238,8 @@ def get_upath_class(protocol: Literal["sftp", "ssh"]) -> type[_SFTPPath]: ...
@overload
def get_upath_class(protocol: Literal["smb"]) -> type[_SMBPath]: ...
@overload
def get_upath_class(protocol: Literal["tar"]) -> type[_TarPath]: ...
@overload
def get_upath_class(protocol: Literal["webdav"]) -> type[_WebdavPath]: ...
@overload
def get_upath_class(protocol: Literal["zip"]) -> type[_ZipPath]: ...
Expand Down
84 changes: 84 additions & 0 deletions upath/tests/implementations/test_tar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import tarfile

import pytest

from upath import UPath
from upath.implementations.tar import TarPath

from ..cases import BaseTests


@pytest.fixture(scope="function")
def tarred_testdir_file(local_testdir, tmp_path_factory):
base = tmp_path_factory.mktemp("tarpath")
tar_path = base / "test.tar"
with tarfile.TarFile(tar_path, "w") as tf:
tf.add(local_testdir, arcname="", recursive=True)
return str(tar_path)


class TestTarPath(BaseTests):

@pytest.fixture(autouse=True)
def path(self, tarred_testdir_file):
self.path = UPath("tar://", fo=tarred_testdir_file)
# self.prepare_file_system() done outside of UPath

def test_is_TarPath(self):
assert isinstance(self.path, TarPath)

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_mkdir(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_mkdir_exists_ok_false(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_mkdir_parents_true_exists_ok_false(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_rename(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_rename2(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_touch(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_touch_unlink(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_write_bytes(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_write_text(self):
pass

@pytest.mark.skip(reason="Tar filesystem is read-only")
def test_fsspec_compat(self):
pass

@pytest.mark.skip(reason="Only testing read on TarPath")
def test_move_local(self, tmp_path):
pass

@pytest.mark.skip(reason="Only testing read on TarPath")
def test_move_into_local(self, tmp_path):
pass

@pytest.mark.skip(reason="Only testing read on TarPath")
def test_move_memory(self, clear_fsspec_memory_cache):
pass

@pytest.mark.skip(reason="Only testing read on TarPath")
def test_move_into_memory(self, clear_fsspec_memory_cache):
pass
1 change: 1 addition & 0 deletions upath/tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"webdav+https",
"github",
"zip",
"tar",
}


Expand Down
17 changes: 17 additions & 0 deletions upath/types/storage_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"SMBStorageOptions",
"WebdavStorageOptions",
"ZipStorageOptions",
"TarStorageOptions",
]


Expand Down Expand Up @@ -376,3 +377,19 @@ class ZipStorageOptions(
compression: int # Compression method (e.g., zipfile.ZIP_STORED, ZIP_DEFLATED)
allowZip64: bool # Enable ZIP64 extensions for large files
compresslevel: int | None # Compression level (None uses default for method)


class TarStorageOptions(
_AbstractStorageOptions,
_ChainableStorageOptions,
total=False,
):
"""Storage options for TAR archive filesystem (read-only)"""

# Archive file settings
fo: str | Any # Path to TAR file or file-like object
# Compression settings
compression: (
str | None
) # Compression method: 'gzip', 'bz2', 'xz', or None for auto-detect
index_store: str | None # Path to store/load the file index cache