Package: src:python-blessed Version: 1.25-1 User: [email protected] Usertags: python3.15 Tags: patch, ftbfs, forky, sid
Hi! While rebuilding the python related packages against the Python 3.15rc1 version we found that python-blessed fails to build from source [1]. The error is: DeprecationWarning: This process (pid=1990) is multi-threaded, use of forkpty() may lead to deadlocks in the child. In order to fix it, I had to apply two changes: - Upstream Commit cee680f: Ignore deprecation warnings fro pty.fork [2] - Run tests against the tests directory (otherwise a patch in the tests tries to run the tests in .pc) I applied these fixes in the sandbox [3] to be able to build the packages that depend on python-blessed, please consider applying the patch to support the upcoming 3.15 version. Happy hacking, [1]: https://debusine.debian.net/debian/r-python-python3.15/artifact/4434437/ [2]: https://github.com/jquast/blessed/commit/cee680ff7fb3ad31f42ae98582ba74629f1fd6b0 [3]: https://debusine.debian.net/debian/r-python-python3.15/ -- "Can you imagine what I would do if I could do all I can?" -- Sun Tzu Saludos /\/\ /\ >< `/
diff -Nru python-blessed-1.25/debian/changelog python-blessed-1.25/debian/changelog --- python-blessed-1.25/debian/changelog 2025-12-24 16:26:02.000000000 +0100 +++ python-blessed-1.25/debian/changelog 2026-09-02 16:19:03.000000000 +0200 @@ -1,3 +1,12 @@ +python-blessed (1.25-2) UNRELEASED; urgency=medium + + * Team upload. + * Add more upstream patch: + 0002-Update-wrap-logic-and-add-optional-testing-for-Python-3.15.patch + * Avoid running tests from .pc + + -- Maximiliano Curia <[email protected]> Wed, 02 Sep 2026 16:19:03 +0200 + python-blessed (1.25-1) unstable; urgency=medium * Team Upload diff -Nru python-blessed-1.25/debian/patches/0002-Update-wrap-logic-and-add-optional-testing-for-Python-3.15.patch python-blessed-1.25/debian/patches/0002-Update-wrap-logic-and-add-optional-testing-for-Python-3.15.patch --- python-blessed-1.25/debian/patches/0002-Update-wrap-logic-and-add-optional-testing-for-Python-3.15.patch 1970-01-01 01:00:00.000000000 +0100 +++ python-blessed-1.25/debian/patches/0002-Update-wrap-logic-and-add-optional-testing-for-Python-3.15.patch 2026-09-02 16:19:03.000000000 +0200 @@ -0,0 +1,253 @@ +From: Avram Lubkin <[email protected]> +Date: Thu, 4 Dec 2025 11:55:11 -0500 +Subject: Update wrap() logic and add optional testing for Python 3.15 (#311) + +* Add optional testing for Python 3.15 + +* Ignore deprecation warnings for pty.fork() + +* Update wrap logic to match 3.15 + +* Add test for placeholder in wrap() + +* Ignore deprecation warnings for pty_test() +Origin: upstream, https://github.com/jquast/blessed/commit/cee680ff7fb3ad31f42ae98582ba74629f1fd6b0 +--- + blessed/sequences.py | 78 +++++++++++++++++++++++++++++++++++++------- + tests/accessories.py | 12 ++++--- + tests/test_wrap.py | 55 +++++++++++++++++++++++++++++++ + 3 files changed, 129 insertions(+), 16 deletions(-) + +--- a/blessed/sequences.py ++++ b/blessed/sequences.py +@@ -155,6 +155,7 @@ class SequenceTextWrapper(textwrap.TextW + self.term = term + textwrap.TextWrapper.__init__(self, width, **kwargs) + ++ # pylint: disable-next=too-complex,too-many-branches + def _wrap_chunks(self, chunks): # type: ignore[no-untyped-def] + """ + Sequence-aware variant of :meth:`textwrap.TextWrapper._wrap_chunks`. +@@ -174,29 +175,82 @@ class SequenceTextWrapper(textwrap.TextW + f"invalid width {self.width!r}({type(self.width)!r}) (must be integer > 0)" + ) + ++ if self.max_lines is not None: ++ indent = self.subsequent_indent if self.max_lines > 1 else self.initial_indent ++ if len(indent) + len(self.placeholder.lstrip()) > self.width: ++ raise ValueError("placeholder too large for max width") ++ + term = self.term +- drop_whitespace = not hasattr(self, 'drop_whitespace' +- ) or self.drop_whitespace ++ ++ # Arrange in reverse order so items can be efficiently popped from a stack of chucks. + chunks.reverse() +- while chunks: +- cur_line: List[str] = [] +- cur_len = 0 ++ while chunks: # pylint: disable=too-many-nested-blocks ++ ++ cur_line = [] # Current line. ++ cur_len = 0 # Length of all the chunks in cur_line ++ ++ # Figure out which static string will prefix this line. + indent = self.subsequent_indent if lines else self.initial_indent ++ # Maximum width for this line. + width = self.width - len(indent) +- if drop_whitespace and lines and not Sequence(chunks[-1], term).strip(): ++ ++ # First chunk on line is whitespace -- drop it, unless this ++ # is the very beginning of the text (ie. no lines started yet). ++ if self.drop_whitespace and lines and not Sequence(chunks[-1], term).strip(): + del chunks[-1] ++ + while chunks: + chunk_len = Sequence(chunks[-1], term).length() + if cur_len + chunk_len > width: +- if chunk_len > width: +- self._handle_long_word(chunks, cur_line, cur_len, width) + break ++ + cur_line.append(chunks.pop()) + cur_len += chunk_len +- if drop_whitespace and (cur_line and not Sequence(cur_line[-1], term).strip()): +- del cur_line[-1] ++ ++ # The current line is full, and the next chunk is too big to fit on *any* line ++ if chunks and Sequence(chunks[-1], term).length() > width: ++ self._handle_long_word(chunks, cur_line, cur_len, width) ++ cur_len = sum(Sequence(chunk, term).length() for chunk in cur_line) ++ ++ # If the last chunk on this line is all whitespace, drop it. ++ if self.drop_whitespace and cur_line: ++ chunk = Sequence(cur_line[-1], term) ++ if not chunk.strip(): ++ cur_len -= chunk.length() ++ del cur_line[-1] ++ + if cur_line: +- lines.append(f'{indent}{"".join(cur_line)}') ++ if ( # pylint: disable=too-many-boolean-expressions ++ self.max_lines is None ++ or len(lines) + 1 < self.max_lines ++ or ( ++ not chunks ++ or self.drop_whitespace ++ and len(chunks) == 1 ++ and not chunks[0].strip() ++ ) ++ and cur_len <= width ++ ): ++ lines.append(indent + ''.join(cur_line)) ++ ++ else: ++ while cur_line: ++ chunk = Sequence(cur_line[-1], term) ++ if (chunk.strip() and cur_len + len(self.placeholder) <= width): ++ cur_line.append(self.placeholder) ++ lines.append(indent + ''.join(cur_line)) ++ break ++ cur_len -= chunk.length() ++ del cur_line[-1] ++ else: ++ if lines: ++ prev_line = lines[-1].rstrip() ++ if len(prev_line) + len(self.placeholder) <= self.width: ++ lines[-1] = prev_line + self.placeholder ++ break ++ lines.append(indent + self.placeholder.lstrip()) ++ break ++ + return lines + + def _handle_long_word(self, # type: ignore[no-untyped-def] +@@ -223,7 +277,7 @@ class SequenceTextWrapper(textwrap.TextW + # If we're allowed to break long words, then do so: put as much + # of the next chunk onto the current line as will fit. + +- if self.break_long_words: ++ if self.break_long_words and space_left > 0: + term = self.term + chunk = reversed_chunks[-1] + idx = nxt = seq_length = 0 +--- a/tests/accessories.py ++++ b/tests/accessories.py +@@ -8,6 +8,7 @@ import traceback + import contextlib + import time + import signal ++import warnings + + # local + from blessed import Terminal +@@ -92,7 +93,10 @@ class as_subprocess(): # pylint: disabl + return + + pid_testrunner = os.getpid() +- pid, master_fd = pty.fork() # pylint: disable=possibly-used-before-assignment ++ with warnings.catch_warnings(): ++ warnings.filterwarnings("ignore", category=DeprecationWarning) ++ pid, master_fd = pty.fork() # pylint: disable=possibly-used-before-assignment ++ + if pid == self._CHILD_PID: + # child process executes function, raises exception + # if failed, causing a non-zero exit code, using the +@@ -321,12 +325,12 @@ def pty_test(child_func, parent_func=Non + result = child_func(term) + return result.decode('utf-8') if isinstance(result, bytes) else (result or '') + +- import pty as pty_module # pylint: disable=import-outside-toplevel +- + if test_name is None: + test_name = getattr(child_func, '__name__', 'pty_test') + +- pid, master_fd = pty_module.fork() ++ with warnings.catch_warnings(): ++ warnings.filterwarnings("ignore", category=DeprecationWarning) ++ pid, master_fd = pty.fork() # pylint: disable=possibly-used-before-assignment + + # Set PTY window size in parent before child starts reading + if pid != 0: +--- a/tests/test_wrap.py ++++ b/tests/test_wrap.py +@@ -1,6 +1,7 @@ + """Tests for Terminal.wrap()""" + + # std imports ++import sys + import textwrap + + # 3rd party +@@ -19,6 +20,10 @@ TEXTWRAP_KEYWORD_COMBINATIONS = [ + {'break_long_words': True, 'drop_whitespace': True, 'subsequent_indent': ''}, + {'break_long_words': True, 'drop_whitespace': False, 'subsequent_indent': ' '}, + {'break_long_words': True, 'drop_whitespace': True, 'subsequent_indent': ' '}, ++ { ++ 'break_long_words': True, 'drop_whitespace': False, ++ 'subsequent_indent': '', 'max_lines': 4, 'placeholder': '~', ++ }, + ] + if TEST_QUICK: + # test only one feature: everything on +@@ -71,6 +76,18 @@ def test_SequenceWrapper(many_columns, k + my_wrapped = term.wrap(pgraph, width=width, **kwargs) + my_wrapped_colored = term.wrap(pgraph_colored, width=width, **kwargs) + ++ # Older versions of textwrap could leave a preceding all whitespace line ++ # https://github.com/python/cpython/issues/140627 ++ if ( ++ kwargs.get('drop_whitespace') and ++ sys.version_info[:2] < (3, 15) and ++ not internal_wrapped[0].strip() ++ ): ++ internal_wrapped = internal_wrapped[1:] ++ # # This also means any subsequent indent got applied to the first line ++ if kwargs.get('subsequent_indent'): ++ internal_wrapped[0] = internal_wrapped[0][len(kwargs['subsequent_indent']):] ++ + # ensure we textwrap ascii the same as python + assert internal_wrapped == my_wrapped + +@@ -179,3 +196,41 @@ def test_greedy_join_with_cojoining(): + assert result == ['ca', 'fe\u0301', '-l', 'at', 'te'] + + child() ++ ++ ++def test_placeholder(): ++ """ENsure placeholder behavior matches stdlib""" ++ ++ @as_subprocess ++ def child(): ++ term = TestTerminal() ++ text = 'The quick brown fox jumps over the lazy dog' ++ kwargs = {'width': 1, 'max_lines': 3, 'placeholder': '...'} ++ ++ try: ++ textwrap.wrap(text, **kwargs) ++ except Exception as e: # pylint: disable=broad-exception-caught ++ stdlib_exc = e ++ else: ++ stdlib_exc = None ++ ++ with pytest.raises(stdlib_exc.__class__) as exc: ++ term.wrap(text, **kwargs) ++ assert exc.value.args == stdlib_exc.args ++ ++ kwargs = {'width': 10, 'max_lines': 3, 'placeholder': '...'} ++ assert term.wrap(text, **kwargs) == textwrap.wrap(text, **kwargs) ++ ++ text = '1234567890 1234567890 extra' ++ kwargs = {'width': 10, 'max_lines': 2, 'placeholder': '...'} ++ assert term.wrap(text, **kwargs) == textwrap.wrap(text, **kwargs) ++ ++ text = '1234567890 1234567890' ++ kwargs = {'width': 10, 'max_lines': 1, 'placeholder': '...'} ++ assert term.wrap(text, **kwargs) == textwrap.wrap(text, **kwargs) ++ ++ text = 'short 1234567890 extra' ++ kwargs = {'width': 10, 'max_lines': 2, 'placeholder': '...'} ++ assert term.wrap(text, **kwargs) == textwrap.wrap(text, **kwargs) ++ ++ child() diff -Nru python-blessed-1.25/debian/patches/series python-blessed-1.25/debian/patches/series --- python-blessed-1.25/debian/patches/series 2025-12-24 15:53:31.000000000 +0100 +++ python-blessed-1.25/debian/patches/series 2026-09-02 16:19:03.000000000 +0200 @@ -1 +1,2 @@ 0001-Get-rid-of-sphinxcontrib-manpage-needs-for-now.patch +0002-Update-wrap-logic-and-add-optional-testing-for-Python-3.15.patch diff -Nru python-blessed-1.25/debian/rules python-blessed-1.25/debian/rules --- python-blessed-1.25/debian/rules 2025-12-24 16:26:02.000000000 +0100 +++ python-blessed-1.25/debian/rules 2026-09-02 16:19:03.000000000 +0200 @@ -14,5 +14,5 @@ override_dh_auto_test: ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) - PYBUILD_SYSTEM=custom PYBUILD_TEST_ARGS="{interpreter} -m pytest -v -x -rs -k 'not SequenceWrapper'" dh_auto_test + PYBUILD_SYSTEM=custom PYBUILD_TEST_ARGS="{interpreter} -m pytest -v -x -rs tests" dh_auto_test endif diff -Nru python-blessed-1.25/debian/tests/python3-blessed python-blessed-1.25/debian/tests/python3-blessed --- python-blessed-1.25/debian/tests/python3-blessed 2025-12-24 16:26:02.000000000 +0100 +++ python-blessed-1.25/debian/tests/python3-blessed 2026-09-02 16:19:03.000000000 +0200 @@ -7,5 +7,5 @@ ln -s /usr/lib/python3/dist-packages/blessed blessed for python3 in $python3_versions; do - $python3 -m pytest -v -x -rs -k 'not SequenceWrapper' || exit 1 + $python3 -m pytest -v -x -rs tests || exit 1 done

