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><script>BOOM</script>Founders</h3>\n" + "<table><thead><tr><th><b>a</b></th></tr></thead>" + "<tbody><tr><td><script>BOOM</script></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}
