flowmark 0.4.4__tar.gz → 0.4.6__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.
- {flowmark-0.4.4 → flowmark-0.4.6}/.gitignore +2 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/PKG-INFO +3 -3
- {flowmark-0.4.4 → flowmark-0.4.6}/pyproject.toml +2 -2
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/line_wrappers.py +54 -2
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/markdown_filling.py +3 -2
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/reformat_api.py +1 -3
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/text_wrapping.py +10 -4
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/test_wrapping.py +50 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/testdocs/testdoc.orig.md +46 -1
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/testdocs/testdoc.out.cleaned.md +68 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/testdocs/testdoc.out.plain.md +68 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/testdocs/testdoc.out.semantic.md +68 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/uv.lock +52 -55
- {flowmark-0.4.4 → flowmark-0.4.6}/.copier-answers.yml +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/.github/workflows/ci.yml +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/.github/workflows/publish.yml +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/LICENSE +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/Makefile +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/README.md +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/development.md +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/devtools/lint.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/installation.md +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/poetry.lock +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/publishing.md +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/__init__.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/cleanups.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/cli.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/custom_marko.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/frontmatter.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/py.typed +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/sentence_split_regex.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/src/flowmark/text_filling.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/test_cleanups.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/test_filling.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/test_frontmatter.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/test_ref_docs.py +0 -0
- {flowmark-0.4.4 → flowmark-0.4.6}/tests/test_sentences.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowmark
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.6
|
|
4
4
|
Summary: Better line wrapping and formatting for plaintext and Markdown
|
|
5
5
|
Project-URL: Repository, https://github.com/jlevy/flowmark
|
|
6
6
|
Author-email: Joshua Levy <joshua@cal.berkeley.edu>
|
|
@@ -16,9 +16,9 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Classifier: Typing :: Typed
|
|
18
18
|
Requires-Python: <4.0,>=3.10
|
|
19
|
-
Requires-Dist: marko>=2.1.
|
|
19
|
+
Requires-Dist: marko>=2.1.3
|
|
20
20
|
Requires-Dist: regex>=2024.11.6
|
|
21
|
-
Requires-Dist: strif>=
|
|
21
|
+
Requires-Dist: strif>=3.0.1
|
|
22
22
|
Requires-Dist: typing-extensions>=4.12.2
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from collections.abc import Callable
|
|
2
3
|
from typing import Protocol
|
|
3
4
|
|
|
@@ -27,6 +28,51 @@ def split_sentences_no_min_length(text: str) -> list[str]:
|
|
|
27
28
|
return split_sentences_regex(text, min_length=0)
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
_line_break_re = re.compile(r"\\\n| \n")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def split_markdown_hard_breaks(text: str) -> list[str]:
|
|
35
|
+
"""
|
|
36
|
+
Split text by explicit Markdown line breaks.
|
|
37
|
+
"""
|
|
38
|
+
return _line_break_re.split(text)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _add_markdown_hard_break_handling(base_wrapper: LineWrapper) -> LineWrapper:
|
|
42
|
+
"""
|
|
43
|
+
Augments a LineWrapper to first split the text by Markdown hard breaks,
|
|
44
|
+
wrap each segment using the base_wrapper, and then rejoin them with
|
|
45
|
+
a normalized Markdown hard break (backslash-newline).
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def enhanced_wrapper(text: str, initial_indent: str, subsequent_indent: str) -> str:
|
|
49
|
+
segments = split_markdown_hard_breaks(text)
|
|
50
|
+
|
|
51
|
+
# Handle empty input.
|
|
52
|
+
if not segments:
|
|
53
|
+
return ""
|
|
54
|
+
# Handle single segment (no hard line breaks).
|
|
55
|
+
if len(segments) == 1:
|
|
56
|
+
return base_wrapper(text, initial_indent, subsequent_indent)
|
|
57
|
+
|
|
58
|
+
wrapped_segments: list[str] = []
|
|
59
|
+
|
|
60
|
+
for i, segment in enumerate(segments):
|
|
61
|
+
is_first = i == 0
|
|
62
|
+
is_last = i == len(segments) - 1
|
|
63
|
+
|
|
64
|
+
cur_initial_indent = initial_indent if is_first else subsequent_indent
|
|
65
|
+
wrapped_segment = base_wrapper(segment, cur_initial_indent, subsequent_indent)
|
|
66
|
+
if is_last:
|
|
67
|
+
wrapped_segments.append(wrapped_segment)
|
|
68
|
+
else:
|
|
69
|
+
wrapped_segments.append(wrapped_segment + "\\")
|
|
70
|
+
|
|
71
|
+
return "\n".join(wrapped_segments)
|
|
72
|
+
|
|
73
|
+
return enhanced_wrapper
|
|
74
|
+
|
|
75
|
+
|
|
30
76
|
def line_wrap_to_width(
|
|
31
77
|
width: int = DEFAULT_WRAP_WIDTH,
|
|
32
78
|
len_fn: Callable[[str], int] = DEFAULT_LEN_FUNCTION,
|
|
@@ -46,7 +92,10 @@ def line_wrap_to_width(
|
|
|
46
92
|
is_markdown=is_markdown,
|
|
47
93
|
)
|
|
48
94
|
|
|
49
|
-
|
|
95
|
+
if is_markdown:
|
|
96
|
+
return _add_markdown_hard_break_handling(line_wrapper)
|
|
97
|
+
else:
|
|
98
|
+
return line_wrapper
|
|
50
99
|
|
|
51
100
|
|
|
52
101
|
def line_wrap_by_sentence(
|
|
@@ -106,4 +155,7 @@ def line_wrap_by_sentence(
|
|
|
106
155
|
|
|
107
156
|
return "\n".join(lines)
|
|
108
157
|
|
|
109
|
-
|
|
158
|
+
if is_markdown:
|
|
159
|
+
return _add_markdown_hard_break_handling(line_wrapper)
|
|
160
|
+
else:
|
|
161
|
+
return line_wrapper
|
|
@@ -30,10 +30,11 @@ def _normalize_html_comments(text: str, break_str: str = "\n\n") -> str:
|
|
|
30
30
|
|
|
31
31
|
# Small hack to avoid changing frontmatter format, for the rare corner
|
|
32
32
|
# case where Markdown contains HTML-style frontmatter.
|
|
33
|
+
# https://github.com/jlevy/frontmatter-format
|
|
33
34
|
def not_frontmatter(text: str) -> bool:
|
|
34
|
-
return "<!---" not in text
|
|
35
|
+
return "<!---" not in text # Three dashes for frontmatter format.
|
|
35
36
|
|
|
36
|
-
# TODO:
|
|
37
|
+
# TODO: Perhaps could add support to do this for block elements like <div>s too?
|
|
37
38
|
return _ensure_surrounding_breaks(
|
|
38
39
|
text, [("<!--", "-->")], break_str=break_str, filter=not_frontmatter
|
|
39
40
|
)
|
|
@@ -63,12 +63,10 @@ def reformat_file(
|
|
|
63
63
|
make_parents: Whether to make parent directories if they don't exist.
|
|
64
64
|
"""
|
|
65
65
|
read_stdin = path == "-"
|
|
66
|
-
write_stdout = output == "-"
|
|
66
|
+
write_stdout = output == "-" or not output
|
|
67
67
|
|
|
68
68
|
if inplace and read_stdin:
|
|
69
69
|
raise ValueError("Cannot use `inplace` with stdin")
|
|
70
|
-
if inplace and output and output != path:
|
|
71
|
-
raise ValueError("Cannot use `inplace` with an output path")
|
|
72
70
|
|
|
73
71
|
if read_stdin:
|
|
74
72
|
text = sys.stdin.read()
|
|
@@ -105,18 +105,24 @@ def wrap_paragraph_lines(
|
|
|
105
105
|
len_fn: Callable[[str], int] = DEFAULT_LEN_FUNCTION,
|
|
106
106
|
is_markdown: bool = False,
|
|
107
107
|
) -> list[str]:
|
|
108
|
-
"""
|
|
108
|
+
r"""
|
|
109
109
|
Wrap a single paragraph of text, returning a list of wrapped lines.
|
|
110
110
|
Rewritten to simplify and generalize Python's textwrap.py.
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
|
|
112
|
+
Set `is_markdown` to True when wrapping markdown text to enable Markdown mode.
|
|
113
|
+
This automatically escapes special markdown characters at the start of wrapped
|
|
114
|
+
lines. It also will preserve line breaks with an explicit Markdown line break,
|
|
115
|
+
i.e. "\\\n" (backslash-newline) or " \n" (two spaces followed by newline) at the
|
|
116
|
+
end of the line. Hard line breaks are normalized to always use "\\\n" as the line
|
|
117
|
+
break.
|
|
113
118
|
"""
|
|
119
|
+
lines: list[str] = []
|
|
120
|
+
|
|
114
121
|
if replace_whitespace:
|
|
115
122
|
text = re.sub(r"\s+", " ", text)
|
|
116
123
|
|
|
117
124
|
words = splitter(text)
|
|
118
125
|
|
|
119
|
-
lines: list[str] = []
|
|
120
126
|
current_line: list[str] = []
|
|
121
127
|
current_width = initial_column
|
|
122
128
|
first_line = True
|
|
@@ -221,3 +221,53 @@ def test_wrap_width():
|
|
|
221
221
|
print(wrapped)
|
|
222
222
|
print([len(line) for line in wrapped])
|
|
223
223
|
assert all(len(line) <= width for line in wrapped)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def test_line_wrap_to_width_with_markdown_breaks():
|
|
227
|
+
from flowmark.line_wrappers import line_wrap_to_width
|
|
228
|
+
|
|
229
|
+
# Get a markdown-aware line wrapper
|
|
230
|
+
wrapper = line_wrap_to_width(width=80, is_markdown=True)
|
|
231
|
+
|
|
232
|
+
# Test trailing space line breaks
|
|
233
|
+
text_with_spaces = "This line ends with spaces \nThis is a new line"
|
|
234
|
+
wrapped_spaces = wrapper(text_with_spaces, initial_indent="", subsequent_indent="")
|
|
235
|
+
assert wrapped_spaces == "This line ends with spaces\\\nThis is a new line"
|
|
236
|
+
|
|
237
|
+
# Test backslash line breaks
|
|
238
|
+
text_with_backslash = "This line ends with backslash\\\nThis is a new line"
|
|
239
|
+
wrapped_backslash = wrapper(text_with_backslash, initial_indent="", subsequent_indent="")
|
|
240
|
+
assert wrapped_backslash == "This line ends with backslash\\\nThis is a new line"
|
|
241
|
+
|
|
242
|
+
# Test wrapping with indentation
|
|
243
|
+
indented_wrapper = line_wrap_to_width(width=40, is_markdown=True)
|
|
244
|
+
long_text = (
|
|
245
|
+
"This is a very long line that will be wrapped and it ends with a line break \n"
|
|
246
|
+
"Next line with content that continues"
|
|
247
|
+
)
|
|
248
|
+
wrapped_long = indented_wrapper(long_text, initial_indent=" ", subsequent_indent=" ")
|
|
249
|
+
assert wrapped_long == (
|
|
250
|
+
" This is a very long line that will be\n"
|
|
251
|
+
" wrapped and it ends with a line\n"
|
|
252
|
+
" break\\\n"
|
|
253
|
+
" Next line with content that\n"
|
|
254
|
+
" continues"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# Test different indentation for segments
|
|
258
|
+
mixed_indent_wrapper = line_wrap_to_width(width=30, is_markdown=True)
|
|
259
|
+
mixed_indent_text = "First segment \nSecond segment\\\nThird segment"
|
|
260
|
+
wrapped_mixed_indent = mixed_indent_wrapper(
|
|
261
|
+
mixed_indent_text, initial_indent="* ", subsequent_indent=" "
|
|
262
|
+
)
|
|
263
|
+
assert wrapped_mixed_indent == ("* First segment\\\n Second segment\\\n Third segment")
|
|
264
|
+
|
|
265
|
+
# Test empty segments
|
|
266
|
+
empty_segment_text = "Before \n\\\nAfter"
|
|
267
|
+
wrapped_empty = wrapper(empty_segment_text, initial_indent="", subsequent_indent="")
|
|
268
|
+
assert wrapped_empty == "Before\\\n\\\nAfter"
|
|
269
|
+
|
|
270
|
+
# Test single segment (no line breaks)
|
|
271
|
+
single_segment = "Text with no breaks"
|
|
272
|
+
wrapped_single = wrapper(single_segment, initial_indent="> ", subsequent_indent=" ")
|
|
273
|
+
assert wrapped_single == "> Text with no breaks"
|
|
@@ -116,7 +116,7 @@ the Center for American Entrepreneurship.)
|
|
|
116
116
|
|
|
117
117
|
### A few paragraphs and bullets
|
|
118
118
|
|
|
119
|
-
A good heuristic is to assume your readers will be **100% intelligent and 100% ignorant**. Of course, in reality, varied experience exists within an individual reader. Most people may already know *something*, and some people are quicker learners than others. Even world class experts most likely only really know parts of a subject, and contributors may have practical experience in one role--perhaps they have been an entrepreneur, for example, but not an investor. So writing with the assumption that each reader could be both has a variety of advantages:
|
|
119
|
+
A good heuristic is to assume your readers will be **100% intelligent and 100% ignorant**. Of course, in reality, varied experience exists within an individual reader. Most people may already know *something*, and some people are quicker learners than others. Even world class experts most likely only really know parts of a subject, and contributors may have practical experience in one role--perhaps they have been an entrepreneur, for example, but not an investor. So writing with the assumption that each reader could be both has a variety of advantages:
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
- Assuming 100% ignorance will help people who think they know a lot about a subject understand **what they didn't know they didn't know**, and fill in the gaps in their knowledge. It encourages contribution from experts and people with practical experience.
|
|
@@ -297,6 +297,9 @@ Recommendations:
|
|
|
297
297
|
|
|
298
298
|
### **3.6 Comparative Features Matrix**
|
|
299
299
|
|
|
300
|
+
This line has a two-space line break.
|
|
301
|
+
And this is a regular line.
|
|
302
|
+
|
|
300
303
|
The following table summarizes the native capabilities of the primary platforms evaluated against the core requirements:
|
|
301
304
|
|
|
302
305
|
| **Feature** | **Vercel** | **Netlify** | **Cloudflare Pages** | **AWS (S3/CloudFront)** |
|
|
@@ -310,6 +313,48 @@ The following table summarizes the native capabilities of the primary platforms
|
|
|
310
313
|
| **Programmatic Invalidation API** | Automatic / Limited | Automatic / Limited | Yes (Cloudflare API) | Yes (CloudFront API) 15-16 |
|
|
311
314
|
| **Basic Cost Model** | Per User / Usage 5 | Per User / Usage 5 | Generous Free Tier / Usage | Usage-Based Components 20-64 |
|
|
312
315
|
|
|
316
|
+
## **3\. Platform\-Specific Mechanisms**
|
|
317
|
+
|
|
318
|
+
Each major operating system provides its own set of system calls, APIs, and conventions for managing temporary files and directories.
|
|
319
|
+
|
|
320
|
+
### **3\.1\. Linux**
|
|
321
|
+
|
|
322
|
+
Linux offers a rich set of POSIX\-compliant and Linux\-specific mechanisms.
|
|
323
|
+
|
|
324
|
+
* **APIs:**
|
|
325
|
+
+ mkstemp(3\): Generates a unique filename based on a template (prefixXXXXXX), creates the file with 0600 permissions, opens it, and returns a file descriptor. It uses O\_EXCL for atomic creation, preventing race conditions.<sup>19</sup> The caller is responsible for unlinking the file.
|
|
326
|
+
+ mkdtemp(3\): Similar to mkstemp but creates a unique directory with 0700 permissions based on a template (prefixXXXXXX), returning the path.<sup>19</sup> The caller must remove the directory.
|
|
327
|
+
+ tmpfile(3\): Creates an unnamed temporary file opened in wb\+ mode (binary read/write), automatically deleted when closed or on process termination.<sup>19</sup> While convenient, POSIX notes potential permission issues and recommends mkstemp followed by
|
|
328
|
+
fdopen for multithreaded apps to avoid leaking file descriptors.<sup>59</sup>
|
|
329
|
+
|
|
330
|
+
* **Recommended Python Approach for Atomic Writes:**
|
|
331
|
+
1. **Use a Library:** Employing a dedicated library like atomicwriter or atomicfile is generally the most robust approach, as they handle temporary file creation in the correct location, atomic renaming (os.replace), and error cleanup across platforms.
|
|
332
|
+
2. **Manual Implementation (if necessary):**
|
|
333
|
+
+ Determine the destination directory: dest\_dir = os.path.dirname(dest\_path)
|
|
334
|
+
+ Create the temporary file in the destination directory: temp\_file = tempfile.NamedTemporaryFile(mode='w', dir=dest\_dir, delete=False) (use appropriate mode, e.g., wb for binary).
|
|
335
|
+
+ Use a try...finally block to ensure cleanup:\
|
|
336
|
+
Python\
|
|
337
|
+
temp\_file = None\
|
|
338
|
+
try:\
|
|
339
|
+
temp\_file = tempfile.NamedTemporaryFile(mode='w', dir=dest\_dir, delete=False, prefix='tmp', suffix='.tmp')\
|
|
340
|
+
# Write data to temp\_file.file (or temp\_file directly in older Pythons)\
|
|
341
|
+
temp\_file.write(...)\
|
|
342
|
+
# Ensure data is on disk before renaming\
|
|
343
|
+
temp\_file.flush()\
|
|
344
|
+
os.fsync(temp\_file.fileno())\
|
|
345
|
+
# Close the file handle before renaming\
|
|
346
|
+
temp\_file.close()\
|
|
347
|
+
# Atomic rename/replace\
|
|
348
|
+
os.replace(temp\_file.name, dest\_path)\
|
|
349
|
+
temp\_file = None # Prevent cleanup in finally block if successful\
|
|
350
|
+
finally:\
|
|
351
|
+
if temp\_file is not None and os.path.exists(temp\_file.name):\
|
|
352
|
+
try:\
|
|
353
|
+
os.unlink(temp\_file.name)\
|
|
354
|
+
except OSError:\
|
|
355
|
+
# Log error, but continue\
|
|
356
|
+
pass\
|
|
357
|
+
|
|
313
358
|
### Boldface, italics, and links
|
|
314
359
|
|
|
315
360
|
X [New York City](https://en.wikipedia.org/wiki/New_York_City).
|
|
@@ -471,6 +471,9 @@ Recommendations:
|
|
|
471
471
|
|
|
472
472
|
### 3.6 Comparative Features Matrix
|
|
473
473
|
|
|
474
|
+
This line has a two-space line break.\
|
|
475
|
+
And this is a regular line.
|
|
476
|
+
|
|
474
477
|
The following table summarizes the native capabilities of the primary platforms
|
|
475
478
|
evaluated against the core requirements:
|
|
476
479
|
|
|
@@ -485,6 +488,71 @@ evaluated against the core requirements:
|
|
|
485
488
|
| **Programmatic Invalidation API** | Automatic / Limited | Automatic / Limited | Yes (Cloudflare API) | Yes (CloudFront API) 15-16 |
|
|
486
489
|
| **Basic Cost Model** | Per User / Usage 5 | Per User / Usage 5 | Generous Free Tier / Usage | Usage-Based Components 20-64 |
|
|
487
490
|
|
|
491
|
+
## 3\. Platform\-Specific Mechanisms
|
|
492
|
+
|
|
493
|
+
Each major operating system provides its own set of system calls, APIs, and conventions
|
|
494
|
+
for managing temporary files and directories.
|
|
495
|
+
|
|
496
|
+
### 3\.1\. Linux
|
|
497
|
+
|
|
498
|
+
Linux offers a rich set of POSIX\-compliant and Linux\-specific mechanisms.
|
|
499
|
+
|
|
500
|
+
* **APIs:**
|
|
501
|
+
|
|
502
|
+
+ mkstemp(3\): Generates a unique filename based on a template (prefixXXXXXX), creates
|
|
503
|
+
the file with 0600 permissions, opens it, and returns a file descriptor.
|
|
504
|
+
It uses O\_EXCL for atomic creation, preventing race conditions.<sup>19</sup> The
|
|
505
|
+
caller is responsible for unlinking the file.
|
|
506
|
+
|
|
507
|
+
+ mkdtemp(3\): Similar to mkstemp but creates a unique directory with 0700 permissions
|
|
508
|
+
based on a template (prefixXXXXXX), returning the path.<sup>19</sup> The caller must
|
|
509
|
+
remove the directory.
|
|
510
|
+
|
|
511
|
+
+ tmpfile(3\): Creates an unnamed temporary file opened in wb\+ mode (binary
|
|
512
|
+
read/write), automatically deleted when closed or on process
|
|
513
|
+
termination.<sup>19</sup> While convenient, POSIX notes potential permission issues
|
|
514
|
+
and recommends mkstemp followed by fdopen for multithreaded apps to avoid leaking
|
|
515
|
+
file descriptors.<sup>59</sup>
|
|
516
|
+
|
|
517
|
+
* **Recommended Python Approach for Atomic Writes:**
|
|
518
|
+
|
|
519
|
+
1. **Use a Library:** Employing a dedicated library like atomicwriter or atomicfile is
|
|
520
|
+
generally the most robust approach, as they handle temporary file creation in the
|
|
521
|
+
correct location, atomic renaming (os.replace), and error cleanup across platforms.
|
|
522
|
+
|
|
523
|
+
2. **Manual Implementation (if necessary):**
|
|
524
|
+
|
|
525
|
+
+ Determine the destination directory: dest\_dir = os.path.dirname(dest\_path)
|
|
526
|
+
|
|
527
|
+
+ Create the temporary file in the destination directory: temp\_file =
|
|
528
|
+
tempfile.NamedTemporaryFile(mode='w', dir=dest\_dir, delete=False) (use
|
|
529
|
+
appropriate mode, e.g., wb for binary).
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
+ Use a try...finally block to ensure cleanup:\
|
|
533
|
+
Python\
|
|
534
|
+
temp\_file = None\
|
|
535
|
+
try:\
|
|
536
|
+
temp\_file = tempfile.NamedTemporaryFile(mode='w', dir=dest\_dir, delete=False,
|
|
537
|
+
prefix='tmp', suffix='.tmp')\
|
|
538
|
+
# Write data to temp\_file.file (or temp\_file directly in older Pythons)\
|
|
539
|
+
temp\_file.write(...)\
|
|
540
|
+
# Ensure data is on disk before renaming\
|
|
541
|
+
temp\_file.flush()\
|
|
542
|
+
os.fsync(temp\_file.fileno())\
|
|
543
|
+
# Close the file handle before renaming\
|
|
544
|
+
temp\_file.close()\
|
|
545
|
+
# Atomic rename/replace\
|
|
546
|
+
os.replace(temp\_file.name, dest\_path)\
|
|
547
|
+
temp\_file = None # Prevent cleanup in finally block if successful\
|
|
548
|
+
finally:\
|
|
549
|
+
if temp\_file is not None and os.path.exists(temp\_file.name):\
|
|
550
|
+
try:\
|
|
551
|
+
os.unlink(temp\_file.name)\
|
|
552
|
+
except OSError:\
|
|
553
|
+
# Log error, but continue\
|
|
554
|
+
pass\
|
|
555
|
+
|
|
488
556
|
### Boldface, italics, and links
|
|
489
557
|
|
|
490
558
|
X [New York City](https://en.wikipedia.org/wiki/New_York_City). XX
|
|
@@ -448,6 +448,9 @@ Recommendations:
|
|
|
448
448
|
|
|
449
449
|
### **3.6 Comparative Features Matrix**
|
|
450
450
|
|
|
451
|
+
This line has a two-space line break.\
|
|
452
|
+
And this is a regular line.
|
|
453
|
+
|
|
451
454
|
The following table summarizes the native capabilities of the primary platforms
|
|
452
455
|
evaluated against the core requirements:
|
|
453
456
|
|
|
@@ -462,6 +465,71 @@ evaluated against the core requirements:
|
|
|
462
465
|
| **Programmatic Invalidation API** | Automatic / Limited | Automatic / Limited | Yes (Cloudflare API) | Yes (CloudFront API) 15-16 |
|
|
463
466
|
| **Basic Cost Model** | Per User / Usage 5 | Per User / Usage 5 | Generous Free Tier / Usage | Usage-Based Components 20-64 |
|
|
464
467
|
|
|
468
|
+
## **3\. Platform\-Specific Mechanisms**
|
|
469
|
+
|
|
470
|
+
Each major operating system provides its own set of system calls, APIs, and conventions
|
|
471
|
+
for managing temporary files and directories.
|
|
472
|
+
|
|
473
|
+
### **3\.1\. Linux**
|
|
474
|
+
|
|
475
|
+
Linux offers a rich set of POSIX\-compliant and Linux\-specific mechanisms.
|
|
476
|
+
|
|
477
|
+
* **APIs:**
|
|
478
|
+
|
|
479
|
+
+ mkstemp(3\): Generates a unique filename based on a template (prefixXXXXXX), creates
|
|
480
|
+
the file with 0600 permissions, opens it, and returns a file descriptor. It uses
|
|
481
|
+
O\_EXCL for atomic creation, preventing race conditions.<sup>19</sup> The caller is
|
|
482
|
+
responsible for unlinking the file.
|
|
483
|
+
|
|
484
|
+
+ mkdtemp(3\): Similar to mkstemp but creates a unique directory with 0700 permissions
|
|
485
|
+
based on a template (prefixXXXXXX), returning the path.<sup>19</sup> The caller must
|
|
486
|
+
remove the directory.
|
|
487
|
+
|
|
488
|
+
+ tmpfile(3\): Creates an unnamed temporary file opened in wb\+ mode (binary
|
|
489
|
+
read/write), automatically deleted when closed or on process
|
|
490
|
+
termination.<sup>19</sup> While convenient, POSIX notes potential permission issues
|
|
491
|
+
and recommends mkstemp followed by fdopen for multithreaded apps to avoid leaking
|
|
492
|
+
file descriptors.<sup>59</sup>
|
|
493
|
+
|
|
494
|
+
* **Recommended Python Approach for Atomic Writes:**
|
|
495
|
+
|
|
496
|
+
1. **Use a Library:** Employing a dedicated library like atomicwriter or atomicfile is
|
|
497
|
+
generally the most robust approach, as they handle temporary file creation in the
|
|
498
|
+
correct location, atomic renaming (os.replace), and error cleanup across platforms.
|
|
499
|
+
|
|
500
|
+
2. **Manual Implementation (if necessary):**
|
|
501
|
+
|
|
502
|
+
+ Determine the destination directory: dest\_dir = os.path.dirname(dest\_path)
|
|
503
|
+
|
|
504
|
+
+ Create the temporary file in the destination directory: temp\_file =
|
|
505
|
+
tempfile.NamedTemporaryFile(mode='w', dir=dest\_dir, delete=False) (use
|
|
506
|
+
appropriate mode, e.g., wb for binary).
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
+ Use a try...finally block to ensure cleanup:\
|
|
510
|
+
Python\
|
|
511
|
+
temp\_file = None\
|
|
512
|
+
try:\
|
|
513
|
+
temp\_file = tempfile.NamedTemporaryFile(mode='w', dir=dest\_dir, delete=False,
|
|
514
|
+
prefix='tmp', suffix='.tmp')\
|
|
515
|
+
# Write data to temp\_file.file (or temp\_file directly in older Pythons)\
|
|
516
|
+
temp\_file.write(...)\
|
|
517
|
+
# Ensure data is on disk before renaming\
|
|
518
|
+
temp\_file.flush()\
|
|
519
|
+
os.fsync(temp\_file.fileno())\
|
|
520
|
+
# Close the file handle before renaming\
|
|
521
|
+
temp\_file.close()\
|
|
522
|
+
# Atomic rename/replace\
|
|
523
|
+
os.replace(temp\_file.name, dest\_path)\
|
|
524
|
+
temp\_file = None # Prevent cleanup in finally block if successful\
|
|
525
|
+
finally:\
|
|
526
|
+
if temp\_file is not None and os.path.exists(temp\_file.name):\
|
|
527
|
+
try:\
|
|
528
|
+
os.unlink(temp\_file.name)\
|
|
529
|
+
except OSError:\
|
|
530
|
+
# Log error, but continue\
|
|
531
|
+
pass\
|
|
532
|
+
|
|
465
533
|
### Boldface, italics, and links
|
|
466
534
|
|
|
467
535
|
X [New York City](https://en.wikipedia.org/wiki/New_York_City). XX
|
|
@@ -471,6 +471,9 @@ Recommendations:
|
|
|
471
471
|
|
|
472
472
|
### **3.6 Comparative Features Matrix**
|
|
473
473
|
|
|
474
|
+
This line has a two-space line break.\
|
|
475
|
+
And this is a regular line.
|
|
476
|
+
|
|
474
477
|
The following table summarizes the native capabilities of the primary platforms
|
|
475
478
|
evaluated against the core requirements:
|
|
476
479
|
|
|
@@ -485,6 +488,71 @@ evaluated against the core requirements:
|
|
|
485
488
|
| **Programmatic Invalidation API** | Automatic / Limited | Automatic / Limited | Yes (Cloudflare API) | Yes (CloudFront API) 15-16 |
|
|
486
489
|
| **Basic Cost Model** | Per User / Usage 5 | Per User / Usage 5 | Generous Free Tier / Usage | Usage-Based Components 20-64 |
|
|
487
490
|
|
|
491
|
+
## **3\. Platform\-Specific Mechanisms**
|
|
492
|
+
|
|
493
|
+
Each major operating system provides its own set of system calls, APIs, and conventions
|
|
494
|
+
for managing temporary files and directories.
|
|
495
|
+
|
|
496
|
+
### **3\.1\. Linux**
|
|
497
|
+
|
|
498
|
+
Linux offers a rich set of POSIX\-compliant and Linux\-specific mechanisms.
|
|
499
|
+
|
|
500
|
+
* **APIs:**
|
|
501
|
+
|
|
502
|
+
+ mkstemp(3\): Generates a unique filename based on a template (prefixXXXXXX), creates
|
|
503
|
+
the file with 0600 permissions, opens it, and returns a file descriptor.
|
|
504
|
+
It uses O\_EXCL for atomic creation, preventing race conditions.<sup>19</sup> The
|
|
505
|
+
caller is responsible for unlinking the file.
|
|
506
|
+
|
|
507
|
+
+ mkdtemp(3\): Similar to mkstemp but creates a unique directory with 0700 permissions
|
|
508
|
+
based on a template (prefixXXXXXX), returning the path.<sup>19</sup> The caller must
|
|
509
|
+
remove the directory.
|
|
510
|
+
|
|
511
|
+
+ tmpfile(3\): Creates an unnamed temporary file opened in wb\+ mode (binary
|
|
512
|
+
read/write), automatically deleted when closed or on process
|
|
513
|
+
termination.<sup>19</sup> While convenient, POSIX notes potential permission issues
|
|
514
|
+
and recommends mkstemp followed by fdopen for multithreaded apps to avoid leaking
|
|
515
|
+
file descriptors.<sup>59</sup>
|
|
516
|
+
|
|
517
|
+
* **Recommended Python Approach for Atomic Writes:**
|
|
518
|
+
|
|
519
|
+
1. **Use a Library:** Employing a dedicated library like atomicwriter or atomicfile is
|
|
520
|
+
generally the most robust approach, as they handle temporary file creation in the
|
|
521
|
+
correct location, atomic renaming (os.replace), and error cleanup across platforms.
|
|
522
|
+
|
|
523
|
+
2. **Manual Implementation (if necessary):**
|
|
524
|
+
|
|
525
|
+
+ Determine the destination directory: dest\_dir = os.path.dirname(dest\_path)
|
|
526
|
+
|
|
527
|
+
+ Create the temporary file in the destination directory: temp\_file =
|
|
528
|
+
tempfile.NamedTemporaryFile(mode='w', dir=dest\_dir, delete=False) (use
|
|
529
|
+
appropriate mode, e.g., wb for binary).
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
+ Use a try...finally block to ensure cleanup:\
|
|
533
|
+
Python\
|
|
534
|
+
temp\_file = None\
|
|
535
|
+
try:\
|
|
536
|
+
temp\_file = tempfile.NamedTemporaryFile(mode='w', dir=dest\_dir, delete=False,
|
|
537
|
+
prefix='tmp', suffix='.tmp')\
|
|
538
|
+
# Write data to temp\_file.file (or temp\_file directly in older Pythons)\
|
|
539
|
+
temp\_file.write(...)\
|
|
540
|
+
# Ensure data is on disk before renaming\
|
|
541
|
+
temp\_file.flush()\
|
|
542
|
+
os.fsync(temp\_file.fileno())\
|
|
543
|
+
# Close the file handle before renaming\
|
|
544
|
+
temp\_file.close()\
|
|
545
|
+
# Atomic rename/replace\
|
|
546
|
+
os.replace(temp\_file.name, dest\_path)\
|
|
547
|
+
temp\_file = None # Prevent cleanup in finally block if successful\
|
|
548
|
+
finally:\
|
|
549
|
+
if temp\_file is not None and os.path.exists(temp\_file.name):\
|
|
550
|
+
try:\
|
|
551
|
+
os.unlink(temp\_file.name)\
|
|
552
|
+
except OSError:\
|
|
553
|
+
# Log error, but continue\
|
|
554
|
+
pass\
|
|
555
|
+
|
|
488
556
|
### Boldface, italics, and links
|
|
489
557
|
|
|
490
558
|
X [New York City](https://en.wikipedia.org/wiki/New_York_City). XX
|
|
@@ -4,14 +4,14 @@ requires-python = ">=3.10, <4.0"
|
|
|
4
4
|
|
|
5
5
|
[[package]]
|
|
6
6
|
name = "basedpyright"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.29.1"
|
|
8
8
|
source = { registry = "https://pypi.org/simple" }
|
|
9
9
|
dependencies = [
|
|
10
10
|
{ name = "nodejs-wheel-binaries" },
|
|
11
11
|
]
|
|
12
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
12
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b9/18/f5e488eac4960ad9a2e71b95f0d91cf93a982c7f68aa90e4e0554f0bc37e/basedpyright-1.29.1.tar.gz", hash = "sha256:06bbe6c3b50ab4af20f80e154049477a50d8b81d2522eadbc9f472f2f92cd44b", size = 21773469 }
|
|
13
13
|
wheels = [
|
|
14
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
14
|
+
{ url = "https://files.pythonhosted.org/packages/95/1b/1bb837bbb7e259928f33d3c105dfef4f5349ef08b3ef45576801256e3234/basedpyright-1.29.1-py3-none-any.whl", hash = "sha256:b7eb65b9d4aaeeea29a349ac494252032a75a364942d0ac466d7f07ddeacc786", size = 11397959 },
|
|
15
15
|
]
|
|
16
16
|
|
|
17
17
|
[[package]]
|
|
@@ -63,9 +63,9 @@ dev = [
|
|
|
63
63
|
|
|
64
64
|
[package.metadata]
|
|
65
65
|
requires-dist = [
|
|
66
|
-
{ name = "marko", specifier = ">=2.1.
|
|
66
|
+
{ name = "marko", specifier = ">=2.1.3" },
|
|
67
67
|
{ name = "regex", specifier = ">=2024.11.6" },
|
|
68
|
-
{ name = "strif", specifier = ">=
|
|
68
|
+
{ name = "strif", specifier = ">=3.0.1" },
|
|
69
69
|
{ name = "typing-extensions", specifier = ">=4.12.2" },
|
|
70
70
|
]
|
|
71
71
|
|
|
@@ -81,14 +81,11 @@ dev = [
|
|
|
81
81
|
|
|
82
82
|
[[package]]
|
|
83
83
|
name = "funlog"
|
|
84
|
-
version = "0.2.
|
|
84
|
+
version = "0.2.1"
|
|
85
85
|
source = { registry = "https://pypi.org/simple" }
|
|
86
|
-
|
|
87
|
-
{ name = "strif" },
|
|
88
|
-
]
|
|
89
|
-
sdist = { url = "https://files.pythonhosted.org/packages/f3/f9/0e345fbcf27983884b8a16439e25c51d70ecdbafb0645d021dc032a540da/funlog-0.2.0.tar.gz", hash = "sha256:315383e29dc866e7d1a656425f34eb12338e60de17304109b7b765b9176eb12f", size = 23147 }
|
|
86
|
+
sdist = { url = "https://files.pythonhosted.org/packages/22/46/4ab8e6d87ab6aae6f1eafdd2f891c009e95e295bec2df8cd0b2f06870417/funlog-0.2.1.tar.gz", hash = "sha256:16f89e9c499077b374b4986a7c7dfc9fcd21ef40eb757fefff1a3631fdddf16f", size = 25691 }
|
|
90
87
|
wheels = [
|
|
91
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
88
|
+
{ url = "https://files.pythonhosted.org/packages/d8/af/420b08227bd021f80008520aa20a001120e71042342801d0c782a6615a39/funlog-0.2.1-py3-none-any.whl", hash = "sha256:eed8d206c21ee8dc96137b4df51689470682d4700f6f99a1a6133a0e065f3798", size = 9399 },
|
|
92
89
|
]
|
|
93
90
|
|
|
94
91
|
[[package]]
|
|
@@ -114,11 +111,11 @@ wheels = [
|
|
|
114
111
|
|
|
115
112
|
[[package]]
|
|
116
113
|
name = "marko"
|
|
117
|
-
version = "2.1.
|
|
114
|
+
version = "2.1.3"
|
|
118
115
|
source = { registry = "https://pypi.org/simple" }
|
|
119
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
116
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f2/e7/b50627a7321668d0c30b193aa494fb59526548ff3e61d376a93ffbb692f0/marko-2.1.3.tar.gz", hash = "sha256:31aacb14867328f054cc39f884212907822a43d6a30cd75b0767e001a5e2f9fc", size = 142700 }
|
|
120
117
|
wheels = [
|
|
121
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
118
|
+
{ url = "https://files.pythonhosted.org/packages/fc/43/c82e8f528887cec56f5afa9152c921b01f98deeffaf9e1bcc3e54e90c291/marko-2.1.3-py3-none-any.whl", hash = "sha256:b4125d44b94606d6f13ddc77fef8cc4c87f70d54bc7d52d6547958b9f998a9d5", size = 42187 },
|
|
122
119
|
]
|
|
123
120
|
|
|
124
121
|
[[package]]
|
|
@@ -132,27 +129,27 @@ wheels = [
|
|
|
132
129
|
|
|
133
130
|
[[package]]
|
|
134
131
|
name = "nodejs-wheel-binaries"
|
|
135
|
-
version = "22.
|
|
132
|
+
version = "22.15.0"
|
|
136
133
|
source = { registry = "https://pypi.org/simple" }
|
|
137
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
134
|
+
sdist = { url = "https://files.pythonhosted.org/packages/45/5b/6c5f973765b96793d4e4d03684bcbd273b17e471ecc7e9bec4c32b595ebd/nodejs_wheel_binaries-22.15.0.tar.gz", hash = "sha256:ff81aa2a79db279c2266686ebcb829b6634d049a5a49fc7dc6921e4f18af9703", size = 8054 }
|
|
138
135
|
wheels = [
|
|
139
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
140
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
141
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
142
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
143
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
144
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
145
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
146
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
136
|
+
{ url = "https://files.pythonhosted.org/packages/d3/a8/a32e5bb99e95c536e7dac781cffab1e7e9f8661b8ee296b93df77e4df7f9/nodejs_wheel_binaries-22.15.0-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:aa16366d48487fff89446fb237693e777aa2ecd987208db7d4e35acc40c3e1b1", size = 50514526 },
|
|
137
|
+
{ url = "https://files.pythonhosted.org/packages/05/e8/eb024dbb3a7d3b98c8922d1c306be989befad4d2132292954cb902f43b07/nodejs_wheel_binaries-22.15.0-py2.py3-none-macosx_11_0_x86_64.whl", hash = "sha256:a54bb3fee9170003fa8abc69572d819b2b1540344eff78505fcc2129a9175596", size = 51409179 },
|
|
138
|
+
{ url = "https://files.pythonhosted.org/packages/3f/0f/baa968456c3577e45c7d0e3715258bd175dcecc67b683a41a5044d5dae40/nodejs_wheel_binaries-22.15.0-py2.py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:867121ccf99d10523f6878a26db86e162c4939690e24cfb5bea56d01ea696c93", size = 57364460 },
|
|
139
|
+
{ url = "https://files.pythonhosted.org/packages/2f/a2/977f63cd07ed8fc27bc0d0cd72e801fc3691ffc8cd40a51496ff18a6d0a2/nodejs_wheel_binaries-22.15.0-py2.py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab0fbcda2ddc8aab7db1505d72cb958f99324b3834c4543541a305e02bfe860", size = 57889101 },
|
|
140
|
+
{ url = "https://files.pythonhosted.org/packages/67/7f/57b9c24a4f0d25490527b043146aa0fdff2d8fdc82f90667cdaf6f00cfc9/nodejs_wheel_binaries-22.15.0-py2.py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2bde1d8e00cd955b9ce9ee9ac08309923e2778a790ee791b715e93e487e74bfd", size = 59190817 },
|
|
141
|
+
{ url = "https://files.pythonhosted.org/packages/fd/7f/970acbe33b81c22b3c7928f52e32347030aa46d23d779cf781cf9a9cf557/nodejs_wheel_binaries-22.15.0-py2.py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:acdd4ef73b6701aab9fbe02ac5e104f208a5e3c300402fa41ad7bc7f49499fbf", size = 60220316 },
|
|
142
|
+
{ url = "https://files.pythonhosted.org/packages/07/4c/030243c04bb60f0de66c2d7ee3be289c6d28ef09113c06ffa417bdfedf8f/nodejs_wheel_binaries-22.15.0-py2.py3-none-win_amd64.whl", hash = "sha256:51deaf13ee474e39684ce8c066dfe86240edb94e7241950ca789befbbbcbd23d", size = 40718853 },
|
|
143
|
+
{ url = "https://files.pythonhosted.org/packages/1f/49/011d472814af4fabeaab7d7ce3d5a1a635a3dadc23ae404d1f546839ecb3/nodejs_wheel_binaries-22.15.0-py2.py3-none-win_arm64.whl", hash = "sha256:01a3fe4d60477f93bf21a44219db33548c75d7fed6dc6e6f4c05cf0adf015609", size = 36436645 },
|
|
147
144
|
]
|
|
148
145
|
|
|
149
146
|
[[package]]
|
|
150
147
|
name = "packaging"
|
|
151
|
-
version = "
|
|
148
|
+
version = "25.0"
|
|
152
149
|
source = { registry = "https://pypi.org/simple" }
|
|
153
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
150
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 }
|
|
154
151
|
wheels = [
|
|
155
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
152
|
+
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
|
|
156
153
|
]
|
|
157
154
|
|
|
158
155
|
[[package]]
|
|
@@ -261,50 +258,50 @@ wheels = [
|
|
|
261
258
|
|
|
262
259
|
[[package]]
|
|
263
260
|
name = "rich"
|
|
264
|
-
version = "
|
|
261
|
+
version = "14.0.0"
|
|
265
262
|
source = { registry = "https://pypi.org/simple" }
|
|
266
263
|
dependencies = [
|
|
267
264
|
{ name = "markdown-it-py" },
|
|
268
265
|
{ name = "pygments" },
|
|
269
266
|
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
|
270
267
|
]
|
|
271
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
268
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 }
|
|
272
269
|
wheels = [
|
|
273
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
270
|
+
{ url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 },
|
|
274
271
|
]
|
|
275
272
|
|
|
276
273
|
[[package]]
|
|
277
274
|
name = "ruff"
|
|
278
|
-
version = "0.11.
|
|
275
|
+
version = "0.11.9"
|
|
279
276
|
source = { registry = "https://pypi.org/simple" }
|
|
280
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
277
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f5/e7/e55dda1c92cdcf34b677ebef17486669800de01e887b7831a1b8fdf5cb08/ruff-0.11.9.tar.gz", hash = "sha256:ebd58d4f67a00afb3a30bf7d383e52d0e036e6195143c6db7019604a05335517", size = 4132134 }
|
|
281
278
|
wheels = [
|
|
282
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
283
|
-
{ url = "https://files.pythonhosted.org/packages/74/
|
|
284
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
285
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
286
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
287
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
288
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
289
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
290
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
291
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
292
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
293
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
294
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
295
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
296
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
297
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
298
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
279
|
+
{ url = "https://files.pythonhosted.org/packages/fb/71/75dfb7194fe6502708e547941d41162574d1f579c4676a8eb645bf1a6842/ruff-0.11.9-py3-none-linux_armv6l.whl", hash = "sha256:a31a1d143a5e6f499d1fb480f8e1e780b4dfdd580f86e05e87b835d22c5c6f8c", size = 10335453 },
|
|
280
|
+
{ url = "https://files.pythonhosted.org/packages/74/fc/ad80c869b1732f53c4232bbf341f33c5075b2c0fb3e488983eb55964076a/ruff-0.11.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:66bc18ca783b97186a1f3100e91e492615767ae0a3be584e1266aa9051990722", size = 11072566 },
|
|
281
|
+
{ url = "https://files.pythonhosted.org/packages/87/0d/0ccececef8a0671dae155cbf7a1f90ea2dd1dba61405da60228bbe731d35/ruff-0.11.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:bd576cd06962825de8aece49f28707662ada6a1ff2db848d1348e12c580acbf1", size = 10435020 },
|
|
282
|
+
{ url = "https://files.pythonhosted.org/packages/52/01/e249e1da6ad722278094e183cbf22379a9bbe5f21a3e46cef24ccab76e22/ruff-0.11.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b1d18b4be8182cc6fddf859ce432cc9631556e9f371ada52f3eaefc10d878de", size = 10593935 },
|
|
283
|
+
{ url = "https://files.pythonhosted.org/packages/ed/9a/40cf91f61e3003fe7bd43f1761882740e954506c5a0f9097b1cff861f04c/ruff-0.11.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0f3f46f759ac623e94824b1e5a687a0df5cd7f5b00718ff9c24f0a894a683be7", size = 10172971 },
|
|
284
|
+
{ url = "https://files.pythonhosted.org/packages/61/12/d395203de1e8717d7a2071b5a340422726d4736f44daf2290aad1085075f/ruff-0.11.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f34847eea11932d97b521450cf3e1d17863cfa5a94f21a056b93fb86f3f3dba2", size = 11748631 },
|
|
285
|
+
{ url = "https://files.pythonhosted.org/packages/66/d6/ef4d5eba77677eab511644c37c55a3bb8dcac1cdeb331123fe342c9a16c9/ruff-0.11.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f33b15e00435773df97cddcd263578aa83af996b913721d86f47f4e0ee0ff271", size = 12409236 },
|
|
286
|
+
{ url = "https://files.pythonhosted.org/packages/c5/8f/5a2c5fc6124dd925a5faf90e1089ee9036462118b619068e5b65f8ea03df/ruff-0.11.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b27613a683b086f2aca8996f63cb3dd7bc49e6eccf590563221f7b43ded3f65", size = 11881436 },
|
|
287
|
+
{ url = "https://files.pythonhosted.org/packages/39/d1/9683f469ae0b99b95ef99a56cfe8c8373c14eba26bd5c622150959ce9f64/ruff-0.11.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e0d88756e63e8302e630cee3ce2ffb77859797cc84a830a24473939e6da3ca6", size = 13982759 },
|
|
288
|
+
{ url = "https://files.pythonhosted.org/packages/4e/0b/c53a664f06e0faab596397867c6320c3816df479e888fe3af63bc3f89699/ruff-0.11.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:537c82c9829d7811e3aa680205f94c81a2958a122ac391c0eb60336ace741a70", size = 11541985 },
|
|
289
|
+
{ url = "https://files.pythonhosted.org/packages/23/a0/156c4d7e685f6526a636a60986ee4a3c09c8c4e2a49b9a08c9913f46c139/ruff-0.11.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:440ac6a7029f3dee7d46ab7de6f54b19e34c2b090bb4f2480d0a2d635228f381", size = 10465775 },
|
|
290
|
+
{ url = "https://files.pythonhosted.org/packages/43/d5/88b9a6534d9d4952c355e38eabc343df812f168a2c811dbce7d681aeb404/ruff-0.11.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:71c539bac63d0788a30227ed4d43b81353c89437d355fdc52e0cda4ce5651787", size = 10170957 },
|
|
291
|
+
{ url = "https://files.pythonhosted.org/packages/f0/b8/2bd533bdaf469dc84b45815ab806784d561fab104d993a54e1852596d581/ruff-0.11.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c67117bc82457e4501473c5f5217d49d9222a360794bfb63968e09e70f340abd", size = 11143307 },
|
|
292
|
+
{ url = "https://files.pythonhosted.org/packages/2f/d9/43cfba291788459b9bfd4e09a0479aa94d05ab5021d381a502d61a807ec1/ruff-0.11.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e4b78454f97aa454586e8a5557facb40d683e74246c97372af3c2d76901d697b", size = 11603026 },
|
|
293
|
+
{ url = "https://files.pythonhosted.org/packages/22/e6/7ed70048e89b01d728ccc950557a17ecf8df4127b08a56944b9d0bae61bc/ruff-0.11.9-py3-none-win32.whl", hash = "sha256:7fe1bc950e7d7b42caaee2a8a3bc27410547cc032c9558ee2e0f6d3b209e845a", size = 10548627 },
|
|
294
|
+
{ url = "https://files.pythonhosted.org/packages/90/36/1da5d566271682ed10f436f732e5f75f926c17255c9c75cefb77d4bf8f10/ruff-0.11.9-py3-none-win_amd64.whl", hash = "sha256:52edaa4a6d70f8180343a5b7f030c7edd36ad180c9f4d224959c2d689962d964", size = 11634340 },
|
|
295
|
+
{ url = "https://files.pythonhosted.org/packages/40/f7/70aad26e5877c8f7ee5b161c4c9fa0100e63fc4c944dc6d97b9c7e871417/ruff-0.11.9-py3-none-win_arm64.whl", hash = "sha256:bcf42689c22f2e240f496d0c183ef2c6f7b35e809f12c1db58f75d9aa8d630ca", size = 10741080 },
|
|
299
296
|
]
|
|
300
297
|
|
|
301
298
|
[[package]]
|
|
302
299
|
name = "strif"
|
|
303
|
-
version = "
|
|
300
|
+
version = "3.0.1"
|
|
304
301
|
source = { registry = "https://pypi.org/simple" }
|
|
305
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
302
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5c/63/bf90e5632aea866c1bbd2567f04a7026191728c648ecf44ff489829898a3/strif-3.0.1.tar.gz", hash = "sha256:a312ea9540062ccd386132ac2eece93c0c4adaeb25a5f5da11eed7867b1ff31e", size = 33981 }
|
|
306
303
|
wheels = [
|
|
307
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
304
|
+
{ url = "https://files.pythonhosted.org/packages/a4/fd/94ebfa518f46b473d238a8ad5837ce75cfe4ea0ce5a91544a88198d8a8d3/strif-3.0.1-py3-none-any.whl", hash = "sha256:5dca44af3ff2f8a947a0398b619d138014f4d29a5aaf6c7e73fe7bec5f5973da", size = 18446 },
|
|
308
305
|
]
|
|
309
306
|
|
|
310
307
|
[[package]]
|
|
@@ -348,9 +345,9 @@ wheels = [
|
|
|
348
345
|
|
|
349
346
|
[[package]]
|
|
350
347
|
name = "typing-extensions"
|
|
351
|
-
version = "4.
|
|
348
|
+
version = "4.13.2"
|
|
352
349
|
source = { registry = "https://pypi.org/simple" }
|
|
353
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
350
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
|
|
354
351
|
wheels = [
|
|
355
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
352
|
+
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
|
|
356
353
|
]
|
|
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
|