Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-hardware for openSUSE:Factory 
checked in at 2026-10-26 10:01:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-hardware (Old)
 and      /work/SRC/openSUSE:Factory/.python-hardware.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-hardware"

Mon Oct 26 10:01:00 2026 rev:4 rq:1392465 version:0.33.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-hardware/python-hardware.changes  
2026-04-28 11:59:54.474325357 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-hardware.new.2252680/python-hardware.changes 
    2026-10-26 10:02:23.467724877 +0100
@@ -1,0 +2,19 @@
+Sun Oct 25 15:40:55 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 0.33.0:
+  * Fix PEP8 line length violations
+  * Modernize configuration file parsing
+  * Refactor get_cpus() function into single-responsibility
+    functions
+  * Update link of ipmitool project
+  * Simplify sphinx version requirement
+  * Remove options for babel
+  * Remove options for sphinx setuptools hook
+  * Bump lint packages
+  * Bump github actions
+  * Update supported python versions
+  * Add pyproject.toml to support pip version 23.1
+  * Run tests on ubuntu 24.04
+  * Remove Python 3.8 support
+
+-------------------------------------------------------------------

Old:
----
  hardware-0.32.0.tar.gz

New:
----
  hardware-0.33.0.tar.gz

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

Other differences:
------------------
++++++ python-hardware.spec ++++++
--- /var/tmp/diff_new_pack.Mtmsc7/_old  2026-10-26 10:02:24.434765431 +0100
+++ /var/tmp/diff_new_pack.Mtmsc7/_new  2026-10-26 10:02:24.439765641 +0100
@@ -24,7 +24,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-hardware
-Version:        0.32.0
+Version:        0.33.0
 Release:        0
 Summary:        Hardware detection and classification utilities
 License:        Apache-2.0

++++++ hardware-0.32.0.tar.gz -> hardware-0.33.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/.github/workflows/docs-testing.yml 
new/hardware-0.33.0/.github/workflows/docs-testing.yml
--- old/hardware-0.32.0/.github/workflows/docs-testing.yml      2024-01-31 
14:59:08.000000000 +0100
+++ new/hardware-0.33.0/.github/workflows/docs-testing.yml      2026-09-24 
14:44:48.000000000 +0200
@@ -5,15 +5,15 @@
 jobs:
   build:
 
-    runs-on: ubuntu-22.04
+    runs-on: ubuntu-24.04
     strategy:
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v7
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v7
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install Tox
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/.github/workflows/pep8-testing.yml 
new/hardware-0.33.0/.github/workflows/pep8-testing.yml
--- old/hardware-0.32.0/.github/workflows/pep8-testing.yml      2024-01-31 
14:59:08.000000000 +0100
+++ new/hardware-0.33.0/.github/workflows/pep8-testing.yml      2026-09-24 
14:44:48.000000000 +0200
@@ -5,15 +5,15 @@
 jobs:
   build:
 
-    runs-on: ubuntu-22.04
+    runs-on: ubuntu-24.04
     strategy:
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v7
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v7
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install Tox
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/.github/workflows/unit-testing.yml 
new/hardware-0.33.0/.github/workflows/unit-testing.yml
--- old/hardware-0.32.0/.github/workflows/unit-testing.yml      2024-01-31 
14:59:08.000000000 +0100
+++ new/hardware-0.33.0/.github/workflows/unit-testing.yml      2026-09-24 
14:44:48.000000000 +0200
@@ -5,15 +5,15 @@
 jobs:
   build:
 
-    runs-on: ubuntu-22.04
+    runs-on: ubuntu-24.04
     strategy:
       matrix:
-        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
+        python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v7
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v7
       with:
         python-version: ${{ matrix.python-version }}
     - name: Install Tox
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/AUTHORS new/hardware-0.33.0/AUTHORS
--- old/hardware-0.32.0/AUTHORS 2024-11-19 14:57:03.000000000 +0100
+++ new/hardware-0.33.0/AUTHORS 2026-09-24 15:03:41.000000000 +0200
@@ -1,5 +1,6 @@
 Alfredo Moralejo <[email protected]>
 Bob Fournier <[email protected]>
