Avoid ResourceWarning: unclosed file (#395)

A number of such warnings showed up when running e.g.
PYTHONWARNINGS=always python build_examples.py
PYTHONWARNINGS=always wireviz ../../examples/demo0?.yml
See https://github.com/wireviz/WireViz/pull/309#issuecomment-2170988381

Fix: All open() calls should be in a "with open() as x" statement
to ensure closing the file when exiting the block in any way.
Otherwise, use the new file_read_text() or file_write_text() functions
to read or write the whole utf-8 text file and closing it.

Co-authored-by: kvid <kvid@users.noreply.github.com>
This commit is contained in:
Daniel Rojas 2025-03-01 19:28:42 +01:00
parent 38ca3e2689
commit 9fbce3bbf7
5 changed files with 26 additions and 12 deletions

View File

@ -16,9 +16,9 @@ from wireviz.wv_dataclasses import AUTOGENERATED_PREFIX, Metadata, Options, Twea
from wireviz.wv_harness import Harness from wireviz.wv_harness import Harness
from wireviz.wv_utils import ( from wireviz.wv_utils import (
expand, expand,
file_read_text,
get_single_key_and_value, get_single_key_and_value,
is_arrow, is_arrow,
open_file_read,
smart_file_resolve, smart_file_resolve,
) )
@ -419,7 +419,7 @@ def _get_yaml_data_and_path(inp: Union[str, Path, Dict]) -> Tuple[Dict, Path]:
try: try:
yaml_path = Path(inp).expanduser().resolve(strict=True) yaml_path = Path(inp).expanduser().resolve(strict=True)
# if no FileNotFoundError exception happens, get file contents # if no FileNotFoundError exception happens, get file contents
yaml_str = open_file_read(yaml_path).read() yaml_str = file_read_text(yaml_path)
except (FileNotFoundError, OSError, ValueError) as e: except (FileNotFoundError, OSError, ValueError) as e:
# if inp is a long YAML string, Pathlib will normally raise # if inp is a long YAML string, Pathlib will normally raise
# FileNotFoundError or OSError(errno = ENAMETOOLONG) when # FileNotFoundError or OSError(errno = ENAMETOOLONG) when

View File

@ -11,7 +11,7 @@ if __name__ == "__main__":
import wireviz.wireviz as wv import wireviz.wireviz as wv
from wireviz import APP_NAME, __version__ from wireviz import APP_NAME, __version__
from wireviz.wv_utils import open_file_read from wireviz.wv_utils import file_read_text
format_codes = { format_codes = {
# "c": "csv", # TODO: support CSV # "c": "csv", # TODO: support CSV
@ -119,7 +119,7 @@ def wireviz(file, format, prepend, output_dir, output_name, version):
raise Exception(f"Path is not a file:\n{prepend_file}") raise Exception(f"Path is not a file:\n{prepend_file}")
print("Prepend file:", prepend_file) print("Prepend file:", prepend_file)
prepend_input += open_file_read(prepend_file).read() + "\n" prepend_input += file_read_text(prepend_file) + "\n"
else: else:
prepend_input = "" prepend_input = ""
@ -140,7 +140,7 @@ def wireviz(file, format, prepend, output_dir, output_name, version):
"Output file: ", f"{Path(_output_dir / _output_name)}.{output_formats_str}" "Output file: ", f"{Path(_output_dir / _output_name)}.{output_formats_str}"
) )
yaml_input = open_file_read(file).read() yaml_input = file_read_text(file)
file_dir = file.parent file_dir = file.parent
yaml_input = prepend_input + yaml_input yaml_input = prepend_input + yaml_input

View File

@ -40,7 +40,7 @@ from wireviz.wv_output import (
embed_svg_images_file, embed_svg_images_file,
generate_html_output, generate_html_output,
) )
from wireviz.wv_utils import OLD_CONNECTOR_ATTR, bom2tsv, check_old, open_file_write from wireviz.wv_utils import OLD_CONNECTOR_ATTR, bom2tsv, check_old, file_write_text
@dataclass @dataclass
@ -424,7 +424,8 @@ class Harness:
# bomlist = [[]] # bomlist = [[]]
if "tsv" in fmt: if "tsv" in fmt:
tsv = bom2tsv(bomlist) tsv = bom2tsv(bomlist)
open_file_write(f"{filename}.tsv").write(tsv) file_write_text(f"{filename}.bom.tsv", tsv)
if "csv" in fmt: if "csv" in fmt:
# TODO: implement CSV output (preferrably using CSV library) # TODO: implement CSV output (preferrably using CSV library)
print("CSV output is not yet supported") print("CSV output is not yet supported")

