Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-curtsies for openSUSE:Factory 
checked in at 2022-09-12 19:08:22
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-curtsies (Old)
 and      /work/SRC/openSUSE:Factory/.python-curtsies.new.2083 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-curtsies"

Mon Sep 12 19:08:22 2022 rev:13 rq:1002735 version:0.4.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-curtsies/python-curtsies.changes  
2022-03-28 17:01:39.533058849 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-curtsies.new.2083/python-curtsies.changes    
    2022-09-12 19:08:23.658560968 +0200
@@ -1,0 +2,13 @@
+Sat Sep 10 15:48:54 UTC 2022 - Arun Persaud <a...@gmx.de>
+
+- specfile:
+  * skip python 3.6
+  * require python-blessed
+
+- update to version 0.4.0:
+  * Clean up both wakeup_fds
+  * Drop support for Python 3.6
+  * Switch to blessed
+  * Typing: add more annotations
+
+-------------------------------------------------------------------

Old:
----
  curtsies-0.3.10.tar.gz

New:
----
  curtsies-0.4.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-curtsies.spec ++++++
--- /var/tmp/diff_new_pack.2tlE16/_old  2022-09-12 19:08:24.114562250 +0200
+++ /var/tmp/diff_new_pack.2tlE16/_new  2022-09-12 19:08:24.118562261 +0200
@@ -18,22 +18,23 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %define         skip_python2 1
+%define         skip_python36 1
 Name:           python-curtsies
-Version:        0.3.10
+Version:        0.4.0
 Release:        0
 Summary:        Curses-like terminal wrapper, with colored strings!
 License:        MIT
 Group:          Development/Languages/Python
 URL:            https://github.com/bpython/curtsies
 Source:         
https://files.pythonhosted.org/packages/source/c/curtsies/curtsies-%{version}.tar.gz
-BuildRequires:  %{python_module blessings}
+BuildRequires:  %{python_module blessed >= 1.5}
 BuildRequires:  %{python_module cwcwidth}
 BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module pyte}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-blessings
+Requires:       python-blessed >= 1.5
 Requires:       python-cwcwidth
 BuildArch:      noarch
 %python_subpackages

++++++ curtsies-0.3.10.tar.gz -> curtsies-0.4.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/PKG-INFO new/curtsies-0.4.0/PKG-INFO
--- old/curtsies-0.3.10/PKG-INFO        2021-10-09 04:42:20.741536100 +0200
+++ new/curtsies-0.4.0/PKG-INFO 2022-08-28 22:39:27.821959300 +0200
@@ -1,12 +1,11 @@
 Metadata-Version: 2.1
 Name: curtsies
-Version: 0.3.10
+Version: 0.4.0
 Summary: Curses-like terminal wrapper, with colored strings!
 Home-page: https://github.com/bpython/curtsies
 Author: Thomas Ballinger
 Author-email: thomasballin...@gmail.com
 License: MIT
-Platform: UNKNOWN
 Classifier: Development Status :: 3 - Alpha
 Classifier: Environment :: Console
 Classifier: Intended Audience :: Developers
@@ -14,7 +13,7 @@
 Classifier: Operating System :: POSIX
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Requires-Python: >=3.6
+Requires-Python: >=3.7
 Description-Content-Type: text/markdown
 License-File: LICENSE
 
@@ -109,5 +108,3 @@
 * Thanks to the many contributors!
 * If all you need are colored strings, consider one of these [other
   
libraries](http://curtsies.readthedocs.io/en/latest/FmtStr.html#fmtstr-rationale)!
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies/__init__.py 
new/curtsies-0.4.0/curtsies/__init__.py
--- old/curtsies-0.3.10/curtsies/__init__.py    2021-10-09 04:39:20.000000000 
+0200
+++ new/curtsies-0.4.0/curtsies/__init__.py     2022-08-28 22:39:24.000000000 
+0200
@@ -1,5 +1,5 @@
 """Terminal-formatted strings"""
-__version__ = "0.3.10"
+__version__ = "0.4.0"
 
 from .window import FullscreenWindow, CursorAwareWindow
 from .input import Input
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies/curtsieskeys.py 
new/curtsies-0.4.0/curtsies/curtsieskeys.py
--- old/curtsies-0.3.10/curtsies/curtsieskeys.py        2021-09-25 
20:39:51.000000000 +0200
+++ new/curtsies-0.4.0/curtsies/curtsieskeys.py 2022-08-28 22:39:24.000000000 
+0200
@@ -92,6 +92,10 @@
     b"\x1b[3~": '<DELETE>',       # delete (.), "Execute"
     b"\x1b[3;5~": '<Ctrl-DELETE>',
 
+    # st (simple terminal) see issue #169
+    b"\x1b[4h": '<INSERT>',
+    b"\x1b[P": '<DELETE>',
+
     # not fixing for back compat.
     # (b"\x1b[4~": u'<SELECT>',       # select
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies/events.py 
new/curtsies-0.4.0/curtsies/events.py
--- old/curtsies-0.3.10/curtsies/events.py      2021-10-08 23:51:39.000000000 
+0200
+++ new/curtsies-0.4.0/curtsies/events.py       2022-08-28 22:39:24.000000000 
+0200
@@ -1,9 +1,9 @@
 """Events for keystrokes and other input events"""
 import codecs
-import encodings
 import itertools
 import sys
-from typing import Optional, List, Union
+from enum import Enum, auto
+from typing import Dict, Optional, List, Union
 
 from .termhelpers import Termmode
 from .curtsieskeys import CURTSIES_NAMES as special_curtsies_names
@@ -12,7 +12,7 @@
 chr_uni = chr
 
 
-CURTSIES_NAMES = {}
+CURTSIES_NAMES: Dict[bytes, str] = {}
 control_chars = {chr_byte(i): "<Ctrl-%s>" % chr(i + 0x60) for i in range(0x00, 
0x1B)}
 CURTSIES_NAMES.update(control_chars)
 for i in range(0x00, 0x80):
@@ -81,6 +81,12 @@
 )
 
 
+class Keynames(Enum):
+    CURTSIES = auto()
+    CURSES = auto()
+    BYTES = auto()
+
+
 class Event:
     pass
 
@@ -159,7 +165,10 @@
 
 
 def get_key(
-    bytes_: List[bytes], encoding: str, keynames: str = "curtsies", full: bool 
= False
+    bytes_: List[bytes],
+    encoding: str,
+    keynames: Keynames = Keynames.CURTSIES,
+    full: bool = False,
 ) -> Optional[str]:
     """Return key pressed from bytes_ or None
 
@@ -193,16 +202,14 @@
     (for 'asdf', first on 'a', then on 'as', then on 'asd' - until a non-None
     value is returned)
     """
-    if not all(isinstance(c, type(b"")) for c in bytes_):
-        raise ValueError("get key expects bytes, got %r" % bytes_)  # expects 
raw bytes
-    if keynames not in ["curtsies", "curses", "bytes"]:
-        raise ValueError("keynames must be one of 'curtsies', 'curses' or 
'bytes'")
+    if not all(isinstance(c, bytes) for c in bytes_):
+        raise TypeError("get key expects bytes, got %r" % bytes_)  # expects 
raw bytes
     seq = b"".join(bytes_)
     if len(seq) > MAX_KEYPRESS_SIZE:
         raise ValueError("unable to decode bytes %r" % seq)
 
     def key_name() -> str:
-        if keynames == "curses":
+        if keynames == Keynames.CURSES:
             # may not be here (and still not decodable) curses names incomplete
             if seq in CURSES_NAMES:
                 return CURSES_NAMES[seq]
@@ -224,14 +231,14 @@
                         "x%02X" % ord(seq[i : i + 1]) for i in range(len(seq))
                     )
                     # TODO if this isn't possible, return multiple meta keys 
as a paste event if paste events enabled
-        elif keynames == "curtsies":
+        elif keynames == Keynames.CURTSIES:
             if seq in CURTSIES_NAMES:
                 return CURTSIES_NAMES[seq]
             return seq.decode(
                 encoding
             )  # assumes that curtsies names are a subset of curses ones
         else:
-            assert keynames == "bytes"
+            assert keynames == Keynames.BYTES
             return seq  # type: ignore
 
     key_known = seq in CURTSIES_NAMES or seq in CURSES_NAMES or decodable(seq, 
encoding)
@@ -306,9 +313,6 @@
     print(
         "press a bunch of keys (not at the same time, but you can hit them 
pretty quickly)"
     )
-    import tty
-    import termios
-    import fcntl
     import os
     from .termhelpers import Cbreak
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies/formatstring.py 
new/curtsies-0.4.0/curtsies/formatstring.py
--- old/curtsies-0.3.10/curtsies/formatstring.py        2021-10-07 
20:41:22.000000000 +0200
+++ new/curtsies-0.4.0/curtsies/formatstring.py 2022-08-28 22:39:24.000000000 
+0200
@@ -19,24 +19,23 @@
 red('hello')
 """
 
-import itertools
 import re
-import sys
 from cwcwidth import wcswidth, wcwidth
+from itertools import chain
 from typing import (
+    Any,
+    Callable,
+    Dict,
+    Iterable,
     Iterator,
-    Tuple,
     List,
-    Union,
-    Optional,
-    Any,
     Mapping,
-    cast,
     MutableMapping,
+    Optional,
+    Tuple,
+    Union,
+    cast,
     no_type_check,
-    Type,
-    Callable,
-    Iterable,
 )
 
 try:
@@ -76,8 +75,8 @@
 xforms.update(two_arg_xforms)
 
 
-class FrozenDict(dict):
-    """Immutable dictionary class"""
+class FrozenAttributes(Dict[str, Union[int, bool]]):
+    """Immutable dictionary class for format string attributes"""
 
     @no_type_check
     def __setitem__(self, key, value):
@@ -87,11 +86,11 @@
     def update(self, *args, **kwds):
         raise Exception("Cannot change value.")
 
-    def extend(self, dictlike: Mapping[str, Union[int, bool]]) -> "FrozenDict":
-        return FrozenDict(itertools.chain(self.items(), dictlike.items()))
+    def extend(self, dictlike: Mapping[str, Union[int, bool]]) -> 
"FrozenAttributes":
+        return FrozenAttributes(chain(self.items(), dictlike.items()))
 
-    def remove(self, *keys: str) -> "FrozenDict":
-        return FrozenDict((k, v) for k, v in self.items() if k not in keys)
+    def remove(self, *keys: str) -> "FrozenAttributes":
+        return FrozenAttributes((k, v) for k, v in self.items() if k not in 
keys)
 
 
 def stable_format_dict(d: Mapping) -> str:
@@ -121,14 +120,14 @@
         if not isinstance(string, str):
             raise ValueError("unicode string required, got %r" % string)
         self._s = string
-        self._atts = FrozenDict(atts if atts else {})
+        self._atts = FrozenAttributes(atts if atts else {})
 
     @property
     def s(self) -> str:
         return self._s
 
     @property
-    def atts(self) -> Mapping[str, Union[int, bool]]:
+    def atts(self) -> FrozenAttributes:
         "Attributes, e.g. {'fg': 34, 'bold': True} where 34 is the escape code 
for ..."
         return self._atts
 
@@ -137,7 +136,7 @@
 
     @property
     def width(self) -> int:
-        width = wcswidth(self._s, None)
+        width = wcswidth(self._s)
         if len(self._s) > 0 and width < 1:
             raise ValueError("Can't calculate width of string %r" % self._s)
         return width
@@ -145,8 +144,8 @@
     @cached_property
     def color_str(self) -> str:
         "Return an escape-coded string to write to the terminal."
-        s = self.s
-        for k, v in sorted(self.atts.items()):
+        s = self._s
+        for k, v in sorted(self._atts.items()):
             # (self.atts sorted for the sake of always acting the same.)
             if k not in xforms:
                 # Unsupported SGR code
@@ -156,6 +155,7 @@
             elif v is True:
                 s = one_arg_xforms[k](s)
             else:
+                # TODO: What's the purpose of this code? It will never be 
executed.
                 s = two_arg_xforms[k](s, v)
         return s
 
@@ -168,16 +168,16 @@
     def __eq__(self, other: Any) -> bool:
         if not isinstance(other, Chunk):
             return NotImplemented
-        return self.s == other.s and self.atts == other.atts
+        return self._s == other._s and self._atts == other._atts
 
     def __hash__(self) -> int:
-        return hash((self.s, self.atts))
+        return hash((self._s, self._atts))
 
     def __repr__(self) -> str:
         return "Chunk({s}{sep}{atts})".format(
-            s=repr(self.s),
-            sep=", " if self.atts else "",
-            atts=stable_format_dict(self.atts) if self.atts else "",
+            s=repr(self._s),
+            sep=", " if self._atts else "",
+            atts=stable_format_dict(self._atts) if self._atts else "",
         )
 
     def repr_part(self) -> str:
@@ -191,10 +191,10 @@
             else:
                 return att
 
-        atts_out = {k: v for (k, v) in self.atts.items() if v}
+        atts_out = {k: v for (k, v) in self._atts.items() if v}
         return (
             "".join(pp_att(att) + "(" for att in sorted(atts_out))
-            + repr(self.s)
+            + repr(self._s)
             + ")" * len(atts_out)
         )
 
@@ -416,8 +416,9 @@
     def copy_with_new_atts(self, **attributes: Union[bool, int]) -> "FmtStr":
         """Returns a new FmtStr with the same content but new formatting"""
 
-        result = FmtStr(*(Chunk(bfs.s, bfs.atts.extend(attributes)) for bfs in 
self.chunks))  # type: ignore
-        return result
+        return FmtStr(
+            *(Chunk(bfs.s, bfs.atts.extend(attributes)) for bfs in self.chunks)
+        )
 
     def join(self, iterable: Iterable[Union[str, "FmtStr"]]) -> "FmtStr":
         """Joins an iterable yielding strings or FmtStrs with self as 
separator"""
@@ -456,8 +457,8 @@
         return [
             self[start:end]
             for start, end in zip(
-                [0] + [m.end() for m in matches],
-                [m.start() for m in matches] + [len(s)],
+                chain((0,), (m.end() for m in matches)),
+                chain((m.start() for m in matches), (len(s),)),
             )
         ]
 
@@ -828,7 +829,7 @@
 
 
 def parse_args(
-    args: Tuple[Union[bytes, str], ...],
+    args: Tuple[str, ...],
     kwargs: MutableMapping[str, Union[int, bool, str]],
 ) -> Mapping[str, Union[int, bool]]:
     """Returns a kwargs dictionary by turning args into kwargs"""
@@ -836,9 +837,8 @@
         args += (cast(str, kwargs["style"]),)
         del kwargs["style"]
     for arg in args:
-        arg = cast(str, arg)
-        if not isinstance(arg, (bytes, str)):
-            raise ValueError("args must be strings:" + repr(args))
+        if not isinstance(arg, str):
+            raise ValueError(f"args must be strings: {arg!r}")
         if arg.lower() in FG_COLORS:
             if "fg" in kwargs:
                 raise ValueError("fg specified twice")
@@ -850,20 +850,20 @@
         elif arg.lower() in STYLES:
             kwargs[arg] = True
         else:
-            raise ValueError("couldn't process arg: " + repr(arg))
+            raise ValueError(f"couldn't process arg: {args!r}")
     for k in kwargs:
-        if k not in ["fg", "bg"] + list(STYLES.keys()):
+        if k not in ("fg", "bg") and k not in STYLES.keys():
             raise ValueError("Can't apply that transformation")
     if "fg" in kwargs:
         if kwargs["fg"] in FG_COLORS:
             kwargs["fg"] = FG_COLORS[cast(str, kwargs["fg"])]
         if kwargs["fg"] not in list(FG_COLORS.values()):
-            raise ValueError("Bad fg value: %r" % kwargs["fg"])
+            raise ValueError(f"Bad fg value: {kwargs['fg']!r}")
     if "bg" in kwargs:
         if kwargs["bg"] in BG_COLORS:
             kwargs["bg"] = BG_COLORS[cast(str, kwargs["bg"])]
         if kwargs["bg"] not in list(BG_COLORS.values()):
-            raise ValueError("Bad bg value: %r" % kwargs["bg"])
+            raise ValueError(f"Bad bg value: {kwargs['bg']!r}")
     return cast(MutableMapping[str, Union[int, bool]], kwargs)
 
 
@@ -877,14 +877,10 @@
     on_red(bold(blue('blarg')))
     """
     atts = parse_args(args, kwargs)
-    if isinstance(string, FmtStr):
-        pass
-    elif isinstance(string, (bytes, str)):
+    if isinstance(string, str):
         string = FmtStr.from_str(string)
-    else:
+    elif not isinstance(string, FmtStr):
         raise ValueError(
-            "Bad Args: {!r} (of type {}), {!r}, {!r}".format(
-                string, type(string), args, kwargs
-            )
+            f"Bad Args: {string!r} (of type {type(string)}), {args!r}, 
{kwargs!r}"
         )
     return string.copy_with_new_atts(**atts)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies/formatstringarray.py 
new/curtsies-0.4.0/curtsies/formatstringarray.py
--- old/curtsies-0.3.10/curtsies/formatstringarray.py   2021-09-25 
20:39:51.000000000 +0200
+++ new/curtsies-0.4.0/curtsies/formatstringarray.py    2022-08-28 
22:39:24.000000000 +0200
@@ -88,10 +88,7 @@
         if isinstance(slicetuple, slice):
             rowslice = normalize_slice(len(self.rows), slicetuple)
             return self.rows[rowslice]
-        (
-            row_slice_or_int,
-            col_slice_or_int,
-        ) = slicetuple  # type: Tuple[Union[int, slice], Union[int, slice]]
+        row_slice_or_int, col_slice_or_int = slicetuple
         rowslice = normalize_slice(len(self.rows), row_slice_or_int)
         colslice = normalize_slice(self.num_columns, col_slice_or_int)
         # TODO clean up slices
@@ -122,7 +119,7 @@
         logger.debug("slice: %r", slicetuple)
         if isinstance(slicetuple, slice):
             rowslice, colslice = slicetuple, slice(None)
-            if isinstance(value, (bytes, str)):
+            if isinstance(value, str):
                 raise ValueError(
                     "if slice is 2D, value must be 2D as in of list type []"
                 )
@@ -251,7 +248,9 @@
         )
 
 
-def fsarray(strings: List[Union[FmtStr, str]], *args: Any, **kwargs: Any) -> 
FSArray:
+def fsarray(
+    strings: Sequence[Union[FmtStr, str]], *args: Any, **kwargs: Any
+) -> FSArray:
     """fsarray(list_of_FmtStrs_or_strings, width=None) -> FSArray
 
     Returns a new FSArray of width of the maximum size of the provided
@@ -279,7 +278,7 @@
     return arr
 
 
-def simple_format(x: Union[FSArray, List[FmtStr]]) -> str:
+def simple_format(x: Union[FSArray, Sequence[FmtStr]]) -> str:
     return "\n".join(str(l) for l in x)
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies/input.py 
new/curtsies-0.4.0/curtsies/input.py
--- old/curtsies-0.3.10/curtsies/input.py       2021-10-09 03:40:58.000000000 
+0200
+++ new/curtsies-0.4.0/curtsies/input.py        2022-08-28 22:39:24.000000000 
+0200
@@ -12,7 +12,18 @@
 from .termhelpers import Nonblocking
 from . import events
 
-from typing import Callable, Type, TextIO, Optional, List, Union, cast, Tuple, 
Any
+from typing import (
+    Callable,
+    ContextManager,
+    Type,
+    TextIO,
+    Optional,
+    List,
+    Union,
+    cast,
+    Tuple,
+    Any,
+)
 from types import TracebackType, FrameType
 
 
@@ -27,7 +38,7 @@
     return threading.current_thread() == threading.main_thread()
 
 
-class ReplacedSigIntHandler:
+class ReplacedSigIntHandler(ContextManager):
     def __init__(self, handler: Callable) -> None:
         self.handler = handler
 
@@ -43,13 +54,13 @@
         signal.signal(signal.SIGINT, self.orig_sigint_handler)
 
 
-class Input:
+class Input(ContextManager["Input"]):
     """Keypress and control event generator"""
 
     def __init__(
         self,
         in_stream: Optional[TextIO] = None,
-        keynames: str = "curtsies",
+        keynames: Union[events.Keynames, str] = events.Keynames.CURTSIES,
         paste_threshold: Optional[int] = events.MAX_KEYPRESS_SIZE + 1,
         sigint_event: bool = False,
         disable_terminal_start_stop: bool = False,
@@ -73,11 +84,24 @@
             in_stream = sys.__stdin__
         self.in_stream = in_stream
         self.unprocessed_bytes: List[bytes] = []  # leftover from stdin, 
unprocessed yet
-        self.keynames = keynames
+        if isinstance(keynames, str):
+            # TODO: Remove this block with the next API breaking release.
+            if keynames == "curtsies":
+                self.keynames = events.Keynames.CURTSIES
+            elif keynames == "curses":
+                self.keynames = events.Keynames.CURSES
+            elif keynames == "bytes":
+                self.keynames = events.Keynames.BYTES
+            else:
+                raise ValueError("keyname is invalid")
+        else:
+            self.keynames = keynames
         self.paste_threshold = paste_threshold
         self.sigint_event = sigint_event
         self.disable_terminal_start_stop = disable_terminal_start_stop
         self.sigints: List[events.SigIntEvent] = []
+        self.wakeup_read_fd: Optional[int] = None
+        self.wakeup_write_fd: Optional[int] = None
 
         self.readers: List[int] = []
         self.queued_interrupting_events: List[Union[events.Event, str]] = []
@@ -110,11 +134,11 @@
             self.orig_sigint_handler = signal.getsignal(signal.SIGINT)
             signal.signal(signal.SIGINT, self.sigint_handler)
 
-        self.wakeup_read_fd, wfd = os.pipe()
-        os.set_blocking(wfd, False)
-        if sys.version_info[0] == 3 and 5 <= sys.version_info[1] < 7:
-            signal.set_wakeup_fd(wfd)
-        elif sys.version_info[0] == 3 and 7 <= sys.version_info[1]:
+        # Non-main threads don't receive signals
+        if is_main_thread():
+            self.wakeup_read_fd, self.wakeup_write_fd = os.pipe()
+            wfd = self.wakeup_write_fd
+            os.set_blocking(wfd, False)
             signal.set_wakeup_fd(wfd, warn_on_full_buffer=False)
 
         return self
@@ -131,12 +155,16 @@
             and self.orig_sigint_handler is not None
         ):
             signal.signal(signal.SIGINT, self.orig_sigint_handler)
-        if sys.version_info[0] == 3 and sys.version_info[1] > 4:
+        if is_main_thread():
             signal.set_wakeup_fd(-1)
+            if self.wakeup_read_fd is not None:
+                os.close(self.wakeup_read_fd)
+            if self.wakeup_write_fd is not None:
+                os.close(self.wakeup_write_fd)
         termios.tcsetattr(self.in_stream, termios.TCSANOW, self.original_stty)
 
     def sigint_handler(
-        self, signum: Union[signal.Signals, int], frame: FrameType
+        self, signum: Union[signal.Signals, int], frame: Optional[FrameType]
     ) -> None:
         self.sigints.append(events.SigIntEvent())
 
@@ -168,10 +196,8 @@
         while True:
             try:
                 (rs, _, _) = select.select(
-                    [
-                        self.in_stream.fileno(),
-                        self.wakeup_read_fd,
-                    ]
+                    [self.in_stream.fileno()]
+                    + ([] if self.wakeup_read_fd is None else 
[self.wakeup_read_fd])
                     + self.readers,
                     [],
                     [],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies/termhelpers.py 
new/curtsies-0.4.0/curtsies/termhelpers.py
--- old/curtsies-0.3.10/curtsies/termhelpers.py 2021-09-25 20:39:51.000000000 
+0200
+++ new/curtsies-0.4.0/curtsies/termhelpers.py  2022-08-28 22:39:24.000000000 
+0200
@@ -3,13 +3,13 @@
 import fcntl
 import os
 
-from typing import IO, Type, List, Union, Optional
+from typing import IO, ContextManager, Type, List, Union, Optional
 from types import TracebackType
 
 _Attr = List[Union[int, List[Union[bytes, int]]]]
 
 
-class Nonblocking:
+class Nonblocking(ContextManager):
     """
     A context manager for making an input stream nonblocking.
     """
@@ -31,7 +31,7 @@
         fcntl.fcntl(self.fd, fcntl.F_SETFL, self.orig_fl)
 
 
-class Termmode:
+class Termmode(ContextManager):
     def __init__(self, stream: IO, attrs: _Attr) -> None:
         self.stream = stream
         self.attrs = attrs
@@ -49,7 +49,7 @@
         termios.tcsetattr(self.stream, termios.TCSANOW, self.original_stty)
 
 
-class Cbreak:
+class Cbreak(ContextManager[Termmode]):
     def __init__(self, stream: IO) -> None:
         self.stream = stream
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies/window.py 
new/curtsies-0.4.0/curtsies/window.py
--- old/curtsies-0.3.10/curtsies/window.py      2021-09-25 20:39:51.000000000 
+0200
+++ new/curtsies-0.4.0/curtsies/window.py       2022-08-28 22:39:24.000000000 
+0200
@@ -1,11 +1,13 @@
 # All windows write only unicode to the terminal -
-# that's what blessings does, so we match it.
+# that's what blessed does, so we match it.
 
 
 from typing import (
+    ContextManager,
     Optional,
     IO,
     Dict,
+    Sequence,
     TypeVar,
     Type,
     Tuple,
@@ -21,7 +23,7 @@
 import re
 import sys
 
-import blessings
+import blessed
 
 from .formatstring import fmtstr, FmtStr
 from .formatstringarray import FSArray
@@ -29,21 +31,18 @@
 
 logger = logging.getLogger(__name__)
 
-SCROLL_DOWN = "\x1bD"
-FIRST_COLUMN = "\x1b[1G"
-
 
 T = TypeVar("T", bound="BaseWindow")
 
 
-class BaseWindow:
+class BaseWindow(ContextManager):
     def __init__(
         self, out_stream: Optional[IO] = None, hide_cursor: bool = True
     ) -> None:
         logger.debug("-------initializing Window object %r------" % self)
         if out_stream is None:
             out_stream = sys.__stdout__
-        self.t = blessings.Terminal(stream=out_stream, force_styling=True)
+        self.t = blessed.Terminal(stream=out_stream, force_styling=True)
         self.out_stream = out_stream
         self.hide_cursor = hide_cursor
         self._last_lines_by_row: Dict[int, Optional[FmtStr]] = {}
@@ -55,7 +54,7 @@
 
         # since scroll-down only moves the screen if cursor is at bottom
         with self.t.location(x=0, y=1000000):
-            self.write(SCROLL_DOWN)  # TODO will blessings do this?
+            self.write(self.t.move_down)
 
     def write(self, msg: str) -> None:
         self.out_stream.write(msg)
@@ -129,7 +128,7 @@
         return for_stdout
 
 
-class FullscreenWindow(BaseWindow):
+class FullscreenWindow(BaseWindow, ContextManager["FullscreenWindow"]):
     """2D-text rendering window that disappears when its context is left
 
     FullscreenWindow will only render arrays the size of the terminal
@@ -199,12 +198,9 @@
             self.on_terminal_size_change(height, width)
 
         current_lines_by_row: Dict[int, Optional[FmtStr]] = {}
-        rows = list(range(height))
-        rows_for_use = rows[: len(array)]
-        rest_of_rows = rows[len(array) :]
 
         # rows which we have content for and don't require scrolling
-        for row, line in zip(rows_for_use, array):
+        for row, line in enumerate(array):
             current_lines_by_row[row] = line
             if line == self._last_lines_by_row.get(row, None):
                 continue
@@ -214,7 +210,7 @@
                 self.write(self.t.clear_eol)
 
         # rows onscreen that we don't have content for
-        for row in rest_of_rows:
+        for row in range(len(array), height):
             if self._last_lines_by_row and row not in self._last_lines_by_row:
                 continue
             self.write(self.t.move(row, 0))
@@ -230,7 +226,7 @@
             self.write(self.t.normal_cursor)
 
 
-class CursorAwareWindow(BaseWindow):
+class CursorAwareWindow(BaseWindow, ContextManager["CursorAwareWindow"]):
     """
     Renders to the normal terminal screen and
     can find the location of the cursor.
@@ -269,10 +265,16 @@
         if in_stream is None:
             in_stream = sys.__stdin__
         self.in_stream = in_stream
+        # whether we can use blessed to handle some operations
+        self._use_blessed = (
+            self.out_stream == sys.__stdout__ and self.in_stream == 
sys.__stdin__
+        )
         self._last_cursor_column: Optional[int] = None
         self._last_cursor_row: Optional[int] = None
         self.keep_last_line = keep_last_line
-        self.cbreak = Cbreak(self.in_stream)
+        self.cbreak = (
+            Cbreak(self.in_stream) if not self._use_blessed else 
self.t.cbreak()
+        )
         self.extra_bytes_callback = extra_bytes_callback
 
         # whether another SIGWINCH is queued up
@@ -296,9 +298,9 @@
     ) -> None:
         if self.keep_last_line:
             # just moves cursor down if not on last line
-            self.write(SCROLL_DOWN)
+            self.write(self.t.move_down)
 
-        self.write(FIRST_COLUMN)
+        self.write(self.t.move_x(0))
         self.write(self.t.clear_eos)
         self.write(self.t.clear_eol)
         self.cbreak.__exit__(type, value, traceback)
@@ -307,7 +309,11 @@
     def get_cursor_position(self) -> Tuple[int, int]:
         """Returns the terminal (row, column) of the cursor
 
-        0-indexed, like blessings cursor positions"""
+        0-indexed, like blessed cursor positions"""
+
+        if self._use_blessed:
+            return self.t.get_location()
+
         # TODO would this be cleaner as a parameter?
         in_stream = self.in_stream
 
