Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-futurist for openSUSE:Factory 
checked in at 2025-11-10 19:17:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-futurist (Old)
 and      /work/SRC/openSUSE:Factory/.python-futurist.new.1980 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-futurist"

Mon Nov 10 19:17:00 2025 rev:17 rq:1316757 version:3.2.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-futurist/python-futurist.changes  
2024-10-08 17:25:47.688499462 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-futurist.new.1980/python-futurist.changes    
    2025-11-10 19:17:14.304090390 +0100
@@ -1,0 +2,30 @@
+Sun Nov  9 14:34:13 UTC 2025 - Dirk Müller <[email protected]>
+
+- update to 3.2.1:
+  * Fix \`shutdown(wait=True)\` to wait for queued tasks
+  * Use consistent validation error message for worker args
+  * Remove sleeps from DynamicThreadPoolExecutor tests
+  * Add DynamicThreadPoolExecutor that resizes itself
+  * Fix ThreadWorker.stop to stop only this worker
+  * Provide insights into the state of ThreadPoolExecutor
+  * Trivial: avoid noqa in \_\_init\_\_.py
+  * add pyproject.toml to support pip 23.1
+  * Drop explicit dependency on python-subunit
+  * tox: Remove basepython
+- switch to singlespec
+
+-------------------------------------------------------------------
+Fri Jul 18 21:46:33 UTC 2025 - Dirk Müller <[email protected]>
+
+- update to 3.1.0:
+  * deprecate eventlet based classes (green executors)
+  * Remove Python 3.8 support
+  * Run pyupgrade to clean up Python 2 syntaxes
+  * Use pre-commit hook to run doc8
+  * pre-commit: Bump versions
+  * Declare Python 3.12 support
+  * Replace use of testtools.testcase.TestSkipped
+  * Remove old excludes
+  * reno: Update master for unmaintained/victoria
+
+-------------------------------------------------------------------

Old:
----
  _service
  futurist-3.0.0.tar.gz

New:
----
  futurist-3.2.1.tar.gz

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

Other differences:
------------------
++++++ python-futurist.spec ++++++
--- /var/tmp/diff_new_pack.IYZznH/_old  2025-11-10 19:17:16.052163622 +0100
+++ /var/tmp/diff_new_pack.IYZznH/_new  2025-11-10 19:17:16.064164124 +0100
@@ -1,7 +1,7 @@
 #
 # spec file for package python-futurist
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,22 +17,30 @@
 
 
 Name:           python-futurist
-Version:        3.0.0
+Version:        3.2.1
 Release:        0
 Summary:        Useful additions to futures, from the future.
 License:        Apache-2.0
 Group:          Development/Languages/Python
 URL:            https://docs.openstack.org/futurist
-Source0:        
https://files.pythonhosted.org/packages/source/f/futurist/futurist-3.0.0.tar.gz
+Source0:        
https://files.pythonhosted.org/packages/source/f/futurist/futurist-%{version}.tar.gz
+BuildRequires:  %{python_module PrettyTable}
+BuildRequires:  %{python_module debtcollector >= 3.0.0}
+BuildRequires:  %{python_module eventlet}
+BuildRequires:  %{python_module oslotest}
+BuildRequires:  %{python_module pip}
+BuildRequires:  %{python_module stestr}
+BuildRequires:  %{python_module testscenarios}
+BuildRequires:  %{python_module wheel}
 BuildRequires:  openstack-macros
-BuildRequires:  python3-PrettyTable
 BuildRequires:  python3-Sphinx
-BuildRequires:  python3-eventlet
 BuildRequires:  python3-openstackdocstheme
-BuildRequires:  python3-oslotest
-BuildRequires:  python3-stestr
-BuildRequires:  python3-testscenarios
+Requires:       python-debtcollector >= 3.0.0
 BuildArch:      noarch
+%if "python%{python_nodots_ver}" == "%{primary_python}"
+Obsoletes:      python3-futurist < %{version}
+%endif
+%python_subpackages
 
 %description
 Useful additions to futures, from the future.
@@ -46,26 +54,25 @@
 This package contains the Python 3.x module.
 
 %prep
-%autosetup -p1 -n futurist-3.0.0
-%py_req_cleanup
+%autosetup -p1 -n futurist-%{version}
 
 %build
-%{py3_build}
+%pyproject_wheel
 
 # generate html docs
-PBR_VERSION=3.0.0 %sphinx_build -b html doc/source doc/build/html
+PBR_VERSION=%{version} sphinx-build -b html doc/source doc/build/html
 # remove the sphinx-build leftovers
 rm -r doc/build/html/.{doctrees,buildinfo}
 
 %install
-%{py3_install}
+%pyproject_install
 
 %check
 %{openstack_stestr_run}
 
-%files -n python3-futurist
+%files %{python_files}
 %doc doc/build/html README.rst
 %license LICENSE
-%{python3_sitelib}/futurist
-%{python3_sitelib}/futurist-*-py?.*.egg-info
+%{python_sitelib}/futurist
+%{python_sitelib}/futurist-%{version}.dist-info
 

++++++ futurist-3.0.0.tar.gz -> futurist-3.2.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/.pre-commit-config.yaml 
new/futurist-3.2.1/.pre-commit-config.yaml
--- old/futurist-3.0.0/.pre-commit-config.yaml  2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/.pre-commit-config.yaml  2025-08-29 17:05:26.000000000 
+0200
@@ -1,15 +1,9 @@
-# We from the Oslo project decided to pin repos based on the
-# commit hash instead of the version tag to prevend arbitrary
-# code from running in developer's machines.  To update to a
-# newer version, run `pre-commit autoupdate` and then replace
-# the newer versions with their commit hash.
-
 default_language_version:
   python: python3
 
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: 9136088a246768144165fcc3ecc3d31bb686920a # v3.3.0
+    rev: v5.0.0
     hooks:
       - id: trailing-whitespace
       # Replaces or checks mixed line ending
@@ -27,13 +21,18 @@
       - id: debug-statements
       - id: check-yaml
         files: .*\.(yaml|yml)$
-  - repo: local
+  - repo: https://opendev.org/openstack/hacking
+    rev: 7.0.0
+    hooks:
+      - id: hacking
+        additional_dependencies: []
+  - repo: https://github.com/PyCQA/doc8
+    rev: v1.1.2
+    hooks:
+      - id: doc8
+        files: doc/source/.*\.rst$
+  - repo: https://github.com/asottile/pyupgrade
+    rev: v3.18.0
     hooks:
-      - id: flake8
-        name: flake8
-        additional_dependencies:
-          - hacking>=6.1.0,<6.2.0
-        language: python
-        entry: flake8
-        files: '^.*\.py$'
-        exclude: '^(doc|releasenotes|tools)/.*$'
+      - id: pyupgrade
+        args: [--py3-only]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/AUTHORS new/futurist-3.2.1/AUTHORS
--- old/futurist-3.0.0/AUTHORS  2024-02-23 13:20:16.000000000 +0100
+++ new/futurist-3.2.1/AUTHORS  2025-08-29 17:06:00.000000000 +0200
@@ -1,4 +1,5 @@
 98k <[email protected]>
+Afonne-CID <[email protected]>
 Akihiro Motoki <[email protected]>
 Andreas Jaeger <[email protected]>
 Anh Tran <[email protected]>
@@ -8,6 +9,7 @@
 Daniel Bengtsson <[email protected]>
 Davanum Srinivas <[email protected]>
 Dmitriy Ukhlov <[email protected]>
+Dmitry Tantsur <[email protected]>
 Dmitry Tantsur <[email protected]>
 Doug Hellmann <[email protected]>
 Flavio Percoco <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/ChangeLog new/futurist-3.2.1/ChangeLog
--- old/futurist-3.0.0/ChangeLog        2024-02-23 13:20:16.000000000 +0100
+++ new/futurist-3.2.1/ChangeLog        2025-08-29 17:06:00.000000000 +0200
@@ -1,6 +1,42 @@
 CHANGES
 =======
 
+3.2.1
+-----
+
+* Fix \`shutdown(wait=True)\` to wait for queued tasks
+
+3.2.0
+-----
+
+* Use consistent validation error message for worker args
+* Remove sleeps from DynamicThreadPoolExecutor tests
+* Add DynamicThreadPoolExecutor that resizes itself
+* Fix ThreadWorker.stop to stop only this worker
+* Provide insights into the state of ThreadPoolExecutor
+* Trivial: avoid noqa in \_\_init\_\_.py
+* add pyproject.toml to support pip 23.1
+* Drop explicit dependency on python-subunit
+* tox: Remove basepython
+
+3.1.1
+-----
+
+
+3.1.0
+-----
+
+* deprecate eventlet based classes (green executors)
+* Remove fallback logic for Python < 3.3
+* Remove Python 3.8 support
+* Run pyupgrade to clean up Python 2 syntaxes
+* Use pre-commit hook to run doc8
+* pre-commit: Bump versions
+* Declare Python 3.12 support
+* Replace use of testtools.testcase.TestSkipped
+* Remove old excludes
+* reno: Update master for unmaintained/victoria
+
 3.0.0
 -----
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/PKG-INFO new/futurist-3.2.1/PKG-INFO
--- old/futurist-3.0.0/PKG-INFO 2024-02-23 13:20:17.195890400 +0100
+++ new/futurist-3.2.1/PKG-INFO 2025-08-29 17:06:00.858912000 +0200
@@ -1,47 +1,10 @@
-Metadata-Version: 1.2
+Metadata-Version: 2.1
 Name: futurist
-Version: 3.0.0
+Version: 3.2.1
 Summary: Useful additions to futures, from the future.
 Home-page: https://docs.openstack.org/futurist/latest/
 Author: OpenStack
 Author-email: [email protected]
-License: UNKNOWN
-Description: ========================
-        Team and repository tags
-        ========================
-        
-        .. image:: https://governance.openstack.org/tc/badges/futurist.svg
-            :target: 
https://governance.openstack.org/tc/reference/tags/index.html
-        
-        .. Change things from this point on
-        
-        ========
-        Futurist
-        ========
-        
-        .. image:: https://img.shields.io/pypi/v/futurist.svg
-            :target: https://pypi.org/project/futurist/
-            :alt: Latest Version
-        
-        .. image:: https://img.shields.io/pypi/dm/futurist.svg
-            :target: https://pypi.org/project/futurist/
-            :alt: Downloads
-        
-        Code from the future, delivered to you in the **now**. The goal of 
this library
-        would be to provide a well documented futures 
classes/utilities/additions that
-        allows for providing a level of transparency in how asynchronous work 
gets
-        executed. This library currently adds statistics gathering, an eventlet
-        executor, a synchronous executor etc.
-        
-        * Free software: Apache license
-        * Documentation: https://docs.openstack.org/futurist/latest/
-        * Source: https://opendev.org/openstack/futurist
-        * Bugs: https://bugs.launchpad.net/futurist
-        * Blueprints: https://blueprints.launchpad.net/futurist
-        * Release notes: https://docs.openstack.org/releasenotes/futurist
-        
-        
-Platform: UNKNOWN
 Classifier: Environment :: OpenStack
 Classifier: Intended Audience :: Information Technology
 Classifier: Intended Audience :: System Administrators
@@ -49,10 +12,47 @@
 Classifier: Operating System :: POSIX :: Linux
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-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 :: 3.12
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: Implementation :: CPython
-Requires-Python: >=3.8
+Requires-Python: >=3.9
+License-File: LICENSE
+Requires-Dist: debtcollector>=3.0.0
+
+========================
+Team and repository tags
+========================
+
+.. image:: https://governance.openstack.org/tc/badges/futurist.svg
+    :target: https://governance.openstack.org/tc/reference/tags/index.html
+
+.. Change things from this point on
+
+========
+Futurist
+========
+
+.. image:: https://img.shields.io/pypi/v/futurist.svg
+    :target: https://pypi.org/project/futurist/
+    :alt: Latest Version
+
+.. image:: https://img.shields.io/pypi/dm/futurist.svg
+    :target: https://pypi.org/project/futurist/
+    :alt: Downloads
+
+Code from the future, delivered to you in the **now**. The goal of this library
+would be to provide a well documented futures classes/utilities/additions that
+allows for providing a level of transparency in how asynchronous work gets
+executed. This library currently adds statistics gathering, an eventlet
+executor, a synchronous executor etc.
+
+* Free software: Apache license
+* Documentation: https://docs.openstack.org/futurist/latest/
+* Source: https://opendev.org/openstack/futurist
+* Bugs: https://bugs.launchpad.net/futurist
+* Blueprints: https://blueprints.launchpad.net/futurist
+* Release notes: https://docs.openstack.org/releasenotes/futurist
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/doc/requirements.txt 
new/futurist-3.2.1/doc/requirements.txt
--- old/futurist-3.0.0/doc/requirements.txt     2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/doc/requirements.txt     2025-08-29 17:05:26.000000000 
+0200
@@ -1,3 +1,3 @@
-sphinx>=2.0.0,!=2.1.0 # BSD
+sphinx>=2.0.0 # BSD
 openstackdocstheme>=2.2.1 # Apache-2.0
 reno>=3.1.0 # Apache-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/doc/source/reference/index.rst 
new/futurist-3.2.1/doc/source/reference/index.rst
--- old/futurist-3.0.0/doc/source/reference/index.rst   2024-02-23 
13:19:46.000000000 +0100
+++ new/futurist-3.2.1/doc/source/reference/index.rst   2025-08-29 
17:05:26.000000000 +0200
@@ -22,6 +22,10 @@
     :members:
     :special-members: __init__
 
+.. autoclass:: futurist.DynamicThreadPoolExecutor
+    :members:
+    :special-members: __init__
+
 -------
 Futures
 -------
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/__init__.py 
new/futurist-3.2.1/futurist/__init__.py
--- old/futurist-3.0.0/futurist/__init__.py     2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/futurist/__init__.py     2025-08-29 17:05:26.000000000 
+0200
@@ -14,17 +14,33 @@
 
 # Promote accessible items to this module namespace (for easy access).
 
-from futurist._futures import Future  # noqa
-from futurist._futures import GreenFuture  # noqa
+from futurist._futures import Future
+from futurist._futures import GreenFuture
 
-from futurist._futures import CancelledError  # noqa
-from futurist._futures import TimeoutError  # noqa
+from futurist._futures import CancelledError
+from futurist._futures import TimeoutError
 
-from futurist._futures import GreenThreadPoolExecutor  # noqa
-from futurist._futures import ProcessPoolExecutor  # noqa
-from futurist._futures import SynchronousExecutor  # noqa
-from futurist._futures import ThreadPoolExecutor  # noqa
+from futurist._futures import DynamicThreadPoolExecutor
+from futurist._futures import GreenThreadPoolExecutor
+from futurist._futures import ProcessPoolExecutor
+from futurist._futures import SynchronousExecutor
+from futurist._futures import ThreadPoolExecutor
 
-from futurist._futures import RejectedSubmission  # noqa
+from futurist._futures import RejectedSubmission
 
-from futurist._futures import ExecutorStatistics  # noqa
+from futurist._futures import ExecutorStatistics
+
+
+__all__ = [
+    'Future', 'GreenFuture',
+
+    'CancelledError', 'TimeoutError',
+
+    'GreenThreadPoolExecutor', 'ProcessPoolExecutor',
+    'SynchronousExecutor', 'ThreadPoolExecutor',
+    'DynamicThreadPoolExecutor',
+
+    'RejectedSubmission',
+
+    'ExecutorStatistics',
+]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/_futures.py 
new/futurist-3.2.1/futurist/_futures.py
--- old/futurist-3.0.0/futurist/_futures.py     2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/futurist/_futures.py     2025-08-29 17:05:26.000000000 
+0200
@@ -13,21 +13,27 @@
 #    under the License.
 
 import functools
+import logging
 import queue
 import threading
+import time
 
 from concurrent import futures as _futures
 from concurrent.futures import process as _process
 
+from debtcollector import removals
+
 from futurist import _green
 from futurist import _thread
 from futurist import _utils
 
-
 TimeoutError = _futures.TimeoutError
 CancelledError = _futures.CancelledError
 
 
+LOG = logging.getLogger(__name__)
+
+
 class RejectedSubmission(Exception):
     """Exception raised when a submitted call is rejected (for some reason)."""
 
@@ -36,7 +42,7 @@
 Future = _futures.Future
 
 
-class _Gatherer(object):
+class _Gatherer:
     def __init__(self, submit_func, lock_factory, start_before_submit=False):
         self._submit_func = submit_func
         self._stats_lock = lock_factory()
@@ -101,6 +107,10 @@
 
     It gathers statistics about the submissions executed for post-analysis...
 
+    Note that this executor never shrinks its thread pool, which will cause
+    the pool to eventually reach its maximum capacity defined by max_workers.
+    Check :py:class:`DynamicThreadPoolExecutor` for an alternative.
+
     See: https://docs.python.org/dev/library/concurrent.futures.html
     """
 
@@ -127,7 +137,7 @@
         if max_workers is None:
             max_workers = _utils.get_optimal_thread_count()
         if max_workers <= 0:
-            raise ValueError("Max workers must be greater than zero")
+            raise ValueError("max_workers must be greater than zero")
         self._max_workers = max_workers
         self._work_queue = queue.Queue()
         self._shutdown_lock = threading.RLock()
@@ -146,23 +156,54 @@
         """Accessor to determine if the executor is alive/active."""
         return not self._shutdown
 
+    @property
+    def queue_size(self):
+        """The current size of the queue.
+
+        This value represents the number of tasks that are waiting for a free
+        worker thread.
+        """
+        return self._work_queue.qsize()
+
+    @property
+    def num_workers(self):
+        """The current number of worker threads."""
+        return len(self._workers)
+
+    def get_num_idle_workers(self):
+        """Get the number of currently idle threads.
+
+        A thread is idle if it's waiting for new tasks from the queue.
+
+        This method is required to obtain the shutdown lock it provides
+        an accurate count.
+        """
+        with self._shutdown_lock:
+            return sum(1 for w in self._workers if w.idle)
+
+    def _add_thread(self):
+        w = _thread.ThreadWorker.create_and_register(
+            self, self._work_queue)
+        # Always save it before we start (so that even if we fail
+        # starting it we can correctly join on it).
+        self._workers.append(w)
+        w.start()
+
     def _maybe_spin_up(self):
         """Spin up a worker if needed."""
-        # Do more advanced idle checks and/or reaping of very idle
-        # threads in the future....
         if (not self._workers or
                 len(self._workers) < self._max_workers):
-            w = _thread.ThreadWorker.create_and_register(
-                self, self._work_queue)
-            # Always save it before we start (so that even if we fail
-            # starting it we can correctly join on it).
-            self._workers.append(w)
-            w.start()
+            self._add_thread()
 
     def shutdown(self, wait=True):
         with self._shutdown_lock:
             if not self._shutdown:
                 self._shutdown = True
+                if wait:
+                    # Wait for all queued work to complete using queue.join()
+                    # This will block until all work items have been processed
+                    # and task_done() has been called for each
+                    self._work_queue.join()
                 for w in self._workers:
                     w.stop()
         if wait:
@@ -177,6 +218,9 @@
 
     def submit(self, fn, *args, **kwargs):
         """Submit some work to be executed (and gather statistics)."""
+        # NOTE(dtantsur): DynamicThreadPoolExecutor relies on this lock for
+        # its complex logic around thread management. If you ever decide to
+        # remove it, please add a lock there instead.
         with self._shutdown_lock:
             if self._shutdown:
                 raise RuntimeError('Can not schedule new futures'
@@ -185,6 +229,174 @@
             return self._gatherer.submit(fn, *args, **kwargs)
 
 
+class DynamicThreadPoolExecutor(ThreadPoolExecutor):
+    """Executor that creates or removes threads on demand.
+
+    As new work is scheduled on the executor, it will try to keep the
+    proportion of busy threads within the provided range (between 40% and 80%
+    by default). A busy thread is a thread that is not waiting on the
+    task queue.
+
+    Each time a task is submitted, the executor makes a decision whether to
+    grow or shrink the pool. It takes the proportion of the number of busy
+    threads to the total number of threads and compares it to shrink_threshold
+    and grow_threshold.
+
+    Initially, the pool is empty, so submitting a task always result in one
+    new thread. Since min_workers must be greater than zero, at least one
+    thread will always be available after this point.
+
+    Once the proportion of busy threads reaches grow_threshold (e.g. 4 out of 5
+    with the default grow_threshold of 0.8), a new thread is created when a
+    task is submitted. If on submitting a task a proportion of busy threads is
+    below shrink_threshold (e.g. only 2 out of 5), one idle thread is stopped.
+
+    The values of grow_threshold and shrink_threshold are different to prevent
+    the number of threads from oscilating on reaching grow_threshold.
+
+    If threads are not created often in your application, the number of idle
+    threads may stay high for a long time. To avoid it, you can call
+    :py:meth:`.maintain` periodically to keep the number of threads within
+    the thresholds.
+
+    """
+
+    def __init__(self, max_workers=None, check_and_reject=None,
+                 min_workers=1, grow_threshold=0.8,
+                 shrink_threshold=0.4):
+        """Initializes a thread pool executor.
+
+        :param max_workers: maximum number of workers that can be
+                            simultaneously active at the same time, further
+                            submitted work will be queued up when this limit
+                            is reached.
+        :type max_workers: int
+        :param check_and_reject: a callback function that will be provided
+                                 two position arguments, the first argument
+                                 will be this executor instance, and the second
+                                 will be the number of currently queued work
+                                 items in this executors backlog; the callback
+                                 should raise a :py:class:`.RejectedSubmission`
+                                 exception if it wants to have this submission
+                                 rejected.
+        :type check_and_reject: callback
+        :param min_workers: the minimum number of workers that can be reached
+                            when shrinking the pool. Note that the pool always
+                            starts at zero workers and will be smaller than
+                            min_workers until enough workers are created.
+                            At least one thread is required.
+        :type max_workers: int
+        :param grow_threshold: minimum proportion of busy threads to total
+                               threads for the pool to grow.
+        :type grow_threshold: float
+        :param shrink_threshold: maximum proportion of busy threads to total
+                                 threads for the pool to shrink.
+        :type shrink_threshold: float
+        """
+        super().__init__(max_workers=max_workers,
+                         check_and_reject=check_and_reject)
+        if min_workers <= 0:
+            raise ValueError('min_workers must be greater than zero')
+        if max_workers and min_workers >= max_workers:
+            raise ValueError('min_workers must be less than max_workers')
+        self._min_workers = min_workers
+
+        if grow_threshold <= 0 or grow_threshold > 1.0:
+            raise ValueError('grow_threshold must be within (0, 1]')
+        if shrink_threshold < 0 or shrink_threshold >= 1.0:
+            raise ValueError('shrink_threshold must be within [0, 1)')
+        if shrink_threshold >= grow_threshold:
+            raise ValueError(
+                'shrink_threshold must be less than grow_threshold')
+        self._grow_threshold = grow_threshold
+        self._shrink_threshold = shrink_threshold
+
+        self._dead_workers = []
+
+    def _drop_thread(self):
+        new_workers = []
+        idle_worker = None
+        for i, w in enumerate(self._workers):
+            if w.idle:
+                new_workers = self._workers[i + 1:]
+                idle_worker = w
+                break
+            new_workers.append(w)
+
+        if idle_worker is None:
+            # Should not actually happen but races are possible; do nothing
+            LOG.warning(
+                'No idle worker thread to delete when shrinking pool %r', self)
+            return False
+
+        w.stop()
+        self._workers = new_workers
+        self._dead_workers.append(w)
+        return True
+
+    def _maybe_spin_up(self):
+        nthreads = self.num_workers
+        if nthreads < self._min_workers:
+            self._add_thread()
+            return True
+
+        # NOTE(dtantsur): here we count the number of threads that are
+        # doing something (i.e. are not waiting on the queue) plus the
+        # number of tasks in the queue. In theory, if there are idle
+        # workers, the queue should be empty. But race conditions are
+        # possible when workers do not pick up tasks quickly enough,
+        # especially in the presence of CPU-bound tasks.
+        idle = self.get_num_idle_workers()
+        busy = (nthreads - idle + self.queue_size) / nthreads
+        if busy >= self._grow_threshold and nthreads < self._max_workers:
+            LOG.debug('Creating a new worker thread for pool %r '
+                      '(%d thread(s) idle, queue size %d, total %d thread(s))',
+                      self, idle, self.queue_size, nthreads)
+            self._add_thread()
+            return True
+        elif busy <= self._shrink_threshold and nthreads > self._min_workers:
+            LOG.debug('Deleting a worker thread from pool %r '
+                      '(%d thread(s) idle, queue size %d, total %d thread(s))',
+                      self, idle, self.queue_size, nthreads)
+            return self._drop_thread()
+
+        return False
+
+    def maintain(self):
+        """Keep the number of threads within the expected range.
+
+        If too many idle threads are running, they are deleted.
+        Additionally, deleted workers are joined to free up resources.
+        """
+        # NOTE(dtantsur): this call can potentially run for some time, so
+        # avoid taking shutdown_lock once and holding it for the entire
+        # duration (blocking any new tasks from being added).
+        keep_going = True
+        while keep_going:
+            if self._shutdown:
+                return
+
+            with self._shutdown_lock:
+                keep_going = self._maybe_spin_up()
+
+            time.sleep(0)
+
+        # NOTE(dtantsur): copy the value of _dead_workers to prevent races with
+        # other invocations for maintain or shutdown.
+        with self._shutdown_lock:
+            dead_workers = self._dead_workers
+            self._dead_workers = []
+
+        for w in dead_workers:
+            w.join()
+
+    def shutdown(self, wait=True):
+        super().shutdown(wait=wait)
+        if wait:
+            for w in self._dead_workers:
+                w.join()
+
+
 class ProcessPoolExecutor(_process.ProcessPoolExecutor):
     """Executor that uses a process pool to execute calls asynchronously.
 
@@ -198,14 +410,14 @@
     def __init__(self, max_workers=None):
         if max_workers is None:
             max_workers = _utils.get_optimal_process_count()
-        super(ProcessPoolExecutor, self).__init__(max_workers=max_workers)
+        super().__init__(max_workers=max_workers)
         if self._max_workers <= 0:
             raise ValueError("Max workers must be greater than zero")
         self._gatherer = _Gatherer(
             # Since our submit will use this gatherer we have to reference
             # the parent submit, bound to this instance (which is what we
             # really want to use anyway).
-            super(ProcessPoolExecutor, self).submit,
+            super().submit,
             self.threading.lock_object)
 
     @property
@@ -236,6 +448,10 @@
 
     threading = _thread.Threading()
 
+    @removals.removed_kwarg('green',
+                            message="Eventlet support is deprecated. "
+                            "Please migrate your code and stop enforcing "
+                            "its usage.")
     def __init__(self, green=False, run_work_func=lambda work: work.run()):
         """Synchronous executor constructor.
 
@@ -299,11 +515,15 @@
         return fut
 
 
[email protected]_class("GreenFuture",
+                        message="Eventlet support is deprecated. "
+                        "Please migrate your code and stop using Green "
+                        "future.")
 class GreenFuture(Future):
     __doc__ = Future.__doc__
 
     def __init__(self):
-        super(GreenFuture, self).__init__()
+        super().__init__()
         if not _utils.EVENTLET_AVAILABLE:
             raise RuntimeError('Eventlet is needed to use a green future')
         # NOTE(harlowja): replace the built-in condition with a greenthread
@@ -315,6 +535,10 @@
             self._condition = _green.threading.condition_object()
 
 
[email protected]_class("GreenThreadPoolExecutor",
+                        message="Eventlet support is deprecated. "
+                        "Please migrate your code and stop using Green "
+                        "executor.")
 class GreenThreadPoolExecutor(_futures.Executor):
     """Executor that uses a green thread pool to execute calls asynchronously.
 
@@ -415,7 +639,7 @@
             self._pool.waitall()
 
 
-class ExecutorStatistics(object):
+class ExecutorStatistics:
     """Holds *immutable* information about a executors executions."""
 
     __slots__ = ['_failures', '_executed', '_runtime', '_cancelled']
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/_green.py 
new/futurist-3.2.1/futurist/_green.py
--- old/futurist-3.0.0/futurist/_green.py       2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/futurist/_green.py       2025-08-29 17:05:26.000000000 
+0200
@@ -32,7 +32,7 @@
     Queue = greenqueue.Queue
     is_monkey_patched = greenpatcher.is_monkey_patched
 
-    class GreenThreading(object):
+    class GreenThreading:
 
         @staticmethod
         def event_object(*args, **kwargs):
@@ -58,7 +58,7 @@
     is_monkey_patched = lambda mod: False
 
 
-class GreenWorker(object):
+class GreenWorker:
     def __init__(self, work, work_queue):
         self.work = work
         self.work_queue = work_queue
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/_thread.py 
new/futurist-3.2.1/futurist/_thread.py
--- old/futurist-3.0.0/futurist/_thread.py      2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/futurist/_thread.py      2025-08-29 17:05:26.000000000 
+0200
@@ -16,7 +16,7 @@
 import weakref
 
 
-class Threading(object):
+class Threading:
 
     @staticmethod
     def event_object(*args, **kwargs):
@@ -37,22 +37,24 @@
 
 _to_be_cleaned = weakref.WeakKeyDictionary()
 _dying = False
-_TOMBSTONE = object()
+
+
+class _Stopping(Exception):
+    pass
 
 
 class ThreadWorker(threading.Thread):
     MAX_IDLE_FOR = 1
 
     def __init__(self, executor, work_queue):
-        super(ThreadWorker, self).__init__()
+        super().__init__()
         self.work_queue = work_queue
         self.should_stop = False
         self.idle = False
         self.daemon = True
         # Ensure that when the owning executor gets cleaned up that these
         # threads also get shutdown (if they were not already shutdown).
-        self.executor_ref = weakref.ref(
-            executor, lambda _obj: work_queue.put(_TOMBSTONE))
+        self.executor_ref = weakref.ref(executor, lambda _obj: self.stop())
 
     @classmethod
     def create_and_register(cls, executor, work_queue):
@@ -84,28 +86,25 @@
                 work = self.work_queue.get(True, self.MAX_IDLE_FOR)
             except queue.Empty:
                 if self._is_dying():
-                    work = _TOMBSTONE
+                    raise _Stopping()
         self.idle = False
         return work
 
-    def stop(self, soon_as_possible=False):
-        if soon_as_possible:
-            # This will potentially leave unfinished work on queues.
-            self.should_stop = True
-        self.work_queue.put(_TOMBSTONE)
+    def stop(self):
+        self.should_stop = True
 
     def run(self):
         while not self._is_dying():
-            work = self._wait_for_work()
             try:
-                if work is _TOMBSTONE:
-                    # Ensure any other threads on the same queue also get
-                    # the tombstone object...
-                    self.work_queue.put(_TOMBSTONE)
-                    return
-                else:
-                    work.run()
+                work = self._wait_for_work()
+            except _Stopping:
+                return
+
+            try:
+                work.run()
             finally:
+                # Mark task as done for queue.join() support
+                self.work_queue.task_done()
                 # Avoid any potential (self) references to the work item
                 # in tracebacks or similar...
                 del work
@@ -118,7 +117,7 @@
     threads_to_wait_for = []
     while _to_be_cleaned:
         worker, _work_val = _to_be_cleaned.popitem()
-        worker.stop(soon_as_possible=True)
+        worker.stop()
         threads_to_wait_for.append(worker)
     while threads_to_wait_for:
         worker = threads_to_wait_for.pop()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/_utils.py 
new/futurist-3.2.1/futurist/_utils.py
--- old/futurist-3.0.0/futurist/_utils.py       2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/futurist/_utils.py       2025-08-29 17:05:26.000000000 
+0200
@@ -29,7 +29,7 @@
     EVENTLET_AVAILABLE = False
 
 
-class WorkItem(object):
+class WorkItem:
     """A thing to be executed by a executor."""
 
     def __init__(self, future, fn, args, kwargs):
@@ -62,7 +62,7 @@
                 del exc_type, exc_value, exc_tb
 
 
-class Failure(object):
+class Failure:
     """Object that captures a exception (and its associated information)."""
 
     def __init__(self, retain_tb):
@@ -139,7 +139,7 @@
         return default
 
 
-class Barrier(object):
+class Barrier:
     """A class that ensures active <= 0 occur before unblocking."""
 
     def __init__(self, cond_cls=threading.Condition):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/periodics.py 
new/futurist-3.2.1/futurist/periodics.py
--- old/futurist-3.0.0/futurist/periodics.py    2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/futurist/periodics.py    2025-08-29 17:05:26.000000000 
+0200
@@ -64,7 +64,7 @@
         return self.callback(*self.args, **self.kwargs)
 
 
-class Watcher(object):
+class Watcher:
     """A **read-only** object representing a periodic callback's activities."""
 
     def __init__(self, metrics, work):
@@ -238,7 +238,7 @@
     return how_often + now
 
 
-class _Schedule(object):
+class _Schedule:
     """Internal heap-based structure that maintains the schedule/ordering.
 
     This stores a heap composed of the following ``(next_run, index)`` where
@@ -278,7 +278,7 @@
                   " seconds):\n%s", kind, cb_name, spacing, traceback)
 
 
-class _Runner(object):
+class _Runner:
     def __init__(self, now_func, retain_traceback=True):
         self.now_func = now_func
         self.retain_traceback = retain_traceback
@@ -316,7 +316,7 @@
 _SCHEDULE_RETRY_EXCEPTIONS = (RuntimeError, futurist.RejectedSubmission)
 
 
-class ExecutorFactory(object):
+class ExecutorFactory:
     """Base class for any executor factory."""
 
     shutdown = True
@@ -338,7 +338,7 @@
         return self._executor
 
 
-class PeriodicWorker(object):
+class PeriodicWorker:
     """Calls a collection of callables periodically (sleeping as needed...).
 
     NOTE(harlowja): typically the :py:meth:`.start` method is executed in a
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/tests/test_executors.py 
new/futurist-3.2.1/futurist/tests/test_executors.py
--- old/futurist-3.0.0/futurist/tests/test_executors.py 2024-02-23 
13:19:46.000000000 +0100
+++ new/futurist-3.2.1/futurist/tests/test_executors.py 2025-08-29 
17:05:26.000000000 +0200
@@ -10,12 +10,14 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+from concurrent import futures
 import threading
 import time
+import unittest
+from unittest import mock
 
 from eventlet.green import threading as green_threading
 import testscenarios
-from testtools import testcase
 
 import futurist
 from futurist import rejection
@@ -38,6 +40,11 @@
     time.sleep(wait_secs)
 
 
+def delayed_with_result(task_id):
+    time.sleep(0.1)
+    return task_id
+
+
 class TestExecutors(testscenarios.TestWithScenarios, base.TestCase):
     scenarios = [
         ('sync', {'executor_cls': futurist.SynchronousExecutor,
@@ -49,16 +56,18 @@
                    'restartable': False, 'executor_kwargs': {}}),
         ('thread', {'executor_cls': futurist.ThreadPoolExecutor,
                     'restartable': False, 'executor_kwargs': {}}),
+        ('thread_dyn', {'executor_cls': futurist.DynamicThreadPoolExecutor,
+                        'restartable': False, 'executor_kwargs': {}}),
         ('process', {'executor_cls': futurist.ProcessPoolExecutor,
                      'restartable': False, 'executor_kwargs': {}}),
     ]
 
     def setUp(self):
-        super(TestExecutors, self).setUp()
+        super().setUp()
         self.executor = self.executor_cls(**self.executor_kwargs)
 
     def tearDown(self):
-        super(TestExecutors, self).tearDown()
+        super().tearDown()
         self.executor.shutdown()
         self.executor = None
 
@@ -93,9 +102,26 @@
         executor.shutdown()
         self.assertRaises(RuntimeError, executor.submit, returns_one)
 
+    def test_shutdown_waits_for_all_tasks(self):
+        num_tasks = 3
+        futures = []
+        for i in range(num_tasks):
+            future = self.executor.submit(delayed_with_result, i)
+            futures.append(future)
+
+        self.executor.shutdown(wait=True)
+
+        results = []
+        for future in futures:
+            self.assertTrue(future.done())
+            results.append(future.result())
+
+        self.assertEqual(len(results), num_tasks)
+        self.assertEqual(set(results), set(range(num_tasks)))
+
     def test_restartable(self):
         if not self.restartable:
-            raise testcase.TestSkipped("not restartable")
+            raise unittest.SkipTest("not restartable")
         else:
             executor = self.executor_cls(**self.executor_kwargs)
             fut = executor.submit(returns_one)
@@ -155,7 +181,7 @@
     ]
 
     def setUp(self):
-        super(TestRejection, self).setUp()
+        super().setUp()
         self.executor = self.executor_cls(**self.executor_kwargs)
         self.addCleanup(self.executor.shutdown, wait=True)
 
@@ -178,3 +204,151 @@
 
         self.assertRaises(futurist.RejectedSubmission,
                           self.executor.submit, returns_one)
+
+
[email protected](futurist.DynamicThreadPoolExecutor, '_add_thread',
+                   # Use the original function behind the scene
+                   side_effect=futurist.DynamicThreadPoolExecutor._add_thread,
+                   autospec=True)
+class TestDynamicThreadPool(base.TestCase):
+
+    def _new(self, *args, **kwargs):
+        executor = futurist.DynamicThreadPoolExecutor(*args, **kwargs)
+        self.addCleanup(executor.shutdown, wait=True)
+        self.assertEqual(0, executor.queue_size)
+        self.assertEqual(0, executor.num_workers)
+        self.assertEqual(0, executor.get_num_idle_workers())
+        self.assertEqual(0, len(executor._dead_workers))
+        return executor
+
+    def test_stays_at_min_worker(self, mock_add_thread):
+        """Executing tasks sequentially: no growth beyond 1 thread."""
+        executor = self._new(max_workers=3)
+        for _i in range(10):
+            executor.submit(lambda: None).result()
+        self.assertEqual(0, executor.queue_size)
+        self.assertEqual(1, executor.num_workers)
+        self.assertEqual(1, executor.get_num_idle_workers())
+        self.assertEqual(0, len(executor._dead_workers))
+        self.assertEqual(1, mock_add_thread.call_count)
+
+    def test_grow_and_shrink(self, mock_add_thread):
+        """Executing tasks in parallel: grows and shrinks."""
+        executor = self._new(max_workers=10)
+        started = threading.Barrier(11)
+        done = threading.Event()
+        tasks = []
+
+        self.addCleanup(started.abort)
+        self.addCleanup(done.set)
+
+        def task():
+            started.wait()
+            done.wait()
+
+        for _i in range(10):
+            tasks.append(executor.submit(task))
+
+        started.wait()  # this ensures that all threads have been started
+        self.assertEqual(0, executor.queue_size)
+        self.assertEqual(10, executor.num_workers)
+        self.assertEqual(0, executor.get_num_idle_workers())
+        self.assertEqual(0, len(executor._dead_workers))
+        self.assertEqual(10, mock_add_thread.call_count)
+
+        done.set()  # this allows all threads to stop
+        futures.wait(tasks)
+        executor.maintain()
+        self.assertEqual(0, executor.queue_size)
+        self.assertEqual(1, executor.num_workers)
+        self.assertEqual(1, executor.get_num_idle_workers())
+        self.assertEqual(0, len(executor._dead_workers))
+
+    def test_shutdown_waits_for_queued_tasks(self, mock_add_thread):
+        results = []
+        results_lock = threading.Lock()
+
+        def slow_task(task_id):
+            time.sleep(0.1)
+            with results_lock:
+                results.append(task_id)
+
+        num_tasks = 5
+        executor = self._new(max_workers=2, min_workers=1)
+        for i in range(num_tasks):
+            executor.submit(slow_task, i)
+
+        executor.shutdown(wait=True)
+
+        self.assertEqual(len(results), num_tasks)
+        self.assertEqual(set(results), set(range(num_tasks)))
+
+
[email protected]('futurist._thread.ThreadWorker.create_and_register', autospec=True)
+class TestDynamicThreadPoolMaintain(base.TestCase):
+    def test_ensure_one_worker(self, mock_create_thread):
+        executor = futurist.DynamicThreadPoolExecutor()
+        executor.maintain()
+        self.assertEqual(1, len(executor._workers))
+        created_worker = mock_create_thread.return_value
+        created_worker.start.assert_called_once_with()
+        created_worker.stop.assert_not_called()
+
+    def test_ensure_min_workers(self, mock_create_thread):
+        executor = futurist.DynamicThreadPoolExecutor(min_workers=42)
+        executor.maintain()
+        self.assertEqual(42, len(executor._workers))
+        created_worker = mock_create_thread.return_value
+        created_worker.start.assert_called_with()
+        self.assertEqual(42, created_worker.start.call_count)
+        created_worker.stop.assert_not_called()
+
+    def test_too_many_idle_workers(self, mock_create_thread):
+        executor = futurist.DynamicThreadPoolExecutor(min_workers=42)
+        executor._workers = [mock.Mock(idle=True)] * 100
+        executor.maintain()
+        self.assertEqual(42, len(executor._workers))
+        mock_create_thread.return_value.start.assert_not_called()
+        self.assertEqual(58, executor._workers[0].stop.call_count)
+
+    def test_all_busy_workers(self, mock_create_thread):
+        executor = futurist.DynamicThreadPoolExecutor(max_workers=100)
+        executor._workers = [mock.Mock(idle=False)] * 100
+        executor.maintain()
+        self.assertEqual(100, len(executor._workers))
+        mock_create_thread.return_value.start.assert_not_called()
+        executor._workers[0].stop.assert_not_called()
+
+    def test_busy_workers_create_more(self, mock_create_thread):
+        executor = futurist.DynamicThreadPoolExecutor(max_workers=200)
+        executor._workers = [mock.Mock(idle=False)] * 100
+        executor.maintain()
+        # NOTE(dtantsur): once the executor reaches 125 threads, the ratio of
+        # busy to total threads is exactly 100/125=0.8 (the default
+        # grow_threshold). One more thread is created, resulting in 126.
+        self.assertEqual(126, len(executor._workers))
+        self.assertEqual(26, executor.get_num_idle_workers())
+        created_worker = mock_create_thread.return_value
+        created_worker.start.assert_called_with()
+        self.assertEqual(26, created_worker.start.call_count)
+        created_worker.stop.assert_not_called()
+
+    def test_busy_workers_within_range(self, mock_create_thread):
+        executor = futurist.DynamicThreadPoolExecutor()
+        executor._workers = [mock.Mock(idle=i < 30) for i in range(100)]
+        executor.maintain()
+        self.assertEqual(100, len(executor._workers))
+        mock_create_thread.return_value.start.assert_not_called()
+
+    def test_busy_workers_and_large_queue(self, mock_create_thread):
+        executor = futurist.DynamicThreadPoolExecutor(max_workers=200)
+        executor._workers = [mock.Mock(idle=i < 30) for i in range(100)]
+        for i in range(20):
+            executor._work_queue.put(None)
+        executor.maintain()
+        # NOTE(dtantsur): initial busy ratio is (70+20)/100=0.9. As workers
+        # are added, it reaches (70+20)/113, which is just below 0.8.
+        self.assertEqual(113, len(executor._workers))
+        created_worker = mock_create_thread.return_value
+        created_worker.start.assert_called_with()
+        self.assertEqual(13, created_worker.start.call_count)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/tests/test_periodics.py 
new/futurist-3.2.1/futurist/tests/test_periodics.py
--- old/futurist-3.0.0/futurist/tests/test_periodics.py 2024-02-23 
13:19:46.000000000 +0100
+++ new/futurist-3.2.1/futurist/tests/test_periodics.py 2025-08-29 
17:05:26.000000000 +0200
@@ -428,7 +428,7 @@
     def test_create_with_arguments(self):
         m = mock.Mock()
 
-        class Object(object):
+        class Object:
             @periodics.periodic(0.5)
             def func1(self, *args, **kwargs):
                 m(*args, **kwargs)
@@ -533,7 +533,7 @@
 
     def __init__(self):
         self._rejections_count = 0
-        super(RejectingExecutor, self).__init__(check_and_reject=self._reject)
+        super().__init__(check_and_reject=self._reject)
 
 
 class TestPformat(base.TestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/tests/test_waiters.py 
new/futurist-3.2.1/futurist/tests/test_waiters.py
--- old/futurist-3.0.0/futurist/tests/test_waiters.py   2024-02-23 
13:19:46.000000000 +0100
+++ new/futurist-3.2.1/futurist/tests/test_waiters.py   2025-08-29 
17:05:26.000000000 +0200
@@ -48,11 +48,11 @@
     ]
 
     def setUp(self):
-        super(TestWaiters, self).setUp()
+        super().setUp()
         self.executor = self.executor_cls(**self.executor_kwargs)
 
     def tearDown(self):
-        super(TestWaiters, self).tearDown()
+        super().tearDown()
         self.executor.shutdown()
         self.executor = None
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist/waiters.py 
new/futurist-3.2.1/futurist/waiters.py
--- old/futurist-3.0.0/futurist/waiters.py      2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/futurist/waiters.py      2025-08-29 17:05:26.000000000 
+0200
@@ -12,11 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-try:
-    from contextlib import ExitStack
-except ImportError:
-    from contextlib2 import ExitStack
-
 import collections
 import contextlib
 import functools
@@ -49,7 +44,7 @@
     # always acquire the conditions in the same order, no matter what; a way
     # to avoid dead-lock).
     fs = sorted(fs, key=id)
-    with ExitStack() as stack:
+    with contextlib.ExitStack() as stack:
         for fut in fs:
             stack.enter_context(fut._condition)
         yield
@@ -113,7 +108,7 @@
                      'wait_for_any', timeout=timeout)
 
 
-class _AllGreenWaiter(object):
+class _AllGreenWaiter:
     """Provides the event that ``_wait_for_all_green`` blocks on."""
 
     def __init__(self, pending):
@@ -137,7 +132,7 @@
         self._decrement_pending()
 
 
-class _AnyGreenWaiter(object):
+class _AnyGreenWaiter:
     """Provides the event that ``_wait_for_any_green`` blocks on."""
 
     def __init__(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist.egg-info/PKG-INFO 
new/futurist-3.2.1/futurist.egg-info/PKG-INFO
--- old/futurist-3.0.0/futurist.egg-info/PKG-INFO       2024-02-23 
13:20:17.000000000 +0100
+++ new/futurist-3.2.1/futurist.egg-info/PKG-INFO       2025-08-29 
17:06:00.000000000 +0200
@@ -1,47 +1,10 @@
-Metadata-Version: 1.2
+Metadata-Version: 2.1
 Name: futurist
-Version: 3.0.0
+Version: 3.2.1
 Summary: Useful additions to futures, from the future.
 Home-page: https://docs.openstack.org/futurist/latest/
 Author: OpenStack
 Author-email: [email protected]
-License: UNKNOWN
-Description: ========================
-        Team and repository tags
-        ========================
-        
-        .. image:: https://governance.openstack.org/tc/badges/futurist.svg
-            :target: 
https://governance.openstack.org/tc/reference/tags/index.html
-        
-        .. Change things from this point on
-        
-        ========
-        Futurist
-        ========
-        
-        .. image:: https://img.shields.io/pypi/v/futurist.svg
-            :target: https://pypi.org/project/futurist/
-            :alt: Latest Version
-        
-        .. image:: https://img.shields.io/pypi/dm/futurist.svg
-            :target: https://pypi.org/project/futurist/
-            :alt: Downloads
-        
-        Code from the future, delivered to you in the **now**. The goal of 
this library
-        would be to provide a well documented futures 
classes/utilities/additions that
-        allows for providing a level of transparency in how asynchronous work 
gets
-        executed. This library currently adds statistics gathering, an eventlet
-        executor, a synchronous executor etc.
-        
-        * Free software: Apache license
-        * Documentation: https://docs.openstack.org/futurist/latest/
-        * Source: https://opendev.org/openstack/futurist
-        * Bugs: https://bugs.launchpad.net/futurist
-        * Blueprints: https://blueprints.launchpad.net/futurist
-        * Release notes: https://docs.openstack.org/releasenotes/futurist
-        
-        
-Platform: UNKNOWN
 Classifier: Environment :: OpenStack
 Classifier: Intended Audience :: Information Technology
 Classifier: Intended Audience :: System Administrators
@@ -49,10 +12,47 @@
 Classifier: Operating System :: POSIX :: Linux
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
-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 :: 3.12
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: Implementation :: CPython
-Requires-Python: >=3.8
+Requires-Python: >=3.9
+License-File: LICENSE
+Requires-Dist: debtcollector>=3.0.0
+
+========================
+Team and repository tags
+========================
+
+.. image:: https://governance.openstack.org/tc/badges/futurist.svg
+    :target: https://governance.openstack.org/tc/reference/tags/index.html
+
+.. Change things from this point on
+
+========
+Futurist
+========
+
+.. image:: https://img.shields.io/pypi/v/futurist.svg
+    :target: https://pypi.org/project/futurist/
+    :alt: Latest Version
+
+.. image:: https://img.shields.io/pypi/dm/futurist.svg
+    :target: https://pypi.org/project/futurist/
+    :alt: Downloads
+
+Code from the future, delivered to you in the **now**. The goal of this library
+would be to provide a well documented futures classes/utilities/additions that
+allows for providing a level of transparency in how asynchronous work gets
+executed. This library currently adds statistics gathering, an eventlet
+executor, a synchronous executor etc.
+
+* Free software: Apache license
+* Documentation: https://docs.openstack.org/futurist/latest/
+* Source: https://opendev.org/openstack/futurist
+* Bugs: https://bugs.launchpad.net/futurist
+* Blueprints: https://blueprints.launchpad.net/futurist
+* Release notes: https://docs.openstack.org/releasenotes/futurist
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist.egg-info/SOURCES.txt 
new/futurist-3.2.1/futurist.egg-info/SOURCES.txt
--- old/futurist-3.0.0/futurist.egg-info/SOURCES.txt    2024-02-23 
13:20:17.000000000 +0100
+++ new/futurist-3.2.1/futurist.egg-info/SOURCES.txt    2025-08-29 
17:06:00.000000000 +0200
@@ -10,6 +10,7 @@
 LICENSE
 MANIFEST.in
 README.rst
+pyproject.toml
 requirements.txt
 setup.cfg
 setup.py
@@ -38,6 +39,7 @@
 futurist.egg-info/dependency_links.txt
 futurist.egg-info/not-zip-safe
 futurist.egg-info/pbr.json
+futurist.egg-info/requires.txt
 futurist.egg-info/top_level.txt
 futurist/tests/__init__.py
 futurist/tests/base.py
@@ -45,9 +47,13 @@
 futurist/tests/test_periodics.py
 futurist/tests/test_waiters.py
 releasenotes/notes/add-reno-996dd44974d53238.yaml
+releasenotes/notes/deprecate-eventlet-d96deb8b97930fef.yaml
 releasenotes/notes/drop-python27-support-5757997ea990b7ca.yaml
+releasenotes/notes/dynamic-pool-ae8811eecd5f9009.yaml
 
releasenotes/notes/improve-get-optimal-count-of-max_workers-for-pool-89368859d3b819e0.yaml
 releasenotes/notes/prettytable-optional-2c9198a9250427b6.yaml
+releasenotes/notes/remove-py38-de797cad7e34f7be.yaml
+releasenotes/notes/shutdown-wait-for-queued-tasks-81a6b584f69ac62f.yaml
 releasenotes/source/conf.py
 releasenotes/source/index.rst
 releasenotes/source/ocata.rst
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist.egg-info/pbr.json 
new/futurist-3.2.1/futurist.egg-info/pbr.json
--- old/futurist-3.0.0/futurist.egg-info/pbr.json       2024-02-23 
13:20:17.000000000 +0100
+++ new/futurist-3.2.1/futurist.egg-info/pbr.json       2025-08-29 
17:06:00.000000000 +0200
@@ -1 +1 @@
-{"git_version": "4e14db5", "is_release": true}
\ No newline at end of file
+{"git_version": "388ec84", "is_release": true}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/futurist.egg-info/requires.txt 
new/futurist-3.2.1/futurist.egg-info/requires.txt
--- old/futurist-3.0.0/futurist.egg-info/requires.txt   1970-01-01 
01:00:00.000000000 +0100
+++ new/futurist-3.2.1/futurist.egg-info/requires.txt   2025-08-29 
17:06:00.000000000 +0200
@@ -0,0 +1 @@
+debtcollector>=3.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/pyproject.toml 
new/futurist-3.2.1/pyproject.toml
--- old/futurist-3.0.0/pyproject.toml   1970-01-01 01:00:00.000000000 +0100
+++ new/futurist-3.2.1/pyproject.toml   2025-08-29 17:05:26.000000000 +0200
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["pbr>=6.1.1"]
+build-backend = "pbr.build"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/futurist-3.0.0/releasenotes/notes/deprecate-eventlet-d96deb8b97930fef.yaml 
new/futurist-3.2.1/releasenotes/notes/deprecate-eventlet-d96deb8b97930fef.yaml
--- 
old/futurist-3.0.0/releasenotes/notes/deprecate-eventlet-d96deb8b97930fef.yaml  
    1970-01-01 01:00:00.000000000 +0100
+++ 
new/futurist-3.2.1/releasenotes/notes/deprecate-eventlet-d96deb8b97930fef.yaml  
    2025-08-29 17:05:26.000000000 +0200
@@ -0,0 +1,9 @@
+---
+deprecations:
+  - |
+    Eventlet usages are deprecated and the removal of Eventlet from
+    OpenStack `is planned
+    
<https://governance.openstack.org/tc//goals/proposed/remove-eventlet.html>`_,
+    for this reason the executors based on Eventlet are deprecated.
+    Start migrating your stack to the threading executor.
+    Please also start considering removing your internal Eventlet usages.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/futurist-3.0.0/releasenotes/notes/dynamic-pool-ae8811eecd5f9009.yaml 
new/futurist-3.2.1/releasenotes/notes/dynamic-pool-ae8811eecd5f9009.yaml
--- old/futurist-3.0.0/releasenotes/notes/dynamic-pool-ae8811eecd5f9009.yaml    
1970-01-01 01:00:00.000000000 +0100
+++ new/futurist-3.2.1/releasenotes/notes/dynamic-pool-ae8811eecd5f9009.yaml    
2025-08-29 17:05:26.000000000 +0200
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Adds a new ``DynamicThreadPoolExecutor`` that can resize (grow or shrink)
+    its pool based on demand. As new work is scheduled on the executor, it will
+    try to keep the proportion of busy threads within the provided range
+    (between 40% and 80% by default).
+
+    The motivation is to provide a scalable alternative to
+    ``GreenThreadPoolExecutor``.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/futurist-3.0.0/releasenotes/notes/remove-py38-de797cad7e34f7be.yaml 
new/futurist-3.2.1/releasenotes/notes/remove-py38-de797cad7e34f7be.yaml
--- old/futurist-3.0.0/releasenotes/notes/remove-py38-de797cad7e34f7be.yaml     
1970-01-01 01:00:00.000000000 +0100
+++ new/futurist-3.2.1/releasenotes/notes/remove-py38-de797cad7e34f7be.yaml     
2025-08-29 17:05:26.000000000 +0200
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - |
+    Support for Python 3.8 has been removed. Now the minimum python version
+    supported is 3.9 .
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/futurist-3.0.0/releasenotes/notes/shutdown-wait-for-queued-tasks-81a6b584f69ac62f.yaml
 
new/futurist-3.2.1/releasenotes/notes/shutdown-wait-for-queued-tasks-81a6b584f69ac62f.yaml
--- 
old/futurist-3.0.0/releasenotes/notes/shutdown-wait-for-queued-tasks-81a6b584f69ac62f.yaml
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/futurist-3.2.1/releasenotes/notes/shutdown-wait-for-queued-tasks-81a6b584f69ac62f.yaml
  2025-08-29 17:05:26.000000000 +0200
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    ``shutdown(wait=True)`` abandoned queued work.
+    This fix ensures it blocks until all submitted tasks (running and queued)
+    finishes, consistent with Python's concurrent.futures.ThreadPoolExecutor
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/releasenotes/source/victoria.rst 
new/futurist-3.2.1/releasenotes/source/victoria.rst
--- old/futurist-3.0.0/releasenotes/source/victoria.rst 2024-02-23 
13:19:46.000000000 +0100
+++ new/futurist-3.2.1/releasenotes/source/victoria.rst 2025-08-29 
17:05:26.000000000 +0200
@@ -3,4 +3,4 @@
 =============================
 
 .. release-notes::
-   :branch: stable/victoria
+   :branch: unmaintained/victoria
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/requirements.txt 
new/futurist-3.2.1/requirements.txt
--- old/futurist-3.0.0/requirements.txt 2024-02-23 13:19:46.000000000 +0100
+++ new/futurist-3.2.1/requirements.txt 2025-08-29 17:05:26.000000000 +0200
@@ -0,0 +1 @@
+debtcollector>=3.0.0 # Apache-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/setup.cfg new/futurist-3.2.1/setup.cfg
--- old/futurist-3.0.0/setup.cfg        2024-02-23 13:20:17.199892000 +0100
+++ new/futurist-3.2.1/setup.cfg        2025-08-29 17:06:00.858912000 +0200
@@ -6,7 +6,7 @@
 author = OpenStack
 author_email = [email protected]
 home_page = https://docs.openstack.org/futurist/latest/