+Cursor <[email protected]>
 Cédric Jeanneret <[email protected]>
 Cédric LECOMTE <[email protected]>
 Dmitry Tantsur <[email protected]>
@@ -30,6 +31,7 @@
 Steven A. Broderick Elias <[email protected]>
 Sylvain Baubeau <[email protected]>
 Sylvain Baubeau <[email protected]>
+Takashi Kajinami <[email protected]>
 Tony Breeds <[email protected]>
 Uros Orozel <[email protected]>
 Victor Stinner <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/ChangeLog 
new/hardware-0.33.0/ChangeLog
--- old/hardware-0.32.0/ChangeLog       2024-11-19 14:57:03.000000000 +0100
+++ new/hardware-0.33.0/ChangeLog       2026-09-24 15:03:41.000000000 +0200
@@ -1,6 +1,23 @@
 CHANGES
 =======
 
+0.33.0
+------
+
+* Fix PEP8 line length violations
+* Modernize configuration file parsing
+* Refactor get\_cpus() function into single-responsibility functions
+* Update link of ipmitool project
+* Simplify sphinx version requirement
+* Remove options for babel
+* Remove options for sphinx setuptools hook
+* Bump lint packages
+* Bump github actions
+* Update supported python versions
+* Add pyproject.toml to support pip version 23.1
+* Run tests on ubuntu 24.04
+* Remove Python 3.8 support
+
 0.32.0
 ------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/PKG-INFO new/hardware-0.33.0/PKG-INFO
--- old/hardware-0.32.0/PKG-INFO        2024-11-19 14:57:03.820579000 +0100
+++ new/hardware-0.33.0/PKG-INFO        2026-09-24 15:03:42.301639000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: hardware
-Version: 0.32.0
+Version: 0.33.0
 Summary: Hardware detection and classification utilities
 Home-page: https://github.com/redhat-cip/hardware
 Author: eNovance
@@ -13,16 +13,25 @@
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3 :: Only
-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
-Requires-Python: >=3.8
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Requires-Python: >=3.10
 License-File: LICENSE
 License-File: AUTHORS
 Requires-Dist: pbr>=3.1.1
 Requires-Dist: pexpect
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license-file
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
 
 ===============================
 hardware
@@ -94,7 +103,7 @@
 
 System
 ======
-* ipmitool from https://sourceforge.net/projects/ipmitool/
+* ipmitool from https://codeberg.org/IPMITool/ipmitool
 * pciutils
 * lshw from http://www.ezix.org/project/wiki/HardwareLiSter
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/README.rst 
new/hardware-0.33.0/README.rst
--- old/hardware-0.32.0/README.rst      2022-12-14 12:38:12.000000000 +0100
+++ new/hardware-0.33.0/README.rst      2026-09-24 14:44:48.000000000 +0200
@@ -68,7 +68,7 @@
 
 System
 ======
-* ipmitool from https://sourceforge.net/projects/ipmitool/
+* ipmitool from https://codeberg.org/IPMITool/ipmitool
 * pciutils
 * lshw from http://www.ezix.org/project/wiki/HardwareLiSter
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/babel.cfg 
new/hardware-0.33.0/babel.cfg
--- old/hardware-0.32.0/babel.cfg       2022-12-14 12:38:12.000000000 +0100
+++ new/hardware-0.33.0/babel.cfg       1970-01-01 01:00:00.000000000 +0100
@@ -1,2 +0,0 @@
-[python: **.py]
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/doc/requirements.txt 
new/hardware-0.33.0/doc/requirements.txt
--- old/hardware-0.32.0/doc/requirements.txt    2022-12-14 12:38:12.000000000 
+0100
+++ new/hardware-0.33.0/doc/requirements.txt    2026-09-24 14:44:48.000000000 
+0200
@@ -1 +1 @@
-sphinx!=1.6.6,!=1.6.7,!=2.1.0,>=1.6.2;python_version>='3.4' # BSD
\ No newline at end of file
+sphinx>=2.0.0 # BSD
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/hardware/cmdb.py 
new/hardware-0.33.0/hardware/cmdb.py
--- old/hardware-0.32.0/hardware/cmdb.py        2022-12-14 12:38:12.000000000 
+0100
+++ new/hardware-0.33.0/hardware/cmdb.py        2026-09-24 14:45:16.000000000 
+0200
@@ -12,6 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import ast
 import errno
 import logging
 import os
