--- Begin Message ---
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
--- End Message ---