@@ -419,7 +425,9 @@
         return cursor_dy
 
     def render_to_terminal(
-        self, array: Union[FSArray, List[FmtStr]], cursor_pos: Tuple[int, int] 
= (0, 0)
+        self,
+        array: Union[FSArray, Sequence[FmtStr]],
+        cursor_pos: Tuple[int, int] = (0, 0),
     ) -> int:
         """Renders array to terminal, returns the number of lines scrolled 
offscreen
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies.egg-info/PKG-INFO 
new/curtsies-0.4.0/curtsies.egg-info/PKG-INFO
--- old/curtsies-0.3.10/curtsies.egg-info/PKG-INFO      2021-10-09 
04:42:20.000000000 +0200
+++ new/curtsies-0.4.0/curtsies.egg-info/PKG-INFO       2022-08-28 
22:39:27.000000000 +0200
@@ -1,12 +1,11 @@
 Metadata-Version: 2.1
 Name: curtsies
-Version: 0.3.10
+Version: 0.4.0
 Summary: Curses-like terminal wrapper, with colored strings!
 Home-page: https://github.com/bpython/curtsies
 Author: Thomas Ballinger
 Author-email: thomasballin...@gmail.com
 License: MIT
-Platform: UNKNOWN
 Classifier: Development Status :: 3 - Alpha
 Classifier: Environment :: Console
 Classifier: Intended Audience :: Developers
@@ -14,7 +13,7 @@
 Classifier: Operating System :: POSIX
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-Requires-Python: >=3.6
+Requires-Python: >=3.7
 Description-Content-Type: text/markdown
 License-File: LICENSE
 
@@ -109,5 +108,3 @@
 * Thanks to the many contributors!
 * If all you need are colored strings, consider one of these [other
   
libraries](http://curtsies.readthedocs.io/en/latest/FmtStr.html#fmtstr-rationale)!
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies.egg-info/SOURCES.txt 
new/curtsies-0.4.0/curtsies.egg-info/SOURCES.txt
--- old/curtsies-0.3.10/curtsies.egg-info/SOURCES.txt   2021-10-09 
04:42:20.000000000 +0200
+++ new/curtsies-0.4.0/curtsies.egg-info/SOURCES.txt    2022-08-28 
22:39:27.000000000 +0200
@@ -2,7 +2,6 @@
 MANIFEST.in
 README.md
 pyproject.toml
-readme.md
 setup.cfg
 setup.py
 curtsies/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/curtsies.egg-info/requires.txt 
new/curtsies-0.4.0/curtsies.egg-info/requires.txt
--- old/curtsies-0.3.10/curtsies.egg-info/requires.txt  2021-10-09 
04:42:20.000000000 +0200
+++ new/curtsies-0.4.0/curtsies.egg-info/requires.txt   2022-08-28 
22:39:27.000000000 +0200
@@ -1,4 +1,4 @@
-blessings>=1.5
+blessed>=1.5
 cwcwidth
 
 [:python_version < "3.8"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/setup.cfg 
new/curtsies-0.4.0/setup.cfg
--- old/curtsies-0.3.10/setup.cfg       2021-10-09 04:42:20.743643000 +0200
+++ new/curtsies-0.4.0/setup.cfg        2022-08-28 22:39:27.821959300 +0200
@@ -18,11 +18,11 @@
        Programming Language :: Python :: 3
 
 [options]
-python_requires = >=3.6
+python_requires = >=3.7
 zip_safe = False
 packages = curtsies
 install_requires = 
-       blessings>=1.5
+       blessed>=1.5
        cwcwidth
        backports.cached-property; python_version < "3.8"
 tests_require = 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/tests/test_events.py 
new/curtsies-0.4.0/tests/test_events.py
--- old/curtsies-0.3.10/tests/test_events.py    2021-09-25 20:39:51.000000000 
+0200
+++ new/curtsies-0.4.0/tests/test_events.py     2022-08-28 22:39:24.000000000 
+0200
@@ -40,25 +40,34 @@
 class TestGetKey(unittest.TestCase):
     def test_utf8_full(self):
         get_utf_full = partial(
-            events.get_key, encoding="utf-8", keynames="curtsies", full=True
+            events.get_key,
+            encoding="utf-8",
+            keynames=events.Keynames.CURTSIES,
+            full=True,
         )
         self.assertEqual(get_utf_full([b"h"]), "h")
         self.assertEqual(get_utf_full([b"\x1b", b"["]), "<Esc+[>")
         self.assertRaises(UnicodeDecodeError, get_utf_full, [b"\xfe\xfe"])
-        self.assertRaises(ValueError, get_utf_full, "a")
+        self.assertRaises(TypeError, get_utf_full, "a")
 
     def test_utf8(self):
         get_utf = partial(
-            events.get_key, encoding="utf-8", keynames="curtsies", full=False
+            events.get_key,
+            encoding="utf-8",
+            keynames=events.Keynames.CURTSIES,
+            full=False,
         )
         self.assertEqual(get_utf([b"h"]), "h")
         self.assertEqual(get_utf([b"\x1b", b"["]), None)
         self.assertEqual(get_utf([b"\xe2"]), None)
-        self.assertRaises(ValueError, get_utf, "a")
+        self.assertRaises(TypeError, get_utf, "a")
 
     def test_multibyte_utf8(self):
         get_utf = partial(
-            events.get_key, encoding="utf-8", keynames="curtsies", full=False
+            events.get_key,
+            encoding="utf-8",
+            keynames=events.Keynames.CURTSIES,
+            full=False,
         )
         self.assertEqual(get_utf([b"\xc3"]), None)
         self.assertEqual(get_utf([b"\xe2"]), None)
@@ -70,10 +79,15 @@
 
     def test_sequences_without_names(self):
         get_utf = partial(
-            events.get_key, encoding="utf-8", keynames="curtsies", full=False
+            events.get_key,
+            encoding="utf-8",
+            keynames=events.Keynames.CURTSIES,
+            full=False,
         )
         self.assertEqual(get_utf([b"\xc3"], full=True), "<Meta-C>")
-        self.assertEqual(get_utf([b"\xc3"], full=True, keynames="curses"), 
"xC3")
+        self.assertEqual(
+            get_utf([b"\xc3"], full=True, keynames=events.Keynames.CURSES), 
"xC3"
+        )
 
     def test_key_names(self):
         "Every key sequence with a Curses name should have a Curtsies name 
too."
@@ -86,22 +100,33 @@
 class TestGetKeyAscii(unittest.TestCase):
     def test_full(self):
         get_ascii_full = partial(
-            events.get_key, encoding="ascii", keynames="curtsies", full=True
+            events.get_key,
+            encoding="ascii",
+            keynames=events.Keynames.CURTSIES,
+            full=True,
         )
         self.assertEqual(get_ascii_full([b"a"]), "a")
         self.assertEqual(get_ascii_full([b"\xe1"]), "<Meta-a>")
-        self.assertEqual(get_ascii_full([b"\xe1"], keynames="curses"), "xE1")
+        self.assertEqual(
+            get_ascii_full([b"\xe1"], keynames=events.Keynames.CURSES), "xE1"
+        )
 
     def test_simple(self):
-        get_ascii_full = partial(events.get_key, encoding="ascii", 
keynames="curtsies")
+        get_ascii_full = partial(
+            events.get_key, encoding="ascii", keynames=events.Keynames.CURTSIES
+        )
         self.assertEqual(get_ascii_full([b"a"]), "a")
         self.assertEqual(get_ascii_full([b"\xe1"]), "<Meta-a>")
-        self.assertEqual(get_ascii_full([b"\xe1"], keynames="curses"), "xE1")
+        self.assertEqual(
+            get_ascii_full([b"\xe1"], keynames=events.Keynames.CURSES), "xE1"
+        )
 
 
 class TestUnknownEncoding(unittest.TestCase):
     def test_simple(self):
-        get_utf16 = partial(events.get_key, encoding="utf16", 
keynames="curtsies")
+        get_utf16 = partial(
+            events.get_key, encoding="utf16", keynames=events.Keynames.CURTSIES
+        )
         self.assertEqual(get_utf16([b"a"]), None)
         self.assertEqual(get_utf16([b"a"], full=True), None)
         self.assertEqual(get_utf16([b"\xe1"]), None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/tests/test_fmtstr.py 
new/curtsies-0.4.0/tests/test_fmtstr.py
--- old/curtsies-0.3.10/tests/test_fmtstr.py    2021-09-25 20:39:51.000000000 
+0200
+++ new/curtsies-0.4.0/tests/test_fmtstr.py     2022-08-28 22:39:24.000000000 
+0200
@@ -20,18 +20,18 @@
     bold,
 )
 from curtsies.termformatconstants import FG_COLORS
-from curtsies.formatstringarray import fsarray, FSArray
+from curtsies.formatstringarray import fsarray, FSArray, simple_format
 
 from unittest import skip
 
 
-def repr_without_leading_u(s):
+def repr_without_leading_u(s: str) -> str:
     assert isinstance(s, str)
     return repr(s)
 
 
 class TestFmtStrInitialization(unittest.TestCase):
-    def test_bad(self):
+    def test_bad(self) -> None:
         # Can't specify fg or bg color two ways
         self.assertRaises(ValueError, fmtstr, "hello", "blue", {"fg": 30})
         self.assertRaises(ValueError, fmtstr, "hello", "on_blue", {"bg": 40})
@@ -41,15 +41,15 @@
         # Only existing xforms can be used in kwargs
         self.assertRaises(ValueError, fmtstr, "hello", "make it big")
 
-    def test_actual_init(self):
+    def test_actual_init(self) -> None:
         FmtStr()
 
 
 class TestFmtStrParsing(unittest.TestCase):
-    def test_no_escapes(self):
+    def test_no_escapes(self) -> None:
         self.assertEqual(str(fmtstr("abc")), "abc")
 
-    def test_simple_escapes(self):
+    def test_simple_escapes(self) -> None:
         self.assertEqual(str(fmtstr("\x1b[33mhello\x1b[0m")), 
"\x1b[33mhello\x1b[39m")
         self.assertEqual(str(fmtstr("\x1b[33mhello\x1b[39m")), 
"\x1b[33mhello\x1b[39m")
         self.assertEqual(str(fmtstr("\x1b[33mhello")), "\x1b[33mhello\x1b[39m")
@@ -68,13 +68,13 @@
             "\x1b[33m\x1b[43mhello\x1b[49m\x1b[39m",
         )
 
-    def test_out_of_order(self):
+    def test_out_of_order(self) -> None:
         self.assertEqual(
             str(fmtstr("\x1b[33m\x1b[43mhello\x1b[39m\x1b[49m")),
             "\x1b[33m\x1b[43mhello\x1b[49m\x1b[39m",
         )
 
-    def test_noncurtsies_output(self):
+    def test_noncurtsies_output(self) -> None:
         fmtstr("\x1b[35mx\x1b[m")
         self.assertEqual(fmtstr("\x1b[Ahello"), "hello")
         self.assertEqual(fmtstr("\x1b[20Ahello"), "hello")
@@ -82,7 +82,9 @@
 
 
 class TestImmutability(unittest.TestCase):
-    def 
test_fmt_strings_remain_unchanged_when_used_to_construct_other_ones(self):
+    def test_fmt_strings_remain_unchanged_when_used_to_construct_other_ones(
+        self,
+    ) -> None:
         a = fmtstr("hi", "blue")
         b = fmtstr("there", "red")
         c = a + b
@@ -90,7 +92,7 @@
         self.assertEqual(a.shared_atts["fg"], FG_COLORS["blue"])
         self.assertEqual(b.shared_atts["fg"], FG_COLORS["red"])
 
-    def test_immutibility_of_FmtStr(self):
+    def test_immutibility_of_FmtStr(self) -> None:
         a = fmtstr("hi", "blue")
         b = green(a)
         self.assertEqual(a.shared_atts["fg"], FG_COLORS["blue"])
@@ -98,11 +100,11 @@
 
 
 class TestFmtStrSplice(unittest.TestCase):
-    def test_simple_beginning_splice(self):
+    def test_simple_beginning_splice(self) -> None:
         self.assertEqual(fmtstr("abc").splice("d", 0), fmtstr("dabc"))
         self.assertEqual(fmtstr("abc").splice("d", 0), "d" + fmtstr("abc"))
 
-    def test_various_splices(self):
+    def test_various_splices(self) -> None:
         a = blue("hi")
         b = a + green("bye")
         c = b + red("!")
@@ -115,10 +117,10 @@
         )
         self.assertEqual(c.splice("asdfg", 1, 5), blue("h") + "asdfg" + 
red("!"))
 
-    def test_splice_of_empty_fmtstr(self):
+    def test_splice_of_empty_fmtstr(self) -> None:
         self.assertEqual(fmtstr("ab").splice("", 1), fmtstr("ab"))
 
-    def test_splice_with_multiple_chunks(self):
+    def test_splice_with_multiple_chunks(self) -> None:
         a = fmtstr("notion")
         b = a.splice("te", 2, 6)
         c = b.splice("de", 0)
@@ -128,7 +130,7 @@
         self.assertEqual(c.s, "denote")
         self.assertEqual(len(c.chunks), 3)
 
-    def test_splice_fmtstr_with_end_without_atts(self):
+    def test_splice_fmtstr_with_end_without_atts(self) -> None:
         a = fmtstr("notion")
         b = a.splice("te", 2, 6)
 
@@ -136,7 +138,7 @@
         self.assertEqual(b.s, "note")
         self.assertEqual(len(b.chunks), 2)
 
-    def test_splice_fmtstr_with_end_with_atts(self):
+    def test_splice_fmtstr_with_end_with_atts(self) -> None:
         # Need to test with fmtstr consisting of multiple chunks
         # and with attributes
         a = fmtstr("notion", "blue")
@@ -151,21 +153,21 @@
         self.assertEqual(b.chunks[1].atts, {})
         self.assertEqual(len(b.chunks), 2)
 
-    def test_splice_fmtstr_without_end(self):
+    def test_splice_fmtstr_without_end(self) -> None:
         a = fmtstr("notion")
         b = a.splice(fmtstr("ta"), 2)
         self.assertEqual(a.s, "notion")
         self.assertEqual(b.s, "notation")
         self.assertEqual(len(b.chunks), 3)
 
-    def test_splice_string_without_end(self):
+    def test_splice_string_without_end(self) -> None:
         a = fmtstr("notion")
         b = a.splice("ta", 2)
         self.assertEqual(a.s, "notion")
         self.assertEqual(b.s, "notation")
         self.assertEqual(len(b.chunks), 3)
 
-    def test_multiple_bfs_splice(self):
+    def test_multiple_bfs_splice(self) -> None:
         self.assertEqual(
             fmtstr("a") + blue("b"),
             on_blue(" " * 2).splice(fmtstr("a") + blue("b"), 0, 2),
@@ -188,13 +190,13 @@
 
 
 class TestFmtStr(unittest.TestCase):
-    def test_copy_with_new_atts(self):
+    def test_copy_with_new_atts(self) -> None:
         a = fmtstr("hello")
         b = a.copy_with_new_atts(bold=True)
         self.assertEqual(a.shared_atts, {})
         self.assertEqual(b.shared_atts, {"bold": True})
 
-    def test_copy_with_new_str(self):
+    def test_copy_with_new_str(self) -> None:
         # Change string but not attributes
         a = fmtstr("hello", "blue")
         b = a.copy_with_new_str("bye")
@@ -202,21 +204,21 @@
         self.assertEqual(b.s, "bye")
         self.assertEqual(a.chunks[0].atts, b.chunks[0].atts)
 
-    def test_append_without_atts(self):
+    def test_append_without_atts(self) -> None:
         a = fmtstr("no")
         b = a.append("te")
         self.assertEqual(a.s, "no")
         self.assertEqual(b.s, "note")
         self.assertEqual(len(b.chunks), 2)
 
-    def test_shared_atts(self):
+    def test_shared_atts(self) -> None:
         a = fmtstr("hi", "blue")
         b = fmtstr("there", "blue")
         c = a + b
         self.assertTrue("fg" in a.shared_atts)
         self.assertTrue("fg" in c.shared_atts)
 
-    def test_new_with_atts_removed(self):
+    def test_new_with_atts_removed(self) -> None:
         a = fmtstr("hi", "blue", "on_green")
         b = fmtstr("there", "blue", "on_red")
         c = a + b
@@ -224,13 +226,13 @@
             c.new_with_atts_removed("fg"), on_green("hi") + on_red("there")
         )
 
-    def setUp(self):
+    def setUp(self) -> None:
         self.s = fmtstr("hello!", "on_blue", fg="red")
 
-    def test_length(self):
+    def test_length(self) -> None:
         self.assertEqual(len(self.s), len(self.s.s))
 
-    def test_split(self):
+    def test_split(self) -> None:
         self.assertEqual(blue("hello there").split(" "), [blue("hello"), 
blue("there")])
         s = blue("hello there")
         self.assertEqual(s.split(" "), [s[:5], s[6:]])
@@ -244,14 +246,14 @@
         )
         self.assertEqual(blue("abcbd").split("b"), [blue("a"), blue("c"), 
blue("d")])
 
-    def test_split_with_spaces(self):
+    def test_split_with_spaces(self) -> None:
         self.assertEqual(blue("a\nb").split(), [blue("a"), blue("b")])
         self.assertEqual(blue("a   \t\n\nb").split(), [blue("a"), blue("b")])
         self.assertEqual(
             blue("hello   \t\n\nthere").split(), [blue("hello"), blue("there")]
         )
 
-    def test_ljust_rjust(self):
+    def test_ljust_rjust(self) -> None:
         b = fmtstr("ab", "blue", "on_red", "bold")
         g = fmtstr("cd", "green", "on_red", "bold")
         s = b + g
@@ -278,7 +280,7 @@
         # formatted string passed in
         # preserve some non-uniform styles (bold, dark, blink) but not others 
(underline, invert)
 
-    def test_linessplit(self):
+    def test_linessplit(self) -> None:
         text = blue("the sum of the squares of the sideways")
         result = [
             blue("the") + blue(" ") + blue("sum"),
@@ -290,7 +292,7 @@
         ]
         self.assertEqual(linesplit(text, 7), result)
 
-    def test_mul(self):
+    def test_mul(self) -> None:
         self.assertEqual(fmtstr("heyhey"), fmtstr("hey") * 2)
         pass
         # TODO raise common attributes when doing equality or when
@@ -300,17 +302,17 @@
         #        bold(blue('hey')+green('there')+blue('hey')+green('there')),
         #        bold(blue('hey')+green('there'))*2)
 
-    def test_change_color(self):
+    def test_change_color(self) -> None:
         a = blue(red("hello"))
         self.assertEqual(a, blue("hello"))
 
-    def test_repr(self):
+    def test_repr(self) -> None:
         self.assertEqual(fmtstr("hello", "red", bold=False), red("hello"))
         self.assertEqual(fmtstr("hello", "red", bold=True), bold(red("hello")))
 
 
 class TestDoubleUnders(unittest.TestCase):
-    def test_equality(self):
+    def test_equality(self) -> None:
         x = fmtstr("adfs")
         self.assertEqual(x, x)
         self.assertTrue(fmtstr("adfs"), fmtstr("adfs"))
@@ -318,28 +320,28 @@
 
 
 class TestConvenience(unittest.TestCase):
-    def test_fg(self):
+    def test_fg(self) -> None:
         red("asdf")
         blue("asdf")
         self.assertTrue(True)
 
-    def test_bg(self):
+    def test_bg(self) -> None:
         on_red("asdf")
         on_blue("asdf")
         self.assertTrue(True)
 
-    def test_styles(self):
+    def test_styles(self) -> None:
         underline("asdf")
         blink("asdf")
         self.assertTrue(True)
 
 
 class TestSlicing(unittest.TestCase):
-    def test_index(self):
+    def test_index(self) -> None:
         self.assertEqual(fmtstr("Hi!", "blue")[0], fmtstr("H", "blue"))
         self.assertRaises(IndexError, fmtstr("Hi!", "blue").__getitem__, 5)
 
-    def test_slice(self):
+    def test_slice(self) -> None:
         self.assertEqual(fmtstr("Hi!", "blue")[1:2], fmtstr("i", "blue"))
         self.assertEqual(fmtstr("Hi!", "blue")[1:], fmtstr("i!", "blue"))
         s = fmtstr("imp") + " "
@@ -351,7 +353,7 @@
 
 
 class TestComposition(unittest.TestCase):
-    def test_simple_composition(self):
+    def test_simple_composition(self) -> None:
         a = fmtstr("hello ", "underline", "on_blue")
         b = fmtstr("there", "red", "on_blue")
         c = a + b
@@ -360,21 +362,21 @@
 
 
 class TestUnicode(unittest.TestCase):
-    def test_output_type(self):
+    def test_output_type(self) -> None:
         self.assertEqual(type(str(fmtstr("hello", "blue"))), str)
 
-    def test_normal_chars(self):
+    def test_normal_chars(self) -> None:
         fmtstr("a", "blue")
         str(fmtstr("a", "blue"))
         self.assertTrue(True)
 
-    def test_funny_chars(self):
+    def test_funny_chars(self) -> None:
         fmtstr("???", "blue")
         str(Chunk("???", {"fg": "blue"}))
         str(fmtstr("???", "blue"))
         self.assertTrue(True)
 
-    def test_right_sequence_in_py3(self):
+    def test_right_sequence_in_py3(self) -> None:
         red_on_blue = fmtstr("hello", "red", "on_blue")
         blue_on_red = fmtstr("there", fg="blue", bg="red")
         green_s = fmtstr("!", "green")
@@ -387,7 +389,7 @@
             "\x1b[31m\x1b[44mhello\x1b[49m\x1b[39m 
\x1b[34m\x1b[41mthere\x1b[49m\x1b[39m\x1b[32m!\x1b[39m",
         )
 
-    def test_len_of_unicode(self):
+    def test_len_of_unicode(self) -> None:
         self.assertEqual(len(fmtstr("??????")), 2)
         lines = ["??????", "an", "??????"]
         r = fsarray(lines)
@@ -398,7 +400,7 @@
         # always coerce everything to unicode?
         # self.assertEqual(len(fmtstr('??????')), 2)
 
-    def test_len_of_unicode_in_fsarray(self):
+    def test_len_of_unicode_in_fsarray(self) -> None:
 
         fsa = FSArray(3, 2)
         fsa.rows[0] = fsa.rows[0].setslice_with_length(0, 2, "??????", 2)
@@ -406,7 +408,7 @@
         fsa.rows[0] = fsa.rows[0].setslice_with_length(0, 2, fmtstr("??????", 
"blue"), 2)
         self.assertEqual(fsa.shape, (3, 2))
 
-    def test_add_unicode_to_byte(self):
+    def test_add_unicode_to_byte(self) -> None:
         fmtstr("???") + fmtstr("a")
         fmtstr("a") + fmtstr("???")
         "???" + fmtstr("???")
@@ -414,30 +416,30 @@
         fmtstr("???") + "???"
         fmtstr("a") + "???"
 
-    def test_unicode_slicing(self):
+    def test_unicode_slicing(self) -> None:
         self.assertEqual(fmtstr("???adfs", "blue")[:2], fmtstr("???a", "blue"))
         self.assertEqual(
             type(fmtstr("???adfs", "blue")[:2].s), type(fmtstr("???a", 
"blue").s)
         )
         self.assertEqual(len(fmtstr("???adfs", "blue")[:2]), 2)
 
-    def test_unicode_repr(self):
+    def test_unicode_repr(self) -> None:
         repr(Chunk("???"))
         self.assertEqual(repr(fmtstr("???")), repr_without_leading_u("???"))
 
 
 class TestCharacterWidth(unittest.TestCase):
-    def test_doublewide_width(self):
+    def test_doublewide_width(self) -> None:
         self.assertEqual(len(fmtstr("???", "blue")), 1)
         self.assertEqual(fmtstr("???", "blue").width, 2)
         self.assertEqual(len(fmtstr("??????")), 2)
         self.assertEqual(fmtstr("??????").width, 4)
 
-    def test_multi_width(self):
+    def test_multi_width(self) -> None:
         self.assertEqual(len(fmtstr("a\u0300")), 2)
         self.assertEqual(fmtstr("a\u0300").width, 1)
 
-    def test_width_aware_slice(self):
+    def test_width_aware_slice(self) -> None:
         self.assertEqual(fmtstr("???").width_aware_slice(slice(None, 1, 
None)).s, " ")
         self.assertEqual(fmtstr("???").width_aware_slice(slice(None, 2, 
None)).s, "???")
         self.assertEqual(
@@ -449,7 +451,7 @@
             fmtstr("???", "blue"),
         )
 
-    def test_width_aware_splitlines(self):
+    def test_width_aware_splitlines(self) -> None:
         s = fmtstr("abcd")
         self.assertEqual(
             list(s.width_aware_splitlines(2)), [fmtstr("ab"), fmtstr("cd")]
@@ -469,7 +471,7 @@
         with self.assertRaises(ValueError):
             s.width_aware_splitlines(1)
 
-    def test_width_at_offset(self):
+    def test_width_at_offset(self) -> None:
         self.assertEqual(fmtstr("ab???cdef").width_at_offset(0), 0)
         self.assertEqual(fmtstr("ab???cdef").width_at_offset(2), 2)
         self.assertEqual(fmtstr("ab???cdef").width_at_offset(3), 4)
@@ -482,7 +484,7 @@
 
 
 class TestWidthHelpers(unittest.TestCase):
-    def test_combining_char_aware_slice(self):
+    def test_combining_char_aware_slice(self) -> None:
         self.assertEqual(width_aware_slice("abc", 0, 2), "ab")
         self.assertEqual(width_aware_slice("abc", 1, 3), "bc")
         self.assertEqual(width_aware_slice("abc", 0, 3), "abc")
@@ -493,7 +495,7 @@
         self.assertEqual(width_aware_slice("ab\u0300\u0300c", 0, 2), 
"ab\u0300\u0300")
         self.assertEqual(width_aware_slice("ab\u0300\u0300c", 2, 3), "c")
 
-    def test_char_width_aware_slice(self):
+    def test_char_width_aware_slice(self) -> None:
         self.assertEqual(width_aware_slice("abc", 1, 2), "b")
         self.assertEqual(width_aware_slice("a???bc", 0, 4), "a???b")
         self.assertEqual(width_aware_slice("a???bc", 1, 4), "???b")
@@ -502,19 +504,19 @@
 
 
 class TestChunk(unittest.TestCase):
-    def test_repr(self):
+    def test_repr(self) -> None:
         c = Chunk("a", {"fg": 32})
         self.assertEqual(repr(c), """Chunk('a', {'fg': 32})""")
 
 
 class TestChunkSplitter(unittest.TestCase):
-    def test_chunk_splitter(self):
+    def test_chunk_splitter(self) -> None:
         splitter = Chunk("asdf", {"fg": 32}).splitter()
         self.assertEqual(splitter.request(1), (1, Chunk("a", {"fg": 32})))
         self.assertEqual(splitter.request(4), (3, Chunk("sdf", {"fg": 32})))
         self.assertEqual(splitter.request(4), None)
 
-    def test_reusing_same_splitter(self):
+    def test_reusing_same_splitter(self) -> None:
         c = Chunk("asdf", {"fg": 32})
         s1 = c.splitter()
         self.assertEqual(s1.request(3), (3, Chunk("asd", {"fg": 32})))
@@ -527,8 +529,8 @@
         s1.reinit(c2)
         self.assertEqual(s1.request(3), (3, Chunk("abc")))
 
-    def test_width_awareness(self):
-        s = Chunk("asdf")
+    def test_width_awareness(self) -> None:
+        Chunk("asdf")
         self.assertEqual(
             Chunk("ab\u0300c").splitter().request(3), (3, Chunk("ab\u0300c"))
         )
@@ -550,19 +552,19 @@
 
 
 class TestFSArray(unittest.TestCase):
-    def test_no_hanging_space(self):
+    def test_no_hanging_space(self) -> None:
         a = FSArray(4, 2)
         self.assertEqual(len(a.rows[0]), 0)
 
-    def test_assignment_working(self):
+    def test_assignment_working(self) -> None:
         t = FSArray(10, 10)
         t[2, 2] = "a"
         # TODO: is this supposed to check something?
         t[2, 2] == "a"
 
-    def test_normalize_slice(self):
+    def test_normalize_slice(self) -> None:
         class SliceBuilder:
-            def __getitem__(self, slice):
+            def __getitem__(self, slice: slice) -> slice:
                 return slice
 
         Slice = SliceBuilder()
@@ -571,14 +573,13 @@
         self.assertEqual(normalize_slice(11, Slice[3:]), slice(3, 11, None))
 
     @skip("TODO")
-    def test_oomerror(self):
+    def test_oomerror(self) -> None:
         a = FSArray(10, 40)
         a[2:-2, 2:-2] = fsarray(["asdf", "zxcv"])
 
 
 class FormatStringTest(unittest.TestCase):
-    def assertFSArraysEqual(self, a, b):
-        # type: (FSArray, FSArray) -> None
+    def assertFSArraysEqual(self, a: FSArray, b: FSArray) -> None:
         self.assertIsInstance(a, FSArray)
         self.assertIsInstance(b, FSArray)
         self.assertEqual(
@@ -593,8 +594,7 @@
                 "FSArrays differ first on line {}:\n{}".format(i, 
FSArray.diff(a, b)),
             )
 
-    def assertFSArraysEqualIgnoringFormatting(self, a, b):
-        # type: (FSArray, FSArray) -> None
+    def assertFSArraysEqualIgnoringFormatting(self, a: FSArray, b: FSArray) -> 
None:
         """Also accepts arrays of strings"""
         self.assertEqual(
             len(a),
@@ -614,7 +614,7 @@
 
 
 class TestFSArrayWithDiff(FormatStringTest):
-    def test_diff_testing(self):
+    def test_diff_testing(self) -> None:
         a = fsarray(["abc", "def"])
         b = fsarray(["abc", "dqf"])
         self.assertRaises(AssertionError, self.assertFSArraysEqual, a, b)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/tests/test_input.py 
new/curtsies-0.4.0/tests/test_input.py
--- old/curtsies-0.3.10/tests/test_input.py     2021-09-25 20:39:51.000000000 
+0200
+++ new/curtsies-0.4.0/tests/test_input.py      2022-08-28 22:39:24.000000000 
+0200
@@ -141,3 +141,9 @@
         t = threading.Thread(target=use)
         t.start()
         t.join()
+
+    def test_cleanup(self):
+        input_generator = Input()
+        for i in range(1000):
+            with input_generator:
+                pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/tests/test_terminal.py 
new/curtsies-0.4.0/tests/test_terminal.py
--- old/curtsies-0.3.10/tests/test_terminal.py  2021-09-25 20:39:51.000000000 
+0200
+++ new/curtsies-0.4.0/tests/test_terminal.py   2022-08-28 22:39:24.000000000 
+0200
@@ -7,7 +7,7 @@
 from io import StringIO
 from unittest import skipUnless, skipIf, expectedFailure
 
-import blessings
+import blessed
 import pyte
 from pyte import control as ctrl, Stream, Screen
 
@@ -88,7 +88,7 @@
         pass
 
 
-@skipUnless(sys.stdin.isatty(), "blessings Terminal needs streams open")
+@skipUnless(sys.stdin.isatty(), "blessed Terminal needs streams open")
 class TestFullscreenWindow(unittest.TestCase):
     def setUp(self):
         self.screen = pyte.Screen(10, 3)
@@ -121,7 +121,7 @@
         pass
 
 
-@skipUnless(sys.stdin.isatty(), "blessings Terminal needs streams open")
+@skipUnless(sys.stdin.isatty(), "blessed Terminal needs streams open")
 class TestCursorAwareWindow(unittest.TestCase):
     def setUp(self):
         self.screen = ReportingScreen(6, 3)
@@ -133,8 +133,8 @@
             out_stream=stdout, in_stream=self.screen._report_file
         )
         self.window.cbreak = NopContext()
-        blessings.Terminal.height = 3
-        blessings.Terminal.width = 6
+        blessed.Terminal.height = 3
+        blessed.Terminal.width = 6
 
     # This isn't passing locally for me anymore :/
     @expectedFailure
@@ -162,7 +162,7 @@
             self.assertEqual(self.screen.display, ["      ", "hi    ", "there 
"])
 
 
-@skipUnless(sys.stdin.isatty(), "blessings Terminal needs streams open")
+@skipUnless(sys.stdin.isatty(), "blessed Terminal needs streams open")
 class TestCursorAwareWindowWithExtraInput(unittest.TestCase):
     def setUp(self):
         self.screen = ReportingScreenWithExtra(6, 3)
@@ -177,8 +177,8 @@
             extra_bytes_callback=self.extra_bytes_callback,
         )
         self.window.cbreak = NopContext()
-        blessings.Terminal.height = 3
-        blessings.Terminal.width = 6
+        blessed.Terminal.height = 3
+        blessed.Terminal.width = 6
 
     def extra_bytes_callback(self, bytes):
         self.extra_bytes.append(bytes)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/curtsies-0.3.10/tests/test_window.py 
new/curtsies-0.4.0/tests/test_window.py
--- old/curtsies-0.3.10/tests/test_window.py    2021-09-25 20:39:51.000000000 
+0200
+++ new/curtsies-0.4.0/tests/test_window.py     2022-08-28 22:39:24.000000000 
+0200
@@ -14,7 +14,7 @@
     height = property(lambda self: 4)
 
 
-@skipIf(fds_closed, "blessings Terminal needs streams open")
+@skipIf(fds_closed, "blessed Terminal needs streams open")
 class TestBaseWindow(unittest.TestCase):
     """Pretty pathetic tests for window"""
 

Reply via email to