[med-svn] [Git][med-team/enlighten][upstream] New upstream version 1.6.2

Shayan Doust gitlab at salsa.debian.org
Thu Sep 3 14:09:06 BST 2020



Shayan Doust pushed to branch upstream at Debian Med / enlighten


Commits:
6b743310 by Shayan Doust at 2020-09-03T14:05:03+01:00
New upstream version 1.6.2
- - - - -


29 changed files:

- README.rst
- doc/api.rst
- doc/conf.py
- doc/examples.rst
- doc/install.rst
- enlighten/__init__.py
- enlighten/_basecounter.py
- enlighten/_counter.py
- enlighten/_manager.py
- enlighten/_statusbar.py
- enlighten/_terminal.py
- enlighten/_util.py
- enlighten/counter.py
- examples/basic.py
- examples/context_manager.py
- examples/demo.py
- examples/floats.py
- examples/ftp_downloader.py
- examples/multicolored.py
- examples/multiple_logging.py
- pylintrc
- setup.py
- setup_helpers.py
- tests/__init__.py
- tests/test_counter.py
- tests/test_manager.py
- tests/test_statusbar.py
- tests/test_terminal.py
- tox.ini


Changes:

=====================================
README.rst
=====================================
@@ -86,8 +86,8 @@ PIP
 
     $ pip install enlighten
 
-EL6 and EL7 (RHEL/CentOS/Scientific)
-------------------------------------
+EL6, EL7, and EL8 (RHEL/CentOS/Scientific)
+------------------------------------------
 
 (EPEL_ repositories must be configured_)
 


=====================================
doc/api.rst
=====================================
@@ -1,5 +1,5 @@
 ..
-  Copyright 2017 - 2019 Avram Lubkin, All Rights Reserved
+  Copyright 2017 - 2020 Avram Lubkin, All Rights Reserved
 
   This Source Code Form is subject to the terms of the Mozilla Public
   License, v. 2.0. If a copy of the MPL was not distributed with this


=====================================
doc/conf.py
=====================================
@@ -53,7 +53,7 @@ master_doc = 'index'
 
 # General information about the project.
 project = 'Enlighten'
-copyright = '2017 - 2019, Avram Lubkin'
+copyright = '2017 - 2020, Avram Lubkin'
 author = 'Avram Lubkin'
 
 # The version info for the project you're documenting, acts as replacement for


=====================================
doc/examples.rst
=====================================
@@ -114,6 +114,7 @@ Status bars, like other bars can be pinned. To pin a status bar to the top of al
 initialize it before any other bars. To pin a bar to the bottom of the screen, use
 ``position=1`` when initializing.
 
+See :py:class:`~enlighten.StatusBar` for more details.
 
 Color
 -----


=====================================
doc/install.rst
=====================================
@@ -24,25 +24,26 @@ RPM
 
 RPMs are available in the Fedora_ and EPEL_ repositories
 
-EL6 and EL7 (RHEL/CentOS/Scientific)
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Fedora and EL8 (RHEL/CentOS/Scientific)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-(EPEL_ repositories must be configured_)
+(EPEL_ repositories must be configured_ for EL8)
 
 .. code-block:: console
 
-    $ yum install python-enlighten
+    $ dnf install python3-enlighten
+
+EL7 (RHEL/CentOS/Scientific)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-Fedora
-^^^^^^
+(EPEL_ repositories must be configured_)
 
 .. code-block:: console
 
-    $ dnf install python2-enlighten
-    $ dnf install python3-enlighten
+    $ yum install python2-enlighten
+    $ yum install python36-enlighten
+
 
 .. _EPEL: https://fedoraproject.org/wiki/EPEL
 .. _Fedora: https://fedoraproject.org/
 .. _configured: https://fedoraproject.org/wiki/EPEL#How_can_I_use_these_extra_packages.3F
-
-


=====================================
enlighten/__init__.py
=====================================
@@ -13,8 +13,9 @@ Provides progress bars and counters which play nice in a TTY console
 
 from enlighten.counter import Counter, StatusBar, SubCounter
 from enlighten._manager import Manager, get_manager
-from enlighten._util import Justify
+from enlighten._util import EnlightenWarning, Justify
 
 
-__version__ = '1.6.0'
-__all__ = ('Counter', 'Justify', 'Manager', 'StatusBar', 'SubCounter', 'get_manager')
+__version__ = '1.6.2'
+__all__ = ('Counter', 'EnlightenWarning', 'Justify', 'Manager',
+           'StatusBar', 'SubCounter', 'get_manager')


=====================================
enlighten/_basecounter.py
=====================================
@@ -43,16 +43,19 @@ class BaseCounter(object):
 
         return '%s(%s)' % (self.__class__.__name__, ', '.join(params))
 
-    def __init__(self, **kwargs):
+    def __init__(self, keywords=None, **kwargs):
 
-        self.count = self.start_count = kwargs.get('count', 0)
+        if keywords is not None:
+            kwargs = keywords
+
+        self.count = self.start_count = kwargs.pop('count', 0)
         self._color = None
 
-        self.manager = kwargs.get('manager', None)
+        self.manager = kwargs.pop('manager', None)
         if self.manager is None:
             raise TypeError('manager must be specified')
 
-        self.color = kwargs.get('color', None)
+        self.color = kwargs.pop('color', None)
 
     @property
     def color(self):
@@ -131,15 +134,17 @@ class PrintableCounter(BaseCounter):
 
     __slots__ = ('enabled', '_fill', 'last_update', 'leave', 'min_delta', '_pinned', 'start')
 
-    def __init__(self, **kwargs):
+    def __init__(self, keywords=None, **kwargs):
 
-        super(PrintableCounter, self).__init__(**kwargs)
+        if keywords is not None:  # pragma: no branch
+            kwargs = keywords
+        super(PrintableCounter, self).__init__(keywords=kwargs)
 
-        self.enabled = kwargs.get('enabled', True)
+        self.enabled = kwargs.pop('enabled', True)
         self._fill = u' '
-        self.fill = kwargs.get('fill', u' ')
-        self.leave = kwargs.get('leave', True)
-        self.min_delta = kwargs.get('min_delta', 0.1)
+        self.fill = kwargs.pop('fill', u' ')
+        self.leave = kwargs.pop('leave', True)
+        self.min_delta = kwargs.pop('min_delta', 0.1)
         self._pinned = False
         self.last_update = self.start = time.time()
 
@@ -226,8 +231,7 @@ class PrintableCounter(BaseCounter):
 
         if self.enabled:
             self.last_update = time.time()
-            self.manager.write(output=self.format(elapsed=elapsed),
-                               flush=flush, counter=self)
+            self.manager.write(output=self.format, flush=flush, counter=self, elapsed=elapsed)
 
     def _fill_text(self, text, width, offset=None):
         """


=====================================
enlighten/_counter.py
=====================================
@@ -11,12 +11,14 @@
 Provides Counter and SubConter classes
 """
 
+import os
 import platform
+import re
 import sys
 import time
 
 from enlighten._basecounter import BaseCounter, PrintableCounter
-from enlighten._util import format_time
+from enlighten._util import EnlightenWarning, format_time, raise_from_none, warn_best_level
 
 COUNTER_FMT = u'{desc}{desc_pad}{count:d} {unit}{unit_pad}' + \
               u'[{elapsed}, {rate:.2f}{unit_pad}{unit}/s]{fill}'
