Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-humanfriendly for
openSUSE:Factory checked in at 2021-08-18 08:56:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-humanfriendly (Old)
and /work/SRC/openSUSE:Factory/.python-humanfriendly.new.1899 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-humanfriendly"
Wed Aug 18 08:56:23 2021 rev:15 rq:912717 version:9.2
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-humanfriendly/python-humanfriendly.changes
2020-04-23 18:37:20.900880987 +0200
+++
/work/SRC/openSUSE:Factory/.python-humanfriendly.new.1899/python-humanfriendly.changes
2021-08-18 08:57:14.074881593 +0200
@@ -1,0 +2,35 @@
+Tue Aug 17 10:58:03 UTC 2021 - John Paul Adrian Glaubitz
<[email protected]>
+
+- Update to 9.2
+ Maintenance release:
+ * Merged pull request `#46`_ which fixes several :pypi:`flake8` warnings.
+ * Merged pull request `#49`_ which marks Python 3.9 support final.
+ * Merged pull request `#51`_ which helps to stabilize the test suite.
+ * Merged pull request `#52`_ which updates the :mod:`humanfriendly.sphinx`
+ module to include Sphinx extension metadata that has become mandatory in a
+ recent Sphinx release. After merging the pull request I added additional
+ metadata including the version.
+- from version 9.1
+ * Added :func:`~humanfriendly.compat.on_macos()` function to detect Apple
MacOS
+ (I need this in an upcoming :pypi:`coloredlogs` release and don't want to
have
+ to think about how to detect MacOS again in the future ????).
+- from version 9.0
+ The major version number was bumped because the bug fix for
+ :func:`~humanfriendly.text.pluralize()` is backwards incompatible
+ and (even though this seems like very "cosmetic" functionality)
+ version numbers are cheap, so who cares ????.
+ **Bug fixes:**
+ * Changed :func:`~humanfriendly.format_number()` to properly support negative
+ numbers (as suggested in `issue #40`_).
+ * Changed :func:`~humanfriendly.text.pluralize()` to generate "1.5 seconds"
+ instead of "1.5 second" (as suggested in `issue #43`_).
+ **Enhancements:**
+ * Enhanced :func:`~humanfriendly.text.concatenate()` to support
``conjunction``
+ and ``serial_comma`` keyword arguments (as suggested in `issue #30`_).
+ * Added :func:`~humanfriendly.text.pluralize_raw()` to select singular or
+ plural form without prefixing the count to the text that is returned.
+- from version 8.2
+ * Added a simple case insensitive dictionary implementation, for details
refer to
+ the new :mod:`humanfriendly.case` module.
+
+-------------------------------------------------------------------
Old:
----
humanfriendly-8.1.tar.gz
New:
----
humanfriendly-9.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-humanfriendly.spec ++++++
--- /var/tmp/diff_new_pack.PfEhKm/_old 2021-08-18 08:57:14.542881042 +0200
+++ /var/tmp/diff_new_pack.PfEhKm/_new 2021-08-18 08:57:14.546881039 +0200
@@ -1,7 +1,7 @@
#
-# spec file for package python
+# spec file
#
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LLC
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -27,7 +27,7 @@
%bcond_without python2
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
Name: python-humanfriendly%{psuffix}
-Version: 8.1
+Version: 9.2
Release: 0
Summary: Human friendly input/output for text interfaces using Python
License: MIT
@@ -38,7 +38,7 @@
BuildRequires: python-rpm-macros
Requires: python
Requires(post): update-alternatives
-Requires(postun): update-alternatives
+Requires(postun):update-alternatives
BuildArch: noarch
%if %{with test}
BuildRequires: %{python_module capturer >= 2.1}
++++++ humanfriendly-8.1.tar.gz -> humanfriendly-9.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/CHANGELOG.rst
new/humanfriendly-9.2/CHANGELOG.rst
--- old/humanfriendly-8.1/CHANGELOG.rst 2020-03-06 22:01:01.000000000 +0100
+++ new/humanfriendly-9.2/CHANGELOG.rst 2021-06-11 12:52:37.000000000 +0200
@@ -11,6 +11,74 @@
.. _Keep a Changelog: http://keepachangelog.com/
.. _semantic versioning: http://semver.org/
+`Release 9.2`_ (2021-06-11)
+---------------------------
+
+Maintenance release:
+
+- Merged pull request `#46`_ which fixes several :pypi:`flake8` warnings.
+
+- Merged pull request `#49`_ which marks Python 3.9 support final.
+
+- Merged pull request `#51`_ which helps to stabilize the test suite.
+
+- Merged pull request `#52`_ which updates the :mod:`humanfriendly.sphinx`
+ module to include Sphinx extension metadata that has become mandatory in a
+ recent Sphinx release. After merging the pull request I added additional
+ metadata including the version.
+
+.. _Release 9.2:
https://github.com/xolox/python-humanfriendly/compare/9.1...9.2
+.. _#46: https://github.com/xolox/python-humanfriendly/pull/46
+.. _#49: https://github.com/xolox/python-humanfriendly/pull/49
+.. _#51: https://github.com/xolox/python-humanfriendly/pull/51
+.. _#52: https://github.com/xolox/python-humanfriendly/pull/52
+
+`Release 9.1`_ (2020-12-10)
+---------------------------
+
+Added :func:`~humanfriendly.compat.on_macos()` function to detect Apple MacOS
+(I need this in an upcoming :pypi:`coloredlogs` release and don't want to have
+to think about how to detect MacOS again in the future ????).
+
+.. _Release 9.1:
https://github.com/xolox/python-humanfriendly/compare/9.0...9.1
+
+`Release 9.0`_ (2020-12-01)
+---------------------------
+
+The major version number was bumped because the bug fix for
+:func:`~humanfriendly.text.pluralize()` is backwards incompatible
+and (even though this seems like very "cosmetic" functionality)
+version numbers are cheap, so who cares ????.
+
+**Bug fixes:**
+
+- Changed :func:`~humanfriendly.format_number()` to properly support negative
+ numbers (as suggested in `issue #40`_).
+
+- Changed :func:`~humanfriendly.text.pluralize()` to generate "1.5 seconds"
+ instead of "1.5 second" (as suggested in `issue #43`_).
+
+**Enhancements:**
+
+- Enhanced :func:`~humanfriendly.text.concatenate()` to support ``conjunction``
+ and ``serial_comma`` keyword arguments (as suggested in `issue #30`_).
+
+- Added :func:`~humanfriendly.text.pluralize_raw()` to select singular or
+ plural form without prefixing the count to the text that is returned.
+
+.. _Release 9.0:
https://github.com/xolox/python-humanfriendly/compare/8.2...9.0
+.. _issue #30: https://github.com/xolox/python-humanfriendly/issues/30
+.. _issue #40: https://github.com/xolox/python-humanfriendly/issues/40
+.. _issue #43: https://github.com/xolox/python-humanfriendly/issues/43
+
+`Release 8.2`_ (2020-04-19)
+---------------------------
+
+Added a simple case insensitive dictionary implementation, for details refer to
+the new :mod:`humanfriendly.case` module.
+
+.. _Release 8.2:
https://github.com/xolox/python-humanfriendly/compare/8.1...8.2
+
`Release 8.1`_ (2020-03-06)
---------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/LICENSE.txt
new/humanfriendly-9.2/LICENSE.txt
--- old/humanfriendly-8.1/LICENSE.txt 2020-02-06 01:08:58.000000000 +0100
+++ new/humanfriendly-9.2/LICENSE.txt 2021-06-11 12:43:08.000000000 +0200
@@ -1,4 +1,4 @@
-Copyright (c) 2020 Peter Odding
+Copyright (c) 2021 Peter Odding
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/PKG-INFO
new/humanfriendly-9.2/PKG-INFO
--- old/humanfriendly-8.1/PKG-INFO 2020-03-06 22:01:19.000000000 +0100
+++ new/humanfriendly-9.2/PKG-INFO 2021-06-11 12:53:09.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: humanfriendly
-Version: 8.1
+Version: 9.2
Summary: Human friendly output for text interfaces using Python
Home-page: https://humanfriendly.readthedocs.io
Author: Peter Odding
@@ -152,7 +152,7 @@
This software is licensed under the `MIT license`_.
- ?? 2020 Peter Odding.
+ ?? 2021 Peter Odding.
.. External references:
.. _#4: https://github.com/xolox/python-humanfriendly/issues/4
@@ -184,6 +184,7 @@
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Communications
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/README.rst
new/humanfriendly-9.2/README.rst
--- old/humanfriendly-8.1/README.rst 2020-02-16 20:22:55.000000000 +0100
+++ new/humanfriendly-9.2/README.rst 2021-06-11 12:43:08.000000000 +0200
@@ -144,7 +144,7 @@
This software is licensed under the `MIT license`_.
-?? 2020 Peter Odding.
+?? 2021 Peter Odding.
.. External references:
.. _#4: https://github.com/xolox/python-humanfriendly/issues/4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/docs/api.rst
new/humanfriendly-9.2/docs/api.rst
--- old/humanfriendly-8.1/docs/api.rst 2020-03-02 01:06:35.000000000 +0100
+++ new/humanfriendly-9.2/docs/api.rst 2020-04-19 02:22:19.000000000 +0200
@@ -34,6 +34,12 @@
.. automodule:: humanfriendly
:members:
+:mod:`humanfriendly.case`
+------------------------
+
+.. automodule:: humanfriendly.case
+ :members:
+
:mod:`humanfriendly.cli`
------------------------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/docs/conf.py
new/humanfriendly-9.2/docs/conf.py
--- old/humanfriendly-8.1/docs/conf.py 2020-03-01 15:16:58.000000000 +0100
+++ new/humanfriendly-9.2/docs/conf.py 2021-06-11 12:43:08.000000000 +0200
@@ -32,7 +32,7 @@
# General information about the project.
project = 'humanfriendly'
-copyright = '2020, Peter Odding'
+copyright = '2021, Peter Odding'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly/__init__.py
new/humanfriendly-9.2/humanfriendly/__init__.py
--- old/humanfriendly-8.1/humanfriendly/__init__.py 2020-03-06
22:01:01.000000000 +0100
+++ new/humanfriendly-9.2/humanfriendly/__init__.py 2021-06-11
12:52:37.000000000 +0200
@@ -1,7 +1,7 @@
# Human friendly input/output in Python.
#
# Author: Peter Odding <[email protected]>
-# Last Change: March 6, 2020
+# Last Change: June 11, 2021
# URL: https://humanfriendly.readthedocs.io
"""The main module of the `humanfriendly` package."""
@@ -51,7 +51,7 @@
)
# Semi-standard module versioning.
-__version__ = '8.1'
+__version__ = '9.2'
# Named tuples to define units of size.
SizeUnit = collections.namedtuple('SizeUnit', 'divider, symbol, name')
@@ -353,7 +353,8 @@
6,000,000,000
"""
integer_part, _, decimal_part = str(float(number)).partition('.')
- reversed_digits = ''.join(reversed(integer_part))
+ negative_sign = integer_part.startswith('-')
+ reversed_digits = ''.join(reversed(integer_part.lstrip('-')))
parts = []
while reversed_digits:
parts.append(reversed_digits[:3])
@@ -362,6 +363,8 @@
decimals_to_add = decimal_part[:num_decimals].rstrip('0')
if decimals_to_add:
formatted_number += '.' + decimals_to_add
+ if negative_sign:
+ formatted_number = '-' + formatted_number
return formatted_number
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly/case.py
new/humanfriendly-9.2/humanfriendly/case.py
--- old/humanfriendly-8.1/humanfriendly/case.py 1970-01-01 01:00:00.000000000
+0100
+++ new/humanfriendly-9.2/humanfriendly/case.py 2020-04-19 02:22:19.000000000
+0200
@@ -0,0 +1,157 @@
+# Human friendly input/output in Python.
+#
+# Author: Peter Odding <[email protected]>
+# Last Change: April 19, 2020
+# URL: https://humanfriendly.readthedocs.io
+
+"""
+Simple case insensitive dictionaries.
+
+The :class:`CaseInsensitiveDict` class is a dictionary whose string keys
+are case insensitive. It works by automatically coercing string keys to
+:class:`CaseInsensitiveKey` objects. Keys that are not strings are
+supported as well, just without case insensitivity.
+
+At its core this module works by normalizing strings to lowercase before
+comparing or hashing them. It doesn't support proper case folding nor
+does it support Unicode normalization, hence the word "simple".
+"""
+
+# Standard library modules.
+import collections
+
+try:
+ # Python >= 3.3.
+ from collections.abc import Iterable, Mapping
+except ImportError:
+ # Python 2.7.
+ from collections import Iterable, Mapping
+
+# Modules included in our package.
+from humanfriendly.compat import basestring, unicode
+
+# Public identifiers that require documentation.
+__all__ = ("CaseInsensitiveDict", "CaseInsensitiveKey")
+
+
+class CaseInsensitiveDict(collections.OrderedDict):
+
+ """
+ Simple case insensitive dictionary implementation (that remembers
insertion order).
+
+ This class works by overriding methods that deal with dictionary keys to
+ coerce string keys to :class:`CaseInsensitiveKey` objects before calling
+ down to the regular dictionary handling methods. While intended to be
+ complete this class has not been extensively tested yet.
+ """
+
+ def __init__(self, other=None, **kw):
+ """Initialize a :class:`CaseInsensitiveDict` object."""
+ # Initialize our superclass.
+ super(CaseInsensitiveDict, self).__init__()
+ # Handle the initializer arguments.
+ self.update(other, **kw)
+
+ def coerce_key(self, key):
+ """
+ Coerce string keys to :class:`CaseInsensitiveKey` objects.
+
+ :param key: The value to coerce (any type).
+ :returns: If `key` is a string then a :class:`CaseInsensitiveKey`
+ object is returned, otherwise the value of `key` is
+ returned unmodified.
+ """
+ if isinstance(key, basestring):
+ key = CaseInsensitiveKey(key)
+ return key
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ """Create a case insensitive dictionary with keys from `iterable` and
values set to `value`."""
+ return cls((k, value) for k in iterable)
+
+ def get(self, key, default=None):
+ """Get the value of an existing item."""
+ return super(CaseInsensitiveDict, self).get(self.coerce_key(key),
default)
+
+ def pop(self, key, default=None):
+ """Remove an item from a case insensitive dictionary."""
+ return super(CaseInsensitiveDict, self).pop(self.coerce_key(key),
default)
+
+ def setdefault(self, key, default=None):
+ """Get the value of an existing item or add a new item."""
+ return super(CaseInsensitiveDict,
self).setdefault(self.coerce_key(key), default)
+
+ def update(self, other=None, **kw):
+ """Update a case insensitive dictionary with new items."""
+ if isinstance(other, Mapping):
+ # Copy the items from the given mapping.
+ for key, value in other.items():
+ self[key] = value
+ elif isinstance(other, Iterable):
+ # Copy the items from the given iterable.
+ for key, value in other:
+ self[key] = value
+ elif other is not None:
+ # Complain about unsupported values.
+ msg = "'%s' object is not iterable"
+ type_name = type(value).__name__
+ raise TypeError(msg % type_name)
+ # Copy the keyword arguments (if any).
+ for key, value in kw.items():
+ self[key] = value
+
+ def __contains__(self, key):
+ """Check if a case insensitive dictionary contains the given key."""
+ return super(CaseInsensitiveDict,
self).__contains__(self.coerce_key(key))
+
+ def __delitem__(self, key):
+ """Delete an item in a case insensitive dictionary."""
+ return super(CaseInsensitiveDict,
self).__delitem__(self.coerce_key(key))
+
+ def __getitem__(self, key):
+ """Get the value of an item in a case insensitive dictionary."""
+ return super(CaseInsensitiveDict,
self).__getitem__(self.coerce_key(key))
+
+ def __setitem__(self, key, value):
+ """Set the value of an item in a case insensitive dictionary."""
+ return super(CaseInsensitiveDict,
self).__setitem__(self.coerce_key(key), value)
+
+
+class CaseInsensitiveKey(unicode):
+
+ """
+ Simple case insensitive dictionary key implementation.
+
+ The :class:`CaseInsensitiveKey` class provides an intentionally simple
+ implementation of case insensitive strings to be used as dictionary keys.
+
+ If you need features like Unicode normalization or proper case folding
+ please consider using a more advanced implementation like the :pypi:`istr`
+ package instead.
+ """
+
+ def __new__(cls, value):
+ """Create a :class:`CaseInsensitiveKey` object."""
+ # Delegate string object creation to our superclass.
+ obj = unicode.__new__(cls, value)
+ # Store the lowercased string and its hash value.
+ normalized = obj.lower()
+ obj._normalized = normalized
+ obj._hash_value = hash(normalized)
+ return obj
+
+ def __hash__(self):
+ """Get the hash value of the lowercased string."""
+ return self._hash_value
+
+ def __eq__(self, other):
+ """Compare two strings as lowercase."""
+ if isinstance(other, CaseInsensitiveKey):
+ # Fast path (and the most common case): Comparison with same type.
+ return self._normalized == other._normalized
+ elif isinstance(other, unicode):
+ # Slow path: Comparison with strings that need lowercasing.
+ return self._normalized == other.lower()
+ else:
+ return NotImplemented
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly/compat.py
new/humanfriendly-9.2/humanfriendly/compat.py
--- old/humanfriendly-8.1/humanfriendly/compat.py 2020-03-01
19:23:29.000000000 +0100
+++ new/humanfriendly-9.2/humanfriendly/compat.py 2020-12-10
01:12:05.000000000 +0100
@@ -1,7 +1,7 @@
# Human friendly input/output in Python.
#
# Author: Peter Odding <[email protected]>
-# Last Change: March 1, 2020
+# Last Change: December 10, 2020
# URL: https://humanfriendly.readthedocs.io
"""
@@ -52,6 +52,7 @@
'is_unicode',
'monotonic',
'name2codepoint',
+ 'on_macos',
'on_windows',
'unichr',
'unicode',
@@ -132,6 +133,15 @@
return isinstance(value, unicode)
+def on_macos():
+ """
+ Check if we're running on Apple MacOS.
+
+ :returns: :data:`True` if running MacOS, :data:`False` otherwise.
+ """
+ return sys.platform.startswith('darwin')
+
+
def on_windows():
"""
Check if we're running on the Microsoft Windows OS.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly/sphinx.py
new/humanfriendly-9.2/humanfriendly/sphinx.py
--- old/humanfriendly-8.1/humanfriendly/sphinx.py 2020-03-01
20:01:16.000000000 +0100
+++ new/humanfriendly-9.2/humanfriendly/sphinx.py 2021-06-11
12:43:08.000000000 +0200
@@ -1,7 +1,7 @@
# Human friendly input/output in Python.
#
# Author: Peter Odding <[email protected]>
-# Last Change: March 1, 2020
+# Last Change: June 11, 2021
# URL: https://humanfriendly.readthedocs.io
"""
@@ -245,12 +245,16 @@
like that idea you may be better of calling the individual functions from
your own ``setup()`` function.
"""
+ from humanfriendly import __version__
+
enable_deprecation_notes(app)
enable_man_role(app)
enable_pypi_role(app)
enable_special_methods(app)
enable_usage_formatting(app)
+ return dict(parallel_read_safe=True, parallel_write_safe=True,
version=__version__)
+
def special_methods_callback(app, what, name, obj, skip, options):
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly/tests.py
new/humanfriendly-9.2/humanfriendly/tests.py
--- old/humanfriendly-8.1/humanfriendly/tests.py 2020-03-06
22:01:01.000000000 +0100
+++ new/humanfriendly-9.2/humanfriendly/tests.py 2021-06-11
12:43:08.000000000 +0200
@@ -4,7 +4,7 @@
# Tests for the `humanfriendly' package.
#
# Author: Peter Odding <[email protected]>
-# Last Change: March 6, 2020
+# Last Change: June 11, 2021
# URL: https://humanfriendly.readthedocs.io
"""Test suite for the `humanfriendly` package."""
@@ -44,6 +44,7 @@
prompts,
round_number,
)
+from humanfriendly.case import CaseInsensitiveDict, CaseInsensitiveKey
from humanfriendly.cli import main
from humanfriendly.compat import StringIO
from humanfriendly.decorators import cached
@@ -129,6 +130,60 @@
"""Container for the `humanfriendly` test suite."""
+ def test_case_insensitive_dict(self):
+ """Test the CaseInsensitiveDict class."""
+ # Test support for the dict(iterable) signature.
+ assert len(CaseInsensitiveDict([('key', True), ('KEY', False)])) == 1
+ # Test support for the dict(iterable, **kw) signature.
+ assert len(CaseInsensitiveDict([('one', True), ('ONE', False)],
one=False, two=True)) == 2
+ # Test support for the dict(mapping) signature.
+ assert len(CaseInsensitiveDict(dict(key=True, KEY=False))) == 1
+ # Test support for the dict(mapping, **kw) signature.
+ assert len(CaseInsensitiveDict(dict(one=True, ONE=False), one=False,
two=True)) == 2
+ # Test support for the dict(**kw) signature.
+ assert len(CaseInsensitiveDict(one=True, ONE=False, two=True)) == 2
+ # Test support for dict.fromkeys().
+ obj = CaseInsensitiveDict.fromkeys(["One", "one", "ONE", "Two", "two",
"TWO"])
+ assert len(obj) == 2
+ # Test support for dict.get().
+ obj = CaseInsensitiveDict(existing_key=42)
+ assert obj.get('Existing_Key') == 42
+ # Test support for dict.pop().
+ obj = CaseInsensitiveDict(existing_key=42)
+ assert obj.pop('Existing_Key') == 42
+ assert len(obj) == 0
+ # Test support for dict.setdefault().
+ obj = CaseInsensitiveDict(existing_key=42)
+ assert obj.setdefault('Existing_Key') == 42
+ obj.setdefault('other_key', 11)
+ assert obj['Other_Key'] == 11
+ # Test support for dict.__contains__().
+ obj = CaseInsensitiveDict(existing_key=42)
+ assert 'Existing_Key' in obj
+ # Test support for dict.__delitem__().
+ obj = CaseInsensitiveDict(existing_key=42)
+ del obj['Existing_Key']
+ assert len(obj) == 0
+ # Test support for dict.__getitem__().
+ obj = CaseInsensitiveDict(existing_key=42)
+ assert obj['Existing_Key'] == 42
+ # Test support for dict.__setitem__().
+ obj = CaseInsensitiveDict(existing_key=42)
+ obj['Existing_Key'] = 11
+ assert obj['existing_key'] == 11
+
+ def test_case_insensitive_key(self):
+ """Test the CaseInsensitiveKey class."""
+ # Test the __eq__() special method.
+ polite = CaseInsensitiveKey("Please don't shout")
+ rude = CaseInsensitiveKey("PLEASE DON'T SHOUT")
+ assert polite == rude
+ # Test the __hash__() special method.
+ mapping = {}
+ mapping[polite] = 1
+ mapping[rude] = 2
+ assert len(mapping) == 1
+
def test_capture_output(self):
"""Test the CaptureOutput class."""
with CaptureOutput() as capturer:
@@ -386,7 +441,7 @@
# Make sure milliseconds are never shown separately when
detailed=False.
# https://github.com/xolox/python-humanfriendly/issues/10
assert '1 minute, 1 second and 100 milliseconds' ==
format_timespan(61.10, detailed=True)
- assert '1 minute and 1.1 second' == format_timespan(61.10,
detailed=False)
+ assert '1 minute and 1.1 seconds' == format_timespan(61.10,
detailed=False)
# Test for loss of precision as reported in issue 11:
# https://github.com/xolox/python-humanfriendly/issues/11
assert '1 minute and 0.3 seconds' == format_timespan(60.300)
@@ -520,6 +575,8 @@
self.assertEqual('1,000', format_number(1000.12, 0))
self.assertEqual('1,000,000', format_number(1000000))
self.assertEqual('1,000,000.42', format_number(1000000.42))
+ # Regression test for
https://github.com/xolox/python-humanfriendly/issues/40.
+ self.assertEqual('-285.67', format_number(-285.67))
def test_round_number(self):
"""Test :func:`humanfriendly.round_number()`."""
@@ -673,6 +730,10 @@
assert concatenate(['one']) == 'one'
assert concatenate(['one', 'two']) == 'one and two'
assert concatenate(['one', 'two', 'three']) == 'one, two and three'
+ # Test the 'conjunction' option.
+ assert concatenate(['one', 'two', 'three'], conjunction='or') == 'one,
two or three'
+ # Test the 'serial_comma' option.
+ assert concatenate(['one', 'two', 'three'], serial_comma=True) ==
'one, two, and three'
def test_split(self):
"""Test :func:`humanfriendly.text.split()`."""
@@ -732,8 +793,8 @@
.replace(ANSI_HIDE_CURSOR, ''))
lines = [line for line in output.split(ANSI_ERASE_LINE) if line]
self.assertTrue(len(lines) > 0)
- self.assertTrue(all('test spinner' in l for l in lines))
- self.assertTrue(all('%' in l for l in lines))
+ self.assertTrue(all('test spinner' in line for line in lines))
+ self.assertTrue(all('%' in line for line in lines))
self.assertEqual(sorted(set(lines)), sorted(lines))
def test_automatic_spinner(self):
@@ -897,7 +958,7 @@
# https://github.com/xolox/python-humanfriendly/issues/28
returncode, output = run_cli(main, '--demo')
assert returncode == 0
- lines = [ansi_strip(l) for l in output.splitlines()]
+ lines = [ansi_strip(line) for line in output.splitlines()]
assert "Text styles:" in lines
assert "Foreground colors:" in lines
assert "Background colors:" in lines
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly/text.py
new/humanfriendly-9.2/humanfriendly/text.py
--- old/humanfriendly-8.1/humanfriendly/text.py 2020-03-01 19:23:29.000000000
+0100
+++ new/humanfriendly-9.2/humanfriendly/text.py 2020-12-01 01:05:44.000000000
+0100
@@ -1,7 +1,7 @@
# Human friendly input/output in Python.
#
# Author: Peter Odding <[email protected]>
-# Last Change: March 1, 2020
+# Last Change: December 1, 2020
# URL: https://humanfriendly.readthedocs.io
"""
@@ -20,7 +20,6 @@
"""
# Standard library modules.
-import math
import numbers
import random
import re
@@ -38,6 +37,7 @@
'is_empty_line',
'join_lines',
'pluralize',
+ 'pluralize_raw',
'random_string',
'split',
'split_paragraphs',
@@ -94,20 +94,40 @@
return ''.join(lines)
-def concatenate(items):
+def concatenate(items, conjunction='and', serial_comma=False):
"""
Concatenate a list of items in a human friendly way.
- :param items: A sequence of strings.
- :returns: A single string.
+ :param items:
+
+ A sequence of strings.
+
+ :param conjunction:
+
+ The word to use before the last item (a string, defaults to "and").
+
+ :param serial_comma:
+
+ :data:`True` to use a `serial comma`_, :data:`False` otherwise
+ (defaults to :data:`False`).
+
+ :returns:
+
+ A single string.
>>> from humanfriendly.text import concatenate
>>> concatenate(["eggs", "milk", "bread"])
'eggs, milk and bread'
+
+ .. _serial comma: https://en.wikipedia.org/wiki/Serial_comma
"""
items = list(items)
if len(items) > 1:
- return ', '.join(items[:-1]) + ' and ' + items[-1]
+ final_item = items.pop()
+ formatted = ', '.join(items)
+ if serial_comma:
+ formatted += ','
+ return ' '.join([formatted, conjunction, final_item])
elif items:
return items[0]
else:
@@ -276,19 +296,36 @@
"""
Combine a count with the singular or plural form of a word.
- If the plural form of the word is not provided it is obtained by
- concatenating the singular form of the word with the letter "s". Of course
- this will not always be correct, which is why you have the option to
- specify both forms.
+ :param count: The count (a number).
+ :param singular: The singular form of the word (a string).
+ :param plural: The plural form of the word (a string or :data:`None`).
+ :returns: The count and singular or plural word concatenated (a string).
+
+ See :func:`pluralize_raw()` for the logic underneath :func:`pluralize()`.
+ """
+ return '%s %s' % (count, pluralize_raw(count, singular, plural))
+
+
+def pluralize_raw(count, singular, plural=None):
+ """
+ Select the singular or plural form of a word based on a count.
:param count: The count (a number).
:param singular: The singular form of the word (a string).
:param plural: The plural form of the word (a string or :data:`None`).
- :returns: The count and singular/plural word concatenated (a string).
+ :returns: The singular or plural form of the word (a string).
+
+ When the given count is exactly 1.0 the singular form of the word is
+ selected, in all other cases the plural form of the word is selected.
+
+ If the plural form of the word is not provided it is obtained by
+ concatenating the singular form of the word with the letter "s". Of course
+ this will not always be correct, which is why you have the option to
+ specify both forms.
"""
if not plural:
plural = singular + 's'
- return '%s %s' % (count, singular if math.floor(float(count)) == 1 else
plural)
+ return singular if float(count) == 1.0 else plural
def random_string(length=(25, 100), characters=string.ascii_letters):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly/usage.py
new/humanfriendly-9.2/humanfriendly/usage.py
--- old/humanfriendly-8.1/humanfriendly/usage.py 2020-02-16
01:03:47.000000000 +0100
+++ new/humanfriendly-9.2/humanfriendly/usage.py 2021-06-11
12:43:08.000000000 +0200
@@ -1,7 +1,7 @@
# Human friendly input/output in Python.
#
# Author: Peter Odding <[email protected]>
-# Last Change: June 24, 2017
+# Last Change: June 11, 2021
# URL: https://humanfriendly.readthedocs.io
"""
@@ -258,7 +258,7 @@
('\n\n'.join(render_paragraph(p, meta_variables) for p in
split_paragraphs(description))).rstrip(),
])
csv_lines = csv_buffer.getvalue().splitlines()
- output.append('\n'.join(' %s' % l for l in csv_lines))
+ output.append('\n'.join(' %s' % line for line in csv_lines))
logger.debug("Rendered output: %s", output)
return '\n\n'.join(trim_empty_lines(o) for o in output)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly.egg-info/PKG-INFO
new/humanfriendly-9.2/humanfriendly.egg-info/PKG-INFO
--- old/humanfriendly-8.1/humanfriendly.egg-info/PKG-INFO 2020-03-06
22:01:19.000000000 +0100
+++ new/humanfriendly-9.2/humanfriendly.egg-info/PKG-INFO 2021-06-11
12:53:09.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 1.2
Name: humanfriendly
-Version: 8.1
+Version: 9.2
Summary: Human friendly output for text interfaces using Python
Home-page: https://humanfriendly.readthedocs.io
Author: Peter Odding
@@ -152,7 +152,7 @@
This software is licensed under the `MIT license`_.
- ?? 2020 Peter Odding.
+ ?? 2021 Peter Odding.
.. External references:
.. _#4: https://github.com/xolox/python-humanfriendly/issues/4
@@ -184,6 +184,7 @@
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Topic :: Communications
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/humanfriendly.egg-info/SOURCES.txt
new/humanfriendly-9.2/humanfriendly.egg-info/SOURCES.txt
--- old/humanfriendly-8.1/humanfriendly.egg-info/SOURCES.txt 2020-03-06
22:01:19.000000000 +0100
+++ new/humanfriendly-9.2/humanfriendly.egg-info/SOURCES.txt 2021-06-11
12:53:09.000000000 +0200
@@ -19,6 +19,7 @@
docs/images/spinner-with-progress.gif
docs/images/spinner-with-timer.gif
humanfriendly/__init__.py
+humanfriendly/case.py
humanfriendly/cli.py
humanfriendly/compat.py
humanfriendly/decorators.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/requirements-tests.txt
new/humanfriendly-9.2/requirements-tests.txt
--- old/humanfriendly-8.1/requirements-tests.txt 2020-03-01
19:23:29.000000000 +0100
+++ new/humanfriendly-9.2/requirements-tests.txt 2021-06-11
12:26:04.000000000 +0200
@@ -1,6 +1,6 @@
# Test suite requirements.
capturer >= 2.1
-coloredlogs >= 2.0
+coloredlogs >= 15.0.1
docutils >= 0.15
mock >= 3.0.5
pytest >= 3.0.7
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/requirements-travis.txt
new/humanfriendly-9.2/requirements-travis.txt
--- old/humanfriendly-8.1/requirements-travis.txt 2018-10-20
07:21:01.000000000 +0200
+++ new/humanfriendly-9.2/requirements-travis.txt 2021-06-11
12:26:04.000000000 +0200
@@ -1,3 +1,5 @@
--requirement=requirements-checks.txt
--requirement=requirements-tests.txt
coveralls
+# Dependency of coveralls:
+cryptography < 3; python_version == '2.7' and platform_python_implementation
== "PyPy"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/humanfriendly-8.1/setup.py
new/humanfriendly-9.2/setup.py
--- old/humanfriendly-8.1/setup.py 2020-02-16 02:15:19.000000000 +0100
+++ new/humanfriendly-9.2/setup.py 2021-06-11 12:43:08.000000000 +0200
@@ -125,6 +125,7 @@
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Topic :: Communications',