monkeyfs 0.2.4__tar.gz → 0.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/PKG-INFO +1 -1
  2. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/virtual.py +30 -14
  3. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs.egg-info/PKG-INFO +1 -1
  4. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/pyproject.toml +1 -1
  5. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_directory_ops.py +60 -0
  6. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/LICENSE +0 -0
  7. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/README.md +0 -0
  8. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/__init__.py +0 -0
  9. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/base.py +0 -0
  10. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/conformance.py +0 -0
  11. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/context.py +0 -0
  12. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/isolated.py +0 -0
  13. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/mount.py +0 -0
  14. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/patching/__init__.py +0 -0
  15. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/patching/core.py +0 -0
  16. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/patching/fdtable.py +0 -0
  17. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/patching/install.py +0 -0
  18. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/patching/patches.py +0 -0
  19. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/py.typed +0 -0
  20. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/readonly.py +0 -0
  21. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs/virtualfile.py +0 -0
  22. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs.egg-info/SOURCES.txt +0 -0
  23. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs.egg-info/dependency_links.txt +0 -0
  24. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs.egg-info/requires.txt +0 -0
  25. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/monkeyfs.egg-info/top_level.txt +0 -0
  26. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/setup.cfg +0 -0
  27. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_bulk_operations.py +0 -0
  28. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_conformance.py +0 -0
  29. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_expanduser.py +0 -0
  30. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_fd_emulation.py +0 -0
  31. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_finalization_and_invalidate.py +0 -0
  32. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_isolated.py +0 -0
  33. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_lazy_binary_read.py +0 -0
  34. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_metadata.py +0 -0
  35. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_mount.py +0 -0
  36. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_open_optional.py +0 -0
  37. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_patching.py +0 -0
  38. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_patching_pathlib.py +0 -0
  39. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_patching_safe_paths.py +0 -0
  40. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_protocol.py +0 -0
  41. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_ranged_read.py +0 -0
  42. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_readonly.py +0 -0
  43. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_symlink_entries.py +0 -0
  44. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_vfs_optional.py +0 -0
  45. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_vfs_size_limit.py +0 -0
  46. {monkeyfs-0.2.4 → monkeyfs-0.2.5}/tests/test_virtual.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: monkeyfs
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Transparent filesystem interception via monkey-patching.
5
5
  Author: ashenfad
6
6
  License-Expression: MIT
@@ -536,6 +536,18 @@ class VirtualFS:
536
536
  """
537
537
  return dict(self._meta_all())
538
538
 
539
+ @staticmethod
540
+ def _absolute(normalized: str) -> str:
541
+ """A normalized key as an absolute path, for handing back to a
542
+ method that resolves its argument.
543
+
544
+ ``resolve_path`` returns the key form, which has no leading
545
+ slash; passed on as it is, the next method reads it as relative
546
+ to the working directory and names a different path whenever
547
+ that is not ``/``.
548
+ """
549
+ return "/" + normalized.strip("/")
550
+
539
551
  def _normalize_path(self, path: str) -> str:
540
552
  """Normalize file path for consistent internal keys.
541
553
 
@@ -1094,11 +1106,12 @@ class VirtualFS:
1094
1106
  if parent and not self.isdir("/" + parent):
1095
1107
  raise FileNotFoundError(f"No such file or directory: '{path}'")
1096
1108
 
1097
- # Check if already exists
1098
- absolute = "/" + normalized
1099
- if self.isfile(path):
1109
+ # Check if already exists (on the absolute path: ``path`` is the
1110
+ # key form, and checked as it is it would resolve a second time)
1111
+ absolute = self._absolute(normalized)
1112
+ if self.isfile(absolute):
1100
1113
  raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), absolute)
1101
- if self.isdir(path):
1114
+ if self.isdir(absolute):
1102
1115
  if exist_ok:
1103
1116
  return
1104
1117
  raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), absolute)
@@ -1130,8 +1143,8 @@ class VirtualFS:
1130
1143
  """
1131
1144
  path = self.resolve_path(path)
1132
1145
 
1133
- if not exist_ok and self.isdir(path):
1134
- absolute = "/" + path.strip("/")
1146
+ if not exist_ok and self.isdir(self._absolute(path)):
1147
+ absolute = self._absolute(path)
1135
1148
  raise FileExistsError(errno.EEXIST, os.strerror(errno.EEXIST), absolute)
1136
1149
 
1137
1150
  parts = path.strip("/").split("/")
@@ -1157,16 +1170,19 @@ class VirtualFS:
1157
1170
  """
1158
1171
  path = self.resolve_path(path)
1159
1172
  normalized = self._normalize_path(path)