-python_requires = >=3.8
+python_requires = >=3.9
 classifier = 
        Environment :: OpenStack
        Intended Audience :: Information Technology
@@ -15,10 +15,10 @@
        Operating System :: POSIX :: Linux
        Programming Language :: Python
        Programming Language :: Python :: 3
-       Programming Language :: Python :: 3.8
        Programming Language :: Python :: 3.9
        Programming Language :: Python :: 3.10
        Programming Language :: Python :: 3.11
+       Programming Language :: Python :: 3.12
        Programming Language :: Python :: 3 :: Only
        Programming Language :: Python :: Implementation :: CPython
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/test-requirements.txt 
new/futurist-3.2.1/test-requirements.txt
--- old/futurist-3.0.0/test-requirements.txt    2024-02-23 13:19:46.000000000 
+0100
+++ new/futurist-3.2.1/test-requirements.txt    2025-08-29 17:05:26.000000000 
+0200
@@ -1,13 +1,9 @@
 # Used for making sure the eventlet executors work.
-eventlet!=0.18.3,!=0.20.1,>=0.18.2 # MIT
+eventlet>=0.18.2 # MIT
 
-doc8>=0.6.0 # Apache-2.0
-coverage!=4.4,>=4.0 # Apache-2.0
-python-subunit>=1.0.0 # Apache-2.0/BSD
+coverage>=4.0 # Apache-2.0
 oslotest>=3.2.0 # Apache-2.0
 stestr>=2.0.0 # Apache-2.0
 testscenarios>=0.4 # Apache-2.0/BSD
 testtools>=2.2.0 # MIT
 PrettyTable>=0.7.1 # BSD
-
-pre-commit>=2.6.0 # MIT
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/futurist-3.0.0/tox.ini new/futurist-3.2.1/tox.ini
--- old/futurist-3.0.0/tox.ini  2024-02-23 13:19:46.000000000 +0100
+++ new/futurist-3.2.1/tox.ini  2025-08-29 17:05:26.000000000 +0200
@@ -1,10 +1,8 @@
 [tox]
 minversion = 3.18.0
 envlist = py3,pep8
-ignore_basepython_conflict = true
 
 [testenv]
-basepython = python3
 deps =
     
-c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
     -r{toxinidir}/requirements.txt
@@ -15,9 +13,10 @@
     stestr run --slowest {posargs}
 
 [testenv:pep8]
+deps =
+  pre-commit>=2.6.0 # MIT
 commands =
-    pre-commit run -a
-    doc8 doc/source
+  pre-commit run -a
 
 [testenv:venv]
 commands = {posargs}

Reply via email to