From d4badf12dde4f99d2691341d266f5ebae98c7ee3 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Sun, 16 Nov 2025 14:39:16 +0100 Subject: [PATCH 1/3] Add a fileno() on StreamHandler This is required when something tries to access the fileno for, e.g. checking if the value is attached to a tty. This will forward the value from the wrapped stream, despite some problems that might arise if something tries to then act on the fileno alone. --- src/dwas/_io.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/dwas/_io.py b/src/dwas/_io.py index b422df7..d821ba8 100644 --- a/src/dwas/_io.py +++ b/src/dwas/_io.py @@ -95,10 +95,14 @@ def flush( class StreamHandler(io.TextIOWrapper): def __init__( - self, var: ContextVar[TextIO], log_var: ContextVar[TextIO] + self, + var: ContextVar[TextIO], + log_var: ContextVar[TextIO], + fileno: int, ) -> None: self._var = var self._log_var = log_var + self._fileno = fileno def read(self, size: int | None = None) -> str: # noqa: ARG002 raise io.UnsupportedOperation("can't read from a memorypipe") @@ -117,6 +121,9 @@ def flush(self) -> None: fd.flush() self._var.get().flush() + def fileno(self) -> int: + return self._fileno + @contextmanager def instrument_streams() -> Generator[None, None, None]: @@ -124,9 +131,9 @@ def instrument_streams() -> Generator[None, None, None]: STDERR.set(sys.stderr) LOG_FILE.set(NoOpWriter()) - with redirect_stdout(StreamHandler(STDOUT, LOG_FILE)), redirect_stderr( - StreamHandler(STDERR, LOG_FILE) - ): + with redirect_stdout( + StreamHandler(STDOUT, LOG_FILE, sys.stdout.fileno()) + ), redirect_stderr(StreamHandler(STDERR, LOG_FILE, sys.stderr.fileno())): yield From a38aa872d3e2bc7d7ca07b041dec6f9ff9d56435 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Sun, 5 Oct 2025 10:29:42 +0200 Subject: [PATCH 2/3] Add support for python3.14 --- .github/workflows/ci.yml | 1 + dwasfile.py | 19 +++++++++++++----- pyproject.toml | 1 + src/dwas/_steps/parametrize.py | 2 +- src/dwas/_steps/steps.py | 13 +++++-------- tests/predefined/test_docformatter.py | 28 +++++++++++++++++++++++++++ 6 files changed, 50 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a797854..275959d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,6 +96,7 @@ jobs: - "3.11" - "3.12" - "3.13" + - "3.14" - "pypy3.10" include: - os: macos-latest diff --git a/dwasfile.py b/dwasfile.py index ef3fbdf..69f18ca 100644 --- a/dwasfile.py +++ b/dwasfile.py @@ -11,7 +11,16 @@ DOCS_REQUIREMENTS = "-rrequirements/requirements-docs.txt" TEST_REQUIREMENTS = "-rrequirements/requirements-test.txt" TYPES_REQUIREMENTS = "-rrequirements/requirements-types.txt" -SUPPORTED_PYTHONS = ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "pypy3.10"] +SUPPORTED_PYTHONS = [ + "3.8", + "3.9", + "3.10", + "3.11", + "3.12", + "3.13", + "3.14", + "pypy3.10", +] OLDEST_SUPPORTED_PYTHON = SUPPORTED_PYTHONS[0] PYTHON_FILES = [ "docs/conf.py", @@ -31,13 +40,13 @@ # Formatting ## dwas.register_managed_step( - dwas.predefined.unimport(), + dwas.predefined.unimport(files=PYTHON_FILES), description="Show which imports are unnecessary", ) dwas.register_managed_step(dwas.predefined.isort(files=PYTHON_FILES)) dwas.register_managed_step( dwas.predefined.docformatter(files=PYTHON_FILES), - dependencies=["docformatter[tomli]<1.7.1"], + dependencies=["docformatter[tomli]"], ) dwas.register_managed_step(dwas.predefined.black()) dwas.register_step_group( @@ -47,6 +56,7 @@ # With auto fix dwas.register_managed_step( dwas.predefined.unimport( + files=PYTHON_FILES, additional_arguments=["--diff", "--remove", "--check", "--gitignore"], ), name="unimport:fix", @@ -68,7 +78,7 @@ ), name="docformatter:fix", run_by_default=False, - dependencies=["docformatter[tomli]<1.7.1"], + dependencies=["docformatter[tomli]"], requires=["isort:fix"], ) dwas.register_managed_step( @@ -83,7 +93,6 @@ additional_arguments=["check", "--fix", "--show-fixes", "--fix-only"], ), dependencies=["ruff"], - python=OLDEST_SUPPORTED_PYTHON, name="ruff:fix", requires=["black:fix"], run_by_default=False, diff --git a/pyproject.toml b/pyproject.toml index ea19140..d59af42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Testing", diff --git a/src/dwas/_steps/parametrize.py b/src/dwas/_steps/parametrize.py index 32ed08f..92ddcc5 100644 --- a/src/dwas/_steps/parametrize.py +++ b/src/dwas/_steps/parametrize.py @@ -304,7 +304,7 @@ def build_parameters(**kwargs: Any) -> Callable[[T], T]: def extract_parameters( - func: Callable[..., Any] + func: Callable[..., Any], ) -> list[tuple[str, dict[str, Any]]]: defaults = getattr(func, _DEFAULTS, {}) diff --git a/src/dwas/_steps/steps.py b/src/dwas/_steps/steps.py index 3f7f943..09ee60e 100644 --- a/src/dwas/_steps/steps.py +++ b/src/dwas/_steps/steps.py @@ -258,11 +258,9 @@ def setup_dependent( Run some logic into a dependent step. :param original_step: The original step handler that was used - when the step defining this method was - called. + when the step defining this method was called. :param current_step: The current step handler, that contains the - context of the step that is going to be - executed. + context of the step that is going to be executed. """ @@ -352,11 +350,10 @@ def gather_artifacts(self, step: StepRunner) -> dict[str, list[Any]]: Gather all artifacts exposed by this step. :param step: The step handler that was used when running the - step. + step. :return: A dictionary of artifact key to a list of arbitrary - data. This **must** return a list, as they are merged - with other steps' artifacts into a single list per - artifact key. + data. This **must** return a list, as they are merged with + other steps' artifacts into a single list per artifact key. """ diff --git a/tests/predefined/test_docformatter.py b/tests/predefined/test_docformatter.py index ec6504b..be6840d 100644 --- a/tests/predefined/test_docformatter.py +++ b/tests/predefined/test_docformatter.py @@ -1,3 +1,5 @@ +import sys + import pytest from .mixins import BaseLinterWithAutofixTest @@ -25,3 +27,29 @@ class TestDocformatter(BaseLinterWithAutofixTest): @pytest.mark.skip("docformatter does not support colored output") def test_respects_color_settings(self): pass # pragma: nocover + + @pytest.mark.parametrize( + "valid", + ( + pytest.param( + True, + id="valid-project", + marks=pytest.mark.xfail( + sys.version_info >= (3, 14), + reason="docformatter does not support python3.14 yet", + strict=True, + ), + ), + pytest.param(False, id="invalid-project"), + ), + ) + def test_simple_behavior(self, cache_path, tmp_path, valid): + return super().test_simple_behavior(cache_path, tmp_path, valid) + + @pytest.mark.xfail( + sys.version_info >= (3, 14), + reason="docformatter does not support python3.14 yet", + strict=True, + ) + def test_can_apply_fixes(self, cache_path, tmp_path): + return super().test_can_apply_fixes(cache_path, tmp_path) From 90e78cf751946acb5faa73c1a903579e07ba9b56 Mon Sep 17 00:00:00 2001 From: Benjamin Schubert Date: Sun, 16 Nov 2025 17:06:54 +0100 Subject: [PATCH 3/3] Test against pypy3.11 not 3.10 Some dependencies have moved on --- .github/workflows/ci.yml | 2 +- dwasfile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 275959d..3f6bbb2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,7 +97,7 @@ jobs: - "3.12" - "3.13" - "3.14" - - "pypy3.10" + - "pypy3.11" include: - os: macos-latest python: "3.8" diff --git a/dwasfile.py b/dwasfile.py index 69f18ca..9284569 100644 --- a/dwasfile.py +++ b/dwasfile.py @@ -19,7 +19,7 @@ "3.12", "3.13", "3.14", - "pypy3.10", + "pypy3.11", ] OLDEST_SUPPORTED_PYTHON = SUPPORTED_PYTHONS[0] PYTHON_FILES = [