lib.Result: Initialize with status = None
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 3m9s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 3m4s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 3m3s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 3m10s
CI / Packaging test (push) Successful in 0s

Define default parameter values for Result's constructor, namely None for exit status, stdout and stderr.

Instantiating a Result object without parameters signifies "this object doesn't contain data from a real process's exit event". Up to now, similar meaning has been hand-crafted by ExecContext's run() and friends by using an error exit status (1) to make sure it wasn't mistaken for success. This commit formalizes that into the Result structure itself, but uses None instead for the exit status.

Controlling default values in Result itself also means that the Result class gets better awareness of what it contains, and its log messages and stdin / stdout can be more fitting:

- If a real process failed, make stdout return at least b'' - If a real process succeeded, make stdout return at least b''

Returning something from .stdout on success fixes a real bug: An attempt to access what "rpm -U somepackage.rpm" returns, namely nothing, raises a bogus exception, because stdout is None.

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2026-06-15 07:30:17 +02:00
commit a739eb0763
Signed by: Jan Lindemann
GPG key ID: 3750640C9E25DD61
2 changed files with 20 additions and 12 deletions

View file

@ -313,7 +313,7 @@ class ExecContext(Base):
await self.open() await self.open()
try: try:
ret = Result(None, None, 1) ret = Result()
with self.CallContext( with self.CallContext(
self, self,
title = title, title = title,
@ -421,7 +421,7 @@ class ExecContext(Base):
# be returned by CallContext and is very much allowed # be returned by CallContext and is very much allowed
assert cmd_input is not None, 'Invalid: cmd_input is None' assert cmd_input is not None, 'Invalid: cmd_input is None'
ret = Result(None, None, 1) ret = Result()
with self.CallContext( with self.CallContext(
self, self,
title = title, title = title,
@ -454,7 +454,7 @@ class ExecContext(Base):
async def _get( async def _get(
self, path: str, wd: str | None, throw: bool, verbose: bool | None, title: str self, path: str, wd: str | None, throw: bool, verbose: bool | None, title: str
) -> Result: ) -> Result:
ret = Result(None, None, 1) ret = Result()
if wd is not None: if wd is not None:
path = wd + '/' + path path = wd + '/' + path
with self.CallContext( with self.CallContext(
@ -508,7 +508,7 @@ class ExecContext(Base):
) -> Result: ) -> Result:
return await self.run(cmd, cmd_input = cmd_input, **kwargs) return await self.run(cmd, cmd_input = cmd_input, **kwargs)
ret = Result(None, None, 1) ret = Result()
try: try:
class RemoteCmd(NamedTuple): class RemoteCmd(NamedTuple):

View file

@ -4,9 +4,9 @@ class Result:
def __init__( def __init__(
self, self,
stdout: bytes | None, stdout: bytes | None = None,
stderr: bytes | None, stderr: bytes | None = None,
status: int, status: int | None = None, # Command has not yet exited
encoding: str = 'UTF-8', encoding: str = 'UTF-8',
strip: bool = True, strip: bool = True,
cmd: list[str] | None = None, cmd: list[str] | None = None,
@ -56,10 +56,11 @@ class Result:
def __repr__(self) -> str: def __repr__(self) -> str:
ret = f'{self.__status}:' ret = f'{self.__status}:'
if self.status != 0: if self.__status is not None:
ret += f' err: {self.stderr_str_or_none}' if self.status != 0:
else: ret += f' err: {self.stderr_str_or_none}'
ret += f' out: {self.__stdout_footprint(quote=True)}' else:
ret += f' out: {self.__stdout_footprint(quote=True)}'
return ret return ret
@property @property
@ -104,7 +105,10 @@ class Result:
if wd is None: if wd is None:
wd = self.__wd wd = self.__wd
call = f'"{pretty_cmd(cmd, wd)}" ' call = f'"{pretty_cmd(cmd, wd)}" '
ret = f'Command {call}has exited with status {self.__status}' ret = (
f'Command {call}has not yet exited' if self.__status is None else
f'Command {call}has exited with status {self.__status}'
)
call = pretty_cmd(cmd, wd) call = pretty_cmd(cmd, wd)
if self.status != 0: if self.status != 0:
ret += f' -> stderr="{self.__stderr!r}"' ret += f' -> stderr="{self.__stderr!r}"'
@ -125,6 +129,8 @@ class Result:
@property @property
def stdout(self) -> bytes: def stdout(self) -> bytes:
if self.__stdout is None: if self.__stdout is None:
if self.__status == 0:
return b''
raise Exception(f'Result has no standard output stream: {self.summary}') raise Exception(f'Result has no standard output stream: {self.summary}')
return self.__stdout return self.__stdout
@ -143,6 +149,8 @@ class Result:
@property @property
def stderr(self) -> bytes: def stderr(self) -> bytes:
if self.__stderr is None: if self.__stderr is None:
if isinstance(self.__status, int) and self.__status != 0:
return b''
raise Exception(f'Result has no standard error stream: {self.summary}') raise Exception(f'Result has no standard error stream: {self.summary}')
return self.__stderr return self.__stderr