utilo 2.110.2__tar.gz → 2.112.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.
Files changed (99) hide show
  1. {utilo-2.110.2/utilo.egg-info → utilo-2.112.0}/PKG-INFO +2 -1
  2. {utilo-2.110.2 → utilo-2.112.0}/pyproject.toml +2 -1
  3. {utilo-2.110.2 → utilo-2.112.0}/tests/test_file.py +54 -46
  4. {utilo-2.110.2 → utilo-2.112.0}/tests/test_logger.py +2 -4
  5. {utilo-2.110.2 → utilo-2.112.0}/tests/test_utils.py +2 -2
  6. {utilo-2.110.2 → utilo-2.112.0}/utilo/baw.py +10 -2
  7. {utilo-2.110.2 → utilo-2.112.0}/utilo/file/__init__.py +116 -62
  8. {utilo-2.110.2 → utilo-2.112.0}/utilo/file/securewrapper.py +3 -3
  9. {utilo-2.110.2 → utilo-2.112.0/utilo.egg-info}/PKG-INFO +2 -1
  10. {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/requires.txt +1 -0
  11. {utilo-2.110.2 → utilo-2.112.0}/LICENSE +0 -0
  12. {utilo-2.110.2 → utilo-2.112.0}/README +0 -0
  13. {utilo-2.110.2 → utilo-2.112.0}/setup.cfg +0 -0
  14. {utilo-2.110.2 → utilo-2.112.0}/tests/test_collection.py +0 -0
  15. {utilo-2.110.2 → utilo-2.112.0}/tests/test_convert.py +0 -0
  16. {utilo-2.110.2 → utilo-2.112.0}/tests/test_datetime.py +0 -0
  17. {utilo-2.110.2 → utilo-2.112.0}/tests/test_decorators.py +0 -0
  18. {utilo-2.110.2 → utilo-2.112.0}/tests/test_error.py +0 -0
  19. {utilo-2.110.2 → utilo-2.112.0}/tests/test_likelihood.py +0 -0
  20. {utilo-2.110.2 → utilo-2.112.0}/tests/test_math.py +0 -0
  21. {utilo-2.110.2 → utilo-2.112.0}/tests/test_pages.py +0 -0
  22. {utilo-2.110.2 → utilo-2.112.0}/tests/test_process.py +0 -0
  23. {utilo-2.110.2 → utilo-2.112.0}/tests/test_quick.py +0 -0
  24. {utilo-2.110.2 → utilo-2.112.0}/tests/test_regex.py +0 -0
  25. {utilo-2.110.2 → utilo-2.112.0}/tests/test_string.py +0 -0
  26. {utilo-2.110.2 → utilo-2.112.0}/tests/test_todo.py +0 -0
  27. {utilo-2.110.2 → utilo-2.112.0}/tests/test_typechecker.py +0 -0
  28. {utilo-2.110.2 → utilo-2.112.0}/tests/test_yaml.py +0 -0
  29. {utilo-2.110.2 → utilo-2.112.0}/utilo/__init__.py +0 -0
  30. {utilo-2.110.2 → utilo-2.112.0}/utilo/__todo__.py +0 -0
  31. {utilo-2.110.2 → utilo-2.112.0}/utilo/alpha.py +0 -0
  32. {utilo-2.110.2 → utilo-2.112.0}/utilo/cacher.py +0 -0
  33. {utilo-2.110.2 → utilo-2.112.0}/utilo/classes.py +0 -0
  34. {utilo-2.110.2 → utilo-2.112.0}/utilo/classifier/__init__.py +0 -0
  35. {utilo-2.110.2 → utilo-2.112.0}/utilo/classifier/base.py +0 -0
  36. {utilo-2.110.2 → utilo-2.112.0}/utilo/classifier/public.py +0 -0
  37. {utilo-2.110.2 → utilo-2.112.0}/utilo/classifier/strategy.py +0 -0
  38. {utilo-2.110.2 → utilo-2.112.0}/utilo/cli/__init__.py +0 -0
  39. {utilo-2.110.2 → utilo-2.112.0}/utilo/collection.py +0 -0
  40. {utilo-2.110.2 → utilo-2.112.0}/utilo/color.py +0 -0
  41. {utilo-2.110.2 → utilo-2.112.0}/utilo/config.py +0 -0
  42. {utilo-2.110.2 → utilo-2.112.0}/utilo/convert.py +0 -0
  43. {utilo-2.110.2 → utilo-2.112.0}/utilo/datetime.py +0 -0
  44. {utilo-2.110.2 → utilo-2.112.0}/utilo/decorator.py +0 -0
  45. {utilo-2.110.2 → utilo-2.112.0}/utilo/error.py +0 -0
  46. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/__init__.py +0 -0
  47. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/cache.py +0 -0
  48. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/collector.py +0 -0
  49. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/config.py +0 -0
  50. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/cprofile.py +0 -0
  51. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/description.py +0 -0
  52. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/outpath.py +0 -0
  53. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/pack.py +0 -0
  54. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/path.py +0 -0
  55. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/processor.py +0 -0
  56. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/userinput.py +0 -0
  57. {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/workplan.py +0 -0
  58. {utilo-2.110.2 → utilo-2.112.0}/utilo/file/action.py +0 -0
  59. {utilo-2.110.2 → utilo-2.112.0}/utilo/file/directory.py +0 -0
  60. {utilo-2.110.2 → utilo-2.112.0}/utilo/file/hashed.py +0 -0
  61. {utilo-2.110.2 → utilo-2.112.0}/utilo/file/info.py +0 -0
  62. {utilo-2.110.2 → utilo-2.112.0}/utilo/file/loader.py +0 -0
  63. {utilo-2.110.2 → utilo-2.112.0}/utilo/file/utils.py +0 -0
  64. {utilo-2.110.2 → utilo-2.112.0}/utilo/group.py +0 -0
  65. {utilo-2.110.2 → utilo-2.112.0}/utilo/hash.py +0 -0
  66. {utilo-2.110.2 → utilo-2.112.0}/utilo/likelihood.py +0 -0
  67. {utilo-2.110.2 → utilo-2.112.0}/utilo/logger.py +0 -0
  68. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/__init__.py +0 -0
  69. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/const.py +0 -0
  70. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/distance.py +0 -0
  71. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/func.py +0 -0
  72. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/line.py +0 -0
  73. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/near.py +0 -0
  74. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/number.py +0 -0
  75. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/rectangle.py +0 -0
  76. {utilo-2.110.2 → utilo-2.112.0}/utilo/math/roman.py +0 -0
  77. {utilo-2.110.2 → utilo-2.112.0}/utilo/optimizer.py +0 -0
  78. {utilo-2.110.2 → utilo-2.112.0}/utilo/pages.py +0 -0
  79. {utilo-2.110.2 → utilo-2.112.0}/utilo/process.py +0 -0
  80. {utilo-2.110.2 → utilo-2.112.0}/utilo/quick.py +0 -0
  81. {utilo-2.110.2 → utilo-2.112.0}/utilo/regex.py +0 -0
  82. {utilo-2.110.2 → utilo-2.112.0}/utilo/secret.py +0 -0
  83. {utilo-2.110.2 → utilo-2.112.0}/utilo/space.py +0 -0
  84. {utilo-2.110.2 → utilo-2.112.0}/utilo/string/__init__.py +0 -0
  85. {utilo-2.110.2 → utilo-2.112.0}/utilo/string/compare.py +0 -0
  86. {utilo-2.110.2 → utilo-2.112.0}/utilo/string/display.py +0 -0
  87. {utilo-2.110.2 → utilo-2.112.0}/utilo/string/modify.py +0 -0
  88. {utilo-2.110.2 → utilo-2.112.0}/utilo/string/table.py +0 -0
  89. {utilo-2.110.2 → utilo-2.112.0}/utilo/tuples.py +0 -0
  90. {utilo-2.110.2 → utilo-2.112.0}/utilo/typechecker.py +0 -0
  91. {utilo-2.110.2 → utilo-2.112.0}/utilo/utils.py +0 -0
  92. {utilo-2.110.2 → utilo-2.112.0}/utilo/xyz/__init__.py +0 -0
  93. {utilo-2.110.2 → utilo-2.112.0}/utilo/xyz/lock.py +0 -0
  94. {utilo-2.110.2 → utilo-2.112.0}/utilo/xyz/table.py +0 -0
  95. {utilo-2.110.2 → utilo-2.112.0}/utilo/xyz/tidy.py +0 -0
  96. {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/SOURCES.txt +0 -0
  97. {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/dependency_links.txt +0 -0
  98. {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/entry_points.txt +0 -0
  99. {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: utilo
3
- Version: 2.110.2
3
+ Version: 2.112.0
4
4
  Author-email: Helmut Konrad Schewe <helmutus@outlook.com>
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/anaticulae/utilo
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.14
11
11
  Requires-Python: >=3.12
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
+ Requires-Dist: filelock<5.0.0,>=4.0.0
14
15
  Requires-Dist: utilosafe<2.0.0,>=1.0.0
15
16
  Provides-Extra: dev
16
17
  Requires-Dist: PyYAML<7.0.0,>=6.0.3; extra == "dev"
@@ -7,13 +7,14 @@ build-backend = "setuptools.build_meta"
7
7
 
8
8
  [project]
9
9
  name = "utilo"
10
- version = "2.110.2"
10
+ version = "2.112.0"
11
11
  description = ""
12
12
  requires-python = ">=3.12"
13
13
  authors = [
14
14
  { name = "Helmut Konrad Schewe", email = "helmutus@outlook.com" },
15
15
  ]
16
16
  dependencies = [
17
+ "filelock>=4.0.0,<5.0.0",
17
18
  "utilosafe>=1.0.0,<2.0.0",
18
19
  ]
19
20
  keywords = []
@@ -18,13 +18,13 @@ import utilo.file.securewrapper
18
18
 
19
19
 
20
20
  def test_file_append_assert(tmpdir):
21
- first = os.path.join(tmpdir, 'abc.txt')
21
+ first = utilo.join(tmpdir, 'abc.txt')
22
22
  with pytest.raises(AssertionError):
23
23
  utilo.file_append(first, '')
24
24
 
25
25
 
26
26
  def test_file_append_create(tmpdir):
27
- first = os.path.join(tmpdir, 'abc.txt')
27
+ first = utilo.join(tmpdir, 'abc.txt')
28
28
  utilo.file_append(first, 'BBB', create=True)
29
29
  assert os.path.exists(first)
30
30
 
@@ -40,7 +40,7 @@ def test_file_from_path_or_raw(tmpdir):
40
40
  load and write
41
41
  """
42
42
 
43
- path = os.path.join(tmpdir, 'example.yaml')
43
+ path = utilo.join(tmpdir, 'example.yaml')
44
44
  utilo.file_create(path, content)
45
45
 
46
46
  from_path = utilo.from_raw_or_path(path)
@@ -122,13 +122,13 @@ def test_file_assert_html_files():
122
122
  @pytest.fixture
123
123
  def content_folder(tmpdir):
124
124
  root = str(tmpdir)
125
- utilo.file_create(os.path.join(root, 'test.txt'))
126
- utilo.file_create(os.path.join(root, 'abc.txt'))
127
- utilo.file_create(os.path.join(root, 'www.txt'))
125
+ utilo.file_create(utilo.join(root, 'test.txt'))
126
+ utilo.file_create(utilo.join(root, 'abc.txt'))
127
+ utilo.file_create(utilo.join(root, 'www.txt'))
128
128
 
129
- os.makedirs(os.path.join(root, 'abc', 'def', 'ghi', 'jklm'))
130
- utilo.file_create(os.path.join(root, 'abc/def/ghi/www.txt'))
131
- utilo.file_create(os.path.join(root, 'abc/def/ghi/jklm/ggg.txt'))
129
+ os.makedirs(utilo.join(root, 'abc', 'def', 'ghi', 'jklm'))
130
+ utilo.file_create(utilo.join(root, 'abc/def/ghi/www.txt'))
131
+ utilo.file_create(utilo.join(root, 'abc/def/ghi/jklm/ggg.txt'))
132
132
  return root
133
133
 
134
134
 
@@ -137,13 +137,13 @@ def test_file_copy_content_recursive(td, content_folder): #pylint:disable=W0621
137
137
  goal = str(td)
138
138
  utilo.copy_content(content_folder, goal, recursive=True)
139
139
 
140
- assert os.path.exists(os.path.join(goal, 'test.txt'))
141
- assert os.path.exists(os.path.join(goal, 'abc.txt'))
142
- assert os.path.exists(os.path.join(goal, 'www.txt'))
140
+ assert os.path.exists(utilo.join(goal, 'test.txt'))
141
+ assert os.path.exists(utilo.join(goal, 'abc.txt'))
142
+ assert os.path.exists(utilo.join(goal, 'www.txt'))
143
143
 
144
- assert os.path.exists(os.path.join(goal, 'abc/def/ghi/jklm'))
145
- assert os.path.exists(os.path.join(goal, 'abc/def/ghi/www.txt'))
146
- assert os.path.exists(os.path.join(goal, 'abc/def/ghi/jklm/ggg.txt'))
144
+ assert os.path.exists(utilo.join(goal, 'abc/def/ghi/jklm'))
145
+ assert os.path.exists(utilo.join(goal, 'abc/def/ghi/www.txt'))
146
+ assert os.path.exists(utilo.join(goal, 'abc/def/ghi/jklm/ggg.txt'))
147
147
 
148
148
 
149
149
  def test_file_copy_content_verbose(td, content_folder, capsys): #pylint:disable=W0621
@@ -162,11 +162,11 @@ def test_file_copy_content_recursive_false(td, content_folder): #pylint:disable
162
162
  goal = str(td)
163
163
  utilo.copy_content(content_folder, goal, recursive=False)
164
164
 
165
- assert os.path.exists(os.path.join(goal, 'test.txt'))
166
- assert os.path.exists(os.path.join(goal, 'abc.txt'))
167
- assert os.path.exists(os.path.join(goal, 'www.txt'))
165
+ assert os.path.exists(utilo.join(goal, 'test.txt'))
166
+ assert os.path.exists(utilo.join(goal, 'abc.txt'))
167
+ assert os.path.exists(utilo.join(goal, 'www.txt'))
168
168
 
169
- assert os.path.exists(os.path.join(goal, 'abc'))
169
+ assert os.path.exists(utilo.join(goal, 'abc'))
170
170
 
171
171
 
172
172
  @pytest.mark.parametrize(
@@ -196,7 +196,7 @@ def test_file_copy_content_pattern(
196
196
  content_folder,
197
197
  ): # pylint:disable=W0621
198
198
  source = content_folder
199
- utilo.file_create(os.path.join(source, 'hallotxt'))
199
+ utilo.file_create(utilo.join(source, 'hallotxt'))
200
200
 
201
201
  root = str(td)
202
202
 
@@ -206,7 +206,7 @@ def test_file_copy_content_pattern(
206
206
 
207
207
 
208
208
  def test_file_replace_file(td):
209
- path = os.path.join(str(td), 'file.txt')
209
+ path = utilo.join(str(td), 'file.txt')
210
210
  assert not os.path.exists(path)
211
211
 
212
212
  utilo.file_replace(path, 'Content')
@@ -226,17 +226,17 @@ def test_file_copy_content_file_to_directory(td):
226
226
  td = str(td)
227
227
  filename = 'abc.txt'
228
228
  utilo.file_create(filename)
229
- destination = os.path.join(td, 'destination')
229
+ destination = utilo.join(td, 'destination')
230
230
  utilo.copy_content(filename, destination)
231
231
 
232
- assert os.path.exists(os.path.join(destination, filename))
232
+ assert os.path.exists(utilo.join(destination, filename))
233
233
 
234
234
 
235
235
  def test_file_copy_content_file_to_file(td):
236
236
  td = str(td)
237
237
  filename = 'abc.txt'
238
238
  utilo.file_create(filename)
239
- destination = os.path.join(td, 'cba.txt')
239
+ destination = utilo.join(td, 'cba.txt')
240
240
  utilo.copy_content(filename, destination)
241
241
 
242
242
  assert os.path.exists(destination)
@@ -246,7 +246,7 @@ def test_file_copy_content_directory_to_directory(td):
246
246
  td = str(td)
247
247
  folder = prepare_example(td)
248
248
 
249
- goal = os.path.join(td, 'goal')
249
+ goal = utilo.join(td, 'goal')
250
250
  utilo.copy_content(folder, goal)
251
251
 
252
252
  assert len(os.listdir(goal)) == 3, os.listdir(goal)
@@ -276,15 +276,13 @@ def test_file_copy_content_access_error(
276
276
 
277
277
  TODO: refactor/simplify with: file_lock/file_unlock
278
278
  """
279
- root = str(td)
280
- source = os.path.join(root, 'source')
281
- sink = os.path.join(root, 'sink')
282
-
279
+ source = utilo.join(td.tmpdir, 'source')
280
+ sink = utilo.join(td.tmpdir, 'sink')
283
281
  for item in (source, sink):
284
282
  os.makedirs(item)
285
- pdf = os.path.join(item, 'single.pdf')
283
+ pdf = utilo.join(item, 'single.pdf')
286
284
  utilo.file_create(pdf)
287
- notdouble = os.path.join(source, 'not_double.pdf')
285
+ notdouble = utilo.join(source, 'not_double.pdf')
288
286
  utilo.file_create(notdouble)
289
287
 
290
288
  def copy(source, _, private: bool = False): # pylint:disable=W0613
@@ -330,11 +328,11 @@ def test_file_copy_lock_withlock(testdir):
330
328
 
331
329
 
332
330
  def prepare_example(directory):
333
- folder = os.path.join(directory, 'first')
331
+ folder = utilo.join(directory, 'first')
334
332
 
335
- folder_abc = os.path.join(folder, 'abc.txt')
336
- folder_def = os.path.join(folder, 'def.txt')
337
- folder_ghi = os.path.join(folder, 'ghi.txt')
333
+ folder_abc = utilo.join(folder, 'abc.txt')
334
+ folder_def = utilo.join(folder, 'def.txt')
335
+ folder_ghi = utilo.join(folder, 'ghi.txt')
338
336
 
339
337
  os.makedirs(folder)
340
338
  for item in (
@@ -412,8 +410,8 @@ def test_file_yaml_path_given():
412
410
  def test_file_compare(first_content, second_content, expected_result, td):
413
411
  root = str(td)
414
412
 
415
- first = os.path.join(root, 'first')
416
- second = os.path.join(root, 'second')
413
+ first = utilo.join(root, 'first')
414
+ second = utilo.join(root, 'second')
417
415
  utilo.file_create(first, first_content)
418
416
  utilo.file_create(second, second_content)
419
417
  equals = utilo.file_compare(first, second)
@@ -422,7 +420,7 @@ def test_file_compare(first_content, second_content, expected_result, td):
422
420
 
423
421
  def test_file_compare_binary_file(td):
424
422
  root = str(td)
425
- utf32 = os.path.join(root, 'example.utf32')
423
+ utf32 = utilo.join(root, 'example.utf32')
426
424
  with open(utf32, mode='w', encoding='utf32') as fp:
427
425
  fp.write('\u1234')
428
426
  equal = utilo.file_compare(utf32, __file__)
@@ -440,7 +438,7 @@ def test_file_compare_not_exists():
440
438
 
441
439
  def test_file_lock(td):
442
440
  root = str(td)
443
- first = os.path.join(root, 'locked.abc')
441
+ first = utilo.join(root, 'locked.abc')
444
442
  utilo.file_create(first, 'file to lock')
445
443
  assert not utilo.file_islocked(first)
446
444
  utilo.file_lock(first)
@@ -477,16 +475,16 @@ def test_file_make_package_root():
477
475
 
478
476
  def test_file_copy_single_file(td):
479
477
  root = str(td)
480
- source = os.path.join(root, 'source')
481
- dest = os.path.join(root, 'destination')
478
+ source = utilo.join(root, 'source')
479
+ dest = utilo.join(root, 'destination')
482
480
  os.makedirs(source)
483
481
  os.makedirs(dest)
484
482
 
485
- sourcefile = os.path.join(source, 'hello')
483
+ sourcefile = utilo.join(source, 'hello')
486
484
  utilo.file_create(sourcefile)
487
485
 
488
486
  utilo.file_copy(sourcefile, dest)
489
- assert os.path.exists(os.path.join(dest, 'hello'))
487
+ assert os.path.exists(utilo.join(dest, 'hello'))
490
488
 
491
489
 
492
490
  def test_file_copy_content_mult(td):
@@ -538,7 +536,7 @@ def test_file_tmpdir():
538
536
 
539
537
 
540
538
  def test_file_replace_binary(tmpdir):
541
- path = os.path.join(tmpdir, 'file.hex')
539
+ path = utilo.join(tmpdir, 'file.hex')
542
540
  utilo.file_replace_binary(path, b'Helm')
543
541
  # replace content
544
542
  utilo.file_replace_binary(path, b'Helm2')
@@ -580,8 +578,8 @@ def test_make_tmpdir():
580
578
  def test_make_tmpdir_remove():
581
579
  with utilo.make_tmpdir(utilo.ROOT, remove=True) as tmp_dir:
582
580
  assert os.path.exists(tmp_dir)
583
- os.makedirs(os.path.join(tmp_dir, 'recursive_path'))
584
- utilo.file_create(os.path.join(tmp_dir, 'helm.txt'))
581
+ os.makedirs(utilo.join(tmp_dir, 'recursive_path'))
582
+ utilo.file_create(utilo.join(tmp_dir, 'helm.txt'))
585
583
  assert not os.path.exists(tmp_dir), tmp_dir
586
584
 
587
585
 
@@ -594,3 +592,13 @@ def test_inform_file_permission(tmpdir, capsys):
594
592
  with pytest.raises(PermissionError, match='Permission denied'):
595
593
  utilo.file_append(path, content='data')
596
594
  assert 'HINT: Ensure that' in utilotest.stderr(capsys)
595
+
596
+
597
+ def test_file_create_owner(tmpdir):
598
+ path = utilo.join(tmpdir, 'file.txt')
599
+ utilo.file_create(path)
600
+ info = os.stat(path)
601
+ mode = info.st_mode
602
+ # -rw-rw-r-- local
603
+ # -rw-r--r-- github
604
+ assert mode in {33204, 33188}
@@ -7,14 +7,12 @@
7
7
  # be prosecuted under federal law. Its content is company confidential.
8
8
  # =============================================================================
9
9
 
10
- import os
11
10
  import time
12
11
 
13
12
  import pytest
14
13
  import utilotest
15
14
 
16
15
  import utilo
17
- import utilo.logger
18
16
 
19
17
 
20
18
  def test_skipcollector():
@@ -166,7 +164,7 @@ def test_level_setup():
166
164
 
167
165
 
168
166
  def test_outfile(td):
169
- logger = os.path.join(td.tmpdir, 'logging.txt')
167
+ logger = utilo.join(td.tmpdir, 'logging.txt')
170
168
  with utilo.outfile_tmp(logger):
171
169
  utilo.log('First Line')
172
170
  utilo.log('Second Line')
@@ -179,7 +177,7 @@ def test_outfile(td):
179
177
 
180
178
 
181
179
  def test_outfile_delete_double(td):
182
- logger = os.path.join(td.tmpdir, 'logging.txt')
180
+ logger = utilo.join(td.tmpdir, 'logging.txt')
183
181
  with utilo.outfile_tmp(logger):
184
182
  utilo.outfile_setup(None)
185
183
  utilo.outfile_setup(None)
@@ -48,7 +48,7 @@ def test_chdir(td):
48
48
  exceptions are handled correctly."""
49
49
  root = str(td)
50
50
 
51
- folder = os.path.join(root, 'folder')
51
+ folder = utilo.join(root, 'folder')
52
52
  os.makedirs(folder)
53
53
  assert os.path.exists(folder)
54
54
 
@@ -75,7 +75,7 @@ def test_chdir_to_filepath(td):
75
75
  """Test to change to a file location with contextmanager."""
76
76
  root = str(td)
77
77
 
78
- filepath = os.path.join(root, 'hello')
78
+ filepath = utilo.join(root, 'hello')
79
79
  utilo.file_create(filepath)
80
80
 
81
81
  assert os.path.exists(filepath)
@@ -15,17 +15,25 @@ import utilo
15
15
  BAW = '.baw'
16
16
 
17
17
 
18
- def baw_root(path: str) -> str:
18
+ def baw_root(path: str, fail: bool = False) -> str:
19
19
  """Go upwards till project config file occurs.
20
20
 
21
21
  >>> baw_root(__file__)
22
22
  '...'
23
+ >>> baw_root('/does/not/exists/', fail=True)
24
+ Traceback (most recent call last):
25
+ ...
26
+ SystemExit: 1
27
+ >>> baw_root('/does/not/exists/', fail=False) is None
28
+ True
23
29
  """
24
30
  current = str(path)
25
- while not utilo.exists(utilo.join(current, BAW)): # pylint:disable=W0149
31
+ while not utilo.exists(utilo.join(current, BAW)):
26
32
  current, base = os.path.split(current)
27
33
  if not str(base).strip():
28
34
  # root of file sytem
35
+ if fail:
36
+ utilo.exitx('could not determine .baw file')
29
37
  return None
30
38
  return current
31
39
 
@@ -21,6 +21,8 @@ import stat
21
21
  import sys
22
22
  import tempfile
23
23
 
24
+ import filelock
25
+
24
26
  import utilo
25
27
  import utilo.file.securewrapper
26
28
  import utilo.typechecker
@@ -82,11 +84,22 @@ def file_append(path: str, content: str, create: bool = False, private: bool = F
82
84
  fp.write(content)
83
85
 
84
86
 
87
+ @contextlib.contextmanager
88
+ def lock_file(path: str, lock: bool = True): # pylint:disable=unused-argument
89
+ if not lock:
90
+ yield
91
+ return
92
+ lockpath = utilo.tmpfile(root=tmp('/utilo'))
93
+ with filelock.FileLock(lockpath):
94
+ yield
95
+
96
+
85
97
  def file_create(
86
98
  path: str,
87
99
  content: str = '',
88
100
  private: bool = False,
89
- base: str = None,
101
+ base: str | None = None,
102
+ lock: bool = True,
90
103
  ):
91
104
  """Create file `path` with the content `content`
92
105
 
@@ -94,23 +107,25 @@ def file_create(
94
107
  path(str): path to write file, path must not exists
95
108
  content(str): content to write in given `path`
96
109
  private(bool): if True, use encryption
97
- base(str): add base to path
110
+ base(str | None): add base to path
111
+ lock(bool): if True use FileLock to protect file create
98
112
  Hint:
99
113
  If file exists, an assertion is raised.
100
114
  """
101
115
  if base:
102
116
  path = utilo.join(base, path)
103
- parent = utilo.path_parent(path)
104
- assert os.path.exists(parent) or not parent, f'{parent} does not exists'
105
- assert not os.path.exists(path), f'{path} already exists'
106
- with utilo.file.securewrapper.open(
107
- path,
108
- mode='w',
109
- newline=utilo.NL,
110
- encoding=utilo.U8,
111
- private=private,
112
- ) as fp:
113
- fp.write(content)
117
+ with lock_file(path, lock=lock):
118
+ parent = utilo.path_parent(path)
119
+ assert os.path.exists(parent) or not parent, f'{parent} does not exists'
120
+ assert not os.path.exists(path), f'{path} already exists'
121
+ with utilo.file.securewrapper.open(
122
+ path,
123
+ mode='w',
124
+ newline=utilo.NL,
125
+ encoding=utilo.U8,
126
+ private=private,
127
+ ) as fp:
128
+ fp.write(content)
114
129
 
115
130
 
116
131
  def file_create_tmp(
@@ -141,40 +156,64 @@ def file_create_tmp(
141
156
  return path
142
157
 
143
158
 
144
- def file_create_binary(path: str, content: bytes = b'', private: bool = False):
159
+ def file_create_binary(
160
+ path: str,
161
+ content: bytes = b'',
162
+ private: bool = False,
163
+ lock: bool = True,
164
+ ):
145
165
  """Create file `path` with the content `content`
146
166
 
147
167
  Args:
148
168
  path(str): path to write file, path must not exists
149
169
  content(str): content to write in given `path`
150
170
  private(bool): if True, use encryption
171
+ lock(bool): if True use FileLock to protect file create
151
172
  Hint:
152
173
  If file already exists, an assertion is raised.
153
174
  """
154
175
  parent = utilo.path_parent(path)
155
- assert os.path.exists(parent) or not parent, f'{parent} does not exists'
156
- assert not os.path.exists(path), f'{path} already exists'
157
- with utilo.file.securewrapper.open(path, mode='wb', private=private) as fp:
158
- fp.write(content)
159
-
160
-
161
- def file_read(path: str, size: int = -1, private: bool = False):
162
- utilo.exists_assert(path)
163
- with utilo.file.securewrapper.open(
164
- path,
165
- mode='r',
166
- newline=utilo.NL,
167
- encoding=utilo.U8,
168
- private=private,
169
- ) as fp:
170
- return fp.read(size)
171
-
172
-
173
- def file_read_binary(path: str, size: int = -1, private: bool = False) -> bytes:
176
+ with lock_file(path, lock=lock):
177
+ assert os.path.exists(parent) or not parent, f'{parent} does not exists'
178
+ assert not os.path.exists(path), f'{path} already exists'
179
+ with utilo.file.securewrapper.open(
180
+ path,
181
+ mode='wb',
182
+ private=private,
183
+ ) as fp:
184
+ fp.write(content)
185
+
186
+
187
+ def file_read(
188
+ path: str,
189
+ size: int = -1,
190
+ private: bool = False,
191
+ lock: bool = True,
192
+ ):
193
+ with lock_file(path, lock=lock):
194
+ utilo.exists_assert(path)
195
+ with utilo.file.securewrapper.open(
196
+ path,
197
+ mode='r',
198
+ newline=utilo.NL,
199
+ encoding=utilo.U8,
200
+ private=private,
201
+ ) as fp:
202
+ return fp.read(size)
203
+
204
+
205
+ def file_read_binary(
206
+ path: str,
207
+ size: int = -1,
208
+ private: bool = False,
209
+ lock: bool = True,
210
+ ) -> bytes:
174
211
  """Read binary file content"""
175
- utilo.exists_assert(path)
176
- with utilo.file.securewrapper.open(path, mode='rb', private=private) as fp:
177
- content = fp.read(size)
212
+ with lock_file(path, lock=lock):
213
+ utilo.exists_assert(path)
214
+ with utilo.file.securewrapper.open(path, mode='rb',
215
+ private=private) as fp:
216
+ content = fp.read(size)
178
217
  return content
179
218
 
180
219
 
@@ -186,33 +225,44 @@ def file_remove(path: str):
186
225
  os.remove(path)
187
226
 
188
227
 
189
- def file_replace(path: str, content: str, private: bool = False):
228
+ def file_replace(
229
+ path: str,
230
+ content: str,
231
+ private: bool = False,
232
+ lock: bool = True,
233
+ ):
190
234
  """Replace file content.
191
235
 
192
236
  Args:
193
237
  path(str): path to file
194
238
  content(str): content to write
195
239
  private(bool): if True, use encryption
240
+ lock(bool): if True use FileLock to protect file create
196
241
 
197
242
  1. If not exit, create file
198
243
  2. If exists, compare content, if changed than replace
199
244
  if not, do nothing
200
245
  """
201
- if not os.path.exists(path):
202
- file_create(path, content, private=private)
203
- return
204
- current_content = file_read(path, private=private)
205
- if current_content == content:
206
- return
207
-
208
- with utilo.file.securewrapper.open(
209
- path,
210
- mode='w',
211
- newline=utilo.NL,
212
- encoding=utilo.U8,
213
- private=private,
214
- ) as fp:
215
- fp.write(content)
246
+ with lock_file(path, lock=lock):
247
+ if not os.path.exists(path):
248
+ file_create(
249
+ path,
250
+ content,
251
+ private=private,
252
+ lock=False,
253
+ )
254
+ return
255
+ current_content = file_read(path, private=private)
256
+ if current_content == content:
257
+ return
258
+ with utilo.file.securewrapper.open(
259
+ path,
260
+ mode='w',
261
+ newline=utilo.NL,
262
+ encoding=utilo.U8,
263
+ private=private,
264
+ ) as fp:
265
+ fp.write(content)
216
266
 
217
267
 
218
268
  def file_replace_binary(path: str, content: bytes, private: bool = False):
@@ -227,15 +277,19 @@ def file_replace_binary(path: str, content: bytes, private: bool = False):
227
277
  2. If exists, compare content, if changed than replace
228
278
  if not, do nothing
229
279
  """
230
- if not os.path.exists(path):
231
- file_create_binary(path, content, private=private)
232
- return
233
- current_content = file_read_binary(path, private=private)
234
- if current_content == content:
235
- return
236
-
237
- with utilo.file.securewrapper.open(path, mode='wb', private=private) as fp:
238
- fp.write(content)
280
+ with lock_file(path, lock=True):
281
+ if not os.path.exists(path):
282
+ file_create_binary(path, content, private=private)
283
+ return
284
+ current_content = file_read_binary(path, private=private)
285
+ if current_content == content:
286
+ return
287
+ with utilo.file.securewrapper.open(
288
+ path,
289
+ mode='wb',
290
+ private=private,
291
+ ) as fp:
292
+ fp.write(content)
239
293
 
240
294
 
241
295
  def file_compare(first: str, second: str) -> bool:
@@ -539,7 +593,7 @@ def tmpfile(root):
539
593
  Returns:
540
594
  filepath(str): to tempfile in TMPDIR
541
595
  """
542
- assert root is None or os.path.exists(root)
596
+ assert root is None or os.path.exists(root), f'root: {root}'
543
597
  tmpfolder = tmp(root)
544
598
  name = tmpname()
545
599
  path = os.path.join(tmpfolder, name)
@@ -110,12 +110,12 @@ def copy(src, dst, private: bool = False):
110
110
  COPY(src, dst)
111
111
  return
112
112
  header = utilo.file_read_binary(src)
113
- header = header[0:len(utilo.file.securewrapper.HEADER_STR)]
114
- if header == utilo.file.securewrapper.HEADER_STR:
113
+ header = header[0:len(HEADER_STR)]
114
+ if header == HEADER_STR:
115
115
  content = utilo.file_read(src, private=True)
116
116
  utilo.file_replace(dst, content, private=True)
117
117
  return
118
- if header == utilo.file.securewrapper.HEADER_BIN:
118
+ if header == HEADER_BIN:
119
119
  content = utilo.file_read_binary(src, private=True)
120
120
  utilo.file_replace_binary(dst, content, private=True)
121
121
  return
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: utilo
3
- Version: 2.110.2
3
+ Version: 2.112.0
4
4
  Author-email: Helmut Konrad Schewe <helmutus@outlook.com>
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/anaticulae/utilo
@@ -11,6 +11,7 @@ Classifier: Programming Language :: Python :: 3.14
11
11
  Requires-Python: >=3.12
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
+ Requires-Dist: filelock<5.0.0,>=4.0.0
14
15
  Requires-Dist: utilosafe<2.0.0,>=1.0.0
15
16
  Provides-Extra: dev
16
17
  Requires-Dist: PyYAML<7.0.0,>=6.0.3; extra == "dev"
@@ -1,3 +1,4 @@
1
+ filelock<5.0.0,>=4.0.0
1
2
  utilosafe<2.0.0,>=1.0.0
2
3
 
3
4
  [dev]
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
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
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
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
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes