New Upstream Release - python-hupper

Ready changes

Summary

Merged new upstream version: 1.12 (was: 1.11).

Resulting package

Built on 2023-06-01T19:34 (took 4m16s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-hupper

Lintian Result

Diff

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..5737055
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+# Set update schedule for GitHub Actions
+
+version: 2
+updates:
+
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      # Check for updates to GitHub Actions every weekday
+      interval: "daily"
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
new file mode 100644
index 0000000..ff8e63c
--- /dev/null
+++ b/.github/workflows/ci-tests.yml
@@ -0,0 +1,97 @@
+name: Build and test
+
+on:
+    # Only on pushes to main or one of the release branches we build on push
+    push:
+        branches:
+            - main
+            - "[0-9].[0-9]+-branch"
+        tags:
+            - "*"
+    # Build pull requests
+    pull_request:
+
+jobs:
+    test:
+        strategy:
+            matrix:
+                py:
+                    - "3.7"
+                    - "3.8"
+                    - "3.9"
+                    - "3.10"
+                    - "3.11"
+                    - "pypy-3.8"
+                os:
+                    - "ubuntu-latest"
+                    - "windows-2022"
+                    - "macos-12"
+                architecture:
+                    - x64
+                    - x86
+
+                include:
+                    # Only run coverage on ubuntu-20.04, except on pypy3
+                    - os: "ubuntu-latest"
+                      pytest-args: "--cov"
+                    - os: "ubuntu-latest"
+                      py: "pypy-3.8"
+                      pytest-args: ""
+
+                exclude:
+                    # Linux and macOS don't have x86 python
+                    - os: "ubuntu-latest"
+                      architecture: x86
+                    - os: "macos-12"
+                      architecture: x86
+
+        name: "Python: ${{ matrix.py }}-${{ matrix.architecture }} on ${{ matrix.os }}"
+        runs-on: ${{ matrix.os }}
+        steps:
+            - uses: actions/checkout@v3
+            - name: Setup python
+              uses: actions/setup-python@v4
+              with:
+                  python-version: ${{ matrix.py }}
+                  architecture: ${{ matrix.architecture }}
+            - run: pip install tox
+            - run: ulimit -n 4096
+              if: ${{ runner.os == 'macOS' }}
+            - name: Running tox
+              run: tox -e py -- ${{ matrix.pytest-args }}
+    coverage:
+        runs-on: ubuntu-latest
+        name: Validate coverage
+        steps:
+            - uses: actions/checkout@v3
+            - name: Setup python
+              uses: actions/setup-python@v4
+              with:
+                  python-version: 3.9
+                  architecture: x64
+            - run: pip install tox
+            - run: tox -e py39,coverage
+    docs:
+        runs-on: ubuntu-latest
+        name: Build the documentation
+        steps:
+            - uses: actions/checkout@v3
+            - name: Setup python
+              uses: actions/setup-python@v4
+              with:
+                  python-version: 3.9
+                  architecture: x64
+            - run: pip install tox
+            - run: tox -e docs
+    lint:
+        runs-on: ubuntu-latest
+        name: Lint the package
+        steps:
+            - uses: actions/checkout@v3
+            - name: Setup python
+              uses: actions/setup-python@v4
+              with:
+                  python-version: 3.9
+                  architecture: x64
+            - run: pip install tox
+            - run: tox -e lint
diff --git a/CHANGES.rst b/CHANGES.rst
index 5a9bafc..6b02a04 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,3 +1,20 @@
+1.12 (2023-04-02)
+=================
+
+- When the reloader is stopped, exit with the same code received from the
+  subprocess.
+  See https://github.com/Pylons/hupper/pull/81
+
+1.11 (2022-01-02)
+=================
+
+- Drop support for Python 2.7, 3.4, 3.5, and 3.6.
+
+- Add support/testing for Python 3.10, and 3.11.
+
+- Explicitly require ``reload_interval`` set greater than ``0`` to avoid
+  spinning the CPU needlessly.
+
 1.10.3 (2021-05-13)
 ===================
 
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 0176efd..5564ff2 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -102,9 +102,10 @@ Before you submit a pull request, check that it meets these guidelines:
 2. If the pull request adds functionality, the docs should be updated. Put
    your new functionality into a function with a docstring, and add the
    feature to the list in README.rst.
-3. The pull request should work for Python 2.7, 3.4 and 3.5, and for PyPy. Check
-   https://travis-ci.org/Pylons/hupper/pull_requests
-   and make sure that the tests pass for all supported Python versions.
+3. The pull request should work for Python 3.7 and up and for PyPy 3.8.
+4. When your pull request is posted, a maintainer will click the button to run
+   Github Actions, afterwards validate that your PR is valid for all tested
+   platforms/Python versions
 
 Tips
 ----
diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt
index fbfeaa6..8cd4bbd 100644
--- a/CONTRIBUTORS.txt
+++ b/CONTRIBUTORS.txt
@@ -110,3 +110,4 @@ Contributors
 - Jens Carl (2017-05-22)
 - Eric Atkin (2019-02-15)
 - Yeray Díaz Díaz (2019-10-03)
+- Marcel Jackwerth (2023-03-23)
diff --git a/MANIFEST.in b/MANIFEST.in
index 39fefc1..73d7730 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,7 @@
 graft src/hupper
 graft tests
 graft docs
+graft .github
 prune docs/_build
 
 include README.rst
@@ -11,6 +12,6 @@ include CONTRIBUTORS.txt
 
 include pyproject.toml setup.cfg
 include .coveragerc .flake8
-include tox.ini appveyor.yml .travis.yml rtd.txt
+include tox.ini rtd.txt pytest.ini
 
 recursive-exclude * __pycache__ *.py[cod]
diff --git a/PKG-INFO b/PKG-INFO
index a362842..813a47d 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,431 +1,96 @@
 Metadata-Version: 2.1
 Name: hupper
-Version: 1.10.3
+Version: 1.12
 Summary: Integrated process monitor for developing and reloading daemons.
 Home-page: https://github.com/Pylons/hupper
 Author: Michael Merickel
 Author-email: pylons-discuss@googlegroups.com
 License: MIT
-Description: ======
-        hupper
-        ======
-        
-        .. image:: https://img.shields.io/pypi/v/hupper.svg
-            :target: https://pypi.python.org/pypi/hupper
-        
-        .. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20Linux/badge.svg?branch=master
-            :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+Linux%22
-        
-        .. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20MacOS/badge.svg?branch=master
-            :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+MacOS%22
-        
-        .. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20Windows/badge.svg?branch=master
-            :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+Windows%22
-        
-        .. image:: https://readthedocs.org/projects/hupper/badge/?version=latest
-            :target: https://readthedocs.org/projects/hupper/?badge=latest
-            :alt: Documentation Status
-        
-        ``hupper`` is an integrated process monitor that will track changes to
-        any imported Python files in ``sys.modules`` as well as custom paths. When
-        files are changed the process is restarted.
-        
-        Command-line Usage
-        ==================
-        
-        Hupper can load any Python code similar to ``python -m <module>`` by using the
-        ``hupper -m <module>`` program.
-        
-        .. code-block:: console
-        
-           $ hupper -m myapp
-           Starting monitor for PID 23982.
-        
-        API Usage
-        =========
-        
-        Start by defining an entry point for your process. This must be an importable
-        path in string format. For example, ``myapp.scripts.serve.main``.
-        
-        .. code-block:: python
-        
-            # myapp/scripts/serve.py
-        
-            import sys
-            import hupper
-            import waitress
-        
-        
-            def wsgi_app(environ, start_response):
-                start_response('200 OK', [('Content-Type', 'text/plain')])
-                yield b'hello'
-        
-        
-            def main(args=sys.argv[1:]):
-                if '--reload' in args:
-                    # start_reloader will only return in a monitored subprocess
-                    reloader = hupper.start_reloader('myapp.scripts.serve.main')
-        
-                    # monitor an extra file
-                    reloader.watch_files(['foo.ini'])
-        
-                waitress.serve(wsgi_app)
-        
-        Acknowledgments
-        ===============
-        
-        ``hupper`` is inspired by initial work done by Carl J Meyer and David Glick
-        during a Pycon sprint and is built to be a more robust and generic version of
-        Ian Bicking's excellent PasteScript ``paste serve --reload`` and Pyramid's
-        ``pserve --reload``.
-        
-        
-        1.10.3 (2021-05-13)
-        ===================
-        
-        - Support Python 3.8 and 3.9.
-        
-        - Fix an issue with bare ``.pyc`` files in the source folder causing unhandled
-          exceptions.
-          See https://github.com/Pylons/hupper/pull/69
-        
-        - Fix issues with using the Watchman file monitor on versions newer than
-          Watchman 4.9.0. This fix modifies ``hupper`` to use Watchman's
-          ``watch-project`` capabilities which also support reading the
-          ``.watchmanconfig`` file to control certain properties of the monitoring.
-          See https://github.com/Pylons/hupper/pull/70
-        
-        1.10.2 (2020-03-02)
-        ===================
-        
-        - Fix a regression that caused SIGINT to not work properly in some situations.
-          See https://github.com/Pylons/hupper/pull/67
-        
-        1.10.1 (2020-02-18)
-        ===================
-        
-        - Performance improvements when using Watchman.
-        
-        1.10 (2020-02-18)
-        =================
-        
-        - Handle a ``SIGTERM`` signal by forwarding it to the child process and
-          gracefully waiting for it to exit. This should enable using ``hupper``
-          from within docker containers and other systems that want to control
-          the reloader process.
-        
-          Previously the ``SIGTERM`` would shutdown ``hupper`` immediately, stranding
-          the worker and relying on it to shutdown on its own.
-        
-          See https://github.com/Pylons/hupper/pull/65
-        
-        - Avoid acquiring locks in the reloader process's signal handlers.
-          See https://github.com/Pylons/hupper/pull/65
-        
-        - Fix deprecation warnings caused by using the ``imp`` module on newer
-          versions of Python.
-          See https://github.com/Pylons/hupper/pull/65
-        
-        1.9.1 (2019-11-12)
-        ==================
-        
-        - Support some scenarios in which user code is symlinked ``site-packages``.
-          These were previously being ignored by the file monitor but should now
-          be tracked.
-          See https://github.com/Pylons/hupper/pull/61
-        
-        1.9 (2019-10-14)
-        ================
-        
-        - Support ``--shutdown-interval`` on the ``hupper`` CLI.
-          See https://github.com/Pylons/hupper/pull/56
-        
-        - Support ``--reload-interval`` on the ``hupper`` CLI.
-          See https://github.com/Pylons/hupper/pull/59
-        
-        - Do not choke when stdin is not a TTY while waiting for changes after a
-          crash. For example, when running in Docker Compose.
-          See https://github.com/Pylons/hupper/pull/58
-        
-        1.8.1 (2019-06-12)
-        ==================
-        
-        - Do not show the ``KeyboardInterrupt`` stacktrace when killing ``hupper``
-          while waiting for a reload.
-        
-        1.8 (2019-06-11)
-        ================
-        
-        - If the worker process crashes, ``hupper`` can be forced to reload the worker
-          by pressing the ``ENTER`` key in the terminal instead of waiting to change a
-          file.
-          See https://github.com/Pylons/hupper/pull/53
-        
-        1.7 (2019-06-04)
-        ================
-        
-        - On Python 3.5+ support recursive glob syntax in ``reloader.watch_files``.
-          See https://github.com/Pylons/hupper/pull/52
-        
-        1.6.1 (2019-03-11)
-        ==================
-        
-        - If the worker crashes immediately, sometimes ``hupper`` would go into a
-          restart loop instead of waiting for a code change.
-          See https://github.com/Pylons/hupper/pull/50
-        
-        1.6 (2019-03-06)
-        ================
-        
-        - On systems that support ``SIGKILL`` and ``SIGTERM`` (not Windows), ``hupper``
-          will now send a ``SIGKILL`` to the worker process as a last resort. Normally,
-          a ``SIGINT`` (Ctrl-C) or ``SIGTERM`` (on reload) will kill the worker. If,
-          within ``shutdown_interval`` seconds, the worker doesn't exit, it will
-          receive a ``SIGKILL``.
-          See https://github.com/Pylons/hupper/pull/48
-        
-        - Support a ``logger`` argument to ``hupper.start_reloader`` to override
-          the default logger that outputs messages to ``sys.stderr``.
-          See https://github.com/Pylons/hupper/pull/49
-        
-        1.5 (2019-02-16)
-        ================
-        
-        - Add support for ignoring custom patterns via the new ``ignore_files``
-          option on ``hupper.start_reloader``. The ``hupper`` cli also supports
-          ignoring files via the ``-x`` option.
-          See https://github.com/Pylons/hupper/pull/46
-        
-        1.4.2 (2018-11-26)
-        ==================
-        
-        - Fix a bug prompting the "ignoring corrupted payload from watchman" message
-          and placing the file monitor in an unrecoverable state when a change
-          triggered a watchman message > 4096 bytes.
-          See https://github.com/Pylons/hupper/pull/44
-        
-        1.4.1 (2018-11-11)
-        ==================
-        
-        - Stop ignoring a few paths that may not be system paths in cases where the
-          virtualenv is the root of your project.
-          See https://github.com/Pylons/hupper/pull/42
-        
-        1.4 (2018-10-26)
-        ================
-        
-        - Ignore changes to any system / installed files. This includes mostly
-          changes to any files in the stdlib and ``site-packages``. Anything that is
-          installed in editable mode or not installed at all will still be monitored.
-          This drastically reduces the number of files that ``hupper`` needs to
-          monitor.
-          See https://github.com/Pylons/hupper/pull/40
-        
-        1.3.1 (2018-10-05)
-        ==================
-        
-        - Support Python 3.7.
-        
-        - Avoid a restart-loop if the app is failing to restart on certain systems.
-          There was a race where ``hupper`` failed to detect that the app was
-          crashing and thus fell into its restart logic when the user manually
-          triggers an immediate reload.
-          See https://github.com/Pylons/hupper/pull/37
-        
-        - Ignore corrupted packets coming from watchman that occur in semi-random
-          scenarios. See https://github.com/Pylons/hupper/pull/38
-        
-        1.3 (2018-05-21)
-        ================
-        
-        - Added watchman support via ``hupper.watchman.WatchmanFileMonitor``.
-          This is the new preferred file monitor on systems supporting unix sockets.
-          See https://github.com/Pylons/hupper/pull/32
-        
-        - The ``hupper.watchdog.WatchdogFileMonitor`` will now output some info
-          when it receives ulimit or other errors from ``watchdog``.
-          See https://github.com/Pylons/hupper/pull/33
-        
-        - Allow ``-q`` and ``-v`` cli options to control verbosity.
-          See https://github.com/Pylons/hupper/pull/33
-        
-        - Pass a ``logger`` value to the ``hupper.interfaces.IFileMonitorFactory``.
-          This is an instance of ``hupper.interfaces.ILogger`` and can be used by
-          file monitors to output errors and debug information.
-          See https://github.com/Pylons/hupper/pull/33
-        
-        1.2 (2018-05-01)
-        ================
-        
-        - Track only Python source files. Previously ``hupper`` would track all pyc
-          and py files. Now, if a pyc file is found then the equivalent source file
-          is searched and, if found, the pyc file is ignored.
-          See https://github.com/Pylons/hupper/pull/31
-        
-        - Allow overriding the default monitor lookup by specifying the
-          ``HUPPER_DEFAULT_MONITOR`` environment variable as a Python dotted-path
-          to a monitor factory. For example,
-          ``HUPPER_DEFAULT_MONITOR=hupper.polling.PollingFileMonitor``.
-          See https://github.com/Pylons/hupper/pull/29
-        
-        - Backward-incompatible changes to the
-          ``hupper.interfaces.IFileMonitorFactory`` API to pass arbitrary kwargs
-          to the factory.
-          See https://github.com/Pylons/hupper/pull/29
-        
-        1.1 (2018-03-29)
-        ================
-        
-        - Support ``-w`` on the CLI to watch custom file paths.
-          See https://github.com/Pylons/hupper/pull/28
-        
-        1.0 (2017-05-18)
-        ================
-        
-        - Copy ``sys.path`` to the worker process and ensure ``hupper`` is on the
-          ``PYTHONPATH`` so that the subprocess can import it to start the worker.
-          This fixes an issue with how ``zc.buildout`` injects dependencies into a
-          process which is done entirely by ``sys.path`` manipulation.
-          See https://github.com/Pylons/hupper/pull/27
-        
-        0.5 (2017-05-10)
-        ================
-        
-        - On non-windows systems ensure an exec occurs so that the worker does not
-          share the same process space as the reloader causing certain code that
-          is imported in both to not ever be reloaded. Under the hood this was a
-          significant rewrite to use subprocess instead of multiprocessing.
-          See https://github.com/Pylons/hupper/pull/23
-        
-        0.4.4 (2017-03-10)
-        ==================
-        
-        - Fix some versions of Windows which were failing to duplicate stdin to
-          the subprocess and crashing.
-          https://github.com/Pylons/hupper/pull/16
-        
-        0.4.3 (2017-03-07)
-        ==================
-        
-        - Fix pdb and other readline-based programs to operate properly.
-          See https://github.com/Pylons/hupper/pull/15
-        
-        0.4.2 (2017-01-24)
-        ==================
-        
-        - Pause briefly after receiving a SIGINT to allow the worker to kill itself.
-          If it does not die then it is terminated.
-          See https://github.com/Pylons/hupper/issues/11
-        
-        - Python 3.6 compatibility.
-        
-        0.4.1 (2017-01-03)
-        ==================
-        
-        - Handle errors that may occur when using watchdog to observe non-existent
-          folders.
-        
-        0.4.0 (2017-01-02)
-        ==================
-        
-        - Support running any Python module via ``hupper -m <module>``. This is
-          equivalent to ``python -m`` except will fully reload the process when files
-          change. See https://github.com/Pylons/hupper/pull/8
-        
-        0.3.6 (2016-12-18)
-        ==================
-        
-        - Read the traceback for unknown files prior to crashing. If an import
-          crashes due to a module-scope exception the file that caused the crash would
-          not be tracked but this should help.
-        
-        0.3.5 (2016-12-17)
-        ==================
-        
-        - Attempt to send imported paths to the monitor process before crashing to
-          avoid cases where the master is waiting for changes in files that it never
-          started monitoring.
-        
-        0.3.4 (2016-11-21)
-        ==================
-        
-        - Add support for globbing using the stdlib ``glob`` module. On Python 3.5+
-          this allows recursive globs using ``**``. Prior to this, the globbing is
-          more limited.
-        
-        0.3.3 (2016-11-19)
-        ==================
-        
-        - Fixed a runtime failure on Windows 32-bit systems.
-        
-        0.3.2 (2016-11-15)
-        ==================
-        
-        - Support triggering reloads via SIGHUP when hupper detected a crash and is
-          waiting for a file to change.
-        
-        - Setup the reloader proxy prior to importing the worker's module. This
-          should allow some work to be done at module-scope instead of in the
-          callable.
-        
-        0.3.1 (2016-11-06)
-        ==================
-        
-        - Fix package long description on PyPI.
-        
-        - Ensure that the stdin file handle is inheritable incase the "spawn" variant
-          of multiprocessing is enabled.
-        
-        0.3 (2016-11-06)
-        ================
-        
-        - Disable bytecode compiling of files imported by the worker process. This
-          should not be necessary when developing and it was causing the process to
-          restart twice on Windows due to how it handles pyc timestamps.
-        
-        - Fix hupper's support for forwarding stdin to the worker processes on
-          Python < 3.5 on Windows.
-        
-        - Fix some possible file descriptor leakage.
-        
-        - Simplify the ``hupper.interfaces.IFileMonitor`` interface by internalizing
-          some of the hupper-specific integrations. They can now focus on just
-          looking for changes.
-        
-        - Add the ``hupper.interfaces.IFileMonitorFactory`` interface to improve
-          the documentation for the ``callback`` argument required by
-          ``hupper.interfaces.IFileMonitor``.
-        
-        0.2 (2016-10-26)
-        ================
-        
-        - Windows support!
-        
-        - Added support for `watchdog <https://pypi.org/project/watchdog/>`_ if it's
-          installed to do inotify-style file monitoring. This is an optional dependency
-          and ``hupper`` will fallback to using polling if it's not available.
-        
-        0.1 (2016-10-21)
-        ================
-        
-        - Initial release.
-        
-Keywords: server daemon autoreload reloader hup file watch process
-Platform: UNKNOWN
+Project-URL: Documentation, https://docs.pylonsproject.org/projects/hupper/en/latest/
+Project-URL: Changelog, https://docs.pylonsproject.org/projects/hupper/en/latest/changes.html
+Project-URL: Issue Tracker, https://github.com/Pylons/hupper/issues
+Keywords: server,daemon,autoreload,reloader,hup,file,watch,process
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Natural Language :: English
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.5
-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 :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
-Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
 Provides-Extra: docs
 Provides-Extra: testing
+License-File: LICENSE.txt
+
+======
+hupper
+======
+
+.. image:: https://img.shields.io/pypi/v/hupper.svg
+    :target: https://pypi.python.org/pypi/hupper
+
+.. image:: https://github.com/Pylons/hupper/actions/workflows/ci-tests.yml/badge.svg?branch=main
+    :target: https://github.com/Pylons/hupper/actions/workflows/ci-tests.yml?query=branch%3Amain
+
+.. image:: https://readthedocs.org/projects/hupper/badge/?version=latest
+    :target: https://readthedocs.org/projects/hupper/?badge=latest
+    :alt: Documentation Status
+
+``hupper`` is an integrated process monitor that will track changes to
+any imported Python files in ``sys.modules`` as well as custom paths. When
+files are changed the process is restarted.
+
+Command-line Usage
+==================
+
+Hupper can load any Python code similar to ``python -m <module>`` by using the
+``hupper -m <module>`` program.
+
+.. code-block:: console
+
+   $ hupper -m myapp
+   Starting monitor for PID 23982.
+
+API Usage
+=========
+
+Start by defining an entry point for your process. This must be an importable
+path in string format. For example, ``myapp.scripts.serve.main``.
+
+.. code-block:: python
+
+    # myapp/scripts/serve.py
+
+    import sys
+    import hupper
+    import waitress
+
+
+    def wsgi_app(environ, start_response):
+        start_response('200 OK', [('Content-Type', 'text/plain')])
+        yield b'hello'
+
+
+    def main(args=sys.argv[1:]):
+        if '--reload' in args:
+            # start_reloader will only return in a monitored subprocess
+            reloader = hupper.start_reloader('myapp.scripts.serve.main')
+
+            # monitor an extra file
+            reloader.watch_files(['foo.ini'])
+
+        waitress.serve(wsgi_app)
+
+Acknowledgments
+===============
+
+``hupper`` is inspired by initial work done by Carl J Meyer and David Glick
+during a Pycon sprint and is built to be a more robust and generic version of
+Ian Bicking's excellent PasteScript ``paste serve --reload`` and Pyramid's
+``pserve --reload``.
diff --git a/README.rst b/README.rst
index 8cc398e..1bc3a5c 100644
--- a/README.rst
+++ b/README.rst
@@ -5,14 +5,8 @@ hupper
 .. image:: https://img.shields.io/pypi/v/hupper.svg
     :target: https://pypi.python.org/pypi/hupper
 
-.. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20Linux/badge.svg?branch=master
-    :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+Linux%22
-
-.. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20MacOS/badge.svg?branch=master
-    :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+MacOS%22
-
-.. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20Windows/badge.svg?branch=master
-    :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+Windows%22
+.. image:: https://github.com/Pylons/hupper/actions/workflows/ci-tests.yml/badge.svg?branch=main
+    :target: https://github.com/Pylons/hupper/actions/workflows/ci-tests.yml?query=branch%3Amain
 
 .. image:: https://readthedocs.org/projects/hupper/badge/?version=latest
     :target: https://readthedocs.org/projects/hupper/?badge=latest
diff --git a/debian/changelog b/debian/changelog
index 020db05..6388def 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+python-hupper (1.12-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 01 Jun 2023 19:30:47 -0000
+
 python-hupper (1.10.3-1) unstable; urgency=medium
 
   [ Debian Janitor ]
diff --git a/pyproject.toml b/pyproject.toml
index c44ccef..44d53bb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
 [tool.black]
 line-length = 79
 skip-string-normalization = true
-py36 = false
+target_version = ["py37", "py38", "py39", "py310", "py311"]
 exclude = '''
 /(
     \.git
@@ -22,11 +22,9 @@ exclude = '''
  # This next section only exists for people that have their editors
 # automatically call isort, black already sorts entries on its own when run.
 [tool.isort]
-multi_line_output = 3
-include_trailing_comma = true
-force_grid_wrap = false
+profile = "black"
+py_version = 3
 combine_as_imports = true
-use_parenthesis = true
 line_length = 79
 force_sort_within_sections = true
 no_lines_before = "THIRDPARTY"
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..8ee2cf1
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,5 @@
+[pytest]
+python_files = test_*.py
+testpaths =
+    src/hupper
+    tests
\ No newline at end of file
diff --git a/rtd.txt b/rtd.txt
index e9704b8..142b6ca 100644
--- a/rtd.txt
+++ b/rtd.txt
@@ -1 +1 @@
-.[docs]
+-e .[docs]
diff --git a/setup.cfg b/setup.cfg
index e76b109..8dad93a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,22 +1,75 @@
-[bdist_wheel]
-universal = 1
-
 [metadata]
-license_file = LICENSE.txt
+name = hupper
+version = 1.12
+author = Michael Merickel
+author_email = pylons-discuss@googlegroups.com
+license = MIT
+license_files = LICENSE.txt
+description = Integrated process monitor for developing and reloading daemons.
+long_description = file:README.rst
+long_description_content_type = text/x-rst
+keywords = 
+	server
+	daemon
+	autoreload
+	reloader
+	hup
+	file
+	watch
+	process
+url = https://github.com/Pylons/hupper
+project_urls = 
+	Documentation = https://docs.pylonsproject.org/projects/hupper/en/latest/
+	Changelog = https://docs.pylonsproject.org/projects/hupper/en/latest/changes.html
+	Issue Tracker = https://github.com/Pylons/hupper/issues
+classifiers = 
+	Development Status :: 5 - Production/Stable
+	Intended Audience :: Developers
+	License :: OSI Approved :: MIT License
+	Natural Language :: English
+	Programming Language :: Python :: 3
+	Programming Language :: Python :: 3.7
+	Programming Language :: Python :: 3.8
+	Programming Language :: Python :: 3.9
+	Programming Language :: Python :: 3.10
+	Programming Language :: Python :: 3.11
+	Programming Language :: Python :: Implementation :: CPython
+	Programming Language :: Python :: Implementation :: PyPy
+
+[options]
+package_dir = 
+	= src
+packages = find:
+zip_safe = False
+include_package_data = True
+python_requires = >=3.7
+
+[options.packages.find]
+where = src
+
+[options.entry_points]
+console_scripts = 
+	hupper = hupper.cli:main
+
+[options.extras_require]
+docs = 
+	watchdog
+	setuptools
+	Sphinx
+	pylons-sphinx-themes
+testing = 
+	watchdog
+	pytest
+	pytest-cov
+	mock
 
 [check-manifest]
+ignore-default-rules = true
 ignore = 
 	.gitignore
 	PKG-INFO
 	*.egg-info
 	*.egg-info/*
-ignore-default-rules = true
-
-[tool:pytest]
-python_files = test_*.py
-testpaths = 
-	src/hupper
-	tests
 
 [egg_info]
 tag_build = 
diff --git a/setup.py b/setup.py
index 7b254a3..6068493 100644
--- a/setup.py
+++ b/setup.py
@@ -1,51 +1,3 @@
-from setuptools import find_packages, setup
+from setuptools import setup
 
-
-def readfile(name):
-    with open(name) as f:
-        return f.read()
-
-
-readme = readfile('README.rst')
-changes = readfile('CHANGES.rst')
-
-docs_require = ['watchdog', 'Sphinx', 'pylons-sphinx-themes']
-
-tests_require = ['watchdog', 'pytest', 'pytest-cov', 'mock']
-
-setup(
-    name='hupper',
-    version='1.10.3',
-    description=(
-        'Integrated process monitor for developing and reloading daemons.'
-    ),
-    long_description=readme + '\n\n' + changes,
-    author='Michael Merickel',
-    author_email='pylons-discuss@googlegroups.com',
-    url='https://github.com/Pylons/hupper',
-    license='MIT',
-    packages=find_packages('src', exclude=['tests']),
-    package_dir={'': 'src'},
-    include_package_data=True,
-    python_requires='>=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*',
-    extras_require={'docs': docs_require, 'testing': tests_require},
-    entry_points={"console_scripts": ["hupper = hupper.cli:main"]},
-    zip_safe=False,
-    keywords='server daemon autoreload reloader hup file watch process',
-    classifiers=[
-        'Development Status :: 5 - Production/Stable',
-        'Intended Audience :: Developers',
-        'License :: OSI Approved :: MIT License',
-        'Natural Language :: English',
-        'Programming Language :: Python :: 2',
-        'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.5',
-        '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',
-    ],
-)
+setup()
diff --git a/src/hupper.egg-info/PKG-INFO b/src/hupper.egg-info/PKG-INFO
index a362842..813a47d 100644
--- a/src/hupper.egg-info/PKG-INFO
+++ b/src/hupper.egg-info/PKG-INFO
@@ -1,431 +1,96 @@
 Metadata-Version: 2.1
 Name: hupper
-Version: 1.10.3
+Version: 1.12
 Summary: Integrated process monitor for developing and reloading daemons.
 Home-page: https://github.com/Pylons/hupper
 Author: Michael Merickel
 Author-email: pylons-discuss@googlegroups.com
 License: MIT
-Description: ======
-        hupper
-        ======
-        
-        .. image:: https://img.shields.io/pypi/v/hupper.svg
-            :target: https://pypi.python.org/pypi/hupper
-        
-        .. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20Linux/badge.svg?branch=master
-            :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+Linux%22
-        
-        .. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20MacOS/badge.svg?branch=master
-            :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+MacOS%22
-        
-        .. image:: https://github.com/Pylons/hupper/workflows/Build/test%20on%20Windows/badge.svg?branch=master
-            :target: https://github.com/Pylons/hupper/actions?query=workflow%3A%22Build%2Ftest+on+Windows%22
-        
-        .. image:: https://readthedocs.org/projects/hupper/badge/?version=latest
-            :target: https://readthedocs.org/projects/hupper/?badge=latest
-            :alt: Documentation Status
-        
-        ``hupper`` is an integrated process monitor that will track changes to
-        any imported Python files in ``sys.modules`` as well as custom paths. When
-        files are changed the process is restarted.
-        
-        Command-line Usage
-        ==================
-        
-        Hupper can load any Python code similar to ``python -m <module>`` by using the
-        ``hupper -m <module>`` program.
-        
-        .. code-block:: console
-        
-           $ hupper -m myapp
-           Starting monitor for PID 23982.
-        
-        API Usage
-        =========
-        
-        Start by defining an entry point for your process. This must be an importable
-        path in string format. For example, ``myapp.scripts.serve.main``.
-        
-        .. code-block:: python
-        
-            # myapp/scripts/serve.py
-        
-            import sys
-            import hupper
-            import waitress
-        
-        
-            def wsgi_app(environ, start_response):
-                start_response('200 OK', [('Content-Type', 'text/plain')])
-                yield b'hello'
-        
-        
-            def main(args=sys.argv[1:]):
-                if '--reload' in args:
-                    # start_reloader will only return in a monitored subprocess
-                    reloader = hupper.start_reloader('myapp.scripts.serve.main')
-        
-                    # monitor an extra file
-                    reloader.watch_files(['foo.ini'])
-        
-                waitress.serve(wsgi_app)
-        
-        Acknowledgments
-        ===============
-        
-        ``hupper`` is inspired by initial work done by Carl J Meyer and David Glick
-        during a Pycon sprint and is built to be a more robust and generic version of
-        Ian Bicking's excellent PasteScript ``paste serve --reload`` and Pyramid's
-        ``pserve --reload``.
-        
-        
-        1.10.3 (2021-05-13)
-        ===================
-        
-        - Support Python 3.8 and 3.9.
-        
-        - Fix an issue with bare ``.pyc`` files in the source folder causing unhandled
-          exceptions.
-          See https://github.com/Pylons/hupper/pull/69
-        
-        - Fix issues with using the Watchman file monitor on versions newer than
-          Watchman 4.9.0. This fix modifies ``hupper`` to use Watchman's
-          ``watch-project`` capabilities which also support reading the
-          ``.watchmanconfig`` file to control certain properties of the monitoring.
-          See https://github.com/Pylons/hupper/pull/70
-        
-        1.10.2 (2020-03-02)
-        ===================
-        
-        - Fix a regression that caused SIGINT to not work properly in some situations.
-          See https://github.com/Pylons/hupper/pull/67
-        
-        1.10.1 (2020-02-18)
-        ===================
-        
-        - Performance improvements when using Watchman.
-        
-        1.10 (2020-02-18)
-        =================
-        
-        - Handle a ``SIGTERM`` signal by forwarding it to the child process and
-          gracefully waiting for it to exit. This should enable using ``hupper``
-          from within docker containers and other systems that want to control
-          the reloader process.
-        
-          Previously the ``SIGTERM`` would shutdown ``hupper`` immediately, stranding
-          the worker and relying on it to shutdown on its own.
-        
-          See https://github.com/Pylons/hupper/pull/65
-        
-        - Avoid acquiring locks in the reloader process's signal handlers.
-          See https://github.com/Pylons/hupper/pull/65
-        
-        - Fix deprecation warnings caused by using the ``imp`` module on newer
-          versions of Python.
-          See https://github.com/Pylons/hupper/pull/65
-        
-        1.9.1 (2019-11-12)
-        ==================
-        
-        - Support some scenarios in which user code is symlinked ``site-packages``.
-          These were previously being ignored by the file monitor but should now
-          be tracked.
-          See https://github.com/Pylons/hupper/pull/61
-        
-        1.9 (2019-10-14)
-        ================
-        
-        - Support ``--shutdown-interval`` on the ``hupper`` CLI.
-          See https://github.com/Pylons/hupper/pull/56
-        
-        - Support ``--reload-interval`` on the ``hupper`` CLI.
-          See https://github.com/Pylons/hupper/pull/59
-        
-        - Do not choke when stdin is not a TTY while waiting for changes after a
-          crash. For example, when running in Docker Compose.
-          See https://github.com/Pylons/hupper/pull/58
-        
-        1.8.1 (2019-06-12)
-        ==================
-        
-        - Do not show the ``KeyboardInterrupt`` stacktrace when killing ``hupper``
-          while waiting for a reload.
-        
-        1.8 (2019-06-11)
-        ================
-        
-        - If the worker process crashes, ``hupper`` can be forced to reload the worker
-          by pressing the ``ENTER`` key in the terminal instead of waiting to change a
-          file.
-          See https://github.com/Pylons/hupper/pull/53
-        
-        1.7 (2019-06-04)
-        ================
-        
-        - On Python 3.5+ support recursive glob syntax in ``reloader.watch_files``.
-          See https://github.com/Pylons/hupper/pull/52
-        
-        1.6.1 (2019-03-11)
-        ==================
-        
-        - If the worker crashes immediately, sometimes ``hupper`` would go into a
-          restart loop instead of waiting for a code change.
-          See https://github.com/Pylons/hupper/pull/50
-        
-        1.6 (2019-03-06)
-        ================
-        
-        - On systems that support ``SIGKILL`` and ``SIGTERM`` (not Windows), ``hupper``
-          will now send a ``SIGKILL`` to the worker process as a last resort. Normally,
-          a ``SIGINT`` (Ctrl-C) or ``SIGTERM`` (on reload) will kill the worker. If,
-          within ``shutdown_interval`` seconds, the worker doesn't exit, it will
-          receive a ``SIGKILL``.
-          See https://github.com/Pylons/hupper/pull/48
-        
-        - Support a ``logger`` argument to ``hupper.start_reloader`` to override
-          the default logger that outputs messages to ``sys.stderr``.
-          See https://github.com/Pylons/hupper/pull/49
-        
-        1.5 (2019-02-16)
-        ================
-        
-        - Add support for ignoring custom patterns via the new ``ignore_files``
-          option on ``hupper.start_reloader``. The ``hupper`` cli also supports
-          ignoring files via the ``-x`` option.
-          See https://github.com/Pylons/hupper/pull/46
-        
-        1.4.2 (2018-11-26)
-        ==================
-        
-        - Fix a bug prompting the "ignoring corrupted payload from watchman" message
-          and placing the file monitor in an unrecoverable state when a change
-          triggered a watchman message > 4096 bytes.
-          See https://github.com/Pylons/hupper/pull/44
-        
-        1.4.1 (2018-11-11)
-        ==================
-        
-        - Stop ignoring a few paths that may not be system paths in cases where the
-          virtualenv is the root of your project.
-          See https://github.com/Pylons/hupper/pull/42
-        
-        1.4 (2018-10-26)
-        ================
-        
-        - Ignore changes to any system / installed files. This includes mostly
-          changes to any files in the stdlib and ``site-packages``. Anything that is
-          installed in editable mode or not installed at all will still be monitored.
-          This drastically reduces the number of files that ``hupper`` needs to
-          monitor.
-          See https://github.com/Pylons/hupper/pull/40
-        
-        1.3.1 (2018-10-05)
-        ==================
-        
-        - Support Python 3.7.
-        
-        - Avoid a restart-loop if the app is failing to restart on certain systems.
-          There was a race where ``hupper`` failed to detect that the app was
-          crashing and thus fell into its restart logic when the user manually
-          triggers an immediate reload.
-          See https://github.com/Pylons/hupper/pull/37
-        
-        - Ignore corrupted packets coming from watchman that occur in semi-random
-          scenarios. See https://github.com/Pylons/hupper/pull/38
-        
-        1.3 (2018-05-21)
-        ================
-        
-        - Added watchman support via ``hupper.watchman.WatchmanFileMonitor``.
-          This is the new preferred file monitor on systems supporting unix sockets.
-          See https://github.com/Pylons/hupper/pull/32
-        
-        - The ``hupper.watchdog.WatchdogFileMonitor`` will now output some info
-          when it receives ulimit or other errors from ``watchdog``.
-          See https://github.com/Pylons/hupper/pull/33
-        
-        - Allow ``-q`` and ``-v`` cli options to control verbosity.
-          See https://github.com/Pylons/hupper/pull/33
-        
-        - Pass a ``logger`` value to the ``hupper.interfaces.IFileMonitorFactory``.
-          This is an instance of ``hupper.interfaces.ILogger`` and can be used by
-          file monitors to output errors and debug information.
-          See https://github.com/Pylons/hupper/pull/33
-        
-        1.2 (2018-05-01)
-        ================
-        
-        - Track only Python source files. Previously ``hupper`` would track all pyc
-          and py files. Now, if a pyc file is found then the equivalent source file
-          is searched and, if found, the pyc file is ignored.
-          See https://github.com/Pylons/hupper/pull/31
-        
-        - Allow overriding the default monitor lookup by specifying the
-          ``HUPPER_DEFAULT_MONITOR`` environment variable as a Python dotted-path
-          to a monitor factory. For example,
-          ``HUPPER_DEFAULT_MONITOR=hupper.polling.PollingFileMonitor``.
-          See https://github.com/Pylons/hupper/pull/29
-        
-        - Backward-incompatible changes to the
-          ``hupper.interfaces.IFileMonitorFactory`` API to pass arbitrary kwargs
-          to the factory.
-          See https://github.com/Pylons/hupper/pull/29
-        
-        1.1 (2018-03-29)
-        ================
-        
-        - Support ``-w`` on the CLI to watch custom file paths.
-          See https://github.com/Pylons/hupper/pull/28
-        
-        1.0 (2017-05-18)
-        ================
-        
-        - Copy ``sys.path`` to the worker process and ensure ``hupper`` is on the
-          ``PYTHONPATH`` so that the subprocess can import it to start the worker.
-          This fixes an issue with how ``zc.buildout`` injects dependencies into a
-          process which is done entirely by ``sys.path`` manipulation.
-          See https://github.com/Pylons/hupper/pull/27
-        
-        0.5 (2017-05-10)
-        ================
-        
-        - On non-windows systems ensure an exec occurs so that the worker does not
-          share the same process space as the reloader causing certain code that
-          is imported in both to not ever be reloaded. Under the hood this was a
-          significant rewrite to use subprocess instead of multiprocessing.
-          See https://github.com/Pylons/hupper/pull/23
-        
-        0.4.4 (2017-03-10)
-        ==================
-        
-        - Fix some versions of Windows which were failing to duplicate stdin to
-          the subprocess and crashing.
-          https://github.com/Pylons/hupper/pull/16
-        
-        0.4.3 (2017-03-07)
-        ==================
-        
-        - Fix pdb and other readline-based programs to operate properly.
-          See https://github.com/Pylons/hupper/pull/15
-        
-        0.4.2 (2017-01-24)
-        ==================
-        
-        - Pause briefly after receiving a SIGINT to allow the worker to kill itself.
-          If it does not die then it is terminated.
-          See https://github.com/Pylons/hupper/issues/11
-        
-        - Python 3.6 compatibility.
-        
-        0.4.1 (2017-01-03)
-        ==================
-        
-        - Handle errors that may occur when using watchdog to observe non-existent
-          folders.
-        
-        0.4.0 (2017-01-02)
-        ==================
-        
-        - Support running any Python module via ``hupper -m <module>``. This is
-          equivalent to ``python -m`` except will fully reload the process when files
-          change. See https://github.com/Pylons/hupper/pull/8
-        
-        0.3.6 (2016-12-18)
-        ==================
-        
-        - Read the traceback for unknown files prior to crashing. If an import
-          crashes due to a module-scope exception the file that caused the crash would
-          not be tracked but this should help.
-        
-        0.3.5 (2016-12-17)
-        ==================
-        
-        - Attempt to send imported paths to the monitor process before crashing to
-          avoid cases where the master is waiting for changes in files that it never
-          started monitoring.
-        
-        0.3.4 (2016-11-21)
-        ==================
-        
-        - Add support for globbing using the stdlib ``glob`` module. On Python 3.5+
-          this allows recursive globs using ``**``. Prior to this, the globbing is
-          more limited.
-        
-        0.3.3 (2016-11-19)
-        ==================
-        
-        - Fixed a runtime failure on Windows 32-bit systems.
-        
-        0.3.2 (2016-11-15)
-        ==================
-        
-        - Support triggering reloads via SIGHUP when hupper detected a crash and is
-          waiting for a file to change.
-        
-        - Setup the reloader proxy prior to importing the worker's module. This
-          should allow some work to be done at module-scope instead of in the
-          callable.
-        
-        0.3.1 (2016-11-06)
-        ==================
-        
-        - Fix package long description on PyPI.
-        
-        - Ensure that the stdin file handle is inheritable incase the "spawn" variant
-          of multiprocessing is enabled.
-        
-        0.3 (2016-11-06)
-        ================
-        
-        - Disable bytecode compiling of files imported by the worker process. This
-          should not be necessary when developing and it was causing the process to
-          restart twice on Windows due to how it handles pyc timestamps.
-        
-        - Fix hupper's support for forwarding stdin to the worker processes on
-          Python < 3.5 on Windows.
-        
-        - Fix some possible file descriptor leakage.
-        
-        - Simplify the ``hupper.interfaces.IFileMonitor`` interface by internalizing
-          some of the hupper-specific integrations. They can now focus on just
-          looking for changes.
-        
-        - Add the ``hupper.interfaces.IFileMonitorFactory`` interface to improve
-          the documentation for the ``callback`` argument required by
-          ``hupper.interfaces.IFileMonitor``.
-        
-        0.2 (2016-10-26)
-        ================
-        
-        - Windows support!
-        
-        - Added support for `watchdog <https://pypi.org/project/watchdog/>`_ if it's
-          installed to do inotify-style file monitoring. This is an optional dependency
-          and ``hupper`` will fallback to using polling if it's not available.
-        
-        0.1 (2016-10-21)
-        ================
-        
-        - Initial release.
-        
-Keywords: server daemon autoreload reloader hup file watch process
-Platform: UNKNOWN
+Project-URL: Documentation, https://docs.pylonsproject.org/projects/hupper/en/latest/
+Project-URL: Changelog, https://docs.pylonsproject.org/projects/hupper/en/latest/changes.html
+Project-URL: Issue Tracker, https://github.com/Pylons/hupper/issues
+Keywords: server,daemon,autoreload,reloader,hup,file,watch,process
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: MIT License
 Classifier: Natural Language :: English
-Classifier: Programming Language :: Python :: 2
-Classifier: Programming Language :: Python :: 2.7
 Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.5
-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 :: 3.10
+Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
-Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
+Requires-Python: >=3.7
+Description-Content-Type: text/x-rst
 Provides-Extra: docs
 Provides-Extra: testing
+License-File: LICENSE.txt
+
+======
+hupper
+======
+
+.. image:: https://img.shields.io/pypi/v/hupper.svg
+    :target: https://pypi.python.org/pypi/hupper
+
+.. image:: https://github.com/Pylons/hupper/actions/workflows/ci-tests.yml/badge.svg?branch=main
+    :target: https://github.com/Pylons/hupper/actions/workflows/ci-tests.yml?query=branch%3Amain
+
+.. image:: https://readthedocs.org/projects/hupper/badge/?version=latest
+    :target: https://readthedocs.org/projects/hupper/?badge=latest
+    :alt: Documentation Status
+
+``hupper`` is an integrated process monitor that will track changes to
+any imported Python files in ``sys.modules`` as well as custom paths. When
+files are changed the process is restarted.
+
+Command-line Usage
+==================
+
+Hupper can load any Python code similar to ``python -m <module>`` by using the
+``hupper -m <module>`` program.
+
+.. code-block:: console
+
+   $ hupper -m myapp
+   Starting monitor for PID 23982.
+
+API Usage
+=========
+
+Start by defining an entry point for your process. This must be an importable
+path in string format. For example, ``myapp.scripts.serve.main``.
+
+.. code-block:: python
+
+    # myapp/scripts/serve.py
+
+    import sys
+    import hupper
+    import waitress
+
+
+    def wsgi_app(environ, start_response):
+        start_response('200 OK', [('Content-Type', 'text/plain')])
+        yield b'hello'
+
+
+    def main(args=sys.argv[1:]):
+        if '--reload' in args:
+            # start_reloader will only return in a monitored subprocess
+            reloader = hupper.start_reloader('myapp.scripts.serve.main')
+
+            # monitor an extra file
+            reloader.watch_files(['foo.ini'])
+
+        waitress.serve(wsgi_app)
+
+Acknowledgments
+===============
+
+``hupper`` is inspired by initial work done by Carl J Meyer and David Glick
+during a Pycon sprint and is built to be a more robust and generic version of
+Ian Bicking's excellent PasteScript ``paste serve --reload`` and Pyramid's
+``pserve --reload``.
diff --git a/src/hupper.egg-info/SOURCES.txt b/src/hupper.egg-info/SOURCES.txt
index 0c60591..609a91a 100644
--- a/src/hupper.egg-info/SOURCES.txt
+++ b/src/hupper.egg-info/SOURCES.txt
@@ -7,10 +7,13 @@ LICENSE.txt
 MANIFEST.in
 README.rst
 pyproject.toml
+pytest.ini
 rtd.txt
 setup.cfg
 setup.py
 tox.ini
+.github/dependabot.yml
+.github/workflows/ci-tests.yml
 docs/Makefile
 docs/api.rst
 docs/changes.rst
@@ -21,7 +24,6 @@ docs/make.bat
 docs/_static/.keep
 src/hupper/__init__.py
 src/hupper/cli.py
-src/hupper/compat.py
 src/hupper/interfaces.py
 src/hupper/ipc.py
 src/hupper/logger.py
diff --git a/src/hupper.egg-info/entry_points.txt b/src/hupper.egg-info/entry_points.txt
index 024ef7f..732550c 100644
--- a/src/hupper.egg-info/entry_points.txt
+++ b/src/hupper.egg-info/entry_points.txt
@@ -1,3 +1,2 @@
 [console_scripts]
 hupper = hupper.cli:main
-
diff --git a/src/hupper.egg-info/requires.txt b/src/hupper.egg-info/requires.txt
index 4c88b51..ae7a63f 100644
--- a/src/hupper.egg-info/requires.txt
+++ b/src/hupper.egg-info/requires.txt
@@ -1,6 +1,7 @@
 
 [docs]
 watchdog
+setuptools
 Sphinx
 pylons-sphinx-themes
 
diff --git a/src/hupper/cli.py b/src/hupper/cli.py
index b6c0f6a..aa7d1e8 100644
--- a/src/hupper/cli.py
+++ b/src/hupper/cli.py
@@ -51,7 +51,7 @@ def main():
         "hupper.cli.main",
         verbose=level,
         ignore_files=args.ignore,
-        **reloader_kw
+        **reloader_kw,
     )
 
     sys.argv[1:] = unknown_args
diff --git a/src/hupper/compat.py b/src/hupper/compat.py
deleted file mode 100644
index 1e30d8b..0000000
--- a/src/hupper/compat.py
+++ /dev/null
@@ -1,106 +0,0 @@
-# flake8: noqa
-import importlib
-import site
-import subprocess
-import sys
-import time
-
-PY2 = sys.version_info[0] == 2
-WIN = sys.platform == 'win32'
-
-
-try:
-    from _thread import interrupt_main
-except ImportError:
-    from thread import interrupt_main
-
-
-try:
-    from importlib.util import source_from_cache as _source_from_cache
-except ImportError:
-    try:
-        # fallback on python < 3.5
-        from imp import source_from_cache as _source_from_cache
-    except ImportError:
-        # fallback on python 2.x
-        _source_from_cache = None
-
-
-def get_py_path(path):
-    if _source_from_cache:
-        try:
-            return _source_from_cache(path)
-        except ValueError:
-            # fallback for solitary *.pyc files outside of __pycache__
-            pass
-
-    return path[:-1]
-
-
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
-
-
-if PY2 or sys.version_info[1] < 5:
-    from glob import glob as gg
-
-    def glob(pathname, recursive=False):
-        return gg(pathname)
-
-
-else:
-    from glob import glob
-
-
-def get_site_packages():  # pragma: no cover
-    try:
-        paths = site.getsitepackages()
-        if site.ENABLE_USER_SITE:
-            paths.append(site.getusersitepackages())
-        return paths
-
-    # virtualenv does not ship with a getsitepackages impl so we fallback
-    # to using distutils if we can
-    # https://github.com/pypa/virtualenv/issues/355
-    except Exception:
-        try:
-            from distutils.sysconfig import get_python_lib
-
-            return [get_python_lib()]
-
-        # just incase, don't fail here, it's not worth it
-        except Exception:
-            return []
-
-
-################################################
-# cross-compatible metaclass implementation
-# Copyright (c) 2010-2012 Benjamin Peterson
-def with_metaclass(meta, base=object):
-    """Create a base class with a metaclass."""
-    return meta("%sBase" % meta.__name__, (base,), {})
-
-
-if PY2:
-
-    def subprocess_wait_with_timeout(process, timeout):
-        max_time = time.time() + timeout
-        while process.poll() is None:
-            dt = max_time - time.time()
-            if dt <= 0:
-                break
-            if dt > 0.5:
-                dt = 0.5
-            time.sleep(dt)
-        return process.poll()
-
-
-else:
-
-    def subprocess_wait_with_timeout(process, timeout):
-        try:
-            return process.wait(timeout)
-        except subprocess.TimeoutExpired:
-            pass
diff --git a/src/hupper/interfaces.py b/src/hupper/interfaces.py
index 072e3cb..7803ea0 100644
--- a/src/hupper/interfaces.py
+++ b/src/hupper/interfaces.py
@@ -1,21 +1,20 @@
-import abc
+from abc import ABC, abstractmethod
 
-from .compat import with_metaclass
 
-
-class IReloaderProxy(with_metaclass(abc.ABCMeta)):
-    @abc.abstractmethod
+class IReloaderProxy(ABC):
+    @abstractmethod
     def watch_files(self, files):
-        """ Signal to the monitor to track some custom paths."""
+        """Signal to the monitor to track some custom paths."""
 
-    @abc.abstractmethod
+    @abstractmethod
     def trigger_reload(self):
-        """ Signal the monitor to execute a reload."""
+        """Signal the monitor to execute a reload."""
 
 
-class IFileMonitorFactory(with_metaclass(abc.ABCMeta)):
+class IFileMonitorFactory(ABC):
+    @abstractmethod
     def __call__(self, callback, **kw):
-        """ Return an :class:`.IFileMonitor` instance.
+        """Return an :class:`.IFileMonitor` instance.
 
         ``callback`` is a callable to be invoked by the ``IFileMonitor``
         when file changes are detected. It should accept the path of
@@ -32,37 +31,37 @@ class IFileMonitorFactory(with_metaclass(abc.ABCMeta)):
         """
 
 
-class IFileMonitor(with_metaclass(abc.ABCMeta)):
-    @abc.abstractmethod
+class IFileMonitor(ABC):
+    @abstractmethod
     def add_path(self, path):
-        """ Start monitoring a new path."""
+        """Start monitoring a new path."""
 
-    @abc.abstractmethod
+    @abstractmethod
     def start(self):
-        """ Start the monitor. This method should not block."""
+        """Start the monitor. This method should not block."""
 
-    @abc.abstractmethod
+    @abstractmethod
     def stop(self):
-        """ Trigger the monitor to stop.
+        """Trigger the monitor to stop.
 
         This should be called before invoking ``join``.
 
         """
 
-    @abc.abstractmethod
+    @abstractmethod
     def join(self):
-        """ Block until the monitor has stopped."""
+        """Block until the monitor has stopped."""
 
 
-class ILogger(with_metaclass(abc.ABCMeta)):
-    @abc.abstractmethod
+class ILogger(ABC):
+    @abstractmethod
     def error(self, msg):
-        """ Record an error message."""
+        """Record an error message."""
 
-    @abc.abstractmethod
+    @abstractmethod
     def info(self, msg):
-        """ Record an informational message."""
+        """Record an informational message."""
 
-    @abc.abstractmethod
+    @abstractmethod
     def debug(self, msg):
-        """ Record a debug-only message."""
+        """Record a debug-only message."""
diff --git a/src/hupper/ipc.py b/src/hupper/ipc.py
index 97e457b..cec1a85 100644
--- a/src/hupper/ipc.py
+++ b/src/hupper/ipc.py
@@ -1,15 +1,16 @@
 import io
 import os
+import pickle
 import struct
 import subprocess
 import sys
 import threading
 
-from .compat import WIN, pickle, subprocess_wait_with_timeout
-from .utils import is_stream_interactive, resolve_spec
+from .utils import WIN, is_stream_interactive, resolve_spec
 
 if WIN:  # pragma: no cover
     import msvcrt
+
     from . import winapi
 
     class ProcessGroup(object):
@@ -33,7 +34,7 @@ if WIN:  # pragma: no cover
             try:
                 return winapi.AssignProcessToJobObject(self.h_job, hp)
             except OSError as ex:
-                if getattr(ex, 'winerror') == 5:
+                if getattr(ex, 'winerror', None) == 5:
                     # skip ACCESS_DENIED_ERROR on windows < 8 which occurs when
                     # the process is already attached to another job
                     pass
@@ -59,7 +60,6 @@ if WIN:  # pragma: no cover
             flags |= os.O_APPEND
         return msvcrt.open_osfhandle(handle, flags)
 
-
 else:
     import fcntl
     import termios
@@ -327,7 +327,10 @@ def wait(process, timeout=None):
     if timeout == 0:
         return process.poll()
 
-    return subprocess_wait_with_timeout(process, timeout)
+    try:
+        return process.wait(timeout)
+    except subprocess.TimeoutExpired:
+        pass
 
 
 def kill(process, soft=False):
diff --git a/src/hupper/polling.py b/src/hupper/polling.py
index ef36e89..6973fc2 100644
--- a/src/hupper/polling.py
+++ b/src/hupper/polling.py
@@ -58,6 +58,6 @@ def get_mtime(path):
         stat = os.stat(path)
         if stat:
             return stat.st_mtime
-    except (OSError, IOError):  # pragma: no cover
+    except OSError:  # pragma: no cover
         pass
     return 0
diff --git a/src/hupper/reloader.py b/src/hupper/reloader.py
index 558cbfc..bb38f13 100644
--- a/src/hupper/reloader.py
+++ b/src/hupper/reloader.py
@@ -1,6 +1,7 @@
 from collections import deque
 from contextlib import contextmanager
 import fnmatch
+from glob import glob
 import os
 import re
 import signal
@@ -8,10 +9,10 @@ import sys
 import threading
 import time
 
-from .compat import WIN, glob
 from .ipc import ProcessGroup
 from .logger import DefaultLogger, SilentLogger
 from .utils import (
+    WIN,
     default,
     is_stream_interactive,
     is_watchdog_supported,
@@ -132,21 +133,26 @@ class Reloader(object):
         """
         Execute the reloader forever, blocking the current thread.
 
-        This will invoke ``sys.exit(1)`` if interrupted.
+        This will invoke ``sys.exit`` with the return code from the
+        subprocess. If interrupted before the process starts then
+        it'll exit with ``-1``.
 
         """
+        exitcode = -1
         with self._setup_runtime():
             while True:
-                result = self._run_worker()
-                start = time.time()
-                if result == WorkerResult.WAIT:
-                    result = self._wait_for_changes()
+                result, exitcode = self._run_worker()
                 if result == WorkerResult.EXIT:
                     break
+                start = time.time()
+                if result == WorkerResult.WAIT:
+                    result, _ = self._wait_for_changes()
+                    if result == WorkerResult.EXIT:
+                        break
                 dt = self.reload_interval - (time.time() - start)
                 if dt > 0:
                     time.sleep(dt)
-        sys.exit(1)
+        sys.exit(exitcode)
 
     def run_once(self):
         """
@@ -154,9 +160,12 @@ class Reloader(object):
 
         This method will return after the worker exits.
 
+        Returns the exit code from the worker process.
+
         """
         with self._setup_runtime():
-            self._run_worker()
+            _, exitcode = self._run_worker()
+            return exitcode
 
     def _run_worker(self):
         worker = Worker(
@@ -289,7 +298,8 @@ def _run_worker(self, worker, logger=None, shutdown_interval=None):
 
                     if worker.is_alive:
                         logger.info(
-                            'Worker pipe died unexpectedly, triggering a reload.'
+                            'Worker pipe died unexpectedly, triggering a '
+                            'reload.'
                         )
                         result = WorkerResult.RELOAD
                         break
@@ -368,7 +378,7 @@ def _run_worker(self, worker, logger=None, shutdown_interval=None):
             worker.join()
         logger.debug('Server exited with code %d.' % worker.exitcode)
 
-    return result
+    return result, worker.exitcode
 
 
 def wait_main():
@@ -476,6 +486,11 @@ def start_reloader(
     if shutdown_interval is default:
         shutdown_interval = reload_interval
 
+    if reload_interval <= 0:
+        raise ValueError(
+            'reload_interval must be greater than 0 to avoid spinning'
+        )
+
     reloader = Reloader(
         worker_path=worker_path,
         worker_args=worker_args,
diff --git a/src/hupper/utils.py b/src/hupper/utils.py
index d8ccfb2..581ecd2 100644
--- a/src/hupper/utils.py
+++ b/src/hupper/utils.py
@@ -2,8 +2,9 @@ import importlib
 import json
 import os
 import subprocess
+import sys
 
-from .compat import WIN
+WIN = sys.platform == 'win32'
 
 
 class Sentinel(object):
@@ -25,7 +26,7 @@ def resolve_spec(spec):
 
 
 def is_watchdog_supported():
-    """ Return ``True`` if watchdog is available."""
+    """Return ``True`` if watchdog is available."""
     try:
         import watchdog  # noqa: F401
     except ImportError:
@@ -34,7 +35,7 @@ def is_watchdog_supported():
 
 
 def is_watchman_supported():
-    """ Return ``True`` if watchman is available."""
+    """Return ``True`` if watchman is available."""
     if WIN:
         # for now we aren't bothering with windows sockets
         return False
@@ -47,7 +48,7 @@ def is_watchman_supported():
 
 
 def get_watchman_sockpath(binpath='watchman'):
-    """ Find the watchman socket or raise."""
+    """Find the watchman socket or raise."""
     path = os.getenv('WATCHMAN_SOCK')
     if path:
         return path
diff --git a/src/hupper/watchdog.py b/src/hupper/watchdog.py
index 6511f6d..4c05305 100644
--- a/src/hupper/watchdog.py
+++ b/src/hupper/watchdog.py
@@ -33,7 +33,7 @@ class WatchdogFileMonitor(FileSystemEventHandler, Observer, IFileMonitor):
             if dirpath not in self.dirpaths:
                 try:
                     self.schedule(self, dirpath)
-                except (OSError, IOError) as ex:  # pragma: no cover
+                except OSError as ex:  # pragma: no cover
                     # watchdog raises exceptions if folders are missing
                     # or if the ulimit is passed
                     self.logger.error('watchdog error: ' + str(ex))
diff --git a/src/hupper/watchman.py b/src/hupper/watchman.py
index 12f9de9..858f0ef 100644
--- a/src/hupper/watchman.py
+++ b/src/hupper/watchman.py
@@ -6,7 +6,6 @@ import socket
 import threading
 import time
 
-from .compat import PY2
 from .interfaces import IFileMonitor
 from .utils import get_watchman_sockpath
 
@@ -27,7 +26,7 @@ class WatchmanFileMonitor(threading.Thread, IFileMonitor):
         sockpath=None,
         binpath='watchman',
         timeout=1.0,
-        **kw
+        **kw,
     ):
         super(WatchmanFileMonitor, self).__init__()
         self.callback = callback
@@ -158,9 +157,7 @@ class WatchmanFileMonitor(threading.Thread, IFileMonitor):
             self._recvbufs.append(b)
 
     def _recv(self):
-        line = self._readline()
-        if not PY2:
-            line = line.decode('utf8')
+        line = self._readline().decode('utf8')
         try:
             return json.loads(line)
         except Exception:  # pragma: no cover
@@ -168,9 +165,7 @@ class WatchmanFileMonitor(threading.Thread, IFileMonitor):
             return {}
 
     def _send(self, msg):
-        cmd = json.dumps(msg)
-        if not PY2:
-            cmd = cmd.encode('ascii')
+        cmd = json.dumps(msg).encode('ascii')
         self._sock.sendall(cmd + b'\n')
 
     def _query(self, msg, timeout=None):
diff --git a/src/hupper/worker.py b/src/hupper/worker.py
index 271e95b..3ce5d11 100644
--- a/src/hupper/worker.py
+++ b/src/hupper/worker.py
@@ -1,5 +1,8 @@
+from _thread import interrupt_main
+from importlib.util import source_from_cache
 import os
 import signal
+import site
 import sys
 import sysconfig
 import threading
@@ -7,13 +10,12 @@ import time
 import traceback
 
 from . import ipc
-from .compat import get_py_path, get_site_packages, interrupt_main
 from .interfaces import IReloaderProxy
 from .utils import resolve_spec
 
 
 class WatchSysModules(threading.Thread):
-    """ Poll ``sys.modules`` for imported modules."""
+    """Poll ``sys.modules`` for imported modules."""
 
     poll_interval = 1
     ignore_system_paths = True
@@ -35,7 +37,7 @@ class WatchSysModules(threading.Thread):
         self.stopped = True
 
     def update_paths(self):
-        """ Check sys.modules for paths to add to our path set."""
+        """Check sys.modules for paths to add to our path set."""
         new_paths = []
         with self.lock:
             for path in expand_source_paths(iter_module_paths()):
@@ -46,10 +48,10 @@ class WatchSysModules(threading.Thread):
             self.watch_paths(new_paths)
 
     def search_traceback(self, tb):
-        """ Inspect a traceback for new paths to add to our path set."""
+        """Inspect a traceback for new paths to add to our path set."""
         new_paths = []
         with self.lock:
-            for filename, line, funcname, txt in traceback.extract_tb(tb):
+            for filename, *_ in traceback.extract_tb(tb):
                 path = os.path.abspath(filename)
                 if path not in self.paths:
                     self.paths.add(path)
@@ -73,6 +75,35 @@ class WatchSysModules(threading.Thread):
         return False
 
 
+def get_py_path(path):
+    try:
+        return source_from_cache(path)
+    except ValueError:
+        # fallback for solitary *.pyc files outside of __pycache__
+        return path[:-1]
+
+
+def get_site_packages():  # pragma: no cover
+    try:
+        paths = site.getsitepackages()
+        if site.ENABLE_USER_SITE:
+            paths.append(site.getusersitepackages())
+        return paths
+
+    # virtualenv does not ship with a getsitepackages impl so we fallback
+    # to using distutils if we can
+    # https://github.com/pypa/virtualenv/issues/355
+    except Exception:
+        try:
+            from distutils.sysconfig import get_python_lib
+
+            return [get_python_lib()]
+
+        # just incase, don't fail here, it's not worth it
+        except Exception:
+            return []
+
+
 def get_system_paths():
     paths = get_site_packages()
     for name in {'stdlib', 'platstdlib', 'platlib', 'purelib'}:
@@ -83,7 +114,7 @@ def get_system_paths():
 
 
 def expand_source_paths(paths):
-    """ Convert pyc files into their source equivalents."""
+    """Convert pyc files into their source equivalents."""
     for src_path in paths:
         # only track the source path if we can find it to avoid double-reloads
         # when the source and the compiled path change because on some
@@ -96,7 +127,7 @@ def expand_source_paths(paths):
 
 
 def iter_module_paths(modules=None):
-    """ Yield paths of all imported modules."""
+    """Yield paths of all imported modules."""
     modules = modules or list(sys.modules.values())
     for module in modules:
         try:
@@ -110,7 +141,7 @@ def iter_module_paths(modules=None):
 
 
 class Worker(object):
-    """ A helper object for managing a worker process lifecycle. """
+    """A helper object for managing a worker process lifecycle."""
 
     def __init__(self, spec, args=None, kwargs=None):
         super(Worker, self).__init__()
@@ -259,7 +290,8 @@ def worker_main(spec, pipe, spec_args=None, spec_kwargs=None):
         raise
     finally:
         try:
-            # attempt to send imported paths to the master prior to closing
+            # attempt to send imported paths to the reloader process prior to
+            # closing
             poller.update_paths()
             poller.stop()
             poller.join()
diff --git a/tox.ini b/tox.ini
index 3807da5..833e93d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,7 @@
 [tox]
 envlist =
     lint,
-    py27,py34,py35,py36,py37,py38,py39,pypy,pypy3,
+    py37,py38,py39,py310,py311,pypy3,
     docs,coverage
 
 isolated_build = true
@@ -30,7 +30,7 @@ setenv =
     COVERAGE_FILE=.coverage
 
 [testenv:docs]
-whitelist_externals =
+allowlist_externals =
     make
 commands =
     make -C docs html BUILDDIR={envdir} SPHINXOPTS="-W -E"
@@ -38,24 +38,29 @@ extras =
     docs
 
 [testenv:lint]
-skip_install = true
+skip_install = True
 commands =
-    flake8 src/hupper/
-    isort --check -rc src/hupper tests setup.py
+    isort --check-only --df src/hupper tests setup.py
     black --check --diff src/hupper tests setup.py
-    python setup.py check -r -s -m
+    flake8 src/hupper tests setup.py
     check-manifest
+    # build sdist/wheel
+    python -m build .
+    twine check dist/*
 deps =
     black
+    build
     check-manifest
     flake8
+    flake8-bugbear
     isort
     readme_renderer
+    twine
 
 [testenv:format]
 skip_install = true
 commands =
-    isort -rc src/hupper tests setup.py
+    isort src/hupper tests setup.py
     black src/hupper tests setup.py
 deps =
     black
@@ -65,12 +70,16 @@ deps =
 skip_install = true
 commands =
     # clean up build/ and dist/ folders
-    python -c 'import shutil; shutil.rmtree("dist", ignore_errors=True)'
-    python setup.py clean --all
-    # build sdist
-    python setup.py sdist --dist-dir {toxinidir}/dist
-    # build wheel from sdist
-    pip wheel -v --no-deps --no-index --no-build-isolation --wheel-dir {toxinidir}/dist --find-links {toxinidir}/dist hupper
+    python -c 'import shutil; shutil.rmtree("build", ignore_errors=True)'
+    # Make sure we aren't forgetting anything
+    check-manifest
+    # build sdist/wheel
+    python -m build .
+    # Verify all is well
+    twine check dist/*
+
 deps =
-    setuptools
-    wheel
+    build
+    check-manifest
+    readme_renderer
+    twine

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.12.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.12.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.12.egg-info/entry_points.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.12.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.12.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.12.egg-info/top_level.txt

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.10.3.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.10.3.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.10.3.egg-info/entry_points.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.10.3.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.10.3.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper-1.10.3.egg-info/top_level.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/hupper/compat.py

No differences were encountered in the control files

More details

Full run details