@@ -36,12 +37,17 @@
     'Load the cmdb.'
     filename = cmdb_filename(cfg_dir, name)
     try:
-        return eval(open(filename).read(-1))
+        with open(filename, 'r') as cmdb_file:
+            content = cmdb_file.read()
+            return ast.literal_eval(content)
     except IOError as xcpt:
         if xcpt.errno != errno.ENOENT:
             LOG.error("exception while processing CMDB (%s) %s" % (filename,
                                                                    str(xcpt)))
         return None
+    except (ValueError, SyntaxError) as e:
+        LOG.error('Invalid CMDB file format %s: %s', filename, e)
+        return None
 
 
 def save_cmdb(cfg_dir, name, cmdb):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/hardware/detect_utils.py 
new/hardware-0.33.0/hardware/detect_utils.py
--- old/hardware-0.32.0/hardware/detect_utils.py        2022-12-14 
12:38:12.000000000 +0100
+++ new/hardware-0.33.0/hardware/detect_utils.py        2026-09-24 
14:44:48.000000000 +0200
@@ -270,36 +270,43 @@
                 break
 
 
-def get_cpus(hw_lst):
-    def _maybe_int(v):
-        try:
-            base = 10
-            if 'x' in v:
-                base = 16
-            v = int(v, base)
-        except Exception:
-            pass
-        return v
+def _maybe_int(v):
+    """Convert value to int with hex support, return original if fails."""
+    try:
+        base = 10
+        if 'x' in v:
+            base = 16
+        v = int(v, base)
+    except Exception:
+        pass
+    return v
+
+
+def _get_governor(lcpu):
+    """Return the scaling governor of a logical core.
 
-    def _get_governor(lcpu):
-        """Return the scaling governor of a logical core.
+    :param lcpu: the logical core number
+    :returns: the scaling governor if it exists, otherwise None
+    """
+    with contextlib.suppress(IOError):
+        file_name = ("/sys/devices/system/cpu/cpufreq/"
+                     "policy{}/scaling_governor".format(lcpu))
+        return from_file(file_name)
+
+    # fallback to the old interface available in kernels < 4.3;
+    with contextlib.suppress(IOError):
+        file_name = ("/sys/devices/system/cpu/cpu{}/cpufreq/"
+                     "scaling_governor".format(lcpu))
+        return from_file(file_name)
+    return None
 
-        :param lcpu: the logical core number
-        :returns: the scaling governor if it exists, otherwise None
-        """
-        with contextlib.suppress(IOError):
-            file_name = ("/sys/devices/system/cpu/cpufreq/"
-                         "policy{}/scaling_governor".format(lcpu))
-            return from_file(file_name)
-
-        # fallback to the old interface available in kernels < 4.3;
-        with contextlib.suppress(IOError):
-            file_name = ("/sys/devices/system/cpu/cpu{}/cpufreq/"
-                         "scaling_governor".format(lcpu))
-            return from_file(file_name)
-        return None
 
-    # Extracting lspcu information
+def _parse_lscpu_output():
+    """Parse lscpu command output into dictionaries.
+
+    :returns: tuple of (lscpu_dict, lscpux_dict) containing parsed output
+    """
+    # Parse basic lscpu information
     lscpu = {}
     output = output_lines('LANG=en_US.UTF-8 lscpu')
 
@@ -308,8 +315,7 @@
             item, value = line.split(':', 1)
             lscpu[item.strip(':')] = value.strip()
 
-    # Extracting lspcu -x information
-    # Use hexadecimal masks for CPU sets
+    # Parse extended lscpu -x information with hexadecimal masks
     lscpux = {}
     output = output_lines('LANG=en_US.UTF-8 lscpu -x')
 
@@ -318,14 +324,27 @@
             item, value = line.split(':', 1)
             lscpux[item.strip(':')] = value.strip()
 
+    return lscpu, lscpux
+
+
+def _detect_physical_cpus(hw_lst, lscpu):
+    """Detect and append physical CPU information.
+
+    :param hw_lst: hardware list to append CPU info to
+    :param lscpu: parsed lscpu output dictionary
+    """
     hw_lst.append(("cpu", "physical", "number", int(lscpu["Socket(s)"])))
 
+    # SMT (Simultaneous Multi-Threading) control
     with contextlib.suppress(IOError):
         value = from_file("/sys/devices/system/cpu/smt/control")
         hw_lst.append(("cpu", "physical", "smt", value))
 
+    # Process each physical processor socket
     for processor in range(int(lscpu["Socket(s)"])):
         ptag = "physical_{}".format(processor)
+
+        # CPU boost setting
         try:
             value = from_file("/sys/devices/system/cpu/cpufreq/boost")
         except IOError:
@@ -334,37 +353,51 @@
             value = 'enabled' if value == '1' else 'disabled'
             hw_lst.append(('cpu', ptag, 'boost', value))
 
-        for (t_key, d_key, conv) in [('vendor', 'Vendor ID', None),
-                                     ('product', 'Model name', None),
-                                     ('cores', 'Core(s) per socket', int),
-                                     ('threads', None, None),
-                                     ('family', 'CPU family', int),
-                                     ('model', 'Model', _maybe_int),
-                                     ('stepping', 'Stepping', _maybe_int),
-                                     ('architecture', 'Architecture', None),
-                                     ('l1d cache', 'L1d cache', None),
-                                     ('l1i cache', 'L1i cache', None),
-                                     ('l2 cache', 'L2 cache', None),
-                                     ('l3 cache', 'L3 cache', None),
-                                     ('min_Mhz', 'CPU min MHz', float),
-                                     ('max_Mhz', 'CPU max MHz', float),
-                                     ('current_Mhz', 'CPU MHz', float),
-                                     ('flags', 'Flags', None),
-                                     ('threads_per_core', 'Thread(s) per core',
-                                      int)]:
+        # CPU properties mapping: (hw_key, lscpu_key, converter_func)
+        cpu_properties = [
+            ('vendor', 'Vendor ID', None),
+            ('product', 'Model name', None),
+            ('cores', 'Core(s) per socket', int),
+            ('threads', None, None),  # Special case: calculated below
+            ('family', 'CPU family', int),
+            ('model', 'Model', _maybe_int),
+            ('stepping', 'Stepping', _maybe_int),
+            ('architecture', 'Architecture', None),
+            ('l1d cache', 'L1d cache', None),
+            ('l1i cache', 'L1i cache', None),
+            ('l2 cache', 'L2 cache', None),
+            ('l3 cache', 'L3 cache', None),
+            ('min_Mhz', 'CPU min MHz', float),
+            ('max_Mhz', 'CPU max MHz', float),
+            ('current_Mhz', 'CPU MHz', float),
+            ('flags', 'Flags', None),
+            ('threads_per_core', 'Thread(s) per core', int)
+        ]
+
+        for (t_key, d_key, conv) in cpu_properties:
             value = None
-            if d_key in lscpu:
+            if d_key and d_key in lscpu:
                 value = lscpu[d_key]
                 if conv:
                     value = conv(value)
             elif t_key == 'threads':
+                # Calculate total threads per socket
                 value = (int(lscpu.get('Thread(s) per core', 1))
                          * int(lscpu.get('Core(s) per socket', 1)))
+
             if value is not None:
                 hw_lst.append(('cpu', ptag, t_key, value))
 
+
+def _detect_logical_cpus(hw_lst, lscpu):
+    """Detect and append logical CPU information including governors.
+
+    :param hw_lst: hardware list to append CPU info to
+    :param lscpu: parsed lscpu output dictionary
+    """
     hw_lst.append(('cpu', 'logical', 'number', int(lscpu['CPU(s)'])))
-    # Governors could be different on logical cpus
+
+    # Check governor for each logical CPU (can be different per CPU)
     for cpu in range(int(lscpu['CPU(s)'])):
         ltag = "logical_{}".format(cpu)
 
@@ -372,49 +405,68 @@
         if governor is not None:
             hw_lst.append(('cpu', ltag, "governor", governor))
 
-    # Extracting numa nodes
+
+def _detect_numa_nodes(hw_lst, lscpu, lscpux):
+    """Detect and append NUMA node information.
+
+    :param hw_lst: hardware list to append NUMA info to
+    :param lscpu: parsed lscpu output dictionary
+    :param lscpux: parsed lscpu -x output dictionary
+    """
+    # Overall NUMA node count
     try:
         hw_lst.append(('numa', 'nodes', 'count', int(lscpu['NUMA node(s)'])))
     except KeyError:
         pass
 
-    # Allow for sparse numa nodes.
+    # Process individual NUMA nodes (handle sparse node numbering)
     numa_nodes = []
     for key in lscpux:
         match = re.match(r"NUMA node(\d+) CPU\(s\)", key)
         if match:
             numa_nodes.append((key, int(match.groups()[0])))
-    # NOTE(tonyb): Explicitly sort the list as prior to python 3.7? keys() did
-    # not have a predictable ordering and there maybe consumers of hw_lst rely
-    # on that.
+
+    # Sort nodes by ID for consistent ordering (ensures reproducible output)
     numa_nodes.sort(key=lambda t: t[1])
+
     for (key, node_id) in numa_nodes:
         ntag = 'node_{}'.format(node_id)
         cpus = lscpu[key]
-        # lscpu -x provides the cpu mask
-        cpu_mask = lscpux[key]
+        cpu_mask = lscpux[key]  # Hexadecimal CPU mask from lscpu -x
         total_cpus = 0
-        min_cpu = None
-        max_cpu = None
 
-        # It's possible to have a NUMA node without any CPUs
+        # Parse CPU range/list (e.g., "0-5,48-53" or "0,1,2")
         if cpus:
             for item in cpus.split(','):
-                # lscpu report numa nodes like 0-5,48-53
                 if "-" in item:
-                    max_cpu = int(item.split("-")[1])
-                    min_cpu = int(item.split("-")[0])
-                    total_cpus = total_cpus + max_cpu - min_cpu + 1
+                    # Handle ranges like "0-5"
+                    start, end = item.split("-")
+                    total_cpus += int(end) - int(start) + 1
                 else:
-                    # or like 0,1
-                    # As we don't have dashes, there is only one core to count
-                    total_cpus = total_cpus + 1
+                    # Handle individual CPUs like "0"
+                    total_cpus += 1
 
-        # total_cpus = 12 for "0-5,48-53"
         hw_lst.append(('numa', ntag, 'cpu_count', total_cpus))
         hw_lst.append(('numa', ntag, 'cpu_mask', cpu_mask))
 
 
+def get_cpus(hw_lst):
+    """Detect and append CPU and NUMA information to hardware list.
+
+    This function orchestrates CPU detection by calling focused sub-functions
+    for different aspects of CPU hardware detection.
+
+    :param hw_lst: list to append hardware tuples to
+    """
+    # Parse lscpu output once and reuse for all detection functions
+    lscpu, lscpux = _parse_lscpu_output()
+
+    # Detect different aspects of CPU hardware
+    _detect_physical_cpus(hw_lst, lscpu)
+    _detect_logical_cpus(hw_lst, lscpu)
+    _detect_numa_nodes(hw_lst, lscpu, lscpux)
+
+
 def modprobe(module):
     """Load a kernel module using modprobe."""
     status, _ = cmd('modprobe %s' % module)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/hardware/state.py 
new/hardware-0.33.0/hardware/state.py
--- old/hardware-0.32.0/hardware/state.py       2022-12-14 12:38:12.000000000 
+0100
+++ new/hardware-0.33.0/hardware/state.py       2026-09-24 14:53:49.000000000 
+0200
@@ -23,6 +23,7 @@
 profile and matches only 3 times then try hw2 any number of times.
 '''
 
+import ast
 import errno
 import logging
 import os
@@ -57,7 +58,20 @@
         self._validate_lockname()
         self.lock()
         LOG.info('Reading state from %s' % self._state_filename)
-        self._data = eval(open(self._state_filename).read(-1))
+        try:
+            with open(self._state_filename, 'r') as state_file:
+                content = state_file.read()
+                self._data = ast.literal_eval(content)
+        except (OSError, IOError) as e:
+            LOG.error('Failed to read state file %s: %s',
+                      self._state_filename, e)
+            raise StateError('Cannot read state file %s: %s' %
+                             (self._state_filename, e))
+        except (ValueError, SyntaxError) as e:
+            LOG.error('Invalid state file format %s: %s',
+                      self._state_filename, e)
+            raise StateError('Invalid state file format %s: %s' %
+                             (self._state_filename, e))
 
     def failed_profile(self, prof):
         '''If we get a failure report, let's reincrement the counter
@@ -95,7 +109,16 @@
         if self._cfg_dir:
             fname = os.path.join(self._cfg_dir, name + '.specs')
             if os.path.exists(fname):
-                return eval(open(fname, 'r').read(-1))
+                try:
+                    with open(fname, 'r') as specs_file:
+                        content = specs_file.read()
+                        return ast.literal_eval(content)
+                except (OSError, IOError) as e:
+                    LOG.error('Failed to read specs file %s: %s', fname, e)
+                    return _INVALID_SPECS
+                except (ValueError, SyntaxError) as e:
+                    LOG.error('Invalid specs file format %s: %s', fname, e)
+                    return _INVALID_SPECS
 
             LOG.info('Specs file %s not found' % fname)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/hardware/tests/test_cmdb.py 
new/hardware-0.33.0/hardware/tests/test_cmdb.py
--- old/hardware-0.32.0/hardware/tests/test_cmdb.py     2022-12-14 
12:38:12.000000000 +0100
+++ new/hardware-0.33.0/hardware/tests/test_cmdb.py     2026-09-24 
14:53:49.000000000 +0200
@@ -12,6 +12,9 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
+import os
+import shutil
+import tempfile
 import unittest
 
 from hardware import cmdb
@@ -57,6 +60,61 @@
         self.assertRaises(cmdb.CmdbError, cmdb.update_cmdb,
                           data, var, var, True)
 
