Skip to content

Commit 74c7f6a

Browse files
authored
python: Use context manager for subprocesses and opening files (OSGeo#4908)
* python: Use context manager for subprocesses and opening files * style: Fix new write-whole-file (FURB103) errors * style: Fix new read-whole-file (FURB101) errors * checks: Remove fixed Ruff SIM115 exclusions * grass.imaging.images2avi: Remove shell=True from subprocess.Popen
1 parent ca25eb3 commit 74c7f6a

File tree

12 files changed

+223
-245
lines changed

12 files changed

+223
-245
lines changed

pyproject.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,6 @@ ignore = [
324324
"python/grass/gunittest/multireport.py" = ["PYI024"]
325325
"python/grass/gunittest/testsu*/d*/s*/s*/subsub*/t*/test_segfaut.py" = ["B018"]
326326
"python/grass/gunittest/testsuite/test_assertions_rast3d.py" = ["FLY002"]
327-
"python/grass/imaging/images2*.py" = ["SIM115"]
328327
"python/grass/imaging/images2ims.py" = ["PTH208"]
329328
"python/grass/jupyter/testsuite/interactivemap_test.py" = ["PGH004"]
330329
"python/grass/jupyter/testsuite/map_test.py" = ["PGH004"]
@@ -343,9 +342,7 @@ ignore = [
343342
"python/grass/pygrass/vector/testsuite/test_table.py" = ["PLW0108"]
344343
"python/grass/script/array.py" = ["A005"]
345344
"python/grass/script/core.py" = ["PTH208"]
346-
"python/grass/script/db.py" = ["SIM115"]
347-
"python/grass/script/raster.py" = ["SIM115"]
348-
"python/grass/script/utils.py" = ["FURB189", "SIM115"]
345+
"python/grass/script/utils.py" = ["FURB189"]
349346
"python/grass/temporal/aggregation.py" = ["SIM115"]
350347
"python/grass/temporal/register.py" = ["SIM115"]
351348
"python/grass/temporal/stds_export.py" = ["SIM115"]

python/grass/gunittest/multirunner.py

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,16 @@ def main():
111111
# we assume that the start script is available and in the PATH
112112
# the shell=True is here because of MS Windows? (code taken from wiki)
113113
startcmd = grass_executable + " --config path"
114-
p = subprocess.Popen(
114+
with subprocess.Popen(
115115
startcmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
116-
)
117-
out, err = p.communicate()
118-
if p.returncode != 0:
119-
print(
120-
"ERROR: Cannot find GRASS GIS start script (%s):\n%s" % (startcmd, err),
121-
file=sys.stderr,
122-
)
123-
return 1
116+
) as p:
117+
out, err = p.communicate()
118+
if p.returncode != 0:
119+
print(
120+
"ERROR: Cannot find GRASS GIS start script (%s):\n%s" % (startcmd, err),
121+
file=sys.stderr,
122+
)
123+
return 1
124124
gisbase = decode(out.strip())
125125

126126
# set GISBASE environment variable
@@ -151,7 +151,7 @@ def main():
151151
# including also type to make it unique and preserve it for sure
152152
report = "report_for_" + location + "_" + location_type
153153
absreport = os.path.abspath(report)
154-
p = subprocess.Popen(
154+
with subprocess.Popen(
155155
[
156156
sys.executable,
157157
"-tt",
@@ -167,23 +167,24 @@ def main():
167167
absreport,
168168
],
169169
cwd=grasssrc,
170-
)
171-
returncode = p.wait()
172-
reports.append(report)
170+
) as p2:
171+
returncode = p2.wait()
172+
reports.append(report)
173173

174174
if main_report:
175175
# TODO: solve the path to source code (work now only for grass source code)
176176
arguments = [
177177
sys.executable,
178-
grasssrc + "/python/grass/gunittest/" + "multireport.py",
179-
"--timestapms",
178+
grasssrc + "/python/grass/gunittest/multireport.py",
179+
"--timestamps",
180+
*reports,
180181
]
181-
arguments.extend(reports)
182-
p = subprocess.Popen(arguments)
183-
returncode = p.wait()
184-
if returncode != 0:
185-
print("ERROR: Creation of main report failed.", file=sys.stderr)
186-
return 1
182+
183+
with subprocess.Popen(arguments) as p3:
184+
returncode = p3.wait()
185+
if returncode != 0:
186+
print("ERROR: Creation of main report failed.", file=sys.stderr)
187+
return 1
187188

188189
return 0
189190

python/grass/gunittest/reporters.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,19 +160,19 @@ def get_svn_revision():
160160
"""
161161
# TODO: here should be starting directory
162162
# but now we are using current as starting
163-
p = subprocess.Popen(
163+
with subprocess.Popen(
164164
["svnversion", "."], stdout=subprocess.PIPE, stderr=subprocess.PIPE
165-
)
166-
stdout, stderr = p.communicate()
167-
rc = p.poll()
168-
if not rc:
165+
) as p:
166+
stdout, stderr = p.communicate()
167+
rc = p.poll()
168+
if rc:
169+
return None
169170
stdout = stdout.strip()
170171
stdout = stdout.removesuffix("M")
171172
if ":" in stdout:
172173
# the first one is the one of source code
173174
stdout = stdout.split(":")[0]
174175
return stdout
175-
return None
176176

177177

178178
def get_svn_info():

python/grass/imaging/images2avi.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -180,22 +180,22 @@ def readAvi(filename, asNumpy=True):
180180

181181
# Run ffmpeg
182182
command = "ffmpeg -i input.avi im%d.jpg"
183-
S = subprocess.Popen(
184-
command, shell=True, cwd=tempDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE
185-
)
186-
187-
# Show what mencodec has to say
188-
outPut = S.stdout.read()
189-
190-
if S.wait():
191-
# An error occurred, show
192-
print(outPut)
193-
print(S.stderr.read())
194-
# Clean up
195-
_cleanDir(tempDir)
196-
msg = "Could not read avi."
197-
raise RuntimeError(msg)
198-
183+
with subprocess.Popen(
184+
command,
185+
cwd=tempDir,
186+
stdout=subprocess.PIPE,
187+
stderr=subprocess.PIPE,
188+
) as S:
189+
# Show what mencodec has to say
190+
outPut = S.stdout.read()
191+
if S.wait():
192+
# An error occurred, show
193+
print(outPut)
194+
print(S.stderr.read())
195+
# Clean up
196+
_cleanDir(tempDir)
197+
msg = "Could not read avi."
198+
raise RuntimeError(msg)
199199
# Read images
200200
images = images2ims.readIms(os.path.join(tempDir, "im*.jpg"), asNumpy)
201201
# Clean up

python/grass/imaging/images2gif.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,8 @@ def writeGifVisvis(
612612
images = gifWriter.convertImagesToPIL(images, dither, nq)
613613

614614
# Write
615-
fp = open(filename, "wb")
616-
try:
615+
with open(filename, "wb") as fp:
617616
gifWriter.writeGifToFile(fp, images, duration, loops, xy, dispose)
618-
finally:
619-
fp.close()
620617

621618

622619
def readGif(filename, asNumpy=True):

python/grass/imaging/images2swf.py

Lines changed: 53 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969

7070
import os
7171
import zlib
72+
from pathlib import Path
7273

7374
try:
7475
import numpy as np
@@ -838,11 +839,8 @@ def writeSwf(filename, images, duration=0.1, repeat=True):
838839
taglist.append(DoActionTag("stop"))
839840

840841
# Build file
841-
fp = open(filename, "wb")
842-
try:
842+
with open(filename, "wb") as fp:
843843
buildFile(fp, taglist, nframes=nframes, framesize=wh, fps=fps)
844-
finally:
845-
fp.close()
846844

847845

848846
def _readPixels(bb, i, tagType, L1):
@@ -923,67 +921,60 @@ def readSwf(filename, asNumpy=True):
923921
images = []
924922

925923
# Open file and read all
926-
fp = open(filename, "rb")
927-
bb = fp.read()
928-
929-
try:
930-
# Check opening tag
931-
tmp = bb[0:3].decode("ascii", "ignore")
932-
if tmp.upper() == "FWS":
933-
pass # ok
934-
elif tmp.upper() == "CWS":
935-
# Decompress movie
936-
bb = bb[:8] + zlib.decompress(bb[8:])
924+
bb = Path(filename).read_bytes()
925+
# Check opening tag
926+
tmp = bb[0:3].decode("ascii", "ignore")
927+
if tmp.upper() == "FWS":
928+
pass # ok
929+
elif tmp.upper() == "CWS":
930+
# Decompress movie
931+
bb = bb[:8] + zlib.decompress(bb[8:])
932+
else:
933+
raise OSError("Not a valid SWF file: " + str(filename))
934+
935+
# Set filepointer at first tag (skipping framesize RECT and two uin16's
936+
i = 8
937+
nbits = bitsToInt(bb[i : i + 1], 5) # skip FrameSize
938+
nbits = 5 + nbits * 4
939+
Lrect = nbits / 8.0
940+
if Lrect % 1:
941+
Lrect += 1
942+
Lrect = int(Lrect)
943+
i += Lrect + 4
944+
945+
# Iterate over the tags
946+
counter = 0
947+
while True:
948+
counter += 1
949+
950+
# Get tag header
951+
head = bb[i : i + 6]
952+
if not head:
953+
break # Done (we missed end tag)
954+
955+
# Determine type and length
956+
T, L1, L2 = getTypeAndLen(head)
957+
if not L2:
958+
print("Invalid tag length, could not proceed")
959+
break
960+
# print(T, L2)
961+
962+
# Read image if we can
963+
if T in {20, 36}:
964+
im = _readPixels(bb, i + 6, T, L1)
965+
if im is not None:
966+
images.append(im)
967+
elif T in {6, 21, 35, 90}:
968+
print("Ignoring JPEG image: cannot read JPEG.")
937969
else:
938-
raise OSError("Not a valid SWF file: " + str(filename))
939-
940-
# Set filepointer at first tag (skipping framesize RECT and two uin16's
941-
i = 8
942-
nbits = bitsToInt(bb[i : i + 1], 5) # skip FrameSize
943-
nbits = 5 + nbits * 4
944-
Lrect = nbits / 8.0
945-
if Lrect % 1:
946-
Lrect += 1
947-
Lrect = int(Lrect)
948-
i += Lrect + 4
949-
950-
# Iterate over the tags
951-
counter = 0
952-
while True:
953-
counter += 1
954-
955-
# Get tag header
956-
head = bb[i : i + 6]
957-
if not head:
958-
break # Done (we missed end tag)
959-
960-
# Determine type and length
961-
T, L1, L2 = getTypeAndLen(head)
962-
if not L2:
963-
print("Invalid tag length, could not proceed")
964-
break
965-
# print(T, L2)
966-
967-
# Read image if we can
968-
if T in [20, 36]:
969-
im = _readPixels(bb, i + 6, T, L1)
970-
if im is not None:
971-
images.append(im)
972-
elif T in [6, 21, 35, 90]:
973-
print("Ignoring JPEG image: cannot read JPEG.")
974-
else:
975-
pass # Not an image tag
976-
977-
# Detect end tag
978-
if T == 0:
979-
break
980-
981-
# Next tag!
982-
i += L2
970+
pass # Not an image tag
983971

984-
finally:
985-
fp.close()
972+
# Detect end tag
973+
if T == 0:
974+
break
986975

976+
# Next tag!
977+
i += L2
987978
# Convert to normal PIL images if needed
988979
if not asNumpy:
989980
images2 = images

python/grass/script/core.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -933,15 +933,14 @@ def parser() -> tuple[dict[str, str], dict[str, bool]]:
933933
argv[0] = os.path.join(sys.path[0], name)
934934

935935
prog = "g.parser.exe" if sys.platform == "win32" else "g.parser"
936-
p = subprocess.Popen([prog, "-n"] + argv, stdout=subprocess.PIPE)
937-
s = p.communicate()[0]
938-
lines = s.split(b"\0")
939-
940-
if not lines or lines[0] != b"@ARGS_PARSED@":
941-
stdout = os.fdopen(sys.stdout.fileno(), "wb")
942-
stdout.write(s)
943-
sys.exit(p.returncode)
944-
return _parse_opts(lines[1:])
936+
with subprocess.Popen([prog, "-n"] + argv, stdout=subprocess.PIPE) as p:
937+
s = p.communicate()[0]
938+
lines = s.split(b"\0")
939+
if not lines or lines[0] != b"@ARGS_PARSED@":
940+
stdout = os.fdopen(sys.stdout.fileno(), "wb")
941+
stdout.write(s)
942+
sys.exit(p.returncode)
943+
return _parse_opts(lines[1:])
945944

946945

947946
# interface to g.tempfile

0 commit comments

Comments
 (0)