Repository: PyFilesystem/pyfilesystem2
Pull request #479 (2021-04-27 11:16:00)

Some setinfo fixes

## Type of changes

- Bug fix

## Checklist

- [x] I've run the latest [black](https://github.com/ambv/black) with default args on new code.
- [x] I've updated CHANGELOG.md and CONTRIBUTORS.md where appropriate.
- [x] I've added tests for new code.
- [x] I accept that @PyFilesystem/maintainers may be pedantic in the code review.

## Description
Unit tests for `FS.setinfo` now function correctly, whereas they basically did nothing before. Also, a bug in OSFS was fixed which caused timestamps to lose precision with `setinfo`.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c24b0e6..f9abcd5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ### Fixed
 
 - Fixed performance bugs in `fs.copy.copy_dir_if_newer`. Test cases were adapted to catch those bugs in the future.
+- Fixed precision bug for timestamps in `fs.OSFS.setinfo`.
 
 
 ## [2.4.13] - 2021-03-27
diff --git a/fs/info.py b/fs/info.py
index 03bf27c..a2208cc 100644
--- a/fs/info.py
+++ b/fs/info.py
@@ -135,6 +135,9 @@ class Info(object):
         When creating an `Info` object, you can add a ``_write`` key to
         each raw namespace that lists which keys are writable or not.
 
+        In general, this means they are compatible with the `setinfo`
+        function of filesystem objects.
+
         Arguments:
             namespace (str): A namespace identifier.
             key (str): A key within the namespace.
diff --git a/fs/osfs.py b/fs/osfs.py
index ac43471..3e6292e 100644
--- a/fs/osfs.py
+++ b/fs/osfs.py
@@ -665,10 +665,10 @@ class OSFS(FS):
         if "details" in info:
             details = info["details"]
             if "accessed" in details or "modified" in details:
-                _accessed = typing.cast(int, details.get("accessed"))
-                _modified = typing.cast(int, details.get("modified", _accessed))
-                accessed = int(_modified if _accessed is None else _accessed)
-                modified = int(_modified)
+                _accessed = typing.cast(float, details.get("accessed"))
+                _modified = typing.cast(float, details.get("modified", _accessed))
+                accessed = float(_modified if _accessed is None else _accessed)
+                modified = float(_modified)
                 if accessed is not None or modified is not None:
                     with convert_os_errors("setinfo", path):
                         os.utime(sys_path, (accessed, modified))


Test patch:
diff --git a/fs/test.py b/fs/test.py
index e40e43c..3e08f4f 100644
--- a/fs/test.py
+++ b/fs/test.py
@@ -12,7 +12,6 @@ from datetime import datetime
 import io
 import itertools
 import json
-import math
 import os
 import time
 import unittest
@@ -1171,15 +1170,21 @@ class FSTestCases(object):
 
     def test_setinfo(self):
         self.fs.create("birthday.txt")
-        now = math.floor(time.time())
+        now = time.time()
 
         change_info = {"details": {"accessed": now + 60, "modified": now + 60 * 60}}
         self.fs.setinfo("birthday.txt", change_info)
-        new_info = self.fs.getinfo("birthday.txt", namespaces=["details"]).raw
-        if "accessed" in new_info.get("_write", []):
-            self.assertEqual(new_info["details"]["accessed"], now + 60)
-        if "modified" in new_info.get("_write", []):
-            self.assertEqual(new_info["details"]["modified"], now + 60 * 60)
+        new_info = self.fs.getinfo("birthday.txt", namespaces=["details"])
+        can_write_acccess = new_info.is_writeable("details", "accessed")
+        can_write_modified = new_info.is_writeable("details", "modified")
+        if can_write_acccess:
+            self.assertAlmostEqual(
+                new_info.get("details", "accessed"), now + 60, places=4
+            )
+        if can_write_modified:
+            self.assertAlmostEqual(
+                new_info.get("details", "modified"), now + 60 * 60, places=4
+            )
 
         with self.assertRaises(errors.ResourceNotFound):
             self.fs.setinfo("nothing", {})
@@ -1188,10 +1193,11 @@ class FSTestCases(object):
         self.fs.create("birthday.txt")
         self.fs.settimes("birthday.txt", accessed=datetime(2016, 7, 5))
         info = self.fs.getinfo("birthday.txt", namespaces=["details"])
-        writeable = info.get("details", "_write", [])
-        if "accessed" in writeable:
+        can_write_acccess = info.is_writeable("details", "accessed")
+        can_write_modified = info.is_writeable("details", "modified")
+        if can_write_acccess:
             self.assertEqual(info.accessed, datetime(2016, 7, 5, tzinfo=pytz.UTC))
-        if "modified" in writeable:
+        if can_write_modified:
             self.assertEqual(info.modified, datetime(2016, 7, 5, tzinfo=pytz.UTC))
 
     def test_touch(self):
diff --git a/tests/test_osfs.py b/tests/test_osfs.py
index ff3af9e..7fdc559 100644
--- a/tests/test_osfs.py
+++ b/tests/test_osfs.py
@@ -111,7 +111,8 @@ class TestOSFS(FSTestCases, unittest.TestCase):
         self.assertTrue(self.fs.exists("bar/file.txt"))
 
         dst_info = self.fs.getinfo("bar/file.txt", namespaces)
-        self.assertEqual(dst_info.modified, src_info.modified)
+        delta = dst_info.modified - src_info.modified
+        self.assertAlmostEqual(delta.total_seconds(), 0, places=2)
 
     @unittest.skipUnless(osfs.sendfile, "sendfile not supported")
     @unittest.skipIf(