From 2b09f425578c1cc8fbeb7a61860fb704ebf8cd48 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:48:50 -0700 Subject: [PATCH 1/4] fix(rag): keep trailing image-only rows in xlsx extraction --- api/core/rag/extractor/excel_extractor.py | 7 ++- .../rag/extractor/test_excel_extractor.py | 56 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/api/core/rag/extractor/excel_extractor.py b/api/core/rag/extractor/excel_extractor.py index 5d5316dc1a310b..f173da111accae 100644 --- a/api/core/rag/extractor/excel_extractor.py +++ b/api/core/rag/extractor/excel_extractor.py @@ -96,7 +96,12 @@ def extract(self) -> list[Document]: valid_columns={column_idx + 1 for column_idx in column_map}, min_row=start_row, ) - for row in sheet.iter_rows(min_row=start_row, max_col=max_col_idx, values_only=False): + # Image-only rows below the last populated cell have no cells of their own. + image_rows = [anchor_row for anchor_row, _ in sheet_image_map] + max_row = max([sheet.max_row or 0, *image_rows]) if image_rows else None + for row in sheet.iter_rows( + min_row=start_row, max_row=max_row, max_col=max_col_idx, values_only=False + ): page_content = [] row_has_content = False for col_idx, cell in enumerate(row): diff --git a/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py b/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py index 5b87121f12b798..ba257724626771 100644 --- a/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py +++ b/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py @@ -1,3 +1,5 @@ +import io +from pathlib import Path from types import SimpleNamespace import pandas as pd @@ -33,6 +35,7 @@ def __init__(self, header_rows, data_rows, images=None): self._header_rows = header_rows self._data_rows = data_rows self._images = images or [] + self.max_row = len(header_rows) + len(data_rows) def iter_rows(self, min_row=1, max_row=None, max_col=None, values_only=False): if values_only: @@ -243,6 +246,59 @@ def test_extract_xlsx_reuses_existing_embedded_image_uploads_on_retry( assert second_docs[0].page_content == expected_page_content assert len(saves) == 1 + def test_extract_xlsx_keeps_trailing_image_only_rows_from_real_workbook( + self, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + database_session_maker: sessionmaker[Session], + ) -> None: + import openpyxl + from openpyxl.drawing.image import Image as WorksheetImage + from PIL import Image as PILImage + + png = io.BytesIO() + PILImage.new("RGB", (4, 4), "red").save(png, format="PNG") + image_bytes = png.getvalue() + + path = tmp_path / "images.xlsx" + workbook = openpyxl.Workbook() + sheet = workbook.active + assert sheet is not None + sheet.append(["Question", "Answer", "Image"]) + sheet.append(["Q1", "A1", None]) + # Row 3 is just past the populated cells; row 12 is past the 10-row header scan. + sheet.add_image(WorksheetImage(io.BytesIO(image_bytes)), "C3") + sheet.add_image(WorksheetImage(io.BytesIO(image_bytes)), "C12") + workbook.save(path) + workbook.close() + saves = _patch_image_persistence(monkeypatch) + + extractor = ExcelExtractor( + str(path), + tenant_id="tenant-1", + user_id="user-1", + source_file_id="source-file-1", + ) + first_docs = extractor.extract() + second_docs = extractor.extract() + upload_files = _get_upload_files(database_session_maker) + + upload_ids = { + row: next(upload_file.id for upload_file in upload_files if f"_r{row}_c3_" in upload_file.key) + for row in (3, 12) + } + expected_page_contents = [ + '"Question":"Q1";"Answer":"A1";"Image":""', + *( + f'"Question":"";"Answer":"";"Image":"![image](http://files.local/files/{upload_ids[row]}/file-preview)"' + for row in (3, 12) + ), + ] + assert [doc.page_content for doc in first_docs] == expected_page_contents + assert [doc.page_content for doc in second_docs] == expected_page_contents + assert len(upload_files) == 2 + assert sorted(saves) == sorted((upload_file.key, image_bytes) for upload_file in upload_files) + def test_extract_xls_path(self, monkeypatch: pytest.MonkeyPatch): class FakeExcelFile: sheet_names = ["Sheet1"] From 2454daee59e90a2137af44b414337f2b3bd2b13b Mon Sep 17 00:00:00 2001 From: Wolfie <221313372+wolfiesch@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:28:02 -0700 Subject: [PATCH 2/4] feat(rag): add opt-in wolfxl reader for .xlsx extraction --- api/core/rag/extractor/excel_extractor.py | 14 ++++++++---- api/pyproject.toml | 3 +++ .../rag/extractor/test_excel_extractor.py | 21 ++++++++++++++++++ api/uv.lock | 22 +++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/api/core/rag/extractor/excel_extractor.py b/api/core/rag/extractor/excel_extractor.py index f173da111accae..c2fd0b82eab27b 100644 --- a/api/core/rag/extractor/excel_extractor.py +++ b/api/core/rag/extractor/excel_extractor.py @@ -13,7 +13,6 @@ from typing import TypedDict, override import pandas as pd -from openpyxl import load_workbook from sqlalchemy import select from configs import dify_config @@ -26,6 +25,13 @@ from models.enums import CreatorUserRole from models.model import UploadFile +try: + from wolfxl import load_workbook # pyrefly: ignore[missing-import] +except ModuleNotFoundError as exc: + if exc.name != "wolfxl": + raise + from openpyxl import load_workbook + logger = logging.getLogger(__name__) @@ -76,7 +82,7 @@ def __init__( @override def extract(self) -> list[Document]: - """Load from Excel file in xls or xlsx format using Pandas and openpyxl.""" + """Load Excel files using Pandas and the installed optional XLSX reader.""" documents = [] file_extension = os.path.splitext(self._file_path)[-1].lower() @@ -289,7 +295,7 @@ def _build_image_file_key( ) def _get_image_bytes(self, image) -> bytes | None: - """Return embedded image bytes from an openpyxl image object.""" + """Return embedded image bytes from a worksheet image object.""" data_loader = getattr(image, "_data", None) if not callable(data_loader): return None @@ -307,7 +313,7 @@ def _get_image_bytes(self, image) -> bytes | None: return None def _get_image_extension(self, image) -> str | None: - """Resolve an image extension from openpyxl metadata.""" + """Resolve an image extension from worksheet image metadata.""" image_format = getattr(image, "format", None) if isinstance(image_format, str) and image_format.strip(): return image_format.strip().lower() diff --git a/api/pyproject.toml b/api/pyproject.toml index e226e804b6705a..a9a62b07c1e6ee 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -129,6 +129,9 @@ override-dependencies = [ [dependency-groups] +# Opt-in XLSX reader. Not included in tool.uv.default-groups. +xlsx-wolfxl = ["wolfxl>=2.0.17,<2.1"] + ############################################################ # [ Dev ] dependency group # Required for development and running tests diff --git a/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py b/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py index ba257724626771..89edc009e03d0a 100644 --- a/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py +++ b/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py @@ -1,3 +1,4 @@ +import importlib.util import io from pathlib import Path from types import SimpleNamespace @@ -92,6 +93,26 @@ def _get_upload_files(session_maker: sessionmaker[Session]) -> list[UploadFile]: class TestExcelExtractor: + def test_installed_xlsx_reader(self, tmp_path): + import openpyxl + + if importlib.util.find_spec("wolfxl") is None: + assert excel_module.load_workbook is openpyxl.load_workbook + else: + assert excel_module.load_workbook is importlib.import_module("wolfxl").load_workbook + + path = tmp_path / "reader.xlsx" + workbook = openpyxl.Workbook() + sheet = workbook.active + assert sheet is not None + sheet.append(["Name", "Count"]) + sheet.append(["Alice", 3]) + workbook.save(path) + workbook.close() + + documents = ExcelExtractor(str(path)).extract() + assert [document.page_content for document in documents] == ['"Name":"Alice";"Count":"3"'] + def test_extract_xlsx_with_hyperlinks_and_sheet_skip(self, monkeypatch: pytest.MonkeyPatch): sheet_with_data = _FakeSheet( header_rows=[("Name", "Link")], diff --git a/api/uv.lock b/api/uv.lock index 8e2887ee428f35..5448d191bfa378 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1773,6 +1773,9 @@ vdb-weaviate = [ vdb-xinference = [ { name = "xinference-client" }, ] +xlsx-wolfxl = [ + { name = "wolfxl" }, +] [package.metadata] requires-dist = [ @@ -1994,6 +1997,7 @@ vdb-vastbase = [{ name = "dify-vdb-vastbase", editable = "providers/vdb/vdb-vast vdb-vikingdb = [{ name = "dify-vdb-vikingdb", editable = "providers/vdb/vdb-vikingdb" }] vdb-weaviate = [{ name = "dify-vdb-weaviate", editable = "providers/vdb/vdb-weaviate" }] vdb-xinference = [{ name = "xinference-client", specifier = ">=2.7.0,<3.0.0" }] +xlsx-wolfxl = [{ name = "wolfxl", specifier = ">=2.0.17,<2.1" }] [[package]] name = "dify-trace-aliyun" @@ -8015,6 +8019,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a1/38/df03f564f43cec2684823f3cccae1a652ee7face1cbaa76fb223096e64d7/werkzeug-3.1.9-py3-none-any.whl", hash = "sha256:6392e50c78460ba618e5b21f08a71f59c99ce99cdc6cf6e3dd7e6ccca8754fab", size = 228700, upload-time = "2026-09-27T18:33:39.685Z" }, ] +[[package]] +name = "wolfxl" +version = "2.0.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "defusedxml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/96/52bf6c84c665cbe9094145c692449ced84a23f058f92d4a64606e68cb42a/wolfxl-2.0.18.tar.gz", hash = "sha256:e0ef2a5450396ea673608cc58c2643eefd43aeb75f8613a5656ab4aae6496747", size = 11664611, upload-time = "2026-10-26T05:40:46.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/83/01e79435feb39eaefaa5edba0ec56ba164cf45b0b2febbf2cb2a505d493a/wolfxl-2.0.18-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a0ae34c15c157bdc6303c03552a5c3921a82900933ad7ce613f776186906afd3", size = 3662909, upload-time = "2026-10-26T05:39:46.578Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4c/518f85d7f2ac8f53633a3eea3af952cf5509f7aceac868ee65be061c22ed/wolfxl-2.0.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ea9fabd84c6b20e8cacec436fce2d9c6658027c9bbd3f62037dbf22c5d613f", size = 3447897, upload-time = "2026-10-26T05:39:48.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d4/ac876b4547a0a44d739a9d52eb630cda5c85eab148a2d31f010a3b043f72/wolfxl-2.0.18-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:0e2a183bb8b32c74e3306ea4b9ed3cefaa9cc13d79ccc134e9d8c7c90f07f2b1", size = 3541176, upload-time = "2026-10-26T05:39:50.783Z" }, + { url = "https://files.pythonhosted.org/packages/27/db/ac0ca2931d8fe1f4aae4bd2b8984fa012cc3706b70b839b731d8dfd9b86a/wolfxl-2.0.18-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:475393d3f515c3db45d110bc1c01c9ffac23ea7dca2b721e7a4ac9b9334e924b", size = 3756856, upload-time = "2026-10-26T05:39:52.546Z" }, + { url = "https://files.pythonhosted.org/packages/19/8c/88363ad7f6c8fd6e4fb799a402ac4b7fed5a6af77ea21ddb60c2af798480/wolfxl-2.0.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b37bbf8c96f40c289176aa1660b89c2995f77dacf36bd9b5c2e5ec2122bdc7", size = 3722847, upload-time = "2026-10-26T05:39:54.632Z" }, + { url = "https://files.pythonhosted.org/packages/ce/fd/84f2b18dfb8457e2824c514ac8f24a78add6bf99a5f7863d6531bf20e106/wolfxl-2.0.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b636205da16072a17489e78e5764b1eef7d11c0dd367ddddf059b8a23685ef8", size = 3963470, upload-time = "2026-10-26T05:39:56.249Z" }, + { url = "https://files.pythonhosted.org/packages/0e/28/4beef4ddc1cc3a87bb81326c047b072116598d2a3c85109952359f002a9c/wolfxl-2.0.18-cp313-cp313-win_amd64.whl", hash = "sha256:515cfb060cb82af974f26dafd67198fee52302fc3f12200c8d97e09fbaa97f81", size = 3751281, upload-time = "2026-10-26T05:39:58.738Z" }, +] + [[package]] name = "wrapt" version = "2.4.0" From 935970f68715777ccc7af8a6028dbc3840ada14a Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Fri, 30 Oct 2026 17:27:02 -0700 Subject: [PATCH 3/4] test(rag): cover broken wolfxl install in excel extractor import --- .../rag/extractor/test_excel_extractor.py | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py b/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py index 89edc009e03d0a..e9189ef5e25ed5 100644 --- a/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py +++ b/api/tests/unit_tests/core/rag/extractor/test_excel_extractor.py @@ -1,7 +1,9 @@ +import builtins import importlib.util import io +from collections.abc import Mapping, Sequence from pathlib import Path -from types import SimpleNamespace +from types import ModuleType, SimpleNamespace import pandas as pd import pytest @@ -113,6 +115,29 @@ def test_installed_xlsx_reader(self, tmp_path): documents = ExcelExtractor(str(path)).extract() assert [document.page_content for document in documents] == ['"Name":"Alice";"Count":"3"'] + def test_broken_wolfxl_install_is_not_masked(self, monkeypatch: pytest.MonkeyPatch) -> None: + real_import = builtins.__import__ + + def import_with_broken_wolfxl( + name: str, + globals: Mapping[str, object] | None = None, + locals: Mapping[str, object] | None = None, + fromlist: Sequence[str] = (), + level: int = 0, + ) -> ModuleType: + if name == "wolfxl": + raise ModuleNotFoundError("No module named 'wolfxl._native'", name="wolfxl._native") + return real_import(name, globals, locals, fromlist, level) + + monkeypatch.setattr(builtins, "__import__", import_with_broken_wolfxl) + spec = importlib.util.spec_from_file_location("_excel_extractor_import_probe", excel_module.__file__) + assert spec is not None + assert spec.loader is not None + + with pytest.raises(ModuleNotFoundError) as exc_info: + spec.loader.exec_module(importlib.util.module_from_spec(spec)) + assert exc_info.value.name == "wolfxl._native" + def test_extract_xlsx_with_hyperlinks_and_sheet_skip(self, monkeypatch: pytest.MonkeyPatch): sheet_with_data = _FakeSheet( header_rows=[("Name", "Link")], From 28c4c7e1150d00a6ce7bf21ee4ee4b15a5ccd5b2 Mon Sep 17 00:00:00 2001 From: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Date: Fri, 30 Oct 2026 19:02:00 -0700 Subject: [PATCH 4/4] build(rag): require wolfxl 2.0.35 for the xlsx-wolfxl group 2.0.35 returns date-only t="d" cells as datetime.date, matching openpyxl. --- api/pyproject.toml | 2 +- api/uv.lock | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/api/pyproject.toml b/api/pyproject.toml index a9a62b07c1e6ee..44e0035baa95a6 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -130,7 +130,7 @@ override-dependencies = [ [dependency-groups] # Opt-in XLSX reader. Not included in tool.uv.default-groups. -xlsx-wolfxl = ["wolfxl>=2.0.17,<2.1"] +xlsx-wolfxl = ["wolfxl>=2.0.35,<2.1"] ############################################################ # [ Dev ] dependency group diff --git a/api/uv.lock b/api/uv.lock index 5448d191bfa378..7ef60d02b32ad3 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1997,7 +1997,7 @@ vdb-vastbase = [{ name = "dify-vdb-vastbase", editable = "providers/vdb/vdb-vast vdb-vikingdb = [{ name = "dify-vdb-vikingdb", editable = "providers/vdb/vdb-vikingdb" }] vdb-weaviate = [{ name = "dify-vdb-weaviate", editable = "providers/vdb/vdb-weaviate" }] vdb-xinference = [{ name = "xinference-client", specifier = ">=2.7.0,<3.0.0" }] -xlsx-wolfxl = [{ name = "wolfxl", specifier = ">=2.0.17,<2.1" }] +xlsx-wolfxl = [{ name = "wolfxl", specifier = ">=2.0.35,<2.1" }] [[package]] name = "dify-trace-aliyun" @@ -4044,15 +4044,15 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/63/28fb5696e52f40f4bafd096a3d0ed2c8679a24311ed89c200a280ee3b68a/litellm-1.100.4.tar.gz", hash = "sha256:c031cbc63848be16403725594f6060fe002dc2e65f0866878285d72923ac27ab" } +sdist = { url = "https://files.pythonhosted.org/packages/71/63/28fb5696e52f40f4bafd096a3d0ed2c8679a24311ed89c200a280ee3b68a/litellm-1.100.4.tar.gz", hash = "sha256:c031cbc63848be16403725594f6060fe002dc2e65f0866878285d72923ac27ab", size = 17338625, upload-time = "2026-09-29T23:04:57.065Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/5e/407031806577da6b1f3c9217774dabcc1353345337282093915411596aa9/litellm-1.100.4-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c86e2e3370674557547022eb9acd8ec842f802bddb2cfa97992332ad56309a53" }, - { url = "https://files.pythonhosted.org/packages/db/71/a284cce63fa6c37dab677415d146c968d9d802e1eed2778efcdddba49010/litellm-1.100.4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:737c99748f9a37e74d4927ad9fe311e3f531a5379ea423eb0d1412d3310f1643" }, - { url = "https://files.pythonhosted.org/packages/0c/55/c16164e6cbae93437f50aaf550c5dcacc7a1ca4f1af80ece2c116fbeb01e/litellm-1.100.4-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7228d4d12eb332e5e7b7dbfeb41a4182784ad2acf86f6b7eb806565e04912ada" }, - { url = "https://files.pythonhosted.org/packages/ff/09/d99612074d3e114c449fc928d7374f09d8e87cc74dc36fdbb466fc1b9f5a/litellm-1.100.4-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ded14b15466163c76c9a7da0cd091663b4bebbff9aa56724a5905386a9d12532" }, - { url = "https://files.pythonhosted.org/packages/6e/a0/cc57f435963705f6baeaa717a870a616dd83981217daa81c9de2c2456a0d/litellm-1.100.4-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb12645456c19dd56f1fa5d7e165a2a76effb1501ace5d0a4df18efc0ac5c39a" }, - { url = "https://files.pythonhosted.org/packages/d5/dd/b7b458120100e47fa17718fa7aa7f1953cc01b34ce0439cc9f681cc746a0/litellm-1.100.4-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aa8e8507678451b18c4369b69506d8948154b2ab4af211454dd062380597c1ce" }, - { url = "https://files.pythonhosted.org/packages/26/35/51d5be49f6add1bea14a0d47cb4f125fc343150095e2e655aa02cba2e885/litellm-1.100.4-cp310-abi3-win_amd64.whl", hash = "sha256:83342175eb948f146f405735b03c1cb1e5c332aeef6a98cfb31f59233d10426a" }, + { url = "https://files.pythonhosted.org/packages/67/5e/407031806577da6b1f3c9217774dabcc1353345337282093915411596aa9/litellm-1.100.4-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c86e2e3370674557547022eb9acd8ec842f802bddb2cfa97992332ad56309a53", size = 23902271, upload-time = "2026-09-29T23:04:39.582Z" }, + { url = "https://files.pythonhosted.org/packages/db/71/a284cce63fa6c37dab677415d146c968d9d802e1eed2778efcdddba49010/litellm-1.100.4-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:737c99748f9a37e74d4927ad9fe311e3f531a5379ea423eb0d1412d3310f1643", size = 23553194, upload-time = "2026-09-29T23:04:43.133Z" }, + { url = "https://files.pythonhosted.org/packages/0c/55/c16164e6cbae93437f50aaf550c5dcacc7a1ca4f1af80ece2c116fbeb01e/litellm-1.100.4-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7228d4d12eb332e5e7b7dbfeb41a4182784ad2acf86f6b7eb806565e04912ada", size = 23696180, upload-time = "2026-09-29T23:04:45.734Z" }, + { url = "https://files.pythonhosted.org/packages/ff/09/d99612074d3e114c449fc928d7374f09d8e87cc74dc36fdbb466fc1b9f5a/litellm-1.100.4-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ded14b15466163c76c9a7da0cd091663b4bebbff9aa56724a5905386a9d12532", size = 24066201, upload-time = "2026-09-29T23:04:48.071Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a0/cc57f435963705f6baeaa717a870a616dd83981217daa81c9de2c2456a0d/litellm-1.100.4-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:eb12645456c19dd56f1fa5d7e165a2a76effb1501ace5d0a4df18efc0ac5c39a", size = 23769546, upload-time = "2026-09-29T23:04:50.353Z" }, + { url = "https://files.pythonhosted.org/packages/d5/dd/b7b458120100e47fa17718fa7aa7f1953cc01b34ce0439cc9f681cc746a0/litellm-1.100.4-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:aa8e8507678451b18c4369b69506d8948154b2ab4af211454dd062380597c1ce", size = 24166511, upload-time = "2026-09-29T23:04:52.583Z" }, + { url = "https://files.pythonhosted.org/packages/26/35/51d5be49f6add1bea14a0d47cb4f125fc343150095e2e655aa02cba2e885/litellm-1.100.4-cp310-abi3-win_amd64.whl", hash = "sha256:83342175eb948f146f405735b03c1cb1e5c332aeef6a98cfb31f59233d10426a", size = 23981383, upload-time = "2026-09-29T23:04:54.838Z" }, ] [[package]] @@ -8021,20 +8021,20 @@ wheels = [ [[package]] name = "wolfxl" -version = "2.0.18" +version = "2.0.35" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "defusedxml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/01/96/52bf6c84c665cbe9094145c692449ced84a23f058f92d4a64606e68cb42a/wolfxl-2.0.18.tar.gz", hash = "sha256:e0ef2a5450396ea673608cc58c2643eefd43aeb75f8613a5656ab4aae6496747", size = 11664611, upload-time = "2026-10-26T05:40:46.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/9f/91eeb1fc3f4c2141ace46d1250712a69ff536911ce9da5bcc6a49ddf8305/wolfxl-2.0.35.tar.gz", hash = "sha256:d9d31596322c695c4e8c0e11e8eaac7611d00c798d964ca0b7d836239d10066a", size = 11758998, upload-time = "2026-10-30T13:41:39.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/83/01e79435feb39eaefaa5edba0ec56ba164cf45b0b2febbf2cb2a505d493a/wolfxl-2.0.18-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a0ae34c15c157bdc6303c03552a5c3921a82900933ad7ce613f776186906afd3", size = 3662909, upload-time = "2026-10-26T05:39:46.578Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4c/518f85d7f2ac8f53633a3eea3af952cf5509f7aceac868ee65be061c22ed/wolfxl-2.0.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f5ea9fabd84c6b20e8cacec436fce2d9c6658027c9bbd3f62037dbf22c5d613f", size = 3447897, upload-time = "2026-10-26T05:39:48.577Z" }, - { url = "https://files.pythonhosted.org/packages/e2/d4/ac876b4547a0a44d739a9d52eb630cda5c85eab148a2d31f010a3b043f72/wolfxl-2.0.18-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:0e2a183bb8b32c74e3306ea4b9ed3cefaa9cc13d79ccc134e9d8c7c90f07f2b1", size = 3541176, upload-time = "2026-10-26T05:39:50.783Z" }, - { url = "https://files.pythonhosted.org/packages/27/db/ac0ca2931d8fe1f4aae4bd2b8984fa012cc3706b70b839b731d8dfd9b86a/wolfxl-2.0.18-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:475393d3f515c3db45d110bc1c01c9ffac23ea7dca2b721e7a4ac9b9334e924b", size = 3756856, upload-time = "2026-10-26T05:39:52.546Z" }, - { url = "https://files.pythonhosted.org/packages/19/8c/88363ad7f6c8fd6e4fb799a402ac4b7fed5a6af77ea21ddb60c2af798480/wolfxl-2.0.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b1b37bbf8c96f40c289176aa1660b89c2995f77dacf36bd9b5c2e5ec2122bdc7", size = 3722847, upload-time = "2026-10-26T05:39:54.632Z" }, - { url = "https://files.pythonhosted.org/packages/ce/fd/84f2b18dfb8457e2824c514ac8f24a78add6bf99a5f7863d6531bf20e106/wolfxl-2.0.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b636205da16072a17489e78e5764b1eef7d11c0dd367ddddf059b8a23685ef8", size = 3963470, upload-time = "2026-10-26T05:39:56.249Z" }, - { url = "https://files.pythonhosted.org/packages/0e/28/4beef4ddc1cc3a87bb81326c047b072116598d2a3c85109952359f002a9c/wolfxl-2.0.18-cp313-cp313-win_amd64.whl", hash = "sha256:515cfb060cb82af974f26dafd67198fee52302fc3f12200c8d97e09fbaa97f81", size = 3751281, upload-time = "2026-10-26T05:39:58.738Z" }, + { url = "https://files.pythonhosted.org/packages/d3/40/a562f04aacc1f2ce55e3ead282f292033d55d4fd21c82218c686c527afdf/wolfxl-2.0.35-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:c2a48d9a1f71ccf6a2b6b82625e3f811efe745c9986af949b77432bb14e403a1", size = 3721923, upload-time = "2026-10-30T13:40:42.411Z" }, + { url = "https://files.pythonhosted.org/packages/87/9e/f1ceee4378a59205f1f0fc0815048dadba6064595011408453e746c13d36/wolfxl-2.0.35-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7b5b10306fa7ff4be0ed11833b2cfd2a0136dc694608cf1c95268238ea79b824", size = 3528438, upload-time = "2026-10-30T13:40:43.956Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fb/284a9f3ad782d2db893431315f161d51b23bc06fe7ba2dbc750805a9527e/wolfxl-2.0.35-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:994fe17568f2dec7e1f28925e07024ddedb0cb2cfbb4a8ab12f71d06ab531d01", size = 3611421, upload-time = "2026-10-30T13:40:45.474Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d3/e69854324ab184a5c2b0a9430a83563683dedf2088d06df729e4830a7ea3/wolfxl-2.0.35-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bf92b706923f4cbbcf9f04de2df8861f3c41245cd761a08a1db4838da4c46441", size = 3814830, upload-time = "2026-10-30T13:40:47.219Z" }, + { url = "https://files.pythonhosted.org/packages/76/1c/c556572642674da69ca417b46e4b49cdf26d8739dfbb353ae1bd1a55d12e/wolfxl-2.0.35-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c12b123973273516870ceb499b8e43361e884c02454526906d4892a701653f6c", size = 3790183, upload-time = "2026-10-30T13:40:48.719Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b9/616f9c0c5c10c9e1d4c916a22716b2c16dc9b8ea28dd47360ad195d198d4/wolfxl-2.0.35-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69ebbc580c326b79b09905a8a03dab6f41b016d969a82161755712092b7a5100", size = 4021133, upload-time = "2026-10-30T13:40:50.569Z" }, + { url = "https://files.pythonhosted.org/packages/59/56/74427cddcfe51614d0c8307eaff5a9872a0097aa1b2c7234f67641c1027f/wolfxl-2.0.35-cp313-cp313-win_amd64.whl", hash = "sha256:9d5bb597c10f392884418072db34cbefa7d3778fe0f6a904e9fe2907e0eea45e", size = 3807771, upload-time = "2026-10-30T13:40:52.092Z" }, ] [[package]]