1160
-
1161
- if not self.exists(path):
1162
- raise FileNotFoundError(f"No such directory: {path}")
1163
- if self.isfile(path):
1164
- raise NotADirectoryError(f"Not a directory: {path}")
1165
- if not self.isdir(path):
1166
- raise FileNotFoundError(f"No such directory: {path}")
1173
+ # Checked on the absolute path: ``path`` is the key form, and the
1174
+ # checks resolve their argument again.
1175
+ absolute = self._absolute(normalized)
1176
+
1177
+ if not self.exists(absolute):
1178
+ raise FileNotFoundError(f"No such directory: {absolute}")
1179
+ if self.isfile(absolute):
1180
+ raise NotADirectoryError(f"Not a directory: {absolute}")
1181
+ if not self.isdir(absolute):
1182
+ raise FileNotFoundError(f"No such directory: {absolute}")
1167
1183
 
1168
1184
  # Check if directory is empty
1169
- children = self.list(path)
1185
+ children = self.list(absolute)
1170
1186
  if children:
1171
1187
  raise OSError(f"Directory not empty: {path}")
1172
1188
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: monkeyfs
3
- Version: 0.2.4
3
+ Version: 0.2.5
4
4
  Summary: Transparent filesystem interception via monkey-patching.
5
5
  Author: ashenfad
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "monkeyfs"
3
- version = "0.2.4"
3
+ version = "0.2.5"
4
4
  description = "Transparent filesystem interception via monkey-patching."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -514,3 +514,63 @@ def test_isolatedfs_recursive_listing_names_are_basenames(tmp_path):
514
514
  ("sub", "/probe/sub"),
515
515
  ("two.txt", "/probe/sub/two.txt"),
516
516
  ]
517
+
518
+
519
+ # -- a working directory other than / (issue #30) -------------------------
520
+ #
521
+ # rmdir, mkdir and makedirs resolved a path, then handed the result (the
522
+ # key form, with no leading slash) to checks that resolved it again,
523
+ # relative to the working directory. From / the two resolutions agreed;
524
+ # from anywhere else they named different paths.
525
+
526
+
527
+ @pytest.fixture(params=["/", "/workspace", "/workspace/src"])
528
+ def cwd_fs(request):
529
+ fs = VirtualFS({})
530
+ fs.makedirs("/workspace/src/inner", exist_ok=True)
531
+ fs.write("/workspace/f.txt", b"data")
532
+ fs.chdir(request.param)
533
+ return fs
534
+
535
+
536
+ def test_rmdir_removes_an_empty_directory_from_any_cwd(cwd_fs):
537
+ cwd_fs.rmdir("/workspace/src/inner")
538
+ assert not cwd_fs.exists("/workspace/src/inner")
539
+
540
+
541
+ def test_rmdir_refuses_a_missing_directory_by_its_absolute_path(cwd_fs):
542
+ with pytest.raises(FileNotFoundError, match="/workspace/nope"):
543
+ cwd_fs.rmdir("/workspace/nope")
544
+
545
+
546
+ def test_rmdir_refuses_a_file_from_any_cwd(cwd_fs):
547
+ with pytest.raises(NotADirectoryError):
548
+ cwd_fs.rmdir("/workspace/f.txt")
549
+
550
+
551
+ def test_mkdir_refuses_an_existing_directory_from_any_cwd(cwd_fs):
552
+ with pytest.raises(FileExistsError):
553
+ cwd_fs.mkdir("/workspace/src")
554
+
555
+
556
+ def test_mkdir_refuses_an_existing_file_and_leaves_it_a_file(cwd_fs):
557
+ with pytest.raises(FileExistsError):
558
+ cwd_fs.mkdir("/workspace/f.txt")
559
+ assert cwd_fs.isfile("/workspace/f.txt")
560
+ assert not cwd_fs.isdir("/workspace/f.txt")
561
+ assert cwd_fs.read("/workspace/f.txt") == b"data"
562
+
563
+
564
+ def test_makedirs_without_exist_ok_refuses_an_existing_directory(cwd_fs):
565
+ with pytest.raises(FileExistsError):
566
+ cwd_fs.makedirs("/workspace/src", exist_ok=False)
567
+
568
+
569
+ def test_a_relative_rmdir_and_mkdir_resolve_against_the_cwd():
570
+ fs = VirtualFS({})
571
+ fs.makedirs("/workspace/src", exist_ok=True)
572
+ fs.chdir("/workspace")
573
+ fs.mkdir("new")
574
+ assert fs.isdir("/workspace/new")
575
+ fs.rmdir("src")
576
+ assert not fs.exists("/workspace/src")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes