Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-threadpoolctl for 
openSUSE:Factory checked in at 2023-12-09 22:49:32
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-threadpoolctl (Old)
 and      /work/SRC/openSUSE:Factory/.python-threadpoolctl.new.25432 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-threadpoolctl"

Sat Dec  9 22:49:32 2023 rev:8 rq:1132143 version:3.2.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-threadpoolctl/python-threadpoolctl.changes    
    2023-06-12 15:24:44.714446355 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-threadpoolctl.new.25432/python-threadpoolctl.changes
     2023-12-09 22:49:56.892354323 +0100
@@ -1,0 +2,10 @@
+Fri Dec  8 16:25:46 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 3.2.0:
+  * Dropped support for Python 3.6 and 3.7.
+  * Added support for custom library controllers. Custom
+    controllers must inherit from the `threadpoolctl.LibController`
+    class and be registered to threadpoolctl using the
+    `threadpoolctl.register` function.
+
+-------------------------------------------------------------------

Old:
----
  threadpoolctl-3.1.0.tar.gz

New:
----
  threadpoolctl-3.2.0.tar.gz

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

Other differences:
------------------
++++++ python-threadpoolctl.spec ++++++
--- /var/tmp/diff_new_pack.glUx5z/_old  2023-12-09 22:49:57.524377141 +0100
+++ /var/tmp/diff_new_pack.glUx5z/_new  2023-12-09 22:49:57.524377141 +0100
@@ -15,16 +15,17 @@
 # Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
+
 %{?sle15_python_module_pythons}
 Name:           python-threadpoolctl
-Version:        3.1.0
+Version:        3.2.0
 Release:        0
 Summary:        Thread-pool Controls
 License:        BSD-3-Clause
 Group:          Development/Languages/Python
 URL:            https://github.com/joblib/threadpoolctl
 Source:         
https://files.pythonhosted.org/packages/source/t/threadpoolctl/threadpoolctl-%{version}.tar.gz
-BuildRequires:  %{python_module devel}
+BuildRequires:  %{python_module devel >= 3.8}
 BuildRequires:  %{python_module flit-core}
 BuildRequires:  %{python_module numpy}
 BuildRequires:  %{python_module pip}

++++++ threadpoolctl-3.1.0.tar.gz -> threadpoolctl-3.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/.azure_pipeline.yml 
new/threadpoolctl-3.2.0/.azure_pipeline.yml
--- old/threadpoolctl-3.1.0/.azure_pipeline.yml 2022-01-31 16:08:24.277708800 
+0100
+++ new/threadpoolctl-3.2.0/.azure_pipeline.yml 2023-07-13 16:19:36.077411700 
+0200
@@ -18,6 +18,21 @@
 - stage:
   jobs:
 
+  - job: 'linting'
+    displayName: Linting
+    pool:
+      vmImage: ubuntu-latest
+    steps:
+      - task: UsePythonVersion@0
+        inputs:
+          versionSpec: '3.11'
+      - script: |
+          pip install black
+        displayName: install black
+      - script: |
+          black --check --diff .
+        displayName: Run black
+
   - template: continuous_integration/windows.yml
     parameters:
       name: Windows
@@ -27,15 +42,15 @@
           VERSION_PYTHON: '*'
           PACKAGER: 'conda-forge'
           BLAS: 'mkl'
-        py39_conda_forge_openblas:
-          VERSION_PYTHON: '3.9'
+        py310_conda_forge_openblas:
+          VERSION_PYTHON: '3.10'
           PACKAGER: 'conda-forge'
           BLAS: 'openblas'
-        py37_conda:
-          VERSION_PYTHON: '3.7'
+        py39_conda:
+          VERSION_PYTHON: '3.9'
           PACKAGER: 'conda'
-        py36_pip:
-          VERSION_PYTHON: '3.6'
+        py38_pip:
+          VERSION_PYTHON: '3.8'
           PACKAGER: 'pip'
 
 
@@ -52,16 +67,16 @@
           VERSION_PYTHON: '3.8'
           CC_OUTER_LOOP: 'gcc'
           CC_INNER_LOOP: 'gcc'
-        py36_ubuntu_openblas_gcc_gcc:
+        py38_ubuntu_openblas_gcc_gcc:
           PACKAGER: 'ubuntu'
           APT_BLAS: 'libopenblas-base libopenblas-dev'
           VERSION_PYTHON: '3.8'
           CC_OUTER_LOOP: 'gcc'
           CC_INNER_LOOP: 'gcc'
-        # Linux + Python 3.7 and homogeneous runtime nesting.
-        py37_conda_openblas_clang_clang:
+        # Linux + Python 3.9 and homogeneous runtime nesting.
+        py39_conda_openblas_clang_clang:
           PACKAGER: 'conda'
-          VERSION_PYTHON: '3.7'
+          VERSION_PYTHON: '3.9'
           BLAS: 'openblas'
           CC_OUTER_LOOP: 'clang-10'
           CC_INNER_LOOP: 'clang-10'
@@ -97,7 +112,6 @@
           OPENBLAS_THREADING_LAYER: 'openmp'
           CC_OUTER_LOOP: 'gcc'
           CC_INNER_LOOP: 'gcc'
-          LINT: 'true'
         # Linux environment with no numpy and heterogeneous OpenMP runtimes.
         pylatest_conda_nonumpy_gcc_clang:
           PACKAGER: 'conda'
@@ -137,16 +151,24 @@
   - template: continuous_integration/posix.yml
     parameters:
       name: macOS
-      vmImage: macOS-10.15
+      vmImage: macOS-11
       matrix:
         # MacOS environment with OpenMP installed through homebrew
-        py36_conda_homebrew_libomp:
+        py38_conda_homebrew_libomp:
           PACKAGER: 'conda'
-          VERSION_PYTHON: '3.6'
+          VERSION_PYTHON: '3.8'
           BLAS: 'openblas'
           CC_OUTER_LOOP: 'clang'
           CC_INNER_LOOP: 'clang'
           INSTALL_LIBOMP: 'homebrew'
+        # MacOS env with OpenBLAS and OpenMP installed through conda-forge 
compilers
+        py39_conda_forge_clang_openblas:
+          PACKAGER: 'conda-forge'
+          VERSION_PYTHON: '*'
+          BLAS: 'openblas'
+          CC_OUTER_LOOP: 'clang'
+          CC_INNER_LOOP: 'clang'
+          INSTALL_LIBOMP: 'conda-forge'
         # MacOS environment with OpenMP installed through conda-forge compilers
         pylatest_conda_forge_clang:
           PACKAGER: 'conda-forge'
@@ -164,7 +186,7 @@
   - job: 'no_test_always_skipped'
     displayName: 'No test always skipped'
     pool:
-      vmImage: ubuntu-20.04
+      vmImage: ubuntu-latest
     steps:
       - download: current
       - script: |
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/.gitignore 
new/threadpoolctl-3.2.0/.gitignore
--- old/threadpoolctl-3.1.0/.gitignore  2019-09-16 14:06:34.688814200 +0200
+++ new/threadpoolctl-3.2.0/.gitignore  2023-07-12 10:17:49.739681000 +0200
@@ -1,11 +1,14 @@
-# Python and Cython generated files
+# Python generated files
 *.pyc
 __pycache__
 .cache
 .pytest_cache
