mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-06-19 19:18:38 +00:00
fix(install): correctly detect already-installed dateutil/websocket-client
Address remaining coderabbitai findings on PR #369: - check_package_installed() did __import__(package_name) directly, but python-dateutil and websocket-client import as dateutil/websocket. Both always failed the "already installed" check and were reinstalled on every run. Add an IMPORT_NAME_MAP for the mismatched names. - _run() still read the entire temp file into memory before slicing the tail. Stream it line-by-line into a deque(maxlen=ERROR_TAIL_LINES) instead so memory use stays bounded for very chatty commands.
This commit is contained in:
@@ -8,6 +8,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import warnings
|
import warnings
|
||||||
|
from collections import deque
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# How many trailing lines of a failed command's output to keep for the
|
# How many trailing lines of a failed command's output to keep for the
|
||||||
@@ -27,8 +28,13 @@ def _run(cmd):
|
|||||||
with tempfile.TemporaryFile(mode='w+b') as f:
|
with tempfile.TemporaryFile(mode='w+b') as f:
|
||||||
result = subprocess.run(cmd, stdout=f, stderr=subprocess.STDOUT) # nosec B603 B607 - hardcoded apt/pip args # nosemgrep
|
result = subprocess.run(cmd, stdout=f, stderr=subprocess.STDOUT) # nosec B603 B607 - hardcoded apt/pip args # nosemgrep
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
lines = f.read().decode('utf-8', errors='replace').splitlines()
|
# Stream line-by-line so only the last ERROR_TAIL_LINES are ever held
|
||||||
return result.returncode == 0, '\n'.join(lines[-ERROR_TAIL_LINES:])
|
# in memory, regardless of how much output the command produced.
|
||||||
|
tail = deque(
|
||||||
|
(line.decode('utf-8', errors='replace').rstrip('\n') for line in f),
|
||||||
|
maxlen=ERROR_TAIL_LINES,
|
||||||
|
)
|
||||||
|
return result.returncode == 0, '\n'.join(tail)
|
||||||
|
|
||||||
|
|
||||||
def install_via_apt(package_name):
|
def install_via_apt(package_name):
|
||||||
@@ -84,14 +90,22 @@ def install_via_pip(package_name):
|
|||||||
return False, output
|
return False, output
|
||||||
|
|
||||||
|
|
||||||
|
# Distribution (pip/apt) names whose importable module name differs.
|
||||||
|
IMPORT_NAME_MAP = {
|
||||||
|
'python-dateutil': 'dateutil',
|
||||||
|
'websocket-client': 'websocket',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def check_package_installed(package_name):
|
def check_package_installed(package_name):
|
||||||
"""Check if a package is already installed."""
|
"""Check if a package is already installed."""
|
||||||
|
import_name = IMPORT_NAME_MAP.get(package_name, package_name)
|
||||||
# Suppress deprecation warnings when checking if packages are installed
|
# Suppress deprecation warnings when checking if packages are installed
|
||||||
# (we're just checking, not using them)
|
# (we're just checking, not using them)
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||||
try:
|
try:
|
||||||
__import__(package_name)
|
__import__(import_name)
|
||||||
return True
|
return True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
return False
|
return False
|
||||||
|
|||||||
Reference in New Issue
Block a user