@@ -26,8 +28,8 @@ BAR_FMT = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}| {count:{len_total}d}/{tota
 
 STATUS_FMT = u'{message}'
 
-# Even with cp65001, Windows doesn't seem to support all unicode characters
-if platform.system() == 'Windows':  # pragma: no cover(Windows)
+# Even with cp65001, Windows doesn't seem to support all unicode characters. Windows Terminal does
+if platform.system() == 'Windows' and not os.environ.get('WT_SESSION', None):  # pragma: no cover
     SERIES_STD = u' ▌█'
 else:
     SERIES_STD = u' ▏▎▍▌▋▊▉█'
@@ -40,6 +42,11 @@ except UnicodeEncodeError:  # pragma: no cover(Non-unicode Terminal)
 except (AttributeError, TypeError):  # pragma: no cover(Non-standard Terminal)
     pass
 
+# Reserved fields
+COUNTER_FIELDS = {'count', 'desc', 'desc_pad', 'elapsed', 'rate', 'unit', 'unit_pad',
+                  'bar', 'eta', 'len_total', 'percentage', 'total', 'fill'}
+RE_SUBCOUNTER_FIELDS = re.compile(r'(?:count|percentage|eta|rate)_\d+')
+
 
 class SubCounter(BaseCounter):
     """
@@ -111,23 +118,25 @@ class SubCounter(BaseCounter):
         """
 
         # Make sure source is a parent or peer
-        if source is self.parent or getattr(source, 'parent', None) is self.parent:
-
-            if self.count + incr < 0 or source.count - incr < 0:
-                raise ValueError('Invalid increment: %s' % incr)
-
-            if source is self.parent:
-                if self.parent.count - self.parent.subcount - incr < 0:
-                    raise ValueError('Invalid increment: %s' % incr)
+        if source is not self.parent and getattr(source, 'parent', None) is not self.parent:
+            raise ValueError('source must be parent or peer')
 
-            else:
-                source.count -= incr
+        # Make sure counts won't go negative
+        if self.count + incr < 0 or source.count - incr < 0:
+            raise ValueError('Invalid increment: %s' % incr)
 
-            self.count += incr
-            self.parent.update(0, force)
+        # Make sure parent count won't go negative
+        if source is self.parent:
+            if self.parent.count - self.parent.subcount - incr < 0:
+                raise ValueError('Invalid increment: %s' % incr)
 
+        # Deduct from peer count
         else:
-            raise ValueError('source must be parent or peer')
+            source.count -= incr
+
+        # Increment self and update parent
+        self.count += incr
+        self.parent.update(0, force)
 
 
 class Counter(PrintableCounter):
@@ -391,7 +400,7 @@ class Counter(PrintableCounter):
     # pylint: disable=too-many-arguments
     def __init__(self, **kwargs):
 
-        super(Counter, self).__init__(**kwargs)
+        super(Counter, self).__init__(keywords=kwargs)
 
         # Accept additional_fields for backwards compatibility
         self.fields = kwargs.pop('fields', kwargs.pop('additional_fields', {}))
@@ -502,6 +511,16 @@ class Counter(PrintableCounter):
 
         fields = self.fields.copy()
         fields.update(self._fields)
+
+        # Warn on reserved fields
+        reserved_fields = (set(fields) & COUNTER_FIELDS) | set(
+            match.group() for match in (RE_SUBCOUNTER_FIELDS.match(key) for key in fields) if match
+        )
+        if reserved_fields:
+            warn_best_level('Ignoring reserved fields specified as user-defined fields: %s' %
+                            ', '.join(reserved_fields),
+                            EnlightenWarning)
+
         fields.update({'bar': u'{0}',
                        'count': self.count,
                        'desc': self.desc or u'',
@@ -559,7 +578,7 @@ class Counter(PrintableCounter):
             try:
                 rtn = self.bar_format.format(**fields)
             except KeyError as e:
-                raise ValueError('%r specified in format, but not provided' % e.args[0])
+                raise_from_none(ValueError('%r specified in format, but not provided' % e.args[0]))
 
             # Format the bar
             if self.offset is None:
@@ -599,7 +618,7 @@ class Counter(PrintableCounter):
         try:
             rtn = self.counter_format.format(**fields)
         except KeyError as e:
-            raise ValueError('%r specified in format, but not provided' % e.args[0])
+            raise_from_none(ValueError('%r specified in format, but not provided' % e.args[0]))
 
         return self._fill_text(rtn, width, offset=self.offset)
 


=====================================
enlighten/_manager.py
=====================================
@@ -12,24 +12,15 @@ Provides Manager class
 """
 
 import atexit
+from collections import OrderedDict
 import signal
 import sys
 import time
 
-try:
-    from collections import OrderedDict
-except ImportError:  # pragma: no cover (Python 2.6)
-    from ordereddict import OrderedDict
-
-
 from enlighten._counter import Counter
 from enlighten._statusbar import StatusBar
 from enlighten._terminal import Terminal
 
-
-# Flag to support unicode in Python 2.6
-NEEDS_UNICODE_HELP = sys.version_info[:2] < (2, 7)
-
 RESIZE_SUPPORTED = hasattr(signal, 'SIGWINCH')
 
 
@@ -45,7 +36,7 @@ class Manager(object):
             below. (Default: :py:data:`None`)
         enabled(bool): Status (Default: True)
         no_resize(bool): Disable resizing support
-        kwargs(dict): Any additional :py:term:`keyword arguments<keyword argument>`
+        kwargs(Dict[str, Any]): Any additional :py:term:`keyword arguments<keyword argument>`
             will be used as default values when :py:meth:`counter` is called.
 
     Manager class for outputting progress bars to streams attached to TTYs
@@ -107,6 +98,7 @@ class Manager(object):
         self.height = self.term.height
         self.process_exit = False
         self.refresh_lock = False
+        self._resize = False
         self.resize_lock = False
         self.scroll_offset = 1
         self.width = self.term.width
@@ -130,7 +122,7 @@ class Manager(object):
         Args:
             position(int): Line number counting from the bottom of the screen
             autorefresh(bool): Refresh this counter when other bars are drawn
-            kwargs(dict): Any additional :py:term:`keyword arguments<keyword argument>`
+            kwargs(Dict[str, Any]): Any additional :py:term:`keyword arguments<keyword argument>`
                 are passed to :py:class:`Counter`
 
         Returns:
@@ -159,7 +151,7 @@ class Manager(object):
         Args:
             position(int): Line number counting from the bottom of the screen
             autorefresh(bool): Refresh this counter when other bars are drawn
-            kwargs(dict): Any additional :py:term:`keyword arguments<keyword argument>`
+            kwargs(Dict[str, Any]): Any additional :py:term:`keyword arguments<keyword argument>`
                 are passed to :py:class:`StatusBar`
 
         Returns:
@@ -185,7 +177,7 @@ class Manager(object):
         Args:
             counter_class(:py:class:`PrintableCounter`): Class to instantiate
             position(int): Line number counting from the bottom of the screen
-            kwargs(dict): Any additional :py:term:`keyword arguments<keyword argument>`
+            kwargs(Dict[str, Any]): Any additional :py:term:`keyword arguments<keyword argument>`
                 are passed to :py:class:`Counter`
 
         Returns:
@@ -199,6 +191,7 @@ class Manager(object):
         """
 
         position = kwargs.pop('position', None)
+        autorefresh = kwargs.pop('autorefresh', False)
 
         # List of counters to refresh due to new position
         toRefresh = []
@@ -211,12 +204,12 @@ class Manager(object):
 
         # Create counter
         new = counter_class(*args, **kwargs)
-        if kwargs.pop('autorefresh', False):
+        if autorefresh:
             self.autorefresh.append(new)
 
         # Get pinned counters
         # pylint: disable=protected-access
-        pinned = dict((pos, ctr) for ctr, pos in self.counters.items() if ctr._pinned)
+        pinned = {pos: ctr for ctr, pos in self.counters.items() if ctr._pinned}
 
         # Check position
         if position is not None:
@@ -258,14 +251,26 @@ class Manager(object):
 
         self._set_scroll_area()
         for ctr in reversed(toRefresh):
-            ctr.refresh()
-        self.stream.flush()
+            ctr.refresh(flush=False)
+        self._flush_streams()
 
         return new
 
-    def _resize_handler(self, *args, **kwarg):  # pylint: disable=unused-argument
+    def _stage_resize(self, *args, **kwarg):  # pylint: disable=unused-argument
         """
         Called when a window resize signal is detected
+        """
+
+        # Set semaphore to trigger resize on next write
+        self._resize = True
+
+        # Reset update time to avoid any delay in resize
+        for counter in self.counters:
+            counter.last_update = 0
+
+    def _resize_handler(self):
+        """
+        Called when a window resize has been detected
 
         Resets the scroll window
         """
@@ -281,15 +286,6 @@ class Manager(object):
             term.clear_cache()
             newHeight = term.height
             newWidth = term.width
-            lastHeight = lastWidth = 0
-
-            while newHeight != lastHeight or newWidth != lastWidth:
-                lastHeight = newHeight
-                lastWidth = newWidth
-                time.sleep(.2)
-                term.clear_cache()
-                newHeight = term.height
-                newWidth = term.width
 
             if newWidth < self.width:
                 offset = (self.scroll_offset - 1) * (1 + self.width // newWidth)
@@ -299,9 +295,9 @@ class Manager(object):
             self.width = newWidth
             self._set_scroll_area(force=True)
 
-            for cter in self.counters:
-                cter.refresh(flush=False)
-            self.stream.flush()
+            for counter in self.counters:
+                counter.refresh(flush=False)
+            self._flush_streams()
 
             self.resize_lock = False
 
@@ -315,7 +311,12 @@ class Manager(object):
 
         # Save scroll offset for resizing
         oldOffset = self.scroll_offset
-        self.scroll_offset = newOffset = max(self.counters.values()) + 1
+        newOffset = max(self.counters.values()) + 1
+        if newOffset > oldOffset:
+            self.scroll_offset = newOffset
+            use_new = True
+        else:
+            use_new = False
 
         if not self.enabled:
             return
@@ -324,20 +325,20 @@ class Manager(object):
         if not self.process_exit:
             atexit.register(self._at_exit)
             if not self.no_resize and RESIZE_SUPPORTED:
-                signal.signal(signal.SIGWINCH, self._resize_handler)
+                signal.signal(signal.SIGWINCH, self._stage_resize)
             self.process_exit = True
 
         if self.set_scroll:
 
             term = self.term
             newHeight = term.height
-            scrollPosition = max(0, newHeight - newOffset)
+            scrollPosition = max(0, newHeight - self.scroll_offset)
 
-            if force or newOffset > oldOffset or newHeight != self.height:
+            if force or use_new or newHeight != self.height:
                 self.height = newHeight
 
                 # Add line feeds so we don't overwrite existing output
-                if newOffset - oldOffset > 0:
+                if use_new:
                     term.move_to(0, max(0, newHeight - oldOffset))
                     self.stream.write(u'\n' * (newOffset - oldOffset))
 
@@ -349,30 +350,37 @@ class Manager(object):
             if self.companion_term is not None:
                 self.companion_term.move_to(0, scrollPosition)
 
+    def _flush_streams(self):
+        """
+        Convenience method for flushing streams
+        """
+
+        self.stream.flush()
+        if self.companion_stream is not None:
+            self.companion_stream.flush()
+
     def _at_exit(self):
         """
         Resets terminal to normal configuration
         """
 
-        if self.process_exit:
-
-            try:
+        if not self.process_exit:
+            return
 
-                term = self.term
+        try:
+            term = self.term
 
-                if self.set_scroll:
-                    term.reset()
-                else:
-                    term.move_to(0, term.height)
+            if self.set_scroll:
+                term.reset()
+            else:
+                term.move_to(0, term.height)
 
-                self.term.feed()
+            self.term.feed()
 
-                self.stream.flush()
-                if self.companion_stream is not None:
-                    self.companion_stream.flush()
+            self._flush_streams()
 
-            except ValueError:  # Possibly closed file handles
-                pass
+        except ValueError:  # Possibly closed file handles
+            pass
 
     def remove(self, counter):
         """
@@ -407,78 +415,93 @@ class Manager(object):
 
         """
 
-        if self.enabled:
+        if not self.enabled:
+            return
 
-            term = self.term
-            stream = self.stream
-            positions = self.counters.values()
+        term = self.term
+        stream = self.stream
+        positions = self.counters.values()
 
-            if not self.no_resize and RESIZE_SUPPORTED:
-                signal.signal(signal.SIGWINCH, self.sigwinch_orig)
+        if not self.no_resize and RESIZE_SUPPORTED:
+            signal.signal(signal.SIGWINCH, self.sigwinch_orig)
 
-            try:
-                for num in range(self.scroll_offset - 1, 0, -1):
-                    if num not in positions:
-                        term.move_to(0, term.height - num)
-                        stream.write(term.clear_eol)
+        try:
+            for num in range(self.scroll_offset - 1, 0, -1):
+                if num not in positions:
+                    term.move_to(0, term.height - num)
+                    stream.write(term.clear_eol)
 
-                stream.flush()
+        finally:
 
-            finally:
+            if self.set_scroll:
 
-                if self.set_scroll:
+                self.term.reset()
 
-                    self.term.reset()
+                if self.companion_term:
+                    self.companion_term.reset()
 
-                    if self.companion_term:
-                        self.companion_term.reset()
+            else:
+                term.move_to(0, term.height)
 
-                else:
-                    term.move_to(0, term.height)
+            self.process_exit = False
+            self.enabled = False
+            for counter in self.counters:
+                counter.enabled = False
 
-                self.process_exit = False
-                self.enabled = False
-                for cter in self.counters:
-                    cter.enabled = False
+        # Feed terminal if lowest position isn't cleared
+        if 1 in positions:
+            term.feed()
 
-            # Feed terminal if lowest position isn't cleared
-            if 1 in positions:
-                term.feed()
+        self._flush_streams()
 
-    def write(self, output='', flush=True, counter=None):
+    def write(self, output='', flush=True, counter=None, **kwargs):
         """
         Args:
-            output(str): Output string
+            output(str): Output string or callable
             flush(bool): Flush the output stream after writing
             counter(:py:class:`Counter`): Bar being written (for position and auto-refresh)
+            kwargs(dict): Additional arguments passed when output is callable
 
-        Write to stream at a given position
-        """
+        Write to the stream.
 
-        position = self.counters[counter] if counter else 0
+        The position is determined by the counter or defaults to the bottom of the terminal
 
-        if self.enabled:
+        If ``output`` is callable, it will be called with any additional keyword arguments
+        to produce the output string
+        """
 
-            term = self.term
-            stream = self.stream
+        if not self.enabled:
+            return
 
+        # If resize signal was caught, handle resize
+        if self._resize and not self.resize_lock:
             try:
-                term.move_to(0, term.height - position)
-                # Include \r and term call to cover most conditions
-                if NEEDS_UNICODE_HELP:  # pragma: no cover (Version dependent 2.6)
-                    encoding = getattr(stream, 'encoding', None) or 'UTF-8'
-                    stream.write(('\r' + term.clear_eol + output).encode(encoding))
-                else:  # pragma: no cover (Version dependent >= 2.7)
-                    stream.write(u'\r' + term.clear_eol + output)
-
+                self._resize_handler()
             finally:
-                # Reset position and scrolling
-                if not self.refresh_lock:
-                    if self.autorefresh:
-                        self._autorefresh(exclude=(counter,))
-                    self._set_scroll_area()
-                    if flush:
-                        stream.flush()
+                self._resize = False
+
+            return
+
+        position = self.counters[counter] if counter else 0
+        term = self.term
+
+        # If output is callable, call it with supplied arguments
+        if callable(output):
+            output = output(**kwargs)
+
+        try:
+            term.move_to(0, term.height - position)
+            # Include \r and term call to cover most conditions
+            self.stream.write(u'\r' + term.clear_eol + output)
+
+        finally:
+            # Reset position and scrolling
+            if not self.refresh_lock:
+                if self.autorefresh:
+                    self._autorefresh(exclude=(counter,))
+                self._set_scroll_area()
+                if flush:
+                    self._flush_streams()
 
     def _autorefresh(self, exclude):
         """
@@ -512,7 +535,7 @@ def get_manager(stream=None, counterclass=Counter, **kwargs):
         stream(:py:term:`file object`): Output stream. If :py:data:`None`,
             defaults to :py:data:`sys.stdout`
         counter_class(:py:term:`class`): Progress bar class (Default: :py:class:`Counter`)
-        kwargs(dict): Any additional :py:term:`keyword arguments<keyword argument>`
+        kwargs(Dict[str, Any]): Any additional :py:term:`keyword arguments<keyword argument>`
             will passed to the manager class.
 
     Returns:


=====================================
enlighten/_statusbar.py
=====================================
@@ -14,7 +14,10 @@ Provides StatusBar class
 import time
 
 from enlighten._basecounter import PrintableCounter
-from enlighten._util import format_time, Justify
+from enlighten._util import EnlightenWarning, format_time, Justify, raise_from_none, warn_best_level
+
+
+STATUS_FIELDS = {'elapsed', 'fill'}
 
 
 class StatusBar(PrintableCounter):
@@ -132,7 +135,8 @@ class StatusBar(PrintableCounter):
     __slots__ = ('fields', '_justify', 'status_format', '_static', '_fields')
 
     def __init__(self, *args, **kwargs):
-        super(StatusBar, self).__init__(**kwargs)
+
+        super(StatusBar, self).__init__(keywords=kwargs)
 
         self.fields = kwargs.pop('fields', {})
         self._justify = None
@@ -185,6 +189,14 @@ class StatusBar(PrintableCounter):
         else:
             fields = self.fields.copy()
             fields.update(self._fields)
+
+            # Warn on reserved fields
+            reserved_fields = (set(fields) & STATUS_FIELDS)
+            if reserved_fields:
+                warn_best_level('Ignoring reserved fields specified as user-defined fields: %s' %
+                                ', '.join(reserved_fields),
+                                EnlightenWarning)
+
             elapsed = elapsed if elapsed is not None else self.elapsed
             fields['elapsed'] = format_time(elapsed)
             fields['fill'] = u'{0}'
@@ -193,7 +205,7 @@ class StatusBar(PrintableCounter):
             try:
                 rtn = self.status_format.format(**fields)
             except KeyError as e:
-                raise ValueError('%r specified in format, but not provided' % e.args[0])
+                raise_from_none(ValueError('%r specified in format, but not provided' % e.args[0]))
 
         rtn = self._fill_text(rtn, width)
 


=====================================
enlighten/_terminal.py
=====================================
@@ -54,12 +54,12 @@ class Terminal(_Terminal):
         self.stream.write(self.csr(0, position))
         self.stream.write(self.move(position, 0))
 
-    def move_to(self, xpos, ypos):
+    def move_to(self, x_pos, y_pos):
         """
         Move cursor to specified position
         """
 
-        self.stream.write(self.move(ypos, xpos))
+        self.stream.write(self.move(y_pos, x_pos))
 
     def _height_and_width(self):
         """
@@ -70,8 +70,8 @@ class Terminal(_Terminal):
         try:
             return self._cache['height_and_width']
         except KeyError:
-            handw = self._cache['height_and_width'] = super(Terminal, self)._height_and_width()
-            return handw
+            h_and_w = self._cache['height_and_width'] = super(Terminal, self)._height_and_width()
+            return h_and_w
 
     def clear_cache(self):
         """


=====================================
enlighten/_util.py
=====================================
@@ -12,11 +12,37 @@
 Provides utility functions and objects
 """
 
+import inspect
+import os
+import sys
+import warnings
+
 try:
     BASESTRING = basestring
 except NameError:
     BASESTRING = str
 
+BASE_DIR = os.path.basename(os.path.dirname(__file__))
+
+
+class EnlightenWarning(Warning):
+    """
+    Generic warning class for Enlighten
+    """
+
+
+def warn_best_level(message, category):
+    """
+    Helper function to warn at first frame stack outside of library
+    """
+
+    level = 5  # Unused default
+    for level, frame in enumerate(inspect.stack(), 1):  # pragma: no cover
+        if os.path.basename(os.path.dirname(frame[1])) != BASE_DIR:
+            break
+
+    warnings.warn(message, category=category, stacklevel=level)
+
 
 def format_time(seconds):
     """
@@ -44,6 +70,17 @@ def format_time(seconds):
     return rtn
 
 
+def raise_from_none(exc):  # pragma: no cover
+    """
+    Convenience function to raise from None in a Python 2/3 compatible manner
+    """
+    raise exc
+
+
+if sys.version_info[0] >= 3:  # pragma: no branch
+    exec('def raise_from_none(exc):\n    raise exc from None')  # pylint: disable=exec-used
+
+
 class Justify(object):
     """
     Enumerated type for justification options


=====================================
enlighten/counter.py
=====================================
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-# Copyright 2017 - 2019 Avram Lubkin, All Rights Reserved
+# Copyright 2017 - 2020 Avram Lubkin, All Rights Reserved
 
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -27,9 +27,10 @@ class Counter(_Counter):  # pylint: disable=missing-docstring
     def __init__(self, **kwargs):
 
         manager = kwargs.get('manager', None)
+        stream = kwargs.pop('stream', sys.stdout)
+
         if manager is None:
-            manager = get_manager(stream=kwargs.get('stream', sys.stdout),
-                                  counter_class=self.__class__, set_scroll=False)
+            manager = get_manager(stream=stream, counter_class=self.__class__, set_scroll=False)
             manager.counters[self] = 1
             kwargs['manager'] = manager
 


=====================================
examples/basic.py
=====================================
@@ -19,7 +19,7 @@ def process_files():
     """
 
     with enlighten.Counter(total=100, desc='Simple', unit='ticks') as pbar:
-        for num in range(100):  # pylint: disable=unused-variable
+        for _ in range(100):
             time.sleep(0.05)
             pbar.update()
 


=====================================
examples/context_manager.py
=====================================
@@ -23,12 +23,12 @@ def process_files():
 
     with enlighten.Manager() as manager:
         with manager.counter(total=SPLINES, desc='Reticulating:', unit='splines') as retic:
-            for num in range(SPLINES):  # pylint: disable=unused-variable
+            for _ in range(SPLINES):
                 time.sleep(random.uniform(0.1, 0.5))  # Random processing time
                 retic.update()
 
         with manager.counter(total=LLAMAS, desc='Herding:', unit='llamas') as herd:
-            for num in range(SPLINES):  # pylint: disable=unused-variable
+            for _ in range(SPLINES):
                 time.sleep(random.uniform(0.1, 0.5))  # Random processing time
                 herd.update()
 


=====================================
examples/demo.py
=====================================
@@ -1,4 +1,4 @@
-# Copyright 2019 Avram Lubkin, All Rights Reserved
+# Copyright 2019 - 2020 Avram Lubkin, All Rights Reserved
 
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -14,6 +14,7 @@ import time
 
 import enlighten
 
+# pylint: disable=wrong-import-order
 from multicolored import run_tests, load
 from multiple_logging import process_files, win_time_granularity
 
@@ -25,7 +26,7 @@ def initialize(manager, initials=15):
 
     # Simulated preparation
     pbar = manager.counter(total=initials, desc='Initializing:', unit='initials')
-    for num in range(initials):  # pylint: disable=unused-variable
+    for _ in range(initials):
         time.sleep(random.uniform(0.05, 0.25))  # Random processing time
         pbar.update()
     pbar.close()


=====================================
examples/floats.py
=====================================
@@ -30,7 +30,7 @@ def process_files(count=None):
     pbar = enlighten.Counter(total=count, desc='Simple', unit='ticks',
                              bar_format=BAR_FMT, counter_format=COUNTER_FMT)
 
-    for num in range(100):  # pylint: disable=unused-variable
+    for _ in range(100):
         time.sleep(0.05)
         pbar.update(1.1)
 


=====================================
examples/ftp_downloader.py
=====================================
@@ -31,10 +31,7 @@ class Writer(object):
     def __init__(self, filename, size, directory=None):
         self.filename = filename
         self.size = size
-        if directory:
-            self.dest = os.path.join(directory, filename)
-        else:
-            self.dest = filename
+        self.dest = os.path.join(directory, filename) if directory else filename
         self.status = self.fileobj = None
 
     def __enter__(self):


=====================================
examples/multicolored.py
=====================================
@@ -1,4 +1,4 @@
-# Copyright 2019 Avram Lubkin, All Rights Reserved
+# Copyright 2019 - 2020 Avram Lubkin, All Rights Reserved
 
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -148,7 +148,7 @@ def load(manager, units=80):
                 pb_loading.update_from(pb_connecting)
 
         # Connect to up to 5 units at a time
-        for _ in range(0, min(units - count, 5 - len(connecting))):
+        for _ in range(min(units - count, 5 - len(connecting))):
             node = Node(count)
             node.connect()
             connecting.append(node)


=====================================
examples/multiple_logging.py
=====================================
@@ -1,4 +1,4 @@
-# Copyright 2017 - 2019 Avram Lubkin, All Rights Reserved
+# Copyright 2017 - 2020 Avram Lubkin, All Rights Reserved
 
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -52,30 +52,30 @@ def process_files(manager):
     enterprise = manager.counter(total=DATACENTERS, desc='Processing:', unit='datacenters')
 
     # Iterate through data centers
-    for dnum in range(1, DATACENTERS + 1):
+    for d_num in range(1, DATACENTERS + 1):
         systems = random.randint(*SYSTEMS)  # Random number of systems
         # Get a child progress bar. leave is False so it can be replaced
-        currCenter = manager.counter(total=systems, desc='  Datacenter %d:' % dnum,
+        datacenter = manager.counter(total=systems, desc='  Datacenter %d:' % d_num,
                                      unit='systems', leave=False)
 
         # Iterate through systems
-        for snum in range(1, systems + 1):
+        for s_num in range(1, systems + 1):
 
             # Has no total, so will act as counter. Leave is False
-            system = manager.counter(desc='    System %d:' % snum, unit='files', leave=False)
+            system = manager.counter(desc='    System %d:' % s_num, unit='files', leave=False)
             files = random.randint(*FILES)  # Random file count
 
             # Iterate through files
-            for fnum in range(files):  # pylint: disable=unused-variable
+            for _ in range(files):
                 system.update()  # Update count
                 time.sleep(random.uniform(0.001, 0.005))  # Random processing time
 
             system.close()  # Close counter so it gets removed
             # Log status
-            LOGGER.info('Updated %d files on System %d in Datacenter %d', files, snum, dnum)
-            currCenter.update()  # Update count
+            LOGGER.info('Updated %d files on System %d in Datacenter %d', files, s_num, d_num)
+            datacenter.update()  # Update count
 
-        currCenter.close()  # Close counter so it gets removed
+        datacenter.close()  # Close counter so it gets removed
 
         enterprise.update()  # Update count
 


=====================================
pylintrc
=====================================
@@ -21,8 +21,9 @@ good-names=e,_
 
 [MESSAGES CONTROL]
 disable=
+    super-with-arguments,  # Python 2
     too-few-public-methods,
-    useless-object-inheritance
+    useless-object-inheritance,  # Python 2
 
 [SIMILARITIES]
 # Minimum lines number of a similarity.


=====================================
setup.py
=====================================
@@ -18,8 +18,7 @@ from setuptools import setup, find_packages
 from setup_helpers import get_version, readme
 
 INSTALL_REQUIRES = ['blessed>=1.17.7']
-TESTS_REQUIRE = ['mock; python_version < "3.3"',
-                 'unittest2; python_version < "2.7"']
+TESTS_REQUIRE = ['mock; python_version < "3.3"']
 
 # Additional requirements
 # html requires sphinx, sphinx_rtd_theme
@@ -51,7 +50,6 @@ setup(
         'Operating System :: POSIX',
         'Operating System :: Microsoft :: Windows',
         'Programming Language :: Python',
-        'Programming Language :: Python :: 2.6',
         'Programming Language :: Python :: 2.7',
         'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',


=====================================
setup_helpers.py
=====================================
@@ -1,4 +1,4 @@
-# Copyright 2017 Avram Lubkin, All Rights Reserved
+# Copyright 2017 - 2020 Avram Lubkin, All Rights Reserved
 
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,6 +15,7 @@ import sys
 
 
 RE_VERSION = re.compile(r'__version__\s*=\s*[\'\"](.+)[\'\"]$')
+DIR_SPELLING = 'build/doc/spelling/'
 
 
 def get_version(filename, encoding='utf8'):
@@ -44,8 +45,11 @@ def print_spelling_errors(filename, encoding='utf8'):
     """
     Print misspelled words returned by sphinxcontrib-spelling
     """
+    try:
+        filesize = os.stat(filename).st_size
+    except FileNotFoundError:
+        filesize = 0
 
-    filesize = os.stat(filename).st_size
     if filesize:
         sys.stdout.write('Misspelled Words:\n')
         with io.open(filename, encoding=encoding) as wordlist:
@@ -55,18 +59,46 @@ def print_spelling_errors(filename, encoding='utf8'):
     return 1 if filesize else 0
 
 
+def print_all_spelling_errors(path):
+    """
+    Print all spelling errors in the path
+    """
+
+    rtn = 0
+    for filename in os.listdir(path):
+        if print_spelling_errors(os.path.join(path, filename)):
+            rtn = 1
+
+    return rtn
+
+
+def spelling_clean_dir(path):
+    """
+    Remove spelling files from path
+    """
+    if not os.path.isdir(path):
+        return
+    for filename in os.listdir(path):
+        os.unlink(os.path.join(path, filename))
+
+
 if __name__ == '__main__':
 
     # Do nothing if no arguments were given
     if len(sys.argv) < 2:
         sys.exit(0)
 
+    # Print misspelled word list
+    if sys.argv[1] == 'spelling-clean':
+        spelling_clean_dir(DIR_SPELLING)
+        sys.exit(0)
+
     # Print misspelled word list
     if sys.argv[1] == 'spelling':
         if len(sys.argv) > 2:
             sys.exit(print_spelling_errors(sys.argv[2]))
         else:
-            sys.exit(print_spelling_errors('build/doc/spelling/output.txt'))
+            sys.exit(print_all_spelling_errors(DIR_SPELLING))
 
     # Unknown option
     else:


=====================================
tests/__init__.py
=====================================
@@ -17,6 +17,7 @@ import pty
 import struct
 import sys
 import termios
+import unittest
 
 from enlighten import Manager
 from enlighten._basecounter import BaseCounter
@@ -24,12 +25,6 @@ from enlighten._counter import Counter
 from enlighten._statusbar import StatusBar
 
 # pylint: disable=import-error
-
-if sys.version_info[:2] < (2, 7):
-    import unittest2 as unittest
-else:
-    import unittest  # pylint: disable=wrong-import-order
-
 if sys.version_info[:2] < (3, 3):
     import mock
 else:
@@ -37,8 +32,10 @@ else:
 
 if sys.version_info[0] < 3:
     from StringIO import StringIO
+    PY2 = True
 else:
     from io import StringIO
+    PY2 = False
 
 # pylint: enable=import-error
 
@@ -55,7 +52,7 @@ class TestCase(unittest.TestCase):
     """
 
 
-# Fix deprecated methods for EL6
+# Fix deprecated methods for 2.7
 def assert_regex(self, text, regex, msg=None):
     """
     Wrapper for assertRegexpMatches
@@ -118,13 +115,8 @@ class MockTTY(object):
     def __init__(self, height=25, width=80):
 
         self.master, self.slave = pty.openpty()
-
-        if sys.version_info[:2] < (2, 7):
-            self.stdout = os.fdopen(self.slave, 'w', 1)
-            self.stdread = os.fdopen(self.master, 'r')
-        else:
-            self.stdout = io.open(self.slave, 'w', 1, encoding='UTF-8', newline='')
-            self.stdread = io.open(self.master, 'r', encoding='UTF-8', newline='\n')
+        self.stdout = io.open(self.slave, 'w', 1, encoding='UTF-8', newline='')
+        self.stdread = io.open(self.master, 'r', encoding='UTF-8', newline='\n')
 
         # Make sure linefeed behavior is consistent between Python 2 and Python 3
         termattrs = termios.tcgetattr(self.slave)
@@ -201,7 +193,11 @@ class MockManager(Manager):
         self.output = []
         self.remove_calls = 0
 
-    def write(self, output='', flush=True, counter=None):
+    def write(self, output='', flush=True, counter=None, **kwargs):
+
+        if callable(output):
+            output = output(**kwargs)
+
         self.output.append('write(output=%s, flush=%s, position=%s)' %
                            (output, flush, counter.position))
 


=====================================
tests/test_counter.py
=====================================
@@ -11,11 +11,10 @@ Test module for enlighten._counter and enlighten.counter
 
 import time
 
-from enlighten import Counter, Manager
+from enlighten import Counter, EnlightenWarning, Manager
 import enlighten._counter
-from enlighten._manager import NEEDS_UNICODE_HELP
 
-from tests import TestCase, mock, MockManager, MockTTY, MockCounter
+from tests import TestCase, mock, MockManager, MockTTY, MockCounter, PY2, unittest
 
 
 # pylint: disable=missing-docstring, protected-access, too-many-public-methods
@@ -396,15 +395,15 @@ class TestCounter(TestCase):
         blueBarFormat = self.manager.term.blue(barFormat)
         self.assertNotEqual(len(barFormat), len(blueBarFormat))
 
-        ctr = self.manager.counter(stream=self.tty.stdout, total=10, desc='Test',
-                                   unit='ticks', count=10, bar_format=barFormat)
+        ctr = self.manager.counter(total=10, desc='Test', unit='ticks',
+                                   count=10, bar_format=barFormat)
         formatted1 = ctr.format(width=80)
         self.assertEqual(len(formatted1), 80)
         barLen1 = formatted1.count(BLOCK)
 
         offset = len(self.manager.term.blue(''))
-        ctr = self.manager.counter(stream=self.tty.stdout, total=10, desc='Test',
-                                   unit='ticks', count=10, bar_format=blueBarFormat)
+        ctr = self.manager.counter(total=10, desc='Test', unit='ticks',
+                                   count=10, bar_format=blueBarFormat)
         formatted2 = ctr.format(width=80)
         self.assertEqual(len(formatted2), 80 + offset)
         barLen2 = formatted2.count(BLOCK)
@@ -420,15 +419,15 @@ class TestCounter(TestCase):
                     u'[{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]'
         barFormat = self.manager.term.blue(barFormat)
 
-        ctr = self.manager.counter(stream=self.tty.stdout, total=10, desc='Test',
-                                   unit='ticks', count=10, bar_format=barFormat, offset=0)
+        ctr = self.manager.counter(total=10, desc='Test', unit='ticks',
+                                   count=10, bar_format=barFormat, offset=0)
         formatted1 = ctr.format(width=80)
         self.assertEqual(len(formatted1), 80)
         barLen1 = formatted1.count(BLOCK)
 
         offset = len(self.manager.term.blue(''))
-        ctr = self.manager.counter(stream=self.tty.stdout, total=10, desc='Test',
-                                   unit='ticks', count=10, bar_format=barFormat, offset=offset)
+        ctr = self.manager.counter(total=10, desc='Test', unit='ticks',
+                                   count=10, bar_format=barFormat, offset=offset)
         formatted2 = ctr.format(width=80)
         self.assertEqual(len(formatted2), 80 + offset)
         barLen2 = formatted2.count(BLOCK)
@@ -436,11 +435,11 @@ class TestCounter(TestCase):
         self.assertTrue(barLen2 == barLen1 + offset)
 
         # Test in counter format
-        ctr = self.manager.counter(stream=self.tty.stdout, total=10, count=50, offset=0)
+        ctr = self.manager.counter(total=10, count=50, offset=0)
         formatted = ctr.format(width=80)
         self.assertEqual(len(formatted), 80)
 
-        ctr = self.manager.counter(stream=self.tty.stdout, total=10, count=50, offset=10)
+        ctr = self.manager.counter(total=10, count=50, offset=10)
         formatted = ctr.format(width=80)
         self.assertEqual(len(formatted), 90)
 
@@ -502,8 +501,6 @@ class TestCounter(TestCase):
 
         self.tty.stdout.write(u'X\n')
         value = self.tty.stdread.readline()
-        if NEEDS_UNICODE_HELP:
-            value = value.decode('utf-8')
 
         self.assertRegex(value, r'Test  50%\|' + u'█+[▏▎▍▌▋▊▉]?' +
                          r'[ ]+\|  50/100 \[00:5\d<00:5\d, \d.\d\d ticks/s\]X\n')
@@ -692,7 +689,7 @@ class TestCounter(TestCase):
         """
 
         bar_format = ctr_format = u'{arg1:s} {count:d}'
-        additional_fields = {'arg1': 'hello', 'count': 100000}
+        additional_fields = {'arg1': 'hello'}
 
         ctr = Counter(stream=self.tty.stdout, total=10, count=1, bar_format=bar_format,
                       fields=additional_fields)
@@ -762,3 +759,29 @@ class TestCounter(TestCase):
         ctr_format = u'{fill}HI{fill}'
         ctr = Counter(stream=self.tty.stdout, count=1, counter_format=ctr_format, fill=u'-')
         self.assertEqual(ctr.format(), u'-' * 39 + 'HI' + u'-' * 39)
+
+    @unittest.skipIf(PY2, 'Skip warnings tests in Python 2')
+    def test_reserved_fields(self):
+        """
+        When reserved fields are used, a warning is raised
+        """
+
+        ctr = Counter(stream=self.tty.stdout, total=10, count=1, fields={'elapsed': 'reserved'})
+        with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn:
+            ctr.format()
+        self.assertRegex(__file__, warn.filename)
+
+        ctr = Counter(stream=self.tty.stdout, total=10, fields={'elapsed': 'reserved'})
+        with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn:
+            ctr.format()
+        self.assertRegex(__file__, warn.filename)
+
+        ctr = Counter(stream=self.tty.stdout, total=10, count=1, elapsed='reserved')
+        with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn:
+            ctr.format()
+        self.assertRegex(__file__, warn.filename)
+
+        ctr = Counter(stream=self.tty.stdout, total=10, elapsed='reserved')
+        with self.assertWarns(EnlightenWarning) as warn:
+            ctr.format()
+        self.assertRegex(__file__, warn.filename)


=====================================
tests/test_manager.py
=====================================
@@ -136,7 +136,7 @@ class TestManager(TestCase):
         self.assertEqual(manager.counters[counter1], 2)
         self.assertEqual(manager.counters[counter2], 1)
         self.assertEqual(counter1.calls,
-                         ['clear(flush=False)', 'refresh(flush=True, elapsed=None)'])
+                         ['clear(flush=False)', 'refresh(flush=False, elapsed=None)'])
         self.assertEqual(counter2.calls, [])
         self.assertEqual(ssa.call_count, 1)
         counter1.calls = []
@@ -149,9 +149,9 @@ class TestManager(TestCase):
         self.assertEqual(manager.counters[counter2], 2)
         self.assertEqual(manager.counters[counter3], 1)
         self.assertEqual(counter1.calls,
-                         ['clear(flush=False)', 'refresh(flush=True, elapsed=None)'])
+                         ['clear(flush=False)', 'refresh(flush=False, elapsed=None)'])
         self.assertEqual(counter2.calls,
-                         ['clear(flush=False)', 'refresh(flush=True, elapsed=None)'])
+                         ['clear(flush=False)', 'refresh(flush=False, elapsed=None)'])
         self.assertEqual(counter3.calls, [])
         self.assertEqual(ssa.call_count, 1)
         counter1.calls = []
@@ -343,7 +343,7 @@ class TestManager(TestCase):
         stdread = self.tty.stdread
         self.assertEqual(manager.scroll_offset, 1)
         self.assertFalse(manager.process_exit)
-        self.assertNotEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
+        self.assertNotEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)
 
         with mock.patch('enlighten._manager.atexit') as atexit:
             with mock.patch.object(term, 'change_scroll'):
@@ -351,7 +351,7 @@ class TestManager(TestCase):
                 self.assertEqual(term.change_scroll.call_count, 1)  # pylint: disable=no-member
 
             self.assertEqual(manager.scroll_offset, 4)
-            self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
+            self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)
 
             self.assertEqual(stdread.readline(), term.move(24, 0) + '\n')
             self.assertEqual(stdread.readline(), '\n')
@@ -372,6 +372,13 @@ class TestManager(TestCase):
 
             self.assertFalse(atexit.register.called)
 
+        # Set max counter lower and make sure scroll_offset hasn't changed
+        manager.counters['dummy'] = 1
+        with mock.patch('enlighten._manager.atexit') as atexit:
+            with mock.patch.object(term, 'change_scroll'):
+                manager._set_scroll_area()
+        self.assertEqual(manager.scroll_offset, 4)
+
     def test_set_scroll_area_height(self):
         manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
         manager.counters['dummy'] = 3
@@ -451,12 +458,12 @@ class TestManager(TestCase):
                     manager._set_scroll_area()
 
             self.assertEqual(manager.scroll_offset, 5)
-            self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
+            self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)
             self.assertTrue(manager.process_exit)
 
             # Clear stream
             self.tty.stdout.write(u'X\n')
-            for num in range(4 + 1):  # pylint: disable=unused-variable
+            for _ in range(4 + 1):
                 self.tty.stdread.readline()
 
             self.assertFalse(reset.called)
@@ -466,7 +473,7 @@ class TestManager(TestCase):
             # No output, No changes
             self.tty.stdout.write(u'X\n')
             self.assertEqual(self.tty.stdread.readline(), 'X\n')
-            self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
+            self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)
             self.assertTrue(manager.process_exit)
 
             manager.enabled = True
@@ -500,7 +507,7 @@ class TestManager(TestCase):
                     manager._set_scroll_area()
 
             self.assertEqual(manager.scroll_offset, 5)
-            self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._resize_handler)
+            self.assertEqual(signal.getsignal(signal.SIGWINCH), manager._stage_resize)
             self.assertTrue(manager.process_exit)
 
             # Stream empty
@@ -577,15 +584,19 @@ class TestManager(TestCase):
             self.assertTrue(termfeed.called)
 
     def test_resize_handler(self):
+        """
+        Resize lock must be False for handler to run
+        Terminal size is cached unless resize handler runs
+        """
 
-        with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockheight:
-            mockheight.side_effect = [80, 85, 87, 70, 70]
+        manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
+        counter3 = MockCounter(manager=manager)
+        manager.counters[counter3] = 3
+        manager.scroll_offset = 4
+        term = manager.term
 
-            manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
-            counter3 = MockCounter(manager=manager)
-            manager.counters[counter3] = 3
-            manager.scroll_offset = 4
-            term = manager.term
+        with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockheight:
+            mockheight.return_value = 70
 
             manager.resize_lock = True
             with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa:
@@ -601,7 +612,6 @@ class TestManager(TestCase):
             self.assertEqual(counter3.calls, [])
 
             manager.resize_lock = False
-            mockheight.side_effect = [80, 85, 87, 70, 70]
             with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa:
                 manager._resize_handler()
                 self.assertEqual(ssa.call_count, 1)
@@ -614,25 +624,37 @@ class TestManager(TestCase):
 
             self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)'])
 
-    def test_resize_handler_no_change(self):
+    def test_resize(self):
+        """
+        Test a resize event
+        """
+        manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
+        counter3 = MockCounter(manager=manager)
+        counter3.last_update = time.time()
+        manager.counters[counter3] = 3
+        manager.scroll_offset = 4
+        term = manager.term
+
+        # simulate resize
+        manager._stage_resize()
+        self.assertTrue(manager._resize)
+        self.assertEqual(counter3.last_update, 0)
 
         with mock.patch('%s.width' % TERMINAL, new_callable=mock.PropertyMock) as mockheight:
-            mockheight.side_effect = [80, 85, 87, 80, 80]
+            mockheight.return_value = 70
 
-            manager = _manager.Manager(stream=self.tty.stdout, counter_class=MockCounter)
-            counter3 = MockCounter(manager=manager)
-            manager.counters[counter3] = 3
-            manager.scroll_offset = 4
+            # resize doesn't happen until a write is called
+            self.assertEqual(manager.width, 80)
 
             with mock.patch('enlighten._manager.Manager._set_scroll_area') as ssa:
-                manager._resize_handler()
+                manager.write()
                 self.assertEqual(ssa.call_count, 1)
 
-            self.assertEqual(manager.width, 80)
-
+            self.assertEqual(manager.width, 70)
             self.tty.stdout.write(u'X\n')
-            self.assertEqual(self.tty.stdread.readline(), 'X\n')
-
+            self.assertEqual(self.tty.stdread.readline(), term.move(19, 0) + term.clear_eos + 'X\n')
+            self.assertFalse(manager.resize_lock)
+            self.assertFalse(manager._resize)
             self.assertEqual(counter3.calls, ['refresh(flush=False, elapsed=None)'])
 
     def test_resize_handler_height_only(self):
@@ -782,7 +804,7 @@ class TestGetManager(TestCase):
             self.assertTrue(manager.defaults['enabled'])
 
     @unittest.skipIf(STDOUT_NO_FD, 'No file descriptor for stdout')
-    def test_get_manager_notty(self):
+    def test_get_manager_no_tty(self):
 
         # stdout is not attached to a tty
         with redirect_output('stdout', OUTPUT):


=====================================
tests/test_statusbar.py
=====================================
@@ -9,9 +9,10 @@
 Test module for enlighten._statusbar
 """
 
-from enlighten import Justify
+from enlighten import EnlightenWarning, Justify
 
-from tests import TestCase, MockManager, MockTTY, MockStatusBar
+import tests
+from tests import TestCase, MockManager, MockTTY, MockStatusBar, PY2, unittest
 
 
 class TestStatusBar(TestCase):
@@ -145,9 +146,24 @@ class TestStatusBar(TestCase):
         Extra fill should be equal
         """
 
-        print(self.manager.term.width)
         sbar = self.manager.status_bar(
             status_format=u'{fill}Helloooo!{fill}Woooorld!{fill}', fill='-'
         )
         self.assertEqual(sbar.format(),
                          u'-' * 20 + 'Helloooo!' + u'-' * 21 + 'Woooorld!' + u'-' * 21)
+
+    @unittest.skipIf(PY2, 'Skip warnings tests in Python 2')
+    def test_reserve_fields(self):
+        """
+        When reserved fields are used, a warning is raised
+        """
+
+        with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn:
+            self.manager.status_bar(status_format=u'Stage: {stage}, Fill: {fill}', stage=1,
+                                    fields={'fill': 'Reserved field'})
+        self.assertRegex(tests.__file__, warn.filename)
+
+        with self.assertWarnsRegex(EnlightenWarning, 'Ignoring reserved fields') as warn:
+            self.manager.status_bar(status_format=u'Stage: {stage}, elapsed: {elapsed}', stage=1,
+                                    elapsed='Reserved field')
+        self.assertRegex(tests.__file__, warn.filename)


=====================================
tests/test_terminal.py
=====================================
@@ -35,12 +35,12 @@ class TestTerminal(TestCase):
         Return values aren't accurate for blessed, but are sufficient for this test
         """
 
-        handw = 'enlighten._terminal._Terminal._height_and_width'
+        h_and_w = 'enlighten._terminal._Terminal._height_and_width'
 
-        with mock.patch(handw, return_value=(1, 2)):
+        with mock.patch(h_and_w, return_value=(1, 2)):
             self.assertEqual(self.terminal._height_and_width(), (1, 2))
 
-        with mock.patch(handw, return_value=(5, 6)):
+        with mock.patch(h_and_w, return_value=(5, 6)):
             self.assertEqual(self.terminal._height_and_width(), (1, 2))
             self.terminal.clear_cache()
             self.assertEqual(self.terminal._height_and_width(), (5, 6))


=====================================
tox.ini
=====================================
@@ -1,5 +1,5 @@
 [tox]
-envlist = lint,coverage,py34,py35,py36,py38,py27,el7,pypy,pypy3,docs
+envlist = lint,coverage,py34,py35,py36,py37,py27,el7,pypy,pypy3,docs
 
 [base]
 deps =
@@ -10,17 +10,11 @@ usedevelop = True
 
 deps =
     {[base]deps}
-    py{26,27,py}: mock
-    py26: ordereddict
-    py26: unittest2
+    py{27,py}: mock
 
 commands =
     {envpython} -m unittest discover -s {toxinidir}/tests {posargs}
 
-[testenv:py26]
-commands =
-    {envpython} -m unittest2.__main__ discover -s {toxinidir}/tests {posargs}
-
 [testenv:el7]
 basepython = python2.7
 deps =
@@ -31,7 +25,7 @@ deps =
 
 [testenv:flake8]
 skip_install = True
-basepython = python3.7
+basepython = python3.8
 deps =
     flake8
 
@@ -41,7 +35,7 @@ commands =
 [testenv:pylint]
 skip_install = True
 ignore_errors=True
-basepython = python3.7
+basepython = python3.8
 deps =
     {[base]deps}
     pylint
@@ -53,7 +47,7 @@ commands =
 [testenv:lint]
 skip_install = True
 ignore_errors=True
-basepython = python3.7
+basepython = python3.8
 deps =
     {[testenv:flake8]deps}
     {[testenv:pylint]deps}
@@ -63,7 +57,7 @@ commands =
     {[testenv:pylint]commands}
 
 [testenv:coverage]
-basepython = python3.7
+basepython = python3.8
 deps =
     {[base]deps}
     coverage
@@ -73,7 +67,7 @@ commands =
     {envpython} -m coverage report
 
 [testenv:codecov]
-basepython = python3.7
+basepython = python3.8
 passenv = CI TRAVIS TRAVIS_*
 deps =
     {[testenv:coverage]deps}
@@ -84,13 +78,14 @@ commands =
     {envpython} -m codecov
 
 [testenv:docs]
-basepython = python3.7
+basepython = python3.8
 deps =
     sphinx
     sphinxcontrib-spelling
     sphinx_rtd_theme
 
 commands=
+    {envpython} setup_helpers.py spelling-clean
     {envpython} setup.py spelling
     {envpython} setup_helpers.py spelling
     {envpython} setup.py html



View it on GitLab: https://salsa.debian.org/med-team/enlighten/-/commit/6b743310bda91ef60849af5dadf47cb6d52d8f2a

-- 
View it on GitLab: https://salsa.debian.org/med-team/enlighten/-/commit/6b743310bda91ef60849af5dadf47cb6d52d8f2a
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/debian-med-commit/attachments/20200903/499de1d0/attachment-0001.html>


More information about the debian-med-commit mailing list