+    def test_load_cmdb_valid_file(self):
+        """Test loading a valid CMDB file with safe parsing."""
+        tmpdir = tempfile.mkdtemp()
+        try:
+            # Create a valid CMDB file
+            valid_data = [{'hostname': 'test1', 'mac': 'aa:bb:cc'},
+                          {'hostname': 'test2', 'used': 1}]
+            cmdb_file = os.path.join(tmpdir, 'test.cmdb')
+            with open(cmdb_file, 'w') as f:
+                import pprint
+                pprint.pprint(valid_data, stream=f)
+
+            result = cmdb.load_cmdb(tmpdir, 'test')
+            self.assertEqual(result, valid_data)
+        finally:
+            shutil.rmtree(tmpdir)
+
+    def test_load_cmdb_invalid_file_syntax(self):
+        """Test that invalid Python syntax in CMDB file returns None."""
+        tmpdir = tempfile.mkdtemp()
+        try:
+            cmdb_file = os.path.join(tmpdir, 'test.cmdb')
+            # Create an invalid CMDB file with syntax errors
+            with open(cmdb_file, 'w') as f:
+                f.write('[{"hostname": "test1", "mac":}]')  # Missing value
+
+            result = cmdb.load_cmdb(tmpdir, 'test')
+            self.assertIsNone(result)
+        finally:
+            shutil.rmtree(tmpdir)
+
+    def test_load_cmdb_malicious_file(self):
+        """Test that malicious code in CMDB file cannot be executed."""
+        tmpdir = tempfile.mkdtemp()
+        try:
+            cmdb_file = os.path.join(tmpdir, 'test.cmdb')
+            # Create a CMDB file with malicious code
+            malicious_code = '__import__("os").system("echo CMDB_BREACH")'
+            with open(cmdb_file, 'w') as f:
+                # Write malicious data that would be executed with eval()
+                exploit_entry = (f'{{"hostname": "test1", '
+                                 f'"exploit": {malicious_code}}}')
+                f.write(f'[{exploit_entry}]')
+
+            result = cmdb.load_cmdb(tmpdir, 'test')
+            # Should return None instead of executing malicious code
+            self.assertIsNone(result)
+        finally:
+            shutil.rmtree(tmpdir)
+
+    def test_load_cmdb_nonexistent_file(self):
+        """Test loading nonexistent CMDB file returns None."""
+        result = cmdb.load_cmdb('/nonexistent/directory', 'test')
+        self.assertIsNone(result)
+
 
 if __name__ == "__main__":
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/hardware.egg-info/PKG-INFO 
new/hardware-0.33.0/hardware.egg-info/PKG-INFO
--- old/hardware-0.32.0/hardware.egg-info/PKG-INFO      2024-11-19 
14:57:03.000000000 +0100
+++ new/hardware-0.33.0/hardware.egg-info/PKG-INFO      2026-09-24 
15:03:41.000000000 +0200
@@ -1,6 +1,6 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.4
 Name: hardware
-Version: 0.32.0
+Version: 0.33.0
 Summary: Hardware detection and classification utilities
 Home-page: https://github.com/redhat-cip/hardware
 Author: eNovance
@@ -13,16 +13,25 @@
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3
 Classifier: Programming Language :: Python :: 3 :: Only
-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
-Requires-Python: >=3.8
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Requires-Python: >=3.10
 License-File: LICENSE
 License-File: AUTHORS
 Requires-Dist: pbr>=3.1.1
 Requires-Dist: pexpect
+Dynamic: author
+Dynamic: author-email
+Dynamic: classifier
+Dynamic: description
+Dynamic: home-page
+Dynamic: license-file
+Dynamic: requires-dist
+Dynamic: requires-python
+Dynamic: summary
 
 ===============================
 hardware
@@ -94,7 +103,7 @@
 
 System
 ======
-* ipmitool from https://sourceforge.net/projects/ipmitool/
+* ipmitool from https://codeberg.org/IPMITool/ipmitool
 * pciutils
 * lshw from http://www.ezix.org/project/wiki/HardwareLiSter
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/hardware.egg-info/SOURCES.txt 
new/hardware-0.33.0/hardware.egg-info/SOURCES.txt
--- old/hardware-0.32.0/hardware.egg-info/SOURCES.txt   2024-11-19 
14:57:03.000000000 +0100
+++ new/hardware-0.33.0/hardware.egg-info/SOURCES.txt   2026-09-24 
15:03:42.000000000 +0200
@@ -8,7 +8,7 @@
 LICENSE
 MANIFEST.in
 README.rst
-babel.cfg
+pyproject.toml
 requirements.txt
 setup.cfg
 setup.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/hardware.egg-info/pbr.json 
