File: test_extensions.py

package info (click to toggle)
universal-pathlib 0.3.10-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,656 kB
  • sloc: python: 20,552; makefile: 5
file content (265 lines) | stat: -rw-r--r-- 7,713 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
import os
import sys
from contextlib import nullcontext

import pytest

from upath import UnsupportedOperation
from upath import UPath
from upath.extensions import ProxyUPath
from upath.implementations.local import FilePath
from upath.implementations.local import PosixUPath
from upath.implementations.local import WindowsUPath
from upath.implementations.memory import MemoryPath

from .cases import BaseTests
from .utils import OverrideMeta
from .utils import extends_base
from .utils import overrides_base


class TestProxyMemoryPath(BaseTests, metaclass=OverrideMeta):
    @pytest.fixture(autouse=True)
    def path(self, local_testdir):
        if not local_testdir.startswith("/"):
            local_testdir = "/" + local_testdir
        self.path = ProxyUPath(f"memory:{local_testdir}")
        self.prepare_file_system()

    @overrides_base
    def test_is_correct_class(self):
        assert isinstance(self.path, ProxyUPath)

    @extends_base
    def test_is_not_wrapped_class(self):
        assert not isinstance(self.path, MemoryPath)


class TestProxyFilePath(BaseTests, metaclass=OverrideMeta):
    @pytest.fixture(autouse=True)
    def path(self, local_testdir):
        self.path = ProxyUPath(f"file://{local_testdir}")
        self.prepare_file_system()

    @overrides_base
    def test_is_correct_class(self):
        assert isinstance(self.path, ProxyUPath)

    @extends_base
    def test_is_not_wrapped_class(self):
        assert not isinstance(self.path, FilePath)

    @overrides_base
    def test_chmod(self):
        self.path.joinpath("file1.txt").chmod(777)

    @overrides_base
    def test_cwd(self):
        # ProxyUPath.cwd() works differently on the instance
        self.path.cwd()
        with pytest.raises(UnsupportedOperation):
            type(self.path).cwd()


class TestProxyPathlibPath(BaseTests, metaclass=OverrideMeta):
    @pytest.fixture(autouse=True)
    def path(self, local_testdir):
        self.path = ProxyUPath(f"{local_testdir}")
        self.prepare_file_system()

    @overrides_base
    def test_is_correct_class(self):
        assert isinstance(self.path, ProxyUPath)

    @extends_base
    def test_is_not_wrapped_class(self):
        assert not isinstance(self.path, (PosixUPath, WindowsUPath))

    @overrides_base
    def test_chmod(self):
        self.path.joinpath("file1.txt").chmod(777)

    @overrides_base
    @pytest.mark.skipif(
        sys.version_info < (3, 12), reason="storage options only handled in 3.12+"
    )
    def test_eq(self):
        super().test_eq()

    if sys.version_info < (3, 12):

        @overrides_base
        def test_storage_options_dont_affect_hash(self):
            # On Python < 3.12, storage_options trigger warnings for LocalPath
            with pytest.warns(
                UserWarning,
                match=r".*on python <= \(3, 11\) ignores protocol and storage_options",
            ):
                super().test_storage_options_dont_affect_hash()

    @overrides_base
    def test_group(self):
        pytest.importorskip("grp")
        self.path.group()

    @overrides_base
    def test_owner(self):
        pytest.importorskip("pwd")
        self.path.owner()

    @overrides_base
    def test_readlink(self):
        try:
            os.readlink
        except AttributeError:
            pytest.skip("os.readlink not available on this platform")
        with pytest.raises((OSError, UnsupportedOperation)):
            self.path.readlink()

    @overrides_base
    def test_protocol(self):
        assert self.path.protocol == ""

    @overrides_base
    def test_as_uri(self):
        assert self.path.as_uri().startswith("file://")

    if sys.version_info < (3, 10):

        @overrides_base
        def test_lstat(self):
            # On Python < 3.10, stat(follow_symlinks=False) triggers warnings
            with pytest.warns(
                UserWarning,
                match=r".*stat\(\) follow_symlinks=False is currently ignored",
            ):
                st = self.path.lstat()
            assert st is not None

    else:

        @overrides_base
        def test_lstat(self):
            st = self.path.lstat()
            assert st is not None

    @overrides_base
    def test_relative_to(self):
        base = self.path
        child = self.path / "folder1" / "file1.txt"
        relative = child.relative_to(base)
        assert str(relative) == f"folder1{os.sep}file1.txt"

    @overrides_base
    def test_cwd(self):
        self.path.cwd()
        with pytest.raises(UnsupportedOperation):
            type(self.path).cwd()

    @overrides_base
    def test_lchmod(self):
        # setup
        a = self.path.joinpath("a")
        b = self.path.joinpath("b")
        a.touch()
        b.symlink_to(a)

        # see: https://github.com/python/cpython/issues/108660#issuecomment-1854645898
        if hasattr(os, "lchmod") or os.chmod in os.supports_follow_symlinks:
            cm = nullcontext()
        else:
            cm = pytest.raises((UnsupportedOperation, NotImplementedError))
        with cm:
            b.lchmod(mode=0o777)

    @overrides_base
    def test_symlink_to(self):
        self.path.joinpath("link").symlink_to(self.path)

    @overrides_base
    def test_hardlink_to(self):
        try:
            self.path.joinpath("link").hardlink_to(self.path)
        except PermissionError:
            pass  # hardlink may require elevated permissions


