python-fsutil 0.11.0__tar.gz → 0.13.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fsutil
3
- Version: 0.11.0
3
+ Version: 0.13.0
4
4
  Summary: high-level file-system operations for lazy devs.
5
5
  Author-email: Fabio Caccamo <fabio.caccamo@gmail.com>
6
6
  Maintainer-email: Fabio Caccamo <fabio.caccamo@gmail.com>
@@ -132,6 +132,7 @@ import fsutil
132
132
  - [`get_file_size_formatted`](#get_file_size_formatted)
133
133
  - [`get_filename`](#get_filename)
134
134
  - [`get_parent_dir`](#get_parent_dir)
135
+ - [`get_permissions`](#get_permissions)
135
136
  - [`get_unique_name`](#get_unique_name)
136
137
  - [`is_dir`](#is_dir)
137
138
  - [`is_empty`](#is_empty)
@@ -165,6 +166,7 @@ import fsutil
165
166
  - [`replace_file`](#replace_file)
166
167
  - [`search_dirs`](#search_dirs)
167
168
  - [`search_files`](#search_files)
169
+ - [`set_permissions`](#set_permissions)
168
170
  - [`split_filename`](#split_filename)
169
171
  - [`split_filepath`](#split_filepath)
170
172
  - [`split_path`](#split_path)
@@ -494,10 +496,17 @@ filename = fsutil.get_filename(path)
494
496
  parent_dir = fsutil.get_parent_dir(path, levels=1)
495
497
  ```
496
498
 
499
+ #### `get_permissions`
500
+
501
+ ```python
502
+ # Get the file/directory permissions.
503
+ permissions = fsutil.get_permissions(path)
504
+ ```
505
+
497
506
  #### `get_unique_name`
498
507
 
499
508
  ```python
500
- # Gets a unique name for a directory/file ath the given directory path.
509
+ # Get a unique name for a directory/file ath the given directory path.
501
510
  unique_name = fsutil.get_unique_name(path, prefix="", suffix="", extension="", separator="-")
502
511
  ```
503
512
 
@@ -747,6 +756,13 @@ dirs = fsutil.search_dirs(path, pattern="**/*")
747
756
  files = fsutil.search_files(path, pattern="**/*.*")
748
757
  ```
749
758
 
759
+ #### `set_permissions`
760
+
761
+ ```python
762
+ # Set the file/directory permissions.
763
+ fsutil.set_permissions(path, 700)
764
+ ```
765
+
750
766
  #### `split_filename`
751
767
 
752
768
  ```python
@@ -772,14 +788,14 @@ path_names = fsutil.split_path(path)
772
788
 
773
789
  ```python
774
790
  # Write file with the specified content at the given path.
775
- fsutil.write_file(path, content, append=False, encoding="utf-8")
791
+ fsutil.write_file(path, content, append=False, encoding="utf-8", atomic=False)
776
792
  ```
777
793
 
778
794
  #### `write_file_json`
779
795
 
780
796
  ```python
781
797
  # Write a json file at the given path with the specified data encoded in json format.
782
- fsutil.write_file_json(path, data, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False)
798
+ fsutil.write_file_json(path, data, encoding="utf-8", atomic=False, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False)
783
799
  ```
784
800
 
785
801
  ## Testing
@@ -74,6 +74,7 @@ import fsutil
74
74
  - [`get_file_size_formatted`](#get_file_size_formatted)
75
75
  - [`get_filename`](#get_filename)
76
76
  - [`get_parent_dir`](#get_parent_dir)
77
+ - [`get_permissions`](#get_permissions)
77
78
  - [`get_unique_name`](#get_unique_name)
78
79
  - [`is_dir`](#is_dir)
79
80
  - [`is_empty`](#is_empty)
@@ -107,6 +108,7 @@ import fsutil
107
108
  - [`replace_file`](#replace_file)
108
109
  - [`search_dirs`](#search_dirs)
109
110
  - [`search_files`](#search_files)
111
+ - [`set_permissions`](#set_permissions)
110
112
  - [`split_filename`](#split_filename)
111
113
  - [`split_filepath`](#split_filepath)
112
114
  - [`split_path`](#split_path)
@@ -436,10 +438,17 @@ filename = fsutil.get_filename(path)
436
438
  parent_dir = fsutil.get_parent_dir(path, levels=1)
437
439
  ```
438
440
 
441
+ #### `get_permissions`
442
+
443
+ ```python
444
+ # Get the file/directory permissions.
445
+ permissions = fsutil.get_permissions(path)
446
+ ```
447
+
439
448
  #### `get_unique_name`
440
449
 
441
450
  ```python
442
- # Gets a unique name for a directory/file ath the given directory path.
451
+ # Get a unique name for a directory/file ath the given directory path.
443
452
  unique_name = fsutil.get_unique_name(path, prefix="", suffix="", extension="", separator="-")
444
453
  ```
445
454
 
@@ -689,6 +698,13 @@ dirs = fsutil.search_dirs(path, pattern="**/*")
689
698
  files = fsutil.search_files(path, pattern="**/*.*")
690
699
  ```
691
700
 
701
+ #### `set_permissions`
702
+
703
+ ```python
704
+ # Set the file/directory permissions.
705
+ fsutil.set_permissions(path, 700)
706
+ ```
707
+
692
708
  #### `split_filename`
693
709
 
694
710
  ```python
@@ -714,14 +730,14 @@ path_names = fsutil.split_path(path)
714
730
 
715
731
  ```python
716
732
  # Write file with the specified content at the given path.
717
- fsutil.write_file(path, content, append=False, encoding="utf-8")
733
+ fsutil.write_file(path, content, append=False, encoding="utf-8", atomic=False)
718
734
  ```
719
735
 
720
736
  #### `write_file_json`
721
737
 
722
738
  ```python
723
739
  # Write a json file at the given path with the specified data encoded in json format.
724
- fsutil.write_file_json(path, data, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False)
740
+ fsutil.write_file_json(path, data, encoding="utf-8", atomic=False, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False)
725
741
  ```
726
742
 
727
743
  ## Testing
@@ -80,6 +80,7 @@ __all__ = [
80
80
  "get_file_size_formatted",
81
81
  "get_filename",
82
82
  "get_parent_dir",
83
+ "get_permissions",
83
84
  "get_unique_name",
84
85
  "is_dir",
85
86
  "is_empty",
@@ -113,6 +114,7 @@ __all__ = [
113
114
  "replace_file",
114
115
  "search_dirs",
115
116
  "search_files",
117
+ "set_permissions",
116
118
  "split_filename",
117
119
  "split_filepath",
118
120
  "split_path",
@@ -788,6 +790,17 @@ def get_parent_dir(path: PathIn, *, levels: int = 1) -> str:
788
790
  return join_path(path, *([os.pardir] * max(1, levels)))
789
791
 
790
792
 
793
+ def get_permissions(path: PathIn) -> int:
794
+ """
795
+ Get the file/directory permissions.
796
+ """
797
+ path = _get_path(path)
798
+ assert_exists(path)
799
+ st_mode = os.stat(path).st_mode
800
+ permissions = int(str(oct(st_mode & 0o777))[2:])
801
+ return permissions
802
+
803
+
791
804
  def get_unique_name(
792
805
  path: PathIn,
793
806
  *,
@@ -797,7 +810,7 @@ def get_unique_name(
797
810
  separator: str = "-",
798
811
  ) -> str:
799
812
  """
800
- Gets a unique name for a directory/file ath the given directory path.
813
+ Get a unique name for a directory/file ath the given directory path.
801
814
  """
802
815
  path = _get_path(path)
803
816
  assert_dir(path)
@@ -1282,6 +1295,16 @@ def search_files(path: PathIn, pattern: str = "**/*.*") -> list[str]:
1282
1295
  return _filter_paths(path, _search_paths(path, pattern), predicate=is_file)
1283
1296
 
1284
1297
 
1298
+ def set_permissions(path: PathIn, value: int) -> None:
1299
+ """
1300
+ Set the file/directory permissions.
1301
+ """
1302
+ path = _get_path(path)
1303
+ assert_exists(path)
1304
+ permissions = int(str(value), 8) & 0o777
1305
+ os.chmod(path, permissions)
1306
+
1307
+
1285
1308
  def split_filename(path: PathIn) -> tuple[str, str]:
1286
1309
  """
1287
1310
  Split a filename and returns its basename and extension.
@@ -1314,12 +1337,58 @@ def split_path(path: PathIn) -> list[str]:
1314
1337
  return names
1315
1338
 
1316
1339
 
1340
+ def _write_file_atomic(
1341
+ path: PathIn,
1342
+ content: str,
1343
+ *,
1344
+ append: bool = False,
1345
+ encoding: str = "utf-8",
1346
+ ) -> None:
1347
+ path = _get_path(path)
1348
+ mode = "a" if append else "w"
1349
+ if append:
1350
+ content = read_file(path, encoding=encoding) + content
1351
+ dirpath, _ = split_filepath(path)
1352
+ try:
1353
+ with tempfile.NamedTemporaryFile(
1354
+ mode=mode,
1355
+ dir=dirpath,
1356
+ delete=True,
1357
+ encoding=encoding,
1358
+ ) as file:
1359
+ file.write(content)
1360
+ file.flush()
1361
+ os.fsync(file.fileno())
1362
+ temp_path = file.name
1363
+ if exists(path):
1364
+ set_permissions(temp_path, get_permissions(path))
1365
+ os.replace(temp_path, path)
1366
+ except FileNotFoundError:
1367
+ # success - the NamedTemporaryFile has not been able
1368
+ # to remove the temp file on __exit__ because the temp file
1369
+ # has replaced atomically the file at path.
1370
+ pass
1371
+
1372
+
1373
+ def _write_file_non_atomic(
1374
+ path: PathIn,
1375
+ content: str,
1376
+ *,
1377
+ append: bool = False,
1378
+ encoding: str = "utf-8",
1379
+ ) -> None:
1380
+ mode = "a" if append else "w"
1381
+ with open(path, mode, encoding=encoding) as file:
1382
+ file.write(content)
1383
+
1384
+
1317
1385
  def write_file(
1318
1386
  path: PathIn,
1319
1387
  content: str,
1320
1388
  *,
1321
1389
  append: bool = False,
1322
1390
  encoding: str = "utf-8",
1391
+ atomic: bool = False,
1323
1392
  ) -> None:
1324
1393
  """
1325
1394
  Write file with the specified content at the given path.
@@ -1327,14 +1396,20 @@ def write_file(
1327
1396
  path = _get_path(path)
1328
1397
  assert_not_dir(path)
1329
1398
  make_dirs_for_file(path)
1330
- mode = "a" if append else "w"
1331
- with open(path, mode, encoding=encoding) as file:
1332
- file.write(content)
1399
+ write_file_func = _write_file_atomic if atomic else _write_file_non_atomic
1400
+ write_file_func(
1401
+ path,
1402
+ content,
1403
+ append=append,
1404
+ encoding=encoding,
1405
+ )
1333
1406
 
1334
1407
 
1335
1408
  def write_file_json(
1336
1409
  path: PathIn,
1337
1410
  data: Any,
1411
+ encoding: str = "utf-8",
1412
+ atomic: bool = False,
1338
1413
  **kwargs: Any,
1339
1414
  ) -> None:
1340
1415
  """
@@ -1351,4 +1426,10 @@ def write_file_json(
1351
1426
 
1352
1427
  kwargs.setdefault("default", default_encoder)
1353
1428
  content = json.dumps(data, **kwargs)
1354
- write_file(path, content)
1429
+ write_file(
1430
+ path,
1431
+ content,
1432
+ append=False,
1433
+ encoding=encoding,
1434
+ atomic=atomic,
1435
+ )
@@ -4,4 +4,4 @@ __description__ = "high-level file-system operations for lazy devs."
4
4
  __email__ = "fabio.caccamo@gmail.com"
5
5
  __license__ = "MIT"
6
6
  __title__ = "python-fsutil"
7
- __version__ = "0.11.0"
7
+ __version__ = "0.13.0"
@@ -85,9 +85,6 @@ exclude = '''
85
85
  )/
86
86
  '''
87
87
 
88
- [tool.isort]
89
- profile = "black"
90
-
91
88
  [tool.mypy]
92
89
  files = ["fsutil"]
93
90
  ignore_missing_imports = true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-fsutil
3
- Version: 0.11.0
3
+ Version: 0.13.0
4
4
  Summary: high-level file-system operations for lazy devs.
5
5
  Author-email: Fabio Caccamo <fabio.caccamo@gmail.com>
6
6
  Maintainer-email: Fabio Caccamo <fabio.caccamo@gmail.com>
@@ -132,6 +132,7 @@ import fsutil
132
132
  - [`get_file_size_formatted`](#get_file_size_formatted)
133
133
  - [`get_filename`](#get_filename)
134
134
  - [`get_parent_dir`](#get_parent_dir)
135
+ - [`get_permissions`](#get_permissions)
135
136
  - [`get_unique_name`](#get_unique_name)
136
137
  - [`is_dir`](#is_dir)
137
138
  - [`is_empty`](#is_empty)
@@ -165,6 +166,7 @@ import fsutil
165
166
  - [`replace_file`](#replace_file)
166
167
  - [`search_dirs`](#search_dirs)
167
168
  - [`search_files`](#search_files)
169
+ - [`set_permissions`](#set_permissions)
168
170
  - [`split_filename`](#split_filename)
169
171
  - [`split_filepath`](#split_filepath)
170
172
  - [`split_path`](#split_path)
@@ -494,10 +496,17 @@ filename = fsutil.get_filename(path)
494
496
  parent_dir = fsutil.get_parent_dir(path, levels=1)
495
497
  ```
496
498
 
499
+ #### `get_permissions`
500
+
501
+ ```python
502
+ # Get the file/directory permissions.
503
+ permissions = fsutil.get_permissions(path)
504
+ ```
505
+
497
506
  #### `get_unique_name`
498
507
 
499
508
  ```python
500
- # Gets a unique name for a directory/file ath the given directory path.
509
+ # Get a unique name for a directory/file ath the given directory path.
501
510
  unique_name = fsutil.get_unique_name(path, prefix="", suffix="", extension="", separator="-")
502
511
  ```
503
512
 
@@ -747,6 +756,13 @@ dirs = fsutil.search_dirs(path, pattern="**/*")
747
756
  files = fsutil.search_files(path, pattern="**/*.*")
748
757
  ```
749
758
 
759
+ #### `set_permissions`
760
+
761
+ ```python
762
+ # Set the file/directory permissions.
763
+ fsutil.set_permissions(path, 700)
764
+ ```
765
+
750
766
  #### `split_filename`
751
767
 
752
768
  ```python
@@ -772,14 +788,14 @@ path_names = fsutil.split_path(path)
772
788
 
773
789
  ```python
774
790
  # Write file with the specified content at the given path.
775
- fsutil.write_file(path, content, append=False, encoding="utf-8")
791
+ fsutil.write_file(path, content, append=False, encoding="utf-8", atomic=False)
776
792
  ```
777
793
 
778
794
  #### `write_file_json`
779
795
 
780
796
  ```python
781
797
  # Write a json file at the given path with the specified data encoded in json format.
782
- fsutil.write_file_json(path, data, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False)
798
+ fsutil.write_file_json(path, data, encoding="utf-8", atomic=False, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None, separators=None, default=None, sort_keys=False)
783
799
  ```
784
800
 
785
801
  ## Testing
@@ -737,6 +737,12 @@ class fsutil_test_case(unittest.TestCase):
737
737
  s = "/root/a/b/c/Document.txt"
738
738
  self.assertEqual(fsutil.get_parent_dir(s, levels=6), "/")
739
739
 
740
+ def test_get_permissions(self):
741
+ path = self.temp_path("a/b/c.txt")
742
+ fsutil.write_file(path, content="Hello World")
743
+ permissions = fsutil.get_permissions(path)
744
+ self.assertEqual(permissions, 644)
745
+
740
746
  def test_get_unique_name(self):
741
747
  path = self.temp_path("a/b/c")
742
748
  fsutil.create_dir(path)
@@ -1181,6 +1187,13 @@ class fsutil_test_case(unittest.TestCase):
1181
1187
  ]
1182
1188
  self.assertEqual(results, expected_results)
1183
1189
 
1190
+ def test_set_permissions(self):
1191
+ path = self.temp_path("a/b/c.txt")
1192
+ fsutil.write_file(path, content="Hello World")
1193
+ fsutil.set_permissions(path, 777)
1194
+ permissions = fsutil.get_permissions(path)
1195
+ self.assertEqual(permissions, 777)
1196
+
1184
1197
  def test_split_filename(self):
1185
1198
  s = "Document"
1186
1199
  self.assertEqual(fsutil.split_filename(s), ("Document", ""))
@@ -1209,11 +1222,26 @@ class fsutil_test_case(unittest.TestCase):
1209
1222
 
1210
1223
  def test_write_file(self):
1211
1224
  path = self.temp_path("a/b/c.txt")
1212
- fsutil.write_file(self.temp_path("a/b/c.txt"), content="Hello World")
1225
+ fsutil.write_file(path, content="Hello World")
1226
+ self.assertEqual(fsutil.read_file(path), "Hello World")
1227
+ fsutil.write_file(path, content="Hello Jupiter")
1228
+ self.assertEqual(fsutil.read_file(path), "Hello Jupiter")
1229
+
1230
+ def test_write_file_atomic(self):
1231
+ path = self.temp_path("a/b/c.txt")
1232
+ fsutil.write_file(path, content="Hello World", atomic=True)
1213
1233
  self.assertEqual(fsutil.read_file(path), "Hello World")
1214
- fsutil.write_file(self.temp_path("a/b/c.txt"), content="Hello Jupiter")
1234
+ fsutil.write_file(path, content="Hello Jupiter", atomic=True)
1215
1235
  self.assertEqual(fsutil.read_file(path), "Hello Jupiter")
1216
1236
 
1237
+ def test_write_file_atomic_permissions_inheritance(self):
1238
+ path = self.temp_path("a/b/c.txt")
1239
+ fsutil.write_file(path, content="Hello World", atomic=False)
1240
+ self.assertEqual(fsutil.get_permissions(path), 644)
1241
+ fsutil.set_permissions(path, 777)
1242
+ fsutil.write_file(path, content="Hello Jupiter", atomic=True)
1243
+ self.assertEqual(fsutil.get_permissions(path), 777)
1244
+
1217
1245
  def test_write_file_with_filename_only(self):
1218
1246
  path = "document.txt"
1219
1247
  fsutil.write_file(path, content="Hello World")
@@ -1230,7 +1258,28 @@ class fsutil_test_case(unittest.TestCase):
1230
1258
  "test_datetime": now,
1231
1259
  "test_decimal": dec,
1232
1260
  }
1233
- fsutil.write_file_json(self.temp_path("a/b/c.json"), data=data)
1261
+ fsutil.write_file_json(path, data=data)
1262
+ self.assertEqual(
1263
+ fsutil.read_file(path),
1264
+ (
1265
+ "{"
1266
+ f'"test": "Hello World", '
1267
+ f'"test_datetime": "{now.isoformat()}", '
1268
+ f'"test_decimal": "{dec}"'
1269
+ "}"
1270
+ ),
1271
+ )
1272
+
1273
+ def test_write_file_json_atomic(self):
1274
+ path = self.temp_path("a/b/c.json")
1275
+ now = datetime.now()
1276
+ dec = Decimal("3.33")
1277
+ data = {
1278
+ "test": "Hello World",
1279
+ "test_datetime": now,
1280
+ "test_decimal": dec,
1281
+ }
1282
+ fsutil.write_file_json(path, data=data, atomic=True)
1234
1283
  self.assertEqual(
1235
1284
  fsutil.read_file(path),
1236
1285
  (
@@ -1244,11 +1293,16 @@ class fsutil_test_case(unittest.TestCase):
1244
1293
 
1245
1294
  def test_write_file_with_append(self):
1246
1295
  path = self.temp_path("a/b/c.txt")
1247
- fsutil.write_file(self.temp_path("a/b/c.txt"), content="Hello World")
1296
+ fsutil.write_file(path, content="Hello World")
1248
1297
  self.assertEqual(fsutil.read_file(path), "Hello World")
1249
- fsutil.write_file(
1250
- self.temp_path("a/b/c.txt"), content=" - Hello Sun", append=True
1251
- )
1298
+ fsutil.write_file(path, content=" - Hello Sun", append=True)
1299
+ self.assertEqual(fsutil.read_file(path), "Hello World - Hello Sun")
1300
+
1301
+ def test_write_file_with_append_atomic(self):
1302
+ path = self.temp_path("a/b/c.txt")
1303
+ fsutil.write_file(path, content="Hello World", atomic=True)
1304
+ self.assertEqual(fsutil.read_file(path), "Hello World")
1305
+ fsutil.write_file(path, content=" - Hello Sun", append=True, atomic=True)
1252
1306
  self.assertEqual(fsutil.read_file(path), "Hello World - Hello Sun")
1253
1307
 
1254
1308
 
File without changes
File without changes