From 938554dde9d893747feff85c1bce86b805763d20 Mon Sep 17 00:00:00 2001 From: Eero af Heurlin Date: Sun, 28 Jan 2024 09:17:00 +0200 Subject: [PATCH 1/4] bump version --- .bumpversion.cfg | 2 +- pyproject.toml | 2 +- src/libpvarki/__init__.py | 2 +- tests/test_libpvarki.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 5c01831..fca4062 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.8.0 +current_version = 1.9.0 commit = False tag = False diff --git a/pyproject.toml b/pyproject.toml index b9fb03d..d3007fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "libpvarki" -version = "1.8.0" +version = "1.9.0" description = "Common helpers like standard logging init" authors = ["Eero af Heurlin "] homepage = "https://github.com/pvarki/python-libpvarki/" diff --git a/src/libpvarki/__init__.py b/src/libpvarki/__init__.py index 7200c22..48589c0 100644 --- a/src/libpvarki/__init__.py +++ b/src/libpvarki/__init__.py @@ -1,2 +1,2 @@ """ Common helpers like standard logging init """ -__version__ = "1.8.0" # NOTE Use `bump2version --config-file patch` to bump versions correctly +__version__ = "1.9.0" # NOTE Use `bump2version --config-file patch` to bump versions correctly diff --git a/tests/test_libpvarki.py b/tests/test_libpvarki.py index 745bc63..39ac8c8 100644 --- a/tests/test_libpvarki.py +++ b/tests/test_libpvarki.py @@ -4,4 +4,4 @@ def test_version() -> None: """Make sure version matches expected""" - assert __version__ == "1.8.0" + assert __version__ == "1.9.0" From 20faf257d34321e5bc6d45b1e3c622dcc37224a9 Mon Sep 17 00:00:00 2001 From: Eero af Heurlin Date: Sun, 28 Jan 2024 09:43:20 +0200 Subject: [PATCH 2/4] Add helper (and tests) for boilerplate stuff in shell command handling --- src/libpvarki/shell.py | 29 +++++++++++++++++++++++ tests/test_shell.py | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 src/libpvarki/shell.py create mode 100644 tests/test_shell.py diff --git a/src/libpvarki/shell.py b/src/libpvarki/shell.py new file mode 100644 index 0000000..cba0af6 --- /dev/null +++ b/src/libpvarki/shell.py @@ -0,0 +1,29 @@ +"""Shell related helpers""" +from typing import Tuple +import asyncio +import logging + +LOGGER = logging.getLogger(__name__) + + +async def call_cmd(cmd: str, timeout: float = 2.5) -> Tuple[int, str, str]: + """Do the boilerplate for calling cmd and returning the exit code and output as strings""" + LOGGER.debug("Calling create_subprocess_shell(({})".format(cmd)) + process = await asyncio.create_subprocess_shell( + cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + out, err = await asyncio.wait_for(process.communicate(), timeout=timeout) + out_str = out.decode("utf-8") + err_str = err.decode("utf-8") + if err: + LOGGER.warning("{} stderr: {}".format(cmd, err_str)) + LOGGER.info(out_str) + assert isinstance(process.returncode, int) # at this point it is, keep mypy happy + if process.returncode != 0: + LOGGER.error("{} returned nonzero code: {} (process: {})".format(cmd, process.returncode, process)) + LOGGER.error("{} stderr: {}".format(cmd, err_str)) + LOGGER.error("{} stdout: {}".format(cmd, out_str)) + + return process.returncode, out_str, err_str diff --git a/tests/test_shell.py b/tests/test_shell.py new file mode 100644 index 0000000..757ff9b --- /dev/null +++ b/tests/test_shell.py @@ -0,0 +1,52 @@ +"""Test shell helpers""" +import logging +import asyncio + +import pytest + +from libpvarki.shell import call_cmd + +LOGGER = logging.getLogger(__name__) + + +@pytest.mark.asyncio +async def test_true() -> None: + """Test that true exists with code 0""" + code, stdout, stderr = await call_cmd("true") + assert code == 0 + assert stdout == "" + assert stderr == "" + + +@pytest.mark.asyncio +async def test_false() -> None: + """Test that false exists with nonzero code""" + code, stdout, stderr = await call_cmd("false") + assert code != 0 + assert stdout == "" + assert stderr == "" + + +@pytest.mark.asyncio +async def test_stdout() -> None: + """Test that echo exists with code 0 and ouputs what we expect to stdout""" + code, stdout, stderr = await call_cmd("echo 'hello world'") + assert code == 0 + assert stdout == "hello world\n" + assert stderr == "" + + +@pytest.mark.asyncio +async def test_stderr() -> None: + """Test that echo exists with code 0 and ouputs what we expect to stderr""" + code, stdout, stderr = await call_cmd("echo 'goodbye world' >&2") + assert code == 0 + assert stdout == "" + assert stderr == "goodbye world\n" + + +@pytest.mark.asyncio +async def test_timeout() -> None: + """Test that long sleeps are aborted on timeout""" + with pytest.raises(asyncio.TimeoutError): + await call_cmd("sleep 5", timeout=1.0) From a67c09f2672e5915a457a6139c9d34413a0cd8d3 Mon Sep 17 00:00:00 2001 From: Eero af Heurlin Date: Sun, 28 Jan 2024 19:16:20 +0200 Subject: [PATCH 3/4] make it possible to skip the warning with stderr output --- src/libpvarki/shell.py | 4 ++-- tests/test_shell.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/libpvarki/shell.py b/src/libpvarki/shell.py index cba0af6..66d8dfd 100644 --- a/src/libpvarki/shell.py +++ b/src/libpvarki/shell.py @@ -6,7 +6,7 @@ LOGGER = logging.getLogger(__name__) -async def call_cmd(cmd: str, timeout: float = 2.5) -> Tuple[int, str, str]: +async def call_cmd(cmd: str, timeout: float = 2.5, *, stderr_warn: bool = True) -> Tuple[int, str, str]: """Do the boilerplate for calling cmd and returning the exit code and output as strings""" LOGGER.debug("Calling create_subprocess_shell(({})".format(cmd)) process = await asyncio.create_subprocess_shell( @@ -17,7 +17,7 @@ async def call_cmd(cmd: str, timeout: float = 2.5) -> Tuple[int, str, str]: out, err = await asyncio.wait_for(process.communicate(), timeout=timeout) out_str = out.decode("utf-8") err_str = err.decode("utf-8") - if err: + if err and stderr_warn: LOGGER.warning("{} stderr: {}".format(cmd, err_str)) LOGGER.info(out_str) assert isinstance(process.returncode, int) # at this point it is, keep mypy happy diff --git a/tests/test_shell.py b/tests/test_shell.py index 757ff9b..4e4bf9f 100644 --- a/tests/test_shell.py +++ b/tests/test_shell.py @@ -40,6 +40,17 @@ async def test_stdout() -> None: async def test_stderr() -> None: """Test that echo exists with code 0 and ouputs what we expect to stderr""" code, stdout, stderr = await call_cmd("echo 'goodbye world' >&2") + # FIXME: Capture log output and check for the warning + assert code == 0 + assert stdout == "" + assert stderr == "goodbye world\n" + + +@pytest.mark.asyncio +async def test_stderr_nowarn() -> None: + """Test that echo exists with code 0 and ouputs what we expect to stderr""" + code, stdout, stderr = await call_cmd("echo 'goodbye world' >&2", stderr_warn=False) + # FIXME: Capture log output and check that there is no warning assert code == 0 assert stdout == "" assert stderr == "goodbye world\n" From 50ed2e0716bf3985e51dba1c576694b79b0ea2f0 Mon Sep 17 00:00:00 2001 From: Eero af Heurlin Date: Mon, 5 Feb 2024 21:31:03 +0200 Subject: [PATCH 4/4] Drop .env file support until Starlette comes to their senses --- src/libpvarki/middleware/mtlsheader.py | 2 +- src/libpvarki/mtlshelp/context.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libpvarki/middleware/mtlsheader.py b/src/libpvarki/middleware/mtlsheader.py index 3310043..3b703a3 100644 --- a/src/libpvarki/middleware/mtlsheader.py +++ b/src/libpvarki/middleware/mtlsheader.py @@ -9,7 +9,7 @@ LOGGER = logging.getLogger(__name__) -CONFIG = Config(".env") +CONFIG = Config() # not supporting .env files anymore because https://github.com/encode/starlette/discussions/2446 DNDict = Mapping[str, str] diff --git a/src/libpvarki/mtlshelp/context.py b/src/libpvarki/mtlshelp/context.py index d7b5aa9..a5cdaad 100644 --- a/src/libpvarki/mtlshelp/context.py +++ b/src/libpvarki/mtlshelp/context.py @@ -8,7 +8,7 @@ LOGGER = logging.getLogger(__name__) -CONFIG = Config(".env") +CONFIG = Config() # not supporting .env files anymore because https://github.com/encode/starlette/discussions/2446 # https://github.com/miguelgrinberg/python-socketio/discussions/1040 was very helpful