new/hardware-0.33.0/hardware.egg-info/pbr.json
--- old/hardware-0.32.0/hardware.egg-info/pbr.json      2024-11-19 
14:57:03.000000000 +0100
+++ new/hardware-0.33.0/hardware.egg-info/pbr.json      2026-09-24 
15:03:41.000000000 +0200
@@ -1 +1 @@
-{"git_version": "93c555b8", "is_release": false}
\ No newline at end of file
+{"git_version": "2270d2d0", "is_release": false}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/pyproject.toml 
new/hardware-0.33.0/pyproject.toml
--- old/hardware-0.32.0/pyproject.toml  1970-01-01 01:00:00.000000000 +0100
+++ new/hardware-0.33.0/pyproject.toml  2026-09-24 14:44:48.000000000 +0200
@@ -0,0 +1,4 @@
+[build-system]
+requires = ["pbr>=6.0.0", "setuptools>=64.0.0"]
+build-backend = "pbr.build"
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/setup.cfg 
new/hardware-0.33.0/setup.cfg
--- old/hardware-0.32.0/setup.cfg       2024-11-19 14:57:03.821579200 +0100
+++ new/hardware-0.33.0/setup.cfg       2026-09-24 15:03:42.303144700 +0200
@@ -6,7 +6,7 @@
 author = eNovance
 author_email = [email protected]
 home_page = https://github.com/redhat-cip/hardware
-python_requires = >=3.8
+python_requires = >=3.10
 classifier = 
        Topic :: System :: Hardware
        Intended Audience :: Information Technology
@@ -16,38 +16,16 @@
        Programming Language :: Python
        Programming Language :: Python :: 3
        Programming Language :: Python :: 3 :: Only
-       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.13
+       Programming Language :: Python :: 3.14
 
 [files]
 packages = 
        hardware
 
-[build_sphinx]
-source-dir = doc/source
-build-dir = doc/build
-all_files = 1
-
-[upload_sphinx]
-upload-dir = doc/build/html
-
-[compile_catalog]
-directory = hardware/locale
-domain = hardware
-
-[update_catalog]
-domain = hardware
-output_dir = hardware/locale
-input_file = hardware/locale/hardware.pot
-
-[extract_messages]
-keywords = _ gettext ngettext l_ lazy_gettext
-mapping_file = babel.cfg
-output_file = hardware/locale/hardware.pot
-
 [entry_points]
 console_scripts = 
        hardware-detect = hardware.detect:main
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/hardware-0.32.0/tox.ini new/hardware-0.33.0/tox.ini
--- old/hardware-0.32.0/tox.ini 2024-06-20 10:23:00.000000000 +0200
+++ new/hardware-0.33.0/tox.ini 2026-09-24 14:44:48.000000000 +0200
@@ -1,6 +1,6 @@
 [tox]
 minversion = 4.4.0
-envlist = py38,py39,py310,py311,py312,pep8,cover,docs
+envlist = py310,py311,py312,py313,py314,pep8,cover,docs
 skipsdist = True
 ignore_basepython_conflict=true
 
@@ -22,8 +22,8 @@
 
 [testenv:pep8]
 deps=
-    hacking~=6.1.0 # Apache-2.0
-    flake8-import-order~=0.18.0 # LGPLv3
+    hacking~=8.1.0 # Apache-2.0
+    flake8-import-order~=0.19.0 # LGPLv3
     pycodestyle>=2.0.0 # MIT
 commands = flake8 {posargs}
 
@@ -36,7 +36,7 @@
 [testenv:cover]
 deps=
     {[testenv]deps}
-    coverage!=4.4,>=4.0 # Apache-2.0
+    coverage>=4.4 # Apache-2.0
 setenv =
     VIRTUAL_ENV={envdir}
     PYTHON=coverage run --source hardware --omit='*tests*' --parallel-mode

Reply via email to