View File

@ -9,9 +9,9 @@ import wireviz # for doing wireviz.__file__
from wireviz import APP_NAME, APP_URL, __version__ from wireviz import APP_NAME, APP_URL, __version__
from wireviz.wv_dataclasses import Metadata, Options from wireviz.wv_dataclasses import Metadata, Options
from wireviz.wv_utils import ( from wireviz.wv_utils import (
file_read_text,
file_write_text,
html_line_breaks, html_line_breaks,
open_file_read,
open_file_write,
smart_file_resolve, smart_file_resolve,
) )
@ -95,14 +95,14 @@ def generate_html_output(
# fall back to built-in simple template if no template was provided # fall back to built-in simple template if no template was provided
templatefile = Path(wireviz.__file__).parent / "templates/simple.html" templatefile = Path(wireviz.__file__).parent / "templates/simple.html"
html = open_file_read(templatefile).read() html = file_read_text(templatefile)
# embed SVG diagram (only if used) # embed SVG diagram (only if used)
def svgdata() -> str: def svgdata() -> str:
return re.sub( return re.sub(
"^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>", "^<[?]xml [^?>]*[?]>[^<]*<!DOCTYPE [^>]*>",
"<!-- XML and DOCTYPE declarations from SVG file removed -->", "<!-- XML and DOCTYPE declarations from SVG file removed -->",
open_file_read(f"{filename}.tmp.svg").read(), file_read_text(f"{filename}.tmp.svg"),
1, 1,
) )
@ -186,4 +186,4 @@ def generate_html_output(
pattern = re.compile("|".join(replacements_escaped)) pattern = re.compile("|".join(replacements_escaped))
html = pattern.sub(lambda match: replacements[match.group(0)], html) html = pattern.sub(lambda match: replacements[match.group(0)], html)
open_file_write(f"{filename}.html").write(html) file_write_text(f"{filename}.html", html)

View File

@ -151,18 +151,31 @@ def clean_whitespace(inp):
def open_file_read(filename): def open_file_read(filename):
"""Open utf-8 encoded text file for reading - remember closing it when finished"""
# TODO: Intelligently determine encoding # TODO: Intelligently determine encoding
return open(filename, "r", encoding="UTF-8") return open(filename, "r", encoding="UTF-8")
def open_file_write(filename): def open_file_write(filename):
"""Open utf-8 encoded text file for writing - remember closing it when finished"""
return open(filename, "w", encoding="UTF-8") return open(filename, "w", encoding="UTF-8")
def open_file_append(filename): def open_file_append(filename):
"""Open utf-8 encoded text file for appending - remember closing it when finished"""
return open(filename, "a", encoding="UTF-8") return open(filename, "a", encoding="UTF-8")
def file_read_text(filename: str) -> str:
"""Read utf-8 encoded text file, close it, and return the text"""
return Path(filename).read_text(encoding="utf-8")
def file_write_text(filename: str, text: str) -> int:
"""Write utf-8 encoded text file, close it, and return the number of characters written"""
return Path(filename).write_text(text, encoding="utf-8")
def is_arrow(inp): def is_arrow(inp):
""" """
Matches strings of one or multiple `-` or `=` (but not mixed) Matches strings of one or multiple `-` or `=` (but not mixed)