Skip to content

Commit b871f30

Browse files
authored
Replace open+read/write with pathlib's read/write_text/bytes (#3263)
1 parent 3f51582 commit b871f30

File tree

12 files changed

+113
-102
lines changed

12 files changed

+113
-102
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ builtins-ignorelist = ["help", "format", "input", "filter", "copyright", "max"]
413413
"pathlib.PosixPath".msg = "Use tmt._compat.pathlib.Path instead."
414414
"warnings.deprecated".msg = "Use tmt._compat.warnings.deprecated instead."
415415
"os.path".msg = "Use tmt._compat.pathlib.Path and pathlib instead."
416+
# Banning builtins is not yet supported: https://github.com/astral-sh/ruff/issues/10079
417+
# "builtins.open".msg = "Use Path.{write_text,append_text,read_text,write_bytes,read_bytes} instead."
416418

417419
[tool.ruff.lint.isort]
418420
known-first-party = ["tmt"]

tests/unit/test_export_to_nitrate.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_export_to_nitrate_step(self):
2222
file_name = 'test.md'
2323
assert file_name in files
2424

25-
step = convert_manual_to_nitrate(file_name)[0]
25+
step = convert_manual_to_nitrate(Path(file_name))[0]
2626
html_generated = """<b>Test</b>\
2727
<p>Step 1.</p><p>Verify tmt shows help page
2828
<code>bash
@@ -46,7 +46,7 @@ def test_export_to_nitrate_expect(self):
4646
file_name = 'test.md'
4747
assert file_name in files
4848

49-
expect = convert_manual_to_nitrate(file_name)[1]
49+
expect = convert_manual_to_nitrate(Path(file_name))[1]
5050
html_generated = """<b>Test</b>\
5151
<p>Step 1.</p><p>Text similar to the one below is displayed
5252
```
@@ -78,7 +78,7 @@ def test_export_to_nitrate_empty_file(self):
7878
files = os.listdir()
7979
file_name = 'test_empty.md'
8080
assert file_name in files
81-
html = convert_manual_to_nitrate(file_name)
81+
html = convert_manual_to_nitrate(Path(file_name))
8282
html_generated = ('', '', '', '')
8383
assert html == html_generated
8484

@@ -87,7 +87,7 @@ def test_export_to_nitrate_setup_doesnt_exist(self):
8787
files = os.listdir()
8888
file_name = 'test.md'
8989
assert file_name in files
90-
cleanup = convert_manual_to_nitrate(file_name)[2]
90+
cleanup = convert_manual_to_nitrate(Path(file_name))[2]
9191
html_generated = ''
9292
assert cleanup == html_generated
9393

@@ -97,7 +97,7 @@ def test_export_to_nitrate_cleanup_latest_heading(self):
9797
file_name = 'test.md'
9898
assert file_name in files
9999

100-
cleanup = convert_manual_to_nitrate(file_name)[3]
100+
cleanup = convert_manual_to_nitrate(Path(file_name))[3]
101101
html_generated = """<p>Optionally remove temporary directory created \
102102
in the first step
103103
2 line of cleanup

tmt/_compat/pathlib.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import os
22
import pathlib
3-
from typing import Union
3+
from collections.abc import Iterator
4+
from typing import Optional, Union
45

56

67
class Path(pathlib.PosixPath):
@@ -43,3 +44,25 @@ def unrooted(self) -> "Path":
4344
return self.relative_to("/")
4445

4546
return self
47+
48+
def append_text(
49+
self,
50+
data: str,
51+
encoding: Optional[str] = None,
52+
errors: Optional[str] = None,
53+
newline: Optional[str] = None) -> int:
54+
""" Open the file pointed to in text mode, append data to it, and close the file """
55+
56+
with self.open('a', encoding=encoding, errors=errors, newline=newline) as f:
57+
return f.write(data)
58+
59+
def splitlines(
60+
self,
61+
encoding: Optional[str] = None,
62+
errors: Optional[str] = None,
63+
keepends: bool = False) -> Iterator[str]:
64+
""" Yield decoded lines of the pointed-to file as a sequence of strings """
65+
66+
yield from self \
67+
.read_text(encoding=encoding, errors=errors) \
68+
.splitlines(keepends=keepends)

tmt/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4326,8 +4326,8 @@ def resolve_dynamic_ref(
43264326

43274327
# Read it, process it and get the value of the attribute 'ref'
43284328
try:
4329-
with open(ref_filepath, encoding='utf-8') as datafile:
4330-
data = tmt.utils.yaml_to_dict(datafile.read())
4329+
data = tmt.utils.yaml_to_dict(ref_filepath.read_text(encoding='utf-8'))
4330+
43314331
except OSError as error:
43324332
raise tmt.utils.FileError(f"Failed to read '{ref_filepath}'.") from error
43334333
# Build a dynamic reference tree, adjust ref based on the context

tmt/checks/avc.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,7 @@ def _save_report(
6161
*report
6262
]
6363

64-
mode = 'a' if append else 'w'
65-
66-
invocation.phase.write(report_filepath, '\n'.join(report), mode=mode)
64+
invocation.phase.write(report_filepath, '\n'.join(report), mode='a' if append else 'w')
6765

6866
return report_filepath
6967

tmt/cli.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,10 +2196,9 @@ def setup_completion(shell: str, install: bool, context: Context) -> None:
21962196
Path(script).write_text(completions)
21972197
# If requested, modify .bashrc or .zshrc
21982198
if shell != 'fish':
2199-
config_path = Path(f'~/.{shell}rc').expanduser()
2200-
with open(config_path, 'a') as shell_config:
2201-
shell_config.write('\n# Generated by tmt\n')
2202-
shell_config.write(f'source {script}')
2199+
shell_config = Path(f'~/.{shell}rc').expanduser()
2200+
shell_config.append_text('\n# Generated by tmt\n')
2201+
shell_config.append_text(f'source {script}')
22032202

22042203
else:
22052204
logger.info(completions)

tmt/convert.py

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,8 @@ def write_markdown(path: Path, content: dict[str, str]) -> None:
157157
to_print += "# Cleanup\n" + content['cleanup'] + '\n'
158158

159159
try:
160-
with open(path, 'w', encoding='utf-8') as md_file:
161-
md_file.write(to_print)
162-
echo(style(
163-
f"Test case successfully stored into '{path}'.", fg='magenta'))
160+
path.write_text(to_print, encoding='utf-8')
161+
echo(style(f"Test case successfully stored into '{path}'.", fg='magenta'))
164162
except OSError:
165163
raise ConvertError(f"Unable to write '{path}'.")
166164

@@ -269,22 +267,21 @@ def read_datafile(
269267
# As 'make' command was specified for test, ensure Makefile present.
270268
makefile_path = path / 'Makefile'
271269
try:
272-
with open(makefile_path, encoding='utf-8') as makefile_file:
273-
makefile = makefile_file.read()
274-
search_result = \
275-
re.search(makefile_regex_test, makefile, re.MULTILINE)
270+
search_result = re.search(
271+
makefile_regex_test,
272+
makefile_path.read_text(encoding='utf-8'),
273+
re.MULTILINE)
276274
except OSError:
277275
raise ConvertError("Makefile is missing.")
278276
# Retrieve the path to the test file from the Makefile
279277
if search_result is not None:
280278
test_path = path / search_result.group(1).split()[-1]
281279
# Read the test file and determine the framework used.
282280
if test_path:
283-
with open(test_path, encoding="utf-8") as test_file:
284-
if re.search("beakerlib", test_file.read(), re.MULTILINE):
285-
data["framework"] = "beakerlib"
286-
else:
287-
data["framework"] = "shell"
281+
if re.search("beakerlib", test_path.read_text(encoding="utf-8"), re.MULTILINE):
282+
data["framework"] = "beakerlib"
283+
else:
284+
data["framework"] = "shell"
288285
else:
289286
data["framework"] = "shell"
290287
echo(style("framework: ", fg="green") + data["framework"])
@@ -452,8 +449,7 @@ def read(
452449
assert filename is not None # type check
453450
datafile_path = path / filename
454451
try:
455-
with open(datafile_path, encoding='utf-8') as datafile_file:
456-
datafile = datafile_file.read()
452+
datafile = datafile_path.read_text(encoding='utf-8')
457453
except OSError:
458454
raise ConvertError(f"Unable to open '{datafile_path}'.")
459455
echo(f"found in '{datafile_path}'.")
@@ -462,8 +458,7 @@ def read(
462458
testinfo_path = path / 'testinfo.desc'
463459
if testinfo_path.is_file():
464460
try:
465-
with open(testinfo_path, encoding='utf-8') as testinfo_file:
466-
old_testinfo = testinfo_file.read()
461+
old_testinfo = testinfo_path.read_text(encoding='utf-8')
467462
testinfo_path.unlink()
468463
except OSError:
469464
raise ConvertError(
@@ -495,8 +490,7 @@ def read(
495490

496491
# Read testinfo.desc
497492
try:
498-
with open(testinfo_path, encoding='utf-8') as testinfo_file:
499-
testinfo = testinfo_file.read()
493+
testinfo = testinfo_path.read_text(encoding='utf-8')
500494
except OSError:
501495
raise ConvertError(f"Unable to open '{testinfo_path}'.")
502496

@@ -561,8 +555,8 @@ def target_content_build() -> list[str]:
561555
# Restore the original testinfo.desc content (if existed)
562556
if old_testinfo:
563557
try:
564-
with open(testinfo_path, 'w', encoding='utf-8') as testinfo_file:
565-
testinfo_file.write(old_testinfo)
558+
testinfo_path.write_text(old_testinfo, encoding='utf-8')
559+
566560
except OSError:
567561
raise ConvertError(
568562
f"Unable to write '{testinfo_path}'.")
@@ -578,8 +572,7 @@ def target_content_build() -> list[str]:
578572
echo(style('Purpose ', fg='blue'), nl=False)
579573
purpose_path = path / 'PURPOSE'
580574
try:
581-
with open(purpose_path, encoding='utf-8') as purpose_file:
582-
content = purpose_file.read()
575+
content = purpose_path.read_text(encoding='utf-8')
583576
echo(f"found in '{purpose_path}'.")
584577
for header in ['PURPOSE', 'Description', 'Author']:
585578
content = re.sub(f'^{header}.*\n', '', content)
@@ -1171,8 +1164,8 @@ def write(path: Path, data: NitrateDataType, quiet: bool = False) -> None:
11711164

11721165
# Store metadata into a fmf file
11731166
try:
1174-
with open(path, 'w', encoding='utf-8') as fmf_file:
1175-
fmf_file.write(tmt.utils.dict_to_yaml(sorted_data))
1167+
path.write_text(tmt.utils.dict_to_yaml(sorted_data), encoding='utf-8')
1168+
11761169
except OSError:
11771170
raise ConvertError(f"Unable to write '{path}'")
11781171
if not quiet:

tmt/export/nitrate.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,10 @@ def get_category(path: Path) -> str:
285285
""" Get category from Makefile """
286286
category = DEFAULT_NITRATE_CATEGORY
287287
try:
288-
with open(path / 'Makefile', encoding='utf-8') as makefile_file:
289-
makefile = makefile_file.read()
290288
category_search = re.search(
291-
r'echo\s+"Type:\s*(.*)"', makefile, re.MULTILINE)
289+
r'echo\s+"Type:\s*(.*)"',
290+
(path / 'Makefile').read_text(encoding='utf-8'),
291+
re.MULTILINE)
292292
if category_search:
293293
category = category_search.group(1)
294294
# Default to 'Sanity' if Makefile or Type not found

tmt/steps/execute/__init__.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,7 @@ def handle_reboot(self) -> bool:
371371

372372
elif self.soft_reboot_requested:
373373
# Extract custom hints from the file, and reset it.
374-
with open(self.reboot_request_path) as reboot_file:
375-
reboot_data = json.loads(reboot_file.read())
374+
reboot_data = json.loads(self.reboot_request_path.read_text())
376375

377376
if reboot_data.get('command'):
378377
with suppress(TypeError):
@@ -629,12 +628,10 @@ def _load_custom_results_file(self, invocation: TestInvocation) -> ResultCollect
629628
filepaths=[custom_results_path_yaml, custom_results_path_json])
630629

631630
if custom_results_path_yaml.exists():
632-
with open(custom_results_path_yaml) as results_file:
633-
collection.results = tmt.utils.yaml_to_list(results_file)
631+
collection.results = tmt.utils.yaml_to_list(custom_results_path_yaml.read_text())
634632

635633
elif custom_results_path_json.exists():
636-
with open(custom_results_path_json) as results_file:
637-
collection.results = tmt.utils.json_to_list(results_file)
634+
collection.results = tmt.utils.json_to_list(custom_results_path_json.read_text())
638635

639636
else:
640637
return collection

tmt/steps/provision/testcloud.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -764,8 +764,7 @@ def prepare_ssh_key(self, key_type: Optional[str] = None) -> None:
764764
command += Command("-t", key_type)
765765
self._run_guest_command(command, silent=True)
766766
self.verbose('key', self.key[0], 'green')
767-
with open(self.workdir / f'{key_name}.pub') as pubkey_file:
768-
public_key = pubkey_file.read()
767+
public_key = (self.workdir / f'{key_name}.pub').read_text()
769768

770769
# Place public key content into the machine configuration
771770
self.config.USER_DATA = Template(USER_DATA).safe_substitute(

0 commit comments

Comments
 (0)