+
+# Cython/C generated files
+*.o
 *.so
 *.dylib
-*.c
+tests/_openmp_test_helper/*.c
 
 # Python install files, build and release artifacts
 *.egg-info/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/CHANGES.md 
new/threadpoolctl-3.2.0/CHANGES.md
--- old/threadpoolctl-3.1.0/CHANGES.md  2022-01-31 17:25:14.640525300 +0100
+++ new/threadpoolctl-3.2.0/CHANGES.md  2023-07-13 16:44:13.625080800 +0200
@@ -1,3 +1,18 @@
+3.2.0 (2023-07-13)
+==================
+
+- Dropped support for Python 3.6 and 3.7.
+
+- Added support for custom library controllers. Custom controllers must 
inherit from
+  the `threadpoolctl.LibController` class and be registered to threadpoolctl 
using the
+  `threadpoolctl.register` function.
+  https://github.com/joblib/threadpoolctl/pull/138
+
+- A warning is raised on macOS when threadpoolctl finds both Intel OpenMP and 
LLVM
+  OpenMP runtimes loaded simultaneously by the same Python program. See 
details and
+  workarounds at 
https://github.com/joblib/threadpoolctl/blob/master/multiple_openmp.md.
+  https://github.com/joblib/threadpoolctl/pull/142
+
 3.1.0 (2022-01-31)
 ==================
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/PKG-INFO 
new/threadpoolctl-3.2.0/PKG-INFO
--- old/threadpoolctl-3.1.0/PKG-INFO    1970-01-01 01:00:00.000000000 +0100
+++ new/threadpoolctl-3.2.0/PKG-INFO    1970-01-01 01:00:00.000000000 +0100
@@ -1,20 +1,20 @@
 Metadata-Version: 2.1
 Name: threadpoolctl
-Version: 3.1.0
+Version: 3.2.0
 Summary: threadpoolctl
 Home-page: https://github.com/joblib/threadpoolctl
 License: BSD-3-Clause
 Author: Thomas Moreau
 Author-email: thomas.moreau.2...@gmail.com
-Requires-Python: >=3.6
+Requires-Python: >=3.8
 Description-Content-Type: text/markdown
 Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: BSD License
 Classifier: Programming Language :: Python :: 3
-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: Topic :: Software Development :: Libraries :: Python Modules
 
 # Thread-pool Controls [![Build 
Status](https://dev.azure.com/joblib/threadpoolctl/_apis/build/status/joblib.threadpoolctl?branchName=master)](https://dev.azure.com/joblib/threadpoolctl/_build/latest?definitionId=1&branchName=master)
 
[![codecov](https://codecov.io/gh/joblib/threadpoolctl/branch/master/graph/badge.svg)](https://codecov.io/gh/joblib/threadpoolctl)
@@ -211,10 +211,25 @@
 ...
 ```
 
+### Writing a custom library controller
+
+Currently, `threadpoolctl` has support for `OpenMP` and the main `BLAS` 
libraries.
+However it can also be used to control the threadpool of other native 
libraries,
+provided that they expose an API to get and set the limit on the number of 
threads.
+For that, one must implement a controller for this library and register it to
+`threadpoolctl`.
+
+A custom controller must be a subclass of the `LibController` class and 
implement
+the attributes and methods described in the docstring of `LibController`. Then 
this
+new controller class must be registered using the `threadpoolctl.register` 
function.
+An complete example can be found [here](
+  
https://github.com/joblib/threadpoolctl/blob/master/tests/_pyMylib/__init__.py).
+
 ### Sequential BLAS within OpenMP parallel region
 
 When one wants to have sequential BLAS calls within an OpenMP parallel region, 
it's
-safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` 
and `user_api="blas"` might not lead to the expected behavior in some 
configurations
+safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` 
and
+`user_api="blas"` might not lead to the expected behavior in some 
configurations
 (e.g. OpenBLAS with the OpenMP threading layer
 https://github.com/xianyi/OpenBLAS/issues/2985).
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/README.md 
new/threadpoolctl-3.2.0/README.md
--- old/threadpoolctl-3.1.0/README.md   2022-01-31 16:08:24.277708800 +0100
+++ new/threadpoolctl-3.2.0/README.md   2023-07-12 10:17:49.739681000 +0200
@@ -192,10 +192,25 @@
 ...
 ```
 
+### Writing a custom library controller
+
+Currently, `threadpoolctl` has support for `OpenMP` and the main `BLAS` 
libraries.
+However it can also be used to control the threadpool of other native 
libraries,
+provided that they expose an API to get and set the limit on the number of 
threads.
+For that, one must implement a controller for this library and register it to
+`threadpoolctl`.
+
+A custom controller must be a subclass of the `LibController` class and 
implement
+the attributes and methods described in the docstring of `LibController`. Then 
this
+new controller class must be registered using the `threadpoolctl.register` 
function.
+An complete example can be found [here](
+  
https://github.com/joblib/threadpoolctl/blob/master/tests/_pyMylib/__init__.py).
+
 ### Sequential BLAS within OpenMP parallel region
 
 When one wants to have sequential BLAS calls within an OpenMP parallel region, 
it's
-safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` 
and `user_api="blas"` might not lead to the expected behavior in some 
configurations
+safer to set `limits="sequential_blas_under_openmp"` since setting `limits=1` 
and
+`user_api="blas"` might not lead to the expected behavior in some 
configurations
 (e.g. OpenBLAS with the OpenMP threading layer
 https://github.com/xianyi/OpenBLAS/issues/2985).
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/threadpoolctl-3.1.0/continuous_integration/build_test_ext.sh 
new/threadpoolctl-3.2.0/continuous_integration/build_test_ext.sh
--- old/threadpoolctl-3.1.0/continuous_integration/build_test_ext.sh    
2019-06-26 11:41:29.299105200 +0200
+++ new/threadpoolctl-3.2.0/continuous_integration/build_test_ext.sh    
2023-07-12 10:17:49.739681000 +0200
@@ -2,6 +2,14 @@
 
 set -e
 
+if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+    pushd tests/_pyMylib
+    rm -rf *.so *.o
+    gcc -c -Wall -Werror -fpic -o my_threaded_lib.o my_threaded_lib.c
+    gcc -shared -o my_threaded_lib.so my_threaded_lib.o
+    popd
+fi
+
 pushd tests/_openmp_test_helper
 rm -rf *.c *.so *.dylib build/
 python setup_inner.py build_ext -i
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/threadpoolctl-3.1.0/continuous_integration/check_no_test_skipped.py 
new/threadpoolctl-3.2.0/continuous_integration/check_no_test_skipped.py
--- old/threadpoolctl-3.1.0/continuous_integration/check_no_test_skipped.py     
2021-09-17 11:26:34.308249000 +0200
+++ new/threadpoolctl-3.2.0/continuous_integration/check_no_test_skipped.py     
2023-06-30 17:31:34.528461200 +0200
@@ -33,11 +33,20 @@
                 always_skipped[test_name] = skipped
 
 print("\n------------------------------------------------------------------\n")
+
+# List of tests that we don't want to fail the CI if they are skipped in
+# every job. This is useful for tests that depend on specific versions of
+# numpy or scipy and we don't want to pin old versions of these libraries.
+SAFE_SKIPPED_TESTS = ["test_multiple_shipped_openblas"]
+
 fail = False
 for test, skipped in always_skipped.items():
     if skipped:
-        fail = True
-        print(test, "was skipped in every job")
+        if test in SAFE_SKIPPED_TESTS:
+            print(test, "was skipped in every job but it's fine to skip it")
+        else:
+            fail = True
+            print(test, "was skipped in every job")
 
 if fail:
     sys.exit(1)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/threadpoolctl-3.1.0/continuous_integration/install_with_blis.sh 
new/threadpoolctl-3.2.0/continuous_integration/install_with_blis.sh
--- old/threadpoolctl-3.1.0/continuous_integration/install_with_blis.sh 
2022-01-20 18:05:26.048133900 +0100
+++ new/threadpoolctl-3.2.0/continuous_integration/install_with_blis.sh 
2023-07-12 15:09:55.561690000 +0200
@@ -13,7 +13,7 @@
 sudo apt-get install libomp-dev
 
 # create conda env
-conda create -n $VIRTUALENV -q --yes python=$VERSION_PYTHON pip cython
+conda create -n $VIRTUALENV -q --yes -c conda-forge python=$VERSION_PYTHON pip 
cython
 source activate $VIRTUALENV
 
 if [[ "$BLIS_CC" == "gcc-8" ]]; then
@@ -41,8 +41,7 @@
 library_dirs = $ABS_PATH/BLIS_install/lib
 include_dirs = $ABS_PATH/BLIS_install/include/blis
 runtime_library_dirs = $ABS_PATH/BLIS_install/lib" > site.cfg
-python setup.py build_ext -i
-pip install -e .
+python setup.py develop
 popd
 
 popd
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/continuous_integration/posix.yml 
new/threadpoolctl-3.2.0/continuous_integration/posix.yml
--- old/threadpoolctl-3.1.0/continuous_integration/posix.yml    2021-09-24 
10:58:15.330052900 +0200
+++ new/threadpoolctl-3.2.0/continuous_integration/posix.yml    2023-07-13 
16:19:36.077411700 +0200
@@ -21,13 +21,6 @@
       displayName: Take ownership of conda installation
       condition: eq('${{ parameters.name }}', 'macOS')
     - script: |
-        conda create -n tmp -y -c conda-forge python black
-        source activate tmp
-        black --check .
-        conda deactivate
-      displayName: Lint
-      condition: eq(variables['LINT'], 'true')
-    - script: |
         continuous_integration/install.sh
       displayName: 'Install without BLIS'
       condition: ne(variables['INSTALL_BLIS'], 'true')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/multiple_openmp.md 
new/threadpoolctl-3.2.0/multiple_openmp.md
--- old/threadpoolctl-3.1.0/multiple_openmp.md  2021-09-15 15:26:21.493843300 
+0200
+++ new/threadpoolctl-3.2.0/multiple_openmp.md  2023-07-12 10:17:49.739681000 
+0200
@@ -29,13 +29,13 @@
 `libgomp` and `libiomp`, which is the most common mix (NumPy with MKL + a
 package compiled with GCC, the most widely used C compiler on that platform).
 
-## Incompatibility between Intel OpenMP and LLVM OpenMP under Linux
+## Incompatibility between Intel OpenMP and LLVM OpenMP
 
 The only unrecoverable incompatibility we encountered happens when loading a
 mix of compiled extensions linked with **`libomp` (LLVM/Clang) and `libiomp`
-(ICC), on Linux**, manifested by crashes or deadlocks. It can happen even with
-the simplest OpenMP calls like getting the maximum number of threads that will
-be used in a subsequent parallel region. A possible explanation is that
+(ICC), on Linux and macOS**, manifested by crashes or deadlocks. It can happen
+even with the simplest OpenMP calls like getting the maximum number of threads
+that will be used in a subsequent parallel region. A possible explanation is 
that
 `libomp` is actually a fork of `libiomp` causing name colliding for instance.
 Using `threadpoolctl` may crash your program in such a setting.
 
@@ -43,21 +43,23 @@
 binary distributions of Python packages for Linux use either GCC or ICC to
 build the Python scientific packages. Therefore this problem would only happen
 if some packagers decide to start shipping Python packages built with
-LLVM/Clang instead of GCC.
-
-Surprisingly, we never encountered this kind of issue on macOS, where this mix
-is the most frequent (Clang being the default C compiler on macOS).
+LLVM/Clang instead of GCC (this is the case for instance with conda's default 
channel).
 
 ## Workarounds for Intel OpenMP and LLVM OpenMP case
 
 As far as we know, the only workaround consists in making sure only of one of
 the two incompatible OpenMP libraries is loaded. For example:
 
-- Tell MKL (used by NumPy) to use the GNU OpenMP runtime instead of the Intel
-  OpenMP runtime by setting the following environment variable:
+- Tell MKL (used by NumPy) to use another threading implementation instead of 
the Intel
+  OpenMP runtime. It can be the GNU OpenMP runtime on Linux or TBB on Linux 
and MacOS
+  for instance. This is done by setting the following environment variable:
 
       export MKL_THREADING_LAYER=GNU
 
+  or, if TBB is installed:
+
+      export MKL_THREADING_LAYER=TBB
+
 - Install a build of NumPy and SciPy linked against OpenBLAS instead of MKL.
   This can be done for instance by installing NumPy and SciPy from PyPI:
 
@@ -82,4 +84,4 @@
 reproducer written in C:
 
 - https://bugs.llvm.org/show_bug.cgi?id=43565
-- https://software.intel.com/en-us/forums/intel-c-compiler/topic/827607
+- 
https://community.intel.com/t5/Intel-C-Compiler/Cannot-call-OpenMP-functions-from-libiomp-after-calling-from/m-p/1176406
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/pyproject.toml 
new/threadpoolctl-3.2.0/pyproject.toml
--- old/threadpoolctl-3.1.0/pyproject.toml      2022-01-31 16:08:24.277708800 
+0100
+++ new/threadpoolctl-3.2.0/pyproject.toml      2023-07-12 10:17:49.743681400 
+0200
@@ -8,20 +8,20 @@
 author-email = "thomas.moreau.2...@gmail.com"
 home-page = "https://github.com/joblib/threadpoolctl";
 description-file = "README.md"
-requires-python = ">=3.6"
+requires-python = ">=3.8"
 license = "BSD-3-Clause"
 classifiers = [
     "Intended Audience :: Developers",
     "License :: OSI Approved :: BSD License",
     "Programming Language :: Python :: 3",
-    "Programming Language :: Python :: 3.6",
-    "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",
     "Topic :: Software Development :: Libraries :: Python Modules",
 ]
 
 [tool.black]
 line-length = 88
-target_version = ['py36', 'py37', 'py38', 'py39']
-experimental_string_processing = true
+target_version = ['py38', 'py39', 'py310', 'py311']
+preview = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/tests/_pyMylib/__init__.py 
new/threadpoolctl-3.2.0/tests/_pyMylib/__init__.py
--- old/threadpoolctl-3.1.0/tests/_pyMylib/__init__.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/threadpoolctl-3.2.0/tests/_pyMylib/__init__.py  2023-07-12 
10:17:49.743681400 +0200
@@ -0,0 +1,46 @@
+import ctypes
+from pathlib import Path
+
+from threadpoolctl import LibController, register
+
+
+path = Path(__file__).parent / "my_threaded_lib.so"
+ctypes.CDLL(path)
+
+
+class MyThreadedLibController(LibController):
+    # names for threadpoolctl's context filtering
+    user_api = "my_threaded_lib"
+    internal_api = "my_threaded_lib"
+
+    # Patterns to identify the name of the linked library to load.
+    # If a dynamic library with a matching filename is linked to the python
+    # process, it will be loaded as the `dynlib` attribute of the LibController
+    # instance.
+    filename_prefixes = ("my_threaded_lib",)
+
+    def get_num_threads(self):
+        # This function should return the current maximum number of threads,
+        # which is reported as "num_threads" by `ThreadpoolController.info`.
+        return getattr(self.dynlib, "mylib_get_num_threads")()
+
+    def set_num_threads(self, num_threads):
+        # This function limits the maximum number of threads,
+        # when `ThreadpoolController.limit` is called.
+        getattr(self.dynlib, "mylib_set_num_threads")(num_threads)
+
+    def get_version(self):
+        # This function returns the version of the linked library if it is 
exposed,
+        # which is reported as "version" by `ThreadpoolController.info`.
+        get_version = getattr(self.dynlib, "mylib_get_version")
+        get_version.restype = ctypes.c_char_p
+        return get_version().decode("utf-8")
+
+    def set_additional_attributes(self):
+        # This function is called during the initialization of the 
LibController.
+        # Additional information meant to be exposed by 
`ThreadpoolController.info`
+        # should be set here as attributes of the LibController instance.
+        self.some_attr = "some_value"
+
+
+register(MyThreadedLibController)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/tests/_pyMylib/my_threaded_lib.c 
new/threadpoolctl-3.2.0/tests/_pyMylib/my_threaded_lib.c
--- old/threadpoolctl-3.1.0/tests/_pyMylib/my_threaded_lib.c    1970-01-01 
01:00:00.000000000 +0100
+++ new/threadpoolctl-3.2.0/tests/_pyMylib/my_threaded_lib.c    2023-07-12 
10:17:49.743681400 +0200
@@ -0,0 +1,17 @@
+int NUM_THREADS = 42;
+int* NUM_THREADS_p = &NUM_THREADS;
+
+
+int mylib_get_num_threads(){
+    return *NUM_THREADS_p;
+}
+
+
+void mylib_set_num_threads(int num_threads){
+    *NUM_THREADS_p = num_threads;
+}
+
+
+char* mylib_get_version(){
+    return "2.0";
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/tests/test_threadpoolctl.py 
new/threadpoolctl-3.2.0/tests/test_threadpoolctl.py
--- old/threadpoolctl-3.1.0/tests/test_threadpoolctl.py 2022-01-31 
16:08:24.277708800 +0100
+++ new/threadpoolctl-3.2.0/tests/test_threadpoolctl.py 2023-07-12 
10:17:49.743681400 +0200
@@ -22,6 +22,21 @@
     return lib_controller.internal_api == "openblas" and 
lib_controller.version is None
 
 
+def skip_if_openblas_openmp():
+    """Helper to skip tests with side effects when OpenBLAS has the OpenMP
+    threading layer.
+    """
+    if any(
+        lib_controller.internal_api == "openblas"
+        and lib_controller.threading_layer == "openmp"
+        for lib_controller in ThreadpoolController().lib_controllers
+    ):
+        pytest.skip(
+            "Setting a limit on OpenBLAS when using the OpenMP threading layer 
also "
+            "impact the OpenMP library. They can't be controlled 
independently."
+        )
+
+
 def effective_num_threads(nthreads, max_threads):
     if nthreads is None or nthreads > max_threads:
         return max_threads
@@ -196,6 +211,10 @@
 def test_threadpool_controller_limit():
     # Check that using the limit method of ThreadpoolController only impact its
     # library controllers.
+
+    # This is not True for OpenBLAS with the OpenMP threading layer.
+    skip_if_openblas_openmp()
+
     blas_controller = ThreadpoolController().select(user_api="blas")
     original_openmp_info = 
ThreadpoolController().select(user_api="openmp").info()
 
@@ -208,14 +227,8 @@
             for lib_controller in blas_controller.lib_controllers
         )
         # original_blas_controller contains only blas libraries so no opemp 
library
-        # should be impacted. This is not True for OpenBLAS with the OpenMP 
threading
-        # layer.
-        if not any(
-            lib_controller.internal_api == "openblas"
-            and lib_controller.threading_layer == "openmp"
-            for lib_controller in blas_controller.lib_controllers
-        ):
-            assert openmp_info == original_openmp_info
+        # should be impacted.
+        assert openmp_info == original_openmp_info
 
 
 def test_get_params_for_sequential_blas_under_openmp():
@@ -407,6 +420,8 @@
 
     check_nested_prange_blas = prange_blas.check_nested_prange_blas
 
+    skip_if_openblas_openmp()
+
     original_info = ThreadpoolController().info()
 
     blas_controller = ThreadpoolController().select(user_api="blas")
@@ -445,7 +460,7 @@
 
 # the method `get_original_num_threads` raises a UserWarning due to different
 # num_threads from libraries with the same `user_api`. It will be raised only
-# in the CI job with 2 openblas (py37_pip_openblas_gcc_clang). It is expected
+# in the CI job with 2 openblas (py38_pip_openblas_gcc_clang). It is expected
 # so we can safely filter it.
 @pytest.mark.filterwarnings("ignore::UserWarning")
 @pytest.mark.parametrize("limit", [1, None])
@@ -657,3 +672,33 @@
     outer_func()
 
     assert ThreadpoolController().info() == original_info
+
+
+def test_custom_controller():
+    # Check that a custom controller can be used to change the number of 
threads
+    # used by a library.
+    try:
+        import tests._pyMylib  # noqa
+    except:
+        pytest.skip("requires my_thread_lib to be compiled")
+
+    controller = ThreadpoolController()
+    original_info = controller.info()
+
+    mylib_controller = controller.select(user_api="my_threaded_lib")
+
+    # my_threaded_lib has been found and there's 1 matching shared library
+    assert len(mylib_controller.lib_controllers) == 1
+    mylib_controller = mylib_controller.lib_controllers[0]
+
+    # we linked against my_threaded_lib v2.0 and by default it uses 42 thread
+    assert mylib_controller.version == "2.0"
+    assert mylib_controller.num_threads == 42
+
+    # my_threaded_lib exposes an additional info "some_attr":
+    assert mylib_controller.info()["some_attr"] == "some_value"
+
+    with controller.limit(limits=1, user_api="my_threaded_lib"):
+        assert mylib_controller.num_threads == 1
+
+    assert ThreadpoolController().info() == original_info
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/threadpoolctl-3.1.0/threadpoolctl.py 
new/threadpoolctl-3.2.0/threadpoolctl.py
--- old/threadpoolctl-3.1.0/threadpoolctl.py    2022-01-31 17:25:14.640525300 
+0100
+++ new/threadpoolctl-3.2.0/threadpoolctl.py    2023-07-13 16:44:13.629080800 
+0200
@@ -15,14 +15,21 @@
 import sys
 import ctypes
 import textwrap
+from typing import final
 import warnings
 from ctypes.util import find_library
 from abc import ABC, abstractmethod
 from functools import lru_cache
 from contextlib import ContextDecorator
 
-__version__ = "3.1.0"
-__all__ = ["threadpool_limits", "threadpool_info", "ThreadpoolController"]
+__version__ = "3.2.0"
+__all__ = [
+    "threadpool_limits",
+    "threadpool_info",
+    "ThreadpoolController",
+    "LibController",
+    "register",
+]
 
 
 # One can get runtime errors or even segfaults due to multiple OpenMP libraries
@@ -40,8 +47,8 @@
 
 # Structure to cast the info on dynamically loaded library. See
 # https://linux.die.net/man/3/dl_iterate_phdr for more details.
-_SYSTEM_UINT = ctypes.c_uint64 if sys.maxsize > 2 ** 32 else ctypes.c_uint32
-_SYSTEM_UINT_HALF = ctypes.c_uint32 if sys.maxsize > 2 ** 32 else 
ctypes.c_uint16
+_SYSTEM_UINT = ctypes.c_uint64 if sys.maxsize > 2**32 else ctypes.c_uint32
+_SYSTEM_UINT_HALF = ctypes.c_uint32 if sys.maxsize > 2**32 else ctypes.c_uint16
 
 
 class _dl_phdr_info(ctypes.Structure):
@@ -60,56 +67,318 @@
     _RTLD_NOLOAD = ctypes.DEFAULT_MODE
 
 
-# List of the supported libraries. The items are indexed by the name of the
-# class to instantiate to create the library controller objects. The items hold
-# the possible prefixes of loaded shared objects, the name of the internal_api
-# to call, the name of the user_api and potentially some symbols that the 
library is
-# expected to have (this is necessary to distinguish between the blas 
implementations
-# when they are all renamed "libblas.dll" on conda-forge on windows).
-_SUPPORTED_LIBRARIES = {
-    "OpenMPController": {
-        "user_api": "openmp",
-        "internal_api": "openmp",
-        "filename_prefixes": ("libiomp", "libgomp", "libomp", "vcomp"),
-    },
-    "OpenBLASController": {
-        "user_api": "blas",
-        "internal_api": "openblas",
-        "filename_prefixes": ("libopenblas", "libblas"),
-        "check_symbols": ("openblas_get_num_threads", 
"openblas_get_num_threads64_"),
-    },
-    "MKLController": {
-        "user_api": "blas",
-        "internal_api": "mkl",
-        "filename_prefixes": ("libmkl_rt", "mkl_rt", "libblas"),
-        "check_symbols": ("MKL_Get_Max_Threads",),
-    },
-    "BLISController": {
-        "user_api": "blas",
-        "internal_api": "blis",
-        "filename_prefixes": ("libblis", "libblas"),
-        "check_symbols": ("bli_thread_get_num_threads",),
-    },
-}
+class LibController(ABC):
+    """Abstract base class for the individual library controllers
+
+    A library controller must expose the following class attributes:
+        - user_api : str
+            Usually the name of the library or generic specification the 
library
+            implements, e.g. "blas" is a specification with different 
implementations.
+        - internal_api : str
+            Usually the name of the library or concrete implementation of some
+            specification, e.g. "openblas" is an implementation of the "blas"
+            specification.
+        - filename_prefixes : tuple
+            Possible prefixes of the shared library's filename that allow to
+            identify the library. e.g. "libopenblas" for libopenblas.so.
+
+    and implement the following methods: `get_num_threads`, `set_num_threads` 
and
+    `get_version`.
+
+    Threadpoolctl loops through all the loaded shared libraries and tries to 
match
+    the filename of each library with the `filename_prefixes`. If a match is 
found, a
+    controller is instantiated and a handler to the library is stored in the 
`dynlib`
+    attribute as a `ctypes.CDLL` object. It can be used to access the 
necessary symbols
+    of the shared library to implement the above methods.
+
+    The following information will be exposed in the info dictionary:
+      - user_api : standardized API, if any, or a copy of internal_api.
+      - internal_api : implementation-specific API.
+      - num_threads : the current thread limit.
+      - prefix : prefix of the shared library's filename.
+      - filepath : path to the loaded shared library.
+      - version : version of the library (if available).
+
+    In addition, each library controller may expose internal API specific 
entries. They
+    must be set as attributes in the `set_additional_attributes` method.
+    """
+
+    @final
+    def __init__(self, *, filepath=None, prefix=None):
+        """This is not meant to be overriden by subclasses."""
+        self.prefix = prefix
+        self.filepath = filepath
+        self.dynlib = ctypes.CDLL(filepath, mode=_RTLD_NOLOAD)
+        self.version = self.get_version()
+        self.set_additional_attributes()
+
+    @final
+    def info(self):
+        """Return relevant info wrapped in a dict
+
+        This is not meant to be overriden by subclasses.
+        """
+        exposed_attrs = {
+            "user_api": self.user_api,
+            "internal_api": self.internal_api,
+            "num_threads": self.num_threads,
+            **vars(self),
+        }
+        exposed_attrs.pop("dynlib")
+        return exposed_attrs
+
+    def set_additional_attributes(self):
+        """Set additional attributes meant to be exposed in the info dict"""
+
+    @property
+    def num_threads(self):
+        """Exposes the current thread limit as a dynamic property
+
+        This is not meant to be used or overriden by subclasses.
+        """
+        return self.get_num_threads()
+
+    @abstractmethod
+    def get_num_threads(self):
+        """Return the maximum number of threads available to use"""
+
+    @abstractmethod
+    def set_num_threads(self, num_threads):
+        """Set the maximum number of threads to use"""
+
+    @abstractmethod
+    def get_version(self):
+        """Return the version of the shared library"""
+
+
+class OpenBLASController(LibController):
+    """Controller class for OpenBLAS"""
+
+    user_api = "blas"
+    internal_api = "openblas"
+    filename_prefixes = ("libopenblas", "libblas")
+    check_symbols = ("openblas_get_num_threads", "openblas_get_num_threads64_")
+
+    def set_additional_attributes(self):
+        self.threading_layer = self._get_threading_layer()
+        self.architecture = self._get_architecture()
+
+    def get_num_threads(self):
+        get_func = getattr(
+            self.dynlib,
+            "openblas_get_num_threads",
+            # Symbols differ when built for 64bit integers in Fortran
+            getattr(self.dynlib, "openblas_get_num_threads64_", lambda: None),
+        )
+
+        return get_func()
+
+    def set_num_threads(self, num_threads):
+        set_func = getattr(
+            self.dynlib,
+            "openblas_set_num_threads",
+            # Symbols differ when built for 64bit integers in Fortran
+            getattr(
+                self.dynlib, "openblas_set_num_threads64_", lambda 
num_threads: None
+            ),
+        )
+        return set_func(num_threads)
+
+    def get_version(self):
+        # None means OpenBLAS is not loaded or version < 0.3.4, since OpenBLAS
+        # did not expose its version before that.
+        get_config = getattr(
+            self.dynlib,
+            "openblas_get_config",
+            getattr(self.dynlib, "openblas_get_config64_", None),
+        )
+        if get_config is None:
+            return None
+
+        get_config.restype = ctypes.c_char_p
+        config = get_config().split()
+        if config[0] == b"OpenBLAS":
+            return config[1].decode("utf-8")
+        return None
+
+    def _get_threading_layer(self):
+        """Return the threading layer of OpenBLAS"""
+        openblas_get_parallel = getattr(
+            self.dynlib,
+            "openblas_get_parallel",
+            getattr(self.dynlib, "openblas_get_parallel64_", None),
+        )
+        if openblas_get_parallel is None:
+            return "unknown"
+        threading_layer = openblas_get_parallel()
+        if threading_layer == 2:
+            return "openmp"
+        elif threading_layer == 1:
+            return "pthreads"
+        return "disabled"
+
+    def _get_architecture(self):
+        """Return the architecture detected by OpenBLAS"""
+        get_corename = getattr(
+            self.dynlib,
+            "openblas_get_corename",
+            getattr(self.dynlib, "openblas_get_corename64_", None),
+        )
+        if get_corename is None:
+            return None
+
+        get_corename.restype = ctypes.c_char_p
+        return get_corename().decode("utf-8")
+
+
+class BLISController(LibController):
+    """Controller class for BLIS"""
+
+    user_api = "blas"
+    internal_api = "blis"
+    filename_prefixes = ("libblis", "libblas")
+    check_symbols = ("bli_thread_get_num_threads",)
+
+    def set_additional_attributes(self):
+        self.threading_layer = self._get_threading_layer()
+        self.architecture = self._get_architecture()
+
+    def get_num_threads(self):
+        get_func = getattr(self.dynlib, "bli_thread_get_num_threads", lambda: 
None)
+        num_threads = get_func()
+        # by default BLIS is single-threaded and get_num_threads
+        # returns -1. We map it to 1 for consistency with other libraries.
+        return 1 if num_threads == -1 else num_threads
+
+    def set_num_threads(self, num_threads):
+        set_func = getattr(
+            self.dynlib, "bli_thread_set_num_threads", lambda num_threads: None
+        )
+        return set_func(num_threads)
+
+    def get_version(self):
+        get_version_ = getattr(self.dynlib, "bli_info_get_version_str", None)
+        if get_version_ is None:
+            return None
+
+        get_version_.restype = ctypes.c_char_p
+        return get_version_().decode("utf-8")
+
+    def _get_threading_layer(self):
+        """Return the threading layer of BLIS"""
+        if self.dynlib.bli_info_get_enable_openmp():
+            return "openmp"
+        elif self.dynlib.bli_info_get_enable_pthreads():
+            return "pthreads"
+        return "disabled"
+
+    def _get_architecture(self):
+        """Return the architecture detected by BLIS"""
+        bli_arch_query_id = getattr(self.dynlib, "bli_arch_query_id", None)
+        bli_arch_string = getattr(self.dynlib, "bli_arch_string", None)
+        if bli_arch_query_id is None or bli_arch_string is None:
+            return None
+
+        # the true restype should be BLIS' arch_t (enum) but int should work
+        # for us:
+        bli_arch_query_id.restype = ctypes.c_int
+        bli_arch_string.restype = ctypes.c_char_p
+        return bli_arch_string(bli_arch_query_id()).decode("utf-8")
+
+
+class MKLController(LibController):
+    """Controller class for MKL"""
+
+    user_api = "blas"
+    internal_api = "mkl"
+    filename_prefixes = ("libmkl_rt", "mkl_rt", "libblas")
+    check_symbols = ("MKL_Get_Max_Threads",)
+
+    def set_additional_attributes(self):
+        self.threading_layer = self._get_threading_layer()
+
+    def get_num_threads(self):
+        get_func = getattr(self.dynlib, "MKL_Get_Max_Threads", lambda: None)
+        return get_func()
+
+    def set_num_threads(self, num_threads):
+        set_func = getattr(self.dynlib, "MKL_Set_Num_Threads", lambda 
num_threads: None)
+        return set_func(num_threads)
+
+    def get_version(self):
+        if not hasattr(self.dynlib, "MKL_Get_Version_String"):
+            return None
+
+        res = ctypes.create_string_buffer(200)
+        self.dynlib.MKL_Get_Version_String(res, 200)
+
+        version = res.value.decode("utf-8")
+        group = re.search(r"Version ([^ ]+) ", version)
+        if group is not None:
+            version = group.groups()[0]
+        return version.strip()
+
+    def _get_threading_layer(self):
+        """Return the threading layer of MKL"""
+        # The function mkl_set_threading_layer returns the current threading
+        # layer. Calling it with an invalid threading layer allows us to safely
+        # get the threading layer
+        set_threading_layer = getattr(
+            self.dynlib, "MKL_Set_Threading_Layer", lambda layer: -1
+        )
+        layer_map = {
+            0: "intel",
+            1: "sequential",
+            2: "pgi",
+            3: "gnu",
+            4: "tbb",
+            -1: "not specified",
+        }
+        return layer_map[set_threading_layer(-1)]
+
+
+class OpenMPController(LibController):
+    """Controller class for OpenMP"""
+
+    user_api = "openmp"
+    internal_api = "openmp"
+    filename_prefixes = ("libiomp", "libgomp", "libomp", "vcomp")
+
+    def get_num_threads(self):
+        get_func = getattr(self.dynlib, "omp_get_max_threads", lambda: None)
+        return get_func()
+
+    def set_num_threads(self, num_threads):
+        set_func = getattr(self.dynlib, "omp_set_num_threads", lambda 
num_threads: None)
+        return set_func(num_threads)
+
+    def get_version(self):
+        # There is no way to get the version number programmatically in OpenMP.
+        return None
+
+
+# Controllers for the libraries that we'll look for in the loaded libraries.
+# Third party libraries can register their own controllers.
+_ALL_CONTROLLERS = [OpenBLASController, BLISController, MKLController, 
OpenMPController]
 
 # Helpers for the doc and test names
-_ALL_USER_APIS = list(set(lib["user_api"] for lib in 
_SUPPORTED_LIBRARIES.values()))
-_ALL_INTERNAL_APIS = [lib["internal_api"] for lib in 
_SUPPORTED_LIBRARIES.values()]
+_ALL_USER_APIS = list(set(lib.user_api for lib in _ALL_CONTROLLERS))
+_ALL_INTERNAL_APIS = [lib.internal_api for lib in _ALL_CONTROLLERS]
 _ALL_PREFIXES = list(
-    set(
-        prefix
-        for lib in _SUPPORTED_LIBRARIES.values()
-        for prefix in lib["filename_prefixes"]
-    )
+    set(prefix for lib in _ALL_CONTROLLERS for prefix in lib.filename_prefixes)
 )
 _ALL_BLAS_LIBRARIES = [
-    lib["internal_api"]
-    for lib in _SUPPORTED_LIBRARIES.values()
-    if lib["user_api"] == "blas"
+    lib.internal_api for lib in _ALL_CONTROLLERS if lib.user_api == "blas"
 ]
-_ALL_OPENMP_LIBRARIES = list(
-    _SUPPORTED_LIBRARIES["OpenMPController"]["filename_prefixes"]
-)
+_ALL_OPENMP_LIBRARIES = OpenMPController.filename_prefixes
+
+
+def register(controller):
+    """Register a new controller"""
+    _ALL_CONTROLLERS.append(controller)
+    _ALL_USER_APIS.append(controller.user_api)
+    _ALL_INTERNAL_APIS.append(controller.internal_api)
+    _ALL_PREFIXES.extend(controller.filename_prefixes)
 
 
 def _format_docstring(*args, **kwargs):
@@ -377,12 +646,6 @@
         return super().wrap(ThreadpoolController(), limits=limits, 
user_api=user_api)
 
 
-@_format_docstring(
-    PREFIXES=", ".join(f'"{prefix}"' for prefix in _ALL_PREFIXES),
-    USER_APIS=", ".join(f'"{api}"' for api in _ALL_USER_APIS),
-    BLAS_LIBS=", ".join(_ALL_BLAS_LIBRARIES),
-    OPENMP_LIBS=", ".join(_ALL_OPENMP_LIBRARIES),
-)
 class ThreadpoolController:
     """Collection of LibController objects for all loaded supported libraries
 
@@ -664,7 +927,6 @@
             buf = ctypes.create_unicode_buffer(MAX_PATH)
             n_size = DWORD()
             for h_module in h_modules:
-
                 # Get the path of the current module
                 if not ps_api.GetModuleFileNameExW(
                     h_process, h_module, ctypes.byref(buf), 
ctypes.byref(n_size)
@@ -687,9 +949,9 @@
 
         # Loop through supported libraries to find if this filename corresponds
         # to a supported one.
-        for controller_class, candidate_lib in _SUPPORTED_LIBRARIES.items():
+        for controller_class in _ALL_CONTROLLERS:
             # check if filename matches a supported prefix
-            prefix = self._check_prefix(filename, 
candidate_lib["filename_prefixes"])
+            prefix = self._check_prefix(filename, 
controller_class.filename_prefixes)
 
             # filename does not match any of the prefixes of the candidate
             # library. move to next library.
@@ -705,7 +967,7 @@
                     libblas = ctypes.CDLL(filepath, _RTLD_NOLOAD)
                     if not any(
                         hasattr(libblas, func)
-                        for func in candidate_lib["check_symbols"]
+                        for func in controller_class.check_symbols
                     ):
                         continue
                 else:
@@ -718,16 +980,8 @@
 
             # filename matches a prefix. Create and store the library
             # controller.
-            user_api = candidate_lib["user_api"]
-            internal_api = candidate_lib["internal_api"]
 
-            lib_controller_class = globals()[controller_class]
-            lib_controller = lib_controller_class(
-                filepath=filepath,
-                prefix=prefix,
-                user_api=user_api,
-                internal_api=internal_api,
-            )
+            lib_controller = controller_class(filepath=filepath, prefix=prefix)
             self.lib_controllers.append(lib_controller)
 
     def _check_prefix(self, library_basename, filename_prefixes):
@@ -742,13 +996,8 @@
 
     def _warn_if_incompatible_openmp(self):
         """Raise a warning if llvm-OpenMP and intel-OpenMP are both loaded"""
-        if sys.platform != "linux":
-            # Only raise the warning on linux
-            return
-
         prefixes = [lib_controller.prefix for lib_controller in 
self.lib_controllers]
-        msg = textwrap.dedent(
-            """
+        msg = textwrap.dedent("""
             Found Intel OpenMP ('libiomp') and LLVM OpenMP ('libomp') loaded at
             the same time. Both libraries are known to be incompatible and this
             can cause random crashes or deadlocks on Linux when loaded in the
@@ -756,8 +1005,7 @@
             Using threadpoolctl may cause crashes or deadlocks. For more
             information and possible workarounds, please see
                 
https://github.com/joblib/threadpoolctl/blob/master/multiple_openmp.md
-            """
-        )
+            """)
         if "libomp" in prefixes and "libiomp" in prefixes:
             warnings.warn(msg, RuntimeWarning)
 
@@ -768,6 +1016,12 @@
         if libc is None:
             libc_name = find_library("c")
             if libc_name is None:  # pragma: no cover
+                warnings.warn(
+                    "libc not found. The ctypes module in Python"
+                    f" {sys.version_info.major}.{sys.version_info.minor} is 
maybe"
+                    " too old for this OS.",
+                    RuntimeWarning,
+                )
                 return None
             libc = ctypes.CDLL(libc_name, mode=_RTLD_NOLOAD)
             cls._system_libraries["libc"] = libc
@@ -783,252 +1037,6 @@
         return dll
 
 
-@_format_docstring(
-    USER_APIS=", ".join('"{}"'.format(api) for api in _ALL_USER_APIS),
-    INTERNAL_APIS=", ".join('"{}"'.format(api) for api in _ALL_INTERNAL_APIS),
-)
-class LibController(ABC):
-    """Abstract base class for the individual library controllers
-
-    A library controller is represented by the following information:
-      - "user_api" : user API. Possible values are {USER_APIS}.
-      - "internal_api" : internal API. Possible values are {INTERNAL_APIS}.
-      - "prefix" : prefix of the shared library's name.
-      - "filepath" : path to the loaded library.
-      - "version" : version of the library (if available).
-      - "num_threads" : the current thread limit.
-
-    In addition, each library controller may contain internal_api specific
-    entries.
-    """
-
-    def __init__(self, *, filepath=None, prefix=None, user_api=None, 
internal_api=None):
-        self.user_api = user_api
-        self.internal_api = internal_api
-        self.prefix = prefix
-        self.filepath = filepath
-        self._dynlib = ctypes.CDLL(filepath, mode=_RTLD_NOLOAD)
-        self.version = self.get_version()
-
-    def info(self):
-        """Return relevant info wrapped in a dict"""
-        all_attrs = dict(vars(self), **{"num_threads": self.num_threads})
-        return {k: v for k, v in all_attrs.items() if not k.startswith("_")}
-
-    @property
-    def num_threads(self):
-        return self.get_num_threads()
-
-    @abstractmethod
-    def get_num_threads(self):
-        """Return the maximum number of threads available to use"""
-        pass  # pragma: no cover
-
-    @abstractmethod
-    def set_num_threads(self, num_threads):
-        """Set the maximum number of threads to use"""
-        pass  # pragma: no cover
-
-    @abstractmethod
-    def get_version(self):
-        """Return the version of the shared library"""
-        pass  # pragma: no cover
-
-
-class OpenBLASController(LibController):
-    """Controller class for OpenBLAS"""
-
-    def __init__(self, **kwargs):
-        super().__init__(**kwargs)
-        self.threading_layer = self._get_threading_layer()
-        self.architecture = self._get_architecture()
-
-    def get_num_threads(self):
-        get_func = getattr(
-            self._dynlib,
-            "openblas_get_num_threads",
-            # Symbols differ when built for 64bit integers in Fortran
-            getattr(self._dynlib, "openblas_get_num_threads64_", lambda: None),
-        )
-
-        return get_func()
-
-    def set_num_threads(self, num_threads):
-        set_func = getattr(
-            self._dynlib,
-            "openblas_set_num_threads",
-            # Symbols differ when built for 64bit integers in Fortran
-            getattr(
-                self._dynlib, "openblas_set_num_threads64_", lambda 
num_threads: None
-            ),
-        )
-        return set_func(num_threads)
-
-    def get_version(self):
-        # None means OpenBLAS is not loaded or version < 0.3.4, since OpenBLAS
-        # did not expose its version before that.
-        get_config = getattr(
-            self._dynlib,
-            "openblas_get_config",
-            getattr(self._dynlib, "openblas_get_config64_", None),
-        )
-        if get_config is None:
-            return None
-
-        get_config.restype = ctypes.c_char_p
-        config = get_config().split()
-        if config[0] == b"OpenBLAS":
-            return config[1].decode("utf-8")
-        return None
-
-    def _get_threading_layer(self):
-        """Return the threading layer of OpenBLAS"""
-        openblas_get_parallel = getattr(
-            self._dynlib,
-            "openblas_get_parallel",
-            getattr(self._dynlib, "openblas_get_parallel64_", None),
-        )
-        if openblas_get_parallel is None:
-            return "unknown"
-        threading_layer = openblas_get_parallel()
-        if threading_layer == 2:
-            return "openmp"
-        elif threading_layer == 1:
-            return "pthreads"
-        return "disabled"
-
-    def _get_architecture(self):
-        """Return the architecture detected by OpenBLAS"""
-        get_corename = getattr(
-            self._dynlib,
-            "openblas_get_corename",
-            getattr(self._dynlib, "openblas_get_corename64_", None),
-        )
-        if get_corename is None:
-            return None
-
-        get_corename.restype = ctypes.c_char_p
-        return get_corename().decode("utf-8")
-
-
-class BLISController(LibController):
-    """Controller class for BLIS"""
-
-    def __init__(self, **kwargs):
-        super().__init__(**kwargs)
-        self.threading_layer = self._get_threading_layer()
-        self.architecture = self._get_architecture()
-
-    def get_num_threads(self):
-        get_func = getattr(self._dynlib, "bli_thread_get_num_threads", lambda: 
None)
-        num_threads = get_func()
-        # by default BLIS is single-threaded and get_num_threads
-        # returns -1. We map it to 1 for consistency with other libraries.
-        return 1 if num_threads == -1 else num_threads
-
-    def set_num_threads(self, num_threads):
-        set_func = getattr(
-            self._dynlib, "bli_thread_set_num_threads", lambda num_threads: 
None
-        )
-        return set_func(num_threads)
-
-    def get_version(self):
-        get_version_ = getattr(self._dynlib, "bli_info_get_version_str", None)
-        if get_version_ is None:
-            return None
-
-        get_version_.restype = ctypes.c_char_p
-        return get_version_().decode("utf-8")
-
-    def _get_threading_layer(self):
-        """Return the threading layer of BLIS"""
-        if self._dynlib.bli_info_get_enable_openmp():
-            return "openmp"
-        elif self._dynlib.bli_info_get_enable_pthreads():
-            return "pthreads"
-        return "disabled"
-
-    def _get_architecture(self):
-        """Return the architecture detected by BLIS"""
-        bli_arch_query_id = getattr(self._dynlib, "bli_arch_query_id", None)
-        bli_arch_string = getattr(self._dynlib, "bli_arch_string", None)
-        if bli_arch_query_id is None or bli_arch_string is None:
-            return None
-
-        # the true restype should be BLIS' arch_t (enum) but int should work
-        # for us:
-        bli_arch_query_id.restype = ctypes.c_int
-        bli_arch_string.restype = ctypes.c_char_p
-        return bli_arch_string(bli_arch_query_id()).decode("utf-8")
-
-
-class MKLController(LibController):
-    """Controller class for MKL"""
-
-    def __init__(self, **kwargs):
-        super().__init__(**kwargs)
-        self.threading_layer = self._get_threading_layer()
-
-    def get_num_threads(self):
-        get_func = getattr(self._dynlib, "MKL_Get_Max_Threads", lambda: None)
-        return get_func()
-
-    def set_num_threads(self, num_threads):
-        set_func = getattr(
-            self._dynlib, "MKL_Set_Num_Threads", lambda num_threads: None
-        )
-        return set_func(num_threads)
-
-    def get_version(self):
-        if not hasattr(self._dynlib, "MKL_Get_Version_String"):
-            return None
-
-        res = ctypes.create_string_buffer(200)
-        self._dynlib.MKL_Get_Version_String(res, 200)
-
-        version = res.value.decode("utf-8")
-        group = re.search(r"Version ([^ ]+) ", version)
-        if group is not None:
-            version = group.groups()[0]
-        return version.strip()
-
-    def _get_threading_layer(self):
-        """Return the threading layer of MKL"""
-        # The function mkl_set_threading_layer returns the current threading
-        # layer. Calling it with an invalid threading layer allows us to safely
-        # get the threading layer
-        set_threading_layer = getattr(
-            self._dynlib, "MKL_Set_Threading_Layer", lambda layer: -1
-        )
-        layer_map = {
-            0: "intel",
-            1: "sequential",
-            2: "pgi",
-            3: "gnu",
-            4: "tbb",
-            -1: "not specified",
-        }
-        return layer_map[set_threading_layer(-1)]
-
-
-class OpenMPController(LibController):
-    """Controller class for OpenMP"""
-
-    def get_num_threads(self):
-        get_func = getattr(self._dynlib, "omp_get_max_threads", lambda: None)
-        return get_func()
-
-    def set_num_threads(self, num_threads):
-        set_func = getattr(
-            self._dynlib, "omp_set_num_threads", lambda num_threads: None
-        )
-        return set_func(num_threads)
-
-    def get_version(self):
-        # There is no way to get the version number programmatically in OpenMP.
-        return None
-
-
 def _main():
     """Commandline interface to display thread-pool information and exit."""
     import argparse

Reply via email to