[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