Hello community,

here is the log from the commit of package python-pyhibp for openSUSE:Factory 
checked in at 2019-08-28 18:37:02
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyhibp (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyhibp.new.7948 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyhibp"

Wed Aug 28 18:37:02 2019 rev:3 rq:726615 version:4.0.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyhibp/python-pyhibp.changes      
2019-07-02 10:39:47.186708781 +0200
+++ /work/SRC/openSUSE:Factory/.python-pyhibp.new.7948/python-pyhibp.changes    
2019-08-28 18:37:07.869266500 +0200
@@ -1,0 +2,12 @@
+Tue Aug 27 14:45:31 UTC 2019 - Marketa Calabkova <[email protected]>
+
+- update to version 4.0.0
+  * Breaking API change: The HIBP API now requires an API key for 
+    calls which search by account.
+  * A User Agent must now be manually set.
+  * Python 2.7 Support Dropped
+  * Function modified: suffix_search(hash_prefix=prefix) is now the 
+    method to search for hash suffixes. 
+  * Read upstream changelog for more info.
+
+-------------------------------------------------------------------

Old:
----
  pyhibp-3.1.0.tar.gz

New:
----
  pyhibp-4.0.0.tar.gz

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

Other differences:
------------------
++++++ python-pyhibp.spec ++++++
--- /var/tmp/diff_new_pack.plwHdw/_old  2019-08-28 18:37:08.737266349 +0200
+++ /var/tmp/diff_new_pack.plwHdw/_new  2019-08-28 18:37:08.737266349 +0200
@@ -17,22 +17,20 @@
 
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%define skip_python2 1
 Name:           python-pyhibp
-Version:        3.1.0
+Version:        4.0.0
 Release:        0
 Summary:        An interface to Troy Hunt's 'Have I Been Pwned' public API
 License:        AGPL-3.0-or-later
 Group:          Development/Languages/Python
 URL:            https://gitlab.com/kitsunix/pyHIBP/pyHIBP
 Source:         
https://files.pythonhosted.org/packages/source/p/pyhibp/pyhibp-%{version}.tar.gz
-BuildRequires:  %{python_module pytest}
 BuildRequires:  %{python_module requests >= 2.20.0 }
 BuildRequires:  %{python_module setuptools}
-BuildRequires:  %{python_module tox}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-requests >= 2.20.0
-Requires:       python-six >= 1.11.0
 BuildArch:      noarch
 %python_subpackages
 

++++++ pyhibp-3.1.0.tar.gz -> pyhibp-4.0.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/CHANGELOG.md 
new/pyhibp-4.0.0/CHANGELOG.md
--- old/pyhibp-3.1.0/CHANGELOG.md       2019-06-30 10:46:01.000000000 +0200
+++ new/pyhibp-4.0.0/CHANGELOG.md       2019-08-11 06:27:23.000000000 +0200
@@ -1,6 +1,33 @@
 pyHIBP Changelog
 ================
-v3.1.0 (In progress...)
+v4.0.0 (2019-08-11)
+------------------------
+- **Breaking API change**: The HIBP API now requires an API key for calls 
which search by account. This means calls to
+  ``pyhibp.get_account_breaches()`` and ``pyhibp.get_pastes()``. API keys can 
be obtained [the HIBP website](https://haveibeenpwned.com/API/Key),
+  and must be loaded into the module by calling 
``pyhibp.set_api_key(key="your_key")`` prior to calling the affected
+  functions.
+- The other functions inside ``pyhibp``, as well as ``pwnedpasswords``, do not 
require an API key to use.
+- As a note, our testing harness has not been tested against valid API keys, 
however we anticipate no issues. If issues
+  are discovered, please raise an issue with details, ideally with a merge 
request to fix the issue.
+- **A User Agent must now be manually set**: As per the HIBP API, the 
User-Agent header set in calls to the API must
+  reflect the name of the calling application. Directly from the API 
documentation, "the user agent should accurately
+  describe the nature of the API consumer such that it can be clearly 
identified in the request. Not doing so may result
+  in the request being blocked." As such, users of this module must now set 
the User-Agent by calling
+  ``pyhibp.set_user_agent(ua=ua_string)``, where ``ua_string`` is a string 
which identifies the application implementing
+  the ``pyhibp`` module.
+- **Python 2.7 Support Dropped**: With Python 2.7 support being dropped in 
January 2020, as stated in the notes for v3.0.0
+  and v3.1.0, ``pyhibp`` is dropping support for Python 2.7.
+- **Function modified**: ``suffix_search(hash_prefix=prefix)`` is now the 
method to search for hash suffixes. The compatability
+  shim left when introducing the function in v3.1.0 has been removed (and thus 
``first_5_hash_chars`` is no longer a valid
+  parameter to the function ``is_password_breached()``).
+- **Return type changes**: As per the changelog from v3.1.0, the return type 
for empty sets has changed as follows for the
+  following functions in the ``pyhibp`` module:
+    - ``get_account_breaches`` -> ``[] / list``
+    - ``get_all_breaches`` -> ``[] / list``
+    - ``get_single_breach`` -> ``{} / dict``
+    - ``get_pastes`` -> ``[] / list``
+
+v3.1.0 (2019-06-30)
 -----------------------
 - **New function**: ``pwnedpasswords.suffix_search(hash_prefix=prefix)`` was 
created in order to have a dedicated function
   return the suffix list.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/PKG-INFO new/pyhibp-4.0.0/PKG-INFO
--- old/pyhibp-3.1.0/PKG-INFO   2019-06-30 11:17:16.000000000 +0200
+++ new/pyhibp-4.0.0/PKG-INFO   2019-08-11 06:27:37.000000000 +0200
@@ -1,102 +1,116 @@
-Metadata-Version: 2.1
-Name: pyhibp
-Version: 3.1.0
-Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API
-Home-page: https://gitlab.com/kitsunix/pyHIBP/pyHIBP
-Author: Kyra F. Kitsune
-License: UNKNOWN
-Description: pyHIBP (pyHave I Been Pwned)
-        ============================
-        
[![image](https://img.shields.io/pypi/v/pyHIBP.svg)](https://pypi.org/project/pyHIBP/)
-        
[![image](https://img.shields.io/pypi/l/pyHIBP.svg)](https://pypi.org/project/pyHIBP/)
-        
[![image](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master)
-        
-        
-        A Python interface to Troy Hunt's 'Have I Been Pwned?' (HIBP) public 
API. A full reference to the API
-        specification can be found at the [HIBP API 
Reference](https://haveibeenpwned.com/API/v2).
-        
-        This module detects when the rate limit of the API has been hit, and 
raises a RuntimeError when the limit
-        is exceeded, or when another API-defined error condition is 
encountered based on the submitted data. When
-        data is found from a call, the data returned will be in the format as 
retrieved from the endpoint, documented
-        in the return-type information for the relevant function.
-        
-        Note that the `pwnedpasswords` API backend does not have a rate limit. 
If you are intending to bulk-query passwords or
-        hashes, you should consider downloading the raw data files accessible 
via the [Pwned Passwords](https://haveibeenpwned.com/Passwords) page.
-        
-        Installing
-        ----------
-        ```bash
-        $ pip install pyhibp
-        ```
-        
-        Example usage
-        -------------
-        For an interactive example, check out the Jupyter Notebook for 
[`pyhibp`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.ipynb),
-        as well as 
[`pyhibp.pwnedpasswords`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.pwnedpasswords.ipynb).
-        
-        ```python
-        import pyhibp
-        from pyhibp import pwnedpasswords as pw
-        
-        # Check a password to see if it has been disclosed in a public breach 
corpus
-        resp = pw.is_password_breached(password="secret")
-        if resp:
-            print("Password breached!")
-            print("This password was used {0} time(s) before.".format(resp))
-        
-        # Get breaches that affect a given account
-        resp = pyhibp.get_account_breaches(account="[email protected]", 
truncate_response=True)
-        
-        # Get all breach information
-        resp = pyhibp.get_all_breaches()
-        
-        # Get a single breach
-        resp = pyhibp.get_single_breach(breach_name="Adobe")
-        
-        # Get pastes affecting a given email address
-        resp = pyhibp.get_pastes(email_address="[email protected]")
-        
-        # Get data classes in the HIBP system
-        resp = pyhibp.get_data_classes()
-        ```
-        
-        Developing
-        ----------
-        This project is currently intended to be compatible with Python 2 and 
Python 3. As such, we use virtual environments via `pipenv`.
-        To develop or test, execute the following:
-        
-        ```bash
-        # Install the prerequisite virtual environment provider
-        $ pip install pipenv
-        # Initialize the pipenv environment and install the module within it
-        $ make dev
-        # To run PEP8, tests, and check the manifest
-        $ make tox
-        ```
-        
-        Other commands can be found in the `Makefile`.
-        
-        Goals
-        -----
-        - Synchronize to the latest HIBP API(s), implementing endpoint 
accessing functions where it makes sense. For instance,
-          in the interest of security, the ability to submit a SHA-1 to the 
Pwned Passwords endpoint is not implemented. See
-          "Regarding password checking" below for further details.
-        - For breaches and pastes, act as an intermediary; return the JSON as 
received from the service.
-        
-        Regarding password checking
-        ---------------------------
-        - For passwords, the option to supply a plaintext password to check is 
provided as an implementation convenience.
-        - For added security, `pwnedpasswords.is_password_breached()` only 
transmits the first five characters of the SHA-1
-          hash to the Pwned Passwords API endpoint; a secure password will 
remain secure without disclosing the full hash.
-        
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Developers
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or 
later (AGPLv3+)
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.5
-Description-Content-Type: text/markdown
-Provides-Extra: dev
+Metadata-Version: 2.1
+Name: pyhibp
+Version: 4.0.0
+Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API
+Home-page: https://gitlab.com/kitsunix/pyHIBP/pyHIBP
+Author: Kyra F. Kitsune
+License: UNKNOWN
+Description: pyHIBP (pyHave I Been Pwned)
+        ============================
+        
[![image](https://img.shields.io/pypi/v/pyHIBP.svg)](https://pypi.org/project/pyHIBP/)
+        
[![image](https://img.shields.io/pypi/l/pyHIBP.svg)](https://pypi.org/project/pyHIBP/)
+        
[![image](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master)
+        
+        
+        A Python interface to Troy Hunt's 'Have I Been Pwned?' (HIBP) public 
API. A full reference to the API
+        specification can be found at the [HIBP API 
Reference](https://haveibeenpwned.com/API/v2).
+        
+        This module detects when the rate limit of the API has been hit, and 
raises a RuntimeError when the limit
+        is exceeded, or when another API-defined error condition is 
encountered based on the submitted data. When
+        data is found from a call, the data returned will be in the format as 
retrieved from the endpoint, documented
+        in the return-type information for the relevant function.
+        
+        Note that the `pwnedpasswords` API backend does not have a rate limit. 
If you are intending to bulk-query passwords or
+        hashes, you should consider downloading the raw data files accessible 
via the [Pwned Passwords](https://haveibeenpwned.com/Passwords) page.
+        
+        Installing
+        ----------
+        ```bash
+        $ pip install pyhibp
+        ```
+        
+        Example usage
+        -------------
+        For an interactive example, check out the Jupyter Notebook for 
[`pyhibp`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.ipynb),
+        as well as 
[`pyhibp.pwnedpasswords`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.pwnedpasswords.ipynb).
+        
+        ```python
+        import pyhibp
+        from pyhibp import pwnedpasswords as pw
+        
+        # Required: A descriptive user agent must be set describing the 
application consuming
+        #   the HIBP API
+        pyhibp.set_user_agent(ua="Awesome application/0.0.1 (An awesome 
description)")
+        
+        # Check a password to see if it has been disclosed in a public breach 
corpus
+        resp = pw.is_password_breached(password="secret")
+        if resp:
+            print("Password breached!")
+            print("This password was used {0} time(s) before.".format(resp))
+        
+        # Get data classes in the HIBP system
+        resp = pyhibp.get_data_classes()
+        
+        # Get all breach information
+        resp = pyhibp.get_all_breaches()
+        
+        # Get a single breach
+        resp = pyhibp.get_single_breach(breach_name="Adobe")
+        
+        # An API key is required for calls which search by email address
+        #   (so get_pastes/get_account_breaches)
+        # See <https://haveibeenpwned.com/API/Key>
+        HIBP_API_KEY = None
+        
+        if HIBP_API_KEY:
+            # Set the API key prior to using the functions which require it.
+            pyhibp.set_api_key(key=HIBP_API_KEY)
+        
+            # Get pastes affecting a given email address
+            resp = pyhibp.get_pastes(email_address="[email protected]")
+        
+            # Get breaches that affect a given account
+            resp = pyhibp.get_account_breaches(account="[email protected]", 
truncate_response=True)
+        ```
+        
+        Developing
+        ----------
+        This project is currently intended to be compatible with Python 2 and 
Python 3. As such, we use virtual environments via `pipenv`.
+        To develop or test, execute the following:
+        
+        ```bash
+        # Install the prerequisite virtual environment provider
+        $ pip install pipenv
+        # Initialize the pipenv environment and install the module within it
+        $ make dev
+        # To run PEP8, tests, and check the manifest
+        $ make tox
+        ```
+        
+        Other commands can be found in the `Makefile`.
+        
+        Goals
+        -----
+        - Synchronize to the latest HIBP API(s), implementing endpoint 
accessing functions where it makes sense. For instance,
+          in the interest of security, the ability to submit a SHA-1 to the 
Pwned Passwords endpoint is not implemented. See
+          "Regarding password checking" below for further details.
+        - For breaches and pastes, act as an intermediary; return the JSON as 
received from the service.
+        
+        Regarding password checking
+        ---------------------------
+        - For passwords, the option to supply a plaintext password to check is 
provided as an implementation convenience.
+        - For added security, `pwnedpasswords.is_password_breached()` only 
transmits the first five characters of the SHA-1
+          hash to the Pwned Passwords API endpoint; a secure password will 
remain secure without disclosing the full hash.
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or 
later (AGPLv3+)
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Description-Content-Type: text/markdown
+Provides-Extra: dev
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/Pipfile new/pyhibp-4.0.0/Pipfile
--- old/pyhibp-3.1.0/Pipfile    2019-06-30 10:46:01.000000000 +0200
+++ new/pyhibp-4.0.0/Pipfile    2019-08-11 06:27:23.000000000 +0200
@@ -5,7 +5,6 @@
 
 [packages]
 requests = ">=2.20.0"
-six = ">=1.11.0"
 
 [dev-packages]
 tox = "*"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/README.md new/pyhibp-4.0.0/README.md
--- old/pyhibp-3.1.0/README.md  2019-06-30 10:46:01.000000000 +0200
+++ new/pyhibp-4.0.0/README.md  2019-08-11 06:27:23.000000000 +0200
@@ -31,14 +31,18 @@
 import pyhibp
 from pyhibp import pwnedpasswords as pw
 
+# Required: A descriptive user agent must be set describing the application 
consuming
+#   the HIBP API
+pyhibp.set_user_agent(ua="Awesome application/0.0.1 (An awesome description)")
+
 # Check a password to see if it has been disclosed in a public breach corpus
 resp = pw.is_password_breached(password="secret")
 if resp:
     print("Password breached!")
     print("This password was used {0} time(s) before.".format(resp))
 
-# Get breaches that affect a given account
-resp = pyhibp.get_account_breaches(account="[email protected]", 
truncate_response=True)
+# Get data classes in the HIBP system
+resp = pyhibp.get_data_classes()
 
 # Get all breach information
 resp = pyhibp.get_all_breaches()
@@ -46,11 +50,20 @@
 # Get a single breach
 resp = pyhibp.get_single_breach(breach_name="Adobe")
 
-# Get pastes affecting a given email address
-resp = pyhibp.get_pastes(email_address="[email protected]")
+# An API key is required for calls which search by email address
+#   (so get_pastes/get_account_breaches)
+# See <https://haveibeenpwned.com/API/Key>
+HIBP_API_KEY = None
+
+if HIBP_API_KEY:
+    # Set the API key prior to using the functions which require it.
+    pyhibp.set_api_key(key=HIBP_API_KEY)
 
-# Get data classes in the HIBP system
-resp = pyhibp.get_data_classes()
+    # Get pastes affecting a given email address
+    resp = pyhibp.get_pastes(email_address="[email protected]")
+
+    # Get breaches that affect a given account
+    resp = pyhibp.get_account_breaches(account="[email protected]", 
truncate_response=True)
 ```
 
 Developing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/setup.cfg new/pyhibp-4.0.0/setup.cfg
--- old/pyhibp-3.1.0/setup.cfg  2019-06-30 11:17:16.000000000 +0200
+++ new/pyhibp-4.0.0/setup.cfg  2019-08-11 06:27:37.000000000 +0200
@@ -1,11 +1,11 @@
-[bdist_wheel]
-universal = 1
-
-[flake8]
-exclude = .git
-ignore = E501
-
-[egg_info]
-tag_build = 
-tag_date = 0
-
+[bdist_wheel]
+universal = 0
+
+[flake8]
+exclude = .git
+ignore = E501
+
+[egg_info]
+tag_build = 
+tag_date = 0
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/setup.py new/pyhibp-4.0.0/setup.py
--- old/pyhibp-3.1.0/setup.py   2019-06-30 10:44:52.000000000 +0200
+++ new/pyhibp-4.0.0/setup.py   2019-08-11 06:27:23.000000000 +0200
@@ -107,10 +107,11 @@
 
         # Specify the Python versions you support here. In particular, ensure
         # that you indicate whether you support Python 2, Python 3 or both.
-        'Programming Language :: Python :: 2',
-        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3 :: Only',
         'Programming Language :: Python :: 3',
         'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
     ],
 
     # This field adds keywords for your project which will appear on the
@@ -141,7 +142,6 @@
     # https://packaging.python.org/en/latest/requirements.html
     install_requires=[
         "requests >= 2.20.0",
-        "six >= 1.11.0",
     ],  # Optional
 
     # List additional groups of dependencies here (e.g. development
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp/__init__.py 
new/pyhibp-4.0.0/src/pyhibp/__init__.py
--- old/pyhibp-3.1.0/src/pyhibp/__init__.py     2019-06-30 10:46:01.000000000 
+0200
+++ new/pyhibp-4.0.0/src/pyhibp/__init__.py     2019-08-11 06:27:23.000000000 
+0200
@@ -1,22 +1,81 @@
 import requests
-import six
 
-from pyhibp import __version__
 
-HIBP_API_BASE_URI = "https://haveibeenpwned.com/api/v2/";
+HIBP_API_BASE_URI = "https://haveibeenpwned.com/api/v3/";
 HIBP_API_ENDPOINT_BREACH_SINGLE = "breach/"
 HIBP_API_ENDPOINT_BREACHES = "breaches"
 HIBP_API_ENDPOINT_BREACHED_ACCT = "breachedaccount/"
 HIBP_API_ENDPOINT_DATA_CLASSES = "dataclasses"
 HIBP_API_ENDPOINT_PASTES = "pasteaccount/"
 
-# The HIBP API requires that a useragent be set.
-pyHIBP_USERAGENT = "pyHIBP/{0} (A Python interface to the public HIBP 
API)".format(__version__.__version__)
+
 # The headers we send along with each request
-pyHIBP_HEADERS = {'User-Agent': pyHIBP_USERAGENT}
+pyHIBP_HEADERS = {
+    'User-Agent': None,
+    'hibp-api-key': None
+}
+
+
+def _require_user_agent(function):
+    """
+    A decorator which enforces setting the User-Agent on each request. As per 
the HIBP API, the UA must be set, or a
+    HTTP 403 response wil; be raised.
+
+    :raises: RuntimeError if the application User-Agent is not set via 
``set_user_agent()`` prior to invoking functions
+    which communicate with the HIBP or PwnedPasswords APIs.
+    """
+    def inner(*args, **kwargs):
+        if pyHIBP_HEADERS.get("User-Agent") is None:
+            raise RuntimeError("The User-Agent must be set. Call 
pyhibp.set_user_agent(ua=your_agent_string) first.")
+        return function(*args, **kwargs)
+    return inner
+
+
+def set_user_agent(ua: str = None):
+    """
+    Sets the User-Agent to be used in subsequent calls sent to the HIBP API 
backend. The UA set should be the name of the
+    application implementing the pyhibp module, and accurately describe the 
nature of the API consumer.
+
+    See: https://haveibeenpwned.com/API/v3#UserAgent
+
+    The HIBP API enforces setting a User-Agent, otherwise an HTTP 403 will be 
returned.
+
+    :param ua: A string representing the application name which is 
implementing the pyhibp module which is sent with
+    each request to the HIBP API.
+    """
+    pyHIBP_HEADERS['User-Agent'] = ua
+
+
+def _require_api_key(function):
+    """
+    A decorator which enforces setting an API key on functions which require 
one to be set. Not setting an API key would
+    result in an HTTP 401 Unauthorised.
+
+    :raises: RuntimeError if the API key for enabling searching the HIBP API 
backend by account (email) is not set prior
+    to calling functions which invoke said capability.
+    """
+    def inner(*args, **kwargs):
+        if pyHIBP_HEADERS.get('hibp-api-key') is None:
+            raise RuntimeError("A HIBP API key is required for this call. Call 
pyhibp.set_api_key(key=your_key) first.")
+        return function(*args, **kwargs)
+    return inner
+
 
+def set_api_key(key: str = None):
+    """
+    Set an API key for use in all future calls to the pyhibp module which 
search the HIBP database by email address.
+
+    Per the HIBP API documentation: "Authorisation is required for all APIs 
that enable searching HIBP by email address,
+    namely retrieving all breaches for an account and retrieving all pastes 
for an account."
+
+    A key can be purchased from <https://haveibeenpwned.com/API/Key>.
+
+    :param key: The key to set for each subsequent request where it is 
required.
+    """
+    pyHIBP_HEADERS['hibp-api-key'] = key
 
-def _process_response(response):
+
+def _process_response(response) -> bool:
     """
     Process the `requests` response from the call to the HIBP API endpoints.
 
@@ -35,6 +94,11 @@
         # Bad request - The account does not comply with an acceptable format 
(i.e., it's an empty string)
         raise RuntimeError(
             "HTTP 400 - Bad request - The account does not comply with an 
acceptable format (i.e., it's an empty string).")
+    elif response.status_code == 401:
+        # Unauthorised - the API key provided was not valid
+        raise RuntimeError(
+            "HTTP 401 - Unauthorised - The API key provided was not valid."
+        )
     elif response.status_code == 403:
         # Forbidden - no user agent has been specified in the request
         raise RuntimeError("HTTP 403 - User agent required for HIBP API 
requests, but no user agent was sent to the API endpoint.")
@@ -48,13 +112,17 @@
         raise NotImplementedError("Returned HTTP status code of {0} was not 
expected.".format(response.status_code))
 
 
-def get_account_breaches(account=None, domain=None, truncate_response=False, 
include_unverified=False):
+@_require_api_key
+@_require_user_agent
+def get_account_breaches(account: str = None, domain: str = None, 
truncate_response: bool = False, include_unverified: bool = False) -> list:
     """
     Gets breaches for a specified account from the HIBP system, optionally 
restricting the returned results
     to a specified domain.
 
-    :param account: The user's account name (such as an email address or a 
user-name). Default None. `str` type.
-    :param domain: The domain to check for breaches. Default None. `str` type.
+    This function requires a HIBP API key to be set. See ``set_api_key()``.
+
+    :param account: The user's account name (such as an email address or a 
user-name). Default None. `str` type. Required.
+    :param domain: The domain to check for breaches. Default None. `str` type. 
Optional
     :param truncate_response: If ``account`` is specified, truncates the 
response down to the breach names.
     Default False. `bool` type.
     :param include_unverified: If set to True, unverified breaches are 
included in the result. Default False. `bool` type
@@ -63,10 +131,10 @@
     the HIBP API.
     :rtype: list
     """
-    # Account/Domain don't need to be specified, but they must be text if so.
-    if account is None or not isinstance(account, six.string_types):
+    if account is None or not isinstance(account, str):
         raise AttributeError("The account parameter must be specified, and 
must be a string.")
-    if domain is not None and not isinstance(domain, six.string_types):
+    # The domain does not need to specified, but is must be text, if so.
+    if domain is not None and not isinstance(domain, str):
         raise AttributeError("The domain parameter, if specified, must be a 
string.")
 
     uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_BREACHED_ACCT + account
@@ -79,14 +147,15 @@
         "includeUnverified": include_unverified,
     }
     resp = requests.get(url=uri, params=query_string_payload, 
headers=pyHIBP_HEADERS)
+
     if _process_response(response=resp):
         return resp.json()
     else:
-        # TODO: v4.0.0: return []
-        return False
+        return []
 
 
-def get_all_breaches(domain=None):
+@_require_user_agent
+def get_all_breaches(domain: str = None) -> list:
     """
     Returns a listing of all sites breached in the HIBP database.
 
@@ -95,21 +164,22 @@
     if ``domain`` is specified, but the resultant list would be length zero.
     :rtype: list
     """
-    if domain is not None and not isinstance(domain, six.string_types):
+    if domain is not None and not isinstance(domain, str):
         raise AttributeError("The domain parameter, if specified, must be a 
string.")
 
     uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_BREACHES
     query_string_payload = {'domain': domain}
     resp = requests.get(url=uri, params=query_string_payload, 
headers=pyHIBP_HEADERS)
+
     # The API will return HTTP200 even if resp.json is length zero.
     if _process_response(response=resp) and len(resp.json()) > 0:
         return resp.json()
     else:
-        # TODO: v4.0.0: return []
-        return False
+        return []
 
 
-def get_single_breach(breach_name=None):
+@_require_user_agent
+def get_single_breach(breach_name: str = None) -> dict:
     """
     Returns a single breach's information from the HIBP's database.
 
@@ -118,40 +188,45 @@
     database. Boolean False is returned if the specified breach was not found.
     :rtype: dict
     """
-    if not isinstance(breach_name, six.string_types):
+    if not isinstance(breach_name, str):
         raise AttributeError("The breach_name must be specified, and be a 
string.")
 
     uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_BREACH_SINGLE + breach_name
     resp = requests.get(url=uri, headers=pyHIBP_HEADERS)
+
     if _process_response(response=resp):
         return resp.json()
     else:
-        # TODO: v4.0.0: return {}
-        return False
+        return {}
 
 
-def get_pastes(email_address=None):
+@_require_api_key
+@_require_user_agent
+def get_pastes(email_address: str = None) -> list:
     """
     Retrieve all pastes for a specified email address.
 
+    This function requires a HIBP API key to be set. See ``set_api_key()``.
+
     :param email_address: The email address to search. Required. `str` type.
     :return: A list object containing one or more dict objects corresponding 
to the pastes the specified email
     address was found in. Boolean False returned if no pastes are detected for 
the given account.
     :rtype: list
     """
-    if not isinstance(email_address, six.string_types):
+    if not isinstance(email_address, str):
         raise AttributeError("The email address supplied must be provided, and 
be a string.")
 
     uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_PASTES + email_address
     resp = requests.get(url=uri, headers=pyHIBP_HEADERS)
+
     if _process_response(response=resp):
         return resp.json()
     else:
-        # TODO: v4.0.0: return []
-        return False
+        return []
 
 
-def get_data_classes():
+@_require_user_agent
+def get_data_classes() -> list:
     """
     Retrieves all available data classes from the HIBP API.
 
@@ -161,6 +236,7 @@
     """
     uri = HIBP_API_BASE_URI + HIBP_API_ENDPOINT_DATA_CLASSES
     resp = requests.get(url=uri, headers=pyHIBP_HEADERS)
+
     if _process_response(response=resp):
         return resp.json()
     else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp/__version__.py 
new/pyhibp-4.0.0/src/pyhibp/__version__.py
--- old/pyhibp-3.1.0/src/pyhibp/__version__.py  2019-06-30 11:09:11.000000000 
+0200
+++ new/pyhibp-4.0.0/src/pyhibp/__version__.py  2019-08-11 06:27:23.000000000 
+0200
@@ -4,5 +4,5 @@
 # |)\/| |||)|
 # | /
 
-__version__ = '3.1.0'
+__version__ = '4.0.0'
 __url__ = 'https://gitlab.com/kitsunix/pyHIBP/pyHIBP'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp/pwnedpasswords.py 
new/pyhibp-4.0.0/src/pyhibp/pwnedpasswords.py
--- old/pyhibp-3.1.0/src/pyhibp/pwnedpasswords.py       2019-06-30 
10:46:02.000000000 +0200
+++ new/pyhibp-4.0.0/src/pyhibp/pwnedpasswords.py       2019-08-11 
06:27:23.000000000 +0200
@@ -1,10 +1,9 @@
 import hashlib
-import warnings
 
 import requests
-import six
 
 import pyhibp
+from pyhibp import _require_user_agent
 
 PWNED_PASSWORDS_API_BASE_URI = "https://api.pwnedpasswords.com/";
 PWNED_PASSWORDS_API_ENDPOINT_RANGE_SEARCH = "range/"
@@ -12,7 +11,7 @@
 RESPONSE_ENCODING = "utf-8-sig"
 
 
-def is_password_breached(password=None, sha1_hash=None, 
first_5_hash_chars=None):
+def is_password_breached(password: str = None, sha1_hash: str = None) -> int:
     """
     Execute a search for a password via the k-anonymity model, checking for 
hashes which match a specified
     prefix instead of supplying the full hash to the Pwned Passwords API.
@@ -39,46 +38,34 @@
     :rtype: int
     """
     # Parameter validation section
-    if not any([password, first_5_hash_chars, sha1_hash]):
-        raise AttributeError("One of password, sha1_hash, or 
first_5_hash_chars must be provided.")
-    elif password is not None and not isinstance(password, six.string_types):
-        raise AttributeError("password must be a string type.")
-    elif sha1_hash is not None and not isinstance(sha1_hash, six.string_types):
-        raise AttributeError("sha1_hash must be a string type.")
-    elif first_5_hash_chars is not None and not isinstance(first_5_hash_chars, 
six.string_types):
-        raise AttributeError("first_5_hash_chars must be a string type.")
-    if first_5_hash_chars and len(first_5_hash_chars) != 5:
-        raise AttributeError("first_5_hash_chars must be of length 5.")
+    if not any([password, sha1_hash]):
+        raise AttributeError("Either password or sha1_hash must be provided.")
+    elif password is not None and not isinstance(password, str):
+        raise AttributeError("password must be a string.")
+    elif sha1_hash is not None and not isinstance(sha1_hash, str):
+        raise AttributeError("sha1_hash must be a string.")
 
     if password:
         sha1_hash = hashlib.sha1(password.encode('utf-8')).hexdigest()
     if sha1_hash:
         # The HIBP API stores the SHA-1 hashes in uppercase, so ensure we have 
it as uppercase here
         sha1_hash = sha1_hash.upper()
-        first_5_hash_chars = sha1_hash[0:5]
+        hash_prefix = sha1_hash[0:5]
 
-    suffix_list = suffix_search(hash_prefix=first_5_hash_chars)
+    suffix_list = suffix_search(hash_prefix=hash_prefix)
 
-    if not sha1_hash:
-        # TODO: v4.0.0: Remove this codepath (first_5_hash_chars)
-        warnings.warn("""
-            Hash suffix searching is being moved to its own discrete function, 
`suffix_search()`. Call `suffix_search(hash_prefix=prefix)`
-            instead. Hash prefixes will not return from is_password_breached() 
in a future release.
-        """)
-        # Return the list of hash suffixes.
-        return suffix_list
-    else:
-        # Since the full SHA-1 hash was provided, check to see if it was in 
the resultant hash suffixes returned.
-        for hash_suffix in suffix_list:
-            if sha1_hash[5:] in hash_suffix:
-                # We found the full hash, so return
-                return int(hash_suffix.split(':')[1])
+    # Since the full SHA-1 hash was provided, check to see if it was in the 
resultant hash suffixes returned.
+    for hash_suffix in suffix_list:
+        if sha1_hash[5:] in hash_suffix:
+            # We found the full hash, so return
+            return int(hash_suffix.split(':')[1])
 
-        # If we get here, there was no match to the supplied SHA-1 hash; 
return zero.
-        return 0
+    # If we get here, there was no match to the supplied SHA-1 hash; return 
zero.
+    return 0
 
 
-def suffix_search(hash_prefix=None):
+@_require_user_agent
+def suffix_search(hash_prefix: str = None) -> list:
     """
     Returns a list of SHA-1 hash suffixes, consisting of the SHA-1 hash 
characters after position five,
     and the number of times that password hash was found in the HIBP database, 
colon separated.
@@ -105,8 +92,8 @@
     :return: A list of hash suffixes.
     :rtype: list
     """
-    if not hash_prefix or not isinstance(hash_prefix, six.string_types):
-        raise AttributeError("hash_prefix must be a supplied, and be a 
string-type.")
+    if not hash_prefix or not isinstance(hash_prefix, str):
+        raise AttributeError("hash_prefix must be a supplied, and be a 
string.")
     if hash_prefix and len(hash_prefix) != 5:
         raise AttributeError("hash_prefix must be of length 5.")
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp.egg-info/PKG-INFO 
new/pyhibp-4.0.0/src/pyhibp.egg-info/PKG-INFO
--- old/pyhibp-3.1.0/src/pyhibp.egg-info/PKG-INFO       2019-06-30 
11:17:15.000000000 +0200
+++ new/pyhibp-4.0.0/src/pyhibp.egg-info/PKG-INFO       2019-08-11 
06:27:37.000000000 +0200
@@ -1,102 +1,116 @@
-Metadata-Version: 2.1
-Name: pyhibp
-Version: 3.1.0
-Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API
-Home-page: https://gitlab.com/kitsunix/pyHIBP/pyHIBP
-Author: Kyra F. Kitsune
-License: UNKNOWN
-Description: pyHIBP (pyHave I Been Pwned)
-        ============================
-        
[![image](https://img.shields.io/pypi/v/pyHIBP.svg)](https://pypi.org/project/pyHIBP/)
-        
[![image](https://img.shields.io/pypi/l/pyHIBP.svg)](https://pypi.org/project/pyHIBP/)
-        
[![image](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master)
-        
-        
-        A Python interface to Troy Hunt's 'Have I Been Pwned?' (HIBP) public 
API. A full reference to the API
-        specification can be found at the [HIBP API 
Reference](https://haveibeenpwned.com/API/v2).
-        
-        This module detects when the rate limit of the API has been hit, and 
raises a RuntimeError when the limit
-        is exceeded, or when another API-defined error condition is 
encountered based on the submitted data. When
-        data is found from a call, the data returned will be in the format as 
retrieved from the endpoint, documented
-        in the return-type information for the relevant function.
-        
-        Note that the `pwnedpasswords` API backend does not have a rate limit. 
If you are intending to bulk-query passwords or
-        hashes, you should consider downloading the raw data files accessible 
via the [Pwned Passwords](https://haveibeenpwned.com/Passwords) page.
-        
-        Installing
-        ----------
-        ```bash
-        $ pip install pyhibp
-        ```
-        
-        Example usage
-        -------------
-        For an interactive example, check out the Jupyter Notebook for 
[`pyhibp`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.ipynb),
-        as well as 
[`pyhibp.pwnedpasswords`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.pwnedpasswords.ipynb).
-        
-        ```python
-        import pyhibp
-        from pyhibp import pwnedpasswords as pw
-        
-        # Check a password to see if it has been disclosed in a public breach 
corpus
-        resp = pw.is_password_breached(password="secret")
-        if resp:
-            print("Password breached!")
-            print("This password was used {0} time(s) before.".format(resp))
-        
-        # Get breaches that affect a given account
-        resp = pyhibp.get_account_breaches(account="[email protected]", 
truncate_response=True)
-        
-        # Get all breach information
-        resp = pyhibp.get_all_breaches()
-        
-        # Get a single breach
-        resp = pyhibp.get_single_breach(breach_name="Adobe")
-        
-        # Get pastes affecting a given email address
-        resp = pyhibp.get_pastes(email_address="[email protected]")
-        
-        # Get data classes in the HIBP system
-        resp = pyhibp.get_data_classes()
-        ```
-        
-        Developing
-        ----------
-        This project is currently intended to be compatible with Python 2 and 
Python 3. As such, we use virtual environments via `pipenv`.
-        To develop or test, execute the following:
-        
-        ```bash
-        # Install the prerequisite virtual environment provider
-        $ pip install pipenv
-        # Initialize the pipenv environment and install the module within it
-        $ make dev
-        # To run PEP8, tests, and check the manifest
-        $ make tox
-        ```
-        
-        Other commands can be found in the `Makefile`.
-        
-        Goals
-        -----
-        - Synchronize to the latest HIBP API(s), implementing endpoint 
accessing functions where it makes sense. For instance,
-          in the interest of security, the ability to submit a SHA-1 to the 
Pwned Passwords endpoint is not implemented. See
-          "Regarding password checking" below for further details.
-        - For breaches and pastes, act as an intermediary; return the JSON as 
received from the service.
-        
-        Regarding password checking
-        ---------------------------
-        - For passwords, the option to supply a plaintext password to check is 
provided as an implementation convenience.
-        - For added security, `pwnedpasswords.is_password_breached()` only 
transmits the first five characters of the SHA-1
-          hash to the Pwned Passwords API endpoint; a secure password will 
remain secure without disclosing the full hash.
-        
-Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Developers
-Classifier: Topic :: Software Development :: Libraries :: Python Modules
-Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or 
later (AGPLv3+)
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.5
-Description-Content-Type: text/markdown
-Provides-Extra: dev
+Metadata-Version: 2.1
+Name: pyhibp
+Version: 4.0.0
+Summary: An interface to Troy Hunt's 'Have I Been Pwned' public API
+Home-page: https://gitlab.com/kitsunix/pyHIBP/pyHIBP
+Author: Kyra F. Kitsune
+License: UNKNOWN
+Description: pyHIBP (pyHave I Been Pwned)
+        ============================
+        
[![image](https://img.shields.io/pypi/v/pyHIBP.svg)](https://pypi.org/project/pyHIBP/)
+        
[![image](https://img.shields.io/pypi/l/pyHIBP.svg)](https://pypi.org/project/pyHIBP/)
+        
[![image](https://mybinder.org/badge.svg)](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master)
+        
+        
+        A Python interface to Troy Hunt's 'Have I Been Pwned?' (HIBP) public 
API. A full reference to the API
+        specification can be found at the [HIBP API 
Reference](https://haveibeenpwned.com/API/v2).
+        
+        This module detects when the rate limit of the API has been hit, and 
raises a RuntimeError when the limit
+        is exceeded, or when another API-defined error condition is 
encountered based on the submitted data. When
+        data is found from a call, the data returned will be in the format as 
retrieved from the endpoint, documented
+        in the return-type information for the relevant function.
+        
+        Note that the `pwnedpasswords` API backend does not have a rate limit. 
If you are intending to bulk-query passwords or
+        hashes, you should consider downloading the raw data files accessible 
via the [Pwned Passwords](https://haveibeenpwned.com/Passwords) page.
+        
+        Installing
+        ----------
+        ```bash
+        $ pip install pyhibp
+        ```
+        
+        Example usage
+        -------------
+        For an interactive example, check out the Jupyter Notebook for 
[`pyhibp`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.ipynb),
+        as well as 
[`pyhibp.pwnedpasswords`](https://mybinder.org/v2/gl/kitsunix%2FpyHIBP%2FpyHIBP-binder/master?filepath=/pyHIBP.pwnedpasswords.ipynb).
+        
+        ```python
+        import pyhibp
+        from pyhibp import pwnedpasswords as pw
+        
+        # Required: A descriptive user agent must be set describing the 
application consuming
+        #   the HIBP API
+        pyhibp.set_user_agent(ua="Awesome application/0.0.1 (An awesome 
description)")
+        
+        # Check a password to see if it has been disclosed in a public breach 
corpus
+        resp = pw.is_password_breached(password="secret")
+        if resp:
+            print("Password breached!")
+            print("This password was used {0} time(s) before.".format(resp))
+        
+        # Get data classes in the HIBP system
+        resp = pyhibp.get_data_classes()
+        
+        # Get all breach information
+        resp = pyhibp.get_all_breaches()
+        
+        # Get a single breach
+        resp = pyhibp.get_single_breach(breach_name="Adobe")
+        
+        # An API key is required for calls which search by email address
+        #   (so get_pastes/get_account_breaches)
+        # See <https://haveibeenpwned.com/API/Key>
+        HIBP_API_KEY = None
+        
+        if HIBP_API_KEY:
+            # Set the API key prior to using the functions which require it.
+            pyhibp.set_api_key(key=HIBP_API_KEY)
+        
+            # Get pastes affecting a given email address
+            resp = pyhibp.get_pastes(email_address="[email protected]")
+        
+            # Get breaches that affect a given account
+            resp = pyhibp.get_account_breaches(account="[email protected]", 
truncate_response=True)
+        ```
+        
+        Developing
+        ----------
+        This project is currently intended to be compatible with Python 2 and 
Python 3. As such, we use virtual environments via `pipenv`.
+        To develop or test, execute the following:
+        
+        ```bash
+        # Install the prerequisite virtual environment provider
+        $ pip install pipenv
+        # Initialize the pipenv environment and install the module within it
+        $ make dev
+        # To run PEP8, tests, and check the manifest
+        $ make tox
+        ```
+        
+        Other commands can be found in the `Makefile`.
+        
+        Goals
+        -----
+        - Synchronize to the latest HIBP API(s), implementing endpoint 
accessing functions where it makes sense. For instance,
+          in the interest of security, the ability to submit a SHA-1 to the 
Pwned Passwords endpoint is not implemented. See
+          "Regarding password checking" below for further details.
+        - For breaches and pastes, act as an intermediary; return the JSON as 
received from the service.
+        
+        Regarding password checking
+        ---------------------------
+        - For passwords, the option to supply a plaintext password to check is 
provided as an implementation convenience.
+        - For added security, `pwnedpasswords.is_password_breached()` only 
transmits the first five characters of the SHA-1
+          hash to the Pwned Passwords API endpoint; a secure password will 
remain secure without disclosing the full hash.
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or 
later (AGPLv3+)
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Description-Content-Type: text/markdown
+Provides-Extra: dev
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/src/pyhibp.egg-info/requires.txt 
new/pyhibp-4.0.0/src/pyhibp.egg-info/requires.txt
--- old/pyhibp-3.1.0/src/pyhibp.egg-info/requires.txt   2019-06-30 
11:17:15.000000000 +0200
+++ new/pyhibp-4.0.0/src/pyhibp.egg-info/requires.txt   2019-08-11 
06:27:37.000000000 +0200
@@ -1,5 +1,4 @@
 requests>=2.20.0
-six>=1.11.0
 
 [dev]
 check-manifest
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/test/conftest.py 
new/pyhibp-4.0.0/test/conftest.py
--- old/pyhibp-3.1.0/test/conftest.py   2019-06-30 10:46:02.000000000 +0200
+++ new/pyhibp-4.0.0/test/conftest.py   2019-08-11 06:27:23.000000000 +0200
@@ -3,12 +3,28 @@
 import pytest
 
 import pyhibp
+from pyhibp import __version__
 
 
 @pytest.fixture(autouse=True)
-def dev_user_agent(monkeypatch):
-    ua_string = pyhibp.pyHIBP_USERAGENT
-    monkeypatch.setattr(pyhibp, 'pyHIBP_USERAGENT', ua_string + " (Testing 
Suite)")
+def set_development_user_agent(monkeypatch):
+    """
+    All calls to the HIBP/PwnedPasswords endpoints require a user agent to be 
set. So set one on all tests.
+    """
+    ua_string = "pyHIBP/{version} (A Python interface to the public HIBP API; 
Testing suite)".format(
+        version=__version__.__version__,
+    )
+    monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', ua_string)
+
+
[email protected]
+def use_fake_api_key(monkeypatch):
+    """
+    As we have a decorator enforcing that an API key is set, we need to set a 
dummy key to test that our
+    parameter checking is working as intended. (So, we use this when we know 
we aren't going to be sending
+    a request that would otherwise be blocked for lack of a valid HIBP API 
key.)
+    """
+    monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'hibp-api-key', 'NotARealKey')
 
 
 @pytest.fixture(name="sleep")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/test/test_pwnedpasswords.py 
new/pyhibp-4.0.0/test/test_pwnedpasswords.py
--- old/pyhibp-3.1.0/test/test_pwnedpasswords.py        2019-06-30 
10:46:02.000000000 +0200
+++ new/pyhibp-4.0.0/test/test_pwnedpasswords.py        2019-08-11 
06:27:23.000000000 +0200
@@ -2,6 +2,7 @@
 
 import pytest
 
+import pyhibp
 from pyhibp import pwnedpasswords as pw
 
 # While the pwnedpasswords endpoint does not have a limit, be kind anyway. 1 
second sleep.
@@ -19,47 +20,19 @@
         # is_password_breached(password=None, first_5_hash_chars=None, 
sha1_hash=None):
         with pytest.raises(AttributeError) as execinfo:
             pw.is_password_breached()
-        assert "One of password, sha1_hash, or first_5_hash_chars must be 
provided." in str(execinfo.value)
+        assert "Either password or sha1_hash must be provided." in 
str(execinfo.value)
 
     def test_password_not_string_raises(self):
         # is_password_breached(password=123, first_5_hash_chars=None, 
sha1_hash=None):
         with pytest.raises(AttributeError) as execinfo:
             pw.is_password_breached(password=123)
-        assert "password must be a string type." in str(execinfo.value)
-
-    def test_first_5_hash_chars_not_string_raises(self):
-        # TODO: Deprecated: To be removed in next major release in favor of 
pw.suffix_search()
-        # is_password_breached(password=None, first_5_hash_chars=123, 
sha1_hash=None):
-        with pytest.raises(AttributeError) as execinfo:
-            pw.is_password_breached(first_5_hash_chars=123)
-        assert "first_5_hash_chars must be a string type." in 
str(execinfo.value)
-
-    def test_first_5_hash_chars_not_length_five_raises(self):
-        # TODO: Deprecated: To be removed in next major release in favor of 
pw.suffix_search()
-        # is_password_breached(password=None, first_5_hash_chars="123456", 
sha1_hash=None):
-        with pytest.raises(AttributeError) as execinfo:
-            pw.is_password_breached(first_5_hash_chars="123456")
-        assert "first_5_hash_chars must be of length 5." in str(execinfo.value)
+        assert "password must be a string." in str(execinfo.value)
 
     def test_sha1_hash_not_string_raises(self):
         # is_password_breached(password=None, first_5_hash_chars=None, 
sha1_hash=123):
         with pytest.raises(AttributeError) as execinfo:
             pw.is_password_breached(sha1_hash=123)
-        assert "sha1_hash must be a string type." in str(execinfo.value)
-
-    @pytest.mark.usefixtures('sleep')
-    def test_list_of_partial_hashes_returned_with_5chars(self):
-        # TODO: Deprecated: To be removed in next major release in favor of 
pw.suffix_search()
-        # is_password_breached(password=None, 
first_5_hash_chars=TEST_PASSWORD_SHA1_HASH[0:5], sha1_hash=None):
-        resp = 
pw.is_password_breached(first_5_hash_chars=TEST_PASSWORD_SHA1_HASH[0:5])
-        assert isinstance(resp, list)
-        assert len(resp) > 100
-        match_found = False
-        for entry in resp:
-            if TEST_PASSWORD_SHA1_HASH[5:] in entry.lower():
-                match_found = True
-                break
-        assert match_found
+        assert "sha1_hash must be a string." in str(execinfo.value)
 
     @pytest.mark.usefixtures('sleep')
     def test_provide_password_to_function(self):
@@ -91,13 +64,13 @@
         # def suffix_search(hash_prefix=None):
         with pytest.raises(AttributeError) as execinfo:
             pw.suffix_search()
-        assert "hash_prefix must be a supplied, and be a string-type." in 
str(execinfo.value)
+        assert "hash_prefix must be a supplied, and be a string." in 
str(execinfo.value)
 
     def test_hash_prefix_not_string_raises(self):
         # def suffix_search(hash_prefix=123):
         with pytest.raises(AttributeError) as execinfo:
             pw.suffix_search(hash_prefix=123)
-        assert "hash_prefix must be a supplied, and be a string-type." in 
str(execinfo.value)
+        assert "hash_prefix must be a supplied, and be a string." in 
str(execinfo.value)
 
     def test_first_5_hash_chars_not_length_five_raises(self):
         # suffix_search(hash_prefix="123456"):
@@ -120,3 +93,14 @@
                 match_found = True
                 break
         assert match_found
+
+    def test_user_agent_must_be_set_or_raise(self, monkeypatch):
+        """
+        The HIBP backend requires a User-Agent; ensure we're forcing one to be 
set.
+
+        Additionally, `is_password_breached` calls `suffix_search`, so we only 
need to check this function.
+        """
+        monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None)
+        with pytest.raises(RuntimeError) as execinfo:
+            pw.suffix_search(hash_prefix=TEST_PASSWORD_SHA1_HASH[0:5])
+        assert "The User-Agent must be set. Call 
pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/test/test_pyHIBP.py 
new/pyhibp-4.0.0/test/test_pyHIBP.py
--- old/pyhibp-3.1.0/test/test_pyHIBP.py        2019-06-30 10:46:02.000000000 
+0200
+++ new/pyhibp-4.0.0/test/test_pyHIBP.py        2019-08-11 06:27:23.000000000 
+0200
@@ -1,5 +1,3 @@
-import uuid
-
 import pytest
 
 import pyhibp
@@ -17,6 +15,14 @@
 
 class TestGetAccountBreaches(object):
     @pytest.mark.usefixtures('sleep')
+    def test_get_breaches_api_key_must_be_specified_or_raise(self):
+        with pytest.raises(RuntimeError) as excinfo:
+            # Will raise because an API key has not been set on this request.
+            pyhibp.get_account_breaches(account=TEST_ACCOUNT)
+        assert "A HIBP API key is required for this call. Call 
pyhibp.set_api_key(key=your_key) first." in str(excinfo.value)
+
+    @pytest.mark.skip("Unable to test due to lack of purchased API key")
+    @pytest.mark.usefixtures('sleep')
     def test_get_breaches_account(self):
         # get_account_breaches(account=TEST_ACCOUNT, domain=None, 
truncate_response=False, include_unverified=False):
         resp = pyhibp.get_account_breaches(account=TEST_ACCOUNT)
@@ -25,6 +31,7 @@
         assert len(resp) >= 20
         assert isinstance(resp[0], dict)
 
+    @pytest.mark.skip("Unable to test due to lack of purchased API key")
     @pytest.mark.usefixtures('sleep')
     def test_get_breaches_account_with_domain(self):
         # get_account_breaches(account=TEST_ACCOUNT, domain=TEST_DOMAIN, 
truncate_response=False, include_unverified=False):
@@ -35,6 +42,7 @@
         assert isinstance(resp[0], dict)
         assert resp[0]['Name'] == TEST_DOMAIN_NAME
 
+    @pytest.mark.skip("Unable to test due to lack of purchased API key")
     @pytest.mark.usefixtures('sleep')
     def test_get_breaches_account_with_truncation(self):
         # get_account_breaches(account=TEST_ACCOUNT, domain=None, 
truncate_response=True, include_unverified=False):
@@ -48,6 +56,7 @@
         assert 'Name' in item
         assert 'DataClasses' not in item
 
+    @pytest.mark.skip("Unable to test due to lack of purchased API key")
     @pytest.mark.usefixtures('sleep')
     def test_get_breaches_retrieve_all_breaches_with_unverified(self):
         # get_account_breaches(account=TEST_ACCOUNT, domain=None, 
truncate_response=False, include_unverified=True):
@@ -62,37 +71,48 @@
                 break
         assert has_unverified
 
+    @pytest.mark.skip("Unable to test due to lack of purchased API key")
     @pytest.mark.usefixtures('sleep')
     def test_get_breaches_return_false_if_no_accounts(self):
         # get_account_breaches(account=TEST_PASSWORD_SHA1_HASH, domain=None, 
truncate_response=False, include_unverified=False):
         resp = 
pyhibp.get_account_breaches(account=TEST_NONEXISTENT_ACCOUNT_NAME)
         assert not resp
-        assert isinstance(resp, bool)
-        # TODO: v4.0.0:
-        # assert not resp
-        # assert isinstance(resp, list)
+        assert isinstance(resp, list)
 
+    @pytest.mark.usefixtures('use_fake_api_key')
     def test_get_breaches_raise_if_account_is_not_specified(self):
         # get_account_breaches(account=1, domain=None, 
truncate_response=False, include_unverified=False):
         with pytest.raises(AttributeError) as excinfo:
-            # Will raise because the account must be a string (specifically, 
six.text_type)
+            # Will raise because the account must be a string
             pyhibp.get_account_breaches(account=None)
         assert "The account parameter must be specified, and must be a string" 
in str(excinfo.value)
 
+    @pytest.mark.usefixtures('use_fake_api_key')
     def test_get_breaches_raise_if_account_is_not_string(self):
         # get_account_breaches(account=1, domain=None, 
truncate_response=False, include_unverified=False):
         with pytest.raises(AttributeError) as excinfo:
-            # Will raise because the account must be a string (specifically, 
six.text_type)
+            # Will raise because the account must be a string
             pyhibp.get_account_breaches(account=1)
         assert "The account parameter must be specified, and must be a string" 
in str(excinfo.value)
 
+    @pytest.mark.usefixtures('use_fake_api_key')
     def test_get_breaches_raise_if_domain_is_not_string(self):
         # get_account_breaches(account=TEST_ACCOUNT, domain=1, 
truncate_response=False, include_unverified=False):
         with pytest.raises(AttributeError) as excinfo:
-            # Will raise because the domain must be a string (specifically, 
six.text_type)
+            # Will raise because the domain must be a string
             pyhibp.get_account_breaches(account=TEST_ACCOUNT, domain=1)
         assert "The domain parameter, if specified, must be a string" in 
str(excinfo.value)
 
+    @pytest.mark.usefixtures('use_fake_api_key')
+    def test_user_agent_must_be_set_or_raise(self, monkeypatch):
+        """
+        The HIBP backend requires a User-Agent; ensure we're forcing one to be 
set on all functions
+        """
+        monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None)
+        with pytest.raises(RuntimeError) as execinfo:
+            pyhibp.get_account_breaches(account=TEST_ACCOUNT)
+        assert "The User-Agent must be set. Call 
pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value)
+
 
 class TestGetAllBreaches(object):
     @pytest.mark.usefixtures('sleep')
@@ -117,18 +137,24 @@
     def test_get_all_breaches_false_if_domain_does_not_exist(self):
         resp = pyhibp.get_all_breaches(domain=TEST_NONEXISTENT_ACCOUNT_NAME)
         assert not resp
-        assert isinstance(resp, bool)
-        # TODO: v4.0.0:
-        # assert not resp
-        # assert isinstance(resp, list)
+        assert isinstance(resp, list)
 
     def test_get_all_breaches_raise_if_not_string(self):
         # def get_all_breaches(domain=1):
         with pytest.raises(AttributeError) as excinfo:
-            # Will raise because the domain must be a string (specifically, 
six.text_type)
+            # Will raise because the domain must be a string
             pyhibp.get_all_breaches(domain=1)
         assert "The domain parameter, if specified, must be a string" in 
str(excinfo.value)
 
+    def test_user_agent_must_be_set_or_raise(self, monkeypatch):
+        """
+        The HIBP backend requires a User-Agent; ensure we're forcing one to be 
set on all functions
+        """
+        monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None)
+        with pytest.raises(RuntimeError) as execinfo:
+            pyhibp.get_all_breaches()
+        assert "The User-Agent must be set. Call 
pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value)
+
 
 class TestGetSingleBreach(object):
     @pytest.mark.usefixtures('sleep')
@@ -142,11 +168,8 @@
     def test_get_single_breach_when_breach_does_not_exist(self):
         # get_single_breach(breach_name="ThisShouldNotExist")
         resp = pyhibp.get_single_breach(breach_name="ThisShouldNotExist")
-        # Boolean False will be returned from the above (as there is no breach 
named what we gave it).
         assert not resp
-        # TODO: v4.0.0:
-        # assert not resp
-        # assert isinstance(resp, dict)
+        assert isinstance(resp, dict)
 
     def test_get_single_breach_raise_when_breach_name_not_specified(self):
         # get_single_breach()
@@ -158,14 +181,32 @@
     def test_get_single_breach_raise_when_breach_name_is_not_a_string(self):
         # get_single_breach(breach_name=1)
         with pytest.raises(AttributeError) as excinfo:
-            # Will raise because the breach_name must be a string 
(specifically, six.text_type)
+            # Will raise because the breach_name must be a string
             pyhibp.get_single_breach(breach_name=1)
         assert "The breach_name must be specified, and be a string" in 
str(excinfo.value)
 
+    def test_user_agent_must_be_set_or_raise(self, monkeypatch):
+        """
+        The HIBP backend requires a User-Agent; ensure we're forcing one to be 
set on all functions
+        """
+        monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None)
+        with pytest.raises(RuntimeError) as execinfo:
+            pyhibp.get_single_breach(breach_name=TEST_DOMAIN_NAME)
+        assert "The User-Agent must be set. Call 
pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value)
+
 
 class TestGetPastes(object):
     @pytest.mark.usefixtures('sleep')
+    def test_get_pastes_api_key_must_be_specified_or_raise(self):
+        with pytest.raises(RuntimeError) as excinfo:
+            # Will raise because an API key has not been set on this request.
+            pyhibp.get_pastes(email_address=TEST_ACCOUNT)
+        assert "A HIBP API key is required for this call. Call 
pyhibp.set_api_key(key=your_key) first." in str(excinfo.value)
+
+    @pytest.mark.skip("Unable to test due to lack of purchased API key")
+    @pytest.mark.usefixtures('sleep')
     def test_get_pastes(self):
+        print(pyhibp.pyHIBP_HEADERS)
         # get_pastes(email_address=TEST_ACCOUNT):
         resp = pyhibp.get_pastes(email_address=TEST_ACCOUNT)
         # The return value is a list, containing multiple dicts (1 or more)
@@ -173,30 +214,50 @@
         for item in resp:
             assert isinstance(item, dict)
 
+    @pytest.mark.skip("Unable to test due to lack of purchased API key")
+    @pytest.mark.usefixtures('sleep')
     def test_get_pastes_return_false_if_no_account(self):
+        print(pyhibp.pyHIBP_HEADERS)
         # get_pastes(email_address=TEST_ACCOUNT):
         resp = pyhibp.get_pastes(email_address=TEST_NONEXISTENT_ACCOUNT_NAME + 
"@example.invalid")
         assert not resp
-        assert isinstance(resp, bool)
-        # TODO: v4.0.0:
-        # assert not resp
-        # assert isinstance(resp, list)
+        assert isinstance(resp, list)
 
-    @pytest.mark.usefixtures('sleep')
+    @pytest.mark.usefixtures('use_fake_api_key')
     def test_get_pastes_raise_if_email_not_specified(self):
         # get_pastes():
         with pytest.raises(AttributeError) as excinfo:
             pyhibp.get_pastes()
         assert "The email address supplied must be provided, and be a string" 
in str(excinfo.value)
 
+    @pytest.mark.usefixtures('use_fake_api_key')
     def test_get_pastes_raise_if_email_not_string(self):
         # get_pastes(email_address=1):
         with pytest.raises(AttributeError) as excinfo:
             pyhibp.get_pastes(email_address=1)
         assert "The email address supplied must be provided, and be a string" 
in str(excinfo.value)
 
+    @pytest.mark.usefixtures('use_fake_api_key')
+    def test_user_agent_must_be_set_or_raise(self, monkeypatch):
+        """
+        The HIBP backend requires a User-Agent; ensure we're forcing one to be 
set on all functions
+        """
+        monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None)
+        with pytest.raises(RuntimeError) as execinfo:
+            pyhibp.get_pastes(email_address=TEST_ACCOUNT)
+        assert "The User-Agent must be set. Call 
pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value)
+
 
 class TestGetDataClasses(object):
+    def test_user_agent_must_be_set_or_raise(self, monkeypatch):
+        """
+        The HIBP backend requires a User-Agent; ensure we're forcing one to be 
set on all functions
+        """
+        monkeypatch.setitem(pyhibp.pyHIBP_HEADERS, 'User-Agent', None)
+        with pytest.raises(RuntimeError) as execinfo:
+            pyhibp.get_data_classes()
+        assert "The User-Agent must be set. Call 
pyhibp.set_user_agent(ua=your_agent_string) first." in str(execinfo.value)
+
     @pytest.mark.usefixtures('sleep')
     def test_get_data_classes(self):
         # get_data_classes():
@@ -207,28 +268,7 @@
 
 
 class TestMiscellaneous(object):
-    @pytest.mark.xfail(reason="The rate limit exists in the API docs, but 
responses are cached, and even attempting to manually (via browser) hit the 
limit isn't happening.")
-    @pytest.mark.usefixtures('sleep')
-    def test_raise_if_rate_limit_exceeded(self):
-        """ The API will respond the same to all exceeded rate limits across 
all endpoints """
-        # The rate limit exists, however all responses are cached; so we need 
to generate some random "accounts".
-        rand_accts = 
["{0}@test-suite.pyhibp.example.com".format(str(uuid.uuid4())) for j in 
range(4)]
-
-        with pytest.raises(RuntimeError) as excinfo:
-            for item in rand_accts:
-                pyhibp.get_account_breaches(account=item, 
truncate_response=True)
-        assert "HTTP 429" in str(excinfo.value)
-
-    @pytest.mark.usefixtures('sleep')
-    def test_raise_if_useragent_is_not_set(self, monkeypatch):
-        # This should never be encountered normally, since we have the 
module-level variable/constant;
-        # That said, test it, since we can, and since we might as well cover 
the line of code.
-        head = {'User-Agent': ''}
-        monkeypatch.setattr(pyhibp, 'pyHIBP_HEADERS', head)
-        with pytest.raises(RuntimeError) as excinfo:
-            
pyhibp.get_account_breaches(account="{0}@test-suite.pyhibp.example.com".format(str(uuid.uuid4())))
-        assert "HTTP 403" in str(excinfo.value)
-
+    @pytest.mark.skip("Unable to test due to lack of purchased API key")
     @pytest.mark.usefixtures('sleep')
     def test_raise_if_invalid_format_submitted(self):
         # For example, if a null (0x00) character is submitted to an endpoint.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyhibp-3.1.0/tox.ini new/pyhibp-4.0.0/tox.ini
--- old/pyhibp-3.1.0/tox.ini    2019-06-30 10:46:02.000000000 +0200
+++ new/pyhibp-4.0.0/tox.ini    2019-08-11 06:27:23.000000000 +0200
@@ -1,9 +1,8 @@
 [tox]
-envlist = py{27,35,36,37}
+envlist = py{35,36,37}
 
 [testenv]
 basepython =
-    py27: python2.7
     py35: python3.5
     py36: python3.6
     py37: python3.7


Reply via email to