kkpyutil 1.45.0__tar.gz → 1.46.1__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.
- {kkpyutil-1.45.0 → kkpyutil-1.46.1}/PKG-INFO +1 -1
- {kkpyutil-1.45.0 → kkpyutil-1.46.1}/kkpyutil.py +137 -53
- {kkpyutil-1.45.0 → kkpyutil-1.46.1}/pyproject.toml +1 -1
- {kkpyutil-1.45.0 → kkpyutil-1.46.1}/LICENSE +0 -0
- {kkpyutil-1.45.0 → kkpyutil-1.46.1}/README.md +0 -0
- {kkpyutil-1.45.0 → kkpyutil-1.46.1}/kkpyutil_helper/windows/kkttssave.ps1 +0 -0
- {kkpyutil-1.45.0 → kkpyutil-1.46.1}/kkpyutil_helper/windows/kkttsspeak.ps1 +0 -0
|
@@ -581,7 +581,7 @@ glogger.setLevel(logging.DEBUG)
|
|
|
581
581
|
def catch_unknown_exception(exc_type, exc_value, exc_traceback):
|
|
582
582
|
"""Global exception to handle uncaught exceptions"""
|
|
583
583
|
exc_info = exc_type, exc_value, exc_traceback
|
|
584
|
-
glogger.error('Unhandled exception:
|
|
584
|
+
glogger.error('Unhandled exception:', exc_info=exc_info)
|
|
585
585
|
# _logger.exception('Unhandled exception: ') # try-except block only.
|
|
586
586
|
# sys.__excepthook__(*exc_info) # Keep commented out to avoid msg dup.
|
|
587
587
|
|
|
@@ -1255,23 +1255,84 @@ def save_winreg_record(full_key, var, value, value_type=winreg.REG_EXPAND_SZ if
|
|
|
1255
1255
|
winreg.SetValueEx(key, var, 0, value_type, value)
|
|
1256
1256
|
|
|
1257
1257
|
|
|
1258
|
+
def _log_subprocess_command(cmd, cwd, logger, func_name="subprocess"):
|
|
1259
|
+
"""Helper function to log subprocess command consistently"""
|
|
1260
|
+
cmd_log = f"""\
|
|
1261
|
+
{func_name}:
|
|
1262
|
+
{' '.join(cmd)}
|
|
1263
|
+
cwd: {osp.abspath(cwd) if cwd else os.getcwd()}"""
|
|
1264
|
+
logger.info(cmd_log)
|
|
1265
|
+
|
|
1266
|
+
|
|
1267
|
+
def _log_subprocess_startup_error(e, cmd, logger, useexception=True):
|
|
1268
|
+
"""Helper function to log subprocess startup errors with structured format"""
|
|
1269
|
+
error_type = type(e).__name__
|
|
1270
|
+
cmd_str = ' '.join(cmd)
|
|
1271
|
+
|
|
1272
|
+
# Create structured error message based on error type
|
|
1273
|
+
if isinstance(e, FileNotFoundError):
|
|
1274
|
+
situation = "Command not found"
|
|
1275
|
+
detail = [f"Command: {cmd_str}", f"Error: {error_type}"]
|
|
1276
|
+
advice = [
|
|
1277
|
+
"Check if the command is installed and available in PATH",
|
|
1278
|
+
"Verify the command name spelling",
|
|
1279
|
+
"Use absolute path if the command is in a specific location"
|
|
1280
|
+
]
|
|
1281
|
+
elif isinstance(e, PermissionError):
|
|
1282
|
+
situation = "Permission denied"
|
|
1283
|
+
detail = [f"Command: {cmd_str}", f"Error: {error_type}"]
|
|
1284
|
+
advice = [
|
|
1285
|
+
"Check if you have permission to execute the command",
|
|
1286
|
+
"Try running with elevated privileges if necessary",
|
|
1287
|
+
"Verify file permissions on the executable"
|
|
1288
|
+
]
|
|
1289
|
+
else:
|
|
1290
|
+
situation = "Subprocess failed to start"
|
|
1291
|
+
detail = [f"Command: {cmd_str}", f"Error: {error_type}: {str(e)}"]
|
|
1292
|
+
advice = [
|
|
1293
|
+
"Check if the command exists and is executable",
|
|
1294
|
+
"Verify all command arguments are valid",
|
|
1295
|
+
"Check system resources and environment"
|
|
1296
|
+
]
|
|
1297
|
+
|
|
1298
|
+
error_msg = format_log(situation, detail=detail, advice=advice)
|
|
1299
|
+
logger.error(error_msg)
|
|
1300
|
+
|
|
1301
|
+
if useexception:
|
|
1302
|
+
raise e
|
|
1303
|
+
return types.SimpleNamespace(returncode=2, stdout='', stderr=safe_encode_text(str(e), encoding=LOCALE_CODEC))
|
|
1304
|
+
|
|
1305
|
+
|
|
1258
1306
|
def run_cmd(cmd, cwd=None, logger=None, check=True, shell=False, verbose=False, useexception=True, env=None, hidedoswin=True):
|
|
1259
1307
|
"""
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1308
|
+
Run a subprocess command and wait for completion.
|
|
1309
|
+
|
|
1310
|
+
Args:
|
|
1311
|
+
cmd: Command list (non-str items auto-converted to str)
|
|
1312
|
+
cwd: Working directory (default: current directory)
|
|
1313
|
+
logger: Logger instance (default: glogger)
|
|
1314
|
+
check: Whether to raise exception on non-zero exit (default: True)
|
|
1315
|
+
shell: Whether to use shell (default: False)
|
|
1316
|
+
verbose: Whether to log stdout at INFO level vs DEBUG (default: False)
|
|
1317
|
+
useexception: Whether to raise exceptions vs return error info (default: True)
|
|
1318
|
+
env: Environment variables (default: None)
|
|
1319
|
+
hidedoswin: Whether to hide DOS window on Windows (default: True)
|
|
1320
|
+
|
|
1321
|
+
Returns:
|
|
1322
|
+
subprocess.CompletedProcess on success, or SimpleNamespace with error info
|
|
1323
|
+
|
|
1324
|
+
Best practices:
|
|
1325
|
+
- Use useexception=False for optional commands that may fail
|
|
1326
|
+
- Use verbose=True to see subprocess output in logs
|
|
1327
|
+
- Use shell=True only when needed (e.g., shell built-ins, complex commands)
|
|
1328
|
+
- Use check=False with useexception=False for commands where failure is expected
|
|
1265
1329
|
"""
|
|
1266
1330
|
cmd = [comp if isinstance(comp, str) else str(comp) for comp in cmd]
|
|
1267
1331
|
logger = logger or glogger
|
|
1268
1332
|
console_info = logger.info if logger and verbose else logger.debug
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
cwd: {osp.abspath(cwd) if cwd else os.getcwd()}
|
|
1273
|
-
"""
|
|
1274
|
-
logger.info(cmd_log)
|
|
1333
|
+
|
|
1334
|
+
# Log command execution
|
|
1335
|
+
_log_subprocess_command(cmd, cwd, logger, "run_cmd")
|
|
1275
1336
|
try:
|
|
1276
1337
|
if hidedoswin and PLATFORM == 'Windows':
|
|
1277
1338
|
startupinfo = subprocess.STARTUPINFO()
|
|
@@ -1282,43 +1343,75 @@ cwd: {osp.abspath(cwd) if cwd else os.getcwd()}
|
|
|
1282
1343
|
stdout_log = safe_decode_bytes(proc.stdout)
|
|
1283
1344
|
stderr_log = safe_decode_bytes(proc.stderr)
|
|
1284
1345
|
if stdout_log:
|
|
1285
|
-
console_info(f'stdout:\n{stdout_log}')
|
|
1346
|
+
console_info(f'stdout:\n{stdout_log.rstrip()}')
|
|
1286
1347
|
if stderr_log:
|
|
1287
|
-
logger.error(f'stderr:\n{stderr_log}')
|
|
1348
|
+
logger.error(f'stderr:\n{stderr_log.rstrip()}')
|
|
1288
1349
|
# subprocess started but failed halfway: check=True, proc returns non-zero
|
|
1289
|
-
# won't trigger this exception when useexception=True
|
|
1290
1350
|
except subprocess.CalledProcessError as e:
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1351
|
+
stdout_log = safe_decode_bytes(e.stdout)
|
|
1352
|
+
stderr_log = safe_decode_bytes(e.stderr)
|
|
1353
|
+
|
|
1354
|
+
# Log subprocess output with clear separation
|
|
1355
|
+
if stdout_log:
|
|
1356
|
+
logger.info(f'stdout:\n{stdout_log.rstrip()}')
|
|
1357
|
+
if stderr_log:
|
|
1358
|
+
logger.error(f'stderr:\n{stderr_log.rstrip()}')
|
|
1359
|
+
|
|
1360
|
+
# Log structured error message
|
|
1361
|
+
situation = "Subprocess completed with non-zero exit code"
|
|
1362
|
+
detail = [
|
|
1363
|
+
f"Command: {' '.join(cmd)}",
|
|
1364
|
+
f"Exit code: {e.returncode}",
|
|
1365
|
+
f"Has stdout: {'Yes' if stdout_log else 'No'}",
|
|
1366
|
+
f"Has stderr: {'Yes' if stderr_log else 'No'}"
|
|
1367
|
+
]
|
|
1368
|
+
error_msg = format_log(situation, detail=detail)
|
|
1369
|
+
logger.error(error_msg)
|
|
1370
|
+
|
|
1296
1371
|
if useexception:
|
|
1297
1372
|
raise e
|
|
1298
1373
|
return types.SimpleNamespace(returncode=1, stdout=e.stdout, stderr=e.stderr)
|
|
1299
1374
|
# subprocess fails to start
|
|
1300
1375
|
except Exception as e:
|
|
1301
|
-
|
|
1302
|
-
# PermissionError, OSError, TimeoutExpired
|
|
1303
|
-
logger.error(e)
|
|
1304
|
-
if useexception:
|
|
1305
|
-
raise e
|
|
1306
|
-
return types.SimpleNamespace(returncode=2, stdout='', stderr=safe_encode_text(str(e), encoding=LOCALE_CODEC))
|
|
1376
|
+
return _log_subprocess_startup_error(e, cmd, logger, useexception)
|
|
1307
1377
|
return proc
|
|
1308
1378
|
|
|
1309
1379
|
|
|
1310
|
-
def run_daemon(cmd, cwd=None, logger=None, shell=False, useexception=True, env=None, hidedoswin=True):
|
|
1380
|
+
def run_daemon(cmd, cwd=None, logger=None, shell=False, verbose=False, useexception=True, env=None, hidedoswin=True):
|
|
1311
1381
|
"""
|
|
1312
|
-
|
|
1382
|
+
Start a subprocess in the background (non-blocking).
|
|
1383
|
+
|
|
1384
|
+
Args:
|
|
1385
|
+
cmd: Command list (non-str items auto-converted to str)
|
|
1386
|
+
cwd: Working directory (default: current directory)
|
|
1387
|
+
logger: Logger instance (default: glogger)
|
|
1388
|
+
shell: Whether to use shell (default: False)
|
|
1389
|
+
verbose: Whether to log at INFO level vs DEBUG (default: False)
|
|
1390
|
+
useexception: Whether to raise exceptions vs return error info (default: True)
|
|
1391
|
+
env: Environment variables (default: None)
|
|
1392
|
+
hidedoswin: Whether to hide DOS window on Windows (default: True)
|
|
1393
|
+
|
|
1394
|
+
Returns:
|
|
1395
|
+
subprocess.Popen object on success, or SimpleNamespace with error info
|
|
1396
|
+
|
|
1397
|
+
Best practices:
|
|
1398
|
+
- Use for long-running processes or fire-and-forget commands
|
|
1399
|
+
- Call proc.communicate() or proc.wait() to get final results
|
|
1400
|
+
- Use verbose=True to see command execution in logs
|
|
1401
|
+
- Background processes won't show stdout/stderr in logs automatically
|
|
1402
|
+
- Use useexception=False for optional background processes
|
|
1403
|
+
|
|
1404
|
+
Note:
|
|
1405
|
+
Background processes capture stdout/stderr but don't log them automatically.
|
|
1406
|
+
Call proc.communicate() to retrieve output when the process completes.
|
|
1313
1407
|
"""
|
|
1314
1408
|
cmd = [comp if isinstance(comp, str) else str(comp) for comp in cmd]
|
|
1315
1409
|
logger = logger or glogger
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
""
|
|
1320
|
-
|
|
1321
|
-
proc = None
|
|
1410
|
+
|
|
1411
|
+
# Log command execution with appropriate level
|
|
1412
|
+
log_func = logger.info if verbose else logger.debug
|
|
1413
|
+
_log_subprocess_command(cmd, cwd, logger, "run_daemon")
|
|
1414
|
+
|
|
1322
1415
|
try:
|
|
1323
1416
|
if hidedoswin and PLATFORM == 'Windows':
|
|
1324
1417
|
startupinfo = subprocess.STARTUPINFO()
|
|
@@ -1326,16 +1419,14 @@ cwd: {osp.abspath(cwd) if cwd else os.getcwd()}
|
|
|
1326
1419
|
proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env, startupinfo=startupinfo)
|
|
1327
1420
|
else:
|
|
1328
1421
|
proc = subprocess.Popen(cmd, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, env=env)
|
|
1329
|
-
|
|
1422
|
+
|
|
1423
|
+
# Log successful startup
|
|
1424
|
+
log_func(f"Background process started successfully (PID: {proc.pid})")
|
|
1425
|
+
return proc
|
|
1426
|
+
|
|
1330
1427
|
# subprocess fails to start
|
|
1331
1428
|
except Exception as e:
|
|
1332
|
-
|
|
1333
|
-
# PermissionError, OSError, TimeoutExpired
|
|
1334
|
-
logger.error(e)
|
|
1335
|
-
if useexception:
|
|
1336
|
-
raise e
|
|
1337
|
-
return types.SimpleNamespace(returncode=2, stdout='', stderr=safe_encode_text(str(e), encoding=LOCALE_CODEC))
|
|
1338
|
-
return proc
|
|
1429
|
+
return _log_subprocess_startup_error(e, cmd, logger, useexception)
|
|
1339
1430
|
|
|
1340
1431
|
|
|
1341
1432
|
def watch_cmd(cmd, cwd=None, logger=None, shell=False, verbose=False, useexception=True, prompt=None, timeout=None, env=None, hidedoswin=True):
|
|
@@ -1348,12 +1439,9 @@ def watch_cmd(cmd, cwd=None, logger=None, shell=False, verbose=False, useexcepti
|
|
|
1348
1439
|
output_queue.put(line)
|
|
1349
1440
|
cmd = [comp if isinstance(comp, str) else str(comp) for comp in cmd]
|
|
1350
1441
|
logger = logger or glogger
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
cwd: {osp.abspath(cwd) if cwd else os.getcwd()}
|
|
1355
|
-
"""
|
|
1356
|
-
logger.info(cmd_log)
|
|
1442
|
+
|
|
1443
|
+
# Log command execution
|
|
1444
|
+
_log_subprocess_command(cmd, cwd, logger, "watch_cmd")
|
|
1357
1445
|
try:
|
|
1358
1446
|
if hidedoswin and PLATFORM == 'Windows':
|
|
1359
1447
|
startupinfo = subprocess.STARTUPINFO()
|
|
@@ -1397,11 +1485,7 @@ cwd: {osp.abspath(cwd) if cwd else os.getcwd()}
|
|
|
1397
1485
|
return proc
|
|
1398
1486
|
# subprocess fails to start
|
|
1399
1487
|
except Exception as e:
|
|
1400
|
-
|
|
1401
|
-
logger.error(e)
|
|
1402
|
-
if useexception:
|
|
1403
|
-
raise e
|
|
1404
|
-
return types.SimpleNamespace(returncode=2, stdout='', stderr=safe_encode_text(str(e), encoding=LOCALE_CODEC))
|
|
1488
|
+
return _log_subprocess_startup_error(e, cmd, logger, useexception)
|
|
1405
1489
|
|
|
1406
1490
|
|
|
1407
1491
|
def extract_call_args(file, caller, callee):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|