Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-tablib for openSUSE:Factory 
checked in at 2026-08-17 16:59:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-tablib (Old)
 and      /work/SRC/openSUSE:Factory/.python-tablib.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-tablib"

Mon Aug 17 16:59:54 2026 rev:17 rq:1371570 version:3.10.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-tablib/python-tablib.changes      
2025-10-29 21:10:49.121122205 +0100
+++ /work/SRC/openSUSE:Factory/.python-tablib.new.1258/python-tablib.changes    
2026-08-17 17:06:39.361263145 +0200
@@ -1,0 +2,19 @@
+Fri Aug 14 11:23:40 UTC 2026 - Nico Krapp <[email protected]>
+
+- Update to 3.10.0 (fixes CVE-2026-9318, bsc#1274960)
+  * Escape dataset title in HTML serialization (#668) @claudep
+  * Add support for Python 3.15 (#667) @hugovk
+  * Use lazy imports on Python 3.15 to improve startup speed (#645) @hugovk
+  * Remove #yaml tag from generic error message URL (#641) @mgrdcm
+  * Drop support for Python 3.9 (#629) @hugovk
+  * Convert XLS cell values when importing a Databook (#665) @vineethsaivs
+  * Fix TypeError when stacking two headerless Datasets with stack_cols
+    (#651) @vineethsaivs
+  * Fix ODS export of boolean values crashing on re-import (#656) @vineethsaivs
+  * Only strip the leading '=' when escaping XLSX formulae (#658) @vidigoat
+  * Raise TypeError for invalid Dataset instance (#649) @koteshyelamati
+  * Raise HeadersNeeded instead of TypeError when subscripting a headerless
+    Dataset by column name (#648) @gaoflow
+  * Preserve column order in YAML export (#287) (#644) @vineethsaivs
+
+-------------------------------------------------------------------

Old:
----
  tablib-3.9.0.tar.gz

New:
----
  tablib-3.10.0.tar.gz

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

Other differences:
------------------
++++++ python-tablib.spec ++++++
--- /var/tmp/diff_new_pack.2nu291/_old  2026-08-17 17:06:40.148290903 +0200
+++ /var/tmp/diff_new_pack.2nu291/_new  2026-08-17 17:06:40.151291009 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-tablib
 #
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 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
@@ -22,7 +22,7 @@
 %endif
 %{?sle15_python_module_pythons}
 Name:           python-tablib
-Version:        3.9.0
+Version:        3.10.0
 Release:        0
 Summary:        Format agnostic tabular data library (XLS, JSON, YAML, CSV)
 License:        MIT

++++++ tablib-3.9.0.tar.gz -> tablib-3.10.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/.github/workflows/docs-lint.yml 
new/tablib-3.10.0/.github/workflows/docs-lint.yml
--- old/tablib-3.9.0/.github/workflows/docs-lint.yml    2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/.github/workflows/docs-lint.yml   2026-07-31 
19:25:57.000000000 +0200
@@ -19,10 +19,10 @@
         - TOXENV: lint
 
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
 
       - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v7
         with:
           python-version: "3.x"
           cache: pip
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/.github/workflows/release-drafter.yml 
new/tablib-3.10.0/.github/workflows/release-drafter.yml
--- old/tablib-3.9.0/.github/workflows/release-drafter.yml      2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/.github/workflows/release-drafter.yml     2026-07-31 
19:25:57.000000000 +0200
@@ -26,6 +26,6 @@
     runs-on: ubuntu-latest
     steps:
       # Drafts your next release notes as pull requests are merged into "main"
-      - uses: release-drafter/release-drafter@v6
+      - uses: release-drafter/release-drafter@v7
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/.github/workflows/release.yml 
new/tablib-3.10.0/.github/workflows/release.yml
--- old/tablib-3.9.0/.github/workflows/release.yml      2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/.github/workflows/release.yml     2026-07-31 
19:25:57.000000000 +0200
@@ -15,12 +15,12 @@
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@v7
         with:
           fetch-depth: 0
 
       - name: Set up Python
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@v7
         with:
           python-version: "3.x"
           cache: pip
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/.github/workflows/test.yml 
new/tablib-3.10.0/.github/workflows/test.yml
--- old/tablib-3.9.0/.github/workflows/test.yml 2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/.github/workflows/test.yml        2026-07-31 
19:25:57.000000000 +0200
@@ -15,14 +15,14 @@
     strategy:
       fail-fast: false
       matrix:
-        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
+        python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.15"]
         os: [ubuntu-latest, macOS-latest, windows-latest]
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v7
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@v7
       with:
         python-version: ${{ matrix.python-version }}
         allow-prereleases: true
@@ -41,6 +41,6 @@
         tox -e py
 
     - name: Upload coverage
-      uses: codecov/codecov-action@v3
+      uses: codecov/codecov-action@v7
       with:
         name: ${{ matrix.os }} Python ${{ matrix.python-version }}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/.pre-commit-config.yaml 
new/tablib-3.10.0/.pre-commit-config.yaml
--- old/tablib-3.9.0/.pre-commit-config.yaml    2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/.pre-commit-config.yaml   2026-07-31 19:25:57.000000000 
+0200
@@ -1,15 +1,10 @@
 repos:
   - repo: https://github.com/astral-sh/ruff-pre-commit
-    rev: v0.13.3
+    rev: v0.15.20
     hooks:
       - id: ruff-check
         args: [--exit-non-zero-on-fix]
 
-  - repo: https://github.com/isidentical/teyit
-    rev: 0.4.3
-    hooks:
-      -   id: teyit
-
   - repo: https://github.com/pre-commit/pygrep-hooks
     rev: v1.10.0
     hooks:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/.readthedocs.yaml 
new/tablib-3.10.0/.readthedocs.yaml
--- old/tablib-3.9.0/.readthedocs.yaml  2025-10-15 20:19:53.000000000 +0200
+++ new/tablib-3.10.0/.readthedocs.yaml 2026-07-31 19:25:57.000000000 +0200
@@ -1,9 +1,9 @@
 version: 2
 
 build:
-  os: ubuntu-20.04
+  os: ubuntu-26.04
   tools:
-    python: "3.10"
+    python: "3.14"
 
 sphinx:
   configuration: docs/conf.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/AUTHORS new/tablib-3.10.0/AUTHORS
--- old/tablib-3.9.0/AUTHORS    2025-10-15 20:19:53.000000000 +0200
+++ new/tablib-3.10.0/AUTHORS   2026-07-31 19:25:57.000000000 +0200
@@ -38,4 +38,6 @@
     Tommy Anthony
     Tsuyoshi Hombashi
     Tushar Makkar
+    Vidit Patankar
+    Vineeth Sai
     Yunis Yilmaz
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/PKG-INFO new/tablib-3.10.0/PKG-INFO
--- old/tablib-3.9.0/PKG-INFO   2025-10-15 20:20:09.751117500 +0200
+++ new/tablib-3.10.0/PKG-INFO  2026-07-31 19:26:15.178299000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: tablib
-Version: 3.9.0
+Version: 3.10.0
 Summary: Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)
 Author-email: Kenneth Reitz <[email protected]>
 Maintainer: Hugo van Kemenade
@@ -17,13 +17,13 @@
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: 3
-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.13
 Classifier: Programming Language :: Python :: 3.14
-Requires-Python: >=3.9
+Classifier: Programming Language :: Python :: 3.15
+Requires-Python: >=3.10
 Description-Content-Type: text/markdown
 License-File: LICENSE
 License-File: AUTHORS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/docs/requirements.txt 
new/tablib-3.10.0/docs/requirements.txt
--- old/tablib-3.9.0/docs/requirements.txt      2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/docs/requirements.txt     2026-07-31 19:25:57.000000000 
+0200
@@ -1 +1 @@
-sphinx==6.1.3
+sphinx==9.1.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/pyproject.toml 
new/tablib-3.10.0/pyproject.toml
--- old/tablib-3.9.0/pyproject.toml     2025-10-15 20:19:53.000000000 +0200
+++ new/tablib-3.10.0/pyproject.toml    2026-07-31 19:25:57.000000000 +0200
@@ -15,7 +15,7 @@
     {name = "Hugo van Kemenade"},
     {name = "Claude Paroz", email = "[email protected]"},
 ]
-requires-python = ">=3.9"
+requires-python = ">=3.10"
 classifiers = [
     "Development Status :: 5 - Production/Stable",
     "Intended Audience :: Developers",
@@ -24,12 +24,12 @@
     "Programming Language :: Python",
     "Programming Language :: Python :: 3 :: Only",
     "Programming Language :: Python :: 3",
-    "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",
+    "Programming Language :: Python :: 3.15",
 ]
 dynamic = ["version"]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/__init__.py 
new/tablib-3.10.0/src/tablib/__init__.py
--- old/tablib-3.9.0/src/tablib/__init__.py     2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/__init__.py    2026-07-31 19:25:57.000000000 
+0200
@@ -1,4 +1,7 @@
 """ Tablib. """
+
+__lazy_modules__ = {"tablib.core"}
+
 try:
     # Generated by setuptools-scm.
     from ._version import version as __version__
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/_version.py 
new/tablib-3.10.0/src/tablib/_version.py
--- old/tablib-3.9.0/src/tablib/_version.py     2025-10-15 20:20:09.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/_version.py    2026-07-31 19:26:15.000000000 
+0200
@@ -1,5 +1,6 @@
-# file generated by setuptools-scm
+# file generated by vcs-versioning
 # don't change, don't track in version control
+from __future__ import annotations
 
 __all__ = [
     "__version__",
@@ -10,25 +11,14 @@
     "commit_id",
 ]
 
-TYPE_CHECKING = False
-if TYPE_CHECKING:
-    from typing import Tuple
-    from typing import Union
-
-    VERSION_TUPLE = Tuple[Union[int, str], ...]
-    COMMIT_ID = Union[str, None]
-else:
-    VERSION_TUPLE = object
-    COMMIT_ID = object
-
 version: str
 __version__: str
-__version_tuple__: VERSION_TUPLE
-version_tuple: VERSION_TUPLE
-commit_id: COMMIT_ID
-__commit_id__: COMMIT_ID
+__version_tuple__: tuple[int | str, ...]
+version_tuple: tuple[int | str, ...]
+commit_id: str | None
+__commit_id__: str | None
 
-__version__ = version = '3.9.0'
-__version_tuple__ = version_tuple = (3, 9, 0)
+__version__ = version = '3.10.0'
+__version_tuple__ = version_tuple = (3, 10, 0)
 
-__commit_id__ = commit_id = 'gbd2ea7ab8'
+__commit_id__ = commit_id = 'gb0ff39fb9'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/core.py 
new/tablib-3.10.0/src/tablib/core.py
--- old/tablib-3.9.0/src/tablib/core.py 2025-10-15 20:19:53.000000000 +0200
+++ new/tablib-3.10.0/src/tablib/core.py        2026-07-31 19:25:57.000000000 
+0200
@@ -8,6 +8,13 @@
     :license: MIT, see LICENSE for more details.
 """
 
+__lazy_modules__ = {
+    "copy",
+    "operator",
+    "tablib.exceptions",
+    "tablib.utils",
+}
+
 from copy import copy
 from operator import itemgetter
 
@@ -169,6 +176,8 @@
 
     def __getitem__(self, key):
         if isinstance(key, str):
+            if self.headers is None:
+                raise HeadersNeeded()
             if key in self.headers:
                 pos = self.headers.index(key)  # get 'key' index from each data
                 return [row[pos] for row in self._data]
@@ -187,6 +196,8 @@
 
     def __delitem__(self, key):
         if isinstance(key, str):
+            if self.headers is None:
+                raise HeadersNeeded()
 
             if key in self.headers:
 
@@ -338,7 +349,7 @@
         """
         error_details = (
             "Please check format documentation "
-            "https://tablib.readthedocs.io/en/stable/formats.html#yaml";
+            "https://tablib.readthedocs.io/en/stable/formats.html";
         )
 
         if not pickle:
@@ -739,7 +750,7 @@
         ``Dataset`` instance."""
 
         if not isinstance(other, Dataset):
-            return
+            raise TypeError("'other' must be a Dataset instance")
 
         if self.width != other.width:
             raise InvalidDimensions
@@ -762,7 +773,7 @@
         has headers set, than the other must as well."""
 
         if not isinstance(other, Dataset):
-            return
+            raise TypeError("'other' must be a Dataset instance")
 
         if self.headers or other.headers:
             if not self.headers or not other.headers:
@@ -778,11 +789,18 @@
 
         _dset = Dataset()
 
-        for column in self.headers:
-            _dset.append_col(col=self[column])
+        if self.headers:
+            for column in self.headers:
+                _dset.append_col(col=self[column])
+
+            for column in other.headers:
+                _dset.append_col(col=other[column])
+        else:
+            for index in range(self.width):
+                _dset.append_col(col=self.get_col(index))
 
-        for column in other.headers:
-            _dset.append_col(col=other[column])
+            for index in range(other.width):
+                _dset.append_col(col=other.get_col(index))
 
         _dset.headers = new_headers
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/__init__.py 
new/tablib-3.10.0/src/tablib/formats/__init__.py
--- old/tablib-3.9.0/src/tablib/formats/__init__.py     2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/src/tablib/formats/__init__.py    2026-07-31 
19:25:57.000000000 +0200
@@ -1,5 +1,17 @@
 """ Tablib - formats
 """
+
+__lazy_modules__ = {
+    "functools",
+    "importlib",
+    "importlib.util",
+    "tablib.exceptions",
+    "tablib.formats._csv",
+    "tablib.formats._json",
+    "tablib.formats._tsv",
+    "tablib.utils",
+}
+
 from functools import partialmethod
 from importlib import import_module
 from importlib.util import find_spec
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_cli.py 
new/tablib-3.10.0/src/tablib/formats/_cli.py
--- old/tablib-3.9.0/src/tablib/formats/_cli.py 2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/formats/_cli.py        2026-07-31 
19:25:57.000000000 +0200
@@ -3,6 +3,9 @@
    Generates a representation for CLI from the dataset.
    Wrapper for tabulate library.
 """
+
+__lazy_modules__ = {"tabulate"}
+
 from tabulate import tabulate as Tabulate
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_csv.py 
new/tablib-3.10.0/src/tablib/formats/_csv.py
--- old/tablib-3.9.0/src/tablib/formats/_csv.py 2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/formats/_csv.py        2026-07-31 
19:25:57.000000000 +0200
@@ -1,6 +1,8 @@
 """ Tablib - *SV Support.
 """
 
+__lazy_modules__ = {"csv", "io"}
+
 import csv
 from io import StringIO
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_dbf.py 
new/tablib-3.10.0/src/tablib/formats/_dbf.py
--- old/tablib-3.9.0/src/tablib/formats/_dbf.py 2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/formats/_dbf.py        2026-07-31 
19:25:57.000000000 +0200
@@ -1,5 +1,13 @@
 """ Tablib - DBF Support.
 """
+
+__lazy_modules__ = {
+    "io",
+    "os",
+    "tablib._vendor",
+    "tempfile",
+}
+
 import io
 import os
 import tempfile
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_html.py 
new/tablib-3.10.0/src/tablib/formats/_html.py
--- old/tablib-3.9.0/src/tablib/formats/_html.py        2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/src/tablib/formats/_html.py       2026-07-31 
19:25:57.000000000 +0200
@@ -1,5 +1,8 @@
 """ Tablib - HTML export support.
 """
+
+__lazy_modules__ = {"xml", "xml.etree"}
+
 from html.parser import HTMLParser
 from xml.etree import ElementTree as ET
 
@@ -44,7 +47,9 @@
         result = ''
         for i, dset in enumerate(databook._datasets):
             title = dset.title if dset.title else f'Set {i}'
-            result += f'<{cls.BOOK_ENDINGS}>{title}</{cls.BOOK_ENDINGS}>\n'
+            title_el = ET.Element(cls.BOOK_ENDINGS)
+            title_el.text = title
+            result += ET.tostring(title_el, method='html', encoding='unicode') 
+ '\n'
             result += dset.html
             result += '\n'
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_json.py 
new/tablib-3.10.0/src/tablib/formats/_json.py
--- old/tablib-3.9.0/src/tablib/formats/_json.py        2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/src/tablib/formats/_json.py       2026-07-31 
19:25:57.000000000 +0200
@@ -1,5 +1,8 @@
 """ Tablib - JSON Support
 """
+
+__lazy_modules__ = {"decimal", "json", "uuid"}
+
 import decimal
 import json
 from uuid import UUID
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_latex.py 
new/tablib-3.10.0/src/tablib/formats/_latex.py
--- old/tablib-3.9.0/src/tablib/formats/_latex.py       2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/src/tablib/formats/_latex.py      2026-07-31 
19:25:57.000000000 +0200
@@ -2,6 +2,9 @@
 
    Generates a LaTeX booktabs-style table from the dataset.
 """
+
+__lazy_modules__ = {"re"}
+
 import re
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_ods.py 
new/tablib-3.10.0/src/tablib/formats/_ods.py
--- old/tablib-3.9.0/src/tablib/formats/_ods.py 2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/formats/_ods.py        2026-07-31 
19:25:57.000000000 +0200
@@ -1,6 +1,8 @@
 """ Tablib - ODF Support.
 """
 
+__lazy_modules__ = {"datetime", "io", "numbers"}
+
 import datetime as dt
 import numbers
 from io import BytesIO
@@ -211,7 +213,12 @@
             odf_row = table.TableRow(stylename=style)
             ws.addElement(odf_row)
             for j, col in enumerate(row):
-                if isinstance(col, numbers.Number):
+                if isinstance(col, bool):
+                    cell = table.TableCell(
+                        valuetype="boolean",
+                        booleanvalue="true" if col else "false",
+                    )
+                elif isinstance(col, numbers.Number):
                     cell = table.TableCell(valuetype="float", value=col)
                 elif isinstance(col, dt.datetime):
                     cell = table.TableCell(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_rst.py 
new/tablib-3.10.0/src/tablib/formats/_rst.py
--- old/tablib-3.9.0/src/tablib/formats/_rst.py 2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/formats/_rst.py        2026-07-31 
19:25:57.000000000 +0200
@@ -1,6 +1,8 @@
 """ Tablib - reStructuredText Support
 """
 
+__lazy_modules__ = {"itertools", "statistics", "textwrap"}
+
 from itertools import zip_longest
 from statistics import median
 from textwrap import TextWrapper
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_sql.py 
new/tablib-3.10.0/src/tablib/formats/_sql.py
--- old/tablib-3.9.0/src/tablib/formats/_sql.py 2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/formats/_sql.py        2026-07-31 
19:25:57.000000000 +0200
@@ -1,4 +1,11 @@
 """Tablib - SQL INSERT Export Support."""
+
+__lazy_modules__ = {
+    "datetime",
+    "decimal",
+    "tablib.exceptions",
+}
+
 import datetime
 import decimal
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_xls.py 
new/tablib-3.10.0/src/tablib/formats/_xls.py
--- old/tablib-3.9.0/src/tablib/formats/_xls.py 2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/formats/_xls.py        2026-07-31 
19:25:57.000000000 +0200
@@ -1,5 +1,8 @@
 """ Tablib - XLS Support.
 """
+
+__lazy_modules__ = {"datetime", "io", "xlrd", "xlrd.xldate"}
+
 import datetime
 import re
 from io import BytesIO
@@ -90,23 +93,20 @@
         return stream.getvalue()
 
     @classmethod
-    def import_set(cls, dset, in_stream, headers=True, skip_lines=0):
-        """Returns databook from XLS stream."""
-
-        dset.wipe()
+    def read_cell(cls, value, type_, datemode):
+        """Returns the Python value of a cell, based on its xlrd type."""
+        if type_ == xlrd.XL_CELL_ERROR:
+            return xlrd.error_text_from_code[value]
+        elif type_ == xlrd.XL_CELL_DATE:
+            return xldate_as_datetime(value, datemode)
+        return value
 
-        xls_book = xlrd.open_workbook(file_contents=in_stream.read())
-        sheet = xls_book.sheet_by_index(0)
+    @classmethod
+    def import_sheet(cls, dset, sheet, datemode, headers=True, skip_lines=0):
+        """Populates dataset with sheet."""
 
         dset.title = sheet.name
 
-        def cell_value(value, type_):
-            if type_ == xlrd.XL_CELL_ERROR:
-                return xlrd.error_text_from_code[value]
-            elif type_ == xlrd.XL_CELL_DATE:
-                return xldate_as_datetime(value, xls_book.datemode)
-            return value
-
         for i in range(sheet.nrows):
             if i < skip_lines:
                 continue
@@ -114,11 +114,22 @@
                 dset.headers = sheet.row_values(i)
             else:
                 dset.append([
-                    cell_value(val, typ)
+                    cls.read_cell(val, typ, datemode)
                     for val, typ in zip(sheet.row_values(i), 
sheet.row_types(i))
                 ])
 
     @classmethod
+    def import_set(cls, dset, in_stream, headers=True, skip_lines=0):
+        """Returns databook from XLS stream."""
+
+        dset.wipe()
+
+        xls_book = xlrd.open_workbook(file_contents=in_stream.read())
+        sheet = xls_book.sheet_by_index(0)
+
+        cls.import_sheet(dset, sheet, xls_book.datemode, headers, skip_lines)
+
+    @classmethod
     def import_book(cls, dbook, in_stream, headers=True):
         """Returns databook from XLS stream."""
 
@@ -127,16 +138,9 @@
         xls_book = xlrd.open_workbook(file_contents=in_stream.read())
 
         for sheet in xls_book.sheets():
-            data = tablib.Dataset()
-            data.title = sheet.name
-
-            for i in range(sheet.nrows):
-                if i == 0 and headers:
-                    data.headers = sheet.row_values(0)
-                else:
-                    data.append(sheet.row_values(i))
-
-            dbook.add_sheet(data)
+            dset = tablib.Dataset()
+            cls.import_sheet(dset, sheet, xls_book.datemode, headers)
+            dbook.add_sheet(dset)
 
     @classmethod
     def dset_sheet(cls, dataset, ws):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_xlsx.py 
new/tablib-3.10.0/src/tablib/formats/_xlsx.py
--- old/tablib-3.9.0/src/tablib/formats/_xlsx.py        2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/src/tablib/formats/_xlsx.py       2026-07-31 
19:25:57.000000000 +0200
@@ -1,5 +1,16 @@
 """ Tablib - XLSX Support.
 """
+
+__lazy_modules__ = {
+    "io",
+    "openpyxl",
+    "openpyxl.reader",
+    "openpyxl.reader.excel",
+    "openpyxl.styles",
+    "openpyxl.utils",
+    "openpyxl.workbook",
+}
+
 import re
 from io import BytesIO
 
@@ -176,7 +187,7 @@
                     cell.value = str(col)
 
                 if escape and cell.data_type == 'f' and 
cell.value.startswith('='):
-                    cell.value = cell.value.replace("=", "")
+                    cell.value = cell.value[1:]
 
     @classmethod
     def _adapt_column_width(cls, worksheet, width):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/formats/_yaml.py 
new/tablib-3.10.0/src/tablib/formats/_yaml.py
--- old/tablib-3.9.0/src/tablib/formats/_yaml.py        2025-10-15 
20:19:53.000000000 +0200
+++ new/tablib-3.10.0/src/tablib/formats/_yaml.py       2026-07-31 
19:25:57.000000000 +0200
@@ -1,6 +1,8 @@
 """ Tablib - YAML Support.
 """
 
+__lazy_modules__ = {"yaml"}
+
 import yaml
 
 import tablib
@@ -14,14 +16,20 @@
     def export_set(cls, dataset):
         """Returns YAML representation of Dataset."""
         return yaml.safe_dump(
-            dataset._package(), default_flow_style=None, allow_unicode=True
+            dataset._package(),
+            default_flow_style=None,
+            allow_unicode=True,
+            sort_keys=False,
         )
 
     @classmethod
     def export_book(cls, databook):
         """Returns YAML representation of Databook."""
         return yaml.safe_dump(
-            databook._package(), default_flow_style=None, allow_unicode=True
+            databook._package(),
+            default_flow_style=None,
+            allow_unicode=True,
+            sort_keys=False,
         )
 
     @classmethod
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib/utils.py 
new/tablib-3.10.0/src/tablib/utils.py
--- old/tablib-3.9.0/src/tablib/utils.py        2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/src/tablib/utils.py       2026-07-31 19:25:57.000000000 
+0200
@@ -1,3 +1,5 @@
+__lazy_modules__ = {"io"}
+
 from io import BytesIO, StringIO
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib.egg-info/PKG-INFO 
new/tablib-3.10.0/src/tablib.egg-info/PKG-INFO
--- old/tablib-3.9.0/src/tablib.egg-info/PKG-INFO       2025-10-15 
20:20:09.000000000 +0200
+++ new/tablib-3.10.0/src/tablib.egg-info/PKG-INFO      2026-07-31 
19:26:15.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: tablib
-Version: 3.9.0
+Version: 3.10.0
 Summary: Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)
 Author-email: Kenneth Reitz <[email protected]>
 Maintainer: Hugo van Kemenade
@@ -17,13 +17,13 @@
 Classifier: Programming Language :: Python
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: 3
-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.13
 Classifier: Programming Language :: Python :: 3.14
-Requires-Python: >=3.9
+Classifier: Programming Language :: Python :: 3.15
+Requires-Python: >=3.10
 Description-Content-Type: text/markdown
 License-File: LICENSE
 License-File: AUTHORS
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib.egg-info/SOURCES.txt 
new/tablib-3.10.0/src/tablib.egg-info/SOURCES.txt
--- old/tablib-3.9.0/src/tablib.egg-info/SOURCES.txt    2025-10-15 
20:20:09.000000000 +0200
+++ new/tablib-3.10.0/src/tablib.egg-info/SOURCES.txt   2026-07-31 
19:26:15.000000000 +0200
@@ -42,6 +42,8 @@
 src/tablib.egg-info/SOURCES.txt
 src/tablib.egg-info/dependency_links.txt
 src/tablib.egg-info/requires.txt
+src/tablib.egg-info/scm_file_list.json
+src/tablib.egg-info/scm_version.json
 src/tablib.egg-info/top_level.txt
 src/tablib/_vendor/__init__.py
 src/tablib/_vendor/dbfpy/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib.egg-info/scm_file_list.json 
new/tablib-3.10.0/src/tablib.egg-info/scm_file_list.json
--- old/tablib-3.9.0/src/tablib.egg-info/scm_file_list.json     1970-01-01 
01:00:00.000000000 +0100
+++ new/tablib-3.10.0/src/tablib.egg-info/scm_file_list.json    2026-07-31 
19:26:15.000000000 +0200
@@ -0,0 +1,81 @@
+{
+  "files": [
+    "RELEASING.md",
+    "tox.ini",
+    ".pre-commit-config.yaml",
+    "HISTORY.md",
+    "pyproject.toml",
+    ".readthedocs.yaml",
+    "CODE_OF_CONDUCT.md",
+    "LICENSE",
+    "AUTHORS",
+    "README.md",
+    "pytest.ini",
+    ".gitignore",
+    ".coveragerc",
+    ".github/release-drafter.yml",
+    ".github/CONTRIBUTING.md",
+    ".github/release.yml",
+    ".github/workflows/release-drafter.yml",
+    ".github/workflows/docs-lint.yml",
+    ".github/workflows/require-pr-label.yml",
+    ".github/workflows/release.yml",
+    ".github/workflows/test.yml",
+    "tests/requirements.txt",
+    "tests/test_tablib.py",
+    "tests/test_tablib_dbfpy_packages_fields.py",
+    "tests/test_tablib_dbfpy_packages_utils.py",
+    "tests/files/dates.xls",
+    "tests/files/bad_dimensions.xlsx",
+    "tests/files/book.ods",
+    "tests/files/ragged.xlsx",
+    "tests/files/ragged.ods",
+    "tests/files/unknown_value_type.ods",
+    "tests/files/issue_524.yaml",
+    "tests/files/xlsx_cell_values.xlsx",
+    "tests/files/founders.xlsx",
+    "tests/files/errors.xls",
+    "src/tablib/__init__.py",
+    "src/tablib/exceptions.py",
+    "src/tablib/core.py",
+    "src/tablib/utils.py",
+    "src/tablib/_vendor/__init__.py",
+    "src/tablib/_vendor/dbfpy/fields.py",
+    "src/tablib/_vendor/dbfpy/record.py",
+    "src/tablib/_vendor/dbfpy/__init__.py",
+    "src/tablib/_vendor/dbfpy/dbf.py",
+    "src/tablib/_vendor/dbfpy/header.py",
+    "src/tablib/_vendor/dbfpy/dbfnew.py",
+    "src/tablib/_vendor/dbfpy/utils.py",
+    "src/tablib/formats/_csv.py",
+    "src/tablib/formats/_xlsx.py",
+    "src/tablib/formats/_json.py",
+    "src/tablib/formats/_xls.py",
+    "src/tablib/formats/_jira.py",
+    "src/tablib/formats/_cli.py",
+    "src/tablib/formats/_html.py",
+    "src/tablib/formats/_sql.py",
+    "src/tablib/formats/_tsv.py",
+    "src/tablib/formats/__init__.py",
+    "src/tablib/formats/_yaml.py",
+    "src/tablib/formats/_df.py",
+    "src/tablib/formats/_dbf.py",
+    "src/tablib/formats/_rst.py",
+    "src/tablib/formats/_latex.py",
+    "src/tablib/formats/_ods.py",
+    "docs/requirements.txt",
+    "docs/index.rst",
+    "docs/conf.py",
+    "docs/install.rst",
+    "docs/krstyle.sty",
+    "docs/development.rst",
+    "docs/Makefile",
+    "docs/__init__.py",
+    "docs/tutorial.rst",
+    "docs/api.rst",
+    "docs/intro.rst",
+    "docs/formats.rst",
+    "docs/_templates/sidebarlogo.html",
+    "docs/_templates/sidebarintro.html"
+  ]
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/src/tablib.egg-info/scm_version.json 
new/tablib-3.10.0/src/tablib.egg-info/scm_version.json
--- old/tablib-3.9.0/src/tablib.egg-info/scm_version.json       1970-01-01 
01:00:00.000000000 +0100
+++ new/tablib-3.10.0/src/tablib.egg-info/scm_version.json      2026-07-31 
19:26:15.000000000 +0200
@@ -0,0 +1,8 @@
+{
+  "tag": "3.10.0",
+  "distance": 0,
+  "node": "gb0ff39fb9b2f457e5332249b4cc2ec11eabf46ee",
+  "dirty": false,
+  "branch": "HEAD",
+  "node_date": "2026-07-31"
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/tests/test_tablib.py 
new/tablib-3.10.0/tests/test_tablib.py
--- old/tablib-3.9.0/tests/test_tablib.py       2025-10-15 20:19:53.000000000 
+0200
+++ new/tablib-3.10.0/tests/test_tablib.py      2026-07-31 19:25:57.000000000 
+0200
@@ -322,6 +322,20 @@
         # Delete from invalid index
         self.assertRaises(IndexError, self.founders.__delitem__, 3)
 
+    def test_getitem_str_key_no_headers_raises(self):
+        """Verify that accessing by column name on a headerless Dataset raises 
HeadersNeeded."""
+        d = tablib.Dataset()
+        d.append([1, 2, 3])
+        with self.assertRaises(tablib.core.HeadersNeeded):
+            _ = d['col']
+
+    def test_delitem_str_key_no_headers_raises(self):
+        """Verify that deleting by column name on a headerless Dataset raises 
HeadersNeeded."""
+        d = tablib.Dataset()
+        d.append([1, 2, 3])
+        with self.assertRaises(tablib.core.HeadersNeeded):
+            del d['col']
+
     def test_str_no_columns(self):
         d = tablib.Dataset(['a', 1], ['b', 2], ['c', 3])
         output = f'{d}'
@@ -502,6 +516,30 @@
         self.assertEqual(column_stacked[0],
                          ("John", "Adams", 90, "John", "Adams", 90))
 
+    def test_stack_rejects_non_dataset(self):
+        """stack() and stack_cols() raise TypeError for non-Dataset input."""
+        with self.assertRaises(TypeError):
+            self.founders.stack('not a dataset')
+        with self.assertRaises(TypeError):
+            self.founders.stack_cols('not a dataset')
+
+    def test_column_stacking_headerless(self):
+        """Column stacking two datasets that both have no headers."""
+
+        left = tablib.Dataset()
+        left.append(("x", "y"))
+        left.append(("p", "q"))
+
+        right = tablib.Dataset()
+        right.append(("1", "2", "3"))
+        right.append(("4", "5", "6"))
+
+        column_stacked = left.stack_cols(right)
+
+        self.assertIsNone(column_stacked.headers)
+        self.assertEqual(column_stacked[0], ("x", "y", "1", "2", "3"))
+        self.assertEqual(column_stacked[1], ("p", "q", "4", "5", "6"))
+
     def test_sorting(self):
         """Sort columns."""
 
@@ -764,6 +802,21 @@
             
f"<h3>Founders</h3>{self.founders_html}<h3>Founders</h3>{self.founders_html}"
         )
 
+    def test_html_databook_export_escaped(self):
+        book = tablib.Databook()
+        dset_with_markup = tablib.Dataset(
+            ["<script>BOOM</script>"],
+            headers=["<b>a</b>"],
+            title='<script>BOOM</script>Founders'
+        )
+        book.add_sheet(dset_with_markup)
+        self.assertEqual(
+            book.html,
+            "<h3>&lt;script&gt;BOOM&lt;/script&gt;Founders</h3>\n"
+            "<table><thead><tr><th>&lt;b&gt;a&lt;/b&gt;</th></tr></thead>"
+            
"<tbody><tr><td>&lt;script&gt;BOOM&lt;/script&gt;</td></tr></tbody></table>\n"
+        )
+
     def test_html_import(self):
         data.html = self.founders_html
 
@@ -1208,6 +1261,16 @@
         self.assertEqual(data.dict[0]['None'], '')
         self.assertEqual(data.dict[0]['empty'], '')
 
+    def test_ods_export_import_boolean(self):
+        data.append(('alice', True))
+        data.append(('bob', False))
+        data.headers = ('name', 'flag')
+        _ods = data.ods
+        data.ods = _ods
+        self.assertIs(data.dict[0]['flag'], True)
+        self.assertIs(data.dict[1]['flag'], False)
+        self.assertEqual(data.dict[0]['name'], 'alice')
+
     def test_ods_export_display(self):
         """Test that exported datetime types are displayed correctly in office 
software"""
         date = dt.date(2019, 10, 4)
@@ -1326,6 +1389,30 @@
             }
         )
 
+    def test_xls_book_date_import(self):
+        """Dates are converted when a book is imported, as they are for a 
set."""
+        xls_source = Path(__file__).parent / 'files' / 'dates.xls'
+        with xls_source.open('rb') as fh:
+            dbook = tablib.Databook().load(fh, 'xls')
+        self.assertEqual(
+            dbook.sheets()[0].dict[0]['birth_date'], dt.datetime(2015, 4, 12, 
0, 0)
+        )
+
+    def test_xls_book_import_with_errors(self):
+        """Errors are kept as errors when a book is imported, as they are for 
a set."""
+        xls_source = Path(__file__).parent / 'files' / 'errors.xls'
+        with xls_source.open('rb') as fh:
+            dbook = tablib.Databook().load(fh, 'xls')
+        self.assertEqual(
+            dbook.sheets()[0].dict[0],
+            {
+                'div by 0': '#DIV/0!',
+                'name unknown': '#NAME?',
+                'not available (formula)': '#N/A',
+                'not available (static)': '#N/A',
+            }
+        )
+
     def test_book_import_from_stream(self):
         in_stream = self.founders.xls
         book = tablib.Databook().load(in_stream, 'xls')
@@ -1490,6 +1577,16 @@
         wb = load_workbook(filename=BytesIO(_xlsx))
         self.assertEqual('SUM(1+1)', wb.active['A1'].value)
 
+    def test_xlsx_export_set_escape_formulae_keeps_inner_equals(self):
+        """
+        Only the leading '=' should be stripped when escaping formulae; '='
+        characters inside the formula (e.g. a comparison) must be preserved.
+        """
+        data.append(('=A1=B1',))
+        _xlsx = data.export('xlsx', escape=True)
+        wb = load_workbook(filename=BytesIO(_xlsx))
+        self.assertEqual('A1=B1', wb.active['A1'].value)
+
     def test_xlsx_export_book_escape_formulae(self):
         """
         Test that formulae are sanitised on export.
@@ -1709,14 +1806,22 @@
 
         self.founders.append(('名字', '李', 60))
         expected = """\
-- {first_name: John, gpa: 90, last_name: Adams}
-- {first_name: George, gpa: 67, last_name: Washington}
-- {first_name: Thomas, gpa: 50, last_name: Jefferson}
-- {first_name: 名字, gpa: 60, last_name: 李}
+- {first_name: John, last_name: Adams, gpa: 90}
+- {first_name: George, last_name: Washington, gpa: 67}
+- {first_name: Thomas, last_name: Jefferson, gpa: 50}
+- {first_name: 名字, last_name: 李, gpa: 60}
 """
         output = self.founders.yaml
         self.assertEqual(output, expected)
 
+    def test_yaml_export_preserves_column_order(self):
+        """YAML export must keep the dataset's column order instead of sorting 
the keys
+        alphabetically."""
+        data = tablib.Dataset()
+        data.headers = ('name', 'id', 'zebra', 'apple')
+        data.append(('x', 1, 'z', 'a'))
+        self.assertEqual(data.yaml.strip(), '- {name: x, id: 1, zebra: z, 
apple: a}')
+
     def test_yaml_load(self):
         """ test issue 524: invalid format  """
         yaml_source = Path(__file__).parent / 'files' / 'issue_524.yaml'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tablib-3.9.0/tox.ini new/tablib-3.10.0/tox.ini
--- old/tablib-3.9.0/tox.ini    2025-10-15 20:19:53.000000000 +0200
+++ new/tablib-3.10.0/tox.ini   2026-07-31 19:25:57.000000000 +0200
@@ -1,17 +1,18 @@
 [tox]
 usedevelop = true
-minversion = 2.4
-envlist =
+requires =
+    tox>=4.32
+env_list =
     docs
     lint
-    py{39,310,311,312,313,314}
+    py{310-315}
 
 [testenv]
 deps =
     -rtests/requirements.txt
 commands_pre =
     - {envpython} -m pip install --only-binary :all: pandas
-passenv =
+pass_env =
     FORCE_COLOR
 commands =
     pytest {posargs:tests}

Reply via email to