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.
- {utilo-2.110.2/utilo.egg-info → utilo-2.112.0}/PKG-INFO +2 -1
- {utilo-2.110.2 → utilo-2.112.0}/pyproject.toml +2 -1
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_file.py +54 -46
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_logger.py +2 -4
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_utils.py +2 -2
- {utilo-2.110.2 → utilo-2.112.0}/utilo/baw.py +10 -2
- {utilo-2.110.2 → utilo-2.112.0}/utilo/file/__init__.py +116 -62
- {utilo-2.110.2 → utilo-2.112.0}/utilo/file/securewrapper.py +3 -3
- {utilo-2.110.2 → utilo-2.112.0/utilo.egg-info}/PKG-INFO +2 -1
- {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/requires.txt +1 -0
- {utilo-2.110.2 → utilo-2.112.0}/LICENSE +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/README +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/setup.cfg +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_collection.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_convert.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_datetime.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_decorators.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_error.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_likelihood.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_math.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_pages.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_process.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_quick.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_regex.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_string.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_todo.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_typechecker.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/tests/test_yaml.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/__init__.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/__todo__.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/alpha.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/cacher.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/classes.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/classifier/__init__.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/classifier/base.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/classifier/public.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/classifier/strategy.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/cli/__init__.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/collection.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/color.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/config.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/convert.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/datetime.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/decorator.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/error.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/__init__.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/cache.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/collector.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/config.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/cprofile.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/description.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/outpath.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/pack.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/path.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/processor.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/userinput.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/feature/workplan.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/file/action.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/file/directory.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/file/hashed.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/file/info.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/file/loader.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/file/utils.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/group.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/hash.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/likelihood.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/logger.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/__init__.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/const.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/distance.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/func.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/line.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/near.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/number.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/rectangle.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/math/roman.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/optimizer.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/pages.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/process.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/quick.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/regex.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/secret.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/space.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/string/__init__.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/string/compare.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/string/display.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/string/modify.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/string/table.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/tuples.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/typechecker.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/utils.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/xyz/__init__.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/xyz/lock.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/xyz/table.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo/xyz/tidy.py +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/SOURCES.txt +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/dependency_links.txt +0 -0
- {utilo-2.110.2 → utilo-2.112.0}/utilo.egg-info/entry_points.txt +0 -0
- {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.
|
|
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.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
126
|
-
utilo.file_create(
|
|
127
|
-
utilo.file_create(
|
|
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(
|
|
130
|
-
utilo.file_create(
|
|
131
|
-
utilo.file_create(
|
|
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(
|
|
141
|
-
assert os.path.exists(
|
|
142
|
-
assert os.path.exists(
|
|
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(
|
|
145
|
-
assert os.path.exists(
|
|
146
|
-
assert os.path.exists(
|
|
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(
|
|
166
|
-
assert os.path.exists(
|
|
167
|
-
assert os.path.exists(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
229
|
+
destination = utilo.join(td, 'destination')
|
|
230
230
|
utilo.copy_content(filename, destination)
|
|
231
231
|
|
|
232
|
-
assert os.path.exists(
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
280
|
-
|
|
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 =
|
|
283
|
+
pdf = utilo.join(item, 'single.pdf')
|
|
286
284
|
utilo.file_create(pdf)
|
|
287
|
-
notdouble =
|
|
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 =
|
|
331
|
+
folder = utilo.join(directory, 'first')
|
|
334
332
|
|
|
335
|
-
folder_abc =
|
|
336
|
-
folder_def =
|
|
337
|
-
folder_ghi =
|
|
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 =
|
|
416
|
-
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 =
|
|
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 =
|
|
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 =
|
|
481
|
-
dest =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
584
|
-
utilo.file_create(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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)):
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
fp
|
|
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(
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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(
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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(
|
|
114
|
-
if header ==
|
|
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 ==
|
|
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.
|
|
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"
|
|
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
|
|
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
|