Skip to content

Commit e678957

Browse files
author
atollk
committed
For the newly added preserve_time parameter, reserved atime and ctime guarantees.
As it turns out, there are platforms which do not allow changing either atime or ctime to custom values. Thus, the proposed behavior was impossible to implement. `preserve_time` now only makes guarantees about mtime.
1 parent b466bcb commit e678957

File tree

10 files changed

+47
-68
lines changed

10 files changed

+47
-68
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1818
- Better documentation of the `writable` parameter of `fs.open_fs`, and
1919
hint about using `fs.wrap.read_only` when a read-only filesystem is
2020
required. Closes [#441](https://github.com/PyFilesystem/pyfilesystem2/issues/441).
21+
- Copy and move operations now provide a parameter `preserve_time` that, when
22+
passed as `True`, makes sure the "mtime" of the destination file will be
23+
the same as that of the source file.
2124

2225
### Changed
2326

fs/base.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -409,8 +409,8 @@ def copy(
409409
dst_path (str): Path to destination file.
410410
overwrite (bool): If `True`, overwrite the destination file
411411
if it exists (defaults to `False`).
412-
preserve_time (bool): If `True`, try to preserve atime, ctime,
413-
and mtime of the resource (defaults to `False`).
412+
preserve_time (bool): If `True`, try to preserve mtime of the
413+
resource (defaults to `False`).
414414
415415
Raises:
416416
fs.errors.DestinationExists: If ``dst_path`` exists,
@@ -443,8 +443,8 @@ def copydir(
443443
dst_path (str): Path to destination directory.
444444
create (bool): If `True`, then ``dst_path`` will be created
445445
if it doesn't exist already (defaults to `False`).
446-
preserve_time (bool): If `True`, try to preserve atime, ctime,
447-
and mtime of the resource (defaults to `False`).
446+
preserve_time (bool): If `True`, try to preserve mtime of the
447+
resource (defaults to `False`).
448448
449449
Raises:
450450
fs.errors.ResourceNotFound: If the ``dst_path``
@@ -1054,8 +1054,8 @@ def movedir(self, src_path, dst_path, create=False, preserve_time=False):
10541054
dst_path (str): Path to destination directory.
10551055
create (bool): If `True`, then ``dst_path`` will be created
10561056
if it doesn't exist already (defaults to `False`).
1057-
preserve_time (bool): If `True`, try to preserve atime, ctime,
1058-
and mtime of the resources (defaults to `False`).
1057+
preserve_time (bool): If `True`, try to preserve mtime of the
1058+
resources (defaults to `False`).
10591059
10601060
Raises:
10611061
fs.errors.ResourceNotFound: if ``dst_path`` does not exist,
@@ -1122,8 +1122,8 @@ def move(self, src_path, dst_path, overwrite=False, preserve_time=False):
11221122
file will be written to.
11231123
overwrite (bool): If `True`, destination path will be
11241124
overwritten if it exists.
1125-
preserve_time (bool): If `True`, try to preserve atime, ctime,
1126-
and mtime of the resources (defaults to `False`).
1125+
preserve_time (bool): If `True`, try to preserve mtime of the
1126+
resources (defaults to `False`).
11271127
11281128
Raises:
11291129
fs.errors.FileExpected: If ``src_path`` maps to a

fs/copy.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ def copy_fs(
4040
dst_path)``.
4141
workers (int): Use `worker` threads to copy data, or ``0`` (default) for
4242
a single-threaded copy.
43-
preserve_time (bool): If `True`, try to preserve atime, ctime,
44-
and mtime of the resources (defaults to `False`).
43+
preserve_time (bool): If `True`, try to preserve mtime of the
44+
resources (defaults to `False`).
4545
4646
"""
4747
return copy_dir(
@@ -76,8 +76,8 @@ def copy_fs_if_newer(
7676
dst_path)``.
7777
workers (int): Use ``worker`` threads to copy data, or ``0`` (default) for
7878
a single-threaded copy.
79-
preserve_time (bool): If `True`, try to preserve atime, ctime,
80-
and mtime of the resources (defaults to `False`).
79+
preserve_time (bool): If `True`, try to preserve mtime of the
80+
resources (defaults to `False`).
8181
8282
"""
8383
return copy_dir_if_newer(
@@ -138,8 +138,8 @@ def copy_file(
138138
src_path (str): Path to a file on the source filesystem.
139139
dst_fs (FS or str): Destination filesystem (instance or URL).
140140
dst_path (str): Path to a file on the destination filesystem.
141-
preserve_time (bool): If `True`, try to preserve atime, ctime,
142-
and mtime of the resource (defaults to `False`).
141+
preserve_time (bool): If `True`, try to preserve mtime of the
142+
resource (defaults to `False`).
143143
144144
"""
145145
with manage_fs(src_fs, writeable=False) as _src_fs:
@@ -182,8 +182,8 @@ def copy_file_internal(
182182
src_path (str): Path to a file on the source filesystem.
183183
dst_fs (FS): Destination filesystem.
184184
dst_path (str): Path to a file on the destination filesystem.
185-
preserve_time (bool): If `True`, try to preserve atime, ctime,
186-
and mtime of the resource (defaults to `False`).
185+
preserve_time (bool): If `True`, try to preserve mtime of the
186+
resource (defaults to `False`).
187187
188188
"""
189189
if src_fs is dst_fs:
@@ -223,8 +223,8 @@ def copy_file_if_newer(
223223
src_path (str): Path to a file on the source filesystem.
224224
dst_fs (FS or str): Destination filesystem (instance or URL).
225225
dst_path (str): Path to a file on the destination filesystem.
226-
preserve_time (bool): If `True`, try to preserve atime, ctime,
227-
and mtime of the resource (defaults to `False`).
226+
preserve_time (bool): If `True`, try to preserve mtime of the
227+
resource (defaults to `False`).
228228
229229
Returns:
230230
bool: `True` if the file copy was executed, `False` otherwise.
@@ -273,8 +273,8 @@ def copy_structure(
273273
walker (~fs.walk.Walker, optional): A walker object that will be
274274
used to scan for files in ``src_fs``. Set this if you only
275275
want to consider a sub-set of the resources in ``src_fs``.
276-
preserve_time (bool): If `True`, try to preserve atime, ctime,
277-
and mtime of the resource (defaults to `False`).
276+
preserve_time (bool): If `True`, try to preserve mtime of the
277+
resource (defaults to `False`).
278278
279279
"""
280280
walker = walker or Walker()
@@ -311,8 +311,8 @@ def copy_dir(
311311
``(src_fs, src_path, dst_fs, dst_path)``.
312312
workers (int): Use ``worker`` threads to copy data, or ``0`` (default) for
313313
a single-threaded copy.
314-
preserve_time (bool): If `True`, try to preserve atime, ctime,
315-
and mtime of the resources (defaults to `False`).
314+
preserve_time (bool): If `True`, try to preserve mtime of the
315+
resources (defaults to `False`).
316316
317317
"""
318318
on_copy = on_copy or (lambda *args: None)
@@ -383,8 +383,8 @@ def copy_dir_if_newer(
383383
``(src_fs, src_path, dst_fs, dst_path)``.
384384
workers (int): Use ``worker`` threads to copy data, or ``0`` (default) for
385385
a single-threaded copy.
386-
preserve_time (bool): If `True`, try to preserve atime, ctime,
387-
and mtime of the resources (defaults to `False`).
386+
preserve_time (bool): If `True`, try to preserve mtime of the
387+
resources (defaults to `False`).
388388
389389
"""
390390
on_copy = on_copy or (lambda *args: None)

fs/mirror.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ def mirror(
7474
workers (int): Number of worker threads used
7575
(0 for single threaded). Set to a relatively low number
7676
for network filesystems, 4 would be a good start.
77-
preserve_time (bool): If `True`, try to preserve atime, ctime,
78-
and mtime of the resources (defaults to `False`).
77+
preserve_time (bool): If `True`, try to preserve mtime of the
78+
resources (defaults to `False`).
7979
8080
"""
8181

fs/move.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ def move_fs(
2929
dst_fs (FS or str): Destination filesystem (instance or URL).
3030
workers (int): Use `worker` threads to copy data, or ``0`` (default) for
3131
a single-threaded copy.
32-
preserve_time (bool): If `True`, try to preserve atime, ctime,
33-
and mtime of the resources (defaults to `False`).
32+
preserve_time (bool): If `True`, try to preserve mtime of the
33+
resources (defaults to `False`).
3434
3535
"""
3636
move_dir(src_fs, "/", dst_fs, "/", workers=workers, preserve_time=preserve_time)
@@ -51,8 +51,8 @@ def move_file(
5151
src_path (str): Path to a file on ``src_fs``.
5252
dst_fs (FS or str): Destination filesystem (instance or URL).
5353
dst_path (str): Path to a file on ``dst_fs``.
54-
preserve_time (bool): If `True`, try to preserve atime, ctime,
55-
and mtime of the resources (defaults to `False`).
54+
preserve_time (bool): If `True`, try to preserve mtime of the
55+
resources (defaults to `False`).
5656
5757
"""
5858
with manage_fs(src_fs) as _src_fs:
@@ -93,8 +93,8 @@ def move_dir(
9393
dst_path (str): Path to a directory on ``dst_fs``.
9494
workers (int): Use ``worker`` threads to copy data, or ``0``
9595
(default) for a single-threaded copy.
96-
preserve_time (bool): If `True`, try to preserve atime, ctime,
97-
and mtime of the resources (defaults to `False`).
96+
preserve_time (bool): If `True`, try to preserve mtime of the
97+
resources (defaults to `False`).
9898
9999
"""
100100

tests/test_copy.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
class TestCopy(unittest.TestCase):
1818
@parameterized.expand([(0,), (1,), (2,), (4,)])
1919
def test_copy_fs(self, workers):
20-
namespaces = ("details", "accessed", "metadata_changed", "modified")
20+
namespaces = ("details", "modified")
2121

2222
src_fs = open_fs("mem://")
2323
src_fs.makedirs("foo/bar")
@@ -37,13 +37,7 @@ def test_copy_fs(self, workers):
3737
dst_file1_info = dst_fs.getinfo("test.txt", namespaces)
3838
dst_file2_info = dst_fs.getinfo("foo/bar/baz.txt", namespaces)
3939
self.assertEqual(dst_file1_info.modified, src_file1_info.modified)
40-
self.assertEqual(
41-
dst_file1_info.metadata_changed, src_file1_info.metadata_changed
42-
)
4340
self.assertEqual(dst_file2_info.modified, src_file2_info.modified)
44-
self.assertEqual(
45-
dst_file2_info.metadata_changed, src_file2_info.metadata_changed
46-
)
4741

4842
def test_copy_value_error(self):
4943
src_fs = open_fs("mem://")
@@ -52,7 +46,7 @@ def test_copy_value_error(self):
5246
fs.copy.copy_fs(src_fs, dst_fs, workers=-1)
5347

5448
def test_copy_dir0(self):
55-
namespaces = ("details", "accessed", "metadata_changed", "modified")
49+
namespaces = ("details", "modified")
5650

5751
src_fs = open_fs("mem://")
5852
src_fs.makedirs("foo/bar")
@@ -69,13 +63,10 @@ def test_copy_dir0(self):
6963

7064
dst_file2_info = dst_fs.getinfo("bar/baz.txt", namespaces)
7165
self.assertEqual(dst_file2_info.modified, src_file2_info.modified)
72-
self.assertEqual(
73-
dst_file2_info.metadata_changed, src_file2_info.metadata_changed
74-
)
7566

7667
@parameterized.expand([(0,), (1,), (2,), (4,)])
7768
def test_copy_dir(self, workers):
78-
namespaces = ("details", "accessed", "metadata_changed", "modified")
69+
namespaces = ("details", "modified")
7970

8071
src_fs = open_fs("mem://")
8172
src_fs.makedirs("foo/bar")
@@ -94,9 +85,6 @@ def test_copy_dir(self, workers):
9485

9586
dst_file2_info = dst_fs.getinfo("bar/baz.txt", namespaces)
9687
self.assertEqual(dst_file2_info.modified, src_file2_info.modified)
97-
self.assertEqual(
98-
dst_file2_info.metadata_changed, src_file2_info.metadata_changed
99-
)
10088

10189
def test_copy_large(self):
10290
data1 = b"foo" * 512 * 1024

tests/test_memoryfs.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,17 @@ def test_copy_preserve_time(self):
7272
self.fs.makedir("bar")
7373
self.fs.touch("foo/file.txt")
7474

75-
namespaces = ("details", "accessed", "metadata_changed", "modified")
75+
namespaces = ("details", "modified")
7676
src_info = self.fs.getinfo("foo/file.txt", namespaces)
7777

7878
self.fs.copy("foo/file.txt", "bar/file.txt", preserve_time=True)
7979
self.assertTrue(self.fs.exists("bar/file.txt"))
8080

8181
dst_info = self.fs.getinfo("bar/file.txt", namespaces)
8282
self.assertEqual(dst_info.modified, src_info.modified)
83-
self.assertEqual(dst_info.metadata_changed, src_info.metadata_changed)
8483

85-
86-
class TestMemoryFile(unittest.TestCase):
8784

85+
class TestMemoryFile(unittest.TestCase):
8886
def setUp(self):
8987
self.fs = memoryfs.MemoryFS()
9088

tests/test_move.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
@parameterized_class(("preserve_time",), [(True,), (False,)])
1212
class TestMove(unittest.TestCase):
1313
def test_move_fs(self):
14-
namespaces = ("details", "accessed", "metadata_changed", "modified")
14+
namespaces = ("details", "modified")
1515

1616
src_fs = open_fs("mem://")
1717
src_fs.makedirs("foo/bar")
@@ -32,16 +32,10 @@ def test_move_fs(self):
3232
dst_file1_info = dst_fs.getinfo("test.txt", namespaces)
3333
dst_file2_info = dst_fs.getinfo("foo/bar/baz.txt", namespaces)
3434
self.assertEqual(dst_file1_info.modified, src_file1_info.modified)
35-
self.assertEqual(
36-
dst_file1_info.metadata_changed, src_file1_info.metadata_changed
37-
)
3835
self.assertEqual(dst_file2_info.modified, src_file2_info.modified)
39-
self.assertEqual(
40-
dst_file2_info.metadata_changed, src_file2_info.metadata_changed
41-
)
4236

4337
def test_move_dir(self):
44-
namespaces = ("details", "accessed", "metadata_changed", "modified")
38+
namespaces = ("details", "modified")
4539

4640
src_fs = open_fs("mem://")
4741
src_fs.makedirs("foo/bar")
@@ -60,6 +54,3 @@ def test_move_dir(self):
6054
if self.preserve_time:
6155
dst_file2_info = dst_fs.getinfo("bar/baz.txt", namespaces)
6256
self.assertEqual(dst_file2_info.modified, src_file2_info.modified)
63-
self.assertEqual(
64-
dst_file2_info.metadata_changed, src_file2_info.metadata_changed
65-
)

tests/test_osfs.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,14 @@ def test_copy_preserve_time(self):
105105
raw_info = {"details": {"accessed": now, "modified": now}}
106106
self.fs.setinfo("foo/file.txt", raw_info)
107107

108-
namespaces = ("details", "accessed", "metadata_changed", "modified")
108+
namespaces = ("details", "modified")
109109
src_info = self.fs.getinfo("foo/file.txt", namespaces)
110110

111111
self.fs.copy("foo/file.txt", "bar/file.txt", preserve_time=True)
112112
self.assertTrue(self.fs.exists("bar/file.txt"))
113113

114114
dst_info = self.fs.getinfo("bar/file.txt", namespaces)
115115
self.assertEqual(dst_info.modified, src_info.modified)
116-
self.assertEqual(dst_info.metadata_changed, src_info.metadata_changed)
117116

118117
@unittest.skipUnless(osfs.sendfile, "sendfile not supported")
119118
@unittest.skipIf(

tests/test_wrap.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def test_scandir(self):
177177
]
178178
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
179179
self.assertEqual(sorted(self.cached.scandir("/"), key=key), expected)
180-
scandir.assert_has_calls([mock.call('/', namespaces=None, page=None)])
180+
scandir.assert_has_calls([mock.call("/", namespaces=None, page=None)])
181181
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
182182
self.assertEqual(sorted(self.cached.scandir("/"), key=key), expected)
183183
scandir.assert_not_called()
@@ -187,7 +187,7 @@ def test_isdir(self):
187187
self.assertTrue(self.cached.isdir("foo"))
188188
self.assertFalse(self.cached.isdir("egg")) # is file
189189
self.assertFalse(self.cached.isdir("spam")) # doesn't exist
190-
scandir.assert_has_calls([mock.call('/', namespaces=None, page=None)])
190+
scandir.assert_has_calls([mock.call("/", namespaces=None, page=None)])
191191
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
192192
self.assertTrue(self.cached.isdir("foo"))
193193
self.assertFalse(self.cached.isdir("egg"))
@@ -199,7 +199,7 @@ def test_isfile(self):
199199
self.assertTrue(self.cached.isfile("egg"))
200200
self.assertFalse(self.cached.isfile("foo")) # is dir
201201
self.assertFalse(self.cached.isfile("spam")) # doesn't exist
202-
scandir.assert_has_calls([mock.call('/', namespaces=None, page=None)])
202+
scandir.assert_has_calls([mock.call("/", namespaces=None, page=None)])
203203
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
204204
self.assertTrue(self.cached.isfile("egg"))
205205
self.assertFalse(self.cached.isfile("foo"))
@@ -211,7 +211,7 @@ def test_getinfo(self):
211211
self.assertEqual(self.cached.getinfo("foo"), self.fs.getinfo("foo"))
212212
self.assertEqual(self.cached.getinfo("/"), self.fs.getinfo("/"))
213213
self.assertNotFound(self.cached.getinfo, "spam")
214-
scandir.assert_has_calls([mock.call('/', namespaces=None, page=None)])
214+
scandir.assert_has_calls([mock.call("/", namespaces=None, page=None)])
215215
with mock.patch.object(self.fs, "scandir", wraps=self.fs.scandir) as scandir:
216216
self.assertEqual(self.cached.getinfo("foo"), self.fs.getinfo("foo"))
217217
self.assertEqual(self.cached.getinfo("/"), self.fs.getinfo("/"))

0 commit comments

Comments
 (0)