def test_custom_subclass():

    class ReversePath(ProxyUPath):
        def read_bytes_reversed(self):
            return self.read_bytes()[::-1]

        def write_bytes_reversed(self, value):
            self.write_bytes(value[::-1])

    b = MemoryPath("memory://base")

    p = b.joinpath("file1")
    p.write_bytes(b"dlrow olleh")

    r = ReversePath("memory://base/file1")
    assert r.read_bytes_reversed() == b"hello world"

    r.parent.joinpath("file2").write_bytes_reversed(b"dlrow olleh")
    assert b.joinpath("file2").read_bytes() == b"hello world"


def test_protocol_dispatch_deprecation_warning():

    class MyPath(UPath):
        _protocol_dispatch = False

    with pytest.warns(DeprecationWarning, match="_protocol_dispatch = False"):
        a = MyPath(".", protocol="memory")

    assert isinstance(a, MyPath)


# Protocol to sample URI mapping for compatibility tests
_PROTOCOL_URIS = {
    "s3": "s3://bucket/folder",
    "gs": "gs://bucket/folder",
    "memory": "memory://folder",
    "file": "file:/tmp/folder",
    "http": "http://example.com/path",
    "": "/tmp/folder",
}

# Generate incompatible combinations
_PROXY_INCOMPATIBLE_CASES = [
    (_PROTOCOL_URIS[uri_protocol], target_protocol)
    for target_protocol in _PROTOCOL_URIS
    for uri_protocol in _PROTOCOL_URIS
    if target_protocol != uri_protocol and uri_protocol != ""
]


@pytest.mark.parametrize("uri,protocol", _PROXY_INCOMPATIBLE_CASES)
def test_proxy_subclass_incompatible_protocol_uri(uri, protocol):
    """Test that ProxyUPath subclasses raise TypeError for incompatible protocols."""

    class MyProxyPath(ProxyUPath):
        pass

    # ProxyUPath wraps the underlying path, so it should also raise TypeError
    with pytest.raises(TypeError, match=r".*incompatible with"):
        MyProxyPath(uri, protocol=protocol)


def test_proxy_upath_copy_from_local(tmp_path):
    """Test ProxyUPath accepts extra kwargs in _copy_from()

    Regression test for https://github.com/fsspec/universal_pathlib/issues/546
    """

    class MyProxyPath(ProxyUPath):
        pass

    source = UPath(tmp_path / "test.txt")
    source.write_text("hello")

    destination = MyProxyPath("memory://bla/test_copy_from.txt")
    source.move(destination)

    assert destination.read_text() == "hello"