safer 4.12.2__tar.gz → 5.0.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: safer
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: 🧿 A safer writer for files and streams 🧿
|
|
5
5
|
Home-page: https://github.com/rec/safer
|
|
6
6
|
License: MIT
|
|
@@ -14,7 +14,6 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Requires-Dist: black (>=23.12.0,<24.0.0)
|
|
18
17
|
Project-URL: Documentation, https://rec.github.io/safer
|
|
19
18
|
Project-URL: Repository, https://github.com/rec/safer
|
|
20
19
|
Description-Content-Type: text/markdown
|
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
[tool.black]
|
|
2
|
-
line-length = 88
|
|
3
|
-
skip-string-normalization = true
|
|
4
|
-
target-version = ["py38"]
|
|
5
|
-
|
|
6
|
-
[tool.doks]
|
|
7
|
-
auto = true
|
|
8
|
-
|
|
9
1
|
[tool.poetry]
|
|
10
2
|
name = "safer"
|
|
11
|
-
version = "
|
|
3
|
+
version = "5.0.0"
|
|
12
4
|
description = "🧿 A safer writer for files and streams 🧿"
|
|
13
5
|
authors = ["Tom Ritchford <tom@swirly.com>"]
|
|
14
6
|
license = "MIT"
|
|
@@ -19,17 +11,14 @@ documentation = "https://rec.github.io/safer"
|
|
|
19
11
|
|
|
20
12
|
[tool.poetry.dependencies]
|
|
21
13
|
python = ">=3.8"
|
|
22
|
-
black = "^23.12.0"
|
|
23
14
|
|
|
24
15
|
[tool.poetry.group.dev.dependencies]
|
|
25
16
|
coverage = "*"
|
|
26
|
-
flake8 = "*"
|
|
27
17
|
pytest = "*"
|
|
28
18
|
readme-renderer = "*"
|
|
29
19
|
tdir = "*"
|
|
30
20
|
toml = "*"
|
|
31
21
|
pyyaml = "*"
|
|
32
|
-
isort = "*"
|
|
33
22
|
ruff = "*"
|
|
34
23
|
mypy = "*"
|
|
35
24
|
|
|
@@ -46,6 +35,18 @@ exclude_lines = [
|
|
|
46
35
|
"if __name__ == .__main__.:",
|
|
47
36
|
"raise NotImplementedError"
|
|
48
37
|
]
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
line-length = 88
|
|
41
|
+
|
|
42
|
+
[tool.ruff.format]
|
|
43
|
+
quote-style = "single"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
[tool.mypy]
|
|
49
47
|
[build-system]
|
|
50
48
|
requires = ["poetry-core"]
|
|
51
49
|
build-backend = "poetry.core.masonry.api"
|
|
50
|
+
|
|
51
|
+
[tool.doks]
|
|
52
|
+
auto = true
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
# 🧿 `safer`: A safer writer 🧿
|
|
1
|
+
"""# 🧿 `safer`: A safer writer 🧿
|
|
3
2
|
|
|
4
3
|
Avoid partial writes or corruption!
|
|
5
4
|
|
|
@@ -42,6 +41,10 @@ https://pypi.org/project/atomicwrites/
|
|
|
42
41
|
It also has a useful `dry_run` setting to let you test your code without
|
|
43
42
|
actually overwriting the target file.
|
|
44
43
|
|
|
44
|
+
NOTE: Just like plain old `open`, if a file that is already opened for writing
|
|
45
|
+
is opened again before the first write has completed, the results are
|
|
46
|
+
unpredictable: so don't do it!
|
|
47
|
+
|
|
45
48
|
* `safer.writer()` wraps an existing writer, socket or stream and writes a
|
|
46
49
|
whole response or nothing
|
|
47
50
|
|
|
@@ -67,7 +70,6 @@ writes the data to a temporary file on disk, which is moved over using
|
|
|
67
70
|
does not work on Windows. (In fact, it's unclear if any of this works on
|
|
68
71
|
Windows, but that certainly won't. Windows developer solicted!)
|
|
69
72
|
|
|
70
|
-
|
|
71
73
|
### Example: `safer.writer()`
|
|
72
74
|
|
|
73
75
|
`safer.writer()` wraps an existing stream - a writer, socket, or callback -
|
|
@@ -143,6 +145,7 @@ With `safer`
|
|
|
143
145
|
for item in items:
|
|
144
146
|
print(item)
|
|
145
147
|
# Either the whole file is written, or nothing
|
|
148
|
+
|
|
146
149
|
"""
|
|
147
150
|
import contextlib
|
|
148
151
|
import functools
|
|
@@ -419,13 +422,13 @@ def open(
|
|
|
419
422
|
|
|
420
423
|
if is_binary:
|
|
421
424
|
if 't' in mode:
|
|
422
|
-
raise ValueError(
|
|
425
|
+
raise ValueError("can't have text and binary mode at once")
|
|
423
426
|
if newline:
|
|
424
|
-
raise ValueError(
|
|
427
|
+
raise ValueError("binary mode doesn't take a newline argument")
|
|
425
428
|
if encoding:
|
|
426
|
-
raise ValueError(
|
|
429
|
+
raise ValueError("binary mode doesn't take an encoding argument")
|
|
427
430
|
if errors:
|
|
428
|
-
raise ValueError(
|
|
431
|
+
raise ValueError("binary mode doesn't take an errors argument")
|
|
429
432
|
|
|
430
433
|
if 'x' in mode and os.path.exists(name):
|
|
431
434
|
raise FileExistsError("File exists: '%s'" % name)
|
|
@@ -657,14 +660,16 @@ class _FileRenameCloser(_FileCloser):
|
|
|
657
660
|
self.target_file = target_file
|
|
658
661
|
self.dry_run = dry_run
|
|
659
662
|
self.is_binary = is_binary
|
|
663
|
+
if temp_file is True:
|
|
664
|
+
parent, file = os.path.split(target_file)
|
|
665
|
+
temp_file = os.path.join(parent, f'.{file}.tmp-safer')
|
|
666
|
+
|
|
660
667
|
super().__init__(temp_file, delete_failures, parent)
|
|
661
668
|
|
|
662
669
|
def _success(self):
|
|
663
670
|
if not self.dry_run:
|
|
664
671
|
if os.path.exists(self.target_file):
|
|
665
672
|
shutil.copymode(self.target_file, self.temp_file)
|
|
666
|
-
else:
|
|
667
|
-
os.chmod(self.temp_file, 0o100644)
|
|
668
673
|
os.replace(self.temp_file, self.target_file)
|
|
669
674
|
|
|
670
675
|
elif callable(self.dry_run):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|