From 5cec34721cb31ba449357ddb3d6ea3a5ccde6e6e Mon Sep 17 00:00:00 2001 From: Creylay Date: Tue, 30 Jun 2026 11:28:40 -0400 Subject: [PATCH 001/308] Refactor session state management in SessionBar and streamline layout rendering in GenerativeContent --- .../src/components/generative/SessionBar.jsx | 14 ++++---- .../pages/generative/GenerativeContent.jsx | 32 ++++++++----------- 2 files changed, 21 insertions(+), 25 deletions(-) diff --git a/DashAI/front/src/components/generative/SessionBar.jsx b/DashAI/front/src/components/generative/SessionBar.jsx index b8ad8a2c5..7c089af96 100644 --- a/DashAI/front/src/components/generative/SessionBar.jsx +++ b/DashAI/front/src/components/generative/SessionBar.jsx @@ -47,16 +47,16 @@ export default function SessionBar({ onToggle }) { ), ), ]; - const initialOpenState = {}; - uniqueDisplayNames.forEach((displayName) => { - initialOpenState[displayName] = false; - }); setOpenSections((prev) => { - // Only update if display names have changed const prevKeys = Object.keys(prev).sort().join(","); - const newKeys = Object.keys(initialOpenState).sort().join(","); + const newKeys = uniqueDisplayNames.slice().sort().join(","); if (prevKeys === newKeys) return prev; - return initialOpenState; + // Preserve existing open/close state; initialize new keys as closed + const merged = {}; + uniqueDisplayNames.forEach((displayName) => { + merged[displayName] = displayName in prev ? prev[displayName] : false; + }); + return merged; }); }, [sessions, tasks]); diff --git a/DashAI/front/src/pages/generative/GenerativeContent.jsx b/DashAI/front/src/pages/generative/GenerativeContent.jsx index 29587ec43..7aee51179 100644 --- a/DashAI/front/src/pages/generative/GenerativeContent.jsx +++ b/DashAI/front/src/pages/generative/GenerativeContent.jsx @@ -97,23 +97,19 @@ export default function GenerativeContent() { return ; }; - const layout = ( - - - - - - {renderCenter()} - - {renderRight()} - - - - ); - - return isCreating ? ( - {layout} - ) : ( - layout + return ( + + + + + + + {renderCenter()} + + {renderRight()} + + + + ); } From 6f7972d26cedf8135a569485d21130a315dbd2c8 Mon Sep 17 00:00:00 2001 From: Creylay Date: Tue, 30 Jun 2026 12:07:10 -0400 Subject: [PATCH 002/308] Reduce description length for SVC multilingual support --- DashAI/back/models/scikit_learn/svc.py | 51 +++----------------------- 1 file changed, 5 insertions(+), 46 deletions(-) diff --git a/DashAI/back/models/scikit_learn/svc.py b/DashAI/back/models/scikit_learn/svc.py index 2257c3398..34b08dc15 100644 --- a/DashAI/back/models/scikit_learn/svc.py +++ b/DashAI/back/models/scikit_learn/svc.py @@ -254,52 +254,11 @@ class SVC(TabularClassificationModel, SklearnLikeClassifier, _SVC): de="Support-Vektor-Maschine (SVM)", ) DESCRIPTION: str = MultilingualString( - en=( - "Support Vector Machine (SVM) is a supervised machine learning algorithm " - "used for classification and regression tasks. It works by finding the " - "optimal hyperplane that maximizes the margin between different classes " - "in a high dimensional feature space. SVMs are effective in cases where " - "the number of features is large relative to the number of samples and " - "can model complex, nonlinear decision boundaries through the use of " - "kernel functions such as linear, polynomial, and radial basis function " - "(RBF) kernels." - ), - es=( - "La Máquina de Vectores de Soporte (SVM) es un algoritmo de aprendizaje " - "automático supervisado utilizado para tareas de clasificación y " - "regresión. Funciona encontrando el hiperplano óptimo que maximiza el " - "margen entre las distintas clases en un espacio de características de " - "alta dimensionalidad. Las SVM son especialmente efectivas cuando el " - "número de características es grande en relación con el número de " - "muestras y pueden modelar fronteras de decisión complejas y no lineales " - "mediante el uso de funciones kernel como lineal, polinomial y de base " - "radial (RBF)." - ), - pt=( - "A Máquina de Vetores de Suporte (SVM) é um algoritmo de aprendizado " - "de máquina supervisionado utilizado para tarefas de classificação e " - "regressão. Funciona encontrando o hiperplano ótimo que maximiza a " - "margem entre as diferentes classes em um espaço de características de " - "alta dimensionalidade. As SVMs são especialmente eficazes quando o " - "número de características é grande em relação ao número de amostras e " - "podem modelar fronteiras de decisão complexas e não lineares mediante " - "o uso de funções kernel como linear, polinomial e de base radial (RBF)." - ), - zh=( - "支持向量机(SVM)是一种监督学习算法,通过在高维特征空间中" - "寻找最优超平面来最大化类间间隔,支持线性、多项式和径向基函数(RBF)核。" - ), - de=( - "Die Support-Vektor-Maschine (SVM) ist ein überwachter " - "Machine Learning Algorithmus für Klassifikations- und " - "Regressionsaufgaben. Sie findet die optimale Hyperebene, die die " - "Margin zwischen verschiedenen Klassen in einem hochdimensionalen " - "Merkmalsraum maximiert. SVMs sind besonders effektiv, wenn die Anzahl " - "der Merkmale im Verhältnis zur Anzahl der Stichproben groß ist, und " - "können komplexe, nichtlineare Entscheidungsgrenzen durch den Einsatz " - "von Kernelfunktionen wie linear, polynomial und radialer Basisfunktion " - "(RBF) modellieren." - ), + en="Finds the optimal hyperplane that maximises the margin between classes.", + es="Encuentra el hiperplano óptimo que maximiza el margen entre clases.", + pt="Encontra o hiperplano ótimo que maximiza a margem entre classes.", + zh="寻找最优超平面以最大化类间间隔的分类算法。", + de="Findet die optimale Hyperebene, die den Margin zwischen Klassen maximiert.", ) COLOR: str = "#FF80AB" ICON: str = "Timeline" From cf74a18fad654078a68c59eb2bb61581ecbd5428 Mon Sep 17 00:00:00 2001 From: Creylay Date: Tue, 30 Jun 2026 13:08:53 -0400 Subject: [PATCH 003/308] Add auto-clear for selected run ID after highlighting run card --- DashAI/front/src/components/models/SessionVisualization.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index 2fd1e0da3..9c5cd7857 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -162,6 +162,7 @@ export default function SessionVisualization() { if (element) { element.scrollIntoView({ behavior: "smooth", block: "center" }); } + setTimeout(() => setSelectedRunId(null), 2000); }, []); const handleViewDetails = React.useCallback((run) => { From f34415543f5cbfca520c75124b57bbc3fa7de1ae Mon Sep 17 00:00:00 2001 From: Creylay Date: Tue, 30 Jun 2026 17:37:45 -0400 Subject: [PATCH 004/308] Prevent saving empty datasets by adding validation and error messages in multiple languages --- DashAI/back/dataloaders/classes/dashai_dataset.py | 12 +++++++++--- .../notebooks/notebook/DatasetPreviewNotebook.jsx | 9 +++++++++ DashAI/front/src/utils/i18n/locales/de/datasets.json | 3 ++- DashAI/front/src/utils/i18n/locales/en/datasets.json | 3 ++- DashAI/front/src/utils/i18n/locales/es/datasets.json | 3 ++- DashAI/front/src/utils/i18n/locales/pt/datasets.json | 3 ++- DashAI/front/src/utils/i18n/locales/zh/datasets.json | 3 ++- 7 files changed, 28 insertions(+), 8 deletions(-) diff --git a/DashAI/back/dataloaders/classes/dashai_dataset.py b/DashAI/back/dataloaders/classes/dashai_dataset.py index c3fbc44b7..7eb55d3de 100644 --- a/DashAI/back/dataloaders/classes/dashai_dataset.py +++ b/DashAI/back/dataloaders/classes/dashai_dataset.py @@ -264,7 +264,9 @@ def _compute_numeric_metadata(self, dataset_df) -> dict: dict Dictionary with statistics for each numeric column. """ - numeric_keys = self._get_numeric_columns() + numeric_keys = [ + k for k in self._get_numeric_columns() if k in dataset_df.columns + ] numeric_cols = dataset_df[numeric_keys] numeric_stats = {} @@ -316,7 +318,9 @@ def _compute_categorical_metadata(self, dataset_df) -> dict: dict Dictionary with statistics for each categorical column. """ - categorical_keys = self._get_categorical_columns() + categorical_keys = [ + k for k in self._get_categorical_columns() if k in dataset_df.columns + ] categorical_cols = dataset_df[categorical_keys] categorical_stats = {} @@ -465,7 +469,9 @@ def _compute_correlations(self, dataset_df) -> dict: dict Nested dictionary representing the correlation matrix. """ - numeric_keys = self._get_numeric_columns() + numeric_keys = [ + k for k in self._get_numeric_columns() if k in dataset_df.columns + ] numeric_cols = dataset_df[numeric_keys] if numeric_cols.empty: diff --git a/DashAI/front/src/components/notebooks/notebook/DatasetPreviewNotebook.jsx b/DashAI/front/src/components/notebooks/notebook/DatasetPreviewNotebook.jsx index dc62344a3..6e89e3095 100644 --- a/DashAI/front/src/components/notebooks/notebook/DatasetPreviewNotebook.jsx +++ b/DashAI/front/src/components/notebooks/notebook/DatasetPreviewNotebook.jsx @@ -248,6 +248,15 @@ export default function DatasetPreviewNotebook({ endIcon={} onClick={(e) => { e.stopPropagation(); + if ( + convertersLoaded && + Object.keys(localColumnTypes).length === 0 + ) { + enqueueSnackbar(t("datasets:error.cannotSaveEmptyDataset"), { + variant: "error", + }); + return; + } setShowSaveDatasetModal(true); if (tourContext && tourContext.run) { setTimeout(() => { diff --git a/DashAI/front/src/utils/i18n/locales/de/datasets.json b/DashAI/front/src/utils/i18n/locales/de/datasets.json index 8d40a9a87..204daf23b 100644 --- a/DashAI/front/src/utils/i18n/locales/de/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/de/datasets.json @@ -93,7 +93,8 @@ "requiresExactColumns_other": "Erfordert genau {{required}} gültige Spalten, aber {{available}} verfügbar.", "requiresMinColumns_one": "Erfordert mindestens {{required}} gültige Spalte, aber nur {{available}} verfügbar.", "requiresMinColumns_other": "Erfordert mindestens {{required}} gültige Spalten, aber nur {{available}} verfügbar.", - "zipContentsNotCompatible": "ZIP enthält keine mit dem ausgewählten Datenlader kompatiblen Dateien" + "zipContentsNotCompatible": "ZIP enthält keine mit dem ausgewählten Datenlader kompatiblen Dateien", + "cannotSaveEmptyDataset": "Dataset kann nicht gespeichert werden: Alle Spalten wurden entfernt. Fügen Sie Spalten hinzu, bevor Sie speichern." }, "label": { "task": "Aufgabe", diff --git a/DashAI/front/src/utils/i18n/locales/en/datasets.json b/DashAI/front/src/utils/i18n/locales/en/datasets.json index 235558247..425d17068 100644 --- a/DashAI/front/src/utils/i18n/locales/en/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/en/datasets.json @@ -91,7 +91,8 @@ "requiresExactColumns_other": "Requires exactly {{required}} valid columns, but {{available}} available.", "requiresMinColumns_one": "Requires at least {{required}} valid column, but only {{available}} available.", "requiresMinColumns_other": "Requires at least {{required}} valid columns, but only {{available}} available.", - "zipContentsNotCompatible": "ZIP does not contain files compatible with the selected dataloader" + "zipContentsNotCompatible": "ZIP does not contain files compatible with the selected dataloader", + "cannotSaveEmptyDataset": "Cannot save dataset: all columns have been removed. Add columns before saving." }, "label": { "task": "Task", diff --git a/DashAI/front/src/utils/i18n/locales/es/datasets.json b/DashAI/front/src/utils/i18n/locales/es/datasets.json index 0c23011e5..af0e77a92 100644 --- a/DashAI/front/src/utils/i18n/locales/es/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/es/datasets.json @@ -96,7 +96,8 @@ "requiresMinColumns_one": "Requiere al menos {{required}} columna válida, pero solo {{available}} disponible.", "requiresMinColumns_many": "Requiere al menos {{required}} columnas válidas, pero solo {{available}} disponibles.", "requiresMinColumns_other": "Requiere al menos {{required}} columnas válidas, pero solo {{available}} disponibles.", - "zipContentsNotCompatible": "El ZIP no contiene archivos compatibles con el dataloader seleccionado" + "zipContentsNotCompatible": "El ZIP no contiene archivos compatibles con el dataloader seleccionado", + "cannotSaveEmptyDataset": "No se puede guardar el dataset: se eliminaron todas las columnas. Agrega columnas antes de guardar." }, "label": { "task": "Tarea", diff --git a/DashAI/front/src/utils/i18n/locales/pt/datasets.json b/DashAI/front/src/utils/i18n/locales/pt/datasets.json index d2230eeb5..b8dc6bb67 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/pt/datasets.json @@ -96,7 +96,8 @@ "requiresMinColumns_one": "Requer pelo menos {{required}} coluna válida, mas apenas {{available}} disponível.", "requiresMinColumns_many": "Requer pelo menos {{required}} colunas válidas, mas apenas {{available}} disponíveis.", "requiresMinColumns_other": "Requer pelo menos {{required}} colunas válidas, mas apenas {{available}} disponíveis.", - "zipContentsNotCompatible": "O arquivo ZIP não contém arquivos compatíveis com o dataloader selecionado" + "zipContentsNotCompatible": "O arquivo ZIP não contém arquivos compatíveis com o dataloader selecionado", + "cannotSaveEmptyDataset": "Não é possível salvar o dataset: todas as colunas foram removidas. Adicione colunas antes de salvar." }, "label": { "task": "Tarefa", diff --git a/DashAI/front/src/utils/i18n/locales/zh/datasets.json b/DashAI/front/src/utils/i18n/locales/zh/datasets.json index 5fe4078f2..10170e274 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/zh/datasets.json @@ -92,7 +92,8 @@ "requiresExactColumns_other": "需要恰好 {{required}} 个有效列,但只有 {{available}} 个可用。", "requiresMinColumns_one": "至少需要 {{required}} 个有效列,但只有 {{available}} 个可用。", "requiresMinColumns_other": "至少需要 {{required}} 个有效列,但只有 {{available}} 个可用。", - "zipContentsNotCompatible": "ZIP 文件不包含与所选数据加载器兼容的文件" + "zipContentsNotCompatible": "ZIP 文件不包含与所选数据加载器兼容的文件", + "cannotSaveEmptyDataset": "无法保存数据集:所有列已被删除,请在保存前添加列。" }, "label": { "task": "任务", From 0b8a630cdad98809c80d95ad484bda33d32085e7 Mon Sep 17 00:00:00 2001 From: Creylay Date: Tue, 30 Jun 2026 18:13:47 -0400 Subject: [PATCH 005/308] Add validation to prevent saving empty datasets and update error messages in multiple languages --- .../dataloaders/classes/dashai_dataset.py | 8 ++++++++ .../datasetCreation/SaveDatasetModal.jsx | 12 +++++++++++- .../notebook/DatasetPreviewNotebook.jsx | 19 ++++++++++--------- .../src/utils/i18n/locales/de/datasets.json | 2 +- .../src/utils/i18n/locales/en/datasets.json | 2 +- .../src/utils/i18n/locales/es/datasets.json | 2 +- .../src/utils/i18n/locales/pt/datasets.json | 2 +- .../src/utils/i18n/locales/zh/datasets.json | 2 +- 8 files changed, 34 insertions(+), 15 deletions(-) diff --git a/DashAI/back/dataloaders/classes/dashai_dataset.py b/DashAI/back/dataloaders/classes/dashai_dataset.py index 7eb55d3de..e8f3ed855 100644 --- a/DashAI/back/dataloaders/classes/dashai_dataset.py +++ b/DashAI/back/dataloaders/classes/dashai_dataset.py @@ -392,6 +392,9 @@ def _compute_quality_metadata(self, dataset_df) -> dict: Dictionary with quality indicators including completeness, constant columns, high cardinality columns, and quality score. """ + if dataset_df.empty: + return {} + # Count rows with missing values rows_with_any_nan = int(dataset_df.isna().any(axis=1).sum()) rows_with_multiple_nan = int((dataset_df.isna().sum(axis=1) > 1).sum()) @@ -515,6 +518,11 @@ def remove_columns(self, column_names: Union[str, List[str]]) -> "DashAIDataset" # Update self with modified dataset attributes self.__dict__.update(modified_dataset.__dict__) + # Keep self.types in sync so Arrow metadata stays consistent + if self.types is not None: + for col in column_names: + self.types.pop(col, None) + return self @beartype diff --git a/DashAI/front/src/components/notebooks/datasetCreation/SaveDatasetModal.jsx b/DashAI/front/src/components/notebooks/datasetCreation/SaveDatasetModal.jsx index e6b67419b..1875c110d 100644 --- a/DashAI/front/src/components/notebooks/datasetCreation/SaveDatasetModal.jsx +++ b/DashAI/front/src/components/notebooks/datasetCreation/SaveDatasetModal.jsx @@ -34,6 +34,7 @@ export function SaveDatasetModal({ appliedConverters, existingDatasets = [], notebook, + hasNoColumns = false, }) { const [name, setName] = useState(""); const [frozenDefaultName, setFrozenDefaultName] = useState(""); @@ -298,10 +299,19 @@ export function SaveDatasetModal({ {/* Footer - always visible */} + {hasNoColumns && ( + + {t("datasets:error.cannotSaveEmptyDataset")} + + )} { + if (!notebook?.file_path) return; + fetchDatasetPage(0, 1, null, null) + .then(({ total }) => setTotalRows(total)) + .catch(() => setTotalRows(null)); + }, [converterKey, fetchDatasetPage]); + if (!notebook) { return ( } onClick={(e) => { e.stopPropagation(); - if ( - convertersLoaded && - Object.keys(localColumnTypes).length === 0 - ) { - enqueueSnackbar(t("datasets:error.cannotSaveEmptyDataset"), { - variant: "error", - }); - return; - } setShowSaveDatasetModal(true); if (tourContext && tourContext.run) { setTimeout(() => { @@ -316,6 +316,7 @@ export default function DatasetPreviewNotebook({ )} existingDatasets={existingDatasets} notebook={notebook} + hasNoColumns={convertersLoaded && totalRows === 0} /> Date: Wed, 1 Jul 2026 10:15:20 -0400 Subject: [PATCH 006/308] fix: align converter scope action buttons in single footer row Move the Set column and help buttons into the same row as the stepper navigation footer, sharing one top border and baseline. Add an sx passthrough on FormSchemaButtonGroup so the footer border/padding can be neutralized when composed with other controls. --- .../ConverterTargetColumnModal.jsx | 3 +- .../converterCreation/ScopeStepConverter.jsx | 58 ++++++++++--------- .../shared/FormSchemaButtonGroup.jsx | 3 + 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/DashAI/front/src/components/notebooks/converterCreation/ConverterTargetColumnModal.jsx b/DashAI/front/src/components/notebooks/converterCreation/ConverterTargetColumnModal.jsx index 6c372e5c1..064ecb715 100644 --- a/DashAI/front/src/components/notebooks/converterCreation/ConverterTargetColumnModal.jsx +++ b/DashAI/front/src/components/notebooks/converterCreation/ConverterTargetColumnModal.jsx @@ -155,7 +155,6 @@ const ConverterTargetColumnModal = ({ variant="outlined" size="small" sx={{ - mr: 2, color: classColumnInitialValue === null ? "error.main" : "inherit", borderColor: classColumnInitialValue === null ? "error.main" : "inherit", @@ -214,7 +213,7 @@ const ConverterTargetColumnModal = ({ - )} - + + + + ); @@ -399,6 +433,7 @@ AddModelDialog.propTypes = { task_name: PropTypes.string, }), preselectedModel: PropTypes.string, + preselectedModelObject: PropTypes.object, existingRuns: PropTypes.array, onRunCreated: PropTypes.func, }; diff --git a/DashAI/front/src/components/models/ModelsRightBar.jsx b/DashAI/front/src/components/models/ModelsRightBar.jsx index 1d87f889e..f77ab0a0d 100644 --- a/DashAI/front/src/components/models/ModelsRightBar.jsx +++ b/DashAI/front/src/components/models/ModelsRightBar.jsx @@ -255,6 +255,7 @@ export default function ModelsRightBar({ onToggle }) { open={configOpen} onClose={closeConfig} preselectedModel={selectedModel?.name} + preselectedModelObject={selectedModel} session={session} existingRuns={existingRuns} onRunCreated={onRunCreated} diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx new file mode 100644 index 000000000..4fd5694ac --- /dev/null +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx @@ -0,0 +1,111 @@ +import React, { useState, useEffect } from "react"; +import { Box, Button, LinearProgress, Typography } from "@mui/material"; +import DownloadIcon from "@mui/icons-material/Download"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { useTranslation } from "react-i18next"; +import { useSnackbar } from "notistack"; +import { + downloadComponent, + deleteComponentDownload, + getComponentDownloadStatus, +} from "../../../api/component"; +import { startJobPolling } from "../../../utils/jobPoller"; + +const formatSize = (bytes) => { + if (bytes == null) return ""; + const mb = bytes / 1024 / 1024; + if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`; + return `${Math.round(mb)} MB`; +}; + +const ComponentDownloadControl = ({ component, onStatusChange }) => { + const { t } = useTranslation(["common"]); + const { enqueueSnackbar } = useSnackbar(); + const meta = component.metadata || {}; + const [downloaded, setDownloaded] = useState(Boolean(component.downloaded)); + const [downloading, setDownloading] = useState(false); + + useEffect(() => { + setDownloaded(Boolean(component.downloaded)); + }, [component.name, component.downloaded]); + + if (!meta.requires_download) return null; + + const finish = (isDownloaded) => { + setDownloading(false); + setDownloaded(isDownloaded); + if (onStatusChange) onStatusChange(isDownloaded); + }; + + const handleDownload = async () => { + setDownloading(true); + try { + const { id } = await downloadComponent(component.name); + startJobPolling( + id, + async () => { + const status = await getComponentDownloadStatus(component.name); + finish(status.downloaded); + enqueueSnackbar(t("common:componentDownload.done"), { + variant: "success", + }); + }, + () => { + finish(false); + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + }, + ); + } catch (e) { + finish(false); + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + } + }; + + const handleDelete = async () => { + await deleteComponentDownload(component.name); + finish(false); + }; + + if (downloading) { + return ( + + + {t("common:componentDownload.downloading")} + + + + ); + } + + if (downloaded) { + return ( + + ); + } + + return ( + + ); +}; + +export default ComponentDownloadControl; diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx new file mode 100644 index 000000000..6c90798e3 --- /dev/null +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx @@ -0,0 +1,41 @@ +import React from "react"; +import { screen, fireEvent, waitFor } from "@testing-library/react"; +import { renderWithProviders } from "../../../test-utils/renderWithProviders"; + +jest.mock("../../../api/component", () => ({ + downloadComponent: jest.fn(() => Promise.resolve({ id: "job-1" })), + deleteComponentDownload: jest.fn(() => Promise.resolve()), + getComponentDownloadStatus: jest.fn(() => + Promise.resolve({ downloaded: false, requires_download: true }), + ), +})); +jest.mock("../../../utils/jobPoller", () => ({ + startJobPolling: jest.fn(), + stopJobPolling: jest.fn(), + subscribeJobs: jest.fn(() => () => {}), +})); + +import ComponentDownloadControl from "./ComponentDownloadControl"; +import { downloadComponent } from "../../../api/component"; + +const component = { + name: "OpusMtEnRoaTransformer", + downloaded: false, + metadata: { requires_download: true, download_size_bytes: 310000000 }, +}; + +describe("ComponentDownloadControl", () => { + it("shows a download button with the size and triggers download", async () => { + renderWithProviders( + {}} + />, + ); + const button = await screen.findByRole("button", { name: /download/i }); + fireEvent.click(button); + await waitFor(() => + expect(downloadComponent).toHaveBeenCalledWith("OpusMtEnRoaTransformer"), + ); + }); +}); diff --git a/DashAI/front/src/utils/i18n/locales/de/common.json b/DashAI/front/src/utils/i18n/locales/de/common.json index 04d5433f4..2f777cdbb 100644 --- a/DashAI/front/src/utils/i18n/locales/de/common.json +++ b/DashAI/front/src/utils/i18n/locales/de/common.json @@ -160,6 +160,14 @@ "columnNameInvalidCharacters": "Spaltenname darf nur Buchstaben, Zahlen und Unterstriche enthalten", "columnNameAlreadyExists": "Eine Spalte mit diesem Namen existiert bereits", "errorRenamingColumn": "Fehler beim Umbenennen der Spalte", + "componentDownload": { + "download": "Herunterladen ({{size}})", + "delete": "Download loeschen", + "downloading": "Wird heruntergeladen...", + "done": "Komponente heruntergeladen", + "failed": "Download der Komponente fehlgeschlagen", + "mustDownload": "Dieses Modell muss vor der Nutzung heruntergeladen werden" + }, "jobQueue": { "title": "Aufgabenwarteschlange", "refresh": "Aktualisieren", diff --git a/DashAI/front/src/utils/i18n/locales/en/common.json b/DashAI/front/src/utils/i18n/locales/en/common.json index e16ba9fdd..0220e806f 100644 --- a/DashAI/front/src/utils/i18n/locales/en/common.json +++ b/DashAI/front/src/utils/i18n/locales/en/common.json @@ -160,6 +160,14 @@ "columnNameInvalidCharacters": "Column name can only contain letters, numbers and underscores", "columnNameAlreadyExists": "A column with this name already exists", "errorRenamingColumn": "Error renaming column", + "componentDownload": { + "download": "Download ({{size}})", + "delete": "Delete download", + "downloading": "Downloading...", + "done": "Component downloaded", + "failed": "Component download failed", + "mustDownload": "This model must be downloaded before use" + }, "jobQueue": { "title": "Job Queue", "refresh": "Refresh", diff --git a/DashAI/front/src/utils/i18n/locales/es/common.json b/DashAI/front/src/utils/i18n/locales/es/common.json index f01424a64..dbe4bbf16 100644 --- a/DashAI/front/src/utils/i18n/locales/es/common.json +++ b/DashAI/front/src/utils/i18n/locales/es/common.json @@ -160,6 +160,14 @@ "columnNameInvalidCharacters": "El nombre de la columna solo puede contener letras, números y guiones bajos", "columnNameAlreadyExists": "Ya existe una columna con este nombre", "errorRenamingColumn": "Error al renombrar la columna", + "componentDownload": { + "download": "Descargar ({{size}})", + "delete": "Eliminar descarga", + "downloading": "Descargando...", + "done": "Componente descargado", + "failed": "La descarga del componente ha fallado", + "mustDownload": "Este modelo debe descargarse antes de usarlo" + }, "jobQueue": { "title": "Cola de trabajos", "refresh": "Actualizar", diff --git a/DashAI/front/src/utils/i18n/locales/pt/common.json b/DashAI/front/src/utils/i18n/locales/pt/common.json index 22c81d1a9..e52110881 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/common.json +++ b/DashAI/front/src/utils/i18n/locales/pt/common.json @@ -160,6 +160,14 @@ "columnNameInvalidCharacters": "O nome da coluna só pode conter letras, números e underscores", "columnNameAlreadyExists": "Já existe uma coluna com este nome", "errorRenamingColumn": "Erro ao renomear a coluna", + "componentDownload": { + "download": "Baixar ({{size}})", + "delete": "Remover download", + "downloading": "Baixando...", + "done": "Componente baixado", + "failed": "Falha ao baixar o componente", + "mustDownload": "Este modelo precisa ser baixado antes de usar" + }, "jobQueue": { "title": "Fila de trabalhos", "refresh": "Atualizar", diff --git a/DashAI/front/src/utils/i18n/locales/zh/common.json b/DashAI/front/src/utils/i18n/locales/zh/common.json index 1e6e7085e..d0623fb42 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/common.json +++ b/DashAI/front/src/utils/i18n/locales/zh/common.json @@ -160,6 +160,14 @@ "columnNameInvalidCharacters": "列名只能包含字母、数字和下划线", "columnNameAlreadyExists": "已存在同名列", "errorRenamingColumn": "重命名列时出错", + "componentDownload": { + "download": "下载 ({{size}})", + "delete": "删除下载", + "downloading": "下载中...", + "done": "组件已下载", + "failed": "组件下载失败", + "mustDownload": "使用前必须先下载此模型" + }, "jobQueue": { "title": "任务队列", "refresh": "刷新", From 513ae7a32b87d2cf3696d0cfbde761d7e721d514 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 1 Jul 2026 15:26:12 -0400 Subject: [PATCH 031/308] fix: stop component download poller on unmount and handle delete errors --- .../src/components/models/AddModelDialog.jsx | 6 ++++- .../models/model/ComponentDownloadControl.jsx | 24 +++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/DashAI/front/src/components/models/AddModelDialog.jsx b/DashAI/front/src/components/models/AddModelDialog.jsx index be97edb44..80604a474 100644 --- a/DashAI/front/src/components/models/AddModelDialog.jsx +++ b/DashAI/front/src/components/models/AddModelDialog.jsx @@ -433,7 +433,11 @@ AddModelDialog.propTypes = { task_name: PropTypes.string, }), preselectedModel: PropTypes.string, - preselectedModelObject: PropTypes.object, + preselectedModelObject: PropTypes.shape({ + name: PropTypes.string, + downloaded: PropTypes.bool, + metadata: PropTypes.object, + }), existingRuns: PropTypes.array, onRunCreated: PropTypes.func, }; diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx index 4fd5694ac..bcc6e207c 100644 --- a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { Box, Button, LinearProgress, Typography } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import DeleteIcon from "@mui/icons-material/Delete"; @@ -9,7 +9,7 @@ import { deleteComponentDownload, getComponentDownloadStatus, } from "../../../api/component"; -import { startJobPolling } from "../../../utils/jobPoller"; +import { startJobPolling, stopJobPolling } from "../../../utils/jobPoller"; const formatSize = (bytes) => { if (bytes == null) return ""; @@ -24,11 +24,18 @@ const ComponentDownloadControl = ({ component, onStatusChange }) => { const meta = component.metadata || {}; const [downloaded, setDownloaded] = useState(Boolean(component.downloaded)); const [downloading, setDownloading] = useState(false); + const pollerIdRef = useRef(null); useEffect(() => { setDownloaded(Boolean(component.downloaded)); }, [component.name, component.downloaded]); + useEffect(() => { + return () => { + if (pollerIdRef.current != null) stopJobPolling(pollerIdRef.current); + }; + }, []); + if (!meta.requires_download) return null; const finish = (isDownloaded) => { @@ -41,9 +48,11 @@ const ComponentDownloadControl = ({ component, onStatusChange }) => { setDownloading(true); try { const { id } = await downloadComponent(component.name); + pollerIdRef.current = id; startJobPolling( id, async () => { + pollerIdRef.current = null; const status = await getComponentDownloadStatus(component.name); finish(status.downloaded); enqueueSnackbar(t("common:componentDownload.done"), { @@ -51,6 +60,7 @@ const ComponentDownloadControl = ({ component, onStatusChange }) => { }); }, () => { + pollerIdRef.current = null; finish(false); enqueueSnackbar(t("common:componentDownload.failed"), { variant: "error", @@ -66,8 +76,14 @@ const ComponentDownloadControl = ({ component, onStatusChange }) => { }; const handleDelete = async () => { - await deleteComponentDownload(component.name); - finish(false); + try { + await deleteComponentDownload(component.name); + finish(false); + } catch { + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + } }; if (downloading) { From 5c9caac164ce481eb0892a02b279f203156796e6 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 1 Jul 2026 15:38:15 -0400 Subject: [PATCH 032/308] fix: persist Opus-MT tokenizer with runs so prediction survives download deletion --- .../hugging_face/base_opus_mt_transformer.py | 27 +++-- .../back/models/test_opus_mt_downloadable.py | 105 ++++++++++++++++++ 2 files changed, 124 insertions(+), 8 deletions(-) diff --git a/DashAI/back/models/hugging_face/base_opus_mt_transformer.py b/DashAI/back/models/hugging_face/base_opus_mt_transformer.py index 38eeecf04..a755cf80d 100644 --- a/DashAI/back/models/hugging_face/base_opus_mt_transformer.py +++ b/DashAI/back/models/hugging_face/base_opus_mt_transformer.py @@ -30,10 +30,13 @@ class OpusMtTransformerMixin(HFDownloadableMixin, TranslationModel): here so each language-pair subclass only needs to set class attributes. .. note:: - The pretrained weights must be downloaded first via ``download()`` - (this component requires a download). ``__init__`` loads the tokenizer - and model from the component's local folder; it does not fetch from - the Hugging Face Hub. + For fresh training the pretrained weights must be downloaded first via + ``download()`` (this component requires a download). ``__init__`` with + no ``pretrained_dir`` loads the tokenizer and model from the + component's local download folder; it does not fetch from the Hugging + Face Hub. When loading a previously saved run, ``pretrained_dir`` is + set to the run directory so the tokenizer is read from there, making + trained runs self-contained. """ MODEL_NAME: str = "" @@ -53,13 +56,19 @@ def hf_repos(cls): """ return [(cls.MODEL_NAME, "model")] if cls.MODEL_NAME else [] - def __init__(self, model=None, **kwargs): + def __init__(self, model=None, pretrained_dir: Optional[str] = None, **kwargs): """Initialize tokenizer and seq2seq model. Parameters ---------- model : transformers.PreTrainedModel or None Preloaded model to reuse instead of downloading weights. + pretrained_dir : str or None + Directory from which to load the tokenizer (and model weights when + ``model`` is ``None``). When ``None`` the component's download + folder is used, which is the correct path for fresh training. Pass + the run directory when restoring a saved run so the trained run + becomes self-contained and independent of the download folder. **kwargs Training hyperparameters forwarded to ``validate_and_transform``. """ @@ -73,8 +82,8 @@ def __init__(self, model=None, **kwargs): ) self.model_name = self.MODEL_NAME - local_dir = str(self._repo_dir(self.MODEL_NAME)) - self.tokenizer = AutoTokenizer.from_pretrained(local_dir) + source = pretrained_dir or str(self._repo_dir(self.MODEL_NAME)) + self.tokenizer = AutoTokenizer.from_pretrained(source) self.training_args = { "num_train_epochs": kwargs.get("num_train_epochs", 2), @@ -95,7 +104,7 @@ def __init__(self, model=None, **kwargs): if model is None: from transformers import AutoModelForSeq2SeqLM - self.model = AutoModelForSeq2SeqLM.from_pretrained(local_dir) + self.model = AutoModelForSeq2SeqLM.from_pretrained(source) else: self.model = model @@ -252,6 +261,7 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args.get("num_train_epochs"), @@ -274,6 +284,7 @@ def load(cls, filename: Union[str, "Path"]): loaded_model = cls( model=model, + pretrained_dir=str(filename), num_train_epochs=custom_params.get("num_train_epochs"), batch_size=custom_params.get("batch_size"), learning_rate=custom_params.get("learning_rate"), diff --git a/tests/back/models/test_opus_mt_downloadable.py b/tests/back/models/test_opus_mt_downloadable.py index fc91adf81..f674b40d8 100644 --- a/tests/back/models/test_opus_mt_downloadable.py +++ b/tests/back/models/test_opus_mt_downloadable.py @@ -1,5 +1,7 @@ """Tests that OpusMtTransformerMixin subclasses expose download metadata.""" +from unittest.mock import MagicMock, patch + import pytest from kink import di @@ -45,3 +47,106 @@ def test_opus_mt_is_downloaded_uses_component_dir(component_root): repo_dir.mkdir(parents=True) (repo_dir / "config.json").write_text("{}") assert OpusMtEnESTransformer.is_downloaded() is True + + +# --------------------------------------------------------------------------- +# Self-contained run tests (no real weights required) +# --------------------------------------------------------------------------- + + +@pytest.fixture +def component_root_for_save_load(tmp_path): + """Inject a temporary COMPONENT_PATH so _repo_dir resolves without touching + the real filesystem or requiring a real download. + """ + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +def test_save_persists_tokenizer(tmp_path, component_root_for_save_load): + """save() must call both model.save_pretrained and tokenizer.save_pretrained.""" + mock_tokenizer = MagicMock() + mock_model = MagicMock() + + with ( + patch("transformers.AutoTokenizer") as tok_cls, + patch("transformers.AutoModelForSeq2SeqLM"), + patch("transformers.AutoConfig") as cfg_cls, + ): + tok_cls.from_pretrained.return_value = mock_tokenizer + + instance = OpusMtEnESTransformer( + model=mock_model, + pretrained_dir=str(tmp_path), + num_train_epochs=1, + batch_size=2, + learning_rate=2e-5, + device="CPU", + weight_decay=0.01, + log_train_every_n_epochs=None, + log_train_every_n_steps=None, + log_validation_every_n_epochs=None, + log_validation_every_n_steps=None, + ) + instance.fitted = True + + save_dir = tmp_path / "run" + cfg_cls.from_pretrained.return_value = MagicMock() + + instance.save(save_dir) + + mock_model.save_pretrained.assert_called_once_with(save_dir) + mock_tokenizer.save_pretrained.assert_called_once_with(save_dir) + + +def test_load_tokenizer_from_run_dir_not_download_folder( + tmp_path, component_root_for_save_load +): + """load() must load the tokenizer from the run dir, not the component download + folder. This verifies that trained runs are self-contained. + """ + run_dir = tmp_path / "my_run" + run_dir.mkdir() + + mock_model = MagicMock() + mock_tokenizer = MagicMock() + mock_config = MagicMock() + mock_config.custom_params = { + "num_train_epochs": 1, + "batch_size": 2, + "learning_rate": 2e-5, + "device": "CPU", + "weight_decay": 0.01, + "fitted": True, + } + + with ( + patch("transformers.AutoTokenizer") as tok_cls, + patch("transformers.AutoModelForSeq2SeqLM") as model_cls, + patch("transformers.AutoConfig") as cfg_cls, + ): + tok_cls.from_pretrained.return_value = mock_tokenizer + model_cls.from_pretrained.return_value = mock_model + cfg_cls.from_pretrained.return_value = mock_config + + loaded = OpusMtEnESTransformer.load(run_dir) + + # The tokenizer must be loaded from the run dir. + tok_cls.from_pretrained.assert_called_once_with(str(run_dir)) + + # The component download folder (under COMPONENT_PATH) must NOT be used. + component_download_dir = str( + component_root_for_save_load / "OpusMtEnESTransformer" / "opus-mt-en-es" + ) + for call in tok_cls.from_pretrained.call_args_list: + assert call.args[0] != component_download_dir, ( + "tokenizer was loaded from the component download folder, not the run dir" + ) + + assert loaded.fitted is True From 2b2ffaea2a616dab7c047ab83502ff79782b6cb5 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 1 Jul 2026 23:59:05 -0400 Subject: [PATCH 033/308] feat: block undownloaded models in the models list with inline download An undownloaded, download-required model now renders disabled in the ModelsRightBar list and cannot open the config dialog; a download control is shown beneath it and the list refreshes on completion. The in-dialog download control is removed (the list is the download surface); the disabled-create-button guard remains as a safety net. --- .../src/components/models/AddModelDialog.jsx | 7 --- .../src/components/models/ModelsRightBar.jsx | 59 ++++++++++++++++--- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/DashAI/front/src/components/models/AddModelDialog.jsx b/DashAI/front/src/components/models/AddModelDialog.jsx index 80604a474..2bd46d5c1 100644 --- a/DashAI/front/src/components/models/AddModelDialog.jsx +++ b/DashAI/front/src/components/models/AddModelDialog.jsx @@ -27,7 +27,6 @@ import { createRun } from "../../api/run"; import { useTranslation } from "react-i18next"; import { useTourContext } from "../tour/TourProvider"; import { checkIfHaveOptimazers } from "../../utils/schema"; -import ComponentDownloadControl from "./model/ComponentDownloadControl"; /** * Dialog for adding a new model run to a session @@ -325,12 +324,6 @@ function AddModelDialog({ )} - {preselectedModelObject?.metadata?.requires_download && ( - - )} )} diff --git a/DashAI/front/src/components/models/ModelsRightBar.jsx b/DashAI/front/src/components/models/ModelsRightBar.jsx index f77ab0a0d..b4864cd07 100644 --- a/DashAI/front/src/components/models/ModelsRightBar.jsx +++ b/DashAI/front/src/components/models/ModelsRightBar.jsx @@ -7,6 +7,7 @@ import { useSnackbar } from "notistack"; import SideBar from "../threeSectionLayout/panelContainers/SideBar"; import { getComponents } from "../../api/component"; import ModelListItem from "./model/ModelListItem"; +import ComponentDownloadControl from "./model/ComponentDownloadControl"; import { useTranslation } from "react-i18next"; import { useTourContext } from "../tour/TourProvider"; import { useModels } from "./ModelsContext"; @@ -20,7 +21,7 @@ export default function ModelsRightBar({ onToggle }) { const [searchQuery, setSearchQuery] = useState(""); const [loading, setLoading] = useState(false); const { enqueueSnackbar } = useSnackbar(); - const { t } = useTranslation(["models"]); + const { t } = useTranslation(["models", "common"]); const { selectedSession: session, @@ -89,6 +90,11 @@ export default function ModelsRightBar({ onToggle }) { }); return; } + // A download-required model that has not been downloaded cannot be + // configured; it is blocked in the list with an inline download control. + if (model.metadata?.requires_download && !model.downloaded) { + return; + } selectModel(model); if (tourContext?.run && tourContext?.stepIndex === 2) { const waitForElement = () => { @@ -236,14 +242,49 @@ export default function ModelsRightBar({ onToggle }) { ) : ( - {filteredModels.map((model, index) => ( - handleModelClick(model)} - data-tour={index === 0 ? "first-model" : undefined} - /> - ))} + {filteredModels.map((model, index) => { + const needsDownload = + Boolean(model.metadata?.requires_download) && + !model.downloaded; + return ( + + handleModelClick(model) + } + data-tour={index === 0 ? "first-model" : undefined} + /> + {needsDownload && ( + { + if (isDownloaded) fetchModels(); + }} + /> + )} + + ); + })} )} From 06ec7edf2fa3ce93c88b8216e472d42b9b306460 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 00:00:46 -0400 Subject: [PATCH 034/308] feat: block undownloaded components in ComponentSelector cards with inline download Cards for a download-required, not-yet-downloaded component are disabled (unclickable, dimmed) and render an inline download control; an onDownloadChange callback lets parents refresh. Non-downloadable components are unchanged. --- .../components/custom/ComponentSelector.jsx | 107 +++++++++++------- 1 file changed, 64 insertions(+), 43 deletions(-) diff --git a/DashAI/front/src/components/custom/ComponentSelector.jsx b/DashAI/front/src/components/custom/ComponentSelector.jsx index b12423a2c..97be244b8 100644 --- a/DashAI/front/src/components/custom/ComponentSelector.jsx +++ b/DashAI/front/src/components/custom/ComponentSelector.jsx @@ -18,6 +18,7 @@ import { Check as CheckIcon, } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; +import ComponentDownloadControl from "../models/model/ComponentDownloadControl"; const ALL_CATEGORY = "All"; const SEARCH_THRESHOLD = 10; @@ -41,8 +42,9 @@ function ComponentSelector({ flat = false, tourDataFor = null, tourDataMatchFn = null, + onDownloadChange = null, }) { - const { t } = useTranslation("custom"); + const { t } = useTranslation(["custom", "common"]); const [search, setSearch] = useState(""); const [activeCategory, setActiveCategory] = useState(ALL_CATEGORY); @@ -107,6 +109,8 @@ function ComponentSelector({ const renderCard = (component) => { const isSelected = selected?.name === component.name; const icon = getIcon?.(component); + const needsDownload = + Boolean(component.metadata?.requires_download) && !component.downloaded; const isCsvComponent = tourDataFor && (tourDataMatchFn @@ -116,61 +120,76 @@ function ComponentSelector({ handleSelect(component)} + onClick={needsDownload ? undefined : () => handleSelect(component)} data-tour={isCsvComponent ? tourDataFor : undefined} sx={{ p: 3, display: "flex", + flexDirection: "column", gap: 3, - alignItems: "flex-start", - cursor: "pointer", + cursor: needsDownload ? "not-allowed" : "pointer", border: 1, borderColor: isSelected ? "primary.main" : "divider", bgcolor: isSelected ? "action.selected" : "background.paper", + opacity: needsDownload ? 0.6 : 1, transition: "border-color 0.15s, background 0.15s", - "&:hover": { borderColor: "secondary.main" }, + "&:hover": { + borderColor: needsDownload ? "divider" : "secondary.main", + }, }} > - {icon && ( - - {icon} + + {icon && ( + + {icon} + + )} + + + {getLabel(component)} + + + {getDescription(component, t("noDescriptionAvailable"))} + - )} - - - {getLabel(component)} - - - {getDescription(component, t("noDescriptionAvailable"))} - + {isSelected && ( + + )} - {isSelected && ( - + {needsDownload && ( + e.stopPropagation()}> + { + if (isDownloaded) onDownloadChange?.(component); + }} + /> + )} ); @@ -358,7 +377,9 @@ ComponentSelector.propTypes = { emptyText: PropTypes.string, getIcon: PropTypes.func, flat: PropTypes.bool, + tourDataFor: PropTypes.string, tourDataMatchFn: PropTypes.func, + onDownloadChange: PropTypes.func, }; export default ComponentSelector; From 4da81af7f4e1aa27144a5a87fc6be20ed9a8ffa5 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 00:11:19 -0400 Subject: [PATCH 035/308] feat: expose download metadata and gate generative models Add BaseGenerativeModel.get_metadata exposing requires_download and download_size_bytes, a 409/422 download gate in upload_generative_session, and a defensive JobError in GenerativeJob.run for undownloaded models. --- .../api_v1/endpoints/generative_session.py | 19 +++++ DashAI/back/job/generative_job.py | 12 +++ DashAI/back/models/base_generative_model.py | 17 ++++- .../test_generative_session_download_gate.py | 73 +++++++++++++++++++ .../test_generative_download_metadata.py | 35 +++++++++ 5 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 tests/back/api/test_generative_session_download_gate.py create mode 100644 tests/back/models/test_generative_download_metadata.py diff --git a/DashAI/back/api/api_v1/endpoints/generative_session.py b/DashAI/back/api/api_v1/endpoints/generative_session.py index 8211dbcf9..982b2b7c3 100644 --- a/DashAI/back/api/api_v1/endpoints/generative_session.py +++ b/DashAI/back/api/api_v1/endpoints/generative_session.py @@ -38,6 +38,13 @@ async def upload_generative_session( with session_factory() as db: try: + # Guard: unknown model name -> 422 + if params.model_name not in component_registry: + raise HTTPException( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=f"Unknown model '{params.model_name}'", + ) + # Check if the model is registered try: model_class = component_registry[params.model_name]["class"] @@ -47,6 +54,18 @@ async def upload_generative_session( detail=f"Model {params.model_name} is not registered.", ) from e + # Guard: model requires download but has not been downloaded -> 409 + entry = component_registry[params.model_name] + if getattr(entry["class"], "REQUIRES_DOWNLOAD", False) and not entry.get( + "downloaded", False + ): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=( + f"Model {params.model_name} must be downloaded before use." + ), + ) + # Check if the model is a subclass of GenerativeModel if not issubclass(model_class, BaseGenerativeModel): raise HTTPException( diff --git a/DashAI/back/job/generative_job.py b/DashAI/back/job/generative_job.py index e52c58a72..bbf5de47e 100644 --- a/DashAI/back/job/generative_job.py +++ b/DashAI/back/job/generative_job.py @@ -151,8 +151,20 @@ def run( model_class = component_registry[generative_session.model_name][ "class" ] + if ( + getattr(model_class, "REQUIRES_DOWNLOAD", False) + and not model_class.is_downloaded() + ): + raise JobError( + f"Model {generative_session.model_name} is not downloaded." + " Download it before use." + ) params = generative_session.parameters model: BaseGenerativeModel = model_class(**params) + except JobError: + generative_process.set_status_as_error() + db.commit() + raise except Exception as e: log.exception(e) generative_process.set_status_as_error() diff --git a/DashAI/back/models/base_generative_model.py b/DashAI/back/models/base_generative_model.py index cff0e18e1..62dbcb9af 100644 --- a/DashAI/back/models/base_generative_model.py +++ b/DashAI/back/models/base_generative_model.py @@ -1,5 +1,5 @@ from abc import ABCMeta, abstractmethod -from typing import Any, Final, List, Tuple, Union +from typing import Any, Dict, Final, List, Tuple, Union from DashAI.back.config_object import ConfigObject @@ -15,6 +15,21 @@ class BaseGenerativeModel(ConfigObject, metaclass=ABCMeta): TYPE: Final[str] = "GenerativeModel" + @classmethod + def get_metadata(cls) -> Dict[str, Any]: + """Get metadata values for the current generative model. + + Returns + ------- + Dict[str, Any] + Dictionary indicating whether the model requires a download + before use and the expected download size in bytes. + """ + metadata: Dict[str, Any] = {} + metadata["requires_download"] = bool(getattr(cls, "REQUIRES_DOWNLOAD", False)) + metadata["download_size_bytes"] = getattr(cls, "DOWNLOAD_SIZE_BYTES", None) + return metadata + @abstractmethod def __init__(self, **kwargs): """Initialize the generative model with configuration parameters. diff --git a/tests/back/api/test_generative_session_download_gate.py b/tests/back/api/test_generative_session_download_gate.py new file mode 100644 index 000000000..b284200a2 --- /dev/null +++ b/tests/back/api/test_generative_session_download_gate.py @@ -0,0 +1,73 @@ +"""Tests for the generative-session creation download gate.""" + +from kink import di + +_SESSION_PAYLOAD_BASE = { + "parameters": {}, + "name": "gen-gate-test-session", + "description": None, +} + + +class _FakeDownloadableGenerativeModel: + """Minimal stub: download-required generative model that is not downloaded.""" + + REQUIRES_DOWNLOAD = True + + @classmethod + def is_downloaded(cls): + return False + + +class _FakeGenerativeRegistry: + """Registry wrapper that injects FakeDownloadableGenerativeModel.""" + + def __init__(self, real): + self._real = real + + def __getitem__(self, name): + if name == "FakeDownloadableGenerativeModel": + return { + "class": _FakeDownloadableGenerativeModel, + "downloaded": False, + } + return self._real[name] + + def get_components_by_types(self, select=None, ignore=None): + return self._real.get_components_by_types(select=select, ignore=ignore) + + def __contains__(self, name): + return name == "FakeDownloadableGenerativeModel" or name in self._real + + +def test_upload_generative_session_rejects_undownloaded_model(client): + """Creating a session for a not-yet-downloaded model must return HTTP 409.""" + old = di["component_registry"] + di["component_registry"] = _FakeGenerativeRegistry(old) + try: + resp = client.post( + "/api/v1/generative-session/", + json={ + "model_name": "FakeDownloadableGenerativeModel", + "task_name": "TextToTextGenerationTask", + **_SESSION_PAYLOAD_BASE, + }, + ) + finally: + di["component_registry"] = old + + assert resp.status_code == 409 + assert "download" in resp.json()["detail"].lower() + + +def test_upload_generative_session_unknown_model_422(client): + """POSTing a session with an unregistered model_name must return HTTP 422.""" + resp = client.post( + "/api/v1/generative-session/", + json={ + "model_name": "__totally_bogus_generative_model_xyz__", + "task_name": "TextToTextGenerationTask", + **_SESSION_PAYLOAD_BASE, + }, + ) + assert resp.status_code == 422 diff --git a/tests/back/models/test_generative_download_metadata.py b/tests/back/models/test_generative_download_metadata.py new file mode 100644 index 000000000..09ff25d01 --- /dev/null +++ b/tests/back/models/test_generative_download_metadata.py @@ -0,0 +1,35 @@ +"""Tests for BaseGenerativeModel.get_metadata download fields.""" + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.base_generative_model import BaseGenerativeModel + + +class _PlainGenerativeModel(BaseGenerativeModel): + def __init__(self, **kwargs): + pass + + def generate(self, input): + return [] + + +class _DownloadableGenerativeModel(HFDownloadableMixin, BaseGenerativeModel): + HF_REPOS = [("owner/x", "model")] + DOWNLOAD_SIZE_BYTES = 1234 + + def __init__(self, **kwargs): + pass + + def generate(self, input): + return [] + + +def test_plain_generative_model_not_downloadable(): + meta = _PlainGenerativeModel.get_metadata() + assert meta["requires_download"] is False + assert meta["download_size_bytes"] is None + + +def test_downloadable_generative_model_metadata(): + meta = _DownloadableGenerativeModel.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == 1234 From f284a7e97e34bc71512cdfaedc3848cdef1ddb85 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 00:23:20 -0400 Subject: [PATCH 036/308] feat: support single-file (allow_patterns) downloads in HFDownloadableMixin --- .../dependencies/downloads/downloadable.py | 98 +++++++++++++++++-- tests/back/downloads/test_downloadable.py | 42 ++++++++ 2 files changed, 132 insertions(+), 8 deletions(-) diff --git a/DashAI/back/dependencies/downloads/downloadable.py b/DashAI/back/dependencies/downloads/downloadable.py index 366d1c814..33bef1a25 100644 --- a/DashAI/back/dependencies/downloads/downloadable.py +++ b/DashAI/back/dependencies/downloads/downloadable.py @@ -9,7 +9,7 @@ import logging import pathlib import shutil -from typing import Callable, List, Optional, Tuple +from typing import Callable, List, Optional, Tuple, Union from huggingface_hub import snapshot_download from kink import di @@ -69,35 +69,117 @@ class HFDownloadableMixin(DownloadableMixin): Each repo is downloaded into ``component_dir()/``. Subclasses set ``HF_REPOS`` or, for a dynamic repo (e.g. derived from a per-subclass ``MODEL_NAME``), override ``hf_repos``. + + ``HF_REPOS`` entries accept two shapes: + + * ``(repo_id, repo_type)`` -- full snapshot download (original behavior). + * ``(repo_id, repo_type, allow_patterns)`` -- partial download; only files + matching the glob patterns in ``allow_patterns`` are fetched. """ - HF_REPOS: List[Tuple[str, str]] = [] + HF_REPOS: List[Union[Tuple[str, str], Tuple[str, str, List[str]]]] = [] @classmethod - def hf_repos(cls) -> List[Tuple[str, str]]: - """Return the ``(repo_id, repo_type)`` pairs this component needs.""" + def hf_repos(cls) -> List[Union[Tuple[str, str], Tuple[str, str, List[str]]]]: + """Return the repo entries this component needs. + + Returns + ------- + list of tuple + Each entry is either ``(repo_id, repo_type)`` or + ``(repo_id, repo_type, allow_patterns)``. + """ return list(cls.HF_REPOS) + @classmethod + def _unpack_entry( + cls, + entry: Union[Tuple[str, str], Tuple[str, str, List[str]]], + ) -> Tuple[str, str, Optional[List[str]]]: + """Normalise a repo entry into ``(repo_id, repo_type, allow_patterns)``. + Parameters + ---------- + entry : tuple + Either a 2-tuple ``(repo_id, repo_type)`` or a 3-tuple + ``(repo_id, repo_type, allow_patterns)``. + + Returns + ------- + tuple of (str, str, list[str] or None) + ``repo_id``, ``repo_type``, and ``allow_patterns`` (``None`` when + the entry was a 2-tuple, meaning a full snapshot download). + + Raises + ------ + ValueError + If ``entry`` has a length other than 2 or 3. + """ + if len(entry) == 2: + rid, rtype = entry + return rid, rtype, None + if len(entry) == 3: + rid, rtype, patterns = entry + return rid, rtype, patterns + raise ValueError( + f"HF_REPOS entries must be 2- or 3-tuples; " + f"got length {len(entry)}: {entry!r}" + ) + @classmethod def _repo_dir(cls, repo_id: str) -> pathlib.Path: - """Return the local directory for a single repo under component_dir().""" + """Return the local directory for a single repo under component_dir(). + + Parameters + ---------- + repo_id : str + HuggingFace repo identifier, e.g. ``"owner/model-name"``. + + Returns + ------- + pathlib.Path + ``component_dir()/``. + """ return cls.component_dir() / repo_id.split("/")[-1] @classmethod def is_downloaded(cls) -> bool: + """Return whether all repo directories exist and are non-empty. + + Returns + ------- + bool + ``True`` when every repo listed in ``hf_repos()`` has a non-empty + local directory; ``False`` otherwise (including when the list is + empty). + """ repos = cls.hf_repos() return bool(repos) and all( cls._repo_dir(rid).is_dir() and any(cls._repo_dir(rid).iterdir()) - for rid, _ in repos + for rid, *_ in repos ) @classmethod def download(cls, report: Optional[ProgressReporter] = None) -> None: - for rid, rtype in cls.hf_repos(): + """Download all repos listed in ``hf_repos()`` into ``component_dir()``. + + Parameters + ---------- + report : ProgressReporter, optional + Callback invoked before each repo download with + ``report(None, "Downloading ")``. ``None`` means no + progress reporting. + """ + for entry in cls.hf_repos(): + rid, rtype, allow_patterns = cls._unpack_entry(entry) target = cls._repo_dir(rid) target.mkdir(parents=True, exist_ok=True) if report is not None: # snapshot_download exposes no aggregate byte count, so progress # is reported as indeterminate (None) with a phase message. report(None, f"Downloading {rid}") - snapshot_download(repo_id=rid, repo_type=rtype, local_dir=str(target)) + kwargs = {} + if allow_patterns is not None: + kwargs["allow_patterns"] = allow_patterns + snapshot_download( + repo_id=rid, repo_type=rtype, local_dir=str(target), **kwargs + ) diff --git a/tests/back/downloads/test_downloadable.py b/tests/back/downloads/test_downloadable.py index 847c74666..6013a276d 100644 --- a/tests/back/downloads/test_downloadable.py +++ b/tests/back/downloads/test_downloadable.py @@ -71,3 +71,45 @@ def test_delete_removes_component_dir(components_root): _populate(components_root, _Dummy, "model-a") _Dummy.delete() assert not (components_root / "_Dummy").exists() + + +# --------------------------------------------------------------------------- +# 3-tuple (allow_patterns) support +# --------------------------------------------------------------------------- + + +class _DummyPartial(dl.HFDownloadableMixin): + HF_REPOS = [("owner/model-a", "model", ["*8_0.gguf"])] + + +def test_download_3tuple_passes_allow_patterns(components_root): + with mock.patch.object(dl, "snapshot_download") as snap: + _DummyPartial.download(lambda frac, msg: None) + snap.assert_called_once_with( + repo_id="owner/model-a", + repo_type="model", + local_dir=str(components_root / "_DummyPartial" / "model-a"), + allow_patterns=["*8_0.gguf"], + ) + + +def test_download_2tuple_no_allow_patterns(components_root): + with mock.patch.object(dl, "snapshot_download") as snap: + _Dummy.download(lambda frac, msg: None) + _call_kwargs = snap.call_args.kwargs + assert "allow_patterns" not in _call_kwargs + + +def test_is_downloaded_3tuple_true_when_present(components_root): + _populate(components_root, _DummyPartial, "model-a") + assert _DummyPartial.is_downloaded() is True + + +def test_is_downloaded_3tuple_false_when_absent(components_root): + assert _DummyPartial.is_downloaded() is False + + +def test_is_downloaded_3tuple_false_when_empty_dir(components_root): + d = components_root / "_DummyPartial" / "model-a" + d.mkdir(parents=True) + assert _DummyPartial.is_downloaded() is False From a07686c1d6ec107902f2c2298cd34204c6414584 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 00:37:57 -0400 Subject: [PATCH 037/308] feat: split GGUF text models into per-checkpoint downloadable components Replace the four enum-based model classes (QwenModel, SmolLMModel, LlamaModel, MistralModel) with nine thin subclasses of GGUFTextGenerationModel (HFDownloadableMixin). Each subclass represents one checkpoint and carries REPO_ID, GGUF_PATTERN, and DOWNLOAD_SIZE_BYTES so the download machinery can manage it independently. The shared schema and __init__/generate logic live in gguf_text_generation_base.py. --- DashAI/back/initial_components.py | 34 +- .../hugging_face/gguf_text_generation_base.py | 389 ++++++++++++ .../back/models/hugging_face/llama_model.py | 587 +++++------------- .../back/models/hugging_face/mistral_model.py | 497 +++------------ DashAI/back/models/hugging_face/qwen_model.py | 534 +++------------- .../back/models/hugging_face/smol_lm_model.py | 507 +++------------ .../back/models/test_gguf_text_generation.py | 89 +++ 7 files changed, 927 insertions(+), 1710 deletions(-) create mode 100644 DashAI/back/models/hugging_face/gguf_text_generation_base.py create mode 100644 tests/back/models/test_gguf_text_generation.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index d12cbafa9..54e166f7e 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -145,10 +145,17 @@ from DashAI.back.models.hugging_face.deberta_v3_transformer import DebertaV3Transformer from DashAI.back.models.hugging_face.distilbert_transformer import DistilBertTransformer from DashAI.back.models.hugging_face.electra_transformer import ElectraTransformer -from DashAI.back.models.hugging_face.llama_model import LlamaModel +from DashAI.back.models.hugging_face.llama_model import ( + Llama31_8BInstruct, + Llama32_1BInstruct, + Llama32_3BInstruct, +) from DashAI.back.models.hugging_face.m2m100_transformer import M2M100Transformer from DashAI.back.models.hugging_face.minilm_transformer import MiniLMTransformer -from DashAI.back.models.hugging_face.mistral_model import MistralModel +from DashAI.back.models.hugging_face.mistral_model import ( + Mistral7BInstructV03, + MistralNemoInstruct2407, +) from DashAI.back.models.hugging_face.mixtral_model import MixtralModel from DashAI.back.models.hugging_face.modernbert_transformer import ModernBertTransformer from DashAI.back.models.hugging_face.multilingual_bert_transformer import ( @@ -174,7 +181,10 @@ OpusMtFrEnTransformer, ) from DashAI.back.models.hugging_face.pixart_sigma_model import PixArtSigmaModel -from DashAI.back.models.hugging_face.qwen_model import QwenModel +from DashAI.back.models.hugging_face.qwen_model import ( + Qwen25_05BInstruct, + Qwen25_15BInstruct, +) from DashAI.back.models.hugging_face.roberta_transformer import RobertaTransformer from DashAI.back.models.hugging_face.sd15_depth_controlnet_model import ( SD15DepthControlNetModel, @@ -189,7 +199,10 @@ SDXLCannyControlNetModel, ) from DashAI.back.models.hugging_face.sdxl_turbo_model import SDXLTurboModel -from DashAI.back.models.hugging_face.smol_lm_model import SmolLMModel +from DashAI.back.models.hugging_face.smol_lm_model import ( + SmolLM2_17BInstruct, + SmolLM2_360MInstruct, +) from DashAI.back.models.hugging_face.stable_diffusion_v1_depth_controlnet import ( StableDiffusionXLV1ControlNet, ) @@ -346,11 +359,14 @@ def get_initial_components(): LinearRegression, LinearSVCClassifier, LinearSVR, - LlamaModel, + Llama31_8BInstruct, + Llama32_1BInstruct, + Llama32_3BInstruct, LogisticRegression, M2M100Transformer, MiniLMTransformer, - MistralModel, + Mistral7BInstructV03, + MistralNemoInstruct2407, MixtralModel, MultilingualBertTransformer, MLPClassifier, @@ -364,7 +380,8 @@ def get_initial_components(): OpusMtEsENTransformer, OpusMtFrEnTransformer, PixArtSigmaModel, - QwenModel, + Qwen25_05BInstruct, + Qwen25_15BInstruct, RandomForestClassifier, RobertaTransformer, RandomForestRegression, @@ -375,7 +392,8 @@ def get_initial_components(): SDXLCannyControlNetModel, SDXLTurboModel, SGDClassifier, - SmolLMModel, + SmolLM2_360MInstruct, + SmolLM2_17BInstruct, StableDiffusionV2Model, StableDiffusionV3Model, StableDiffusionXLModel, diff --git a/DashAI/back/models/hugging_face/gguf_text_generation_base.py b/DashAI/back/models/hugging_face/gguf_text_generation_base.py new file mode 100644 index 000000000..189f15629 --- /dev/null +++ b/DashAI/back/models/hugging_face/gguf_text_generation_base.py @@ -0,0 +1,389 @@ +"""Shared base for GGUF-backed text-generation models loaded via llama.cpp.""" + +from typing import List, Optional, Union + +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + float_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.text_to_text_generation_model import ( + TextToTextGenerationTaskModel, +) +from DashAI.back.models.utils import ( + LLAMA_DEVICE_ENUM, + LLAMA_DEVICE_PLACEHOLDER, + LLAMA_DEVICE_TO_IDX, +) + + +class GGUFTextGenerationSchema(BaseSchema): + """Schema for GGUF-based text-generation model hyperparameters. + + All GGUF checkpoint subclasses share this schema. The schema controls + generation length, sampling randomness, repetition penalty, context budget, + and the hardware device used by llama.cpp. + """ + + max_tokens: schema_field( + int_field(ge=1), + placeholder=100, + description=MultilingualString( + en=( + "Maximum number of new tokens the model will generate per response. " + "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " + "answers, 500-1000 for detailed explanations or code. Must not " + "exceed the context window minus the prompt length." + ), + es=( + "Número máximo de tokens nuevos que el modelo generará por respuesta. " + "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " + "para respuestas cortas, 500-1000 para explicaciones detalladas o " + "código. No debe superar la ventana de contexto menos la longitud " + "del prompt." + ), + pt=( + "Número máximo de tokens novos que o modelo gerará por resposta. " + "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " + "para respostas curtas, 500-1000 para explicações detalhadas ou " + "código. Não deve exceder a janela de contexto menos o comprimento " + "do prompt." + ), + de=( + "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " + "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " + "Antworten, 500-1000 für ausführliche Erklärungen oder Code. " + "Darf die Kontextfenstergröße abzüglich der Prompt-Länge nicht " + "überschreiten." + ), + zh=( + "模型每次响应生成的最大新 token 数量。" + "大约 1 token 约等于 0.75 个英文单词。短答案设为 100-200," + "详细说明或代码设为 500-1000。不得超过上下文窗口减去提示词长度的值。" + ), + ), + alias=MultilingualString( + en="Max tokens", + es="Tokens máximos", + pt="Tokens máximos", + de="Maximale neue Token", + zh="最大 token 数", + ), + ) # type: ignore + + temperature: schema_field( + float_field(ge=0.0, le=1.0), + placeholder=0.7, + description=MultilingualString( + en=( + "Sampling temperature controlling output randomness (range 0.0-1.0). " + "At 0.0 the model always picks the most likely token (greedy, fully " + "deterministic). Around 0.7 is a good balance for conversational " + "tasks. At 1.0 outputs are maximally varied and unpredictable." + ), + es=( + "Temperatura de muestreo que controla la aleatoriedad de la salida " + "(rango 0.0-1.0). En 0.0 el modelo siempre elige el token más " + "probable (greedy, totalmente determinista). Alrededor de 0.7 es " + "un buen equilibrio para tareas conversacionales. En 1.0 las " + "salidas son máximamente variadas e impredecibles." + ), + pt=( + "Temperatura de amostragem que controla a aleatoriedade da saída " + "(intervalo 0.0-1.0). Em 0.0 o modelo sempre escolhe o token mais " + "provável (greedy, totalmente determinístico). Em torno de 0.7 é " + "um bom equilíbrio para tarefas conversacionais. Em 1.0 as " + "saídas são maximamente variadas e imprevisíveis." + ), + de=( + "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." + "Bei 0.0 wählt das Modell stets den wahrscheinlichsten Token (greedy, " + "vollständig deterministisch). Um 0.7 ist ein gutes Gleichgewicht für " + "Konversationsaufgaben. Bei 1.0 sind Ausgaben maximal variiert und " + "unvorhersehbar." + ), + zh=( + "控制输出随机性的采样温度(范围 0.0-1.0)。" + "0.0 时模型始终选择最可能的 token(贪心,完全确定性)。" + "0.7 左右是对话任务的良好平衡点。1.0 时输出变化最大,不可预测。" + ), + ), + alias=MultilingualString( + en="Temperature", + es="Temperatura", + pt="Temperatura", + de="Temperatur", + zh="温度", + ), + ) # type: ignore + + frequency_penalty: schema_field( + float_field(ge=0.0, le=2.0), + placeholder=0.1, + description=MultilingualString( + en=( + "Penalizes tokens that have already appeared in the output based on " + "how often they occur (range 0.0-2.0). At 0.0 there is no penalty " + "and the model may repeat itself. Values around 0.1-0.3 gently " + "discourage repetition. High values (1.5+) strongly prevent reuse " + "of any word, which may produce less coherent text." + ), + es=( + "Penaliza los tokens que ya aparecieron en la salida según su " + "frecuencia (rango 0.0-2.0). En 0.0 no hay penalización y el modelo " + "puede repetirse. Valores en torno a 0.1-0.3 desincentivan " + "suavemente la repetición. Valores altos (1.5+) previenen " + "fuertemente la reutilización de palabras, lo que puede producir " + "texto menos coherente." + ), + pt=( + "Penaliza os tokens que já apareceram na saída com base em " + "sua frequência (intervalo 0.0-2.0). Em 0.0 não há penalização e o " + "modelo pode se repetir. Valores em torno de 0.1-0.3 desestimulam " + "suavemente a repetição. Valores altos (1.5+) impedem fortemente a " + "reutilização de palavras, o que pode produzir texto menos coerente." + ), + de=( + "Bestraft Token, die bereits in der Ausgabe erschienen sind, " + "basierend auf ihrer Häufigkeit (0.0-2.0). Bei 0.0 gibt es keine " + "Strafe und das Modell kann sich wiederholen. Werte um 0.1-0.3 " + "hemmen Wiederholungen sanft. Hohe Werte (1.5+) verhindern die " + "Wiederverwendung von Wörtern stark, was zu weniger kohärentem Text " + "führen kann." + ), + zh=( + "根据 token 在输出中出现的频率对其进行惩罚(范围 0.0-2.0)。" + "0.0 时无惩罚,模型可能重复输出。0.1-0.3 左右可轻微抑制重复。" + "高值(1.5+)会强烈阻止任何词的复用,可能导致文本连贯性下降。" + ), + ), + alias=MultilingualString( + en="Frequency penalty", + es="Penalización de frecuencia", + pt="Penalização de frequência", + de="Häufigkeitsstrafe", + zh="频率惩罚", + ), + ) # type: ignore + + context_window: schema_field( + int_field(ge=1, le=131072), + placeholder=512, + description=MultilingualString( + en=( + "Total token budget for a single forward pass, including both the " + "input prompt and the generated response. Larger values allow longer " + "conversations but consume more RAM/VRAM. Llama 3.1 supports up to " + "128K tokens natively; Llama 3.2 models support up to 128K tokens." + ), + es=( + "Presupuesto total de tokens para una sola pasada, incluyendo tanto " + "el prompt de entrada como la respuesta generada. Valores más altos " + "permiten conversaciones más largas pero consumen más RAM/VRAM. " + "Llama 3.1 soporta hasta 128K tokens de forma nativa; los modelos " + "Llama 3.2 soportan hasta 128K tokens." + ), + pt=( + "Orçamento total de tokens para uma única passagem, incluindo tanto " + "o prompt de entrada quanto a resposta gerada. Valores maiores " + "permitem conversas mais longas mas consomem mais RAM/VRAM. " + "Llama 3.1 suporta até 128K tokens nativamente; os modelos " + "Llama 3.2 suportam até 128K tokens." + ), + de=( + "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " + "einschließlich Eingabe-Prompt und generierter Antwort. Größere Werte " + "ermöglichen längere Gespräche, verbrauchen jedoch mehr RAM/VRAM. " + "Llama 3.1 unterstützt nativ bis zu 128K Token; Llama-3.2-Modelle " + "unterstützen ebenfalls bis zu 128K Token." + ), + zh=( + "单次前向传播的总 token 预算,包含输入提示和生成响应。" + "较大的值允许更长的对话,但会消耗更多 RAM/VRAM。" + "Llama 3.1 原生支持最多 128K token;" + "Llama 3.2 模型同样支持最多 128K token。" + ), + ), + alias=MultilingualString( + en="Context window", + es="Ventana de contexto", + pt="Janela de contexto", + de="Kontextfenster", + zh="上下文窗口", + ), + ) # type: ignore + + device: schema_field( + enum_field(enum=LLAMA_DEVICE_ENUM), + placeholder=LLAMA_DEVICE_PLACEHOLDER, + description=MultilingualString( + en=( + "Hardware device for llama.cpp inference. 'CPU' runs the model " + "fully in RAM with no GPU requirement. Selecting a GPU option " + "offloads all layers for faster inference, setting n_gpu_layers=-1 " + "so every transformer layer is GPU-accelerated." + ), + es=( + "Dispositivo de hardware para la inferencia con llama.cpp. 'CPU' " + "ejecuta el modelo completamente en RAM sin requisito de GPU. " + "Seleccionar una opción de GPU descarga todas las capas para " + "inferencia más rápida, estableciendo n_gpu_layers=-1 para que " + "cada capa del transformer sea acelerada por GPU." + ), + pt=( + "Dispositivo de hardware para inferência com llama.cpp. 'CPU' " + "executa o modelo completamente na RAM sem requisito de GPU. " + "Selecionar uma opção de GPU descarrega todas as camadas para " + "inferência mais rápida, definindo n_gpu_layers=-1 para que " + "cada camada do transformer seja acelerada por GPU." + ), + de=( + "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " + "vollständig im RAM ohne GPU-Anforderung aus. Eine GPU-Option " + "lagert alle Schichten für schnellere Inferenz aus und setzt " + "n_gpu_layers=-1, damit jede Transformer-Schicht GPU-beschleunigt wird." + ), + zh=( + "llama.cpp 推理所使用的硬件设备。'CPU' 完全在内存中运行模型,无需 GPU。" + "选择 GPU 选项会将所有层卸载以加快推理速度," + "并设置 n_gpu_layers=-1 使每个 Transformer 层均由 GPU 加速。" + ), + ), + alias=MultilingualString( + en="Device", + es="Dispositivo", + pt="Dispositivo", + de="Gerät", + zh="设备", + ), + ) # type: ignore + + +class GGUFTextGenerationModel(HFDownloadableMixin, TextToTextGenerationTaskModel): + """Base class for GGUF quantized text-generation models loaded via llama.cpp. + + Each concrete subclass represents one specific checkpoint and sets the class + attributes ``REPO_ID``, ``GGUF_PATTERN``, and ``DOWNLOAD_SIZE_BYTES``. The + base class provides the shared ``hf_repos`` classmethod, a helper that locates + the downloaded GGUF file on disk, the ``__init__`` that loads the model, and + the ``generate`` method. + + Subclasses must NOT override ``hf_repos`` or ``_local_gguf_path`` unless they + need non-standard repo layout. + """ + + REPO_ID: str = "" + GGUF_PATTERN: str = "" + DOWNLOAD_SIZE_BYTES: Optional[int] = None + COMPATIBLE_COMPONENTS = ["TextToTextGenerationTask"] + SCHEMA = GGUFTextGenerationSchema + + @classmethod + def hf_repos( + cls, + ) -> List[Union[tuple, tuple]]: + """Return the single HuggingFace repo entry for this checkpoint. + + Returns + ------- + list of tuple + A list containing one 3-tuple ``(repo_id, "model", [gguf_pattern])`` + when ``REPO_ID`` is set, or an empty list otherwise. + """ + if cls.REPO_ID: + return [(cls.REPO_ID, "model", [cls.GGUF_PATTERN])] + return [] + + @classmethod + def _local_gguf_path(cls): + """Locate the downloaded GGUF file within this component's repo directory. + + Returns + ------- + pathlib.Path + Absolute path to the first ``*.gguf`` file found under the repo + directory for ``REPO_ID``. + + Raises + ------ + StopIteration + If no ``*.gguf`` file exists inside the repo directory. + """ + return next(iter(cls._repo_dir(cls.REPO_ID).glob("*.gguf"))) + + def __init__(self, **kwargs): + """Load a GGUF checkpoint from disk and initialise the llama.cpp model. + + Parameters + ---------- + **kwargs : dict + max_tokens : int, optional + Maximum number of new tokens to generate per response. Default 100. + temperature : float, optional + Sampling temperature in [0.0, 1.0]. Default 0.7. + frequency_penalty : float, optional + Token-frequency penalty in [0.0, 2.0]. Default 0.1. + context_window : int, optional + Total token budget for a single forward pass. Default 512. + device : str, optional + Target device from ``LLAMA_DEVICE_ENUM``. CPU runs in RAM only; + a GPU label enables full GPU offload via ``n_gpu_layers=-1``. + + Raises + ------ + RuntimeError + If ``llama-cpp-python`` is not installed. + """ + try: + from llama_cpp import Llama + except ImportError as e: + raise RuntimeError( + "llama-cpp-python is not installed. " + "Please install it to use this model." + ) from e + + kwargs = self.validate_and_transform(kwargs) + self.max_tokens = kwargs.pop("max_tokens", 100) + self.temperature = kwargs.pop("temperature", 0.7) + self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) + self.n_ctx = kwargs.pop("context_window", 512) + + device_val = kwargs.get("device") + use_gpu = LLAMA_DEVICE_TO_IDX.get(device_val, -1) >= 0 + main_gpu = LLAMA_DEVICE_TO_IDX.get(device_val, 0) if use_gpu else 0 + + self.model = Llama( + model_path=str(self._local_gguf_path()), + verbose=True, + n_ctx=self.n_ctx, + n_gpu_layers=-1 if use_gpu else 0, + main_gpu=main_gpu, + ) + + def generate(self, prompt: list) -> List[str]: + """Generate a reply for the given chat prompt. + + Parameters + ---------- + prompt : list of dict + Conversation history in OpenAI chat format. Each dict must contain + at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) + and ``"content"`` (the message text). + + Returns + ------- + list of str + A single-element list containing the model's reply text, extracted + from ``choices[0]["message"]["content"]``. + """ + output = self.model.create_chat_completion( + messages=prompt, + max_tokens=self.max_tokens, + temperature=self.temperature, + frequency_penalty=self.frequency_penalty, + ) + return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/llama_model.py b/DashAI/back/models/hugging_face/llama_model.py index 18f36239b..170817f39 100644 --- a/DashAI/back/models/hugging_face/llama_model.py +++ b/DashAI/back/models/hugging_face/llama_model.py @@ -1,485 +1,186 @@ -from typing import List +"""Llama 3.x Instruct GGUF checkpoint subclasses for DashAI.""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) -LLAMA_FILENAME_MAP = { - "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF": "*Q4_K_M.gguf", - "bartowski/Llama-3.2-1B-Instruct-GGUF": "*Q4_K_M.gguf", - "bartowski/Llama-3.2-3B-Instruct-GGUF": "*Q4_K_M.gguf", -} +class Llama31_8BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Meta Llama 3.1 8B Instruct GGUF checkpoint (Q4_K_M quantization). -class LlamaSchema(BaseSchema): - """Configuration schema for Meta Llama 3.x text generation. + An 8B-parameter instruction-tuned model from Meta with strong general + reasoning and multilingual ability. Weights are stored locally after a + one-time download from HuggingFace. - Configures the GGUF checkpoint variant (``model_name``), generation - behaviour (``max_tokens``, ``temperature``, ``frequency_penalty``), - context length (``context_window``), device target (``device``), and - system prompt (``system_prompt``) for ``LlamaModel``. + References + ---------- + - https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF """ - model_name: schema_field( - enum_field( - enum=[ - "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF", - "bartowski/Llama-3.2-1B-Instruct-GGUF", - "bartowski/Llama-3.2-3B-Instruct-GGUF", - ] + REPO_ID = "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 4_900_000_000 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#1a237e" + DISPLAY_NAME = MultilingualString( + en="Llama 3.1 8B Instruct", + es="Llama 3.1 8B Instruct", + pt="Llama 3.1 8B Instruct", + de="Llama 3.1 8B Instruct", + zh="Llama 3.1 8B Instruct", + ) + DESCRIPTION = MultilingualString( + en=( + "Meta Llama 3.1 8B Instruct is an 8B-parameter instruction-tuned language " + "model, loaded as a Q4_K_M GGUF for efficient CPU or GPU inference. It " + "offers strong reasoning, coding, and multilingual capabilities. This is " + "the largest text-generation model in DashAI and benefits from a GPU. " + "Model available at " + "https://huggingface.co/bartowski/Meta-Llama-3.1-8B-Instruct-GGUF." ), - placeholder="bartowski/Llama-3.2-3B-Instruct-GGUF", - description=MultilingualString( - en=( - "The Meta Llama 3.x Instruct checkpoint to load in GGUF format via " - "bartowski's community quantizations. 'Llama-3.2-1B' (~1B parameters) " - "is the smallest and fastest, ideal for CPU-only systems. " - "'Llama-3.2-3B' (~3B parameters) offers a good speed/quality " - "trade-off. " - "'Meta-Llama-3.1-8B' (~8B parameters) delivers the highest quality " - "at the cost of more RAM and slower inference." - ), - es=( - "El checkpoint Meta Llama 3.x Instruct a cargar en formato GGUF " - "mediante las cuantizaciones comunitarias de bartowski. " - "'Llama-3.2-1B' (~1B parámetros) es el más pequeño y rápido, " - "ideal para sistemas solo con CPU. " - "'Llama-3.2-3B' (~3B parámetros) ofrece un buen equilibrio entre " - "velocidad y calidad. 'Meta-Llama-3.1-8B' (~8B parámetros) entrega " - "la mayor calidad a costa de más RAM e inferencia más lenta." - ), - pt=( - "O checkpoint Meta Llama 3.x Instruct para carregar em formato GGUF " - "via quantizações comunitárias de bartowski. " - "'Llama-3.2-1B' (~1B parâmetros) é o menor e mais rápido, " - "ideal para sistemas apenas com CPU. " - "'Llama-3.2-3B' (~3B parâmetros) oferece um bom equilíbrio entre " - "velocidade e qualidade. 'Meta-Llama-3.1-8B' (~8B parâmetros) " - "entrega a maior qualidade ao custo de mais RAM e " - "inferência mais lenta." - ), - de=( - "Der im GGUF-Format zu ladende Meta Llama 3.x Instruct-Checkpoint " - "über bartowskis Community-Quantisierungen. " - "'Llama-3.2-1B' (~1B Parameter) ist der kleinste und schnellste, " - "ideal für reine CPU-Systeme. " - "'Llama-3.2-3B' (~3B Parameter) bietet ein gutes Geschwindigkeit-" - "Qualitäts-Verhältnis. 'Meta-Llama-3.1-8B' (~8B Parameter) liefert " - "die höchste Qualität auf Kosten von mehr RAM und langsamerer Inferenz." - ), - zh=( - "通过 bartowski 社区量化加载的 Meta Llama 3.x Instruct GGUF 检查点。" - "'Llama-3.2-1B'(约 1B 参数)是最小最快的版本,适合仅使用 CPU 的系统。" - "'Llama-3.2-3B'(约 3B 参数)在速度与质量之间取得良好平衡。" - "'Meta-Llama-3.1-8B'(约 8B 参数)质量最高," - "但需要更多内存且推理速度较慢。" - ), + es=( + "Meta Llama 3.1 8B Instruct es un modelo de lenguaje de 8B parametros " + "ajustado para instrucciones, cargado como GGUF Q4_K_M para inferencia " + "eficiente en CPU o GPU. Ofrece solida capacidad de razonamiento, " + "programacion y multilingue. Es el modelo de generacion de texto mas " + "grande de DashAI y se beneficia de una GPU." ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", + pt=( + "Meta Llama 3.1 8B Instruct e um modelo de linguagem de 8B parametros " + "ajustado para instrucoes, carregado como GGUF Q4_K_M para inferencia " + "eficiente em CPU ou GPU. Oferece solida capacidade de raciocinio, " + "programacao e multilingue. E o maior modelo de geracao de texto do " + "DashAI e se beneficia de uma GPU." ), - ) # type: ignore - - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " - "answers, 500-1000 for detailed explanations or code. Must not " - "exceed the context window minus the prompt length." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " - "para respuestas cortas, 500-1000 para explicaciones detalladas o " - "código. No debe superar la ventana de contexto menos la longitud " - "del prompt." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " - "para respostas curtas, 500-1000 para explicações detalhadas ou " - "código. Não deve exceder a janela de contexto menos o comprimento " - "do prompt." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " - "Antworten, 500-1000 für ausführliche Erklärungen oder Code. " - "Darf die Kontextfenstergröße abzüglich der Prompt-Länge nicht " - "überschreiten." - ), - zh=( - "模型每次响应生成的最大新 token 数。" - "约 1 token ≈ 0.75 个英文单词。简短回答设置 100-200," - "详细说明或代码设置 500-1000。不得超过上下文窗口减去提示词长度的值。" - ), + de=( + "Meta Llama 3.1 8B Instruct ist ein 8B-Parameter-Instruktionsmodell, " + "als Q4_K_M-GGUF fuer effiziente CPU- oder GPU-Inferenz geladen. Es " + "bietet starke Faehigkeiten in Schlussfolgern, Programmierung und " + "Mehrsprachigkeit. Es ist das groesste Textgenerierungsmodell in DashAI " + "und profitiert von einer GPU." ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大 token 数", + zh=( + "Meta Llama 3.1 8B Instruct 是 80 亿参数的指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持高效的 CPU 或 GPU 推理。" + "它具备强大的推理、编程和多语言能力。" + "这是 DashAI 中最大的文本生成模型,使用 GPU 效果更佳。" ), - ) # type: ignore + ) - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 the model always picks the most likely token (greedy, fully " - "deterministic). Around 0.7 is a good balance for conversational " - "tasks. At 1.0 outputs are maximally varied and unpredictable." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad de la salida " - "(rango 0.0-1.0). En 0.0 el modelo siempre elige el token más " - "probable (greedy, totalmente determinista). Alrededor de 0.7 es " - "un buen equilibrio para tareas conversacionales. En 1.0 las " - "salidas son máximamente variadas e impredecibles." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade da saída " - "(intervalo 0.0-1.0). Em 0.0 o modelo sempre escolhe o token mais " - "provável (greedy, totalmente determinístico). Em torno de 0.7 é " - "um bom equilíbrio para tarefas conversacionais. Em 1.0 as " - "saídas são maximamente variadas e imprevisíveis." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 wählt das Modell stets den wahrscheinlichsten Token (greedy, " - "vollständig deterministisch). Um 0.7 ist ein gutes Gleichgewicht für " - "Konversationsaufgaben. Bei 1.0 sind Ausgaben maximal variiert und " - "unvorhersehbar." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时模型始终选择最可能的 token(贪心,完全确定性)。" - "0.7 左右是对话任务的良好平衡点。1.0 时输出变化最大,不可预测。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "how often they occur (range 0.0-2.0). At 0.0 there is no penalty " - "and the model may repeat itself. Values around 0.1-0.3 gently " - "discourage repetition. High values (1.5+) strongly prevent reuse " - "of any word, which may produce less coherent text." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). En 0.0 no hay penalización y el modelo " - "puede repetirse. Valores en torno a 0.1-0.3 desincentivan " - "suavemente la repetición. Valores altos (1.5+) previenen " - "fuertemente la reutilización de palabras, lo que puede producir " - "texto menos coherente." - ), - pt=( - "Penaliza tokens que já apareceram na saída com base em sua " - "frequência (intervalo 0.0-2.0). Em 0.0 não há penalização e o modelo " - "pode se repetir. Valores em torno de 0.1-0.3 desencorajam " - "suavemente a repetição. Valores altos (1.5+) impedem fortemente " - "o reuso de palavras, o que pode produzir texto menos coerente." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Bei 0.0 gibt es keine " - "Strafe und das Modell kann sich wiederholen. Werte um 0.1-0.3 " - "hemmen Wiederholungen sanft. Hohe Werte (1.5+) verhindern die " - "Wiederverwendung von Wörtern stark, was zu weniger kohärentem Text " - "führen kann." - ), - zh=( - "根据 token 在输出中出现的频率对其进行惩罚(范围 0.0-2.0)。" - "0.0 时无惩罚,模型可能重复自身。0.1-0.3 左右的值可温和抑制重复。" - "高值(1.5+)会强烈阻止任何词的重复使用,可能导致文本连贯性下降。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalidade de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", - ), - ) # type: ignore +class Llama32_1BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Meta Llama 3.2 1B Instruct GGUF checkpoint (Q4_K_M quantization). + + A lightweight 1B-parameter instruction-tuned model from Meta suitable for + CPU inference. Weights are stored locally after a one-time download from + HuggingFace. + + References + ---------- + - https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF + """ - context_window: schema_field( - int_field(ge=1, le=131072), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including both the " - "input prompt and the generated response. Larger values allow longer " - "conversations but consume more RAM/VRAM. Llama 3.1 supports up to " - "128K tokens natively; Llama 3.2 models support up to 128K tokens." - ), - es=( - "Presupuesto total de tokens para una sola pasada, incluyendo tanto " - "el prompt de entrada como la respuesta generada. Valores más altos " - "permiten conversaciones más largas pero consumen más RAM/VRAM. " - "Llama 3.1 soporta hasta 128K tokens de forma nativa; los modelos " - "Llama 3.2 soportan hasta 128K tokens." - ), - pt=( - "Orçamento total de tokens para uma única passagem, incluindo tanto " - "o prompt de entrada quanto a resposta gerada. Valores maiores " - "permitem conversas mais longas mas consomem mais RAM/VRAM. " - "Llama 3.1 suporta até 128K tokens nativamente; os modelos " - "Llama 3.2 suportam até 128K tokens." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabe-Prompt und generierter Antwort. Größere Werte " - "ermöglichen längere Gespräche, verbrauchen jedoch mehr RAM/VRAM. " - "Llama 3.1 unterstützt nativ bis zu 128K Token; Llama-3.2-Modelle " - "unterstützen ebenfalls bis zu 128K Token." - ), - zh=( - "单次前向传播的总 token 预算,包含输入提示和生成响应。" - "较大的值允许更长的对话,但会消耗更多 RAM/VRAM。" - "Llama 3.1 原生支持最多 128K token;" - "Llama 3.2 模型同样支持最多 128K token。" - ), + REPO_ID = "bartowski/Llama-3.2-1B-Instruct-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 800_000_000 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#1a237e" + DISPLAY_NAME = MultilingualString( + en="Llama 3.2 1B Instruct", + es="Llama 3.2 1B Instruct", + pt="Llama 3.2 1B Instruct", + de="Llama 3.2 1B Instruct", + zh="Llama 3.2 1B Instruct", + ) + DESCRIPTION = MultilingualString( + en=( + "Meta Llama 3.2 1B Instruct is a lightweight 1B-parameter " + "instruction-tuned language model, loaded as a Q4_K_M GGUF for fast CPU " + "inference. It is a good balance of speed and quality for everyday tasks. " + "Model available at " + "https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + es=( + "Meta Llama 3.2 1B Instruct es un modelo de lenguaje ligero de 1B " + "parametros ajustado para instrucciones, cargado como GGUF Q4_K_M para " + "inferencia rapida en CPU. Ofrece un buen equilibrio entre velocidad y " + "calidad para tareas cotidianas." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM with no GPU requirement. Selecting a GPU option " - "offloads all layers for faster inference, setting n_gpu_layers=-1 " - "so every transformer layer is GPU-accelerated." - ), - es=( - "Dispositivo de hardware para la inferencia con llama.cpp. 'CPU' " - "ejecuta el modelo completamente en RAM sin requisito de GPU. " - "Seleccionar una opción de GPU descarga todas las capas para " - "inferencia más rápida, estableciendo n_gpu_layers=-1 para que " - "cada capa del transformer sea acelerada por GPU." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' " - "executa o modelo completamente em RAM sem requisito de GPU. " - "Selecionar uma opção de GPU descarrega todas as camadas para " - "inferência mais rápida, definindo n_gpu_layers=-1 para que " - "cada camada do transformer seja acelerada por GPU." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "vollständig im RAM ohne GPU-Anforderung aus. Eine GPU-Option " - "lagert alle Schichten für schnellere Inferenz aus und setzt " - "n_gpu_layers=-1, damit jede Transformer-Schicht GPU-beschleunigt wird." - ), - zh=( - "llama.cpp 推理的硬件设备。'CPU' 完全在内存中运行模型,无需 GPU。" - "选择 GPU 选项可卸载所有层以加快推理速度," - "设置 n_gpu_layers=-1 使每个 Transformer 层均由 GPU 加速。" - ), + pt=( + "Meta Llama 3.2 1B Instruct e um modelo de linguagem leve de 1B " + "parametros ajustado para instrucoes, carregado como GGUF Q4_K_M para " + "inferencia rapida em CPU. Oferece um bom equilibrio entre velocidade e " + "qualidade para tarefas cotidianas." ), - alias=MultilingualString( - en="Device", - es="Dispositivo", - pt="Dispositivo", - de="Gerät", - zh="设备", + de=( + "Meta Llama 3.2 1B Instruct ist ein leichtes 1B-Parameter-" + "Instruktionsmodell, als Q4_K_M-GGUF fuer schnelle CPU-Inferenz geladen. " + "Es bietet ein gutes Gleichgewicht aus Geschwindigkeit und Qualitaet fuer " + "alltaegliche Aufgaben." ), - ) # type: ignore - + zh=( + "Meta Llama 3.2 1B Instruct 是轻量级的 10 亿参数指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持快速 CPU 推理。" + "在日常任务中兼顾速度与质量。" + ), + ) -class LlamaModel(TextToTextGenerationTaskModel): - """Meta Llama 3.x instruction-tuned model for text generation via llama.cpp. - Wraps the Meta Llama 3.x family of open-weight instruction-tuned LLMs - loaded in Q4_K_M GGUF format using the ``llama-cpp-python`` library. - GGUF quantization enables efficient CPU and GPU inference without requiring - full-precision weights, making the models practical on consumer hardware. +class Llama32_3BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Meta Llama 3.2 3B Instruct GGUF checkpoint (Q4_K_M quantization). - Three sizes are available via bartowski's community quantizations: - 1B (fastest, CPU-friendly), 3B (balanced), and 8B (highest quality). + A 3B-parameter instruction-tuned model from Meta offering higher quality + than the 1B variant while remaining CPU-friendly. Weights are stored locally + after a one-time download from HuggingFace. References ---------- - - [1] Meta AI, "Llama 3", 2024. https://ai.meta.com/blog/meta-llama-3/ - - [2] https://huggingface.co/bartowski + - https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF """ - SCHEMA = LlamaSchema + REPO_ID = "bartowski/Llama-3.2-3B-Instruct-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 2_000_000_000 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#1a237e" - DISPLAY_NAME: str = MultilingualString( - en="Llama Model", - es="Modelo Llama", - pt="Modelo Llama", - de="Llama-Modell", - zh="Llama 模型", + DISPLAY_NAME = MultilingualString( + en="Llama 3.2 3B Instruct", + es="Llama 3.2 3B Instruct", + pt="Llama 3.2 3B Instruct", + de="Llama 3.2 3B Instruct", + zh="Llama 3.2 3B Instruct", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "Meta Llama 3.x is a family of open instruction-tuned large language " - "models developed by Meta AI, loaded in GGUF format for efficient CPU " - "and GPU inference via the llama.cpp library. It supports multi-turn " - "conversation, reasoning, coding, and general text generation. Available " - "in 1B, 3B, and 8B parameter sizes. Models are hosted at " - "https://huggingface.co/bartowski." + "Meta Llama 3.2 3B Instruct is a 3B-parameter instruction-tuned language " + "model, loaded as a Q4_K_M GGUF for efficient CPU or GPU inference. It " + "offers stronger reasoning and generation quality than the 1B variant. " + "Model available at " + "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF." ), es=( - "Meta Llama 3.x es una familia de modelos de lenguaje grande de código " - "abierto ajustados para instrucciones, desarrollados por Meta AI, cargados " - "en formato GGUF para inferencia eficiente en CPU y GPU mediante la " - "librería llama.cpp. Soporta conversación multi-turno, razonamiento, " - "programación y generación de texto en general. Disponible en tamaños de " - "1B, 3B y 8B parámetros. Los modelos están en " - "https://huggingface.co/bartowski." + "Meta Llama 3.2 3B Instruct es un modelo de lenguaje de 3B parametros " + "ajustado para instrucciones, cargado como GGUF Q4_K_M para inferencia " + "eficiente en CPU o GPU. Ofrece mejor razonamiento y calidad de " + "generacion que la variante de 1B." ), pt=( - "Meta Llama 3.x é uma família de modelos de linguagem grande de código " - "aberto ajustados para instruções, desenvolvidos pela Meta AI, carregados " - "em formato GGUF para inferência eficiente em CPU e GPU via a biblioteca " - "llama.cpp. Suporta conversação multi-turno, raciocínio, programação e " - "geração de texto em geral. Disponível nos tamanhos de parâmetros 1B, 3B " - "e 8B. Os modelos estão em https://huggingface.co/bartowski." + "Meta Llama 3.2 3B Instruct e um modelo de linguagem de 3B parametros " + "ajustado para instrucoes, carregado como GGUF Q4_K_M para inferencia " + "eficiente em CPU ou GPU. Oferece melhor raciocinio e qualidade de " + "geracao do que a variante de 1B." ), de=( - "Meta Llama 3.x ist eine Familie offener instruktionsoptimierter großer " - "Sprachmodelle von Meta AI, im GGUF-Format für effiziente CPU- und " - "GPU-Inferenz über die llama.cpp-Bibliothek geladen. Unterstützt " - "Mehrfachdialog, Schlussfolgerung, Programmierung und allgemeine " - "Textgenerierung. Verfügbar in den Parametergrößen 1B, 3B und 8B. " - "Modelle unter https://huggingface.co/bartowski." + "Meta Llama 3.2 3B Instruct ist ein 3B-Parameter-Instruktionsmodell, " + "als Q4_K_M-GGUF fuer effiziente CPU- oder GPU-Inferenz geladen. Es " + "bietet besseres Schlussfolgern und Generierungsqualitaet als die " + "1B-Variante." ), zh=( - "Meta Llama 3.x 是 Meta AI 开发的开放指令微调大语言模型系列," - "以 GGUF 格式加载,通过 llama.cpp 库实现高效的 CPU 和 GPU 推理。" - "支持多轮对话、推理、编程和通用文本生成。提供 1B、3B 和 8B 参数规格。" - "模型托管于 https://huggingface.co/bartowski。" + "Meta Llama 3.2 3B Instruct 是 30 亿参数的指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持高效的 CPU 或 GPU 推理。" + "与 1B 变体相比,它具有更强的推理能力和生成质量。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a Llama 3.x GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to ``"bartowski/Llama-3.2-3B-Instruct-GGUF"``. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. " - "Please install it to use this model." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get( - "model_name", "bartowski/Llama-3.2-3B-Instruct-GGUF" - ) - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = LLAMA_FILENAME_MAP.get(self.model_name, "*Q4_K_M.gguf") - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=(LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0), - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/mistral_model.py b/DashAI/back/models/hugging_face/mistral_model.py index ab0f450bf..5b07d1996 100644 --- a/DashAI/back/models/hugging_face/mistral_model.py +++ b/DashAI/back/models/hugging_face/mistral_model.py @@ -1,441 +1,124 @@ -from typing import List +"""Mistral Instruct GGUF checkpoint subclasses for DashAI.""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) -class MistralSchema(BaseSchema): - """Schema for MistralModel hyperparameters. - - Configures the checkpoint variant, generation length, sampling temperature, - frequency penalty, context window, and target device for Mistral Instruct - models loaded via ``llama-cpp-python`` in GGUF format. - """ - - model_name: schema_field( - enum_field( - enum=[ - "bartowski/Mistral-7B-Instruct-v0.3-GGUF", - "bartowski/Mistral-Nemo-Instruct-2407-GGUF", - ] - ), - placeholder="bartowski/Mistral-7B-Instruct-v0.3-GGUF", - description=MultilingualString( - en=( - "The Mistral Instruct checkpoint to load in GGUF format. " - "'Mistral-7B-Instruct-v0.3' is a 7B-parameter instruction model " - "that delivers strong performance for its size. " - "'Mistral-Nemo-Instruct-2407' is a 12B-parameter model jointly " - "developed with NVIDIA, featuring a 128K context window and " - "improved multilingual capabilities." - ), - es=( - "El checkpoint Mistral Instruct a cargar en formato GGUF. " - "'Mistral-7B-Instruct-v0.3' es un modelo de instrucción de 7B " - "parámetros con fuerte rendimiento para su tamaño. " - "'Mistral-Nemo-Instruct-2407' es un modelo de 12B parámetros " - "desarrollado conjuntamente con NVIDIA, con una ventana de contexto " - "de 128K y mejores capacidades multilingües." - ), - pt=( - "O checkpoint Mistral Instruct para carregar em formato GGUF. " - "'Mistral-7B-Instruct-v0.3' é um modelo de instrução de 7B " - "parâmetros com forte desempenho para seu tamanho. " - "'Mistral-Nemo-Instruct-2407' é um modelo de 12B parâmetros " - "desenvolvido conjuntamente com a NVIDIA, com uma janela de contexto " - "de 128K e melhores capacidades multilíngues." - ), - de=( - "Der im GGUF-Format zu ladende Mistral Instruct-Checkpoint. " - "'Mistral-7B-Instruct-v0.3' ist ein 7B-Parameter-Instruktionsmodell " - "mit starker Leistung für seine Größe. " - "'Mistral-Nemo-Instruct-2407' ist ein 12B-Parameter-Modell, gemeinsam " - "mit NVIDIA entwickelt, mit einem 128K-Kontextfenster und verbesserten " - "mehrsprachigen Fähigkeiten." - ), - zh=( - "要加载的 Mistral Instruct 检查点(GGUF 格式)。" - "'Mistral-7B-Instruct-v0.3' 是 7B 参数指令模型,性能出色。" - "'Mistral-Nemo-Instruct-2407' 是与 NVIDIA 联合开发的 12B 参数模型," - "支持 128K 上下文窗口,多语言能力更强。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore +class Mistral7BInstructV03(GGUFTextGenerationModel): + """Mistral 7B Instruct v0.3 GGUF checkpoint (Q4_K_M quantization). - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " - "answers, 500-1000 for detailed explanations or code." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " - "para respuestas cortas, 500-1000 para explicaciones detalladas " - "o código." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " - "para respostas curtas, 500-1000 para explicações detalhadas " - "ou código." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " - "Antworten, 500-1000 für ausführliche Erklärungen oder Code." - ), - zh=( - "模型每次响应生成的最大新 token 数。" - "大约 1 token 约等于 0.75 个英文单词。" - "短回答设为 100-200,详细解释或代码设为 500-1000。" - ), - ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大 token 数", - ), - ) # type: ignore + A 7B-parameter instruction-tuned model from Mistral AI with strong general + performance. Weights are stored locally after a one-time download from + HuggingFace. - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 the model picks the most likely token (deterministic). " - "Around 0.7 balances quality and creativity. At 1.0 outputs are " - "maximally varied." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad (rango 0.0-1.0). " - "En 0.0 el modelo elige el token más probable (determinista). " - "Alrededor de 0.7 equilibra calidad y creatividad. En 1.0 las salidas " - "son máximamente variadas." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade " - "(intervalo 0.0-1.0). " - "Em 0.0 o modelo escolhe o token mais provável (determinístico). " - "Em torno de 0.7 equilibra qualidade e criatividade. Em 1.0 as saídas " - "são maximamente variadas." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 wählt das Modell den wahrscheinlichsten Token " - "(deterministisch). " - "Ca. 0.7 balanciert Qualität und Kreativität. Bei 1.0 sind Ausgaben " - "maximal variiert." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时模型选择最可能的 token(确定性)。" - "0.7 左右在质量与创造性之间取得平衡。1.0 时输出变化最大。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore + References + ---------- + - https://huggingface.co/bartowski/Mistral-7B-Instruct-v0.3-GGUF + """ - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "frequency (range 0.0-2.0). Higher values discourage repetition." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). Valores más altos desincentivan " - "la repetición." - ), - pt=( - "Penaliza tokens que já apareceram na saída com base na " - "frequência (intervalo 0.0-2.0). Valores mais altos desencorajam " - "a repetição." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Höhere Werte reduzieren " - "Wiederholungen." - ), - zh=( - "根据频率对已出现在输出中的 token 施加惩罚(范围 0.0-2.0)。" - "较高值可抑制重复。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalidade de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", + REPO_ID = "bartowski/Mistral-7B-Instruct-v0.3-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 4_400_000_000 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#ff6f00" + DISPLAY_NAME = MultilingualString( + en="Mistral 7B Instruct v0.3", + es="Mistral 7B Instruct v0.3", + pt="Mistral 7B Instruct v0.3", + de="Mistral 7B Instruct v0.3", + zh="Mistral 7B Instruct v0.3", + ) + DESCRIPTION = MultilingualString( + en=( + "Mistral 7B Instruct v0.3 is a 7B-parameter instruction-tuned language " + "model from Mistral AI, loaded as a Q4_K_M GGUF for efficient CPU or GPU " + "inference. It offers strong general reasoning and generation quality. " + "Model available at " + "https://huggingface.co/bartowski/Mistral-7B-Instruct-v0.3-GGUF." ), - ) # type: ignore - - context_window: schema_field( - int_field(ge=1, le=131072), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including prompt and " - "response. Mistral-7B supports up to 32K tokens; Mistral-Nemo " - "supports up to 128K tokens." - ), - es=( - "Presupuesto total de tokens por pasada, incluyendo prompt y " - "respuesta. Mistral-7B soporta hasta 32K tokens; Mistral-Nemo " - "soporta hasta 128K tokens." - ), - pt=( - "Orçamento total de tokens por passagem, incluindo prompt e " - "resposta. Mistral-7B suporta até 32K tokens; Mistral-Nemo " - "suporta até 128K tokens." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabeaufforderung und Antwort. Mistral-7B unterstützt" - "bis zu 32K Token; Mistral-Nemo bis zu 128K Token." - ), - zh=( - "单次前向传播的总 token 预算,包含提示词和回复。" - "Mistral-7B 支持最多 32K token;Mistral-Nemo 支持最多 128K token。" - ), + es=( + "Mistral 7B Instruct v0.3 es un modelo de lenguaje de 7B parametros " + "ajustado para instrucciones por Mistral AI, cargado como GGUF Q4_K_M " + "para inferencia eficiente en CPU o GPU. Ofrece solida capacidad de " + "razonamiento y calidad de generacion." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + pt=( + "Mistral 7B Instruct v0.3 e um modelo de linguagem de 7B parametros " + "ajustado para instrucoes pela Mistral AI, carregado como GGUF Q4_K_M " + "para inferencia eficiente em CPU ou GPU. Oferece solida capacidade de " + "raciocinio e qualidade de geracao." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM. A GPU option offloads all layers for faster inference." - ), - es=( - "Dispositivo de hardware para inferencia con llama.cpp. 'CPU' ejecuta " - "el modelo en RAM. Una opción de GPU descarga todas las capas para " - "inferencia más rápida." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' executa " - "o modelo em RAM. Uma opção de GPU descarrega todas as camadas para " - "inferência mais rápida." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "vollständig im RAM aus. Eine GPU-Option lagert alle Schichten für " - "schnellere Inferenz aus." - ), - zh=( - "llama.cpp 推理所用的硬件设备。'CPU' 完全在内存中运行模型。" - "选择 GPU 选项可卸载所有层以加快推理速度。" - ), + de=( + "Mistral 7B Instruct v0.3 ist ein 7B-Parameter-Instruktionsmodell von " + "Mistral AI, als Q4_K_M-GGUF fuer effiziente CPU- oder GPU-Inferenz " + "geladen. Es bietet starke allgemeine Schlussfolgerungs- und " + "Generierungsqualitaet." ), - alias=MultilingualString( - en="Device", - es="Dispositivo", - pt="Dispositivo", - de="Gerät", - zh="设备", + zh=( + "Mistral 7B Instruct v0.3 是 Mistral AI 推出的 70 亿参数指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持高效的 CPU 或 GPU 推理。" + "它具备强大的通用推理和生成质量。" ), - ) # type: ignore - + ) -class MistralModel(TextToTextGenerationTaskModel): - """Mistral Instruct model for open-ended text generation via llama.cpp. - Mistral is a 7B-parameter transformer language model developed by Mistral AI, - designed to deliver high performance with efficient inference. It uses grouped- - query attention (GQA) for faster decoding and sliding-window attention (SWA) to - handle long contexts efficiently. The 12B Mistral-Nemo variant, developed jointly - with NVIDIA, extends the context window to 128 K tokens and improves multilingual - capability. +class MistralNemoInstruct2407(GGUFTextGenerationModel): + """Mistral Nemo Instruct 2407 GGUF checkpoint (Q4_K_M quantization). - Models are loaded as GGUF quantized checkpoints via ``llama-cpp-python``, - allowing CPU and GPU inference without requiring a full PyTorch stack. + A 12B-parameter instruction-tuned model from Mistral AI and NVIDIA with a + large context window. This is a heavy model that benefits from a GPU. + Weights are stored locally after a one-time download from HuggingFace. References ---------- - - [1] Jiang et al. (2023) "Mistral 7B" https://arxiv.org/abs/2310.06825 - - [2] https://huggingface.co/mistralai + - https://huggingface.co/bartowski/Mistral-Nemo-Instruct-2407-GGUF """ - SCHEMA = MistralSchema + REPO_ID = "bartowski/Mistral-Nemo-Instruct-2407-GGUF" + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 7_100_000_000 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#ff6f00" - DISPLAY_NAME: str = MultilingualString( - en="Mistral Model", - es="Modelo Mistral", - pt="Modelo Mistral", - de="Mistral-Modell", - zh="Mistral 模型", + DISPLAY_NAME = MultilingualString( + en="Mistral Nemo Instruct 2407", + es="Mistral Nemo Instruct 2407", + pt="Mistral Nemo Instruct 2407", + de="Mistral Nemo Instruct 2407", + zh="Mistral Nemo Instruct 2407", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "Mistral instruction-tuned models by Mistral AI, loaded in GGUF format " - "for efficient CPU and GPU inference via the llama.cpp library. Mistral " - "models are known for strong performance relative to their parameter count " - "and efficient inference. Supports multi-turn conversation, reasoning, " - "and general text generation. Available in 7B (Mistral-7B-v0.3) and 12B " - "(Mistral-Nemo-2407) variants. Models hosted at " - "https://huggingface.co/bartowski." + "Mistral Nemo Instruct 2407 is a 12B-parameter instruction-tuned language " + "model built by Mistral AI and NVIDIA, loaded as a Q4_K_M GGUF. It offers " + "high generation quality and a large context window, and is the heaviest " + "text-generation model in DashAI; a GPU is recommended. Model available at " + "https://huggingface.co/bartowski/Mistral-Nemo-Instruct-2407-GGUF." ), es=( - "Modelos ajustados para instrucciones de Mistral AI, cargados en formato " - "GGUF para inferencia eficiente en CPU y GPU mediante llama.cpp. " - "Los modelos " - "Mistral son conocidos por su fuerte rendimiento relativo a su cantidad de " - "parámetros e inferencia eficiente. Soporta conversación multi-turno, " - "razonamiento y generación de texto en general. Disponible en variantes de " - "7B (Mistral-7B-v0.3) y 12B (Mistral-Nemo-2407). Modelos en " - "https://huggingface.co/bartowski." + "Mistral Nemo Instruct 2407 es un modelo de lenguaje de 12B parametros " + "ajustado para instrucciones, creado por Mistral AI y NVIDIA, cargado como " + "GGUF Q4_K_M. Ofrece alta calidad de generacion y una gran ventana de " + "contexto; es el modelo mas pesado de DashAI y se recomienda una GPU." ), pt=( - "Modelos ajustados para instruções da Mistral AI, carregados em formato " - "GGUF para inferência eficiente em CPU e GPU via llama.cpp. Os modelos " - "Mistral são conhecidos pelo forte desempenho em relação à sua quantidade " - "de parâmetros e inferência eficiente. Suporta conversação multi-turno, " - "raciocínio e geração de texto em geral. Disponível nas variantes de " - "7B (Mistral-7B-v0.3) e 12B (Mistral-Nemo-2407). Modelos em " - "https://huggingface.co/bartowski." + "Mistral Nemo Instruct 2407 e um modelo de linguagem de 12B parametros " + "ajustado para instrucoes, criado pela Mistral AI e NVIDIA, carregado como " + "GGUF Q4_K_M. Oferece alta qualidade de geracao e uma grande janela de " + "contexto; e o modelo mais pesado do DashAI e uma GPU e recomendada." ), de=( - "Instruktionsoptimierte Mistral-Modelle von Mistral AI, im GGUF-Format " - "für effiziente CPU- und GPU-Inferenz über die llama.cpp-Bibliothek. " - "Mistral-Modelle sind bekannt für starke Leistung relativ zu ihrer " - "Parameteranzahl und effizienter Inferenz. Unterstützt Mehrfachdialog, " - "Schlussfolgerung und allgemeine Textgenerierung. Verfügbar in 7B " - "(Mistral-7B-v0.3) und 12B (Mistral-Nemo-2407) Varianten. Modelle unter " - "https://huggingface.co/bartowski." + "Mistral Nemo Instruct 2407 ist ein 12B-Parameter-Instruktionsmodell von " + "Mistral AI und NVIDIA, als Q4_K_M-GGUF geladen. Es bietet hohe " + "Generierungsqualitaet und ein grosses Kontextfenster und ist das " + "schwerste Textgenerierungsmodell in DashAI; eine GPU wird empfohlen." ), zh=( - "Mistral AI 的指令微调模型,以 GGUF 格式加载," - "通过 llama.cpp 库实现高效的 CPU 和 GPU 推理。" - "支持多轮对话、推理和通用文本生成。提供 7B 和 12B 两种规格。" + "Mistral Nemo Instruct 2407 是 Mistral AI 与 NVIDIA 共同打造的 " + "120 亿参数指令微调语言模型,以 Q4_K_M GGUF 格式加载。" + "它具有高生成质量和大上下文窗口,是 DashAI 中最重的文本生成模型," + "建议使用 GPU。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a Mistral Instruct GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to ``"bartowski/Mistral-7B-Instruct-v0.3-GGUF"``. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. " - "Please install it to use this model." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get( - "model_name", "bartowski/Mistral-7B-Instruct-v0.3-GGUF" - ) - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = "*Q4_K_M.gguf" - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=(LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0), - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/qwen_model.py b/DashAI/back/models/hugging_face/qwen_model.py index 475c536bc..56afdcb6c 100644 --- a/DashAI/back/models/hugging_face/qwen_model.py +++ b/DashAI/back/models/hugging_face/qwen_model.py @@ -1,468 +1,136 @@ -from typing import List +"""Qwen 2.5 Instruct GGUF checkpoint subclasses for DashAI.""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) -class QwenSchema(BaseSchema): - """Schema for QwenModel hyperparameters. - - Configures the Qwen 2.5 Instruct checkpoint variant (0.5B or 1.5B), generation - length, sampling temperature, frequency penalty, context window, and target - device. The GGUF filename is selected automatically using a Q8_0 quantization - pattern; no manual filename override is exposed. - """ - - model_name: schema_field( - enum_field( - enum=[ - "Qwen/Qwen2.5-0.5B-Instruct-GGUF", - "Qwen/Qwen2.5-1.5B-Instruct-GGUF", - ] - ), - placeholder="Qwen/Qwen2.5-1.5B-Instruct-GGUF", - description=MultilingualString( - en=( - "The Qwen 2.5 Instruct checkpoint to load in GGUF format. " - "'0.5B' (500M parameters) is faster and uses less memory, suitable " - "for lightweight tasks on CPU. '1.5B' (1.5B parameters) is more " - "capable and produces higher-quality responses at the cost of " - "more memory and slightly slower inference." - ), - es=( - "El checkpoint Qwen 2.5 Instruct a cargar en formato GGUF. " - "'0.5B' (500M parámetros) es más rápido y usa menos memoria, " - "adecuado para tareas ligeras en CPU. '1.5B' (1.5B parámetros) " - "es más capaz y produce respuestas de mayor calidad a costa de " - "más memoria e inferencia levemente más lenta." - ), - pt=( - "O checkpoint Qwen 2.5 Instruct a carregar em formato GGUF. " - "'0.5B' (500M parâmetros) é mais rápido e usa menos memória, " - "adequado para tarefas leves em CPU. '1.5B' (1.5B parâmetros) " - "é mais capaz e produz respostas de maior qualidade ao custo de " - "mais memória e inferência levemente mais lenta." - ), - de=( - "Der im GGUF-Format zu ladende Qwen 2.5 Instruct-Checkpoint. " - "'0.5B' (500M Parameter) ist schneller und verbraucht weniger Speicher," - "geeignet für leichte CPU-Aufgaben. '1.5B' (1,5B Parameter) ist " - "leistungsfähiger und liefert qualitativ hochwertigere Antworten " - "auf Kosten von mehr Speicher und etwas langsamerer Inferenz." - ), - zh=( - "要加载的 Qwen 2.5 Instruct GGUF 格式检查点。" - "'0.5B'(5亿参数)速度更快、内存占用更少,适合 CPU 上的轻量级任务。" - "'1.5B'(15亿参数)能力更强,生成质量更高,但需要更多内存且推理速度略慢。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore +class Qwen25_05BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Qwen 2.5 0.5B Instruct GGUF checkpoint (Q8_0 quantization). - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " - "answers, 500-1000 for detailed explanations or code. Must not " - "exceed the context window minus the prompt length." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " - "para respuestas cortas, 500-1000 para explicaciones detalladas o " - "código. No debe superar la ventana de contexto menos la longitud " - "del prompt." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " - "para respostas curtas, 500-1000 para explicações detalhadas ou " - "código. Não deve exceder a janela de contexto menos o comprimento " - "do prompt." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " - "Antworten, 500-1000 für ausführliche Erklärungen oder Code. " - "Darf die Kontextfenstergröße abzüglich der Prompt-Länge nicht " - "überschreiten." - ), - zh=( - "模型每次响应生成的最大新 token 数量。" - "大约 1 token 约等于 0.75 个英文单词。短答案设为 100-200," - "详细说明或代码设为 500-1000。不得超过上下文窗口减去提示词长度的值。" - ), - ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大 token 数", - ), - ) # type: ignore + A compact 500M-parameter instruction-tuned model from Alibaba Cloud, + well suited for lightweight CPU inference. Weights are stored locally + after a one-time download from HuggingFace. - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 the model always picks the most likely token (greedy, fully " - "deterministic). Around 0.7 is a good balance for conversational " - "tasks. At 1.0 outputs are maximally varied and unpredictable." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad de la salida " - "(rango 0.0-1.0). En 0.0 el modelo siempre elige el token más " - "probable (greedy, totalmente determinista). Alrededor de 0.7 es " - "un buen equilibrio para tareas conversacionales. En 1.0 las " - "salidas son máximamente variadas e impredecibles." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade da saída " - "(intervalo 0.0-1.0). Em 0.0 o modelo sempre escolhe o token mais " - "provável (greedy, totalmente determinístico). Em torno de 0.7 é " - "um bom equilíbrio para tarefas conversacionais. Em 1.0 as " - "saídas são maximamente variadas e imprevisíveis." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 wählt das Modell stets den wahrscheinlichsten Token (greedy, " - "vollständig deterministisch). Um 0.7 ist ein gutes Gleichgewicht für " - "Konversationsaufgaben. Bei 1.0 sind Ausgaben maximal variiert und " - "unvorhersehbar." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时模型始终选择最可能的 token(贪心,完全确定性)。" - "0.7 左右是对话任务的良好平衡点。1.0 时输出变化最大,不可预测。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore + References + ---------- + - Qwen Team (2024). "Qwen2.5 Technical Report." + https://arxiv.org/abs/2412.15115 + - https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF + """ - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "how often they occur (range 0.0-2.0). At 0.0 there is no penalty " - "and the model may repeat itself. Values around 0.1-0.3 gently " - "discourage repetition. High values (1.5+) strongly prevent reuse " - "of any word, which may produce less coherent text." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). En 0.0 no hay penalización y el modelo " - "puede repetirse. Valores en torno a 0.1-0.3 desincentivan " - "suavemente la repetición. Valores altos (1.5+) previenen " - "fuertemente la reutilización de palabras, lo que puede producir " - "texto menos coherente." - ), - pt=( - "Penaliza os tokens que já apareceram na saída com base em " - "sua frequência (intervalo 0.0-2.0). Em 0.0 não há penalização e o " - "modelo pode se repetir. Valores em torno de 0.1-0.3 desestimulam " - "suavemente a repetição. Valores altos (1.5+) impedem fortemente a " - "reutilização de palavras, o que pode produzir texto menos coerente." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Bei 0.0 gibt es keine " - "Strafe und das Modell kann sich wiederholen. Werte um 0.1-0.3 " - "hemmen Wiederholungen sanft. Hohe Werte (1.5+) verhindern die " - "Wiederverwendung von Wörtern stark, was zu weniger kohärentem Text " - "führen kann." - ), - zh=( - "根据 token 在输出中出现的频率对其进行惩罚(范围 0.0-2.0)。" - "0.0 时无惩罚,模型可能重复输出。0.1-0.3 左右可轻微抑制重复。" - "高值(1.5+)会强烈阻止任何词的复用,可能导致文本连贯性下降。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalização de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", + REPO_ID = "Qwen/Qwen2.5-0.5B-Instruct-GGUF" + GGUF_PATTERN = "*8_0.gguf" + DOWNLOAD_SIZE_BYTES = 700_000_000 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#2e7d32" + DISPLAY_NAME = MultilingualString( + en="Qwen2.5 0.5B Instruct", + es="Qwen2.5 0.5B Instruct", + pt="Qwen2.5 0.5B Instruct", + de="Qwen2.5 0.5B Instruct", + zh="Qwen2.5 0.5B Instruct", + ) + DESCRIPTION = MultilingualString( + en=( + "Qwen 2.5 0.5B Instruct is a 500M-parameter instruction-tuned " + "language model by Alibaba Cloud, loaded as a Q8_0 GGUF for " + "efficient CPU inference. It is the fastest and most " + "memory-efficient Qwen 2.5 variant in DashAI, ideal for rapid " + "prototyping or devices with limited RAM. Model available at " + "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF." ), - ) # type: ignore - - context_window: schema_field( - int_field(ge=1, le=32768), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including both the " - "input prompt and the generated response. Larger values allow longer " - "conversations but consume more RAM/VRAM. Qwen 2.5 supports up to " - "32768 tokens natively; keep this at or below that limit." - ), - es=( - "Presupuesto total de tokens para una sola pasada, incluyendo tanto " - "el prompt de entrada como la respuesta generada. Valores más altos " - "permiten conversaciones más largas pero consumen más RAM/VRAM. " - "Qwen 2.5 soporta hasta 32768 tokens de forma nativa; mantenga " - "este valor igual o por debajo de ese límite." - ), - pt=( - "Orçamento total de tokens para uma única passagem, incluindo tanto " - "o prompt de entrada quanto a resposta gerada. Valores maiores " - "permitem conversas mais longas mas consomem mais RAM/VRAM. " - "Qwen 2.5 suporta até 32768 tokens nativamente; mantenha " - "este valor igual ou abaixo desse limite." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabe-Prompt und generierter Antwort. Größere Werte " - "ermöglichen längere Gespräche, verbrauchen jedoch mehr RAM/VRAM. " - "Qwen 2.5 unterstützt nativ bis zu 32768 Token; halten Sie " - "diesen Wert gleich oder unter diesem Limit." - ), - zh=( - "单次前向传播的总 token 预算,包含输入提示词和生成的响应。" - "较大的值允许更长的对话,但会消耗更多 RAM/VRAM。" - "Qwen 2.5 原生支持最多 32768 个 token,请保持此值不超过该限制。" - ), + es=( + "Qwen 2.5 0.5B Instruct es un modelo de 500M parámetros ajustado " + "para instrucciones por Alibaba Cloud, cargado como GGUF Q8_0 " + "para inferencia eficiente en CPU. Es la variante Qwen 2.5 más " + "rápida y con menor uso de memoria en DashAI, ideal para " + "prototipado rápido o dispositivos con RAM limitada." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + pt=( + "Qwen 2.5 0.5B Instruct é um modelo de 500M parâmetros ajustado " + "para instruções pela Alibaba Cloud, carregado como GGUF Q8_0 " + "para inferência eficiente em CPU. É a variante Qwen 2.5 mais " + "rápida e com menor uso de memória no DashAI, ideal para " + "prototipagem rápida ou dispositivos com RAM limitada." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM with no GPU requirement. Selecting a GPU option " - "offloads all layers for faster inference, setting n_gpu_layers=-1 " - "so every transformer layer is GPU-accelerated." - ), - es=( - "Dispositivo de hardware para la inferencia con llama.cpp. 'CPU' " - "ejecuta el modelo completamente en RAM sin requisito de GPU. " - "Seleccionar una opción de GPU descarga todas las capas para " - "inferencia más rápida, estableciendo n_gpu_layers=-1 para que " - "cada capa del transformer sea acelerada por GPU." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' " - "executa o modelo completamente na RAM sem requisito de GPU. " - "Selecionar uma opção de GPU descarrega todas as camadas para " - "inferência mais rápida, definindo n_gpu_layers=-1 para que " - "cada camada do transformer seja acelerada por GPU." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "vollständig im RAM ohne GPU-Anforderung aus. Eine GPU-Option " - "lagert alle Schichten für schnellere Inferenz aus und setzt " - "n_gpu_layers=-1, damit jede Transformer-Schicht GPU-beschleunigt wird." - ), - zh=( - "llama.cpp 推理所使用的硬件设备。'CPU' 完全在内存中运行模型,无需 GPU。" - "选择 GPU 选项会将所有层卸载以加快推理速度," - "并设置 n_gpu_layers=-1 使每个 Transformer 层均由 GPU 加速。" - ), + de=( + "Qwen 2.5 0.5B Instruct ist ein 500M-Parameter-Instruktionsmodell " + "von Alibaba Cloud, als Q8_0-GGUF für effiziente CPU-Inferenz " + "geladen. Es ist die schnellste und speichereffizienteste " + "Qwen-2.5-Variante in DashAI, ideal für schnelles Prototyping " + "oder Geräte mit begrenztem RAM." ), - alias=MultilingualString( - en="Device", - es="Dispositivo", - pt="Dispositivo", - de="Gerät", - zh="设备", + zh=( + "Qwen 2.5 0.5B Instruct 是阿里云推出的 5 亿参数指令微调语言模型," + "以 Q8_0 GGUF 格式加载,支持高效 CPU 推理。" + "这是 DashAI 中速度最快、内存占用最低的 Qwen 2.5 变体," + "非常适合快速原型开发或内存受限的设备。" ), - ) # type: ignore - + ) -class QwenModel(TextToTextGenerationTaskModel): - """Qwen 2.5 Instruct model for efficient text generation via llama.cpp. - Qwen 2.5 is a series of dense transformer language models from Alibaba Cloud, - spanning 0.5B to 72B parameters. The DashAI integration exposes the 0.5B and - 1.5B Instruct variants, which run comfortably on CPU. Both are trained on 18 - trillion tokens with improved coding, mathematics, and multilingual capability - over Qwen 2. +class Qwen25_15BInstruct(GGUFTextGenerationModel): # noqa: N801 + """Qwen 2.5 1.5B Instruct GGUF checkpoint (Q8_0 quantization). - Models are loaded as GGUF Q8_0 quantized checkpoints via ``llama-cpp-python``; - the quantization file is selected automatically from the HuggingFace repo. + A 1.5B-parameter instruction-tuned model from Alibaba Cloud that offers + higher response quality than the 0.5B variant while still running on CPU. + Weights are stored locally after a one-time download from HuggingFace. References ---------- - - [1] Qwen Team (2024). "Qwen2.5 Technical Report." https://arxiv.org/abs/2412.15115 - - [2] https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF + - Qwen Team (2024). "Qwen2.5 Technical Report." + https://arxiv.org/abs/2412.15115 + - https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF """ - SCHEMA = QwenSchema + REPO_ID = "Qwen/Qwen2.5-1.5B-Instruct-GGUF" + GGUF_PATTERN = "*8_0.gguf" + DOWNLOAD_SIZE_BYTES = 1_900_000_000 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#2e7d32" - DISPLAY_NAME: str = MultilingualString( - en="Qwen Model", - es="Modelo Qwen", - pt="Modelo Qwen", - de="Qwen-Modell", - zh="Qwen 模型", + DISPLAY_NAME = MultilingualString( + en="Qwen2.5 1.5B Instruct", + es="Qwen2.5 1.5B Instruct", + pt="Qwen2.5 1.5B Instruct", + de="Qwen2.5 1.5B Instruct", + zh="Qwen2.5 1.5B Instruct", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "Qwen 2.5 is an instruction-tuned large language model by Alibaba Cloud, " - "loaded in GGUF format for efficient CPU and GPU inference via the " - "llama.cpp library. It supports multi-turn conversation, reasoning, " - "coding, and general text generation. Available in 0.5B and 1.5B " - "parameter sizes. Models are available at " - "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF and " + "Qwen 2.5 1.5B Instruct is a 1.5B-parameter instruction-tuned " + "language model by Alibaba Cloud, loaded as a Q8_0 GGUF for " + "efficient CPU inference. It provides stronger reasoning and " + "generation quality than the 0.5B variant at the cost of slightly " + "more memory. Model available at " "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF." ), es=( - "Qwen 2.5 es un modelo de lenguaje grande ajustado para instrucciones " - "por Alibaba Cloud, cargado en formato GGUF para inferencia eficiente en " - "CPU y GPU mediante la librería llama.cpp. Soporta conversación " - "multi-turno, razonamiento, programación y generación de texto en " - "general. Disponible en tamaños de 0.5B y 1.5B parámetros. Los modelos " - "están disponibles en " - "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF y " - "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF." + "Qwen 2.5 1.5B Instruct es un modelo de 1.5B parámetros ajustado " + "para instrucciones por Alibaba Cloud, cargado como GGUF Q8_0 " + "para inferencia eficiente en CPU. Ofrece mayor capacidad de " + "razonamiento y calidad de generación que la variante de 0.5B a " + "costa de un poco más de memoria." ), pt=( - "Qwen 2.5 é um modelo de linguagem grande ajustado para instruções " - "pela Alibaba Cloud, carregado em formato GGUF para inferência eficiente " - "em CPU e GPU via biblioteca llama.cpp. Suporta conversa multi-turno, " - "raciocínio, programação e geração de texto em geral. Disponível nos " - "tamanhos 0.5B e 1.5B parâmetros. Os modelos estão disponíveis em " - "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF e " - "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF." + "Qwen 2.5 1.5B Instruct é um modelo de 1.5B parâmetros ajustado " + "para instruções pela Alibaba Cloud, carregado como GGUF Q8_0 " + "para inferência eficiente em CPU. Oferece melhor raciocínio e " + "qualidade de geração que a variante de 0.5B ao custo de um pouco " + "mais de memória." ), de=( - "Qwen 2.5 ist ein instruktionsoptimiertes großes Sprachmodell von " - "Alibaba Cloud, im GGUF-Format für effiziente CPU- und GPU-Inferenz über " - "die llama.cpp-Bibliothek geladen. Unterstützt Mehrfachdialog, " - "Schlussfolgerung, Programmierung und allgemeine Textgenerierung. " - "Verfügbar in den Parametergrößen 0,5B und 1,5B. Modelle verfügbar unter " - "https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct-GGUF und " - "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF." + "Qwen 2.5 1.5B Instruct ist ein 1,5B-Parameter-Instruktionsmodell " + "von Alibaba Cloud, als Q8_0-GGUF für effiziente CPU-Inferenz " + "geladen. Es bietet besseres Schlussfolgern und " + "Generierungsqualität als die 0,5B-Variante, benötigt jedoch " + "etwas mehr Speicher." ), zh=( - "Qwen 2.5 是阿里云开发的指令微调大语言模型," - "以 GGUF 格式加载,通过 llama.cpp 库实现高效的 CPU 和 GPU 推理。" - "支持多轮对话、推理、编程和通用文本生成。提供 0.5B 和 1.5B 参数规格。" + "Qwen 2.5 1.5B Instruct 是阿里云推出的 15 亿参数指令微调语言模型," + "以 Q8_0 GGUF 格式加载,支持高效 CPU 推理。" + "与 0.5B 变体相比,它具有更强的推理能力和生成质量,但需要稍多的内存。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a Qwen 2.5 Instruct GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. The Q8_0 quantization file is always selected regardless - of the chosen model variant. - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to ``"Qwen/Qwen2.5-1.5B-Instruct-GGUF"``. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. Please install it to use QwenModel." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get("model_name", "Qwen/Qwen2.5-1.5B-Instruct-GGUF") - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = "*8_0.gguf" - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0, - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/DashAI/back/models/hugging_face/smol_lm_model.py b/DashAI/back/models/hugging_face/smol_lm_model.py index a5f56bdd9..b2999369d 100644 --- a/DashAI/back/models/hugging_face/smol_lm_model.py +++ b/DashAI/back/models/hugging_face/smol_lm_model.py @@ -1,454 +1,123 @@ -from typing import List +"""SmolLM2 Instruct GGUF checkpoint subclasses for DashAI.""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) -SMOLLM_FILENAME_MAP = { - "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF": "*q4_k_m.gguf", - "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF": "*q8_0.gguf", -} +class SmolLM2_360MInstruct(GGUFTextGenerationModel): # noqa: N801 + """SmolLM2 360M Instruct GGUF checkpoint (Q8_0 quantization). -class SmolLMSchema(BaseSchema): - """Schema for SmolLM2 model hyperparameters. + A very small 360M-parameter instruction-tuned model from HuggingFace, + designed for fast on-device inference. Weights are stored locally after a + one-time download from HuggingFace. - Configures the SmolLM2 Instruct checkpoint variant (360M or 1.7B), generation - length, sampling temperature, frequency penalty, context window, and target - device. The GGUF filename is resolved automatically from ``SMOLLM_FILENAME_MAP``: - Q4_K_M quantization for 1.7B and Q8_0 quantization for 360M. + References + ---------- + - https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct-GGUF """ - model_name: schema_field( - enum_field( - enum=[ - "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF", - "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF", - ] - ), - placeholder="HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF", - description=MultilingualString( - en=( - "The SmolLM2 Instruct checkpoint to load in GGUF format. " - "'SmolLM2-1.7B' is a 1.7B-parameter instruction model with strong " - "performance for on-device and edge inference. " - "'SmolLM2-360M' is an ultra-compact 360M-parameter model for " - "extremely fast CPU inference with minimal memory usage (~300 MB). " - "Both models are trained on diverse synthetic datasets by Hugging Face." - ), - es=( - "El checkpoint SmolLM2 Instruct a cargar en formato GGUF. " - "'SmolLM2-1.7B' es un modelo de instrucción de 1.7B parámetros con " - "fuerte rendimiento para inferencia en dispositivos y en el borde. " - "'SmolLM2-360M' es un modelo ultra-compacto de 360M parámetros para " - "inferencia CPU extremadamente rápida con uso mínimo de memoria " - "(~300 MB). " - "Ambos modelos son entrenados en datasets sintéticos diversos por " - "Hugging Face." - ), - pt=( - "O checkpoint SmolLM2 Instruct a carregar em formato GGUF. " - "'SmolLM2-1.7B' é um modelo de instrução de 1.7B parâmetros com " - "forte desempenho para inferência em dispositivos e na borda. " - "'SmolLM2-360M' é um modelo ultra-compacto de 360M parâmetros para " - "inferência CPU extremamente rápida com uso mínimo de memória " - "(~300 MB). " - "Ambos os modelos são treinados em conjuntos de dados sintéticos " - "diversos pelo Hugging Face." - ), - de=( - "Der im GGUF-Format zu ladende SmolLM2 Instruct-Checkpoint. " - "'SmolLM2-1.7B' ist ein 1,7B-Parameter-Instruktionsmodell mit starker " - "Leistung für Inferenz auf Endgeräten und Edge-Systemen. " - "'SmolLM2-360M' ist ein ultra-kompaktes 360M-Parameter-Modell für " - "extrem schnelle CPU-Inferenz mit minimalem Speicherbedarf (~300 MB). " - "Beide Modelle werden von Hugging Face auf diversen synthetischen " - "Datensätzen trainiert." - ), - zh=( - "以 GGUF 格式加载的 SmolLM2 Instruct 检查点。" - "'SmolLM2-1.7B' 是 17 亿参数指令模型,适用于端侧和边缘推理。" - "'SmolLM2-360M' 是 3.6 亿参数超紧凑模型,CPU 推理极快," - "内存占用极低(约 300 MB)。" - "两款模型均由 Hugging Face 在多样化合成数据集上训练。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. SmolLM2 models are optimized " - "for short to medium-length responses." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Los modelos " - "SmolLM2 están optimizados para respuestas cortas a medianas." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Os modelos " - "SmolLM2 são otimizados para respostas curtas a médias." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. SmolLM2-Modelle sind " - "für kurze bis mittellange Antworten optimiert." - ), - zh=( - "模型每次响应生成的最大新词元数。" - "约 1 词元 ≈ 0.75 个英文单词。" - "SmolLM2 模型针对短至中等长度的响应进行了优化。" - ), - ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大词元数", - ), - ) # type: ignore - - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 outputs are deterministic. Around 0.7 balances quality and " - "creativity." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad (rango 0.0-1.0). " - "En 0.0 las salidas son deterministas. Alrededor de 0.7 equilibra " - "calidad y creatividad." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade da saída " - "(intervalo 0.0-1.0). Em 0.0 as saídas são determinísticas. " - "Em torno de 0.7 equilibra qualidade e criatividade." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 sind die Ausgaben deterministisch. Um 0.7 balanciert " - "Qualität und Kreativität." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时输出为确定性结果,0.7 左右可平衡质量与创造力。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore - - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "frequency (range 0.0-2.0). Higher values discourage repetition." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). Valores más altos desincentivan " - "la repetición." - ), - pt=( - "Penaliza os tokens que já apareceram na saída com base na " - "frequência (intervalo 0.0-2.0). Valores mais altos desestimulam " - "a repetição." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Höhere Werte reduzieren " - "Wiederholungen." - ), - zh=( - "根据词元在输出中出现的频率对其进行惩罚(范围 0.0-2.0)。" - "较高的值可抑制重复内容。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalização de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", + REPO_ID = "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF" + GGUF_PATTERN = "*q8_0.gguf" + DOWNLOAD_SIZE_BYTES = 400_000_000 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#00695c" + DISPLAY_NAME = MultilingualString( + en="SmolLM2 360M Instruct", + es="SmolLM2 360M Instruct", + pt="SmolLM2 360M Instruct", + de="SmolLM2 360M Instruct", + zh="SmolLM2 360M Instruct", + ) + DESCRIPTION = MultilingualString( + en=( + "SmolLM2 360M Instruct is a compact 360M-parameter instruction-tuned " + "language model from HuggingFace, loaded as a Q8_0 GGUF for very fast " + "CPU inference. It is the lightest text-generation model in DashAI, " + "ideal for constrained devices. Model available at " + "https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct-GGUF." ), - ) # type: ignore - - context_window: schema_field( - int_field(ge=1, le=8192), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including both the " - "input prompt and the generated response. SmolLM2 models support " - "up to 8K tokens natively." - ), - es=( - "Presupuesto total de tokens por pasada, incluyendo prompt y " - "respuesta. Los modelos SmolLM2 soportan hasta 8K tokens de " - "forma nativa." - ), - pt=( - "Orçamento total de tokens por passagem, incluindo prompt e " - "resposta. Os modelos SmolLM2 suportam até 8K tokens nativamente." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabe-Prompt und Antwort. " - "SmolLM2-Modelle unterstützen nativ bis zu 8K Token." - ), - zh=( - "单次前向传播的词元总预算,包含输入提示和生成响应。" - "SmolLM2 模型原生支持最多 8K 词元。" - ), + es=( + "SmolLM2 360M Instruct es un modelo de lenguaje compacto de 360M " + "parametros ajustado para instrucciones por HuggingFace, cargado como " + "GGUF Q8_0 para inferencia muy rapida en CPU. Es el modelo de generacion " + "de texto mas ligero de DashAI, ideal para dispositivos limitados." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + pt=( + "SmolLM2 360M Instruct e um modelo de linguagem compacto de 360M " + "parametros ajustado para instrucoes pela HuggingFace, carregado como " + "GGUF Q8_0 para inferencia muito rapida em CPU. E o modelo de geracao de " + "texto mais leve do DashAI, ideal para dispositivos limitados." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM with no GPU requirement. SmolLM2 models are small " - "enough to run efficiently on CPU even on modest hardware." - ), - es=( - "Dispositivo de hardware para inferencia con llama.cpp. 'CPU' ejecuta " - "el modelo en RAM sin requisito de GPU. Los modelos SmolLM2 son lo " - "suficientemente pequeños para ejecutarse eficientemente en CPU " - "incluso " - "en hardware modesto." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' executa " - "o modelo na RAM sem requisito de GPU. Os modelos SmolLM2 são " - "pequenos o suficiente para rodar eficientemente em CPU " - "mesmo em hardware modesto." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "im RAM ohne GPU-Anforderung aus. SmolLM2-Modelle sind klein genug, " - "um auch auf bescheidener Hardware effizient auf der CPU zu laufen." - ), - zh=( - "llama.cpp 推理所用的硬件设备。'CPU' 将模型完全加载至内存运行," - "无需 GPU。" - "SmolLM2 模型体积小巧,即使在普通硬件上也能高效地在 CPU 上运行。" - ), + de=( + "SmolLM2 360M Instruct ist ein kompaktes 360M-Parameter-Instruktionsmodell " + "von HuggingFace, als Q8_0-GGUF fuer sehr schnelle CPU-Inferenz geladen. " + "Es ist das leichteste Textgenerierungsmodell in DashAI, ideal fuer " + "eingeschraenkte Geraete." ), - alias=MultilingualString( - en="Device", es="Dispositivo", pt="Dispositivo", de="Gerät", zh="设备" + zh=( + "SmolLM2 360M Instruct 是 HuggingFace 推出的 3.6 亿参数指令微调语言模型," + "以 Q8_0 GGUF 格式加载,支持极快的 CPU 推理。" + "这是 DashAI 中最轻量的文本生成模型,非常适合资源受限的设备。" ), - ) # type: ignore - - -class SmolLMModel(TextToTextGenerationTaskModel): - """SmolLM2 Instruct model for on-device text generation via llama.cpp. + ) - SmolLM2 is a family of compact, instruction-tuned language models developed by - Hugging Face TB, designed for efficient on-device and edge deployment. Unlike - larger language models, SmolLM2 achieves competitive benchmark results at very - small parameter counts by training on high-quality synthetic datasets including - cosmopedia-v2, FineWeb-Edu, and StackEdu. - The DashAI integration exposes the 360M and 1.7B Instruct variants. The 360M - model requires under 300 MB of RAM and runs comfortably on modest CPU hardware; - the 1.7B model delivers higher-quality responses while remaining deployable - without a GPU. +class SmolLM2_17BInstruct(GGUFTextGenerationModel): # noqa: N801 + """SmolLM2 1.7B Instruct GGUF checkpoint (Q4_K_M quantization). - Models are loaded as GGUF quantized checkpoints via ``llama-cpp-python``. The - quantization level is variant-dependent: Q8_0 for 360M (higher fidelity at small - size) and Q4_K_M for 1.7B (balanced quality/size trade-off). The filename is - resolved automatically from ``SMOLLM_FILENAME_MAP``. + A 1.7B-parameter instruction-tuned model from HuggingFace offering stronger + generation quality than the 360M variant. Weights are stored locally after a + one-time download from HuggingFace. References ---------- - - [1] Allal, L.B. et al. (2024). "SmolLM2: with great data, comes great - performance." Hugging Face Blog. - https://huggingface.co/blog/smollm2 - - [2] https://huggingface.co/HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF - - [3] https://huggingface.co/HuggingFaceTB/SmolLM2-360M-Instruct-GGUF + - https://huggingface.co/HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF """ - SCHEMA = SmolLMSchema + REPO_ID = "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF" + GGUF_PATTERN = "*q4_k_m.gguf" + DOWNLOAD_SIZE_BYTES = 1_100_000_000 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#00695c" - DISPLAY_NAME: str = MultilingualString( - en="SmolLM Model", - es="Modelo SmolLM", - pt="Modelo SmolLM", - de="SmolLM-Modell", - zh="SmolLM 模型", + DISPLAY_NAME = MultilingualString( + en="SmolLM2 1.7B Instruct", + es="SmolLM2 1.7B Instruct", + pt="SmolLM2 1.7B Instruct", + de="SmolLM2 1.7B Instruct", + zh="SmolLM2 1.7B Instruct", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "SmolLM2 is a family of compact instruction-tuned language models by " - "Hugging Face, loaded in GGUF format for efficient CPU and GPU inference " - "via the llama.cpp library. Designed for on-device and edge deployment, " - "SmolLM2 achieves strong benchmark results at very small parameter counts. " - "The 360M variant requires less than 300 MB of RAM, making it ideal for " - "resource-constrained environments. Available in 360M and 1.7B variants. " - "Models available at https://huggingface.co/HuggingFaceTB." + "SmolLM2 1.7B Instruct is a 1.7B-parameter instruction-tuned language " + "model from HuggingFace, loaded as a Q4_K_M GGUF for efficient CPU " + "inference. It provides better reasoning and generation quality than the " + "360M variant. Model available at " + "https://huggingface.co/HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF." ), es=( - "SmolLM2 es una familia de modelos de lenguaje compactos ajustados para " - "instrucciones por Hugging Face, cargados en formato GGUF para inferencia " - "eficiente en CPU y GPU mediante llama.cpp. Diseñados para despliegue en " - "dispositivo y en el borde, SmolLM2 logra fuertes resultados de benchmark " - "con muy pocos parámetros. La variante de 360M requiere menos de 300 MB de " - "RAM, ideal para entornos con recursos limitados. Disponible en variantes " - "de 360M y 1.7B. Modelos en https://huggingface.co/HuggingFaceTB." + "SmolLM2 1.7B Instruct es un modelo de lenguaje de 1.7B parametros " + "ajustado para instrucciones por HuggingFace, cargado como GGUF Q4_K_M " + "para inferencia eficiente en CPU. Ofrece mejor razonamiento y calidad de " + "generacion que la variante de 360M." ), pt=( - "SmolLM2 é uma família de modelos de linguagem compactos ajustados para " - "instruções pelo Hugging Face, carregados em formato GGUF para inferência " - "eficiente em CPU e GPU via llama.cpp. Projetados para implantação em " - "dispositivos e na borda, SmolLM2 alcança fortes resultados de benchmark " - "com pouquíssimos parâmetros. A variante de 360M requer menos de 300 MB de " - "RAM, ideal para ambientes com recursos limitados. " - "Disponível nas variantes " - "360M e 1.7B. Modelos disponíveis em https://huggingface.co/HuggingFaceTB." + "SmolLM2 1.7B Instruct e um modelo de linguagem de 1.7B parametros " + "ajustado para instrucoes pela HuggingFace, carregado como GGUF Q4_K_M " + "para inferencia eficiente em CPU. Oferece melhor raciocinio e qualidade " + "de geracao do que a variante de 360M." ), de=( - "SmolLM2 ist eine Familie kompakter instruktionsoptimierter Sprachmodelle " - "von Hugging Face, im GGUF-Format für effiziente CPU- und GPU-Inferenz " - "über llama.cpp geladen. Für Deployment auf Endgeräten und Edge-Systemen " - "konzipiert, erzielt SmolLM2 starke Benchmark-Ergebnisse mit sehr wenigen " - "Parametern. Die 360M-Variante benötigt weniger als 300 MB RAM und ist " - "ideal für ressourcenbeschränkte Umgebungen. Verfügbar in den Varianten " - "360M und 1,7B. Modelle unter https://huggingface.co/HuggingFaceTB." + "SmolLM2 1.7B Instruct ist ein 1,7B-Parameter-Instruktionsmodell von " + "HuggingFace, als Q4_K_M-GGUF fuer effiziente CPU-Inferenz geladen. " + "Es bietet besseres Schlussfolgern und Generierungsqualitaet als die " + "360M-Variante." ), zh=( - "SmolLM2 是 Hugging Face 推出的紧凑型指令微调语言模型系列," - "以 GGUF 格式加载,通过 llama.cpp 库高效推理。" - "专为端侧和边缘部署设计,提供 360M 和 1.7B 两种规格。" + "SmolLM2 1.7B Instruct 是 HuggingFace 推出的 17 亿参数指令微调语言模型," + "以 Q4_K_M GGUF 格式加载,支持高效 CPU 推理。" + "与 360M 变体相比,它具有更强的推理能力和生成质量。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a SmolLM2 Instruct GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. The GGUF filename is resolved from ``SMOLLM_FILENAME_MAP`` - (Q4_K_M for 1.7B, Q8_0 for 360M). - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to ``"HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF"``. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. " - "Please install it to use this model." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get( - "model_name", "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF" - ) - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = SMOLLM_FILENAME_MAP.get(self.model_name, "*q4_k_m.gguf") - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=(LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0), - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/tests/back/models/test_gguf_text_generation.py b/tests/back/models/test_gguf_text_generation.py new file mode 100644 index 000000000..5c40da2c2 --- /dev/null +++ b/tests/back/models/test_gguf_text_generation.py @@ -0,0 +1,89 @@ +"""Tests for the per-checkpoint GGUF text-generation components.""" + +import pathlib + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.initial_components import get_initial_components +from DashAI.back.models.hugging_face.llama_model import ( + Llama31_8BInstruct, + Llama32_1BInstruct, + Llama32_3BInstruct, +) +from DashAI.back.models.hugging_face.mistral_model import ( + Mistral7BInstructV03, + MistralNemoInstruct2407, +) +from DashAI.back.models.hugging_face.qwen_model import ( + Qwen25_05BInstruct, + Qwen25_15BInstruct, +) +from DashAI.back.models.hugging_face.smol_lm_model import ( + SmolLM2_17BInstruct, + SmolLM2_360MInstruct, +) + +ALL_CHECKPOINTS = [ + Qwen25_05BInstruct, + Qwen25_15BInstruct, + SmolLM2_360MInstruct, + SmolLM2_17BInstruct, + Llama31_8BInstruct, + Llama32_1BInstruct, + Llama32_3BInstruct, + Mistral7BInstructV03, + MistralNemoInstruct2407, +] + + +@pytest.fixture +def component_root(tmp_path): + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield pathlib.Path(tmp_path) + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize("cls", ALL_CHECKPOINTS) +def test_checkpoint_is_downloadable(cls): + assert issubclass(cls, HFDownloadableMixin) + assert cls.REQUIRES_DOWNLOAD is True + assert cls.DOWNLOAD_SIZE_BYTES is not None + assert cls.REPO_ID + assert cls.GGUF_PATTERN + + +@pytest.mark.parametrize("cls", ALL_CHECKPOINTS) +def test_hf_repos_single_file_entry(cls): + assert cls.hf_repos() == [(cls.REPO_ID, "model", [cls.GGUF_PATTERN])] + + +@pytest.mark.parametrize("cls", ALL_CHECKPOINTS) +def test_metadata_flags_download(cls): + meta = cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == cls.DOWNLOAD_SIZE_BYTES + + +def test_is_downloaded_reflects_component_dir(component_root): + cls = Qwen25_05BInstruct + assert cls.is_downloaded() is False + + repo_dir = component_root / cls.__name__ / cls.REPO_ID.split("/")[-1] + repo_dir.mkdir(parents=True) + (repo_dir / "model-q8_0.gguf").write_text("weights") + assert cls.is_downloaded() is True + + +def test_new_classes_registered_old_ones_gone(): + registered = {c.__name__ for c in get_initial_components()} + for cls in ALL_CHECKPOINTS: + assert cls.__name__ in registered + for old in {"QwenModel", "SmolLMModel", "LlamaModel", "MistralModel"}: + assert old not in registered From 6108b45cbe60595db424103eb11cc954aa115522 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 00:43:03 -0400 Subject: [PATCH 038/308] fix: keep generative session unknown-model response at 400 and update GGUF-split tests Reverts the generative session-creation guard to the endpoint's existing 400 "not registered" behavior for unknown models (dropping the added 422 that broke existing tests); the 409 download gate is unchanged. Updates the session fixtures to use a non-download model after the GGUF split removed the old QwenModel component. --- .../api_v1/endpoints/generative_session.py | 7 ---- .../test_generative_session_download_gate.py | 7 ++-- tests/back/api/test_session_api.py | 36 +++++++++++-------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/DashAI/back/api/api_v1/endpoints/generative_session.py b/DashAI/back/api/api_v1/endpoints/generative_session.py index 982b2b7c3..92c1b105e 100644 --- a/DashAI/back/api/api_v1/endpoints/generative_session.py +++ b/DashAI/back/api/api_v1/endpoints/generative_session.py @@ -38,13 +38,6 @@ async def upload_generative_session( with session_factory() as db: try: - # Guard: unknown model name -> 422 - if params.model_name not in component_registry: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=f"Unknown model '{params.model_name}'", - ) - # Check if the model is registered try: model_class = component_registry[params.model_name]["class"] diff --git a/tests/back/api/test_generative_session_download_gate.py b/tests/back/api/test_generative_session_download_gate.py index b284200a2..42c77a969 100644 --- a/tests/back/api/test_generative_session_download_gate.py +++ b/tests/back/api/test_generative_session_download_gate.py @@ -60,8 +60,8 @@ def test_upload_generative_session_rejects_undownloaded_model(client): assert "download" in resp.json()["detail"].lower() -def test_upload_generative_session_unknown_model_422(client): - """POSTing a session with an unregistered model_name must return HTTP 422.""" +def test_upload_generative_session_unknown_model_400(client): + """POSTing a session with an unregistered model_name must return HTTP 400.""" resp = client.post( "/api/v1/generative-session/", json={ @@ -70,4 +70,5 @@ def test_upload_generative_session_unknown_model_422(client): **_SESSION_PAYLOAD_BASE, }, ) - assert resp.status_code == 422 + assert resp.status_code == 400 + assert "is not registered" in resp.json()["detail"] diff --git a/tests/back/api/test_session_api.py b/tests/back/api/test_session_api.py index 1572a28eb..ece8d95cc 100644 --- a/tests/back/api/test_session_api.py +++ b/tests/back/api/test_session_api.py @@ -54,17 +54,20 @@ def create_session_2(client: TestClient): @pytest.fixture(scope="module", name="response_3") def create_session_3(client: TestClient): - """Create testing session 3 using job system.""" + """Create testing session 3 using a non-download-required model.""" params = { - "model_name": "QwenModel", - "task_name": "TextToTextGenerationTask", + "model_name": "StableDiffusionV2Model", + "task_name": "TextToImageGenerationTask", "parameters": { - "model_name": "Qwen/Qwen2.5-1.5B-Instruct-GGUF", - "max_tokens": 100, - "temperature": 0.9, - "frequency_penalty": 0.1, - "context_window": 512, + "num_inference_steps": 1, + "model_name": "sd2-community/stable-diffusion-2", + "guidance_scale": 6.0, "device": "CPU", + "negative_prompt": "", + "seed": 42, + "width": 256, + "height": 256, + "num_images_per_prompt": 1, }, "name": "session_3", "description": None, @@ -80,17 +83,20 @@ def create_session_3(client: TestClient): @pytest.fixture(scope="module", name="response_4") def create_session_4(client: TestClient): - """Create testing session 4 using job system.""" + """Create testing session 4 with an invalid task (valid model).""" params = { - "model_name": "QwenModel", + "model_name": "StableDiffusionV2Model", "task_name": "SomeTask", "parameters": { - "model_name": "Qwen/Qwen2.5-1.5B-Instruct-GGUF", - "max_tokens": 100, - "temperature": 0.9, - "frequency_penalty": 0.1, - "context_window": 512, + "num_inference_steps": 1, + "model_name": "sd2-community/stable-diffusion-2", + "guidance_scale": 6.0, "device": "CPU", + "negative_prompt": "", + "seed": 42, + "width": 256, + "height": 256, + "num_images_per_prompt": 1, }, "name": "session_4", "description": None, From a59d251e40e641b08e4836ec5957191ad67581de Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 00:45:11 -0400 Subject: [PATCH 039/308] feat: allow changing a generative session's model with a download gate Extend PATCH /generative-session/{id} to accept model_name, validating it is a registered generative model and rejecting undownloaded download-required models with 409. --- .../api_v1/endpoints/generative_session.py | 38 +++++++++- .../test_generative_session_download_gate.py | 74 +++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/DashAI/back/api/api_v1/endpoints/generative_session.py b/DashAI/back/api/api_v1/endpoints/generative_session.py index 92c1b105e..6c59fb3d6 100644 --- a/DashAI/back/api/api_v1/endpoints/generative_session.py +++ b/DashAI/back/api/api_v1/endpoints/generative_session.py @@ -321,7 +321,9 @@ async def update_generative_session( session_id: int, name: Union[str, None] = None, description: Union[str, None] = None, + model_name: Union[str, None] = None, session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), + component_registry: "ComponentRegistry" = Depends(lambda: di["component_registry"]), ): """Update the generative session associated with the provided ID. @@ -333,9 +335,15 @@ async def update_generative_session( New name for the session. description : Union[str, None], optional New description for the session. + model_name : Union[str, None], optional + New model (component name) for the session. Must be a registered + generative model; if it requires a download it must already be + downloaded. session_factory : Callable[..., ContextManager[Session]] A factory that creates a context manager that handles a SQLAlchemy session. The generated session can be used to access and query the database. + component_registry : ComponentRegistry + The DashAI component registry, used to validate the new model. Returns ------- @@ -345,8 +353,10 @@ async def update_generative_session( Raises ------ HTTPException - If the session does not exist, name is invalid, or name already exists. + If the session does not exist, the name is invalid or taken, or the new + model is unknown, not a generative model, or not yet downloaded. """ + from DashAI.back.models.base_generative_model import BaseGenerativeModel with session_factory() as db: try: session = db.get(GenerativeSession, session_id) @@ -385,7 +395,31 @@ async def update_generative_session( if description is not None: setattr(session, "description", description) - if name is not None or description is not None: + # Validate and apply a model change if provided + if model_name is not None: + try: + model_class = component_registry[model_name]["class"] + except KeyError as e: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Model {model_name} is not registered.", + ) from e + if not issubclass(model_class, BaseGenerativeModel): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Model {model_name} is not a valid generative model.", + ) + entry = component_registry[model_name] + if getattr( + entry["class"], "REQUIRES_DOWNLOAD", False + ) and not entry.get("downloaded", False): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Model {model_name} must be downloaded before use.", + ) + setattr(session, "model_name", model_name) + + if name is not None or description is not None or model_name is not None: session.last_modified = datetime.now() db.commit() db.refresh(session) diff --git a/tests/back/api/test_generative_session_download_gate.py b/tests/back/api/test_generative_session_download_gate.py index 42c77a969..57de0d995 100644 --- a/tests/back/api/test_generative_session_download_gate.py +++ b/tests/back/api/test_generative_session_download_gate.py @@ -72,3 +72,77 @@ def test_upload_generative_session_unknown_model_400(client): ) assert resp.status_code == 400 assert "is not registered" in resp.json()["detail"] + + +_SD_PARAMS = { + "num_inference_steps": 1, + "model_name": "sd2-community/stable-diffusion-2", + "guidance_scale": 6.0, + "device": "CPU", + "negative_prompt": "", + "seed": 42, + "width": 256, + "height": 256, + "num_images_per_prompt": 1, +} + + +def _create_sd_session(client, name): + return client.post( + "/api/v1/generative-session/", + json={ + "model_name": "StableDiffusionV2Model", + "task_name": "TextToImageGenerationTask", + "parameters": _SD_PARAMS, + "name": name, + "description": None, + }, + ) + + +def test_change_session_model_to_undownloaded_returns_409(client): + """Switching a session to a not-downloaded model must return HTTP 409.""" + created = _create_sd_session(client, "gen-switch-409") + assert created.status_code == 201 + session_id = created.json()["id"] + + resp = client.patch( + f"/api/v1/generative-session/{session_id}", + params={"model_name": "Qwen25_15BInstruct"}, + ) + assert resp.status_code == 409 + assert "download" in resp.json()["detail"].lower() + + client.delete(f"/api/v1/generative-session/{session_id}") + + +def test_change_session_model_unknown_returns_400(client): + """Switching a session to an unregistered model must return HTTP 400.""" + created = _create_sd_session(client, "gen-switch-400") + assert created.status_code == 201 + session_id = created.json()["id"] + + resp = client.patch( + f"/api/v1/generative-session/{session_id}", + params={"model_name": "__totally_bogus_generative_model_xyz__"}, + ) + assert resp.status_code == 400 + assert "is not registered" in resp.json()["detail"] + + client.delete(f"/api/v1/generative-session/{session_id}") + + +def test_change_session_model_valid_returns_200(client): + """Switching a session to a valid (non-download) model must succeed.""" + created = _create_sd_session(client, "gen-switch-200") + assert created.status_code == 201 + session_id = created.json()["id"] + + resp = client.patch( + f"/api/v1/generative-session/{session_id}", + params={"model_name": "StableDiffusionV2Model"}, + ) + assert resp.status_code == 200 + assert resp.json()["model_name"] == "StableDiffusionV2Model" + + client.delete(f"/api/v1/generative-session/{session_id}") From adb678121158fb79128e28a0b9a7fd69dfc1bb7d Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 09:04:04 -0400 Subject: [PATCH 040/308] feat: split Mixtral into per-quantization downloadable components Replace the enum-based MixtralModel with two per-quantization components (Q4_K_M ~26GB and Q2_K ~16GB) on the shared GGUFTextGenerationModel base, each downloading only its one GGUF file. --- DashAI/back/initial_components.py | 8 +- .../back/models/hugging_face/mixtral_model.py | 584 +++--------------- .../back/models/test_gguf_text_generation.py | 14 +- 3 files changed, 115 insertions(+), 491 deletions(-) diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 54e166f7e..056769619 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -156,7 +156,10 @@ Mistral7BInstructV03, MistralNemoInstruct2407, ) -from DashAI.back.models.hugging_face.mixtral_model import MixtralModel +from DashAI.back.models.hugging_face.mixtral_model import ( + Mixtral8x7BInstructQ2K, + Mixtral8x7BInstructQ4KM, +) from DashAI.back.models.hugging_face.modernbert_transformer import ModernBertTransformer from DashAI.back.models.hugging_face.multilingual_bert_transformer import ( MultilingualBertTransformer, @@ -367,7 +370,8 @@ def get_initial_components(): MiniLMTransformer, Mistral7BInstructV03, MistralNemoInstruct2407, - MixtralModel, + Mixtral8x7BInstructQ2K, + Mixtral8x7BInstructQ4KM, MultilingualBertTransformer, MLPClassifier, MLPRegression, diff --git a/DashAI/back/models/hugging_face/mixtral_model.py b/DashAI/back/models/hugging_face/mixtral_model.py index 402eb281a..978818d87 100644 --- a/DashAI/back/models/hugging_face/mixtral_model.py +++ b/DashAI/back/models/hugging_face/mixtral_model.py @@ -1,524 +1,132 @@ -from typing import List +"""Mixtral 8x7B Instruct GGUF checkpoint subclasses for DashAI. + +Mixtral 8x7B is a single Sparse Mixture-of-Experts repo published in several +quantizations. Each quantization is exposed as its own downloadable component so +the user fetches only the one GGUF file they intend to run. +""" -from DashAI.back.core.schema_fields import ( - BaseSchema, - enum_field, - float_field, - int_field, - schema_field, -) from DashAI.back.core.utils import MultilingualString -from DashAI.back.models.text_to_text_generation_model import ( - TextToTextGenerationTaskModel, -) -from DashAI.back.models.utils import ( - LLAMA_DEVICE_ENUM, - LLAMA_DEVICE_PLACEHOLDER, - LLAMA_DEVICE_TO_IDX, +from DashAI.back.models.hugging_face.gguf_text_generation_base import ( + GGUFTextGenerationModel, + GGUFTextGenerationSchema, ) +_MIXTRAL_REPO = "mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF" -class MixtralSchema(BaseSchema): - """Schema for MixtralModel hyperparameters. - Configures the checkpoint variant (with optional GGUF filename override), - generation length, sampling temperature, frequency penalty, context window, - and target device for Mixtral Sparse-MoE models loaded via - ``llama-cpp-python``. - """ - - model_name: schema_field( - enum_field( - enum=[ - "mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF", - ] - ), - placeholder="mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF", - description=MultilingualString( - en=( - "The Mixtral Instruct checkpoint to load in GGUF format. " - "'Mixtral-8x7B-Instruct-v0.1' is a Sparse Mixture-of-Experts (SMoE) " - "model with 8 expert networks of 7B parameters each, activating 2 " - "experts per token. It achieves quality comparable to larger dense " - "models while being more efficient at inference. " - "Warning: this model requires ~26 GB of RAM for the Q4_K_M " - "quantization." - ), - es=( - "El checkpoint Mixtral Instruct a cargar en formato GGUF. " - "'Mixtral-8x7B-Instruct-v0.1' es un modelo de Mezcla Dispersa de " - "Expertos (SMoE) con 8 redes expertas de 7B parámetros cada una, " - "activando 2 expertos por token. Logra calidad comparable a modelos " - "densos más grandes siendo más eficiente en inferencia. " - "Advertencia: este modelo requiere ~26 GB de RAM para la " - "cuantización Q4_K_M." - ), - pt=( - "O checkpoint Mixtral Instruct a carregar em formato GGUF. " - "'Mixtral-8x7B-Instruct-v0.1' é um modelo de Mistura Esparsa de " - "Especialistas (SMoE) com 8 redes especialistas de 7B parâmetros cada, " - "ativando 2 especialistas por token. Alcança qualidade comparável a " - "modelos densos maiores sendo mais eficiente na inferência. " - "Aviso: este modelo requer ~26 GB de RAM para a " - "quantização Q4_K_M." - ), - de=( - "Der im GGUF-Format zu ladende Mixtral Instruct-Checkpoint. " - "'Mixtral-8x7B-Instruct-v0.1' ist ein Sparse Mixture-of-Experts " - "(SMoE)-Modell mit 8 Expertennetzwerken à 7B Parameter, das 2 " - "Experten pro Token aktiviert. Es erreicht eine mit größeren dichten " - "Modellen vergleichbare Qualität bei effizienterer Inferenz. " - "Warnung: dieses Modell benötigt ~26 GB RAM für die " - "Q4_K_M-Quantisierung." - ), - zh=( - "以 GGUF 格式加载的 Mixtral Instruct 检查点。" - "'Mixtral-8x7B-Instruct-v0.1' 是一个稀疏混合专家(SMoE)模型," - "包含 8 个各 70 亿参数的专家网络,每个 token 激活 2 个专家。" - "推理效率高于同等质量的稠密模型。" - "警告:Q4_K_M 量化需要约 26 GB 内存。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore +class Mixtral8x7BInstructQ4KM(GGUFTextGenerationModel): + """Mixtral 8x7B Instruct GGUF checkpoint (Q4_K_M quantization). - filename: schema_field( - enum_field( - enum=[ - "Mixtral-8x7B-Instruct-v0.1.Q2_K.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q3_K_M.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q4_0.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q4_K_M.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q5_0.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q5_K_M.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q6_K.gguf", - "Mixtral-8x7B-Instruct-v0.1.Q8_0.gguf", - ] - ), - placeholder="Mixtral-8x7B-Instruct-v0.1.Q2_K.gguf", - description=MultilingualString( - en=( - "The specific GGUF file to load for the Mixtral model. The different " - "quantization levels (Q2_K, Q3_K_M, Q4_0, Q4_K_M, Q5_0, Q5_K_M, " - "Q6_K, Q8_0) represent various trade-offs between model size, " - "inference speed, and output quality. Q4_K_M is a popular choice " - "for balancing performance and resource requirements." - ), - es=( - "El archivo GGUF específico a cargar para el modelo Mixtral. Los " - "diferentes niveles de cuantización (Q2_K, Q3_K_M, Q4_0, Q4_K_M, " - "Q5_0, Q5_K_M, Q6_K, Q8_0) representan varios compromisos entre " - "tamaño del modelo, velocidad de inferencia y calidad de salida. " - "Q4_K_M es una opción popular para equilibrar rendimiento y " - "requisitos de recursos." - ), - pt=( - "O arquivo GGUF específico a carregar para o modelo Mixtral. Os " - "diferentes níveis de quantização (Q2_K, Q3_K_M, Q4_0, Q4_K_M, " - "Q5_0, Q5_K_M, Q6_K, Q8_0) representam vários compromissos entre " - "tamanho do modelo, velocidade de inferência e qualidade de saída. " - "Q4_K_M é uma escolha popular para equilibrar desempenho e " - "requisitos de recursos." - ), - de=( - "Die zu ladende spezifische GGUF-Datei für das Mixtral-Modell. " - "Die verschiedenen Quantisierungsstufen (Q2_K, Q3_K_M, Q4_0, Q4_K_M, " - "Q5_0, Q5_K_M, Q6_K, Q8_0) stellen verschiedene Kompromisse zwischen " - "Modellgröße, Inferenzgeschwindigkeit und Ausgabequalität dar. " - "Q4_K_M ist eine beliebte Wahl für ausgewogene Leistung und " - "Ressourcenbedarf." - ), - zh=( - "为 Mixtral 模型加载的具体 GGUF 文件。" - "不同量化级别(Q2_K、Q3_K_M、Q4_0、Q4_K_M、Q5_0、Q5_K_M、Q6_K、Q8_0)" - "在模型大小、推理速度和输出质量之间存在不同权衡。" - "Q4_K_M 是兼顾性能与资源需求的常用选择。" - ), - ), - alias=MultilingualString( - en="Filename", - es="Nombre del archivo", - pt="Nome do archivo", - de="Dateiname", - zh="文件名", - ), - ) # type: ignore + A Sparse Mixture-of-Experts model (8 experts of 7B parameters, 2 active per + token) from Mistral AI. The Q4_K_M quantization balances quality and size + and requires roughly 26 GB of RAM. Weights are stored locally after a + one-time download from HuggingFace. - max_tokens: schema_field( - int_field(ge=1), - placeholder=100, - description=MultilingualString( - en=( - "Maximum number of new tokens the model will generate per response. " - "Roughly 1 token ≈ 0.75 English words. Set to 100-200 for short " - "answers, 500-1000 for detailed explanations or code." - ), - es=( - "Número máximo de tokens nuevos que el modelo generará por respuesta. " - "Aproximadamente 1 token ≈ 0.75 palabras en español. Use 100-200 " - "para respuestas cortas, 500-1000 para explicaciones detalladas " - "o código." - ), - pt=( - "Número máximo de tokens novos que o modelo gerará por resposta. " - "Aproximadamente 1 token ≈ 0.75 palavras em português. Use 100-200 " - "para respostas curtas, 500-1000 para explicações detalhadas " - "ou código." - ), - de=( - "Maximale Anzahl neuer Token, die das Modell pro Antwort erzeugt. " - "Ungefähr 1 Token ≈ 0,75 englische Wörter. 100-200 für kurze " - "Antworten, 500-1000 für ausführliche Erklärungen oder Code." - ), - zh=( - "模型每次响应生成的最大新 token 数。" - "约 1 token ≈ 0.75 个英文单词。" - "短回答设为 100-200,详细说明或代码设为 500-1000。" - ), - ), - alias=MultilingualString( - en="Max tokens", - es="Tokens máximos", - pt="Tokens máximos", - de="Maximale neue Token", - zh="最大 token 数", - ), - ) # type: ignore - - temperature: schema_field( - float_field(ge=0.0, le=1.0), - placeholder=0.7, - description=MultilingualString( - en=( - "Sampling temperature controlling output randomness (range 0.0-1.0). " - "At 0.0 outputs are deterministic. Around 0.7 balances quality and " - "creativity." - ), - es=( - "Temperatura de muestreo que controla la aleatoriedad (rango 0.0-1.0). " - "En 0.0 las salidas son deterministas. Alrededor de 0.7 equilibra " - "calidad y creatividad." - ), - pt=( - "Temperatura de amostragem que controla a aleatoriedade " - "(intervalo 0.0-1.0). " - "Em 0.0 as saídas são determinísticas. Em torno de 0.7 equilibra " - "qualidade e criatividade." - ), - de=( - "Stichprobentemperatur zur Steuerung der Ausgabezufälligkeit (0.0-1.0)." - "Bei 0.0 sind die Ausgaben deterministisch. Um 0.7 balanciert " - "Qualität und Kreativität." - ), - zh=( - "控制输出随机性的采样温度(范围 0.0-1.0)。" - "0.0 时输出确定性最强,0.7 左右可平衡质量与创造性。" - ), - ), - alias=MultilingualString( - en="Temperature", - es="Temperatura", - pt="Temperatura", - de="Temperatur", - zh="温度", - ), - ) # type: ignore + References + ---------- + - Jiang et al. (2024) "Mixtral of Experts" https://arxiv.org/abs/2401.04088 + - https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF + """ - frequency_penalty: schema_field( - float_field(ge=0.0, le=2.0), - placeholder=0.1, - description=MultilingualString( - en=( - "Penalizes tokens that have already appeared in the output based on " - "frequency (range 0.0-2.0). Higher values discourage repetition." - ), - es=( - "Penaliza los tokens que ya aparecieron en la salida según su " - "frecuencia (rango 0.0-2.0). Valores más altos desincentivan " - "la repetición." - ), - pt=( - "Penaliza os tokens que já apareceram na saída com base na " - "frequência (intervalo 0.0-2.0). Valores mais altos desestimulam " - "a repetição." - ), - de=( - "Bestraft Token, die bereits in der Ausgabe erschienen sind, " - "basierend auf ihrer Häufigkeit (0.0-2.0). Höhere Werte reduzieren " - "Wiederholungen." - ), - zh=( - "根据频率对已出现在输出中的 token 施加惩罚(范围 0.0-2.0)。" - "较高的值可抑制重复内容。" - ), - ), - alias=MultilingualString( - en="Frequency penalty", - es="Penalización de frecuencia", - pt="Penalização de frequência", - de="Häufigkeitsstrafe", - zh="频率惩罚", + REPO_ID = _MIXTRAL_REPO + GGUF_PATTERN = "*Q4_K_M.gguf" + DOWNLOAD_SIZE_BYTES = 26_000_000_000 + SCHEMA = GGUFTextGenerationSchema + COLOR: str = "#4a148c" + DISPLAY_NAME = MultilingualString( + en="Mixtral 8x7B Instruct (Q4_K_M)", + es="Mixtral 8x7B Instruct (Q4_K_M)", + pt="Mixtral 8x7B Instruct (Q4_K_M)", + de="Mixtral 8x7B Instruct (Q4_K_M)", + zh="Mixtral 8x7B Instruct (Q4_K_M)", + ) + DESCRIPTION = MultilingualString( + en=( + "Mixtral 8x7B Instruct is a Sparse Mixture-of-Experts model by Mistral " + "AI (8 experts of 7B parameters, 2 active per token), loaded as a Q4_K_M " + "GGUF for a balance of quality and size. It matches larger dense models " + "on many tasks. Warning: requires ~26 GB of RAM; a GPU with >= 24 GB " + "VRAM is recommended. Model available at " + "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." ), - ) # type: ignore - - context_window: schema_field( - int_field(ge=1, le=32768), - placeholder=512, - description=MultilingualString( - en=( - "Total token budget for a single forward pass, including both the " - "input prompt and the generated response. Mixtral 8x7B supports " - "up to 32K tokens natively." - ), - es=( - "Presupuesto total de tokens por pasada, incluyendo prompt y " - "respuesta. Mixtral 8x7B soporta hasta 32K tokens de forma nativa." - ), - pt=( - "Orçamento total de tokens por passagem, incluindo prompt e " - "resposta. Mixtral 8x7B suporta até 32K tokens nativamente." - ), - de=( - "Gesamtes Token-Budget für einen einzelnen Vorwärtsdurchlauf, " - "einschließlich Eingabe-Prompt und generierter Antwort. " - "Mixtral 8x7B unterstützt nativ bis zu 32K Token." - ), - zh=( - "单次前向传播的总 token 预算,包含输入提示和生成响应。" - "Mixtral 8x7B 原生支持最多 32K token。" - ), + es=( + "Mixtral 8x7B Instruct es un modelo de Mezcla Dispersa de Expertos de " + "Mistral AI (8 expertos de 7B parametros, 2 activos por token), cargado " + "como GGUF Q4_K_M para equilibrar calidad y tamano. Advertencia: " + "requiere ~26 GB de RAM; se recomienda una GPU con >= 24 GB de VRAM." ), - alias=MultilingualString( - en="Context window", - es="Ventana de contexto", - pt="Janela de contexto", - de="Kontextfenster", - zh="上下文窗口", + pt=( + "Mixtral 8x7B Instruct e um modelo de Mistura Esparsa de Especialistas " + "da Mistral AI (8 especialistas de 7B parametros, 2 ativos por token), " + "carregado como GGUF Q4_K_M para equilibrar qualidade e tamanho. Aviso: " + "requer ~26 GB de RAM; recomenda-se uma GPU com >= 24 GB de VRAM." ), - ) # type: ignore - - device: schema_field( - enum_field(enum=LLAMA_DEVICE_ENUM), - placeholder=LLAMA_DEVICE_PLACEHOLDER, - description=MultilingualString( - en=( - "Hardware device for llama.cpp inference. 'CPU' runs the model " - "fully in RAM. A GPU option offloads all layers for faster inference. " - "Due to the large size of Mixtral, a GPU with at least 24 GB VRAM " - "is recommended for full GPU offloading." - ), - es=( - "Dispositivo de hardware para inferencia con llama.cpp. 'CPU' ejecuta " - "el modelo en RAM. Una opción de GPU descarga todas las capas para " - "inferencia más rápida. Debido al gran tamaño de Mixtral, " - "se recomienda " - "una GPU con al menos 24 GB de VRAM para descarga completa." - ), - pt=( - "Dispositivo de hardware para inferência com llama.cpp. 'CPU' executa " - "o modelo na RAM. Uma opção de GPU descarrega todas as camadas para " - "inferência mais rápida. Devido ao grande tamanho do Mixtral, " - "recomenda-se " - "uma GPU com pelo menos 24 GB de VRAM para descarregamento completo." - ), - de=( - "Hardware-Gerät für die llama.cpp-Inferenz. 'CPU' führt das Modell " - "im RAM aus. Eine GPU-Option lagert alle Schichten für schnellere " - "Inferenz aus. Aufgrund der Größe von Mixtral wird eine GPU mit " - "mindestens 24 GB VRAM für vollständiges GPU-Offloading empfohlen." - ), - zh=( - "llama.cpp 推理所用的硬件设备。'CPU' 在内存中运行模型。" - "GPU 选项可将所有层卸载以加快推理速度。" - "由于 Mixtral 体量较大,完整 GPU 卸载建议使用至少 24 GB 显存的 GPU。" - ), + de=( + "Mixtral 8x7B Instruct ist ein Sparse-Mixture-of-Experts-Modell von " + "Mistral AI (8 Experten mit 7B Parametern, 2 pro Token aktiv), als " + "Q4_K_M-GGUF fuer ein Gleichgewicht aus Qualitaet und Groesse geladen. " + "Warnung: benoetigt ~26 GB RAM; eine GPU mit >= 24 GB VRAM wird empfohlen." ), - alias=MultilingualString( - en="Device", - es="Dispositivo", - pt="Dispositivo", - de="Gerät", - zh="设备", + zh=( + "Mixtral 8x7B Instruct 是 Mistral AI 的稀疏混合专家模型" + "(8 个 70 亿参数专家,每 token 激活 2 个)," + "以 Q4_K_M GGUF 格式加载,兼顾质量与体积。" + "警告:需要约 26 GB 内存;建议使用显存不低于 24 GB 的 GPU。" ), - ) # type: ignore - + ) -class MixtralModel(TextToTextGenerationTaskModel): - """Mixtral Sparse Mixture-of-Experts (SMoE) model for text generation via llama.cpp. - Mixtral 8x7B is a transformer language model with 8 expert feed-forward - networks per layer; only 2 experts are activated per token, giving it the - computational cost of a 12B-parameter dense model while retaining capacity - equivalent to a 47B model. It matches or surpasses Llama 2 70B and GPT-3.5 - on most benchmarks. +class Mixtral8x7BInstructQ2K(GGUFTextGenerationModel): + """Mixtral 8x7B Instruct GGUF checkpoint (Q2_K quantization). - Models are loaded as GGUF quantized checkpoints via ``llama-cpp-python``. - The Q4_K_M quantization requires approximately 26 GB of RAM. + The smallest quantization of the Mixtral 8x7B Sparse Mixture-of-Experts + model, trading some quality for a lower memory footprint (roughly 16 GB). + Weights are stored locally after a one-time download from HuggingFace. References ---------- - - [1] Jiang et al. (2024) "Mixtral of Experts" https://arxiv.org/abs/2401.04088 - - [2] https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1 + - Jiang et al. (2024) "Mixtral of Experts" https://arxiv.org/abs/2401.04088 + - https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF """ - SCHEMA = MixtralSchema + REPO_ID = _MIXTRAL_REPO + GGUF_PATTERN = "*Q2_K.gguf" + DOWNLOAD_SIZE_BYTES = 16_000_000_000 + SCHEMA = GGUFTextGenerationSchema COLOR: str = "#4a148c" - DISPLAY_NAME: str = MultilingualString( - en="Mixtral Model", - es="Modelo Mixtral", - pt="Modelo Mixtral", - de="Mixtral-Modell", - zh="Mixtral 模型", + DISPLAY_NAME = MultilingualString( + en="Mixtral 8x7B Instruct (Q2_K)", + es="Mixtral 8x7B Instruct (Q2_K)", + pt="Mixtral 8x7B Instruct (Q2_K)", + de="Mixtral 8x7B Instruct (Q2_K)", + zh="Mixtral 8x7B Instruct (Q2_K)", ) - DESCRIPTION: str = MultilingualString( + DESCRIPTION = MultilingualString( en=( - "Mixtral 8x7B Instruct, a Sparse Mixture-of-Experts (SMoE) model by " - "Mistral AI, loaded in GGUF format for efficient CPU and GPU inference " - "via the llama.cpp library. The model uses 8 expert networks of 7B " - "parameters each, activating only 2 experts per token, achieving " - "performance comparable to larger dense models while being more efficient " - "at inference. Supports multi-turn conversation, reasoning, coding, and " - "general text generation. Warning: requires ~26 GB of RAM for the Q4_K_M " - "quantization. Model hosted at " + "Mixtral 8x7B Instruct is a Sparse Mixture-of-Experts model by Mistral " + "AI (8 experts of 7B parameters, 2 active per token). This Q2_K " + "quantization is the smallest variant (~16 GB), trading some output " + "quality for lower memory use. Model available at " "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." ), es=( - "Mixtral 8x7B Instruct, un modelo de Mezcla Dispersa de Expertos (SMoE) " - "de Mistral AI, cargado en formato GGUF para inferencia eficiente en CPU " - "y GPU mediante llama.cpp. El modelo usa 8 redes expertas de 7B parámetros " - "cada una, activando solo 2 expertos por token, logrando un rendimiento " - "comparable a modelos densos más grandes " - "siendo más eficiente en inferencia. " - "Soporta conversación multi-turno, razonamiento, programación y generación " - "de texto en general. Advertencia: requiere ~26 GB de RAM para la " - "cuantización Q4_K_M. Modelo en " - "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." + "Mixtral 8x7B Instruct es un modelo de Mezcla Dispersa de Expertos de " + "Mistral AI. Esta cuantizacion Q2_K es la variante mas pequena (~16 GB), " + "sacrificando algo de calidad por menor uso de memoria." ), pt=( - "Mixtral 8x7B Instruct, um modelo de Mistura Esparsa de Especialistas " - "(SMoE) da Mistral AI, carregado em formato GGUF para inferência eficiente " - "em CPU e GPU via biblioteca llama.cpp. O modelo usa 8 redes especialistas " - "de 7B parâmetros cada, ativando apenas 2 especialistas por token, " - "alcançando desempenho comparável a modelos densos maiores sendo mais " - "eficiente na inferência. Suporta conversa multi-turno, raciocínio, " - "programação e geração de texto em geral. Aviso: requer ~26 GB de RAM para " - "a quantização Q4_K_M. Modelo disponível em " - "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." + "Mixtral 8x7B Instruct e um modelo de Mistura Esparsa de Especialistas " + "da Mistral AI. Esta quantizacao Q2_K e a variante menor (~16 GB), " + "trocando alguma qualidade por menor uso de memoria." ), de=( - "Mixtral 8x7B Instruct, ein Sparse Mixture-of-Experts (SMoE)-Modell von " - "Mistral AI, im GGUF-Format für effiziente CPU- und GPU-Inferenz über die " - "llama.cpp-Bibliothek geladen. Das Modell nutzt 8 Expertennetzwerke à 7B " - "Parameter und aktiviert nur 2 Experten pro Token, was mit größeren dichten" - "Modellen vergleichbare Leistung bei effizienterer Inferenz ermöglicht. " - "Unterstützt Mehrfachdialog, Schlussfolgerung, Programmierung und " - "allgemeine " - "Textgenerierung. Warnung: erfordert ~26 GB RAM für die " - "Q4_K_M-Quantisierung. " - "Modell unter " - "https://huggingface.co/mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF." + "Mixtral 8x7B Instruct ist ein Sparse-Mixture-of-Experts-Modell von " + "Mistral AI. Diese Q2_K-Quantisierung ist die kleinste Variante " + "(~16 GB) und tauscht etwas Qualitaet gegen geringeren Speicherbedarf." ), zh=( - "Mixtral 8x7B Instruct 是 Mistral AI 的稀疏混合专家(SMoE)模型," - "以 GGUF 格式加载,通过 llama.cpp 库高效推理。" - "支持多轮对话、推理、编程和通用文本生成。" - "注意:Q4_K_M 量化需要约 26 GB 内存。" + "Mixtral 8x7B Instruct 是 Mistral AI 的稀疏混合专家模型。" + "此 Q2_K 量化是最小的变体(约 16 GB)," + "以部分输出质量换取更低的内存占用。" ), ) - - def __init__(self, **kwargs): - """Download and initialise a Mixtral 8x7B Instruct GGUF model via llama.cpp. - - The model weights are fetched from HuggingFace Hub using - ``Llama.from_pretrained`` and kept in memory for repeated calls to - ``generate``. - - Parameters - ---------- - **kwargs : dict - model_name : str, optional - HuggingFace repo ID for the GGUF checkpoint. - Defaults to - ``"mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF"``. - filename : str, optional - Specific GGUF quantization file to load (e.g. - ``"mixtral-8x7b-instruct-v0.1.Q4_K_M.gguf"``). Defaults to - the Q2_K variant. Higher quantizations use more RAM but - produce better output quality. - max_tokens : int, optional - Maximum number of new tokens to generate per call. Default 100. - temperature : float, optional - Sampling temperature in [0.0, 1.0]. Default 0.7. - frequency_penalty : float, optional - Token-frequency penalty in [0.0, 2.0]. Default 0.1. - context_window : int, optional - Total token budget (prompt + response) for a single forward - pass. Default 512. - device : str, optional - Target device from ``LLAMA_DEVICE_ENUM``. Any value whose - index is >= 0 enables full GPU offload (``n_gpu_layers=-1``); - ``"CPU"`` runs fully in RAM. - - Raises - ------ - RuntimeError - If ``llama-cpp-python`` is not installed. - """ - try: - from llama_cpp import Llama - except ImportError as e: - raise RuntimeError( - "llama-cpp-python is not installed. " - "Please install it to use this model." - ) from e - - kwargs = self.validate_and_transform(kwargs) - self.model_name = kwargs.get( - "model_name", "mradermacher/Mixtral-8x7B-Instruct-v0.1-GGUF" - ) - self.max_tokens = kwargs.pop("max_tokens", 100) - self.temperature = kwargs.pop("temperature", 0.7) - self.frequency_penalty = kwargs.pop("frequency_penalty", 0.1) - self.n_ctx = kwargs.pop("context_window", 512) - - self.filename = kwargs.get("filename", "Mixtral-8x7B-Instruct-v0.1.Q2_K.gguf") - use_gpu = LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 - - self.model = Llama.from_pretrained( - repo_id=self.model_name, - filename=self.filename, - verbose=True, - n_ctx=self.n_ctx, - n_gpu_layers=-1 if use_gpu else 0, - main_gpu=(LLAMA_DEVICE_TO_IDX.get(kwargs.get("device")) if use_gpu else 0), - ) - - def generate(self, prompt: list[dict[str, str]]) -> List[str]: - """Generate a reply for the given chat prompt. - - Parameters - ---------- - prompt : list of dict - Conversation history in OpenAI chat format. Each dict must contain - at least ``"role"`` (``"system"``, ``"user"``, or ``"assistant"``) - and ``"content"`` (the message text). - - Returns - ------- - list of str - A single-element list containing the model's reply text, extracted - from ``choices[0]["message"]["content"]``. - """ - output = self.model.create_chat_completion( - messages=prompt, - max_tokens=self.max_tokens, - temperature=self.temperature, - frequency_penalty=self.frequency_penalty, - ) - return [output["choices"][0]["message"]["content"]] diff --git a/tests/back/models/test_gguf_text_generation.py b/tests/back/models/test_gguf_text_generation.py index 5c40da2c2..714800608 100644 --- a/tests/back/models/test_gguf_text_generation.py +++ b/tests/back/models/test_gguf_text_generation.py @@ -16,6 +16,10 @@ Mistral7BInstructV03, MistralNemoInstruct2407, ) +from DashAI.back.models.hugging_face.mixtral_model import ( + Mixtral8x7BInstructQ2K, + Mixtral8x7BInstructQ4KM, +) from DashAI.back.models.hugging_face.qwen_model import ( Qwen25_05BInstruct, Qwen25_15BInstruct, @@ -35,6 +39,8 @@ Llama32_3BInstruct, Mistral7BInstructV03, MistralNemoInstruct2407, + Mixtral8x7BInstructQ4KM, + Mixtral8x7BInstructQ2K, ] @@ -85,5 +91,11 @@ def test_new_classes_registered_old_ones_gone(): registered = {c.__name__ for c in get_initial_components()} for cls in ALL_CHECKPOINTS: assert cls.__name__ in registered - for old in {"QwenModel", "SmolLMModel", "LlamaModel", "MistralModel"}: + for old in { + "QwenModel", + "SmolLMModel", + "LlamaModel", + "MistralModel", + "MixtralModel", + }: assert old not in registered From e90d4ca9fc8b817ad046f20396b85da619c9ec1e Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 09:12:50 -0400 Subject: [PATCH 041/308] feat: add session-level model switcher for generative sessions Add a model switcher in the generative chat header that lists same-task models (undownloaded ones disabled), persists the choice via the gated PATCH endpoint, and reloads the params panel. Extend updateGenerativeSession with model_name and refetch the create-session picker when a model download completes. --- DashAI/front/src/api/session.ts | 2 +- .../generative/CreateSessionCenter.jsx | 2 + .../generative/CreateSessionContext.jsx | 26 ++--- .../components/generative/GenerativeChat.jsx | 15 ++- .../components/generative/ModelSwitcher.jsx | 104 ++++++++++++++++++ .../src/components/generative/ParamsBar.jsx | 3 +- .../src/utils/i18n/locales/de/generative.json | 7 +- .../src/utils/i18n/locales/en/generative.json | 7 +- .../src/utils/i18n/locales/es/generative.json | 7 +- .../src/utils/i18n/locales/pt/generative.json | 7 +- .../src/utils/i18n/locales/zh/generative.json | 7 +- 11 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 DashAI/front/src/components/generative/ModelSwitcher.jsx diff --git a/DashAI/front/src/api/session.ts b/DashAI/front/src/api/session.ts index a08e4d534..4f1ed1602 100644 --- a/DashAI/front/src/api/session.ts +++ b/DashAI/front/src/api/session.ts @@ -31,7 +31,7 @@ export const updateGenerativeSession = async ({ formData, }: { id: string; - formData: { name?: string; task_name?: string }; + formData: { name?: string; task_name?: string; model_name?: string }; }): Promise => { const response = await api.patch(`/v1/generative-session/${id}`, null, { params: formData, diff --git a/DashAI/front/src/components/generative/CreateSessionCenter.jsx b/DashAI/front/src/components/generative/CreateSessionCenter.jsx index 2480df2b4..f15ce47ea 100644 --- a/DashAI/front/src/components/generative/CreateSessionCenter.jsx +++ b/DashAI/front/src/components/generative/CreateSessionCenter.jsx @@ -29,6 +29,7 @@ export default function CreateSessionCenter() { step, models, loadingModels, + refetchModels, selectedModel, handleSelectModel, formik, @@ -126,6 +127,7 @@ export default function CreateSessionCenter() { components={models} selected={selectedModel} onSelect={handleSelectModelWithTour} + onDownloadChange={() => refetchModels()} categoryKey="task_display_name" searchPlaceholder={t("generative:label.searchModels")} tourDataFor={tourContext?.run ? "model-card-qwen" : null} diff --git a/DashAI/front/src/components/generative/CreateSessionContext.jsx b/DashAI/front/src/components/generative/CreateSessionContext.jsx index 43a639088..34bdd62ce 100644 --- a/DashAI/front/src/components/generative/CreateSessionContext.jsx +++ b/DashAI/front/src/components/generative/CreateSessionContext.jsx @@ -42,14 +42,14 @@ export function CreateSessionProvider({ children }) { const [selectedModel, setSelectedModel] = useState(null); const [submitting, setSubmitting] = useState(false); - // Load all generative models grouped by their compatible task. - // Re-fetches when language changes so display_name/description are translated. - useEffect(() => { - if (!tasks || tasks.length === 0) return; - let cancelled = false; + // Load all generative models grouped by their compatible task. Exposed as + // refetchModels so callers (e.g. an inline download control) can refresh the + // list when a model's downloaded state changes. Re-fetches when language + // changes so display_name/description are translated. + const loadModels = useCallback(() => { + if (!tasks || tasks.length === 0) return Promise.resolve(); setLoadingModels(true); - - Promise.all( + return Promise.all( tasks.map((task) => getRelatedComponents(task.name).then((components) => components.map((c) => ({ @@ -61,7 +61,6 @@ export function CreateSessionProvider({ children }) { ), ) .then((perTaskLists) => { - if (cancelled) return; // Deduplicate by model name (a model may appear under several tasks). const seen = new Set(); const flat = []; @@ -79,14 +78,14 @@ export function CreateSessionProvider({ children }) { }); }) .finally(() => { - if (!cancelled) setLoadingModels(false); + setLoadingModels(false); }); - - return () => { - cancelled = true; - }; }, [tasks, enqueueSnackbar, t]); + useEffect(() => { + loadModels(); + }, [loadModels]); + const processedProperties = useMemo( () => selectedModel?.schema?.properties @@ -219,6 +218,7 @@ export function CreateSessionProvider({ children }) { step, models, loadingModels, + refetchModels: loadModels, selectedModel, handleSelectModel, formik, diff --git a/DashAI/front/src/components/generative/GenerativeChat.jsx b/DashAI/front/src/components/generative/GenerativeChat.jsx index 5f12cd2d3..6609cb543 100644 --- a/DashAI/front/src/components/generative/GenerativeChat.jsx +++ b/DashAI/front/src/components/generative/GenerativeChat.jsx @@ -15,6 +15,7 @@ import { enqueueGenerativeProcessJob } from "../../api/job"; import { startJobQueue } from "../../api/job"; import { getHistoryBySessionId, getSessionById } from "../../api/session"; import InfoSessionModal from "./InfoSessionModal"; +import ModelSwitcher from "./ModelSwitcher"; import { useSnackbar } from "notistack"; import { MediaInput } from "./MediaInput"; import { Trans, useTranslation } from "react-i18next"; @@ -29,6 +30,8 @@ export default function GenerativeChat() { selectedTaskName: taskName, tasks, paramsVersion, + setParamsVersion, + fetchSessions, } = useGenerative(); const inputsCardinality = useMemo(() => { @@ -254,7 +257,17 @@ export default function GenerativeChat() { {sessionInfo?.description ? ":" : null} {sessionInfo?.description} - + + { + getSessionInfo(); + fetchSessions(); + setParamsVersion((v) => v + 1); + }} + /> setSessionInfoVisible(true)}> { + if (!taskName) return; + getRelatedComponents(taskName) + .then((components) => setModels(components || [])) + .catch(() => setModels([])); + }, [taskName]); + + const handleChange = async (event) => { + const newModel = event.target.value; + if (!newModel || newModel === currentModelName) return; + setSaving(true); + try { + await updateGenerativeSession({ + id: sessionId, + formData: { model_name: newModel }, + }); + if (onChanged) onChanged(newModel); + } catch (error) { + const status = error?.response?.status; + enqueueSnackbar( + status === 409 + ? t("common:componentDownload.mustDownload") + : t("generative:error.modelSwitchFailed"), + { variant: "error" }, + ); + } finally { + setSaving(false); + } + }; + + // Ensure the current model is always a selectable value even if the list + // has not loaded yet (avoids an out-of-range MUI Select warning). + const hasCurrent = models.some((m) => m.name === currentModelName); + const options = hasCurrent + ? models + : [{ name: currentModelName, display_name: currentModelName }, ...models]; + + if (!currentModelName) return null; + + return ( + + + {t("generative:label.sessionModel")} + + + + ); +} + +ModelSwitcher.propTypes = { + sessionId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + taskName: PropTypes.string, + currentModelName: PropTypes.string, + onChanged: PropTypes.func, +}; diff --git a/DashAI/front/src/components/generative/ParamsBar.jsx b/DashAI/front/src/components/generative/ParamsBar.jsx index a6aa35ca7..3b85c76dd 100644 --- a/DashAI/front/src/components/generative/ParamsBar.jsx +++ b/DashAI/front/src/components/generative/ParamsBar.jsx @@ -21,6 +21,7 @@ export default function ParamsBar({ onToggle }) { const { selectedSessionId, selectedTaskName: taskName, + paramsVersion, setParamsVersion, } = useGenerative(); const [parameters, setParameters] = useState({}); @@ -60,7 +61,7 @@ export default function ParamsBar({ onToggle }) { } }); }); - }, [selectedSessionId, t]); + }, [selectedSessionId, t, paramsVersion]); useEffect(() => { if (selectedModel?.schema?.properties) { diff --git a/DashAI/front/src/utils/i18n/locales/de/generative.json b/DashAI/front/src/utils/i18n/locales/de/generative.json index 2446ff33d..701767f7c 100644 --- a/DashAI/front/src/utils/i18n/locales/de/generative.json +++ b/DashAI/front/src/utils/i18n/locales/de/generative.json @@ -17,7 +17,8 @@ "nameRequired": "Name ist erforderlich", "processError": "Der Prozess ist fehlgeschlagen. Wird gelöscht... {{error}}", "sessionNameEmpty": "Sitzungsname darf nicht leer sein", - "sessionNameExists": "Eine Sitzung mit diesem Namen existiert bereits" + "sessionNameExists": "Eine Sitzung mit diesem Namen existiert bereits", + "modelSwitchFailed": "Sitzungsmodell konnte nicht geändert werden" }, "label": { "confirmDeleteSession": "Sind Sie sicher, dass Sie die Sitzung \"{{name}}\" löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", @@ -57,7 +58,9 @@ "attachMedia": "Medien anhängen", "attachMediaToContinue": "Medien anhängen, um fortzufahren", "noInputAvailable": "Keine Eingabe für diese Aufgabe verfügbar", - "selectModelCrumb": "Modell auswählen" + "selectModelCrumb": "Modell auswählen", + "sessionModel": "Modell", + "downloadRequired": "Download erforderlich" }, "message": { "sessionCreatedSuccess": "Sitzung erfolgreich erstellt.", diff --git a/DashAI/front/src/utils/i18n/locales/en/generative.json b/DashAI/front/src/utils/i18n/locales/en/generative.json index 654597724..9bd5ddb0c 100644 --- a/DashAI/front/src/utils/i18n/locales/en/generative.json +++ b/DashAI/front/src/utils/i18n/locales/en/generative.json @@ -17,7 +17,8 @@ "nameRequired": "Name is required", "processError": "The process has failed. Deleting it... {{error}}", "sessionNameEmpty": "Session name cannot be empty", - "sessionNameExists": "A session with this name already exists" + "sessionNameExists": "A session with this name already exists", + "modelSwitchFailed": "Failed to change the session model" }, "label": { "confirmDeleteSession": "Are you sure you want to delete the session \"{{name}}\"? This action cannot be undone.", @@ -57,7 +58,9 @@ "attachMedia": "Attach media", "attachMediaToContinue": "Attach media to continue", "noInputAvailable": "No input available for this task", - "selectModelCrumb": "Select Model" + "selectModelCrumb": "Select Model", + "sessionModel": "Model", + "downloadRequired": "download required" }, "message": { "sessionCreatedSuccess": "Session successfully created.", diff --git a/DashAI/front/src/utils/i18n/locales/es/generative.json b/DashAI/front/src/utils/i18n/locales/es/generative.json index 97f059735..08f4bdefe 100644 --- a/DashAI/front/src/utils/i18n/locales/es/generative.json +++ b/DashAI/front/src/utils/i18n/locales/es/generative.json @@ -17,7 +17,8 @@ "nameRequired": "Se requiere un nombre", "processError": "El proceso ha fallado. Eliminándolo... {{error}}", "sessionNameEmpty": "El nombre de la sesión no puede estar vacío", - "sessionNameExists": "Ya existe una sesión con este nombre" + "sessionNameExists": "Ya existe una sesión con este nombre", + "modelSwitchFailed": "No se pudo cambiar el modelo de la sesión" }, "label": { "confirmDeleteSession": "¿Seguro que quieres eliminar la sesión \"{{name}}\"? Esta acción no se puede deshacer.", @@ -57,7 +58,9 @@ "attachMedia": "Adjuntar medio", "attachMediaToContinue": "Adjunta medios para continuar", "noInputAvailable": "No hay entrada disponible para esta tarea", - "selectModelCrumb": "Select Model" + "selectModelCrumb": "Select Model", + "sessionModel": "Modelo", + "downloadRequired": "descarga requerida" }, "message": { "sessionCreatedSuccess": "Sesión creada exitosamente.", diff --git a/DashAI/front/src/utils/i18n/locales/pt/generative.json b/DashAI/front/src/utils/i18n/locales/pt/generative.json index c2b513a51..69f867d4b 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/generative.json +++ b/DashAI/front/src/utils/i18n/locales/pt/generative.json @@ -17,7 +17,8 @@ "nameRequired": "É necessário um nome", "processError": "O processo falhou. Excluindo... {{error}}", "sessionNameEmpty": "O nome da sessão não pode estar vazio", - "sessionNameExists": "Já existe uma sessão com este nome" + "sessionNameExists": "Já existe uma sessão com este nome", + "modelSwitchFailed": "Falha ao alterar o modelo da sessão" }, "label": { "confirmDeleteSession": "Tem certeza de que deseja excluir a sessão \"{{name}}\"? Esta ação não pode ser desfeita.", @@ -57,7 +58,9 @@ "attachMedia": "Anexar mídia", "attachMediaToContinue": "Anexe uma mídia para continuar", "noInputAvailable": "Não há entrada disponível para esta tarefa", - "selectModelCrumb": "Selecionar Modelo" + "selectModelCrumb": "Selecionar Modelo", + "sessionModel": "Modelo", + "downloadRequired": "download necessário" }, "message": { "sessionCreatedSuccess": "Sessão criada com sucesso.", diff --git a/DashAI/front/src/utils/i18n/locales/zh/generative.json b/DashAI/front/src/utils/i18n/locales/zh/generative.json index bd86dc015..6f3b9870c 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/generative.json +++ b/DashAI/front/src/utils/i18n/locales/zh/generative.json @@ -17,7 +17,8 @@ "nameRequired": "名称为必填项", "processError": "处理失败,正在删除...{{error}}", "sessionNameEmpty": "会话名称不能为空", - "sessionNameExists": "已存在同名会话" + "sessionNameExists": "已存在同名会话", + "modelSwitchFailed": "更改会话模型失败" }, "label": { "confirmDeleteSession": "确定要删除会话 \"{{name}}\" 吗?此操作无法撤销。", @@ -57,7 +58,9 @@ "attachMedia": "附加媒体", "attachMediaToContinue": "附加媒体以继续", "noInputAvailable": "此任务无可用输入", - "selectModelCrumb": "选择模型" + "selectModelCrumb": "选择模型", + "sessionModel": "模型", + "downloadRequired": "需下载" }, "message": { "sessionCreatedSuccess": "会话创建成功。", From ddc682d1745368207a3859d336f82fb10feaf0e4 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 09:36:59 -0400 Subject: [PATCH 042/308] fix: reconcile component download status against filesystem on read --- .../back/api/api_v1/endpoints/components.py | 8 ++++++ tests/back/api/test_components_api.py | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/DashAI/back/api/api_v1/endpoints/components.py b/DashAI/back/api/api_v1/endpoints/components.py index 873d2f6df..64b59ec84 100644 --- a/DashAI/back/api/api_v1/endpoints/components.py +++ b/DashAI/back/api/api_v1/endpoints/components.py @@ -230,6 +230,14 @@ async def get_components( components_with_related_type, ) + # Reconcile the download state of download required components against the + # filesystem before returning. Downloads happen in the worker process, so + # the in memory registry flag can be stale; a fresh check (a cheap folder + # stat per downloadable component) keeps the list truthful. + for comp_name, component_dict in selected_components.items(): + if getattr(component_dict.get("class"), "REQUIRES_DOWNLOAD", False): + component_registry.refresh_download_status(comp_name) + return [ _filter_by_language(_delete_class(component_dict), accept_language) for component_dict in selected_components.values() diff --git a/tests/back/api/test_components_api.py b/tests/back/api/test_components_api.py index 26cd14699..6f598d304 100644 --- a/tests/back/api/test_components_api.py +++ b/tests/back/api/test_components_api.py @@ -164,6 +164,7 @@ def test_get_component_by_id(client: TestClient): "description": "Task 1.", "display_name": "Test Task 1", "color": "#795548", + "downloaded": True, } response = client.get("/api/v1/component/TestTask2/") @@ -182,6 +183,7 @@ def test_get_component_by_id(client: TestClient): "description": "Task 2.", "display_name": None, "color": None, + "downloaded": True, } response = client.get("/api/v1/component/TestDataloader1/") @@ -199,6 +201,7 @@ def test_get_component_by_id(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, } @@ -292,6 +295,7 @@ def test_get_components_select_only_tasks(client: TestClient): "description": "Task 1.", "display_name": "Test Task 1", "color": "#795548", + "downloaded": True, }, { "name": "TestTask2", @@ -307,6 +311,7 @@ def test_get_components_select_only_tasks(client: TestClient): "description": "Task 2.", "display_name": None, "color": None, + "downloaded": True, }, ] @@ -330,6 +335,7 @@ def test_get_components_select_only_dataloaders(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -344,6 +350,7 @@ def test_get_components_select_only_dataloaders(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader3", @@ -358,6 +365,7 @@ def test_get_components_select_only_dataloaders(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -438,6 +446,7 @@ def test_get_components_ignore_models(client: TestClient): "description": "Task 1.", "display_name": "Test Task 1", "color": "#795548", + "downloaded": True, }, { "name": "TestTask2", @@ -453,6 +462,7 @@ def test_get_components_ignore_models(client: TestClient): "description": "Task 2.", "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader1", @@ -467,6 +477,7 @@ def test_get_components_ignore_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -481,6 +492,7 @@ def test_get_components_ignore_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader3", @@ -495,6 +507,7 @@ def test_get_components_ignore_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -517,6 +530,7 @@ def test_get_components_ignore_tasks_and_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -531,6 +545,7 @@ def test_get_components_ignore_tasks_and_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader3", @@ -545,6 +560,7 @@ def test_get_components_ignore_tasks_and_models(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -608,6 +624,7 @@ def test_get_components_related_inverse_relation(client: TestClient): "description": "Task 1.", "display_name": "Test Task 1", "color": "#795548", + "downloaded": True, } ] @@ -653,6 +670,7 @@ def test_get_components_dataloader_component_parent(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -667,6 +685,7 @@ def test_get_components_dataloader_component_parent(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -706,6 +725,7 @@ def test_get_components_by_type_and_task(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -720,6 +740,7 @@ def test_get_components_by_type_and_task(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -758,6 +779,7 @@ def test_get_components_select_and_ignore_by_type(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -772,6 +794,7 @@ def test_get_components_select_and_ignore_by_type(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader3", @@ -786,6 +809,7 @@ def test_get_components_select_and_ignore_by_type(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] @@ -811,6 +835,7 @@ def test_get_components_select_type_and_parent(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, { "name": "TestDataloader2", @@ -825,5 +850,6 @@ def test_get_components_select_type_and_parent(client: TestClient): "description": None, "display_name": None, "color": None, + "downloaded": True, }, ] From 70b0e618eeddcab1b01fb0e9ae691e496baa1d26 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 09:44:27 -0400 Subject: [PATCH 043/308] fix: keep download controls mounted and add inline delete for models --- .../components/custom/ComponentSelector.jsx | 10 ++- .../src/components/models/ModelsRightBar.jsx | 70 +++++++++---------- .../models/model/ComponentDownloadControl.jsx | 46 +++++++++++- .../model/ComponentDownloadControl.test.jsx | 37 +++++++++- .../components/models/model/ModelListItem.jsx | 14 ++++ 5 files changed, 130 insertions(+), 47 deletions(-) diff --git a/DashAI/front/src/components/custom/ComponentSelector.jsx b/DashAI/front/src/components/custom/ComponentSelector.jsx index 97be244b8..997b9790d 100644 --- a/DashAI/front/src/components/custom/ComponentSelector.jsx +++ b/DashAI/front/src/components/custom/ComponentSelector.jsx @@ -109,8 +109,8 @@ function ComponentSelector({ const renderCard = (component) => { const isSelected = selected?.name === component.name; const icon = getIcon?.(component); - const needsDownload = - Boolean(component.metadata?.requires_download) && !component.downloaded; + const requiresDownload = Boolean(component.metadata?.requires_download); + const needsDownload = requiresDownload && !component.downloaded; const isCsvComponent = tourDataFor && (tourDataMatchFn @@ -181,13 +181,11 @@ function ComponentSelector({ /> )} - {needsDownload && ( + {requiresDownload && ( e.stopPropagation()}> { - if (isDownloaded) onDownloadChange?.(component); - }} + onStatusChange={() => onDownloadChange?.(component)} /> )} diff --git a/DashAI/front/src/components/models/ModelsRightBar.jsx b/DashAI/front/src/components/models/ModelsRightBar.jsx index b4864cd07..85be2c054 100644 --- a/DashAI/front/src/components/models/ModelsRightBar.jsx +++ b/DashAI/front/src/components/models/ModelsRightBar.jsx @@ -243,46 +243,40 @@ export default function ModelsRightBar({ onToggle }) { ) : ( {filteredModels.map((model, index) => { - const needsDownload = - Boolean(model.metadata?.requires_download) && - !model.downloaded; + const requiresDownload = Boolean( + model.metadata?.requires_download, + ); + const needsDownload = requiresDownload && !model.downloaded; return ( - - handleModelClick(model) - } - data-tour={index === 0 ? "first-model" : undefined} - /> - {needsDownload && ( - { - if (isDownloaded) fetchModels(); - }} - /> - )} - + model={ + needsDownload + ? { + ...model, + tooltip: t( + "common:componentDownload.mustDownload", + ), + } + : model + } + disabled={needsDownload} + onClick={ + needsDownload + ? undefined + : () => handleModelClick(model) + } + data-tour={index === 0 ? "first-model" : undefined} + action={ + requiresDownload ? ( + fetchModels()} + /> + ) : null + } + /> ); })} diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx index bcc6e207c..d751d43dc 100644 --- a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx @@ -1,5 +1,13 @@ import React, { useState, useEffect, useRef } from "react"; -import { Box, Button, LinearProgress, Typography } from "@mui/material"; +import { + Box, + Button, + CircularProgress, + IconButton, + LinearProgress, + Tooltip, + Typography, +} from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import DeleteIcon from "@mui/icons-material/Delete"; import { useTranslation } from "react-i18next"; @@ -18,7 +26,11 @@ const formatSize = (bytes) => { return `${Math.round(mb)} MB`; }; -const ComponentDownloadControl = ({ component, onStatusChange }) => { +const ComponentDownloadControl = ({ + component, + onStatusChange, + compact = false, +}) => { const { t } = useTranslation(["common"]); const { enqueueSnackbar } = useSnackbar(); const meta = component.metadata || {}; @@ -86,6 +98,36 @@ const ComponentDownloadControl = ({ component, onStatusChange }) => { } }; + if (compact) { + if (downloading) { + return ( + + + + ); + } + if (downloaded) { + return ( + + + + + + ); + } + return ( + + + + + + ); + } + if (downloading) { return ( diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx index 6c90798e3..9bd72583a 100644 --- a/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx @@ -16,7 +16,10 @@ jest.mock("../../../utils/jobPoller", () => ({ })); import ComponentDownloadControl from "./ComponentDownloadControl"; -import { downloadComponent } from "../../../api/component"; +import { + downloadComponent, + deleteComponentDownload, +} from "../../../api/component"; const component = { name: "OpusMtEnRoaTransformer", @@ -38,4 +41,36 @@ describe("ComponentDownloadControl", () => { expect(downloadComponent).toHaveBeenCalledWith("OpusMtEnRoaTransformer"), ); }); + + it("compact mode triggers download from an icon button", async () => { + renderWithProviders( + {}} + />, + ); + const button = await screen.findByRole("button", { name: /download/i }); + fireEvent.click(button); + await waitFor(() => + expect(downloadComponent).toHaveBeenCalledWith("OpusMtEnRoaTransformer"), + ); + }); + + it("shows a delete control for a downloaded component and deletes it", async () => { + renderWithProviders( + {}} + />, + ); + const button = await screen.findByRole("button", { name: /delete/i }); + fireEvent.click(button); + await waitFor(() => + expect(deleteComponentDownload).toHaveBeenCalledWith( + "OpusMtEnRoaTransformer", + ), + ); + }); }); diff --git a/DashAI/front/src/components/models/model/ModelListItem.jsx b/DashAI/front/src/components/models/model/ModelListItem.jsx index c846e9baa..dd7a07d29 100644 --- a/DashAI/front/src/components/models/model/ModelListItem.jsx +++ b/DashAI/front/src/components/models/model/ModelListItem.jsx @@ -9,6 +9,7 @@ export default function ModelListItem({ model, disabled = false, onClick, + action = null, ...props }) { const theme = useTheme(); @@ -152,6 +153,19 @@ export default function ModelListItem({ {model.display_name || model.name} + + {/* Trailing action (e.g. download/delete control) */} + {action && ( + e.stopPropagation()} + onMouseDown={(e) => e.stopPropagation()} + onDragStart={(e) => e.stopPropagation()} + sx={{ flexShrink: 0, display: "flex", alignItems: "center" }} + > + {action} + + )} {!disabled && ( From 823bf0b44051838201fca6adf70b073168f5286a Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 09:49:47 -0400 Subject: [PATCH 044/308] feat: notify on successful component download deletion --- .../src/components/models/model/ComponentDownloadControl.jsx | 3 +++ DashAI/front/src/utils/i18n/locales/de/common.json | 1 + DashAI/front/src/utils/i18n/locales/en/common.json | 1 + DashAI/front/src/utils/i18n/locales/es/common.json | 1 + DashAI/front/src/utils/i18n/locales/pt/common.json | 1 + DashAI/front/src/utils/i18n/locales/zh/common.json | 1 + 6 files changed, 8 insertions(+) diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx index d751d43dc..4ef233238 100644 --- a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx @@ -91,6 +91,9 @@ const ComponentDownloadControl = ({ try { await deleteComponentDownload(component.name); finish(false); + enqueueSnackbar(t("common:componentDownload.deleted"), { + variant: "success", + }); } catch { enqueueSnackbar(t("common:componentDownload.failed"), { variant: "error", diff --git a/DashAI/front/src/utils/i18n/locales/de/common.json b/DashAI/front/src/utils/i18n/locales/de/common.json index 2f777cdbb..07e9df0e7 100644 --- a/DashAI/front/src/utils/i18n/locales/de/common.json +++ b/DashAI/front/src/utils/i18n/locales/de/common.json @@ -165,6 +165,7 @@ "delete": "Download loeschen", "downloading": "Wird heruntergeladen...", "done": "Komponente heruntergeladen", + "deleted": "Download geloescht", "failed": "Download der Komponente fehlgeschlagen", "mustDownload": "Dieses Modell muss vor der Nutzung heruntergeladen werden" }, diff --git a/DashAI/front/src/utils/i18n/locales/en/common.json b/DashAI/front/src/utils/i18n/locales/en/common.json index 0220e806f..478d80af9 100644 --- a/DashAI/front/src/utils/i18n/locales/en/common.json +++ b/DashAI/front/src/utils/i18n/locales/en/common.json @@ -165,6 +165,7 @@ "delete": "Delete download", "downloading": "Downloading...", "done": "Component downloaded", + "deleted": "Download deleted", "failed": "Component download failed", "mustDownload": "This model must be downloaded before use" }, diff --git a/DashAI/front/src/utils/i18n/locales/es/common.json b/DashAI/front/src/utils/i18n/locales/es/common.json index dbe4bbf16..f22b31b60 100644 --- a/DashAI/front/src/utils/i18n/locales/es/common.json +++ b/DashAI/front/src/utils/i18n/locales/es/common.json @@ -165,6 +165,7 @@ "delete": "Eliminar descarga", "downloading": "Descargando...", "done": "Componente descargado", + "deleted": "Descarga eliminada", "failed": "La descarga del componente ha fallado", "mustDownload": "Este modelo debe descargarse antes de usarlo" }, diff --git a/DashAI/front/src/utils/i18n/locales/pt/common.json b/DashAI/front/src/utils/i18n/locales/pt/common.json index e52110881..583d45dec 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/common.json +++ b/DashAI/front/src/utils/i18n/locales/pt/common.json @@ -165,6 +165,7 @@ "delete": "Remover download", "downloading": "Baixando...", "done": "Componente baixado", + "deleted": "Download removido", "failed": "Falha ao baixar o componente", "mustDownload": "Este modelo precisa ser baixado antes de usar" }, diff --git a/DashAI/front/src/utils/i18n/locales/zh/common.json b/DashAI/front/src/utils/i18n/locales/zh/common.json index d0623fb42..02985d5fb 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/common.json +++ b/DashAI/front/src/utils/i18n/locales/zh/common.json @@ -165,6 +165,7 @@ "delete": "删除下载", "downloading": "下载中...", "done": "组件已下载", + "deleted": "下载已删除", "failed": "组件下载失败", "mustDownload": "使用前必须先下载此模型" }, From e89a65bfc326b1327ffb641a188694acf1d32450 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 09:55:28 -0400 Subject: [PATCH 045/308] refactor: remove session info button from GenerativeChat component --- .../front/src/components/generative/GenerativeChat.jsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/DashAI/front/src/components/generative/GenerativeChat.jsx b/DashAI/front/src/components/generative/GenerativeChat.jsx index 6609cb543..798c0bdb2 100644 --- a/DashAI/front/src/components/generative/GenerativeChat.jsx +++ b/DashAI/front/src/components/generative/GenerativeChat.jsx @@ -268,16 +268,6 @@ export default function GenerativeChat() { setParamsVersion((v) => v + 1); }} /> - setSessionInfoVisible(true)}> - - From b0388c9a3d3504bb9aad131a0d33c7ee1afb5112 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 10:30:53 -0400 Subject: [PATCH 046/308] feat: gate generative sessions on model download and log model switches --- ...1b0_add_model_name_to_parameter_history.py | 28 ++++ .../api_v1/endpoints/generative_session.py | 86 +++++++--- DashAI/back/dependencies/database/models.py | 4 + .../generative/CreateSessionContext.jsx | 23 ++- .../components/generative/GenerativeChat.jsx | 147 +++++++++++++++--- .../components/generative/ModelSwitcher.jsx | 12 +- .../src/utils/i18n/locales/de/generative.json | 2 + .../src/utils/i18n/locales/en/generative.json | 2 + .../src/utils/i18n/locales/es/generative.json | 2 + .../src/utils/i18n/locales/pt/generative.json | 2 + .../src/utils/i18n/locales/zh/generative.json | 2 + .../test_generative_session_download_gate.py | 49 +++++- 12 files changed, 301 insertions(+), 58 deletions(-) create mode 100644 DashAI/alembic/versions/a7d2c9e4f1b0_add_model_name_to_parameter_history.py diff --git a/DashAI/alembic/versions/a7d2c9e4f1b0_add_model_name_to_parameter_history.py b/DashAI/alembic/versions/a7d2c9e4f1b0_add_model_name_to_parameter_history.py new file mode 100644 index 000000000..ca9604eae --- /dev/null +++ b/DashAI/alembic/versions/a7d2c9e4f1b0_add_model_name_to_parameter_history.py @@ -0,0 +1,28 @@ +"""Add model_name to parameter_history + +Revision ID: a7d2c9e4f1b0 +Revises: f1a2b3c4d5e6 +Create Date: 2026-07-02 00:00:00.000000 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +revision: str = "a7d2c9e4f1b0" +down_revision: Union[str, None] = "f1a2b3c4d5e6" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + with op.batch_alter_table("parameter_history", schema=None) as batch_op: + batch_op.add_column(sa.Column("model_name", sa.String(), nullable=True)) + + +def downgrade() -> None: + with op.batch_alter_table("parameter_history", schema=None) as batch_op: + batch_op.drop_column("model_name") diff --git a/DashAI/back/api/api_v1/endpoints/generative_session.py b/DashAI/back/api/api_v1/endpoints/generative_session.py index 6c59fb3d6..d4ec8d305 100644 --- a/DashAI/back/api/api_v1/endpoints/generative_session.py +++ b/DashAI/back/api/api_v1/endpoints/generative_session.py @@ -115,6 +115,7 @@ async def upload_generative_session( session_params_entry = GenerativeSessionParameterHistory( session_id=session.id, parameters=session.parameters, + model_name=session.model_name, modified_at=datetime.now(), ) db.add(session_params_entry) @@ -357,6 +358,7 @@ async def update_generative_session( model is unknown, not a generative model, or not yet downloaded. """ from DashAI.back.models.base_generative_model import BaseGenerativeModel + with session_factory() as db: try: session = db.get(GenerativeSession, session_id) @@ -395,8 +397,10 @@ async def update_generative_session( if description is not None: setattr(session, "description", description) - # Validate and apply a model change if provided - if model_name is not None: + # Validate and apply a model change if provided. A model may be + # selected even when it is not downloaded yet; the chat blocks input + # and offers a download until the weights become available. + if model_name is not None and model_name != session.model_name: try: model_class = component_registry[model_name]["class"] except KeyError as e: @@ -409,15 +413,37 @@ async def update_generative_session( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Model {model_name} is not a valid generative model.", ) - entry = component_registry[model_name] - if getattr( - entry["class"], "REQUIRES_DOWNLOAD", False - ) and not entry.get("downloaded", False): - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail=f"Model {model_name} must be downloaded before use.", + + # Resolve the parameters for the new model: reuse the most + # recent parameters used for it in this session, else fall back + # to the model's schema defaults (its field placeholders). + last_used = ( + db.query(GenerativeSessionParameterHistory) + .filter( + GenerativeSessionParameterHistory.session_id == session_id, + GenerativeSessionParameterHistory.model_name == model_name, + ) + .order_by(GenerativeSessionParameterHistory.modified_at.desc()) + .first() + ) + if last_used is not None: + new_parameters = last_used.parameters + else: + properties = model_class.get_schema().get("properties", {}) + new_parameters = { + key: prop.get("placeholder") for key, prop in properties.items() + } + + session.model_name = model_name + session.parameters = new_parameters + db.add( + GenerativeSessionParameterHistory( + session_id=session.id, + parameters=new_parameters, + model_name=model_name, + modified_at=datetime.now(), ) - setattr(session, "model_name", model_name) + ) if name is not None or description is not None or model_name is not None: session.last_modified = datetime.now() @@ -489,6 +515,7 @@ async def update_generative_session_params( session_params_entry = GenerativeSessionParameterHistory( session_id=session.id, parameters=updated_parameters, + model_name=session.model_name, modified_at=datetime.now(), ) db.add(session_params_entry) @@ -617,26 +644,42 @@ async def get_parameter_history_entry( ) parameters_history = [p.__dict__ for p in parameters_history] + if not parameters_history: + return [] events = [] prev_params = parameters_history[0]["parameters"] + prev_model = parameters_history[0].get("model_name") for i in range(1, len(parameters_history)): curr = parameters_history[i] curr_params = curr["parameters"] + curr_model = curr.get("model_name") changes = [] - for key in curr_params: - old_val = prev_params.get(key) - new_val = curr_params[key] - if old_val != new_val: - changes.append( - { - "parameter": key, - "oldValue": old_val, - "newValue": new_val, - } - ) + # A model switch resets parameters to the new model's own + # values, so the raw parameter diff would be noise; report only + # the model change for that entry. + if curr_model and prev_model and curr_model != prev_model: + changes.append( + { + "parameter": "model", + "oldValue": prev_model, + "newValue": curr_model, + } + ) + else: + for key in curr_params: + old_val = prev_params.get(key) + new_val = curr_params[key] + if old_val != new_val: + changes.append( + { + "parameter": key, + "oldValue": old_val, + "newValue": new_val, + } + ) events.append( { @@ -646,6 +689,7 @@ async def get_parameter_history_entry( } ) prev_params = curr_params + prev_model = curr_model return events diff --git a/DashAI/back/dependencies/database/models.py b/DashAI/back/dependencies/database/models.py index cdf57d968..20b547eb6 100644 --- a/DashAI/back/dependencies/database/models.py +++ b/DashAI/back/dependencies/database/models.py @@ -658,6 +658,10 @@ class GenerativeSessionParameterHistory(Base): nullable=False, ) parameters: Mapped[JSON] = mapped_column(JSON, nullable=False) + # Model active when this snapshot was taken. Nullable so pre-migration rows + # remain valid; the parameters-history derivation only emits a model-change + # event when two consecutive snapshots both carry a model name that differs. + model_name: Mapped[str] = mapped_column(String, nullable=True) modified_at: Mapped[DateTime] = mapped_column( DateTime, default=datetime.now, diff --git a/DashAI/front/src/components/generative/CreateSessionContext.jsx b/DashAI/front/src/components/generative/CreateSessionContext.jsx index 34bdd62ce..5aa188a24 100644 --- a/DashAI/front/src/components/generative/CreateSessionContext.jsx +++ b/DashAI/front/src/components/generative/CreateSessionContext.jsx @@ -192,14 +192,35 @@ export function CreateSessionProvider({ children }) { [existingSessions], ); + // A model whose download was removed can no longer be used to create a + // session, so it must not stay selected. + const isUnavailable = (model) => + Boolean(model?.metadata?.requires_download) && !model?.downloaded; + // Sync selectedModel from URL param on load and after language-triggered // model refetch so display_name / description reflect the active language. + // If the URL points at a model that is no longer downloaded, drop back to + // the model selection step. useEffect(() => { if (!modelName || models.length === 0) return; const match = models.find((m) => m.name === modelName); - if (match) handleSelectModel(match); + if (!match) return; + if (isUnavailable(match)) { + setSelectedModel(null); + navigate("/app/generative/sessions/new"); + } else { + handleSelectModel(match); + } }, [modelName, models]); + // On the selection step, deselect a model if its download disappears (e.g. + // deleted from the inline control) after a models refetch. + useEffect(() => { + if (!selectedModel) return; + const match = models.find((m) => m.name === selectedModel.name); + if (match && isUnavailable(match)) setSelectedModel(null); + }, [models]); + const handleNext = () => { if (step === 0 && selectedModel) navigate(`/app/generative/sessions/new/${selectedModel.name}`); diff --git a/DashAI/front/src/components/generative/GenerativeChat.jsx b/DashAI/front/src/components/generative/GenerativeChat.jsx index 798c0bdb2..016a9a631 100644 --- a/DashAI/front/src/components/generative/GenerativeChat.jsx +++ b/DashAI/front/src/components/generative/GenerativeChat.jsx @@ -14,8 +14,14 @@ import { postProcess } from "../../api/process"; import { enqueueGenerativeProcessJob } from "../../api/job"; import { startJobQueue } from "../../api/job"; import { getHistoryBySessionId, getSessionById } from "../../api/session"; +import { + getComponentById, + getComponentDownloadStatus, +} from "../../api/component"; +import { getRelatedComponents } from "../../api/generativeTask"; import InfoSessionModal from "./InfoSessionModal"; import ModelSwitcher from "./ModelSwitcher"; +import ComponentDownloadControl from "../models/model/ComponentDownloadControl"; import { useSnackbar } from "notistack"; import { MediaInput } from "./MediaInput"; import { Trans, useTranslation } from "react-i18next"; @@ -48,6 +54,8 @@ export default function GenerativeChat() { const [showScrollButton, setShowScrollButton] = useState(false); const [sessionInfo, setSessionInfo] = useState(null); const [sessionInfoVisible, setSessionInfoVisible] = useState(false); + const [modelComponent, setModelComponent] = useState(null); + const [modelsByName, setModelsByName] = useState({}); const { enqueueSnackbar } = useSnackbar(); const { t } = useTranslation(["generative"]); const tourContext = useTourContext(); @@ -80,6 +88,49 @@ export default function GenerativeChat() { }); }; + // Resolve the session model's metadata plus a reconciled download status, so + // the chat can block input and offer a download when the weights are missing + // (e.g. after switching to a not downloaded model or deleting its download). + const modelName = sessionInfo?.model_name; + const refreshModelStatus = () => { + if (!modelName) { + setModelComponent(null); + return; + } + Promise.all([ + getComponentById(modelName), + getComponentDownloadStatus(modelName), + ]) + .then(([component, status]) => { + setModelComponent({ ...component, downloaded: status.downloaded }); + }) + .catch(() => setModelComponent(null)); + }; + + useEffect(() => { + refreshModelStatus(); + }, [modelName, paramsVersion]); + + // Map component name -> display name for the task's models, used to render + // model change history events with friendly names instead of class names. + useEffect(() => { + const currentTaskName = sessionInfo?.task_name; + if (!currentTaskName) return; + getRelatedComponents(currentTaskName) + .then((components) => { + const map = {}; + (components || []).forEach((c) => { + map[c.name] = c.display_name || c.name; + }); + setModelsByName(map); + }) + .catch(() => setModelsByName({})); + }, [sessionInfo?.task_name]); + + const modelBlocked = + Boolean(modelComponent?.metadata?.requires_download) && + !modelComponent?.downloaded; + const getMessages = () => { getProcessesBySessionId(sessionId).then((response) => { setIsLoadingMessage(false); @@ -202,23 +253,37 @@ export default function GenerativeChat() { }); let historyObject = history.map((entry) => { + const isModelChange = entry.changes.some((c) => c.parameter === "model"); return { type: "history", timestamp: entry.timestamp, id: entry.id, - changedMessage: entry.changes.map((change) => ( - - {change.parameter}: {change.oldValue}{" "} - {change.newValue}{" "} - - )), + isModelChange, + changedMessage: entry.changes.map((change) => { + const isModel = change.parameter === "model"; + const label = isModel + ? t("generative:label.sessionModel") + : change.parameter; + const oldValue = isModel + ? modelsByName[change.oldValue] || change.oldValue + : change.oldValue; + const newValue = isModel + ? modelsByName[change.newValue] || change.newValue + : change.newValue; + return ( + + {label}: {oldValue} {" "} + {newValue}{" "} + + ); + }), }; }); @@ -313,8 +378,17 @@ export default function GenerativeChat() { > {message.type === "history" ? ( - - Parameters updated: {message.changedMessage} + + {message.isModelChange + ? "Model changed: " + : "Parameters updated: "} + {message.changedMessage} ) : ( @@ -364,15 +438,40 @@ export default function GenerativeChat() { )} - {/* Chat input */} - { - handleSendMessage(input); - }} - isLoading={isLoadingMessage} - inputsCardinality={inputsCardinality} - /> + {/* Chat input, or a download prompt when the model is not available */} + {modelBlocked ? ( + + + {t("generative:label.modelNotDownloaded")} + + refreshModelStatus()} + /> + + ) : ( + { + handleSendMessage(input); + }} + isLoading={isLoadingMessage} + inputsCardinality={inputsCardinality} + /> + )} {/* Session Info Modal */} {sessionInfo && ( diff --git a/DashAI/front/src/components/generative/ModelSwitcher.jsx b/DashAI/front/src/components/generative/ModelSwitcher.jsx index 0a25dbc9c..54308112c 100644 --- a/DashAI/front/src/components/generative/ModelSwitcher.jsx +++ b/DashAI/front/src/components/generative/ModelSwitcher.jsx @@ -74,18 +74,16 @@ export default function ModelSwitcher({ sx={{ minWidth: 200 }} > {options.map((model) => { - const needsDownload = + // A not-downloaded model is still selectable; the chat blocks input + // and offers the download once it becomes the session's model. + const notDownloaded = Boolean(model.metadata?.requires_download) && !model.downloaded && model.name !== currentModelName; return ( - + {model.display_name || model.name} - {needsDownload + {notDownloaded ? ` (${t("generative:label.downloadRequired")})` : ""} diff --git a/DashAI/front/src/utils/i18n/locales/de/generative.json b/DashAI/front/src/utils/i18n/locales/de/generative.json index 701767f7c..bf88e144f 100644 --- a/DashAI/front/src/utils/i18n/locales/de/generative.json +++ b/DashAI/front/src/utils/i18n/locales/de/generative.json @@ -39,6 +39,8 @@ "startBySelectingATask": "Beginnen Sie mit der Auswahl einer generativen Aufgabe.", "noSessionsFound": "Keine Sitzungen gefunden", "parameterChangeEvent": "Parameter aktualisiert: <1>", + "modelChangeEvent": "Modell geändert: <1>", + "modelNotDownloaded": "Das Modell dieser Sitzung ist noch nicht heruntergeladen. Laden Sie es herunter, um fortzufahren.", "parameterChangeHistory": "Parameteränderungsverlauf für die aktuelle Sitzung", "searchSessions": "Sitzungen suchen", "selectGenerativeTask": "Generative Aufgabe für neue Sitzung auswählen", diff --git a/DashAI/front/src/utils/i18n/locales/en/generative.json b/DashAI/front/src/utils/i18n/locales/en/generative.json index 9bd5ddb0c..3c2ade281 100644 --- a/DashAI/front/src/utils/i18n/locales/en/generative.json +++ b/DashAI/front/src/utils/i18n/locales/en/generative.json @@ -39,6 +39,8 @@ "startBySelectingATask": "Start by selecting a generative task.", "noSessionsFound": "No sessions found", "parameterChangeEvent": "Parameters updated: <1>", + "modelChangeEvent": "Model changed: <1>", + "modelNotDownloaded": "This session's model is not downloaded yet. Download it to continue.", "parameterChangeHistory": "Parameter change history for the current session", "searchSessions": "Search Sessions", "selectGenerativeTask": "Select a generative task to start a new session", diff --git a/DashAI/front/src/utils/i18n/locales/es/generative.json b/DashAI/front/src/utils/i18n/locales/es/generative.json index 08f4bdefe..fcf27f01a 100644 --- a/DashAI/front/src/utils/i18n/locales/es/generative.json +++ b/DashAI/front/src/utils/i18n/locales/es/generative.json @@ -39,6 +39,8 @@ "startBySelectingATask": "Comienza seleccionando una tarea generativa.", "noSessionsFound": "No se encontraron sesiones", "parameterChangeEvent": "Parámetros actualizados: <1>", + "modelChangeEvent": "Modelo cambiado: <1>", + "modelNotDownloaded": "El modelo de esta sesión aún no está descargado. Descárgalo para continuar.", "parameterChangeHistory": "Historial de cambios de parámetros para la sesión actual", "searchSessions": "Buscar Sesiones", "selectGenerativeTask": "Seleccione una tarea generativa para comenzar una nueva sesión", diff --git a/DashAI/front/src/utils/i18n/locales/pt/generative.json b/DashAI/front/src/utils/i18n/locales/pt/generative.json index 69f867d4b..06643287f 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/generative.json +++ b/DashAI/front/src/utils/i18n/locales/pt/generative.json @@ -39,6 +39,8 @@ "startBySelectingATask": "Comece selecionando uma tarefa generativa.", "noSessionsFound": "Nenhuma sessão encontrada", "parameterChangeEvent": "Parâmetros atualizados: <1>", + "modelChangeEvent": "Modelo alterado: <1>", + "modelNotDownloaded": "O modelo desta sessão ainda não foi baixado. Baixe-o para continuar.", "parameterChangeHistory": "Histórico de alterações de parâmetros para a sessão atual", "searchSessions": "Buscar Sessões", "selectGenerativeTask": "Selecione uma tarefa generativa para começar uma nova sessão", diff --git a/DashAI/front/src/utils/i18n/locales/zh/generative.json b/DashAI/front/src/utils/i18n/locales/zh/generative.json index 6f3b9870c..093dfda13 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/generative.json +++ b/DashAI/front/src/utils/i18n/locales/zh/generative.json @@ -39,6 +39,8 @@ "startBySelectingATask": "从选择生成式任务开始。", "noSessionsFound": "未找到会话", "parameterChangeEvent": "参数已更新:<1>", + "modelChangeEvent": "模型已更改:<1>", + "modelNotDownloaded": "该会话的模型尚未下载。请先下载后再继续。", "parameterChangeHistory": "当前会话的参数变更历史", "searchSessions": "搜索会话", "selectGenerativeTask": "选择生成式任务以开始新会话", diff --git a/tests/back/api/test_generative_session_download_gate.py b/tests/back/api/test_generative_session_download_gate.py index 57de0d995..6881df992 100644 --- a/tests/back/api/test_generative_session_download_gate.py +++ b/tests/back/api/test_generative_session_download_gate.py @@ -100,9 +100,14 @@ def _create_sd_session(client, name): ) -def test_change_session_model_to_undownloaded_returns_409(client): - """Switching a session to a not-downloaded model must return HTTP 409.""" - created = _create_sd_session(client, "gen-switch-409") +def test_change_session_model_to_undownloaded_succeeds(client): + """Switching to a not-downloaded model is allowed; the chat gates its use. + + A user may point a session at any registered generative model even if its + weights are not present yet; the download is offered from the chat instead + of being blocked at switch time. + """ + created = _create_sd_session(client, "gen-switch-undownloaded") assert created.status_code == 201 session_id = created.json()["id"] @@ -110,8 +115,8 @@ def test_change_session_model_to_undownloaded_returns_409(client): f"/api/v1/generative-session/{session_id}", params={"model_name": "Qwen25_15BInstruct"}, ) - assert resp.status_code == 409 - assert "download" in resp.json()["detail"].lower() + assert resp.status_code == 200 + assert resp.json()["model_name"] == "Qwen25_15BInstruct" client.delete(f"/api/v1/generative-session/{session_id}") @@ -146,3 +151,37 @@ def test_change_session_model_valid_returns_200(client): assert resp.json()["model_name"] == "StableDiffusionV2Model" client.delete(f"/api/v1/generative-session/{session_id}") + + +def test_switch_model_resets_params_and_records_history(client): + """A switch resets params to the new model's defaults and logs the change.""" + created = _create_sd_session(client, "gen-switch-history") + assert created.status_code == 201 + session_id = created.json()["id"] + + resp = client.patch( + f"/api/v1/generative-session/{session_id}", + params={"model_name": "Qwen25_15BInstruct"}, + ) + assert resp.status_code == 200 + params = resp.json()["parameters"] + # Parameters were reset to the target model's own fields, not carried over + # from the Stable Diffusion session. + assert "num_inference_steps" not in params + assert "max_tokens" in params + + history = client.get(f"/api/v1/generative-session/parameters-history/{session_id}") + assert history.status_code == 200 + model_changes = [ + change + for event in history.json() + for change in event["changes"] + if change["parameter"] == "model" + ] + assert { + "parameter": "model", + "oldValue": "StableDiffusionV2Model", + "newValue": "Qwen25_15BInstruct", + } in model_changes + + client.delete(f"/api/v1/generative-session/{session_id}") From a54c780e1c1db1f5d2c78127c907cceb90f32178 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:07:57 -0400 Subject: [PATCH 047/308] feat: make text classification transformers downloadable --- .../models/hugging_face/albert_transformer.py | 1 + .../base_text_classification_transformer.py | 58 +++++++++++++++++-- .../models/hugging_face/bert_transformer.py | 1 + .../models/hugging_face/bertin_transformer.py | 1 + .../models/hugging_face/beto_transformer.py | 1 + .../hugging_face/deberta_v3_transformer.py | 1 + .../hugging_face/distilbert_transformer.py | 1 + .../hugging_face/electra_transformer.py | 1 + .../models/hugging_face/minilm_transformer.py | 1 + .../hugging_face/modernbert_transformer.py | 1 + .../multilingual_bert_transformer.py | 1 + .../hugging_face/roberta_transformer.py | 1 + .../hugging_face/xlm_roberta_transformer.py | 1 + .../models/hugging_face/xlnet_transformer.py | 1 + ...st_base_text_classification_transformer.py | 5 +- .../test_text_classification_downloadable.py | 45 ++++++++++++++ 16 files changed, 116 insertions(+), 5 deletions(-) create mode 100644 tests/back/models/test_text_classification_downloadable.py diff --git a/DashAI/back/models/hugging_face/albert_transformer.py b/DashAI/back/models/hugging_face/albert_transformer.py index a01622ab0..104deb58a 100644 --- a/DashAI/back/models/hugging_face/albert_transformer.py +++ b/DashAI/back/models/hugging_face/albert_transformer.py @@ -59,4 +59,5 @@ class AlbertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Speed" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "albert-base-v2" + DOWNLOAD_SIZE_BYTES: int = 47_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_albert" diff --git a/DashAI/back/models/hugging_face/base_text_classification_transformer.py b/DashAI/back/models/hugging_face/base_text_classification_transformer.py index 741207eae..dc839c699 100644 --- a/DashAI/back/models/hugging_face/base_text_classification_transformer.py +++ b/DashAI/back/models/hugging_face/base_text_classification_transformer.py @@ -7,10 +7,11 @@ """ from pathlib import Path -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Optional, Union from sklearn.exceptions import NotFittedError +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin from DashAI.back.models.text_classification_model import TextClassificationModel from DashAI.back.models.utils import ( GPU_OR_CPU_PLACEHOLDER, @@ -22,7 +23,9 @@ from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset -class HuggingFaceTextClassificationTransformer(TextClassificationModel): +class HuggingFaceTextClassificationTransformer( + HFDownloadableMixin, TextClassificationModel +): """Base implementation for Hugging Face text classification wrappers. Subclasses are expected to define at least ``MODEL_NAME`` and optionally @@ -44,8 +47,51 @@ class HuggingFaceTextClassificationTransformer(TextClassificationModel): "DashAI/back/user_models/temp_checkpoints_hf_text_classification" ) MAX_TOKEN_LENGTH: int = 512 + # Approximate on-disk size of the pretrained checkpoint; subclasses override + # with a value closer to their specific model for the download UI. + DOWNLOAD_SIZE_BYTES: int = 450_000_000 - def __init__(self, model=None, **kwargs): + @classmethod + def hf_repos(cls): + """Derive the single HuggingFace repo from the subclass ``MODEL_NAME``. + + Returns + ------- + list of tuple of (str, str) + A single ``(repo_id, repo_type)`` pair derived from ``MODEL_NAME``, + or an empty list when ``MODEL_NAME`` is not set. + """ + return [(cls.MODEL_NAME, "model")] if cls.MODEL_NAME else [] + + def _pretrained_source(self, pretrained_dir: Optional[str]) -> str: + """Resolve where to load the tokenizer and weights from. + + Prefers an explicit ``pretrained_dir`` (a saved run), then the local + component download folder when the weights are present, and finally + falls back to the Hugging Face Hub repo id. Downloading is enforced by + the run/session gates before real use; the Hub fallback keeps direct + instantiation working when nothing has been downloaded. + + Parameters + ---------- + pretrained_dir : str or None + Directory of a previously saved run, if any. + + Returns + ------- + str + A path or repo id accepted by ``from_pretrained``. + """ + if pretrained_dir: + return pretrained_dir + try: + if self.is_downloaded(): + return str(self._repo_dir(self.MODEL_NAME)) + except Exception: + pass + return self.MODEL_NAME + + def __init__(self, model=None, pretrained_dir: Optional[str] = None, **kwargs): """Initialize the transformer model. The process includes the instantiation of the pretrained model and the @@ -77,7 +123,7 @@ def __init__(self, model=None, **kwargs): f"{self.__class__.__name__} must define a non-empty MODEL_NAME." ) - self.model_name = self.MODEL_NAME + self.model_name = self._pretrained_source(pretrained_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) self.log_train_every_n_epochs = kwargs.get("log_train_every_n_epochs", 1) @@ -375,6 +421,9 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + # Persist the tokenizer alongside the weights so a saved run is + # self-contained and does not depend on the component download folder. + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args_params.get("num_train_epochs"), @@ -419,6 +468,7 @@ def load( loaded_model = cls( model=model, + pretrained_dir=str(filename), num_labels=custom_params.get("num_labels"), num_train_epochs=custom_params.get("num_train_epochs", 2), batch_size=custom_params.get("batch_size", 16), diff --git a/DashAI/back/models/hugging_face/bert_transformer.py b/DashAI/back/models/hugging_face/bert_transformer.py index 7fddc261f..d22451107 100644 --- a/DashAI/back/models/hugging_face/bert_transformer.py +++ b/DashAI/back/models/hugging_face/bert_transformer.py @@ -58,4 +58,5 @@ class BertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bert-base-uncased" + DOWNLOAD_SIZE_BYTES: int = 440_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_bert" diff --git a/DashAI/back/models/hugging_face/bertin_transformer.py b/DashAI/back/models/hugging_face/bertin_transformer.py index 693004535..109a7b649 100644 --- a/DashAI/back/models/hugging_face/bertin_transformer.py +++ b/DashAI/back/models/hugging_face/bertin_transformer.py @@ -58,4 +58,5 @@ class BertinTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "RecordVoiceOver" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bertin-project/bertin-roberta-base-spanish" + DOWNLOAD_SIZE_BYTES: int = 500_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_bertin" diff --git a/DashAI/back/models/hugging_face/beto_transformer.py b/DashAI/back/models/hugging_face/beto_transformer.py index cddfb45a8..db8440718 100644 --- a/DashAI/back/models/hugging_face/beto_transformer.py +++ b/DashAI/back/models/hugging_face/beto_transformer.py @@ -58,4 +58,5 @@ class BetoTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "RecordVoiceOver" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "dccuchile/bert-base-spanish-wwm-cased" + DOWNLOAD_SIZE_BYTES: int = 440_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_beto" diff --git a/DashAI/back/models/hugging_face/deberta_v3_transformer.py b/DashAI/back/models/hugging_face/deberta_v3_transformer.py index da258deb9..271fa0f76 100644 --- a/DashAI/back/models/hugging_face/deberta_v3_transformer.py +++ b/DashAI/back/models/hugging_face/deberta_v3_transformer.py @@ -61,4 +61,5 @@ class DebertaV3Transformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DebertaV3TransformerSchema MODEL_NAME: str = "microsoft/deberta-v3-base" + DOWNLOAD_SIZE_BYTES: int = 440_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_deberta_v3" diff --git a/DashAI/back/models/hugging_face/distilbert_transformer.py b/DashAI/back/models/hugging_face/distilbert_transformer.py index f43df4752..334ce67ec 100644 --- a/DashAI/back/models/hugging_face/distilbert_transformer.py +++ b/DashAI/back/models/hugging_face/distilbert_transformer.py @@ -305,4 +305,5 @@ class DistilBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "distilbert-base-uncased" + DOWNLOAD_SIZE_BYTES: int = 270_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_distilbert" diff --git a/DashAI/back/models/hugging_face/electra_transformer.py b/DashAI/back/models/hugging_face/electra_transformer.py index 7371a6f2d..00e26919e 100644 --- a/DashAI/back/models/hugging_face/electra_transformer.py +++ b/DashAI/back/models/hugging_face/electra_transformer.py @@ -58,4 +58,5 @@ class ElectraTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "ElectricBolt" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "google/electra-small-discriminator" + DOWNLOAD_SIZE_BYTES: int = 54_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_electra" diff --git a/DashAI/back/models/hugging_face/minilm_transformer.py b/DashAI/back/models/hugging_face/minilm_transformer.py index 6d8e75a87..23a323b4d 100644 --- a/DashAI/back/models/hugging_face/minilm_transformer.py +++ b/DashAI/back/models/hugging_face/minilm_transformer.py @@ -58,4 +58,5 @@ class MiniLMTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Speed" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "microsoft/MiniLM-L12-H384-uncased" + DOWNLOAD_SIZE_BYTES: int = 130_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_minilm" diff --git a/DashAI/back/models/hugging_face/modernbert_transformer.py b/DashAI/back/models/hugging_face/modernbert_transformer.py index ae2be1fff..ad7e26033 100644 --- a/DashAI/back/models/hugging_face/modernbert_transformer.py +++ b/DashAI/back/models/hugging_face/modernbert_transformer.py @@ -55,5 +55,6 @@ class ModernBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = ModernBertTransformerSchema MODEL_NAME: str = "answerdotai/ModernBERT-base" + DOWNLOAD_SIZE_BYTES: int = 600_000_000 MAX_TOKEN_LENGTH: int = 8192 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_modernbert" diff --git a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py index bd1e44bfe..d012a2e8b 100644 --- a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py +++ b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py @@ -59,6 +59,7 @@ class MultilingualBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Translate" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bert-base-multilingual-cased" + DOWNLOAD_SIZE_BYTES: int = 680_000_000 TEMP_CHECKPOINT_DIR: str = ( "DashAI/back/user_models/temp_checkpoints_multilingual_bert" ) diff --git a/DashAI/back/models/hugging_face/roberta_transformer.py b/DashAI/back/models/hugging_face/roberta_transformer.py index 5f65ea004..8e8fd40f1 100644 --- a/DashAI/back/models/hugging_face/roberta_transformer.py +++ b/DashAI/back/models/hugging_face/roberta_transformer.py @@ -58,4 +58,5 @@ class RobertaTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "SmartToy" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "roberta-base" + DOWNLOAD_SIZE_BYTES: int = 500_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_roberta" diff --git a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py index 6e04652be..51722452e 100644 --- a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py +++ b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py @@ -62,4 +62,5 @@ class XlmRobertaTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Language" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "xlm-roberta-base" + DOWNLOAD_SIZE_BYTES: int = 1_100_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_xlm_roberta" diff --git a/DashAI/back/models/hugging_face/xlnet_transformer.py b/DashAI/back/models/hugging_face/xlnet_transformer.py index 892157c7e..0bc110548 100644 --- a/DashAI/back/models/hugging_face/xlnet_transformer.py +++ b/DashAI/back/models/hugging_face/xlnet_transformer.py @@ -58,4 +58,5 @@ class XlnetTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "AutoAwesome" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "xlnet-base-cased" + DOWNLOAD_SIZE_BYTES: int = 470_000_000 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_xlnet" diff --git a/tests/back/models/test_base_text_classification_transformer.py b/tests/back/models/test_base_text_classification_transformer.py index 223fdee92..b3d879ced 100644 --- a/tests/back/models/test_base_text_classification_transformer.py +++ b/tests/back/models/test_base_text_classification_transformer.py @@ -4,7 +4,10 @@ class DummyTokenizer: - pass + def save_pretrained(self, save_directory): + save_path = Path(save_directory) + save_path.mkdir(parents=True, exist_ok=True) + (save_path / "tokenizer.json").write_text("{}", encoding="utf-8") class DummyConfig: diff --git a/tests/back/models/test_text_classification_downloadable.py b/tests/back/models/test_text_classification_downloadable.py new file mode 100644 index 000000000..243a05762 --- /dev/null +++ b/tests/back/models/test_text_classification_downloadable.py @@ -0,0 +1,45 @@ +"""Tests that text classification transformers expose download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.hugging_face.distilbert_transformer import DistilBertTransformer + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +def test_text_classification_is_downloadable(): + assert issubclass(DistilBertTransformer, HFDownloadableMixin) + assert DistilBertTransformer.REQUIRES_DOWNLOAD is True + assert DistilBertTransformer.DOWNLOAD_SIZE_BYTES is not None + + +def test_hf_repos_derived_from_model_name(): + assert DistilBertTransformer.hf_repos() == [("distilbert-base-uncased", "model")] + + +def test_metadata_flags_download(): + meta = DistilBertTransformer.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == DistilBertTransformer.DOWNLOAD_SIZE_BYTES + + +def test_is_downloaded_uses_component_dir(component_root): + assert DistilBertTransformer.is_downloaded() is False + + repo_dir = component_root / "DistilBertTransformer" / "distilbert-base-uncased" + repo_dir.mkdir(parents=True) + (repo_dir / "config.json").write_text("{}") + assert DistilBertTransformer.is_downloaded() is True From 4f3a4a3cf719fa6d86268bc873b01a3ffd272220 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:13:16 -0400 Subject: [PATCH 048/308] feat: make translation transformers downloadable --- .../dependencies/downloads/downloadable.py | 50 ++++++++++++++++ .../models/hugging_face/m2m100_transformer.py | 13 ++++- .../models/hugging_face/nllb_transformer.py | 14 ++++- .../hugging_face/t5_small_transformer.py | 13 ++++- tests/back/models/test_nllb_transformer.py | 5 ++ .../models/test_translation_downloadable.py | 58 +++++++++++++++++++ 6 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 tests/back/models/test_translation_downloadable.py diff --git a/DashAI/back/dependencies/downloads/downloadable.py b/DashAI/back/dependencies/downloads/downloadable.py index 33bef1a25..5aa4fbe0b 100644 --- a/DashAI/back/dependencies/downloads/downloadable.py +++ b/DashAI/back/dependencies/downloads/downloadable.py @@ -183,3 +183,53 @@ def download(cls, report: Optional[ProgressReporter] = None) -> None: snapshot_download( repo_id=rid, repo_type=rtype, local_dir=str(target), **kwargs ) + + +class HFPretrainedDownloadMixin(HFDownloadableMixin): + """HuggingFace mixin for models built around a single ``MODEL_NAME`` repo. + + Covers the common ``from_pretrained`` case: the repo is derived from the + subclass ``MODEL_NAME`` and ``_pretrained_source`` returns where to load + from (a saved run, the local download folder, or the Hub as a fallback). + """ + + MODEL_NAME: str = "" + + @classmethod + def hf_repos(cls): + """Derive the single repo entry from ``MODEL_NAME``. + + Returns + ------- + list of tuple of (str, str) + ``[(MODEL_NAME, "model")]`` or an empty list when unset. + """ + return [(cls.MODEL_NAME, "model")] if cls.MODEL_NAME else [] + + def _pretrained_source(self, pretrained_dir: Optional[str] = None) -> str: + """Resolve where ``from_pretrained`` should load from. + + Prefers an explicit ``pretrained_dir`` (a saved run), then the local + component download folder when present, and finally falls back to the + Hub repo id. Downloading is enforced by the run/session gates before + real use; the Hub fallback keeps direct instantiation working when + nothing has been downloaded. + + Parameters + ---------- + pretrained_dir : str or None + Directory of a previously saved run, if any. + + Returns + ------- + str + A path or repo id accepted by ``from_pretrained``. + """ + if pretrained_dir: + return pretrained_dir + try: + if self.is_downloaded(): + return str(self._repo_dir(self.MODEL_NAME)) + except Exception: + pass + return self.MODEL_NAME diff --git a/DashAI/back/models/hugging_face/m2m100_transformer.py b/DashAI/back/models/hugging_face/m2m100_transformer.py index 264e0a49f..99c023d00 100644 --- a/DashAI/back/models/hugging_face/m2m100_transformer.py +++ b/DashAI/back/models/hugging_face/m2m100_transformer.py @@ -9,6 +9,9 @@ from DashAI.back.core.schema_fields import schema_field, string_field from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( OpusMtEnESTransformerSchema, ) @@ -97,7 +100,7 @@ class M2M100TransformerSchema(OpusMtEnESTransformerSchema): ) # type: ignore -class M2M100Transformer(TranslationModel): +class M2M100Transformer(HFPretrainedDownloadMixin, TranslationModel): """M2M100 multilingual seq2seq model for configurable language-pair translation. Fine-tunes the ``facebook/m2m100_418M`` checkpoint from Meta AI. The base @@ -154,13 +157,15 @@ class M2M100Transformer(TranslationModel): ) COLOR: str = "#6A1B9A" ICON: str = "Language" + MODEL_NAME: str = "facebook/m2m100_418M" + DOWNLOAD_SIZE_BYTES: int = 1_900_000_000 - def __init__(self, model=None, **kwargs): + def __init__(self, model=None, pretrained_dir=None, **kwargs): kwargs = self.validate_and_transform(kwargs) from transformers import AutoTokenizer - self.model_name = "facebook/m2m100_418M" + self.model_name = self._pretrained_source(pretrained_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) self.source_language = kwargs.get("source_language", "en") @@ -352,6 +357,7 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args.get("num_train_epochs"), @@ -376,6 +382,7 @@ def load(cls, filename: Union[str, "Path"]): loaded_model = cls( model=model, + pretrained_dir=str(filename), num_train_epochs=custom_params.get("num_train_epochs"), batch_size=custom_params.get("batch_size"), learning_rate=custom_params.get("learning_rate"), diff --git a/DashAI/back/models/hugging_face/nllb_transformer.py b/DashAI/back/models/hugging_face/nllb_transformer.py index ab7a5556c..6628ba9d6 100644 --- a/DashAI/back/models/hugging_face/nllb_transformer.py +++ b/DashAI/back/models/hugging_face/nllb_transformer.py @@ -9,6 +9,9 @@ from DashAI.back.core.schema_fields import schema_field, string_field from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( OpusMtEnESTransformerSchema, ) @@ -121,7 +124,7 @@ class NllbTransformerSchema(OpusMtEnESTransformerSchema): ) # type: ignore -class NllbTransformer(TranslationModel): +class NllbTransformer(HFPretrainedDownloadMixin, TranslationModel): """Pretrained transformer for configurable multilingual translation. This model fine-tunes the ``facebook/nllb-200-distilled-600M`` checkpoint from @@ -201,7 +204,10 @@ def _resolve_language_token_id(self, language_code: str, field_name: str) -> int raise ValueError(f"Unsupported {field_name} '{language_code}'.") - def __init__(self, model=None, **kwargs): + MODEL_NAME: str = "facebook/nllb-200-distilled-600M" + DOWNLOAD_SIZE_BYTES: int = 2_400_000_000 + + def __init__(self, model=None, pretrained_dir=None, **kwargs): """Initialize the NLLB tokenizer and model. Downloads the ``facebook/nllb-200-distilled-600M`` tokenizer and, @@ -231,7 +237,7 @@ def __init__(self, model=None, **kwargs): from transformers import AutoTokenizer - self.model_name = "facebook/nllb-200-distilled-600M" + self.model_name = self._pretrained_source(pretrained_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) self.source_language = kwargs.get("source_language", "spa_Latn") @@ -533,6 +539,7 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args.get("num_train_epochs"), @@ -573,6 +580,7 @@ def load(cls, filename: Union[str, "Path"]): loaded_model = cls( model=model, + pretrained_dir=str(filename), num_train_epochs=custom_params.get("num_train_epochs"), batch_size=custom_params.get("batch_size"), learning_rate=custom_params.get("learning_rate"), diff --git a/DashAI/back/models/hugging_face/t5_small_transformer.py b/DashAI/back/models/hugging_face/t5_small_transformer.py index 91373a064..366de45b5 100644 --- a/DashAI/back/models/hugging_face/t5_small_transformer.py +++ b/DashAI/back/models/hugging_face/t5_small_transformer.py @@ -9,6 +9,9 @@ from DashAI.back.core.schema_fields import enum_field, schema_field from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.hugging_face.opus_mt_en_es_transformer import ( OpusMtEnESTransformerSchema, ) @@ -73,7 +76,7 @@ class T5SmallTransformerSchema(OpusMtEnESTransformerSchema): ) # type: ignore -class T5SmallTransformer(TranslationModel): +class T5SmallTransformer(HFPretrainedDownloadMixin, TranslationModel): """T5-small seq2seq model for English-to-{German, French, Romanian} translation. Fine-tunes the ``t5-small`` checkpoint from Google. Translation direction is @@ -127,13 +130,15 @@ class T5SmallTransformer(TranslationModel): ) COLOR: str = "#00695C" ICON: str = "Language" + MODEL_NAME: str = "t5-small" + DOWNLOAD_SIZE_BYTES: int = 240_000_000 - def __init__(self, model=None, **kwargs): + def __init__(self, model=None, pretrained_dir=None, **kwargs): kwargs = self.validate_and_transform(kwargs) from transformers import AutoTokenizer - self.model_name = "t5-small" + self.model_name = self._pretrained_source(pretrained_dir) self.tokenizer = AutoTokenizer.from_pretrained(self.model_name) self.target_language = kwargs.get("target_language", "German") @@ -318,6 +323,7 @@ def save(self, filename: Union[str, "Path"]) -> None: save_dir.mkdir(parents=True, exist_ok=True) self.model.save_pretrained(save_dir) + self.tokenizer.save_pretrained(save_dir) config = AutoConfig.from_pretrained(save_dir) config.custom_params = { "num_train_epochs": self.training_args.get("num_train_epochs"), @@ -341,6 +347,7 @@ def load(cls, filename: Union[str, "Path"]): loaded_model = cls( model=model, + pretrained_dir=str(filename), num_train_epochs=custom_params.get("num_train_epochs"), batch_size=custom_params.get("batch_size"), learning_rate=custom_params.get("learning_rate"), diff --git a/tests/back/models/test_nllb_transformer.py b/tests/back/models/test_nllb_transformer.py index 772e0b8f3..70f79c32b 100644 --- a/tests/back/models/test_nllb_transformer.py +++ b/tests/back/models/test_nllb_transformer.py @@ -27,6 +27,11 @@ def decode(self, token_ids, skip_special_tokens=True): def convert_tokens_to_ids(self, token): return self.lang_code_to_id.get(token, 0) + def save_pretrained(self, save_directory): + save_path = Path(save_directory) + save_path.mkdir(parents=True, exist_ok=True) + (save_path / "tokenizer.json").write_text("{}", encoding="utf-8") + class DummyNllbTokenizerNoLangMap: def __init__(self): diff --git a/tests/back/models/test_translation_downloadable.py b/tests/back/models/test_translation_downloadable.py new file mode 100644 index 000000000..cba69d495 --- /dev/null +++ b/tests/back/models/test_translation_downloadable.py @@ -0,0 +1,58 @@ +"""Tests that translation transformers expose download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) +from DashAI.back.models.hugging_face.m2m100_transformer import M2M100Transformer +from DashAI.back.models.hugging_face.nllb_transformer import NllbTransformer +from DashAI.back.models.hugging_face.t5_small_transformer import T5SmallTransformer + +_CASES = [ + (M2M100Transformer, "facebook/m2m100_418M"), + (NllbTransformer, "facebook/nllb-200-distilled-600M"), + (T5SmallTransformer, "t5-small"), +] + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_translation_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_translation_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_translation_is_downloaded_uses_component_dir( + model_cls, repo_id, component_root +): + assert model_cls.is_downloaded() is False + + leaf = repo_id.split("/")[-1] + repo_dir = component_root / model_cls.__name__ / leaf + repo_dir.mkdir(parents=True) + (repo_dir / "config.json").write_text("{}") + assert model_cls.is_downloaded() is True From 9099cccdc26c117beff8c0c39901482e3b79ab24 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:22:28 -0400 Subject: [PATCH 049/308] feat: make torchvision image classifiers downloadable --- .../dependencies/downloads/downloadable.py | 86 +++++++++++++++++++ .../efficientnet_b0_image_classifier.py | 15 +++- .../back/models/resnet18_image_classifier.py | 13 ++- .../back/models/resnet50_image_classifier.py | 13 ++- .../test_image_classification_downloadable.py | 54 ++++++++++++ 5 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 tests/back/models/test_image_classification_downloadable.py diff --git a/DashAI/back/dependencies/downloads/downloadable.py b/DashAI/back/dependencies/downloads/downloadable.py index 5aa4fbe0b..56112910f 100644 --- a/DashAI/back/dependencies/downloads/downloadable.py +++ b/DashAI/back/dependencies/downloads/downloadable.py @@ -233,3 +233,89 @@ def _pretrained_source(self, pretrained_dir: Optional[str] = None) -> str: except Exception: pass return self.MODEL_NAME + + +class TorchvisionDownloadMixin(DownloadableMixin): + """Downloadable mixin for torchvision models with ImageNet-pretrained weights. + + torchvision fetches pretrained weights into a global ``torch.hub`` cache. + This mixin redirects that cache to ``component_dir()`` so the weights are + stored and gated like any other downloadable component. Subclasses build + their backbone inside :meth:`local_hub` so the pretrained weights are read + from (and written to) the component's own folder. + + .. note:: + The download provides the ImageNet weights used when the model is built + with ``pretrained=True`` (the default). Training a fresh model with + ``pretrained=False`` needs no weights but is still gated as a + download-required component. + """ + + @classmethod + def _weights(cls): + """Return the torchvision weights enum member to download. + + Returns + ------- + torchvision.models.WeightsEnum + The pretrained weights descriptor whose file is fetched. + """ + raise NotImplementedError + + @classmethod + def _checkpoints_dir(cls): + """Return the directory where torch.hub stores downloaded checkpoints.""" + return cls.component_dir() / "checkpoints" + + @classmethod + def local_hub(cls): + """Context manager that points ``torch.hub`` at ``component_dir()``. + + Returns + ------- + contextlib.AbstractContextManager + A context that temporarily sets the torch hub directory to this + component's folder and restores the previous value on exit. + """ + import contextlib + + import torch + + @contextlib.contextmanager + def _ctx(): + old = torch.hub.get_dir() + cls.component_dir().mkdir(parents=True, exist_ok=True) + torch.hub.set_dir(str(cls.component_dir())) + try: + yield + finally: + torch.hub.set_dir(old) + + return _ctx() + + @classmethod + def is_downloaded(cls) -> bool: + """Return whether the pretrained weights file is present locally. + + Returns + ------- + bool + ``True`` when the component's ``checkpoints`` folder exists and is + non-empty. + """ + ckpt = cls._checkpoints_dir() + return ckpt.is_dir() and any(ckpt.iterdir()) + + @classmethod + def download(cls, report: Optional[ProgressReporter] = None) -> None: + """Fetch the pretrained weights into ``component_dir()``. + + Parameters + ---------- + report : ProgressReporter, optional + Callback invoked with an indeterminate progress message. + """ + if report is not None: + report(None, f"Downloading {cls.__name__} weights") + with cls.local_hub(): + cls._weights().get_state_dict(progress=False) diff --git a/DashAI/back/models/efficientnet_b0_image_classifier.py b/DashAI/back/models/efficientnet_b0_image_classifier.py index a6be1bcb2..56517f400 100644 --- a/DashAI/back/models/efficientnet_b0_image_classifier.py +++ b/DashAI/back/models/efficientnet_b0_image_classifier.py @@ -1,13 +1,16 @@ """EfficientNet-B0 image classifier for DashAI.""" from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import TorchvisionDownloadMixin from DashAI.back.models.base_torchvision_image_classifier import ( TorchvisionImageClassifier, TorchvisionImageClassifierSchema, ) -class EfficientNetB0ImageClassifier(TorchvisionImageClassifier): +class EfficientNetB0ImageClassifier( + TorchvisionDownloadMixin, TorchvisionImageClassifier +): """EfficientNet-B0 image classifier (Tan & Le, 2019). Compact baseline of the EfficientNet family, which scales network width, @@ -53,13 +56,21 @@ class EfficientNetB0ImageClassifier(TorchvisionImageClassifier): ) COLOR: str = "#00838F" ICON: str = "Speed" + DOWNLOAD_SIZE_BYTES: int = 21_000_000 + + @classmethod + def _weights(cls): + from torchvision.models import EfficientNet_B0_Weights + + return EfficientNet_B0_Weights.DEFAULT def _build_backbone(self, num_classes: int, pretrained: bool): import torch.nn as nn from torchvision.models import EfficientNet_B0_Weights, efficientnet_b0 weights = EfficientNet_B0_Weights.DEFAULT if pretrained else None - model = efficientnet_b0(weights=weights) + with self.local_hub(): + model = efficientnet_b0(weights=weights) in_features = model.classifier[1].in_features model.classifier = nn.Sequential( nn.Dropout(self.dropout_rate), diff --git a/DashAI/back/models/resnet18_image_classifier.py b/DashAI/back/models/resnet18_image_classifier.py index aed89a626..bcc8fee43 100644 --- a/DashAI/back/models/resnet18_image_classifier.py +++ b/DashAI/back/models/resnet18_image_classifier.py @@ -1,13 +1,14 @@ """ResNet-18 image classifier for DashAI.""" from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import TorchvisionDownloadMixin from DashAI.back.models.base_torchvision_image_classifier import ( TorchvisionImageClassifier, TorchvisionImageClassifierSchema, ) -class ResNet18ImageClassifier(TorchvisionImageClassifier): +class ResNet18ImageClassifier(TorchvisionDownloadMixin, TorchvisionImageClassifier): """ResNet-18 image classifier (He et al., 2015). 18-layer residual network with skip connections that solve the vanishing @@ -52,13 +53,21 @@ class ResNet18ImageClassifier(TorchvisionImageClassifier): ) COLOR: str = "#2E7D32" ICON: str = "AccountTree" + DOWNLOAD_SIZE_BYTES: int = 47_000_000 + + @classmethod + def _weights(cls): + from torchvision.models import ResNet18_Weights + + return ResNet18_Weights.DEFAULT def _build_backbone(self, num_classes: int, pretrained: bool): import torch.nn as nn from torchvision.models import ResNet18_Weights, resnet18 weights = ResNet18_Weights.DEFAULT if pretrained else None - model = resnet18(weights=weights) + with self.local_hub(): + model = resnet18(weights=weights) in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(self.dropout_rate), diff --git a/DashAI/back/models/resnet50_image_classifier.py b/DashAI/back/models/resnet50_image_classifier.py index 92f07a662..c556a3960 100644 --- a/DashAI/back/models/resnet50_image_classifier.py +++ b/DashAI/back/models/resnet50_image_classifier.py @@ -1,13 +1,14 @@ """ResNet-50 image classifier for DashAI.""" from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import TorchvisionDownloadMixin from DashAI.back.models.base_torchvision_image_classifier import ( TorchvisionImageClassifier, TorchvisionImageClassifierSchema, ) -class ResNet50ImageClassifier(TorchvisionImageClassifier): +class ResNet50ImageClassifier(TorchvisionDownloadMixin, TorchvisionImageClassifier): """ResNet-50 image classifier (He et al., 2015). 50-layer residual network using bottleneck blocks. Deeper and more @@ -54,13 +55,21 @@ class ResNet50ImageClassifier(TorchvisionImageClassifier): ) COLOR: str = "#1B5E20" ICON: str = "AccountTree" + DOWNLOAD_SIZE_BYTES: int = 100_000_000 + + @classmethod + def _weights(cls): + from torchvision.models import ResNet50_Weights + + return ResNet50_Weights.DEFAULT def _build_backbone(self, num_classes: int, pretrained: bool): import torch.nn as nn from torchvision.models import ResNet50_Weights, resnet50 weights = ResNet50_Weights.DEFAULT if pretrained else None - model = resnet50(weights=weights) + with self.local_hub(): + model = resnet50(weights=weights) in_features = model.fc.in_features model.fc = nn.Sequential( nn.Dropout(self.dropout_rate), diff --git a/tests/back/models/test_image_classification_downloadable.py b/tests/back/models/test_image_classification_downloadable.py new file mode 100644 index 000000000..df315d2c1 --- /dev/null +++ b/tests/back/models/test_image_classification_downloadable.py @@ -0,0 +1,54 @@ +"""Tests that torchvision image classifiers expose download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import TorchvisionDownloadMixin +from DashAI.back.models.efficientnet_b0_image_classifier import ( + EfficientNetB0ImageClassifier, +) +from DashAI.back.models.resnet18_image_classifier import ResNet18ImageClassifier +from DashAI.back.models.resnet50_image_classifier import ResNet50ImageClassifier + +_CASES = [ + ResNet18ImageClassifier, + ResNet50ImageClassifier, + EfficientNetB0ImageClassifier, +] + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize("model_cls", _CASES) +def test_image_classifier_is_downloadable(model_cls): + assert issubclass(model_cls, TorchvisionDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + + +@pytest.mark.parametrize("model_cls", _CASES) +def test_image_classifier_metadata_flags_download(model_cls): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES + + +@pytest.mark.parametrize("model_cls", _CASES) +def test_is_downloaded_uses_checkpoints_dir(model_cls, component_root): + assert model_cls.is_downloaded() is False + + ckpt = component_root / model_cls.__name__ / "checkpoints" + ckpt.mkdir(parents=True) + (ckpt / "weights.pth").write_bytes(b"w") + assert model_cls.is_downloaded() is True From 3886d58f2ae582894f38f206fe309bbb1dcbfac1 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:33:33 -0400 Subject: [PATCH 050/308] fix: reconcile model download gate against the filesystem --- .../back/api/api_v1/endpoints/generative_session.py | 11 ++++++----- DashAI/back/api/api_v1/endpoints/runs.py | 11 ++++++----- .../back/api/test_generative_session_download_gate.py | 5 +++++ tests/back/api/test_run_download_gate.py | 5 +++++ 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/DashAI/back/api/api_v1/endpoints/generative_session.py b/DashAI/back/api/api_v1/endpoints/generative_session.py index d4ec8d305..503b8c72d 100644 --- a/DashAI/back/api/api_v1/endpoints/generative_session.py +++ b/DashAI/back/api/api_v1/endpoints/generative_session.py @@ -47,11 +47,12 @@ async def upload_generative_session( detail=f"Model {params.model_name} is not registered.", ) from e - # Guard: model requires download but has not been downloaded -> 409 - entry = component_registry[params.model_name] - if getattr(entry["class"], "REQUIRES_DOWNLOAD", False) and not entry.get( - "downloaded", False - ): + # Guard: model requires download but has not been downloaded -> 409. + # Reconcile against the filesystem so a model downloaded after startup + # (in the worker process) is recognised without an API restart. + if getattr( + model_class, "REQUIRES_DOWNLOAD", False + ) and not component_registry.refresh_download_status(params.model_name): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=( diff --git a/DashAI/back/api/api_v1/endpoints/runs.py b/DashAI/back/api/api_v1/endpoints/runs.py index 0c4dd2119..cdc61958e 100644 --- a/DashAI/back/api/api_v1/endpoints/runs.py +++ b/DashAI/back/api/api_v1/endpoints/runs.py @@ -325,17 +325,18 @@ async def upload_run( status_code=status.HTTP_404_NOT_FOUND, detail="Model session not found", ) - # REQUIRES_DOWNLOAD is read from the class (static contract); - # "downloaded" is read from the registry dict (runtime state, Task 4). + # REQUIRES_DOWNLOAD is the static contract; the download state is + # reconciled against the filesystem so a model downloaded after + # startup (in the worker process) is recognised without a restart. if params.model_name not in component_registry: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=f"Unknown model '{params.model_name}'", ) entry = component_registry[params.model_name] - if getattr(entry["class"], "REQUIRES_DOWNLOAD", False) and not entry.get( - "downloaded", False - ): + if getattr( + entry["class"], "REQUIRES_DOWNLOAD", False + ) and not component_registry.refresh_download_status(params.model_name): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=( diff --git a/tests/back/api/test_generative_session_download_gate.py b/tests/back/api/test_generative_session_download_gate.py index 6881df992..0ceaf1a03 100644 --- a/tests/back/api/test_generative_session_download_gate.py +++ b/tests/back/api/test_generative_session_download_gate.py @@ -36,6 +36,11 @@ def __getitem__(self, name): def get_components_by_types(self, select=None, ignore=None): return self._real.get_components_by_types(select=select, ignore=ignore) + def refresh_download_status(self, name): + if name == "FakeDownloadableGenerativeModel": + return _FakeDownloadableGenerativeModel.is_downloaded() + return self._real.refresh_download_status(name) + def __contains__(self, name): return name == "FakeDownloadableGenerativeModel" or name in self._real diff --git a/tests/back/api/test_run_download_gate.py b/tests/back/api/test_run_download_gate.py index e8e89ab9b..52af92061 100644 --- a/tests/back/api/test_run_download_gate.py +++ b/tests/back/api/test_run_download_gate.py @@ -40,6 +40,11 @@ def __getitem__(self, name): def get_components_by_types(self, select=None, ignore=None): return self._real.get_components_by_types(select=select, ignore=ignore) + def refresh_download_status(self, name): + if name == "FakeDownloadableModel": + return _FakeDownloadableModel.is_downloaded() + return self._real.refresh_download_status(name) + def __contains__(self, name): return name == "FakeDownloadableModel" or name in self._real From 592385e897099562df23d186d82acc82810515ab Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:38:30 -0400 Subject: [PATCH 051/308] feat: split Stable Diffusion 2 into downloadable per-checkpoint models --- DashAI/back/initial_components.py | 10 +- .../hugging_face/stable_diffusion_v2_model.py | 169 +++++++++++------- tests/back/api/conftest.py | 17 ++ .../test_generative_session_download_gate.py | 8 +- tests/back/api/test_process_api.py | 2 +- tests/back/api/test_session_api.py | 10 +- .../test_stable_diffusion2_downloadable.py | 58 ++++++ 7 files changed, 201 insertions(+), 73 deletions(-) create mode 100644 tests/back/models/test_stable_diffusion2_downloadable.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 056769619..689d007c9 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -210,7 +210,10 @@ StableDiffusionXLV1ControlNet, ) from DashAI.back.models.hugging_face.stable_diffusion_v2_model import ( - StableDiffusionV2Model, + StableDiffusion2, + StableDiffusion2_512, + StableDiffusion21, + StableDiffusion21_512, ) from DashAI.back.models.hugging_face.stable_diffusion_v3_model import ( StableDiffusionV3Model, @@ -398,7 +401,10 @@ def get_initial_components(): SGDClassifier, SmolLM2_360MInstruct, SmolLM2_17BInstruct, - StableDiffusionV2Model, + StableDiffusion2, + StableDiffusion2_512, + StableDiffusion21, + StableDiffusion21_512, StableDiffusionV3Model, StableDiffusionXLModel, StableDiffusionXLV1ControlNet, diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py b/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py index 8354499f2..7d9230f46 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,65 +29,6 @@ class StableDiffusionSchema(BaseSchema): ``StableDiffusionV2Model``. """ - model_name: schema_field( - enum_field( - enum=[ - "sd2-community/stable-diffusion-2", - "sd2-community/stable-diffusion-2-base", - "sd2-community/stable-diffusion-2-1", - "sd2-community/stable-diffusion-2-1-base", - ] - ), - placeholder="sd2-community/stable-diffusion-2", - description=MultilingualString( - en=( - "The specific Stable Diffusion 2.x checkpoint to load. " - "The '-base' variants are trained at 512x512 px and are faster; " - "the nonbase variants target 768x768 px and produce sharper detail. " - "The '2-1' variants are fine-tuned further " - "and generally outperform '2'." - ), - es=( - "El checkpoint específico de Stable Diffusion 2.x a cargar. " - "Las variantes '-base' se entrenan a 512x512 px y son más rápidas; " - "las variantes sin '-base' apuntan a 768x768 px " - "y producen mayor detalle. " - "Las variantes '2-1' están más ajustadas " - "y generalmente superan a '2'." - ), - pt=( - "O checkpoint específico do Stable Diffusion 2.x a carregar. " - "As variantes '-base' são treinadas a 512x512 px e são mais rápidas; " - "as variantes sem '-base' visam 768x768 px " - "e produzem maior detalhe. " - "As variantes '2-1' são mais ajustadas " - "e geralmente superam a '2'." - ), - de=( - "Der zu ladende spezifische Stable Diffusion 2.x-Checkpoint. " - "Die '-base'-Varianten werden bei 512x512 px trainiert und sind " - "schneller; " - "die Nicht-base-Varianten zielen auf 768x768 px ab " - "und liefern schärfere Details. " - "Die '2-1'-Varianten sind weiter feinabgestimmt " - "und übertreffen '2' in der Regel." - ), - zh=( - "要加载的 Stable Diffusion 2.x 检查点。" - "'-base' 变体在 512x512 像素下训练,速度更快;" - "非 base 变体目标分辨率为 768x768 像素,细节更清晰。" - "'2-1' 变体经过进一步微调,通常优于 '2'。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - negative_prompt: Optional[ schema_field( string_field(), @@ -403,7 +347,9 @@ class StableDiffusionSchema(BaseSchema): ) # type: ignore -class StableDiffusionV2Model(TextToImageGenerationTaskModel): +class StableDiffusion2GenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Latent diffusion model for high resolution text-to-image generation. Wraps the Stable Diffusion 2.x family of checkpoints released by @@ -431,6 +377,7 @@ class StableDiffusionV2Model(TextToImageGenerationTaskModel): """ SCHEMA = StableDiffusionSchema + MODEL_NAME: str = "" COLOR: str = "#1565c0" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion V2", @@ -538,7 +485,7 @@ def __init__(self, **kwargs): self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = kwargs.get("model_name", "sd2-community/stable-diffusion-2") + self.model_name = self._pretrained_source(None) self.model = DiffusionPipeline.from_pretrained( self.model_name, @@ -589,3 +536,103 @@ def generate(self, input: str) -> List[Any]: output = self.model(**params) return output.images + + +class StableDiffusion2(StableDiffusion2GenerationModel): + """768px Stable Diffusion 2 checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "sd2-community/stable-diffusion-2" + # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. + DOWNLOAD_SIZE_BYTES: int = 5_200_000_000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 2", + es="Stable Diffusion 2", + pt="Stable Diffusion 2", + de="Stable Diffusion 2", + zh="Stable Diffusion 2", + ) + DESCRIPTION = MultilingualString( + en="768px Stable Diffusion 2 checkpoint.", + es="768px Stable Diffusion 2 checkpoint.", + pt="768px Stable Diffusion 2 checkpoint.", + de="768px Stable Diffusion 2 checkpoint.", + zh="768px Stable Diffusion 2 checkpoint.", + ) + + +class StableDiffusion2_512(StableDiffusion2GenerationModel): # noqa: N801 + """512px base Stable Diffusion 2 checkpoint (faster). + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "sd2-community/stable-diffusion-2-base" + # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. + DOWNLOAD_SIZE_BYTES: int = 5_200_000_000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 2 (512px)", + es="Stable Diffusion 2 (512px)", + pt="Stable Diffusion 2 (512px)", + de="Stable Diffusion 2 (512px)", + zh="Stable Diffusion 2 (512px)", + ) + DESCRIPTION = MultilingualString( + en="512px base Stable Diffusion 2 checkpoint (faster).", + es="512px base Stable Diffusion 2 checkpoint (faster).", + pt="512px base Stable Diffusion 2 checkpoint (faster).", + de="512px base Stable Diffusion 2 checkpoint (faster).", + zh="512px base Stable Diffusion 2 checkpoint (faster).", + ) + + +class StableDiffusion21(StableDiffusion2GenerationModel): + """768px Stable Diffusion 2.1 checkpoint (further fine-tuned). + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "sd2-community/stable-diffusion-2-1" + # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. + DOWNLOAD_SIZE_BYTES: int = 5_200_000_000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 2.1", + es="Stable Diffusion 2.1", + pt="Stable Diffusion 2.1", + de="Stable Diffusion 2.1", + zh="Stable Diffusion 2.1", + ) + DESCRIPTION = MultilingualString( + en="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", + es="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", + pt="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", + de="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", + zh="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", + ) + + +class StableDiffusion21_512(StableDiffusion2GenerationModel): # noqa: N801 + """512px base Stable Diffusion 2.1 checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "sd2-community/stable-diffusion-2-1-base" + # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. + DOWNLOAD_SIZE_BYTES: int = 5_200_000_000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 2.1 (512px)", + es="Stable Diffusion 2.1 (512px)", + pt="Stable Diffusion 2.1 (512px)", + de="Stable Diffusion 2.1 (512px)", + zh="Stable Diffusion 2.1 (512px)", + ) + DESCRIPTION = MultilingualString( + en="512px base Stable Diffusion 2.1 checkpoint.", + es="512px base Stable Diffusion 2.1 checkpoint.", + pt="512px base Stable Diffusion 2.1 checkpoint.", + de="512px base Stable Diffusion 2.1 checkpoint.", + zh="512px base Stable Diffusion 2.1 checkpoint.", + ) diff --git a/tests/back/api/conftest.py b/tests/back/api/conftest.py index f9dc1ed9b..074e299f1 100644 --- a/tests/back/api/conftest.py +++ b/tests/back/api/conftest.py @@ -46,6 +46,23 @@ def client(test_path: Path): remove_dir_with_retry(app.container._services["config"]["LOCAL_PATH"]) +@pytest.fixture(scope="module", autouse=True) +def _mark_stable_diffusion2_downloaded(client): + """Make ``StableDiffusion2`` appear downloaded for session/run tests. + + All generative models now require a download, so the download gate would + reject session/run creation. Creating the component's repo folder lets the + filesystem-reconciled gate treat it as available without fetching weights. + """ + config = client.app.container._services["config"] + repo_dir = ( + Path(config["COMPONENT_PATH"]) / "StableDiffusion2" / "stable-diffusion-2" + ) + repo_dir.mkdir(parents=True, exist_ok=True) + (repo_dir / "config.json").write_text("{}", encoding="utf-8") + return + + @pytest.fixture(name="dataset_1", scope="module") def create_dataset_1(client) -> Dataset: """Create testing dataset 1 using job system.""" diff --git a/tests/back/api/test_generative_session_download_gate.py b/tests/back/api/test_generative_session_download_gate.py index 0ceaf1a03..c454ca785 100644 --- a/tests/back/api/test_generative_session_download_gate.py +++ b/tests/back/api/test_generative_session_download_gate.py @@ -96,7 +96,7 @@ def _create_sd_session(client, name): return client.post( "/api/v1/generative-session/", json={ - "model_name": "StableDiffusionV2Model", + "model_name": "StableDiffusion2", "task_name": "TextToImageGenerationTask", "parameters": _SD_PARAMS, "name": name, @@ -150,10 +150,10 @@ def test_change_session_model_valid_returns_200(client): resp = client.patch( f"/api/v1/generative-session/{session_id}", - params={"model_name": "StableDiffusionV2Model"}, + params={"model_name": "StableDiffusion2"}, ) assert resp.status_code == 200 - assert resp.json()["model_name"] == "StableDiffusionV2Model" + assert resp.json()["model_name"] == "StableDiffusion2" client.delete(f"/api/v1/generative-session/{session_id}") @@ -185,7 +185,7 @@ def test_switch_model_resets_params_and_records_history(client): ] assert { "parameter": "model", - "oldValue": "StableDiffusionV2Model", + "oldValue": "StableDiffusion2", "newValue": "Qwen25_15BInstruct", } in model_changes diff --git a/tests/back/api/test_process_api.py b/tests/back/api/test_process_api.py index 7a65cbea5..35b1fcfd7 100644 --- a/tests/back/api/test_process_api.py +++ b/tests/back/api/test_process_api.py @@ -6,7 +6,7 @@ def session(client: TestClient): """Create a valid session for process tests.""" params = { - "model_name": "StableDiffusionV2Model", + "model_name": "StableDiffusion2", "task_name": "TextToImageGenerationTask", "parameters": { "num_inference_steps": 1, diff --git a/tests/back/api/test_session_api.py b/tests/back/api/test_session_api.py index ece8d95cc..bb8e2b411 100644 --- a/tests/back/api/test_session_api.py +++ b/tests/back/api/test_session_api.py @@ -6,7 +6,7 @@ def create_session_1(client: TestClient): """Create testing session 1 using job system.""" params = { - "model_name": "StableDiffusionV2Model", + "model_name": "StableDiffusion2", "task_name": "TextToImageGenerationTask", "parameters": { "num_inference_steps": 1, @@ -56,7 +56,7 @@ def create_session_2(client: TestClient): def create_session_3(client: TestClient): """Create testing session 3 using a non-download-required model.""" params = { - "model_name": "StableDiffusionV2Model", + "model_name": "StableDiffusion2", "task_name": "TextToImageGenerationTask", "parameters": { "num_inference_steps": 1, @@ -85,7 +85,7 @@ def create_session_3(client: TestClient): def create_session_4(client: TestClient): """Create testing session 4 with an invalid task (valid model).""" params = { - "model_name": "StableDiffusionV2Model", + "model_name": "StableDiffusion2", "task_name": "SomeTask", "parameters": { "num_inference_steps": 1, @@ -116,7 +116,7 @@ def test_create_session(response_1): data = response_1.json() assert data["id"] is not None, "Session ID is missing" assert data["name"] == "session_1", "Session name does not match" - assert data["model_name"] == "StableDiffusionV2Model", "Model name does not match" + assert data["model_name"] == "StableDiffusion2", "Model name does not match" assert data["task_name"] == "TextToImageGenerationTask", "Task name does not match" @@ -135,7 +135,7 @@ def test_get_session_by_id(client: TestClient, response_1): data = response.json() assert data["id"] == session_id, "Retrieved session ID does not match" assert data["name"] == "session_1", "Session name does not match" - assert data["model_name"] == "StableDiffusionV2Model", "Model name does not match" + assert data["model_name"] == "StableDiffusion2", "Model name does not match" assert data["task_name"] == "TextToImageGenerationTask", "Task name does not match" diff --git a/tests/back/models/test_stable_diffusion2_downloadable.py b/tests/back/models/test_stable_diffusion2_downloadable.py new file mode 100644 index 000000000..b86b179c6 --- /dev/null +++ b/tests/back/models/test_stable_diffusion2_downloadable.py @@ -0,0 +1,58 @@ +"""Tests that Stable Diffusion 2 per-checkpoint models expose download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin +from DashAI.back.models.hugging_face.stable_diffusion_v2_model import ( + StableDiffusion2, + StableDiffusion2_512, + StableDiffusion21, + StableDiffusion21_512, +) + +_CASES = [ + (StableDiffusion2, "sd2-community/stable-diffusion-2"), + (StableDiffusion2_512, "sd2-community/stable-diffusion-2-base"), + (StableDiffusion21, "sd2-community/stable-diffusion-2-1"), + (StableDiffusion21_512, "sd2-community/stable-diffusion-2-1-base"), +] + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd2_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd2_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd2_is_downloaded_uses_component_dir(model_cls, repo_id, component_root): + assert model_cls.is_downloaded() is False + + leaf = repo_id.split("/")[-1] + repo_dir = component_root / model_cls.__name__ / leaf + repo_dir.mkdir(parents=True) + (repo_dir / "config.json").write_text("{}") + assert model_cls.is_downloaded() is True From 1ac62679b465e5cd7e370dcf189b1c48bf85730f Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:41:40 -0400 Subject: [PATCH 052/308] feat: split SDXL and make SDXL-Turbo downloadable per checkpoint --- DashAI/back/initial_components.py | 6 +- .../models/hugging_face/sdxl_turbo_model.py | 10 +- .../hugging_face/stable_diffusion_xl_model.py | 116 +++++++++--------- tests/back/models/test_sdxl_downloadable.py | 31 +++++ 4 files changed, 101 insertions(+), 62 deletions(-) create mode 100644 tests/back/models/test_sdxl_downloadable.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 689d007c9..ff31ea330 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -219,7 +219,8 @@ StableDiffusionV3Model, ) from DashAI.back.models.hugging_face.stable_diffusion_xl_model import ( - StableDiffusionXLModel, + RealVisXLV4, + StableDiffusionXL, ) from DashAI.back.models.hugging_face.t5_small_transformer import T5SmallTransformer from DashAI.back.models.hugging_face.tongyi_z_image_model import TongyiZImageModel @@ -406,7 +407,8 @@ def get_initial_components(): StableDiffusion21, StableDiffusion21_512, StableDiffusionV3Model, - StableDiffusionXLModel, + StableDiffusionXL, + RealVisXLV4, StableDiffusionXLV1ControlNet, SVC, SVR, diff --git a/DashAI/back/models/hugging_face/sdxl_turbo_model.py b/DashAI/back/models/hugging_face/sdxl_turbo_model.py index 918d90aed..6d7a16f52 100644 --- a/DashAI/back/models/hugging_face/sdxl_turbo_model.py +++ b/DashAI/back/models/hugging_face/sdxl_turbo_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -299,7 +302,7 @@ class SDXLTurboSchema(BaseSchema): ) # type: ignore -class SDXLTurboModel(TextToImageGenerationTaskModel): +class SDXLTurboModel(HFPretrainedDownloadMixin, TextToImageGenerationTaskModel): """Distilled SDXL model for near real time text-to-image generation. Wraps ``stabilityai/sdxl-turbo``, a version of Stable Diffusion XL @@ -323,6 +326,9 @@ class SDXLTurboModel(TextToImageGenerationTaskModel): """ SCHEMA = SDXLTurboSchema + MODEL_NAME: str = "stabilityai/sdxl-turbo" + # SDXL-Turbo diffusers pipeline is ~7 GB. + DOWNLOAD_SIZE_BYTES: int = 7_000_000_000 COLOR: str = "#b71c1c" DISPLAY_NAME: str = MultilingualString( en="SDXL Turbo", @@ -423,7 +429,7 @@ def __init__(self, **kwargs): ) self.model = StableDiffusionXLPipeline.from_pretrained( - "stabilityai/sdxl-turbo", + self._pretrained_source(None), torch_dtype=torch.float16 if use_gpu else torch.float32, variant="fp16" if use_gpu else None, ).to(self.device) diff --git a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py index 40aec1b3b..6a7944911 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,60 +29,6 @@ class StableDiffusionXLSchema(BaseSchema): ``StableDiffusionXLModel``. """ - model_name: schema_field( - enum_field( - enum=[ - "stabilityai/stable-diffusion-xl-base-1.0", - "SG161222/RealVisXL_V4.0", - ] - ), - placeholder="stabilityai/stable-diffusion-xl-base-1.0", - description=MultilingualString( - en=( - "The Stable Diffusion XL checkpoint to load. " - "'stable-diffusion-xl-base-1.0' is the official base model trained " - "at 1024x1024 px for high-quality photorealistic generation. " - "'RealVisXL_V4.0' is a popular community fine-tune of SDXL " - "optimized for realistic portraits and photography." - ), - es=( - "El checkpoint Stable Diffusion XL a cargar. " - "'stable-diffusion-xl-base-1.0' es el modelo base oficial entrenado " - "a 1024x1024 px para generación fotorrealista de alta calidad. " - "'RealVisXL_V4.0' es un popular fine-tune comunitario de SDXL " - "optimizado para retratos realistas y fotografía." - ), - pt=( - "O checkpoint Stable Diffusion XL a carregar. " - "'stable-diffusion-xl-base-1.0' é o modelo base oficial treinado " - "a 1024x1024 px para geração fotorrealista de alta qualidade. " - "'RealVisXL_V4.0' é um popular fine-tune comunitário do SDXL " - "otimizado para retratos realistas e fotografia." - ), - de=( - "Der zu ladende Stable Diffusion XL-Checkpoint. " - "'stable-diffusion-xl-base-1.0' ist das offizielle Basismodell, " - "bei 1024x1024 px für hochwertige fotorealistische Generierung " - "trainiert. " - "'RealVisXL_V4.0' ist ein beliebter Community-Fine-Tune von SDXL, " - "optimiert für realistische Porträts und Fotografie." - ), - zh=( - "要加载的 Stable Diffusion XL 检查点。" - "'stable-diffusion-xl-base-1.0' 是官方基础模型," - "在 1024x1024px 下训练,用于高质量写实图像生成。" - "'RealVisXL_V4.0' 是针对写实人像和摄影优化的热门社区微调版本。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - negative_prompt: Optional[ schema_field( string_field(), @@ -393,7 +342,9 @@ class StableDiffusionXLSchema(BaseSchema): ) # type: ignore -class StableDiffusionXLModel(TextToImageGenerationTaskModel): +class StableDiffusionXLGenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Latent diffusion model for high-resolution 1024 px text-to-image generation. Wraps Stable Diffusion XL (SDXL) checkpoints. SDXL scales the standard @@ -415,6 +366,7 @@ class StableDiffusionXLModel(TextToImageGenerationTaskModel): """ SCHEMA = StableDiffusionXLSchema + MODEL_NAME: str = "" COLOR: str = "#0d47a1" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion XL", @@ -483,9 +435,7 @@ def __init__(self, **kwargs): self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = kwargs.get( - "model_name", "stabilityai/stable-diffusion-xl-base-1.0" - ) + self.model_name = self._pretrained_source(None) self.model = StableDiffusionXLPipeline.from_pretrained( self.model_name, @@ -534,3 +484,53 @@ def generate(self, input: str) -> List[Any]: output = self.model(**params) return output.images + + +class StableDiffusionXL(StableDiffusionXLGenerationModel): + """Stable Diffusion XL base 1.0 checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-xl-base-1.0" + # SDXL diffusers pipeline (base + refiner-less) is ~7 GB. + DOWNLOAD_SIZE_BYTES: int = 7_000_000_000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion XL", + es="Stable Diffusion XL", + pt="Stable Diffusion XL", + de="Stable Diffusion XL", + zh="Stable Diffusion XL", + ) + DESCRIPTION = MultilingualString( + en="Stable Diffusion XL base 1.0 checkpoint.", + es="Stable Diffusion XL base 1.0 checkpoint.", + pt="Stable Diffusion XL base 1.0 checkpoint.", + de="Stable Diffusion XL base 1.0 checkpoint.", + zh="Stable Diffusion XL base 1.0 checkpoint.", + ) + + +class RealVisXLV4(StableDiffusionXLGenerationModel): + """RealVisXL V4.0 photorealistic SDXL checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "SG161222/RealVisXL_V4.0" + # SDXL diffusers pipeline (base + refiner-less) is ~7 GB. + DOWNLOAD_SIZE_BYTES: int = 7_000_000_000 + DISPLAY_NAME = MultilingualString( + en="RealVisXL V4.0", + es="RealVisXL V4.0", + pt="RealVisXL V4.0", + de="RealVisXL V4.0", + zh="RealVisXL V4.0", + ) + DESCRIPTION = MultilingualString( + en="RealVisXL V4.0 photorealistic SDXL checkpoint.", + es="RealVisXL V4.0 photorealistic SDXL checkpoint.", + pt="RealVisXL V4.0 photorealistic SDXL checkpoint.", + de="RealVisXL V4.0 photorealistic SDXL checkpoint.", + zh="RealVisXL V4.0 photorealistic SDXL checkpoint.", + ) diff --git a/tests/back/models/test_sdxl_downloadable.py b/tests/back/models/test_sdxl_downloadable.py new file mode 100644 index 000000000..3239fd924 --- /dev/null +++ b/tests/back/models/test_sdxl_downloadable.py @@ -0,0 +1,31 @@ +"""Tests that SDXL / SDXL-Turbo models expose download metadata.""" + +import pytest + +from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin +from DashAI.back.models.hugging_face.sdxl_turbo_model import SDXLTurboModel +from DashAI.back.models.hugging_face.stable_diffusion_xl_model import ( + RealVisXLV4, + StableDiffusionXL, +) + +_CASES = [ + (StableDiffusionXL, "stabilityai/stable-diffusion-xl-base-1.0"), + (RealVisXLV4, "SG161222/RealVisXL_V4.0"), + (SDXLTurboModel, "stabilityai/sdxl-turbo"), +] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sdxl_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sdxl_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES From 4f64bda10b7f7a6aaac55d6c33cc6a4732c45e06 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:43:41 -0400 Subject: [PATCH 053/308] feat: split PixArt-Sigma and Tongyi Z-Image into downloadable checkpoints --- DashAI/back/initial_components.py | 16 ++- .../models/hugging_face/pixart_sigma_model.py | 118 +++++++++--------- .../hugging_face/tongyi_z_image_model.py | 113 +++++++++-------- .../models/test_pixart_tongyi_downloadable.py | 35 ++++++ 4 files changed, 159 insertions(+), 123 deletions(-) create mode 100644 tests/back/models/test_pixart_tongyi_downloadable.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index ff31ea330..19a319fef 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -183,7 +183,10 @@ from DashAI.back.models.hugging_face.opus_mt_fr_en_transformer import ( OpusMtFrEnTransformer, ) -from DashAI.back.models.hugging_face.pixart_sigma_model import PixArtSigmaModel +from DashAI.back.models.hugging_face.pixart_sigma_model import ( + PixArtSigma512, + PixArtSigma1024, +) from DashAI.back.models.hugging_face.qwen_model import ( Qwen25_05BInstruct, Qwen25_15BInstruct, @@ -223,7 +226,10 @@ StableDiffusionXL, ) from DashAI.back.models.hugging_face.t5_small_transformer import T5SmallTransformer -from DashAI.back.models.hugging_face.tongyi_z_image_model import TongyiZImageModel +from DashAI.back.models.hugging_face.tongyi_z_image_model import ( + TongyiZImage, + TongyiZImageTurbo, +) from DashAI.back.models.hugging_face.xlm_roberta_transformer import ( XlmRobertaTransformer, ) @@ -387,7 +393,8 @@ def get_initial_components(): OpusMtEnPtTransformer, OpusMtEsENTransformer, OpusMtFrEnTransformer, - PixArtSigmaModel, + PixArtSigma1024, + PixArtSigma512, Qwen25_05BInstruct, Qwen25_15BInstruct, RandomForestClassifier, @@ -414,7 +421,8 @@ def get_initial_components(): SVR, T5SmallTransformer, TfIdfLogRegTextClassificationModel, - TongyiZImageModel, + TongyiZImage, + TongyiZImageTurbo, XlmRobertaTransformer, XlnetTransformer, MLPImageClassifier, diff --git a/DashAI/back/models/hugging_face/pixart_sigma_model.py b/DashAI/back/models/hugging_face/pixart_sigma_model.py index e4ca5b6dd..b03ecbb96 100644 --- a/DashAI/back/models/hugging_face/pixart_sigma_model.py +++ b/DashAI/back/models/hugging_face/pixart_sigma_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,64 +29,6 @@ class PixArtSigmaSchema(BaseSchema): ``PixArtSigmaModel``. """ - model_name: schema_field( - enum_field( - enum=[ - "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", - "PixArt-alpha/PixArt-Sigma-XL-2-512-MS", - ] - ), - placeholder="PixArt-alpha/PixArt-Sigma-XL-2-1024-MS", - description=MultilingualString( - en=( - "The PixArt-Sigma checkpoint to load. " - "'PixArt-Sigma-XL-2-1024-MS' is the high resolution variant " - "trained at 1024px with multiscale support, delivering the best " - "image quality. " - "'PixArt-Sigma-XL-2-512-MS' is the 512px variant, faster and lighter " - "while still producing sharp results." - ), - es=( - "El checkpoint PixArt-Sigma a cargar. " - "'PixArt-Sigma-XL-2-1024-MS' es la variante de alta resolución " - "entrenada a 1024px con soporte multiescala, entregando la mejor " - "calidad de imagen. " - "'PixArt-Sigma-XL-2-512-MS' es la variante de 512px, más rápida y " - "ligera manteniendo resultados nítidos." - ), - pt=( - "O checkpoint PixArt-Sigma a carregar. " - "'PixArt-Sigma-XL-2-1024-MS' é a variante de alta resolução " - "treinada a 1024px com suporte multiescala, entregando a melhor " - "qualidade de imagem. " - "'PixArt-Sigma-XL-2-512-MS' é a variante de 512px, mais rápida e " - "leve, mantendo resultados nítidos." - ), - de=( - "Der zu ladende PixArt-Sigma-Checkpoint. " - "'PixArt-Sigma-XL-2-1024-MS' ist die hochauflösende Variante, " - "bei 1024px mit Multi-Skalen-Unterstützung trainiert und liefert " - "die beste Bildqualität. " - "'PixArt-Sigma-XL-2-512-MS' ist die 512px-Variante, schneller und " - "leichter bei dennoch scharfen Ergebnissen." - ), - zh=( - "要加载的 PixArt-Sigma 检查点。" - "'PixArt-Sigma-XL-2-1024-MS' 是以 1024px 训练的高分辨率变体," - "支持多尺度,图像质量最佳。" - "'PixArt-Sigma-XL-2-512-MS' 是 512px 变体,速度更快、更轻量," - "同样能产生清晰效果。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - negative_prompt: Optional[ schema_field( string_field(), @@ -376,7 +321,9 @@ class PixArtSigmaSchema(BaseSchema): ) # type: ignore -class PixArtSigmaModel(TextToImageGenerationTaskModel): +class PixArtSigmaGenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Diffusion Transformer model for high efficiency text-to-image generation. Wraps the PixArt-Sigma pipeline, which replaces the U-Net backbone used @@ -398,6 +345,7 @@ class PixArtSigmaModel(TextToImageGenerationTaskModel): """ SCHEMA = PixArtSigmaSchema + MODEL_NAME: str = "" COLOR: str = "#6a1b9a" DISPLAY_NAME: str = MultilingualString( en="PixArt-Sigma", @@ -510,9 +458,7 @@ def __init__(self, **kwargs): self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = kwargs.get( - "model_name", "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - ) + self._pretrained_source(None) self.model = PixArtSigmaPipeline.from_pretrained( self.model_name, @@ -557,3 +503,51 @@ def generate(self, input: str) -> List[Any]: num_images_per_prompt=self.num_images_per_prompt, ) return output.images + + +class PixArtSigma1024(PixArtSigmaGenerationModel): + """PixArt-Sigma XL 1024px checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" + DOWNLOAD_SIZE_BYTES: int = 2500000000 + DISPLAY_NAME = MultilingualString( + en="PixArt-Sigma 1024", + es="PixArt-Sigma 1024", + pt="PixArt-Sigma 1024", + de="PixArt-Sigma 1024", + zh="PixArt-Sigma 1024", + ) + DESCRIPTION = MultilingualString( + en="PixArt-Sigma XL 1024px checkpoint.", + es="PixArt-Sigma XL 1024px checkpoint.", + pt="PixArt-Sigma XL 1024px checkpoint.", + de="PixArt-Sigma XL 1024px checkpoint.", + zh="PixArt-Sigma XL 1024px checkpoint.", + ) + + +class PixArtSigma512(PixArtSigmaGenerationModel): + """PixArt-Sigma XL 512px checkpoint (faster). + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-512-MS" + DOWNLOAD_SIZE_BYTES: int = 2500000000 + DISPLAY_NAME = MultilingualString( + en="PixArt-Sigma 512", + es="PixArt-Sigma 512", + pt="PixArt-Sigma 512", + de="PixArt-Sigma 512", + zh="PixArt-Sigma 512", + ) + DESCRIPTION = MultilingualString( + en="PixArt-Sigma XL 512px checkpoint (faster).", + es="PixArt-Sigma XL 512px checkpoint (faster).", + pt="PixArt-Sigma XL 512px checkpoint (faster).", + de="PixArt-Sigma XL 512px checkpoint (faster).", + zh="PixArt-Sigma XL 512px checkpoint (faster).", + ) diff --git a/DashAI/back/models/hugging_face/tongyi_z_image_model.py b/DashAI/back/models/hugging_face/tongyi_z_image_model.py index e1af35586..94bb0bebc 100644 --- a/DashAI/back/models/hugging_face/tongyi_z_image_model.py +++ b/DashAI/back/models/hugging_face/tongyi_z_image_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,61 +29,6 @@ class TongyiZImageSchema(BaseSchema): ``TongyiZImageModel``. """ - model_name: schema_field( - enum_field(enum=["Tongyi-MAI/Z-Image", "Tongyi-MAI/Z-Image-Turbo"]), - placeholder="Tongyi-MAI/Z-Image", - description=MultilingualString( - en=( - "The Tongyi Z-Image checkpoint to load. " - "'Tongyi-Z-Image' is Alibaba's 6B parameter text-to-image model " - "using a unique S3-DiT (Sparse Spatial-Spectral Diffusion Transformer) " - "architecture, one of the most downloaded models on " - "Hugging Face. It outperforms previous open source state of the art " - "models at a fraction of their parameter count." - ), - es=( - "El checkpoint Tongyi Z-Image a cargar. " - "'Tongyi-Z-Image' es el modelo de texto a imagen de 6B parámetros de " - "Alibaba que usa una arquitectura S3-DiT única (Sparse " - "Spatial-Spectral Diffusion Transformer), uno de los " - "más descargados en " - "Hugging Face. Supera a modelos de última generación anteriores con " - "una fracción de su cantidad de parámetros." - ), - pt=( - "O checkpoint Tongyi Z-Image a carregar. " - "'Tongyi-Z-Image' é o modelo de texto para imagem de 6B parâmetros " - "da Alibaba que usa uma arquitetura S3-DiT única (Sparse " - "Spatial-Spectral Diffusion Transformer), um dos " - "mais baixados no " - "Hugging Face. Supera modelos anteriores de última geração com " - "uma fração de sua quantidade de parâmetros." - ), - de=( - "Der zu ladende Tongyi Z-Image-Checkpoint. " - "'Tongyi-Z-Image' ist Alibabas 6B-Parameter-Text-zu-Bild-Modell " - "mit einer einzigartigen S3-DiT-Architektur (Sparse Spatial-Spectral " - "Diffusion Transformer), eines der am häufigsten heruntergeladenen " - "Modelle auf Hugging Face. Es übertrifft frühere Open-Source-Modelle " - "auf dem neuesten Stand bei einem Bruchteil deren Parameteranzahl." - ), - zh=( - "要加载的 Tongyi Z-Image 检查点。" - "'Tongyi-Z-Image' 是阿里巴巴的 60 亿参数文本到图像模型," - "采用独特的 S3-DiT 架构(稀疏空间-频谱扩散变换器)," - "是 Hugging Face 上下载量最高的模型之一。" - "以更少的参数量超越了此前的开源最先进模型。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - negative_prompt: Optional[ schema_field( string_field(), @@ -360,7 +308,9 @@ class TongyiZImageSchema(BaseSchema): ) # type: ignore -class TongyiZImageModel(TextToImageGenerationTaskModel): +class TongyiZImageGenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Tongyi Z-Image S3-DiT model for high quality text-to-image generation. Wraps Alibaba's 6B parameter Tongyi Z-Image pipeline. The model uses a @@ -376,6 +326,7 @@ class TongyiZImageModel(TextToImageGenerationTaskModel): """ SCHEMA = TongyiZImageSchema + MODEL_NAME: str = "" COLOR: str = "#e65100" DISPLAY_NAME: str = MultilingualString( en="Tongyi Z-Image", @@ -443,7 +394,7 @@ def __init__(self, **kwargs): ) self.model = DiffusionPipeline.from_pretrained( - kwargs.get("model_name"), + self._pretrained_source(None), torch_dtype=torch.float16 if use_gpu else torch.float32, ).to(self.device) @@ -485,3 +436,51 @@ def generate(self, input: str) -> List[Any]: num_images_per_prompt=self.num_images_per_prompt, ) return output.images + + +class TongyiZImage(TongyiZImageGenerationModel): + """Tongyi Z-Image text-to-image checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "Tongyi-MAI/Z-Image" + DOWNLOAD_SIZE_BYTES: int = 8000000000 + DISPLAY_NAME = MultilingualString( + en="Tongyi Z-Image", + es="Tongyi Z-Image", + pt="Tongyi Z-Image", + de="Tongyi Z-Image", + zh="Tongyi Z-Image", + ) + DESCRIPTION = MultilingualString( + en="Tongyi Z-Image text-to-image checkpoint.", + es="Tongyi Z-Image text-to-image checkpoint.", + pt="Tongyi Z-Image text-to-image checkpoint.", + de="Tongyi Z-Image text-to-image checkpoint.", + zh="Tongyi Z-Image text-to-image checkpoint.", + ) + + +class TongyiZImageTurbo(TongyiZImageGenerationModel): + """Tongyi Z-Image Turbo fast checkpoint. + + Downloads its checkpoint into the component's own download folder. + """ + + MODEL_NAME: str = "Tongyi-MAI/Z-Image-Turbo" + DOWNLOAD_SIZE_BYTES: int = 8000000000 + DISPLAY_NAME = MultilingualString( + en="Tongyi Z-Image Turbo", + es="Tongyi Z-Image Turbo", + pt="Tongyi Z-Image Turbo", + de="Tongyi Z-Image Turbo", + zh="Tongyi Z-Image Turbo", + ) + DESCRIPTION = MultilingualString( + en="Tongyi Z-Image Turbo fast checkpoint.", + es="Tongyi Z-Image Turbo fast checkpoint.", + pt="Tongyi Z-Image Turbo fast checkpoint.", + de="Tongyi Z-Image Turbo fast checkpoint.", + zh="Tongyi Z-Image Turbo fast checkpoint.", + ) diff --git a/tests/back/models/test_pixart_tongyi_downloadable.py b/tests/back/models/test_pixart_tongyi_downloadable.py new file mode 100644 index 000000000..1a646268a --- /dev/null +++ b/tests/back/models/test_pixart_tongyi_downloadable.py @@ -0,0 +1,35 @@ +"""Tests that PixArt-Sigma / Tongyi Z-Image models expose download metadata.""" + +import pytest + +from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin +from DashAI.back.models.hugging_face.pixart_sigma_model import ( + PixArtSigma512, + PixArtSigma1024, +) +from DashAI.back.models.hugging_face.tongyi_z_image_model import ( + TongyiZImage, + TongyiZImageTurbo, +) + +_CASES = [ + (PixArtSigma1024, "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS"), + (PixArtSigma512, "PixArt-alpha/PixArt-Sigma-XL-2-512-MS"), + (TongyiZImage, "Tongyi-MAI/Z-Image"), + (TongyiZImageTurbo, "Tongyi-MAI/Z-Image-Turbo"), +] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES From 0f72825391c21e0cfff7c963c4e61d473e680294 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:45:53 -0400 Subject: [PATCH 054/308] feat: split Stable Diffusion 3 into downloadable per-checkpoint models --- DashAI/back/initial_components.py | 10 +- .../hugging_face/stable_diffusion_v3_model.py | 177 +++++++++++------- tests/back/models/test_sd3_downloadable.py | 33 ++++ 3 files changed, 153 insertions(+), 67 deletions(-) create mode 100644 tests/back/models/test_sd3_downloadable.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 19a319fef..55be38ecb 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -219,7 +219,10 @@ StableDiffusion21_512, ) from DashAI.back.models.hugging_face.stable_diffusion_v3_model import ( - StableDiffusionV3Model, + StableDiffusion3Medium, + StableDiffusion35Large, + StableDiffusion35LargeTurbo, + StableDiffusion35Medium, ) from DashAI.back.models.hugging_face.stable_diffusion_xl_model import ( RealVisXLV4, @@ -413,7 +416,10 @@ def get_initial_components(): StableDiffusion2_512, StableDiffusion21, StableDiffusion21_512, - StableDiffusionV3Model, + StableDiffusion3Medium, + StableDiffusion35Medium, + StableDiffusion35Large, + StableDiffusion35LargeTurbo, StableDiffusionXL, RealVisXLV4, StableDiffusionXLV1ControlNet, diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py b/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py index 34685fd51..979cc79b9 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py @@ -9,6 +9,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFPretrainedDownloadMixin, +) from DashAI.back.models.text_to_image_generation_model import ( TextToImageGenerationTaskModel, ) @@ -26,67 +29,6 @@ class StableDiffusionSchema(BaseSchema): (``num_images_per_prompt``) for ``StableDiffusionV3Model``. """ - model_name: schema_field( - enum_field( - enum=[ - "stabilityai/stable-diffusion-3-medium-diffusers", - "stabilityai/stable-diffusion-3.5-medium", - "stabilityai/stable-diffusion-3.5-large", - "stabilityai/stable-diffusion-3.5-large-turbo", - ] - ), - placeholder="stabilityai/stable-diffusion-3-medium-diffusers", - description=MultilingualString( - en=( - "The SD3/SD3.5 checkpoint to load. 'sd-3-medium' is the baseline " - "2B-parameter model. 'sd-3.5-medium' improves quality at similar " - "speed. 'sd-3.5-large' (8B) delivers the highest quality but needs " - "more VRAM. 'sd-3.5-large-turbo' is a distilled large model that " - "requires far fewer steps (4-8) for fast high-quality generation. " - "All variants target 1024x1024 px natively." - ), - es=( - "El checkpoint SD3/SD3.5 a cargar. 'sd-3-medium' es el modelo base " - "de 2B parámetros. 'sd-3.5-medium' mejora la calidad a velocidad " - "similar. 'sd-3.5-large' (8B) ofrece la mayor calidad pero necesita " - "más VRAM. 'sd-3.5-large-turbo' es un modelo large destilado que " - "requiere muchos menos pasos (4-8) para generación rápida de alta " - "calidad. Todas las variantes apuntan a 1024x1024 px de forma nativa." - ), - pt=( - "O checkpoint SD3/SD3.5 a carregar. 'sd-3-medium' é o modelo base " - "de 2B parâmetros. 'sd-3.5-medium' melhora a qualidade a velocidade " - "similar. 'sd-3.5-large' (8B) oferece a maior qualidade mas precisa " - "de mais VRAM. 'sd-3.5-large-turbo' é um modelo large destilado que " - "requer muito menos passos (4-8) para geração rápida de alta " - "qualidade. " - "Todas as variantes visam 1024x1024 px nativamente." - ), - de=( - "Der zu ladende SD3/SD3.5-Checkpoint. 'sd-3-medium' ist das " - "2B-Parameter-Basismodell. 'sd-3.5-medium' verbessert die Qualität " - "bei ähnlicher Geschwindigkeit. 'sd-3.5-large' (8B) liefert die höchste" - "Qualität, benötigt aber mehr VRAM. 'sd-3.5-large-turbo' ist ein " - "destilliertes Large-Modell, das deutlich weniger Schritte (4-8) für " - "schnelle hochwertige Generierung benötigt. " - "Alle Varianten zielen nativ auf 1024x1024 px ab." - ), - zh=( - "要加载的 SD3/SD3.5 检查点。'sd-3-medium' 是 2B 参数基准模型。" - "'sd-3.5-medium' 以相近速度提升质量。'sd-3.5-large'(8B)质量最高但" - "需要更多显存。'sd-3.5-large-turbo' 是蒸馏版大模型,仅需 4-8 步即可" - "快速生成高质量图像。所有变体原生目标分辨率为 1024x1024 像素。" - ), - ), - alias=MultilingualString( - en="Model name", - es="Nombre del modelo", - pt="Nome do modelo", - de="Modellname", - zh="模型名称", - ), - ) # type: ignore - huggingface_key: schema_field( string_field(), placeholder="", @@ -454,7 +396,9 @@ class StableDiffusionSchema(BaseSchema): ) # type: ignore -class StableDiffusionV3Model(TextToImageGenerationTaskModel): +class StableDiffusion3GenerationModel( + HFPretrainedDownloadMixin, TextToImageGenerationTaskModel +): """Multimodal Diffusion Transformer model for high-quality text-to-image generation. Wraps the Stable Diffusion 3 and 3.5 family of checkpoints from @@ -477,6 +421,7 @@ class StableDiffusionV3Model(TextToImageGenerationTaskModel): """ SCHEMA = StableDiffusionSchema + MODEL_NAME: str = "" COLOR: str = "#6a1b9a" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion V3", @@ -544,9 +489,7 @@ def __init__(self, **kwargs): self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = kwargs.get( - "model_name", "stabilityai/stable-diffusion-3-medium-diffusers" - ) + self.model_name = self._pretrained_source(None) self.huggingface_key = kwargs.get("huggingface_key") if self.huggingface_key: @@ -613,3 +556,107 @@ def generate(self, input: str) -> List[Any]: output = self.model(**params) return output.images + + +class StableDiffusion3Medium(StableDiffusion3GenerationModel): + """Stable Diffusion 3 Medium checkpoint (gated). + + Downloads its checkpoint into the component's own download folder. This is + a gated Hugging Face repo; downloading requires prior authentication + (an HF token in the environment). + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-3-medium-diffusers" + DOWNLOAD_SIZE_BYTES: int = 5500000000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 3 Medium", + es="Stable Diffusion 3 Medium", + pt="Stable Diffusion 3 Medium", + de="Stable Diffusion 3 Medium", + zh="Stable Diffusion 3 Medium", + ) + DESCRIPTION = MultilingualString( + en="Stable Diffusion 3 Medium checkpoint (gated).", + es="Stable Diffusion 3 Medium checkpoint (gated).", + pt="Stable Diffusion 3 Medium checkpoint (gated).", + de="Stable Diffusion 3 Medium checkpoint (gated).", + zh="Stable Diffusion 3 Medium checkpoint (gated).", + ) + + +class StableDiffusion35Medium(StableDiffusion3GenerationModel): + """Stable Diffusion 3.5 Medium checkpoint (gated). + + Downloads its checkpoint into the component's own download folder. This is + a gated Hugging Face repo; downloading requires prior authentication + (an HF token in the environment). + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-medium" + DOWNLOAD_SIZE_BYTES: int = 10000000000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 3.5 Medium", + es="Stable Diffusion 3.5 Medium", + pt="Stable Diffusion 3.5 Medium", + de="Stable Diffusion 3.5 Medium", + zh="Stable Diffusion 3.5 Medium", + ) + DESCRIPTION = MultilingualString( + en="Stable Diffusion 3.5 Medium checkpoint (gated).", + es="Stable Diffusion 3.5 Medium checkpoint (gated).", + pt="Stable Diffusion 3.5 Medium checkpoint (gated).", + de="Stable Diffusion 3.5 Medium checkpoint (gated).", + zh="Stable Diffusion 3.5 Medium checkpoint (gated).", + ) + + +class StableDiffusion35Large(StableDiffusion3GenerationModel): + """Stable Diffusion 3.5 Large checkpoint (gated). + + Downloads its checkpoint into the component's own download folder. This is + a gated Hugging Face repo; downloading requires prior authentication + (an HF token in the environment). + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-large" + DOWNLOAD_SIZE_BYTES: int = 16000000000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 3.5 Large", + es="Stable Diffusion 3.5 Large", + pt="Stable Diffusion 3.5 Large", + de="Stable Diffusion 3.5 Large", + zh="Stable Diffusion 3.5 Large", + ) + DESCRIPTION = MultilingualString( + en="Stable Diffusion 3.5 Large checkpoint (gated).", + es="Stable Diffusion 3.5 Large checkpoint (gated).", + pt="Stable Diffusion 3.5 Large checkpoint (gated).", + de="Stable Diffusion 3.5 Large checkpoint (gated).", + zh="Stable Diffusion 3.5 Large checkpoint (gated).", + ) + + +class StableDiffusion35LargeTurbo(StableDiffusion3GenerationModel): + """Stable Diffusion 3.5 Large Turbo checkpoint (gated). + + Downloads its checkpoint into the component's own download folder. This is + a gated Hugging Face repo; downloading requires prior authentication + (an HF token in the environment). + """ + + MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-large-turbo" + DOWNLOAD_SIZE_BYTES: int = 16000000000 + DISPLAY_NAME = MultilingualString( + en="Stable Diffusion 3.5 Large Turbo", + es="Stable Diffusion 3.5 Large Turbo", + pt="Stable Diffusion 3.5 Large Turbo", + de="Stable Diffusion 3.5 Large Turbo", + zh="Stable Diffusion 3.5 Large Turbo", + ) + DESCRIPTION = MultilingualString( + en="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", + es="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", + pt="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", + de="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", + zh="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", + ) diff --git a/tests/back/models/test_sd3_downloadable.py b/tests/back/models/test_sd3_downloadable.py new file mode 100644 index 000000000..4e3a3bbd3 --- /dev/null +++ b/tests/back/models/test_sd3_downloadable.py @@ -0,0 +1,33 @@ +"""Tests that Stable Diffusion 3 per-checkpoint models expose download metadata.""" + +import pytest + +from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin +from DashAI.back.models.hugging_face.stable_diffusion_v3_model import ( + StableDiffusion3Medium, + StableDiffusion35Large, + StableDiffusion35LargeTurbo, + StableDiffusion35Medium, +) + +_CASES = [ + (StableDiffusion3Medium, "stabilityai/stable-diffusion-3-medium-diffusers"), + (StableDiffusion35Medium, "stabilityai/stable-diffusion-3.5-medium"), + (StableDiffusion35Large, "stabilityai/stable-diffusion-3.5-large"), + (StableDiffusion35LargeTurbo, "stabilityai/stable-diffusion-3.5-large-turbo"), +] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd3_is_downloadable(model_cls, repo_id): + assert issubclass(model_cls, HFPretrainedDownloadMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + assert model_cls.hf_repos() == [(repo_id, "model")] + + +@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) +def test_sd3_metadata_flags_download(model_cls, repo_id): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES From beef665862abef30a4357aeffd0db98df1f37a02 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 14:49:48 -0400 Subject: [PATCH 055/308] feat: make ControlNet models downloadable with per-repo local loading --- .../dependencies/downloads/downloadable.py | 26 ++++++ .../sd15_depth_controlnet_model.py | 14 +++- .../hugging_face/sd15_hed_controlnet_model.py | 14 +++- .../sd15_openpose_controlnet_model.py | 14 +++- .../sdxl_canny_controlnet_model.py | 17 +++- .../stable_diffusion_v1_depth_controlnet.py | 17 +++- .../models/test_controlnet_downloadable.py | 79 +++++++++++++++++++ 7 files changed, 164 insertions(+), 17 deletions(-) create mode 100644 tests/back/models/test_controlnet_downloadable.py diff --git a/DashAI/back/dependencies/downloads/downloadable.py b/DashAI/back/dependencies/downloads/downloadable.py index 56112910f..2334b25a0 100644 --- a/DashAI/back/dependencies/downloads/downloadable.py +++ b/DashAI/back/dependencies/downloads/downloadable.py @@ -158,6 +158,32 @@ def is_downloaded(cls) -> bool: for rid, *_ in repos ) + @classmethod + def _local_or_repo(cls, repo_id: str) -> str: + """Return the local dir for a repo if downloaded, else the repo id. + + Lets multi-repo components (e.g. ControlNet pipelines) load each repo + from the component's own download folder when present, falling back to + the Hub otherwise. Downloading is enforced by the run/session gates. + + Parameters + ---------- + repo_id : str + HuggingFace repo identifier. + + Returns + ------- + str + A local path (when the repo is present) or ``repo_id``. + """ + try: + target = cls._repo_dir(repo_id) + if target.is_dir() and any(target.iterdir()): + return str(target) + except Exception: + pass + return repo_id + @classmethod def download(cls, report: Optional[ProgressReporter] = None) -> None: """Download all repos listed in ``hf_repos()`` into ``component_dir()``. diff --git a/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py index 900bfb44f..a81a9459a 100644 --- a/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -239,7 +242,7 @@ def get_depth_map_sd15(image, device): return image -class SD15DepthControlNetModel(BaseControlNetModel): +class SD15DepthControlNetModel(HFDownloadableMixin, BaseControlNetModel): """Depth-conditioned ControlNet pipeline built on Stable Diffusion 1.5. Takes an input image and a text prompt. A depth map is estimated from the @@ -257,6 +260,11 @@ class SD15DepthControlNetModel(BaseControlNetModel): """ SCHEMA = SD15DepthControlNetSchema + HF_REPOS = [ + ("runwayml/stable-diffusion-v1-5", "model"), + ("lllyasviel/sd-controlnet-depth", "model"), + ] + DOWNLOAD_SIZE_BYTES = 5400000000 COLOR: str = "#4e342e" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 Depth ControlNet", @@ -349,12 +357,12 @@ def __init__(self, **kwargs: Any): ) controlnet = ControlNetModel.from_pretrained( - "lllyasviel/sd-controlnet-depth", + self._local_or_repo("lllyasviel/sd-controlnet-depth"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionControlNetPipeline.from_pretrained( - "runwayml/stable-diffusion-v1-5", + self._local_or_repo("runwayml/stable-diffusion-v1-5"), controlnet=controlnet, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) diff --git a/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py index c04273c9b..54bfc6516 100644 --- a/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -166,7 +169,7 @@ class SD15HEDControlNetSchema(BaseSchema): ) # type: ignore -class SD15HEDControlNetModel(BaseControlNetModel): +class SD15HEDControlNetModel(HFDownloadableMixin, BaseControlNetModel): """HED soft-edge-conditioned ControlNet pipeline built on Stable Diffusion 1.5. Takes an input image and a text prompt. Soft edge maps are extracted from @@ -188,6 +191,11 @@ class SD15HEDControlNetModel(BaseControlNetModel): """ SCHEMA = SD15HEDControlNetSchema + HF_REPOS = [ + ("runwayml/stable-diffusion-v1-5", "model"), + ("lllyasviel/sd-controlnet-hed", "model"), + ] + DOWNLOAD_SIZE_BYTES = 5400000000 COLOR: str = "#006064" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 HED ControlNet", @@ -305,12 +313,12 @@ def __init__(self, **kwargs: Any): self.hed_detector = HEDdetector.from_pretrained("lllyasviel/Annotators") controlnet = ControlNetModel.from_pretrained( - "lllyasviel/sd-controlnet-hed", + self._local_or_repo("lllyasviel/sd-controlnet-hed"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionControlNetPipeline.from_pretrained( - "runwayml/stable-diffusion-v1-5", + self._local_or_repo("runwayml/stable-diffusion-v1-5"), controlnet=controlnet, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) diff --git a/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py index 4b575a2a1..fcee46933 100644 --- a/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -162,7 +165,7 @@ class SD15OpenPoseControlNetSchema(BaseSchema): ) # type: ignore -class SD15OpenPoseControlNetModel(BaseControlNetModel): +class SD15OpenPoseControlNetModel(HFDownloadableMixin, BaseControlNetModel): """OpenPose-conditioned ControlNet pipeline built on Stable Diffusion 1.5. Takes an input image and a text prompt. Human body keypoints and skeleton @@ -183,6 +186,11 @@ class SD15OpenPoseControlNetModel(BaseControlNetModel): """ SCHEMA = SD15OpenPoseControlNetSchema + HF_REPOS = [ + ("runwayml/stable-diffusion-v1-5", "model"), + ("lllyasviel/sd-controlnet-openpose", "model"), + ] + DOWNLOAD_SIZE_BYTES = 5400000000 COLOR: str = "#880e4f" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 OpenPose ControlNet", @@ -298,12 +306,12 @@ def __init__(self, **kwargs: Any): self.pose_detector = OpenposeDetector.from_pretrained("lllyasviel/Annotators") controlnet = ControlNetModel.from_pretrained( - "lllyasviel/sd-controlnet-openpose", + self._local_or_repo("lllyasviel/sd-controlnet-openpose"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionControlNetPipeline.from_pretrained( - "runwayml/stable-diffusion-v1-5", + self._local_or_repo("runwayml/stable-diffusion-v1-5"), controlnet=controlnet, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) diff --git a/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py b/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py index ba0cb3594..c7b603a84 100644 --- a/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -267,7 +270,7 @@ def get_canny_image( return Image.fromarray(edges_rgb) -class SDXLCannyControlNetModel(BaseControlNetModel): +class SDXLCannyControlNetModel(HFDownloadableMixin, BaseControlNetModel): """Canny-edge-conditioned ControlNet pipeline built on Stable Diffusion XL 1.0. Takes an input image and a text prompt. Canny edge maps are extracted using @@ -288,6 +291,12 @@ class SDXLCannyControlNetModel(BaseControlNetModel): """ SCHEMA = SDXLCannyControlNetSchema + HF_REPOS = [ + ("stabilityai/stable-diffusion-xl-base-1.0", "model"), + ("diffusers/controlnet-canny-sdxl-1.0", "model"), + ("madebyollin/sdxl-vae-fp16-fix", "model"), + ] + DOWNLOAD_SIZE_BYTES = 10000000000 COLOR: str = "#1a237e" DISPLAY_NAME: str = MultilingualString( en="SDXL Canny ControlNet", @@ -402,17 +411,17 @@ def __init__(self, **kwargs: Any): ) controlnet = ControlNetModel.from_pretrained( - "diffusers/controlnet-canny-sdxl-1.0", + self._local_or_repo("diffusers/controlnet-canny-sdxl-1.0"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) vae = AutoencoderKL.from_pretrained( - "madebyollin/sdxl-vae-fp16-fix", + self._local_or_repo("madebyollin/sdxl-vae-fp16-fix"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionXLControlNetPipeline.from_pretrained( - "stabilityai/stable-diffusion-xl-base-1.0", + self._local_or_repo("stabilityai/stable-diffusion-xl-base-1.0"), controlnet=controlnet, vae=vae, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py index 1482b1da5..589d8aa43 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py @@ -8,6 +8,9 @@ ) from DashAI.back.core.schema_fields.base_schema import BaseSchema from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + HFDownloadableMixin, +) from DashAI.back.models.controlnet_model import ControlNetModel as BaseControlNetModel from DashAI.back.models.utils import DEVICE_ENUM, DEVICE_PLACEHOLDER, DEVICE_TO_IDX @@ -205,11 +208,17 @@ def get_depth_map(image, device): return image -class StableDiffusionXLV1ControlNet(BaseControlNetModel): +class StableDiffusionXLV1ControlNet(HFDownloadableMixin, BaseControlNetModel): """A wrapper implementation of ControlNet with depth preprocessing and stable diffusion xl 1.0 as pipeline.""" SCHEMA = StableDiffusionXLV1ControlNetSchema + HF_REPOS = [ + ("stabilityai/stable-diffusion-xl-base-1.0", "model"), + ("diffusers/controlnet-depth-sdxl-1.0-small", "model"), + ("madebyollin/sdxl-vae-fp16-fix", "model"), + ] + DOWNLOAD_SIZE_BYTES = 10000000000 COLOR: str = "#e65100" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion XL V1 ControlNet", @@ -313,19 +322,19 @@ def __init__(self, **kwargs: Any): ) self.controlnet = ControlNetModel.from_pretrained( - "diffusers/controlnet-depth-sdxl-1.0-small", + self._local_or_repo("diffusers/controlnet-depth-sdxl-1.0-small"), variant="fp16", use_safetensors=True, torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.vae = AutoencoderKL.from_pretrained( - "madebyollin/sdxl-vae-fp16-fix", + self._local_or_repo("madebyollin/sdxl-vae-fp16-fix"), torch_dtype=torch.float32 if self.device == "cpu" else torch.float16, ).to(self.device) self.pipe = StableDiffusionXLControlNetPipeline.from_pretrained( - "stabilityai/stable-diffusion-xl-base-1.0", + self._local_or_repo("stabilityai/stable-diffusion-xl-base-1.0"), controlnet=self.controlnet, vae=self.vae, variant="fp16", diff --git a/tests/back/models/test_controlnet_downloadable.py b/tests/back/models/test_controlnet_downloadable.py new file mode 100644 index 000000000..cb32f6764 --- /dev/null +++ b/tests/back/models/test_controlnet_downloadable.py @@ -0,0 +1,79 @@ +"""Tests that ControlNet models expose multi-repo download metadata.""" + +import pytest +from kink import di + +from DashAI.back.dependencies.downloads.downloadable import HFDownloadableMixin +from DashAI.back.models.hugging_face.sd15_depth_controlnet_model import ( + SD15DepthControlNetModel, +) +from DashAI.back.models.hugging_face.sd15_hed_controlnet_model import ( + SD15HEDControlNetModel, +) +from DashAI.back.models.hugging_face.sd15_openpose_controlnet_model import ( + SD15OpenPoseControlNetModel, +) +from DashAI.back.models.hugging_face.sdxl_canny_controlnet_model import ( + SDXLCannyControlNetModel, +) +from DashAI.back.models.hugging_face.stable_diffusion_v1_depth_controlnet import ( + StableDiffusionXLV1ControlNet, +) + +_CLASSES = [ + SD15DepthControlNetModel, + SD15HEDControlNetModel, + SD15OpenPoseControlNetModel, + SDXLCannyControlNetModel, + StableDiffusionXLV1ControlNet, +] + + +@pytest.fixture +def component_root(tmp_path): + """Inject a temporary COMPONENT_PATH into the kink DI container.""" + sentinel = object() + old = di["config"] if "config" in di else sentinel # noqa: SIM401 + di["config"] = {"COMPONENT_PATH": str(tmp_path)} + yield tmp_path + if old is sentinel: + del di["config"] + else: + di["config"] = old + + +@pytest.mark.parametrize("model_cls", _CLASSES) +def test_controlnet_is_downloadable(model_cls): + assert issubclass(model_cls, HFDownloadableMixin) + assert model_cls.REQUIRES_DOWNLOAD is True + assert model_cls.DOWNLOAD_SIZE_BYTES is not None + # Each ControlNet pulls several repos (base + controlnet [+ vae]). + assert len(model_cls.hf_repos()) >= 2 + + +@pytest.mark.parametrize("model_cls", _CLASSES) +def test_controlnet_metadata_flags_download(model_cls): + meta = model_cls.get_metadata() + assert meta["requires_download"] is True + assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES + + +def test_controlnet_is_downloaded_requires_all_repos(component_root): + """is_downloaded must be True only when every repo dir is present.""" + model_cls = SD15DepthControlNetModel + repos = [rid for rid, *_ in model_cls.hf_repos()] + + assert model_cls.is_downloaded() is False + + # Only the first repo present -> still not downloaded. + first = component_root / model_cls.__name__ / repos[0].split("/")[-1] + first.mkdir(parents=True) + (first / "config.json").write_text("{}") + assert model_cls.is_downloaded() is False + + # All repos present -> downloaded. + for rid in repos[1:]: + d = component_root / model_cls.__name__ / rid.split("/")[-1] + d.mkdir(parents=True) + (d / "config.json").write_text("{}") + assert model_cls.is_downloaded() is True From ec3203a42add36e096b41f51bec8dc60dd214027 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 2 Jul 2026 15:08:52 -0400 Subject: [PATCH 056/308] feat: download ControlNet preprocessors into the component folder --- .../hugging_face/sd15_depth_controlnet_model.py | 15 ++++++++------- .../hugging_face/sd15_hed_controlnet_model.py | 7 +++++-- .../sd15_openpose_controlnet_model.py | 7 +++++-- .../stable_diffusion_v1_depth_controlnet.py | 15 ++++++++------- tests/back/models/test_controlnet_downloadable.py | 15 +++++++++++++++ 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py index a81a9459a..cead31cfb 100644 --- a/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py @@ -180,7 +180,7 @@ class SD15DepthControlNetSchema(BaseSchema): ) # type: ignore -def get_depth_map_sd15(image, device): +def get_depth_map_sd15(image, device, model_source="Intel/dpt-hybrid-midas"): """Convert an input image to a normalised depth map for SD 1.5 ControlNet. Uses Intel's DPT-Hybrid-MiDaS model to estimate per-pixel depth, then @@ -208,10 +208,8 @@ def get_depth_map_sd15(image, device): from PIL import Image from transformers import DPTForDepthEstimation, DPTImageProcessor - depth_estimator = DPTForDepthEstimation.from_pretrained( - "Intel/dpt-hybrid-midas" - ).to(device) - feature_extractor = DPTImageProcessor.from_pretrained("Intel/dpt-hybrid-midas") + depth_estimator = DPTForDepthEstimation.from_pretrained(model_source).to(device) + feature_extractor = DPTImageProcessor.from_pretrained(model_source) pixel_values = feature_extractor(images=image, return_tensors="pt").pixel_values.to( device @@ -263,8 +261,9 @@ class SD15DepthControlNetModel(HFDownloadableMixin, BaseControlNetModel): HF_REPOS = [ ("runwayml/stable-diffusion-v1-5", "model"), ("lllyasviel/sd-controlnet-depth", "model"), + ("Intel/dpt-hybrid-midas", "model"), ] - DOWNLOAD_SIZE_BYTES = 5400000000 + DOWNLOAD_SIZE_BYTES = 5900000000 COLOR: str = "#4e342e" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 Depth ControlNet", @@ -390,7 +389,9 @@ def generate(self, input: Tuple["Image.Image", str]) -> List[Any]: image = input[0] prompt = input[1] - depth_map = get_depth_map_sd15(image, self.device) + depth_map = get_depth_map_sd15( + image, self.device, self._local_or_repo("Intel/dpt-hybrid-midas") + ) output = self.pipe( prompt=prompt, image=depth_map, diff --git a/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py index 54bfc6516..461a25a4d 100644 --- a/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py @@ -194,8 +194,9 @@ class SD15HEDControlNetModel(HFDownloadableMixin, BaseControlNetModel): HF_REPOS = [ ("runwayml/stable-diffusion-v1-5", "model"), ("lllyasviel/sd-controlnet-hed", "model"), + ("lllyasviel/Annotators", "model"), ] - DOWNLOAD_SIZE_BYTES = 5400000000 + DOWNLOAD_SIZE_BYTES = 7400000000 COLOR: str = "#006064" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 HED ControlNet", @@ -310,7 +311,9 @@ def __init__(self, **kwargs: Any): f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.hed_detector = HEDdetector.from_pretrained("lllyasviel/Annotators") + self.hed_detector = HEDdetector.from_pretrained( + self._local_or_repo("lllyasviel/Annotators") + ) controlnet = ControlNetModel.from_pretrained( self._local_or_repo("lllyasviel/sd-controlnet-hed"), diff --git a/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py index fcee46933..1036adf31 100644 --- a/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py @@ -189,8 +189,9 @@ class SD15OpenPoseControlNetModel(HFDownloadableMixin, BaseControlNetModel): HF_REPOS = [ ("runwayml/stable-diffusion-v1-5", "model"), ("lllyasviel/sd-controlnet-openpose", "model"), + ("lllyasviel/Annotators", "model"), ] - DOWNLOAD_SIZE_BYTES = 5400000000 + DOWNLOAD_SIZE_BYTES = 7400000000 COLOR: str = "#880e4f" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 OpenPose ControlNet", @@ -303,7 +304,9 @@ def __init__(self, **kwargs: Any): f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.pose_detector = OpenposeDetector.from_pretrained("lllyasviel/Annotators") + self.pose_detector = OpenposeDetector.from_pretrained( + self._local_or_repo("lllyasviel/Annotators") + ) controlnet = ControlNetModel.from_pretrained( self._local_or_repo("lllyasviel/sd-controlnet-openpose"), diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py index 589d8aa43..5965f1c43 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py @@ -156,7 +156,7 @@ class StableDiffusionXLV1ControlNetSchema(BaseSchema): ) # type: ignore -def get_depth_map(image, device): +def get_depth_map(image, device, model_source="Intel/dpt-hybrid-midas"): """Convert an input image to a normalised depth map for SDXL ControlNet. Uses Intel's DPT-Hybrid-MiDaS model to estimate per-pixel depth, then @@ -182,10 +182,8 @@ def get_depth_map(image, device): from PIL import Image from transformers import DPTForDepthEstimation, DPTImageProcessor - depth_estimator = DPTForDepthEstimation.from_pretrained( - "Intel/dpt-hybrid-midas" - ).to(device) - feature_extractor = DPTImageProcessor.from_pretrained("Intel/dpt-hybrid-midas") + depth_estimator = DPTForDepthEstimation.from_pretrained(model_source).to(device) + feature_extractor = DPTImageProcessor.from_pretrained(model_source) image = feature_extractor(images=image, return_tensors="pt").pixel_values.to(device) @@ -217,8 +215,9 @@ class StableDiffusionXLV1ControlNet(HFDownloadableMixin, BaseControlNetModel): ("stabilityai/stable-diffusion-xl-base-1.0", "model"), ("diffusers/controlnet-depth-sdxl-1.0-small", "model"), ("madebyollin/sdxl-vae-fp16-fix", "model"), + ("Intel/dpt-hybrid-midas", "model"), ] - DOWNLOAD_SIZE_BYTES = 10000000000 + DOWNLOAD_SIZE_BYTES = 10500000000 COLOR: str = "#e65100" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion XL V1 ControlNet", @@ -364,7 +363,9 @@ def generate(self, input: Tuple[Any, str]) -> List[Any]: image = input[0] prompt = input[1] - depth_map = get_depth_map(image, self.device) + depth_map = get_depth_map( + image, self.device, self._local_or_repo("Intel/dpt-hybrid-midas") + ) image = self.pipe( prompt=prompt, image=depth_map, diff --git a/tests/back/models/test_controlnet_downloadable.py b/tests/back/models/test_controlnet_downloadable.py index cb32f6764..461884509 100644 --- a/tests/back/models/test_controlnet_downloadable.py +++ b/tests/back/models/test_controlnet_downloadable.py @@ -51,6 +51,21 @@ def test_controlnet_is_downloadable(model_cls): assert len(model_cls.hf_repos()) >= 2 +# Preprocessor repos are fetched by download() (not from the Hub at run time), +# so they must be part of hf_repos() for the models that use one. +_PREPROCESSOR_REPOS = { + SD15DepthControlNetModel: "Intel/dpt-hybrid-midas", + SD15HEDControlNetModel: "lllyasviel/Annotators", + SD15OpenPoseControlNetModel: "lllyasviel/Annotators", + StableDiffusionXLV1ControlNet: "Intel/dpt-hybrid-midas", +} + + +@pytest.mark.parametrize(("model_cls", "repo"), _PREPROCESSOR_REPOS.items()) +def test_controlnet_preprocessor_included(model_cls, repo): + assert repo in [rid for rid, *_ in model_cls.hf_repos()] + + @pytest.mark.parametrize("model_cls", _CLASSES) def test_controlnet_metadata_flags_download(model_cls): meta = model_cls.get_metadata() From cfd736a2b4e15e0821b6d29aec36caa96fc303b1 Mon Sep 17 00:00:00 2001 From: Creylay Date: Thu, 2 Jul 2026 16:55:41 -0400 Subject: [PATCH 057/308] Add FCFM as leading institution --- README.rst | 1 + docs/static/img/institutions/fcfm-logo.png | Bin 0 -> 35827 bytes docs/static/institutions/institutions.json | 8 ++++++++ images/logos.png | Bin 70891 -> 90543 bytes 4 files changed, 9 insertions(+) create mode 100644 docs/static/img/institutions/fcfm-logo.png diff --git a/README.rst b/README.rst index 4834973fd..92949612e 100644 --- a/README.rst +++ b/README.rst @@ -528,6 +528,7 @@ Acknowledgments This project is developed in collaboration with: * `University of Chile `_ - Leading Institution +* `Fcfm `_ - Leading Institution * `CENIA `_ - Associated Institution * `IMFD `_ - Collaborator * `Unholster `_ - Industry Partner diff --git a/docs/static/img/institutions/fcfm-logo.png b/docs/static/img/institutions/fcfm-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..260cc3ce3960e9c8620b32deb0e925aa4a5b40ff GIT binary patch literal 35827 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

k)bk5B0*=*dkB)9I^R1t~ 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 a26Ho^&+l(D0I&1{Ht}5jT-G@yGywoIRcUwt literal 0 HcmV?d00001 diff --git a/docs/static/institutions/institutions.json b/docs/static/institutions/institutions.json index 74822d113..52e64ea01 100644 --- a/docs/static/institutions/institutions.json +++ b/docs/static/institutions/institutions.json @@ -10,6 +10,14 @@ "logo": "img/institutions/dcc-logo.png", "small": true }, + { + "id": "fcfm", + "name": "Fcfm", + "fullName": "Facultad de Ciencias Físicas y Matemáticas", + "role": "Leading Institution", + "url": "https://www.fcfm.uchile.cl/", + "logo": "img/institutions/fcfm-logo.png" + }, { "id": "cenia", "name": "CENIA", diff --git a/images/logos.png b/images/logos.png index c4cd13fd0fd1ef1cc77a89949c09936d7e7f61c7..43505f0a615d7669f9689fbf41dfa918f3c320a2 100644 GIT binary patch literal 90543 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 Y`v$IKR*ll#=SB%L>d{J%Rg_M|B3d9%|l4I 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 Y7z;U0XDz(m9ss@!^h|Wib)2IA1Jg7%;Q#;t literal 70891 zcmeFYbyJ*O*FA{4y9H?o7F>e|XxycN;2J!*OXKbY4-gz0f=h4+9-QFr?(R(A_w#$+ zsd?uUOx66;*VWZh-TSP4_S$Q&6RD~!gNa6l1_J|wDJKh5hk-%*1ik-(f&_ikO#eXz z0}BHq2Nc)zTs+S9_)aJF5T4uA!f->+Z01d>nG>4Fbg~xF$03&7*)k+6eu=67B!B{$?{%GunBrO&A@K)4|v88#e}~!BX_+|Gl=xrJF%#*Epr-fsQw4nTg*U~x?)(l@>k3=gSxI5L zC+Z5Sp1lZg8m=}(Wn|lWZFHSE{9ov+FmT0epS`ZpS^ObpE32=gG2pz8fHM$<2PXtG zs_?mLq!yKW0G3<7LGXY5XexbS+I#8qm><2}NJl}`#wVEq$|U(awXHZ5zX)`WM4MnO z;nU#V->Vy+$eJguZb7F10e;2Qr#+M{Z2R-)jaG7v&Rkh{AKqeE(9xl_myKqHdPb4S zs}uPcm>OfZqpY#1w)D~ceDGtyS$9XE{p1CQ=y-LBV0sq0|n-t<+{$gkpgL`E`?hcQrBpu z;5#S|Yao5t?40hB?3^1C;|?FCdL`9VWBt>6XQrd3Sq%S9Qf{61O!1J9y*d?LkF8DL ze*2EH`9EPKvSpx2%#C0TKW?6Jgs-SpC=1;tjf2|@b2$E|+4pE&M_uk+!xyy_9B}Cb zZw6($V18rMnU8;lx>731{{558eb}6z#WEH)7;?lO_2f~=^3`H$T%7cnrtWE`r`gu)-jjucB@a9; ziE1=!stXZUwRTzLH6K8dbmWo6xsO|%&px-3B`h8h~zUi3;A7Yt>J{@AQ36V8YlM&We$ zAy-SdLZdWT&X}sIPa}FqKmbLE(Bx21XvqLqY&IZd9+II&Oa+O;2cfW zZ(Ay7e&@N&&AU{d8udezgi3L0W>S8x<};39fmVYiQkg=rhvy&JleFPJ;!1rSy8k;x zM3)n(f>;qTY{zO3&UL?xo$ZXoJA}aP$VXTrY}%>m#Z_!QZ#8b6EEvR4C2bY&ec`I( zji(dQB=!ucm@37=wKGZ_855#8z{pNB?2m{)k+a!*$0Yi~a?tKxa?!fhZ~GN)oc4IX z@T9^;?xcwybI%u5u7pweoa1Ee0XBB+4eND&GQF4{Z)+g2bB9k5d~2Je=OFjDZ>@)% zlOpS^XiG+{;PhG}A(L!TSuFa|AxGgB9~sc0a*(2J{dU@CWwaLLs*m;0&Y^wJ?nrbV z?m1mR5Dz>-(Z{qyh=DhZn9GG3L1;0U5r8JQ*^HF`ba4_dl^=+%*@VtNy?)rOVUdIS zpcGr3@v#h58|GW|xY5Cs$v zJ!=-q?tFZQxd(<{n(6y48E4x2^NrrlRx(;D6Z{M>=OhuRY9p?7zx@PD zk!_&5Q(x{&XZz5aCw_4ajeOD0J%`7ycSj^d_sD9R$Uy5FKG?&+H&~@EoFDMAus$&3 z%{DIo0E$`CH)+A(?L*Qc4yNfvPtBJ_3q!32)pHH}%5#mZW3ospWl!h-=FL5h0Ah3i zEAn6f_AlqjL_JPud<0O2=tb~W^TXYKN(i8!N+=i!7#HLTxG$EH;_LjD2wmZ)`lGc) zUK7d%SSS5FnB^N<9aX4K*R+NuS5hi=gmLbNr}s6G-wvfuhH`*hFr?+LsETRN#Wy2g zQt-29b?P-6RUQ-*6CAx%Vnn|aeppZzMXG3gzsD%ujHi?`y;2%l^Zl@+ZB%r9T{r)5 zRbzir%1jmp&Dq0FU%`glHy$zsS^6#dWC3U$36o>lx#H}ixnPtLnvftk;Y9z)Zg{J( ze)NfRbr-!$Z0k;`%SnOcJtxa|zTtm)4H4l>qOAQb>bR{a0W0C=Ai@AypF%m&aO8CA zUG&gHD_~ z(atmuBhvw;%;uBtB3_R{e$D3^OFwoJs#V1$4V-X+2+S63iL~bfapJzYnfURD<2~k6 zbo6#HC&$3r&+1F|l6&m_l5-Z?QUly!o5}bGk0(@C0v9d>UpwK;hNOe!*}~~Gj9D9- zuvt^jW#Xlyi%O%nz1DrKnq#cLn(h;J7IC?ey%T#ygh$%1{IJti)TNuONG)$LgQy6* z7;t!Bo>9Z%#pg(SPT(ki{BXf>PB|e-aB{&{jjz^H0W+U9$nvrlT&P4vf*6BL7PuGb zEA?;w=qMhR$Lulibr?1f1nl+3H}H%1MHMA)8x(z-a63G#G1F18Yw{o)h#^Pp44W zDg5A4JQ$Lfy%nTaowyI$#n4heFR%U~3|Z01TOQk4giqftN#FoF z;tSQso_b&d_8`WwlSqnjZ|~sjWay#_14VyGY;Rp)oU0j=IP#w>t-l9M7La-p-IFF# zUt&2rw;eM9E@HQhMu9uhps4a%x6kPm#3`+xt>vfd^9+kDT?+Ce>`svTZyVzToU|Y zZv$Rz3~*p#B5;36?0nD)8q&xg#2?pN!n!qPO&Yz-8gei?K1I_<7z_^pB;s%h9%{V)>%}67?7R&ZtWlGyXYMcI0C!W38Es@sW;V2C>5x4$DRJzhxI0{e324 z4F6@cyp?$DRgN*MOkfDCp?Be4{5%($-vcT3ZI>iW*h?%&{t>mLyN`7Ej-$8`@J%JXKzb1QFb{%lglX=bk} z%t^{>-W%_u@N@g7cQ>y!*vbp1u>omkJicF&(1*F2lAI4KIwhi{>bM zl+{exp=jE#cOX%p`PAb!6a2H$VeZ!UuCr7Btak&v=VjUbFpdks$@g=0++G_4$dd_j zs;I0gD)s|msc(K@@a>8n$qq*^f0){hwL8TeNztnMPF-vKUQ75|Pi2JRk0^7lbbYJ_ zq6$P%TQO3-e%?)tOR30#XKgqI@KuaI`E3rIXpR?SOh!(8%z%+-M}!->%78B`h-iWW zNq<~KtYI{X@T5#q9yp>exO)kbVfp=$;;Un@8vye&ACb>hS0J!>Z!2QT#2#@Pc8vy4 zsFI#b_KZfp`10Au8e{~=NQF}d|aGOe|{4UeZt8X zN0g&Ox@}!2x%K-!+_g7zhMa?DaEPoR+|>XHOjANgA( zLVWXupk!d|R*3|h6(SbQS3nfTLMEfSIXU2>_n&_ow_|6=Fckr7x6W*qZBmr7ES)L{**TmhYLSew&Ry0^x~{s6A^@Ua z;Mrk1;firc3yA~L3_$<%w81HvcH6ohN2OFPd(7ScWheLRTRvewREx{_`fiL?qTBgM zC2(|;I`WEI2OUm5lWE;4hxhIl9rI2oeVSr1!_605Jz2A`s^01N&x#rTAP9j%WR{J^ zPN$ratfVVSecDa{k}6~Pz2P|!2eSTTj(p!S#L3caUv zfAy861BmzEb>GQ-tr2K!zzi~`5$|!o`>`To%o?f0FV1C+0%xa93F3^~8C*1O8aH%$MAFlO>`3wregs zALP-_7s5zP{(oM+N;^96*>WxQLV-I5oYMJc^C+u^q^UuaS)x?g-)wt*I>zIHV zTEy=PbeV04%NW8^baBVX$K(!YUt$F*j%wC7BD;x$sTrfBP}L+F(A!za-jI<<=f8}i z^Bj$V%eV5{JDz_H3aaWxR!Py;j?HEFX2Wjbb^Ol~#PP=9c5_QiXqdmflfIZoe5&n> z&}Ghx?S~`qjv_qu_2MJ6ZQlJBf8Ja8(Dt!C8F|fWW9V-8sw<22dG8@Zn7bi+$>@9E z@tEl9_rE>J>NQ8x8?5dNT0p)(C|Ja<;fjr9A)qrNt6{-Iw2O7 zSt-jcc&eI9p0~r#WPczHp=sWVqp5pa-F|aHKY;eiFU^*DJ6szhpIu*G(ds8;{#Jpb z%7%cilz5nd(R(I7m(vtCa);xB!M|7TaL00D??oF+!XK-^DI?-flNW!Dk{126564Bw zNB2HgfFt0EVK;TYtxsbpQb+BkqX{ExLS`2@Uw!c$5!AK^Ha;Z{o^81R!}h4&<0&v- z)ILQ_nY9yk;sFh*eQ+}>I#EaHxMZQkv0@m$25Cm;xYQI6|5$FZ_qEi`F$6Iw9v-t*uvQfsdeZ3m? z=Ih{Mn{Ki+!dM`~EDOqm(Wg=v8?|8F8u~{hLra0RyVgz}y{Qz>{I3{_uM@A8k-mh# zK%kNqA;bWDDf4`Ya^#ybnb~Uv#IGwB^0-dMrlJn?&xfqJ$BX`3F=w$F@g(}3_u007 zJsgY3ded_;r!fllaR0v078^BCQS9R}zlXLF&|X$5kK+76Or$5KS;Vt}>Gdr1^YfaS zhJam@({alMPOV(LuXdu*+a1Wc$NBq3O{~>eQNDXMPDzKlw*qVikO$nt_wK=HKPu*n zPP930@A+dU^W58H;IE zM#0=wR7NzDn}%)0Bh4CLf5}G%;!+Rb6tz9_I})kU4QO<6tjhx&5}E?9 z0x9z&5?B&-E+_9vn)|?5aWGEp>{Fjjpj?_0ZJN5;6e>!6%Kb3rxh`hF-p0Yd zk420TRn?C~EuEzP8_N zP9MI5WOe^2uRuMo|7xzeWf+-Ix`fN_du1MX>A7wmGHn8$LpD+CEbpK0#FFThJ>a-* z1sUBe8ImnUUJzqxh3SK2T15#Xx&pEKi!`xa^Gx>U-e9T#JBa4Y3_)*5-%?FlV9Z_G6O-)qR_ z#&N>b7Rz6UEQp`)K&RgBNRh=XHYsb%cWfShZYu6%YrW-S0_iHRx15yVpNJUG=Y=QD z=hdvR^VY%AV}hm%Io?(8%tG`9#jTzScqik7K6z0iJ`b-3}NPOyr;~@7oP;Yu^{v)kM zp^@E?X&4`Ehswv!?Bi}^uI;AI!YzhQy6>X!?n|VYTQ7Gvd3~lJx&dnB5e^C*fqG3S zxtZ{Ln1RZjw6MKJoxf43n#2OIfZ>jxsFRKs)Z`(xT;=1CVyGmPkK}w77M(%COX!~gh=^N^w zhiaXO#u)!*LR&;LJbE>L8eZ4VpHsAVPl{WW6h@s}E8)uRmkD3>rlq2(Ya?iW?+}yW zZC(s-$b7$zT>ROgTOUYR=ML7Jj#45uGh`fVdl;^Bc=p%UnI?=xIEa6?x^>z8lgRLF zw{*Sl=RfIwb^t!`JhU*v2wZeyTeL9G?{0O!9-8}_OxZl{ut+KVplyFAR!@&ePAfr! zF<;&oX%w>-r1bYQf{A57FDnffo zKEVq+`S{|g3gG?IYO>T&YLvb^Oqv|;2)1We+#uLKapGBOq#W6(4))kAHq-X*mhi*h zM=WjQeZnuU8{-6$DAet@9@utAoMPHr>v8l8CNv4t`mqi`Z>UHgN$P2 zR%+Y!f*O4P>N?@QZt8E$N1fSrRRtojzLWyB6Iy)^T^ofQz6JaRZXW^E)n<-T^H_{v zUSXcqL2tS*FQ6Or;6q7}8zka6ZSc-PNket!3xof^qC}0mEZN2;ZBwCX%3{3=j7nkX1mth6iN%xr2sB{P|p{TUkD zv#+t1)O>fFWq~lin^`AS!}bDE{O*=zdickRE|&I8Y;2j7eUX-CFmuAgX#DQPWM48$ zCLLF`OGS3-zO8vi8$V*S^@j}}x9N*J3-n$k*nL?ii5%tc>m4Z61hmglLIuoJ$5{%F zunwQol8YdRsq>#Ma$oD*T-($mp#@y+saYlXYzvbr&b;m$y{M4!{Am{y^?g%K7)K*z z-e8JqjW=g9<|XqXPqds7aSK;6fB32jvj_mLPO zPuG0W8PMSeG9tp?Eza@q*pd+zdn3R4+4;fG@dl09;wWyc*rYzHlN=vwUTN1_3-%ho z&jnWtOsIhyW*{!RQYC1~a z6X4JM=vAW%NFYhzf2@L~<&wpiWd7`6(rsF8l@E!Y_n(9tto7ezikMws#CJ(gAS90w zlL3LNAkeusv()-mTmc=Bb~p+Y!fAm^Qrm_gKQpCU2Hl{%VShdA*kG#6$U zLGJ^eF+2@HdRk6)lX_j9nr`f7BgCMav*?GMnVSoNbve+BW!sDT5q#<2hC6-NcCY_% z4S9$vV+wpF*qLkzI!wuSinZg)PnE|>x)w3aSs2o&8tJZ5zzrc&3dBeeVZflNkFvz@ zXcG*FZzSn&Fq#jY`{HrYu4icG-vRWa546OP4k@Sdigy5;UIos^TIQo9{FI*Ib`6#` z%vcamEnZcxHl4%*;Dl{EG{-uAMb~ASbXx!wdOV<{$R(GxUC_VlZ25K^_@o9^He0G* zglt$vz^$-D<^b-?9O2u*@ak*>g3~r+*0h7yEu#WW9hrB^4=LIQEt_PjMR$!tjGecF z#e6br@Yph|v>q!8rhk=6MQ&WKMV?7kz)uVau-R#^fr~0sDy9XP|ID>Cv=z=qn@q{b z2Pvzm`bu(Wz<*g`T#arRU~E)`v7i&b*Ulzs)7WU3ZfGWeh8ls~civl_GO*)#Uf$Ph z_U!$`mi=|R%1E!9kg*Np_UK2Lk7Xu%N}a21YrJ|zGX8CIsaEKh58p0a8t5^@^lc|Z z&SoyZx2`gddYxxawef$^65MkSX>Ui=V{L%SPVRY+!jvbe z6yJCXfkg~7ubH)h-6#kqgO*`2UpxU)3e7++EcUT**irwazu~bvla-APWKoQtO6B{n z9-gZ?T|)_($#&)t(>0iCEbZ}qX82r;_Jp1NO|!4rOtNDUYDzsn;T1%q)OxiKDkbUunTAXHpz81+aYo0vkuizV zqm3h4NRZ)bfBQlB_Qa1-ivcD+B?^ls#QgqbISs{pRYRO3zO1mB=;Zw8WoIlkqUy{ ztlt!wIFORd^X>=j;m1Abw(tSrz$rkl*TZQAm})#Iqjol>?y`BZbEcsAva*g@?5 zjtaR~&(gH363}9BpE>D!=xkh5OkhLhWsB9{8UMHO^4V}`4yg zexR4zQoE&?1XtxV`e8sm>ei4(C=i=RH@|$QzhjjrMdXmWq4$z zr=FavAj(!YFDQEszlYPBCNT)!nYw=@%l*Sf$M}iBNlj~b-#oMMVz%W6PU+M!1IZ=> z>Lv;t#NQ5+gDu4gyJAP+OI_N(ezw!c~sweWFr z*@eE^F!4OC@!R(gPZJ1;_%^dqpD2Eo0IYGDOnX{EP<02)yPPj*70@zE;?fG4h0Rr) zS?Hn)#07B0FwPvRK^7Y${OqX#bG_EO@VM46U)5%Eai#d9BbE^qsV`eRb$}A{;pijCl2ASM{!8fQG;fXOqkm!5&lzTE{M1J^6UvQQCRe#((Ixl1?-Df z+cOUUJ9;z#966}J-Z&2*3hSq{Jpi;GY>9k+I0en1r>ZGd1JvQ4^?|P?kR?`=6{GS) zO2DNO?xeq(EKnN$EoSM?NOOibRZ?e38CV%nHBP|6tz0y-`j*by|4crw3IbO^-tZIG z;Ppyvk~#aeXH?1#kAiP~8Y6tAL9L*h1S!w{!dT8utr?L(UwwzB(!=;UTrlasg-8^I zJ3_V)->RVc?u#UDVD9n-y|ySvBtQ5T)qV8~!g$9=^jbODrvzokfh+EZhinI@{sDed zPXlji2-y#M!qMEXM@$z_$n;KY3N9SHa+jfK14a)7cYa%2Jm&b{f)l!!8BT>$#Y6=| zQ%?a~S+?nXoz?0_1GY9XzWhNjLtheKhm>XkZVhT;d7W<-tfi)8WTvpbxksiQj=L(za;`RB*=mO$r^X76? znyJN0oEtEH8^iIeuxY>BjN8#;iJ)-$NO82R#CCycC(vw?#_G>^d>&5zoN2d5 zz8TlwZ>4p|Yy2&L>%hg~*xP@zLf(>Np3cu0;F3}viZhyXs=}mEZBe{%yZ&Mwpa1iv z_kQ$wyBMs_O7KHWQ*zkQ^jiCQnZ;#!~8XgtZ^w<2K*RRQ1W&$qH2cUjP2Dz7YVH%MY3>`rGnH!ArQLW33VnlHHF_5 z?F2fLg-m(e6&!W?=Frk06&(2V7mmn6zHHsDPN##;Dq2~qq3trXVj`uGLe> z=72>$jR{$t)i2LiBR%-QdS0YR%klRCwIp-|xw=6c3_Refn{oTQrI{++I~`kWoW$J7 zL63kkh<78TQToMPz!11wYLglo8i3oM&NRC?8tAcU@p=6Ta9IdB#qrVpj(~xiC|6@V zIYR})!)`poZo_$6P>M<$hsT18!%h4Px>^541W&Cx^CGpaglnxjlL7jz84WqbZbQI0 zA^v2l5(kbNjP4SEJnmED4iYDN1d5d&vN=5@0i5GFcMU+YRLCDO@{gZ@rv!;L!^{B@gRoFFrzI9L7CF8o+MoU~yV&B12ekSrA}X3| ziQ`qS4}qNZ53Gs<4dKJIq75M4h>lqiifRTQzsUoWx_Ce@fsh*&Z{Ula`^W)U~^lJQT=^K_=G!GWGS?&pN<4M&e`F0YqQ}ztghf;}G zdo+lTd+vA1U~X4^NyxE=Ig5U#U5xU!txVxdxl|Y{S5>#u-fj(ctZ~!oEB>XyNNcNj zZ0CeOXv7hZma;Q4qWf~qP8DMiwiVSYeNZH;5BvL+vQ#s{6>aDjs`E$K-Ws%fIyJ%a z!&WYxs2Xoxe%w+2)_zb?Pq!eG|21~Pn{O}f0Jy;`m%OzHU~i)0QI}hOgob`1mgzBf zq1_;MYwV*_{X$7DOJ~E_62cf3sxcz#+tw%b-gBtNk|~Ay5i`O3?JRK7)Htc2L-OI~ z?Z*Zhcn&F9?!h$5R)|Z27cWbvPn6htt7H%P;WunE>)0>z-br>}i!pFzn{nqgogdn4mN=m5f6OIP&+FVdnQndxwhk%FA6^PK1JRWYAfh1$vSgK(`T)y~VX*Ne+kwbCCVfpaQ02!jKQTnq~~vBb#>FVFSb)G_#oxF{A@5 z4a^OWuhfnIJN!zyg5d<{;G*pWytQ+M`{Y1uG;Rj*VScx|L%dq$Z5BN_lH?abU5b$aFnCJP@fEy5p|HJlo<=1 z@u%%7Tp4E^8U1US7!2RlPQ_0?PoD~8v6p6~c7MDKQTkH9BYq=bC89OEKCF}@VVjo$ zO%F*t@gbcY()-NQnBP8-yd!==z=W%PI+KJ>ECTl?ohDWC{m8F6Q1GF1WhWN=tq64k zw~9iqjh|hsK&Bd~OG%L4pRR8!zCc;BZ5t2~-?UNRAQI5Xxj# zg#Yb(+E=SGSK^NO`nj3pKlW8`{;!fM6$-`s74MN(>h~^m^TOE`{WFZ>mV#=^7^gk` zRd@CNu5RXxY1s&ae@3Id)MzOA<4M(gV!r&kPW7|y;r5!p#))=F$>B@0U!aa<9s0h+ zq-S}Xn!PWR`*rb+eqQtR;B)knTn0iQwtCHc{gh)Lhqu^F+_Hs+w6Fx`tY&*G0^8^5 z_dg+Tgoz?s;y3=q@KlJEWX}=WGb^SSW{%+BdL!G}+otNV@6#rT<*Ln9@Q60}H`z^v z#!0R)ZSA*A|D3zw9;nV-rFDxHg4g&5ZZ3I=ZdHdO1_Ngo$FU04@jq(_73uAjAGC5pDeYW47uuC1ud(vz-P&|C;pIAEcTka_a>7t_IQRK- z;YsKqWcx!?UP|_%jjKHCc9qLeGSkfColKc&^Ueb4bv^O^hwwJ;rtr1ZD=ZZyYfB*8 z&}ZbYf0Qgw#*8R)|L|ffec9Z0x0#|m*kv(V^nlA|z6Pf}vy!q_Jtr1OC+((^f#D5jH=UDMOXtu4sC#6vUGHL|8ctv}RZ*TOks z5D3gvZ4-e()0IN_Bk6JnJK68RtUS*bo;`AK6bB3 z%NtFM)PH{KEJbhNwQ8>+gUTBhm_oMY;$it=e7kkNrWnfzql%~{19V7x^s{|hn?*QC zVex8#Ijf*FHHNbu(Lz-0z#`}1fZAFZ5Aapf;U+uq(%HtA1u!>@hDJ>$meIZqiIc*E zUE?mUD?si@EE>Yd+d{UFAmpLtG35N7Qy^4UuQ^EXLfu~BltoE&M9XVBVf?VupB^6h zEWxHWa~Fa1bYe^u3Gq8(X8Fr3$S@^KU@_G|0$Zp66bz*Qkzz4?r3L#+2N-}Hib3LT zyg)BXDG8>j7nz25LO2hF6nPM9+!}}tun@zJ0$p$BcQRq(AJdWj=uclhj|E3yXwL{h zT7J>3|GeOI0Z!8Xr!h_CzwBX_B>Q{JYl3mtF)5+q5$XDSOGd^)Qrjq0DSXefsVMv( zJB5s$Sd^dt4T?&;K)i~tIS+njEk(;H%Gm3kLffsO&|Gc@IypueP1>cGi!y)O@(o|^ z+mVZ~*Q=?6Nh;Wgwcv3jlJt|EUMo_(U^LP4TYZI$+=3%YCttFi7RYGO?XVRosY_ap3hbj*r< z+9%O;(9t_98Cafu`Ey-0`f_%QX{e;$eY@!FIJ%E-v`R0+OkNtRp5_-(!zeIb!jJK* zvo57oNH<-ZDftTj2{O+wqBorOonq@6}#eG$?x_zG{e{x*9OM|I>kQb;ibNZJ3kS zz*IbPxS8a$Y7)FlCe3Spf9JyOwO_OlPhmGt{V^2 z;Fk}w22jIvj?!tbncUY7Ql+&gz9hv>hk973?*TDJ=I^+_?C3_g>C+?0W%ydcyr-md zu0Ud#gKvuCs&Lyz566w0TA_3vHh5VFMPlm7|NAbLVT5N`Kb2o@CatL4{G{B9buL3g zk2}L@)y-;=M(SZlERCCQU1Z^8BHX@(7PrXb*5V6V}&afwjlY?M+g7d zV(2%ORax$*bQbKU(-xe$Ufd1(?E>3yd#qZ{eQR?+;$)4pfB!NZ7!BiuLCJi7#5IOc z&H^a8*u3?cX~_=xy>aij-Zu$Dj4gCMn4IPQuygFJEqYYsOo+Ltl72C#M0JwQvql{M zsfYJ|5Vzvw@B?8EMXs#Fos5}HymYUXPPz_T`$Kazm(MI&W_qikx|$x@?wt4Aq4(>` z&m`LT*V}4%ha7xY_qi;)zc&S9Z(ZVl*_K=kC(OjG4mx!LQP5RFbj3K&?^-sCHfVGj zeBJl0rc7}H#m2I_gWp}1DioHLMnCDqGL=?DSi+tCR{5QE#@J!`zS?*qkWRG4Sa;i%A#F6}@=PEwBwqrB@gQVe#UaIUq+i}jd*&M}F2^A`A!2e-Q@gBZoVtr_*r}2r zQ&S3OKSN>Ae}Bbxe?PWG~!tivOgiWtb3J9 zKy&8mpO6j~0Mw~ojWLN6tq|Lg?pEhyAlAVZgfuMhpaet%sCS0EpeKCUATa(vTYF8Q z8T@dS%33H?Pao4FUR5Zo!>+IZ+DDP+WOToE)G~Co9kIr}^hw50tPMKEt;OwT(wSw} z{$pZ&vJ=G!h_{>QEgEo;;Y5-1RD0T-+G^T6O!grAxN4-bf*8=W>-u*=+eBV3@B>xO zh*`YS#+A(1K32<$S}TjY%M%%?1?0|eb@StnPzk75))sO zxst6gSa_47AFFNFD-5DSjgp|=Z}!Dt(YAG*8zIEe?*`H zei9O@a=O#XuC3uS30)Av&y%8O_yLkCQFzmelhfFk@xDkZmYFRe;s)~1w^oaS>4fO` zvb9%a7iNQ%tm3fK+r7!$j?MG~urPNofZ3J~diH>~O*6#QPCxA%BkA%YK`6##$kVBF zq6m4Iv&0o68ed`lAKt6tEpkiG4y0-#MSDrGqoxQ6I|Q+*TB(QQd$>h&f8av;qmiJ!UP zQGTt<~7p~?UbC!PodC~bGI!`_0T$uNQ#IDm#)`Vu)g{yJA&vk zdU<&BiUyGjp*HV%3917TciQHeRH$zoE%qrEYK|=QSV|dNGZNo9@!0n{$Awy2FxNh& zEtkZX?EFejbQR1**dztgU83QNe;~YkYtC3qj{9euz7}~zXy;#(A59FOd@j$Ocsk4C zTwu}Nm&6m@dW1l=Jjkafbp)rL+&4WVt{Q0wJu!5- zv8&MqK?`Lc$WEArU`18-Ub#yeGbTEq97Uk84QC)PL-lux=h!dl@p3$mos?eJTOy4* zS`}s7&D&*luQdyj?PvA$y#=-XeevC7rKUY&9XI{mo(9&{HrwxwaXNUO!lDNlGToQi zxF4&%#69l*k1WE;p2NMjrY+-7{g~JG8cl|$YXJm=GW@P^MirmSM2-;OucPU}y|p6w zT|{md2}8Pd0n{cxyWSZhdy{vP%Z4)dOQChof{r=`2iAemFdohm zEukO%4?-lD;l8+9H?f39|2Myi5Y58hhqAnk01FT+tEm?%M|9>S6eVbjX9R4lluN-SF<_AFVU3`QNuH5I=!QB;jfb(~VGDW@cdjJ(^CQiyu1JBEd zuL{aLa1)J64>O|gPsI@ZTQP`Dop4vl#-nBHAe1ngnl~rnPn9h4J9vq;5d_3)+I`(O zg%B`z|8?mhf1m{}=I{k?^{=ZoMsNZQie`ggiWccFWnf1Egt+4@!)C++@+djS_wO-= zV8UzV5Fw-VzU=Wa^WY22V^I^74&2quzoS!=VtW*dA{hv ze3lGT47{vx1tlW65)>gpMp1!8D0*dtb07A=@y3f7L8)Hpixi2WYl zbJaimw;#5;&y}^94QhPN6X`r`le0hkv#3wvIu~Yj1_oc^m8@j$>?7i{PSHs+b{tnqE-ab}6`^ zLLs{*8E@?4Q?TxYTTL{yli+G2Cw}-3_D$A7L^zp7} zy{6K({5qj)Ya7OHWSO5sbFsvpTWMb&Cs|WYp9g_{r~LNV-yO~7T+Z%IXY=nlKYj)` zT={rpY!oni?t3N|JAM`n(mh(R5E=B#;@AoNM~dE58ID z6die{H*h%SUV-v{Ch%gHmp$MUV!UyKei&LQsv1T=Gc(bRDDHC%mLC5_C-HHE5rZ>bou559Iw;=! zIL ze;M7kP2TH3jJROaCQeBi-3P!L zLXO&uQ28GLn#!|7zw5h)M=^!IJ;g2AOcTHq0#EtjGsOETKVk$g(Hi1y0G@7%_+A1L zf*h8taI=~oi{s}?;_*gza0ch!kcG(~b#{twWuU(if|?EBozhd4#BR&ScWp%<{)X)C z=_xvItl45p+;B5}5&L`DTOyWycA(0S+Ygm`W*yhc=p$h?^ez>%S4rPY$z>lrt*OX$ zE4{3^M zT??wjFo@v{Xk?VJs2>%XyvyC$q-e(1eRk?YeB|Sa8VvHZmq)H@|4Z~jKqu0sfBhGi zO@(2I)7>|F^0uEN(`)_Z#w)w>vItMCUUry1Wr&Na~%Kc@mH|q!}Jy9jj!#VtJ!atz}Xrep;MX6l*?w0Htnw;z$UkA?j07( zq%M9JFSGJ08v{MqKEBS)Zp&{_@6Lq@t~f%ej)DtdC^qYK@OD8^@VC|Xo36(^yWF2! z6HPfA*{1AF7qhbUp)j!WV^{ZA{Y$kPN+Lf~xTF#AKRaVD$DbM6&LPyI=$oqX96jyLUCs zqB*8TP?SNuen?ymLz7YaJ%J)C7_$4`?|IU_qK>-0&uu6$7j$u zOb%QJx*h6sH3Wwk&ST&y%lfBCHFJvz+_?sB28t_OY*!DY1y^ArAJ~ihiccP&7Q^XL zerT!b=I%T46z~S0Q?d9(*wLOM(j|&>*U|PwtxFZRnUk+jEJv0@PwMZA zu}w=q+gmAfckQA`;}F+Qd=02Ei%n$rZGjW<>xR_5kQaOj1}7KNa8Z6}y4oi9kt4VT{Zj3zAL8-<|_)C|+#X8Ep${dQfN z#v?Jv-nhD15L$0wD4I8dJ?^h~!dmvC7upf%L{EPP^_pplC0(P8fY?tCIf25@^!aNy zIymBl=ftgyHZLAvd4Umth;0+bhcB0xZcQS0?kPGfnG&`QDXV_j-VFKSmVf4?;1%c? zd!DcWVgwo5q}|8MuMVJs24WXsjMIdlZYkN~#ezSSm#ydU>~$V0wzEGW&LdvGsh|H6 zN|D=?`%ul83Kff|O9mKdPJjAp6A|b^ZO=DetbHRV6*|i$uJ?oQcEBws zCu~k?DQ-6(*I0U$sDj#I{i|J_sA?(D^!&wjy(5dDD2{L^Org^ev|s|xLfDh_=!<_% z2|OjT2MO)%S_Czr2}b<2YSznD+spFVhz3LFAA=t|z77?B|5{UTz4i$#OqhN#dlC0x z-+JFb7()qEDcj)}Z4NSw*pp(qN_U`&G4fvo?fQ{(u2w2b{GDPknu$SZl3~)>a`pZ# zacPQ?5RqGU1Oh0Q9qHgS+hoqm5UC0cgI!mHM}=0M$K8(c`_5E2+oHIv_lK{v<7iEl zo0P|3-6#gZ{wUcNLw{EuI0gkfbfGsU$_p@cs1qG2hT7m!-JcEDcNP0|MVGLOX9ZE% zeH!ezi|)H4v=T{6GM6spL~ACt)pN zGd5YYQuM-!Rj-v&`KO}hYu4WAVns#vqv;0_8DS!I+pgVk8qH-GAJsielvISR9p)v)X$j);lp!hCOokQx(+g zg``mKr1n2ay&&w@ogD6nQ+HZRoKK2JIFeEgb)8E0R;b@3gy zxy?>E?{Q=@titFiUsy~JsUFo8i1g?RsA7m78%kr}-W)3Gc7Ge?H4?vX#HJKH!uB0Q z_o>n!rC+m?JtJ!QHKnN;&B6V`$7}AI9P;6TqJ8vOM#DVRTOS`%hXw+z1&xWRX>dcNithCvE z>33L&c1x6{It$ z3Hf`$>Y6=WvCFzITCmM+z>1ksqQnh)jXA>}Q4sOONBE(pyY(#FU7LMAm;0vnC#=Y-T5INKELPd|K18Dp1LsB zVNWP`IpN3z9+LH7!Q~-7OYllNXiS1mkgVZ`aKe^e@Vzi^MV)ghtv_2h{9KA~oR}p2 z*u$wE?WEj5yE|&~=X!0rAwxeGON=v-I*+(p{3P(Dt?gkTA4_fO@7unWgEU+wS5f=uiQw3AoLJ`;=D%wE!;H9ZF_G6s?9vP+L+w9aJXWkab!$X4k!595X5$Du5@PSCKH89}CHMp;2 z)1v*;F>UWNkF}43mf!zl6qzh`tlJm4OVRT`(81t>QGTj!=`2|3`Z9FCpbu0E8{x*z1l1xU(zRVbgs$d3;q=JqjkEjY(i@&WuEz`H@}dQU}NH=RIejsg|dlm+Bz2g z@3r+y=#N(n+xnZm5lgIkHor5`4X!ZqlpQ4)aq4_!j{GS0H`f(;eQbUz_w3dFxLEQA z9v+j}IdP;4eZupc@mw4ha9u`XOFhlXE*CoIq{}QhnHZ{H7PS;_A$l%P)c&GUc6*!b zg?V+m7$c|j=J9N^yz>eRCtEYy!1%UQ0j=4 zN=>&pnv5^9z?dur2vsL3$PAwUbIuSe=Lp8J1%E7(#||PbU^3=Mn5%$&^=CevxIH;R zWWc=(XRS$Jb;|2zIHlIGWd&-lH2m={JATo&a(E$*RR3zHU;g8lDo03Sr^s_y$PgSg zjO3B(QZ1Wj|FZ0%K$kp@8K>$kbaBdnIg69j#lhv3i~ShvD%4L&e3a?qnSI5}M~6nU z5Gf423ks3>Ny$MkyF%pz9J2uRb@XHt>k#dSX?e|nJjR) z$}xYeUItG2-$j?SEQgO6o^T%Ci)&Gmiekn8~c3nEpQ>6dQY-$1i3VaXM}ug$GO_~>Hnp)V6@(uUnK=pRUdg`6$>Ox~I_ z{30FP5$qO;6vto0h!v!=S>uJN!Cd)u!=pC7jCuqdml&P?l0A%WiOl(W_v7@5bCv!e zJ?Rr$(s%6fMa|z^R!^VNPu@*CB~S=nxQQtTPTMpJNTn8bKS{Pn_8 zxVHR=?!Ngj@UV1~^mt02=ftOayf(0hc5EO&Nuv`8zMo_7YpbI(A_*oo3 zqa)JMF6i?=2~MD@K|!=jRL<&_qbgf}to?_D+pAHYiJ*C|Mwd)#WU&BhA4TToUMi$F zo=X!M_iuqqDQI3A`^$O`$AsI_D&T(k;ynJ;WO8ZI^iC32tlH$B*<6Iwjn5K$DZMY( zlQvK8W?OgCV>jz&1=eY!3ukzGCyQd(|VXse&4fJG^|C3v3xfOJRCx%AQ6R>Kek@1&hOyK zDFSEE`e`8;z)fWh{sdPhXbN658YE#5L>n9k@($mM0IRkK3oJ1KZ3gm1o52|SbGz`% zQ2(%Ay_foxA8PG)->FpZ!)nDnMehF4|0rY#a2O5{7^`vLChJuGH|2Wh4W4|i8|o|= zk_~CO4_5x1dL{BG_(~_D#-&iww0HJ9Y_K9dC54#I(Xv)P6XTOS@7a({9P7g2JKf@x zH|BBJ8U7ZYcLdz7Y^1V%a;+)1IB$~3m0wUazm?+-Y}5)R$3NR)_g8FGeo3<`Y=;Ug z_WlRehcvH^vHzf3uMz_OHvd|F8a=Jeu_GlnK&o3WW_gb98f|Y7@LGXr>@($DE7^W` zgMkSq!H<$%-&-!A`+d>vjI2WBA3uS;Bzd53@|{d9XYj-?_m7>^T0F5Trw#jt_e6m2 zxjY#3nnh<~#kkWp5e> zG$)$s7t(S)G51-I8ltx;GpFcrRaZX(b^_++jT8K45Ej_1gg9_Ug)V~CR=E(V6V(rG z4uKf2wxgml;IqZg1_#?q{%&Fsi$6zZPB&zwLJsKLp`NE^(MV_g&XS`J0xJtoBekC& zn%m;)zWPep&ea4`c$`BO&XAE~#GPZ&K~>B~W??mTQgz{&YoZ`U+aD+d=aNCyxYGDK zjJPu9XT_${=aON@WUM0h`+A6pSI@7(E3uT4g=1F(Vptpuo!(Z`di?mK;pe^R`nPX1 znaIe}5?31JJ@uyyuXcWk?&~*JeAzYxa}#*!LyVE>BZt{*tpE35nb9PzQViRWj!#wRIZ#IS5Ylf9Sau1eg0IAMGIixf}WJ5NF|=D z1WvcknjMFb)8i6Gunf}V6p~9DK=`75Q9&7}ZJvy~-5g3GEYf~A2YhBUuw*@TShF%s`|q(R*v`e# zq@~b}#V8&qxrH1DdClB?R??4m{gW{*Lgc5Rwgl+JQg9THv zeX^OP!1W;ME6w;{;l0XcbSZ zzi_tuUn($AgDm)sMesQot({nK$gR@d`H!bVu3 zoTdEW8PJ=+!alI&8tV4-Jq6E|Ce~a2UyJ=^+Sxmj>Y8JLW<6=(rN2RsGd<9tFWM_Y&+wgk?QZmB0~9PF z?Le0Q_EG8b@hD&ZpYX*K&`_d8|LDa*5a_>hK=F`W3<}p6#Jo?CZF+P!%fefLy3dC9$a~utsQUth|J8krVqCJJ zQp9z3HHiR_n)8!K2!pd&d#fn(#MxJF6fGKO^d5tKA>kj7IxFiapSx4sYv}L&EA#&C z_>S-o)}6D`5&5Si%$C2ce-le$XtcK{jz9j&J11pJ#eL1?sEMOQD52wiYXVWlNhF)H z)u`XYk&y0Hf9>>T+{?0Slh@7mu7>QnngYZmI<$CuzxQ-^uME?Gu)l;oaQpzkcR3kCo5y&_wDmxoU+Bt??Xz(YgfNwW< z=7b0*K>k-vKjQPl4*#OryCRn;hQUd`-%{%8*cJ52DEIR zEIwxs=8An~EnFo9%1=_D{7fEo*mOMc_87cp|AY!)oWSS)=$3p$k|sYvfsE7}zK1cZ zG8gxK#V4-JXCd7%RBxt97x-hmmX^D?aLMKoXPl%!W~}pQN$auiWJBpZ<2{mmWs)Y! z|04pR${#J42>rZoy{yxtQ#LZiKl5XJcWfmX>bdyj3s=(}{Dc=orp*_hV0hcFFQS=p zB_5hYXIDc##R{|jDVtx7f*_mk=KN06$lK(EVVc4Vn0vQ3@7_rb*JDKfBF(q{GSRGWcMTmseiJ zXtRfzprexb9tjVLpIj$dF-LH_%k#u!^eE&;&D9w(84JaZ=lnb2eYyCv))aXFW5n;f zN~PBw?VQccE?c3WPKy2gktThLQuHD%=>qi7yNM#flV9W}7Axo?Oc#YwcbaK_K(-d) zHRu%HCu-yVBah|gW(Z3c&PAH^`U%lAM%W~5Z2=_nKa1#sKtWfR_FLgZZL}j6%}kLu zZ&lglIDJ(ukMk6{OcD0}(ZmE+7@VFU5EV;bNUxa7&Izk2@@dF4D}4-o7FVnIDabTg z5+l=sr7$BC^QrW8|o=`1@R)HNK)I%NtNBK6)DT7FD0xCx$a`zVG7T z>nUFG5Lw`oIv1^N%oNE$C2A?V-Ol)g^UR_o=T&Ke$juQa<7cQ;M0QJSKys>OvP?0& zmf=2etGXCnPqH~@aQNf%^G^G5YNXAjh_j1U^8+il z7u}rZ?>uSa{XZI)GOXH-ew!{<^ZKwbb?s7%Z*%37;lUhBG|D9LA3AB~&i!-AHL@hK zCNz(_Kqzr5`upG+JXCdf4O#Cn95k=(*q_nxo%4}~T-#MS(I=xj8uUAIi zO)R${$y0VyzCiu^YMW8O{_$=}F*1IdzfoF66gb{~kgk#x*WL#|mC2N7LH5lQjPw6_!aTGL$x|@vx2cKb^$Z=;-NyZMMn`DjP#Q zpQ2oPg8?0#2;jSXgcNnSJ%{8{P=sx-oY?lku%>@wp^&g6NTQZwJW=P_ff9dnGdI1} z(ZZLPZeG}-mt;sUfvsh;#^_XDxajqV~GxXgs1SM0YK z$bfj_8%<(F5eIBmfsPvVa`aH2kwnbNGn4_pniy}NY0dXv)onUVgVGSFQObZJ$Avygm}#WGQt~`_+2wIRxDGQ zs=F)yv0t4wn|{g?t_sJ%zRt9K@K*K=zX@IV;;PQ?I;;$bG|}n;I6zO&)4KO z?R`*(ijoc0m9E-e3)`uEl-0d3zRjRTUR*NvtnU8V!5>E^z z{H6b>SmEr%7pK{TwqE*;(#&I0NuJCPW^hy$8wlVdVJwk*Ry(18 zP3wBrC9JKWj`3A`Wn+-t7@0uNX`Ew{A*bZ9?6agReXxk=6kf4co$AvHHUy9oa`Z>0n#JcP3rWCA&=|^n-4hNU2p2$+pP#K4dYcH0*Lm9F z{n(*y-;f7aMez-f8y6;u$C^pQ!O@DSP8v`a@L)*4+~bssug{)VPtJ&_&xiUQ;aNgo z1};XbEQ==^97FC1_&02kho74%|0Kz8{2+~nk96cwNw-(OHJs&HrONQ`!s6GHYW5=T zUUogWB75`s*xC7raHl23_Z(02`^Tq)^5ojLwu93q7*jRrD_pqwu|nIp-4IHWCvXJx zbl|rElh!|YZvkJARIUKOFX%(57?2=NnhmmiByOqSJ<~C9;&rble^4PdQ!VX(EEwCh z3>PEYP@1|B0OMC%Db@#pHW!A`+l;E)%>tph(pygauW1}OgJD;xAY7@ni$K!MVZU!j^4J{h=CybVlHbp;PY~rYApMQA23^wiSv*f8Zb%G z{JiFL_&x=%#+Q33)S&KL|4+kZj_*59m?Unv1HRh(QB2{MCgdXHr#Jr4MvsYE{DIa4 z$D`lBMGI1>ur>evW=d=NFF9Th!kWWn81LGoJn z2~Vw?R+sf9y>q0F>RShnNh%-f!53B9s3I>vCXxi+<1IGyWk_FN9z+9UcIZU+@1B+# z-8(;#6*Z9jV67F1{t>3?Ds=o?6x%OZ{yk%jr9T!8ZtBPA7xuxS%k;Ci0wMhg&m@U2 zG`L^lkQJYpYOhsFNCWfiTHRTa1)JoKmhMPZ3Hymb<3Uc4$2VUyX?>uJK2t3*r( zcTqIDX*2={6GS@)n?!5G#&2S7)uLND`&u^T!1 z$e@n!@K8}M_t3Br-|ZIe`rmams#-eul_MV-pU%q2e-eeqmj1f=@tFppB<7o3YmLKS+uSGi-fcE5p*k~E zOyf^tWE6qGF^gz)(N^Y0xwm~vr9n*-{!1~F#o&v}9kL;jCvC4bR2gfvMw{R(X&x1d-V+~fi61|6+|rpLaUOJxL15C*sUix})<0bP4%Unm-- z6`Acqgir7;EL!2W2S;c?3+z`MN|C^u^F(RLy;cFhY&WHQL@n&BQ zVo3zL>xNNsdl`W~;3b|I*uCfhS?#T!!3^o|&V}Zs5KARc+h=+5aY^6xfB7*-=&Q6a9v6CN|`t&h^)d);_A>f>fKKi&_5$Xd*) zZp&Q*G~G^FQ|8)ie3|aal_Z}nmVraypT(;xqThJmyw$jwo2Ygh<~LA=;ZK`*qjQHyqDZ{Vp zX7fZY+SQhLo)0VZlO1g;V6>QYkVQC*8&1Wn%2i@ArXEEF~I_gW0k4R5Gdkh9- zHl!2>Z||RRO@x8{-7}>QH;L&ZlS@7zd^1(%RIaW%p%H420Z)pone+z03>YRXu{t8I z*b&5}EnD*_@xOr#>^Q(#oQ-a*A5$P(jbPW@2<7@2G1aY!nS7s1;lT{h%mN3ib-|-R zZyWSx?g$dH@foLd(Ypoa-(G$ch~QUdJCO^SAQoDaFH+#ujUJ|i`HUcN7G$~^l3ZtygxZu`? z&<`9)@q%pL)_sjQqyrNHbh!HT^1~}y=o88Wq|`{pl1^QFIs~#cqu1ZgB5pCamhR`2}X!FD{ekv2+xH4XYg68!garlYS)yVUdMIhMBWtkU#m47hDqT0cIf*LUMufP6FuS}0Sb5T+ir2#b&sc6jt zFn%n6{?um>A{1o9(t2@Sav^*C-UXl7=0zwda2#Pw>REoSG#_ZQ)W!p0YH@s{NYd+? z6`>Ycn|iK@h8_ZlJv+xMD>Vb(q`Qe5;sE>|^SU_{r{Ahg^qAZb{ar@&!|tohg~bK! zRp`_`xB|-i)(lt5IuF^&VgK&oslMfG-(T2Je0EcIYKFyH`>pW`Z3pNLCh^|%Q39U= zTFcmHwk!sgAEA=>GvVn2_Zzt&^Rxa_*$(|@o0Ks!LGP|BOT`a$xNnki ze_f@3(q4Ge?mQ#(URN?upreA(i`XhJ?%A7^iSCey$gHH+R;8Kwpp^e-K{;!hAy6J3m3E15Vn+;cVa^qK3c0?ltU= zlA%S4$q`G{u*Lqq?=p~(r|GL^3)TR9nXOk)(U|v1YJuW^EkSqa>UvViA9xB3p$xG7 zNa!M7`pG!Vrzye@t5p-4AJ?;U%4f zbUM1aS-du?K^2Y&ZWZMZ0DVKdO7=UuCAM;Rh8$idz^=)#JW{ga1F7J2FkUQ|KGZ>X zS~T)ilAx4LN`weA7}$ps^cH$lvGA-0Tc8Ge+Z|E%RzkPsBZob;uMVI`F2LxUJZ`@Y zy$B`?Rlckx)xSff1EzdgDu2#8-*E6)pM`Hi|Nd3VnW8)IP66=fiztcr-*`qZ3-mY6 zfS6H#y~rcq@q2d!&pPT_=RnF8yrAkJh6wA7D+CkTk@|q`fnPVKa1W7Se-|W>3Y1$RKD7i{VJ5l~emJDdIs551t9!2c zsCVL3Id$GC$@rmEL`0)!`xc^g|MSn%#{KS2??@!h0tZA-fVC4Mj$?x1xwD$#FF9J> zVZyzUQD(p_dS`5iRYU3VXLNEjkodyYMW^;NjpY-rq-(&Znu5OEBet{hJh@+LRcXWV zGLfc298uhv@0d+-Y(>OLQw)DxS_z3uYOtMLUu{GuTogCy9y6PKzll3KK4?^{N@Q#_ zeQMeG)&C*9&nxZ8lw<_9U$kHENpfSdySihZ#d>#p0Z1$GvFmgvDbaD0gL;YGf|{?# zLy!_y2P$e}U;&@Pid7JxPb`Ti{Z8pP^GVSM>kX0{C@y5@(Y+HeQXKCd(XNbp`O5tX z*!@(&Qx7e;3{jweg3<$UJr-D8RqPW*(1Ty~M)t0iM`%Gk+~3)tRP6hrQ^`63_S5m` zVQlCXHmisWEYMtA2O3Byx38_GF-;XY%mk#6@7$Z=G(8;VWVTw!4iVi#BkYNzDdfuc z*LD-;dskTH1F!sxn!e(cf`s2et6|TH%xAP;N?_tdiB#I&Ijt!9Ay66#4sNz}QnA1L zBF9-`EBDS=GqdgQMoucf*FTr^Y65E^re=X;8~SSkdR;uWMt*K}4*UcN0&Zlml}j{% zLMgDbpu5|vja3f?Ok)Mwh`hd{+2h@Kc#mVcZ&%+H_C{?N?da1c0UA04$5Xs!R*=a~ z#jB4!&v=b;jbwww7)maSe|IKF7`kslUhAp5RB>;RI)9TCcjmUH0eFA$F04dvcHpyh zUt=g`nd!y73UyC)0f(ezGEZKnc457#`5X`kYQ*T1^UzO%Ry6zx>CbxhxH-&sX|V%m zJ}9U>IUMbFH8VB&!9pkdmZl!Qoq=0j?P+Kvf`+LJmNyKaTlt2$j}`&sIQ4r~NYLOF*myzE1#ML1o!9H2e6 z^e^UW??nziDunPDKYH(gizx0hRq04?XBHe#{B>$!zVO|!XJcpwkye~xI=6&74CYu0 zyJ53__!OZWr4`^%N9=a`>|ye5jO#dlG~tya@?SRdTvd=a;;D?~jZ9AC$zajc=FGwl zq1KF&d44GQL}=1b--iBl+zb3W z6YBrE81}3Q?oopIWWkK<(EJc6YJ^p%c^nF7_v5vH^Z6{5QmD;H<(J%NR2H$Pol^8y zT`D{1U%P$HI&f7nDh8Isbk5UYB)Vsrz1*T_KHaLbz=-?x!J88R1^ z0;#aXId=qrFrr|rBvh(=>h=T-=`$AhI?hd|NE(*I1Q42CVYql4nFv49Ta?80^-RcJ z@=J5f&>d?6Y05_A8;I^>RB2h)J`s8n#5;t8c4T0Q&{NMg!jKGX?0pBpHcpZH z%ZBENE=8NGfX38MB?-CdnM_8eGdvf88uBGhaG7E=?$s0(?qTe?>St{Ro{E` zZ@w+({bZ$UXM6k^OJ$eSQBw|EciB$c4-YY^{c_CjX=)dk6D9c1n$=e)=$TE{P)=CR5I(k;P_| z;{DBhWWh$dkJCW-gGiDRj=lTu_>V81)uEDsSY&@H*E<@-V%qGkkX##g7m}fGlIE_g z(Z-|@iAi^k^>}K!;a&P6hp#mFbP?kj|9I3N{VB!lWZmhF`AVGZhCUV_7IIi1SrZFm z|B{_ncQU3KC+bGBuwH6qU&cVBMKta3ypvX!&(u3Jvp^G)G{{%6uSe*Wk|CLY(}TU? z^A&I>gQ@)kO3$fcaqUAuv+V-C`1i+wSNJ?3xs@6{G=s%Xdt*j7?4~ zR}93)Z!m1{nd?0Z$>7}U&>(jU3vVkbYsU@j-0J7_W%*X;=O=U-@pTp_$jHc4_zkt} zQe#k&8N(!hPtMF3V10MqMj@*_j?c;>t*CX9N7U~x(T2t*n&W7nUo0*#OX9G$<|8`* z_J?zL*S4IgS%Il?TTC#eI>yL$o_F;H^Js^-{;jv!rVS8|obvK65F<=!uIUylR-7SD zZqlhn9iT41{N-d<8EPh|9vBi}qRTF0Jsq&WzfYH5sq!mNv~ug%V#w#BznPFDd%(^X z+_pTEac|JcRV=SOhw8Me{%^4Clb-vH=ij{POMUR=&^6sfeb?}+A6YC>K=y`(nx^0W zhX#}*wbf0vb-t<1c(UlDW11Y{^g{-5$?G70rll8ZKn}oAzmB}s9e`DF^tF`R3eBS| zUkXSi?6y7UrHtF8q)Bs?mt8|5X%PEBZc%J-4@V)!s&TmMbu_vYyxh#QEWLA!{ozL1HMK7HgVAUSMbG4%PX)+#{hMNt`NHK2|3!QmSDIyKE z7)l$m{rZ*i1joaj-axpcNy1Rz0Qv$P*R0kXmk}KWXS>E5xemHc`;t?JVHQ;r>JFyCw zCAi-E%!Zj};obf>3LHx6xmArL<=Bt>zvdsV`TANB2Xh0trno5GXMg@bK z8?LN2CD{YjUkch_)d9g=e=F;QIGvY)hO*|&!t?Oql7S|w8+3x+iNk8Sf>q?3oo(mx z8TeevkNuee%(L}{YErH8ji~N8KqD9kb1`x2f_EWbu{_6`;PB$SA4%MP|+_6d)7Fut0XT2I|~XCWC=o?6PxFgmL!CzE4S4SGDJd9*U>cvNmsdZr$0_(%&#i zs0>xD8)?8L zi)!;r3Rf)C|Kp7K;|F}Z}+_3bvVj6`;nSyRz%C>*ro=isc$DTpLx8AH(_J%-n zzxf8YjXIN8>~~@s_HBLF_QU8v3NAG9yCpjfvm$T&3dsYL_2=eD>u};)Qi0*E6eg&i z5?L4;Eep%CNP4+?=_Mp-zbdIJP5X5PwfKS~fw(nOX$##SH(9kE;}>RvFS<9oKMVA~ zUsO+%DTaLzNWK3h%#!@D_-ejAFfaXN>19yUtQ}8$Ch+*!do#TFpykC_gXIoW^Jg}B z!lK{z>~>?n@%}wm^07QirYL|GH{{bW&7DM8z z55DZmhOTg~?fwNlnlBMzsFg?*2$!E;%qO?`9cyzB8t$!=|0hz`uUUFeCZ?$sAEB-> z8tc$_H`65WTK};sXct9ew-MfivL1ewL^Rk(9GdQXA}kEqQ(g?#C+|*8o?iCD^7@XZISuT!te{d=eyAy zee7y=%W&wpfs%z*a{jLweMCcaeuqQq!Bh(N-cB8&g+>u_r2c9yV6)IHAQXFgf}^9%1#H2i>cS zlivEi50BH&51g)385I3WD^OOH?-n%art9s*i@LBJD>{#vIt`1!<;nT$+~b;K>UO!J zLd;dQDWcJJju2|PKfwgF^fl4tm29YzrUFcM&Ux1^8E!**vQO~Cj0lp8K#d?~1+Auw|K(d88 zaT6jl9Q9XALlDqGHa(^4~Xf~NPFVHZqm$iR;ct{!Ooj#IbS^=E_nT>)c71yPqFtj zbDPVjbkX<=Eu*AvT$UYBM2whrx?skD`Rv4GgHZd`_E&#VjunIBMQcT3q`^okD%9g& zJ!dnAt~;Gg>GrVq!#n$)55H85IP#7)1qIUvUm87^T|N?A-T2uYchw#)W-Z(Mgs3Bi z(s$`58bfeOK7O|*14Q+<=L1m zh0D#5pMGAgOx*;+rr7~Y*3&b|M(p~5+rL%y!1WG{J*slOA>tVd!@))%9zQrSqev~8 zd#lCdQA;B;MIDlLexNY0mCl>|ZlJ+2-!rHC>B=xB@kwxZs)Ye(;s@L{;wg7f{*}ew zxLBhb5~Y&zhL58S!K${_;R#r!i$vBu%Rg;5yJKdzF%e!3x%kmAa`fhbajC~@%F9nP za#$XNdT5Y7YmWT(+QcsvP{m$H{giO}oe}nGORIQLRm5g~Zr<0Z)9UBecz#a=Tphx z0J?*(+xRD$Z!Auu_6k#Qq-2<*1~F)r>7pi9jDu_;$EX>H%=eV(J2?UI#@H# zxlas}FbPMsTrBTXV&=LxrR~0K?pVSLs`M-HZ5(Ci27WwVhzc2+9F;1H|H{C*||9+eWOw>v!7X*uH?5=0Z1>)H#lr2!E>sUpyp;;{|A$AVIrSoqCwT~ zgC1s-)mf1PXY993{3+;OSvs&4{Ut2v_Tm=AsHS<#3U7Yq(puSOOcsRrGy0onVJdDi zur}D4Feth(GV(;4MR|&p*z)=_9*X{#)@rQcv^74j31LEmjblvObdRx4d`w6X{*U;B zdY`Q!ML{s(qG7{n2JLkIItY=OUESN!^P#X7Ibh)x4o2`mq+&$ED-Gf#3XzA3AiCf$ zb%vrDc~_fmw%6_TLh@xdN)7?P0<5$GZ|(mU>%0ygru-01lTjo-C2F@9Pj<5UFrqQk z^&8*4jAsPfiQIR8Oa{G{On|3{l~#dzp;sP9QlcNEO38(6B~>KE+RMiz=BI<2;bO3? zC-QrxRo0ALA=I6CtpN7+Fbf9JTKi73?=8_BDi&8u61G~a7vYZISNN=D#>JyrK@ozA zkq=cP5h9QbI&Ire6l`y&glDmYhW6g_dr48#AiYDqtxC`U%}rId258FXXbK~be{8XT z`(+or&x2ol`GX!V3Xbp^yejx^@8Ua@po+FB^U?^x05rkBB*q4Uf+jE(neroyQJhT! zZE>v~KhaBYx_hQi?^|GNtNJP!`R#sF(GzCpwaZQ2r!U%AE3K$}-}w5?ekwgiRhGfn zSTy_+`kK`Hp6^fa?~sqyZ$`@YlBy`%9K8L?Ufe%DJ!7Y$NuA3IdYxo1TCKc{vL?XI z#UY)fe>A;@W3(21ul7nKjEJK{DEUYJzF>*~8j!+6fs;@Kao=<*p;`Aw$_EQkg|>Ph z-bT8mvFTqvsz>Nr6TVI3Xi0E9uj!;ZK9*0k>NT^L<3cCaltKk%`DP7;C|<_uRGeLoKtaf(jlMV$EPL*KD3UlBGJf$G=|4MWr~6| zA;T;KT48u5|7wao70gkanj=IlU0oCDKHn_;?a1K?2aHmD;e`;HApLJsKjDWH5#~8# z;>xRPp1f)Dd+1OJ;jX#^)n&~4#L?&TMf8UG-seT8I6FdDSp8`2$n1N6b;pD3;4v_rgh-utWZTD1Bu;~4AQX; zGanXZqGK*82F%*o6`yEAdr^m^hiywy+$Qw8*e)kX?}rNZt5lCdjkNT-nT6+)os!L4-&G%Qbntxe5imFM&G0N^F>8rlTa(%@L z<@<{BZ|BR=g*|XJs-k~0f(SFb1*(wEzK<<3m!^>L`K?n|G?|Bxlief5nXrKP_JT1p^?yclp_`^D%zErIfBIgQ(Q8j>Kw=^y7f)Z*I5AGmXzhsT%zSBDn-C%P^5OCs z%!ngLBUyNY(oaM4DUMJdV<$&v?ZdqNv-S{3j7P-!x=3CqZ>MEoW93k9%uYLsLOLfR zHN&S!5cG(1F3v|Q+aJz|il>QI!qb-g#QAo@rDR+}lj#}`my4r?mGH(|xMq<+CecD0 z$Lz;P3OME{Z4nKf0BL=F{hs2eXpf2-G7p^^9OE|%mhe%H-QoB_2_(kEGI^WBeyoHq z)Rdk?s5B6Krg#!8&?9aigXLfK;km_P+4#(+mJ_FX%l$s;C~~ldb~j@wHMwp#CP7}m z#yYj`hOLD>wnZf&(WmpoqG*;%+kM5jN~B@4q7tUjy?9hE7sayHm}m7I60E`-qi$la zYFLG^c+@VDCbgYB(|Adody%~NnV$Ve@bNPt^J5|1JDoJ;MHnf3Y8GyF|Jg}DI=P~q>45A+b!ZR;153Hr_Jq$gf&l!RUf}n{Z43hfF0q}ch za%GJ!igD6$CJ1N80#us%xmE-qb4s=UA(y#lO@!%I4}ecm3L}O9VOD;5?!TQgFR67? z$@VatKA>!ivrehnWy@k<+rna%!?XI_tJM9LK zoSwYK8`cAlF|Uw?M`9)NK`{^`fklNJd=>SmC03Vb6h!zapFwvNu2fdY437v6K>Wwd199J3h#!(G`VANr{ne z46Ydpxbpe}ALP<{^w6f#|5Z{CTBSS~5{ejL0*uczv4mjp>F0shKjXzET?@6E-IKXW zDPe*{dk(NnikS~GK{*-%C&a_%keC+%=ra3p`9SY2LWmtcIMSk4KaY|K?~xZI7S7T>bB;kliHh>Z{O9{ zQ0GaW=sG=v8B5^_A^b4HNvTxjn1P7%`cY#x3q#y01@cTi@r2N%*>WWDui`Fkp6+O= zYHwaPt+i?S7KLnQDiITVN=XkZ?jcK1tb<}!!c9I?U0vgqq|aKTo?vxNjR?c&i&Fca z2-7u!Abu>L?%1ty#gczEH7t8-*^-6dBMuK_=8%=jGjqs{FbD!akxU(0EH$6YHYGfo zR2GxtlQB85EGEaK$`Usyr4=Nk+W^V9_jqM0Ha-y(6O%DHF&UE+6EQI%m5`GP`P`z? zn%0jRyLA}R4N@Y{CN@Kw{P3idFSrzK+tJ#v{nADAzuUB8$(1XXE&O>)w0Fh;Cx;y zRtg2E_6}{*zSDHQTsEugV7B9Jxdq&b(2lO_>(2^jq1jweW z0BHnpFtoXN3ab_%+yiZVei5|y#|YyIG8w=H(CMe1MDJ*-A|^r`lgC%H&J}|wGKywu z53j|%!x4a+Ou69@&ev%}NY8ca+F-3F3RzYmXmkYf`w}1u2+IhyZ-&8r?}kBsRd1RW zn;!FdRqNw_=nQjNm!#7j<^7-69r?%@r&5L+;K+T84RdrT1MoS*SVwyD==-nYz}c_h*biREPnLX$rocy?Szm%s@5QjF zki#uaOW+ELP0Ig=0p=K(0uVy24j=*(L(%F-<3H{Brq;nD$SnzQ(Q2ePS^;2*8GU~> zfYE&vCS!yU?*OPZ0Ul?70C=qE-!YI^1Q7}ILCHwqo_;40{lLthLPUS;)4!EObP_ZF zfrxywokX89<`a=nO8L+#>QW=_IJB4D>m~cZ0}Ku-B7s$>6^;#}n(@rOQa@^ytIeov z#mr9;(Zqg^Z5$+E-y>$6l; zR0!e4@71!P64eC5kwPv~VQ#Y(%NAa`bn(J@U0t1p;=Z=EwqzDBod0rHTjP-~@v$Z) zm|sjMErbYzFr1i3Ri0m}G+E8qr@pbq6A1`8(v&5LNHLUfB`$4iZS_l@3utd`db%tT zd&P0xr02Sx=Xoj5i+PDe;=p3LU0q$#yt#80EnF~f>4N!l7tfzRcZG_gndTPSBa>3B z7JfA6gQW}S&s)4;{@le2=FeTcaQ?g{^X7iEsI#*pT&XRc*gXtZ%(^Xg$}9GyL%+_Y z+xA<&e97$YuC7R_US2t!PIGH(OLob^c{i&dH&GC+P@3S;UM7O_c|V+9UQu(>FxyfQ zky45s&yklEa$Qv&Eh|o1v24-*tysFq-v2B%J|7S%QGVuWC_m$i5UCWrY1@D!9W9G} zW>+|4Mnl%t!rO5No#Kiav0Zio7krc*e>A-9r-6e2v0Zw{KF4<58QvCC5&O(8pi~*0 zabw}_xI?enB8ni&%Te>o%iwLd4ZN+l0z4P7ou}(MRixle+ZN84(ctbhyzRDyGiD4_ zM<<+#6A;^JI-H3U;BGbrqP!g5_S4{u84Yjy?Le^@ylLBj%F7`tE8uOjH8=?Md{c+= zFQ0+b7fu4_^YC`u0iwF9hitKc^#Iuf(GK7=0M7zg1mH^m{y9jmssq5UpiRQ31308O zxD-GufNKCO?X%7Z=sdZn19%+0UKztdY6v6Co>{eqfjNby!nJMBo^;vBBOY2Fws(zk z%O_ty`sCjozvQNG5)p`DLfjS>gPcpyzA|50`j-P9ne^4Aap9$!g6{M=)qDPNuHQWC z|5~1UenU|eF#u0UEGT4g>fE>S_o+LgTsVlpU*$mi4SvWoH*wV{AAozY?DkOV&LKSCj4~i94yME zK|-gzl0@)KJ4XPT0w2r#Jf81t#(&xx@zZfrv1_Ud0)!X4+wjw+bFi?GMN$l$hU?P@ z-vmHH2=?Gn9$EYVg0_K2=OExLh~2fp6G$SujG2FpHBfsF5E%XyfV-G^_C|OBJR-Ub zz^=@^qY0?&LPU=;^Xts~a}0g(iel!Igb=R)s24&k2XG%VHxbd+rhj=Cv|q!w0n`a0 z9s%$hrPNbI^j#v_hne>wZScHtgrySzj0Es9Ghd;3 zRsJ&(ok>Ky0XUzCP6F@{Gv@&uOhl8J`5_|O!}J4hA)@D*`A%kj48SrXdeQ{)PXw?( zGd}|$NknFZH!cQn01+JwU^gQAmQv~(B07MG&QeP4RnkZO7ZJS#;6`SC8Ne7J#HFU) zi$=>|7eZ_Y;Ay4Qbr zqIuNlvHP=d7g298-4R4kMEep+Zzu{R(kYOHE99%0blYj|?d@T|{voCwjSb6QA3J{1 zi6PT{MgK}>7E%Zl{NP|I<*iY)^3b}nROPNJ;vI>QhbC_oGnA0DAjoy?+|ttA+5f(3 zawU_=a3+)a@A!$64=ee>T9NRjB< z9{GG8-CgZRO6gRD#^{DXva>y%88CgM{vGS*I5<*DL`r>(`7iYwNR?OZ5k_j75aes& zuufs+I&^2cqxI6Rt}b4c|4VaI!_wN3^*c&8Ik!m6GsqJ}Dq{LwP0fgZwY9YdLuyNc zVrEAmJY?CDB~SEs&RAGrVcfpxaFSM^0O-wLJOoYq>=a*T_^wOPEQ2wqo0F z{Hp%wlOtA;L{bi4hi)OgzYbgxAWPqeuYQj_e+wbs zf*6na-^x_(*UHc1QT}eb26X-pFHrogfKskU*pa;x&#yi6^e_u>Q zRl_XrRc1bzncGYd=y(7ZF!O%Qd_6Ot0pLB8r#uDVP%}*c_zg231)$0V!5tI4{|11q zO?yuP@GSs)oB4v7D~RZGM07b3xg})GzRY~1`MZXhPZvU*3t%!cp9ReUIp{%0FshXkiRcI-Y6Eb%AzPN3Hme2jJF_1b5YcHyeJ#vqDu6SX`5aT; zPRx8EfL)7*$^dFidtGaYhd8vE_o2)jH_JH8d;oy&mh`>B%yXa>H~%q{P6qIcB1r?F zit3G4%U*sF+0}CNJqBKl)8g*!y?Gie*bj0CPkRvqnZJdv3T-;wR0S}09HLj3Nr+e zP=%2HP+wo48DsmJRXN|+^=h&jb4LwAt>+X(qHZB>SCc&cfq(do8w>d+bCFCni?BpuK1dH z4u&SfYN&wGUM#WG5Sz)=k^9I>5ug15y~bl>|g#CF&o zPW?z^?tciJ%_4c!5y(CH3{+byQYRgcaOpC1{qfHT8=D|1D7uuvS4n3-Bs41y7RND$( zQ*vWCND?J@kbR9v`R%MS_LR2h-`tXr;{ab>e)s1eoAkA&m~gztpgVg}?E(L2jWUf_ zw>%Josdg0Z(_mPjR&Fej`?rbJBu%CdT9S&*B|{%W6+h(sDgjv*i&aN zT6q1qnfav~)V)ZMz?^)>gu}I82NehHt)+`21%Oxd3O8 z-3hPPJx14GM-+AakFs){he?WV}DP|@!KVs&}-iT388jxZ= z(q`J*&?*oprKn_^MXs^*nEK3o0W)7hL|XzVi=wCnz#M3=2tW^zDZRs#j@m9LrA{(R zg)L0KxIroPVXCAkdd6y_HpGyW{6@3CK3?cQgI+!}H{OHjn z))Z(0D96lmp$Ax%Zo37*=MCwz8GuS=&MKwmGjro07ezZWU&73n5z%O79&0=j3reY_ z=KUyug~jmhSwavYm(AQW9OHC9m;IaL#J|t1R57g*2@;;?IYStPuRSsqFUjY#vxiVe zeZy{RQ={VLcM!?n02~@37-1O22qa41$>Z_37lt^_1m+zg3d1OJlAd?#vZagX3??TG>c@lU#o`L$5YxO45&2;hd5M_&y@rWaldQzDsV%}kAt8dcxs@X=gQDg}-r1Ru;n^-nKF*Y!7|@cJ9b zzxF0-et8)RZ@h)FqYcz8kqJwHY`S>4#B0Mhqx7zY9@X{5L0;meS+*|{0JelS zAbb?ScL00_z^Tw9@~WUmfW2EmJ$dIM#T1#*%%NR66AL+Pd>Z3Tle*| zYxcOd!S6~}I*F<~#+~u>;R~^<}8Z(e`tVY-Gmn5J`>0j@TacB(&g z4k0+#ggycM66E_ihw>)X)~W^$bq*8=98UfE+h6lpRWL9=#Ph#<`0-4h2VmY17946_ z`yycG#TfcPEengmyg!_H+-vFR&bALFeNc~5gL%1>5<#H6p2s$?M0l<$WuS0e`F#6| zwrFVQzwU)66S0|H*#aCXVGO;Yl=|5Em6g?Xg)piTQiROxFe_gQ>EsKU8~Uco8VW(t z+|-yJH*xd3^9BD5DP^b?3=CJgpGhQ>GU)7FBWp|F_aQjEl8BBaAkRh}Cj=>_qKQP# z%dte&2V=&S-Rn5=zg->e?{##v=UJ(B?$8xaBu9opP$Pu!nb|W2@&K^?_z9Cwk61Yb zQp37W61tdCPo%v-^nf2{?^rxe`CM+WlOZG`k5qVLTWhN&d@VjLcVdY0a%3O+pT3WV z0rD@t0uBQB@61B}xfj7<2o6J#ld}7LFF}boIA1{FwKrf)+u*-DOMlONS3ggb0xJbkQ31~9QJDEIND5u1 zQpmsjDmY(2{*@WvLIEV@>R9nvz!D&zCgh;gU>$3Sghc?pXI`LAgdXwq#sDvmeIKAT z04@aZCjf63`$#3f)8=y@fX#>n@XJ&e0(4AX#{o zBog+Nwpdlr`tO~ue>l#qKIroLqn>OGy0WcdCjYbg&pkQ9Nq*sn4gbBH0XQTOenMnK z>9#JY*=Ofz@iE){zUAqAKJuFi0%VmS*f1lTkx8xgt%iSIHziiP)85I=&T0<2)0=s< zyWT$T^xF*MP&YEs_f_0c z5K4)Nht}#uwEFeowA#9k}oTOp{W+rj+^&GY`iTge2%H1$@T@xr^zv1Op%? zLx$yy%4YGGkvVX+D{IZ~?Oqz`gECj#}nW2)ia=Od*M^SqYf-0xX4 zCz+u<2c_fG83SRHDhr-;gd&IqtMjbUskZ0|#e9Y|ofINM1m27lJ44I}F(}$vn_sP| z9d)N4s?!A_%b>VUg+&TL7)Ie%MB>MOA^LH$d_-5OvgRs3m$|m7siAXSo*b!EYGikI zC9c7dLLl#l=Mr%4uv68TzKkJJgz>XN7)H}fD!^!<^+@P*C+WG*+CGEDrw(RJ9=Qo{ zE(bUcsH_Ye1UikJF+Ydn=-*)oiW_U6DAK=uAEKrj-M8NfIc7A%B}=tZOltK#lmc7_ zxyPRbd7dseRi?`_enL|opG?dt_4<|zUZo!$DjIT^Z$;jcPo-$peg9iesjcu|M=JVbFLigRyiRn2t8JM z9p3PD`5`Nebjrow#-H_*Yet{=`h_+7+;Qu;)9#Cr)8lbqfekXX8UPeH#FzQOEln`q&@_|3uh4UZzt4Fk`cL63m*!q17Z+(ou*hu>^iK zX&Zd5Y9zX&5D_yTXlnvNY%s(E0q|KNV#dK$wK%(eA`YsmMZ^rB`BQs+71X8w@ztLl zA{jCQC`14$Fa=-;fQ2G-i@drRir+5`g?2Ilh0B#vM}d$9m89#?FBKjM;YU$4&5`n1 z*L8>7ys2rai@^8}16c;gORqE;Nx_m4r~slud?3n%x4JAE*}T6N78aWd)^G%de&?b! z9*i*DAoALXQLWD7y+5DH{@X>QO17hjNJL@SrIgC8ZhZsaZxti}upvN%3d10!lv*qC zp=>tGnRLh5jw9|;jHFRo_~w1xF+_;(Na=@BSm6iJFNBwD8ar;{LF@9lshG?9o*&E% zDTFVGvH}?1hbU{jV~QU|L}3&~3CD5ds`~V-6e1Nh+r(EEpE^iGR44^V3C?5z-`9kL zl)WTLF?m7pH&O3=DfQouqsb4~1zcAz7e>15Akbuklq;{#tmhl$#!5GO#l1KG;=H0_ zkV_s4{e7DhUn@^XZyBnjEDKly!~y`G2haiFasX!oxByxevMYcu1Na+s&fTf2D5tfb zW61qHKM8<4#JY8xe9HZ)gNg=U2SJ-{VlAnV&M}0BGlVn)?+-FMr`2zU}v) zn)HpCCs*#gnKCBA0tC61s1O2lY9I&{%RjbahY3$j{>F@BDz^XG^02d`HOht~MA?>- zgz1pmfE_mE)M^lD2)m*$E&a>B1rEF7((xL?F8}3ZwQ3Eitz2xQ}E)nz3{dAiRe}#;zD4VpTj%Z zE;vH0vAO!%>QsfysCQ$yZ_Ax=-RA{7^A*$Rr(7-CHturiVg+Ff;bsZ@<{s@#8|Jlq33NcoRfmrJRb2N_FDr zKfhTl7ITJ5IwXHxzHiz2l_8itAw{pjDFMo1AS59xB*tA^?|UPi)siyVk(?G57HjkQ zN+dxf%y<8vPofXUCAp!D&9x#T1VLD_M*U@_Mp(|czhJqF`cp?rv3hAwi3mcFQ~C^M zW(XnG+9V&^+uFj;)}}AH0!KgfN-vjL*-(PTr|-Viordbvf-cVGQFh!> za3)Pe*wh5o(Fyi_a3%xQ-mX($g&|a%{>_;TI0*FbTn?(M3kX9%DX5N4M2$`GcA5@v zhwUJ0YLGhRB#2}Js;djCy#t)iSmSXE*c4bB4gvt*0I&qWThP88cL11g=1+rGgB*&% z(=NFE7z9#3qzDbOZtnoty*NSxA14{&(8SH}*Fswy3a#4khu|&30A>#1e7+3Ylyi*~ zAd6I#=~BGHB_tFmEKP?+fXFafc6GfIr%30w3!*R()_}xf(DX%`1i7l^iD#CDom>5G z)CsRwJBd_t)YVnvlx=g%*wf~oRJqfyuWEVxidVA>yNYL`BmuAveDe@P$5d33klt=& zc+u(cab@4DJNR2imv8@z0*7b{yShfmR8=POTfW!$@BOmM$J$7NB_V_)0bz0+uTLl* zdb@2bh@e$PZ|0Vyzq359#(){))mk$uC&Aht33mho99zyJr2$?9v0hP7*#rQ4I z9fdulKu$&Y#^_0ymFvcBjf;_T+#z{lNFrpy07ur0!mlQ5iS{r+rwSp65HX`%x)|fd z(Vp!F^$>XLclc|>3Rh3u8i!TYVYy#G@!4=h^+^0C+6rgPeH*TDK7}KL2#UkIh{zR0 zw*s71tdd|TfE58AbF?M*DmjOQx{;$RKC|aO&TTi})% zXNg$MEio=#HCm`GDF1EJsmxS{U3YatNC&I{5+OKLa8eFN+);^+-MgLWQl^!1n+?&? zY`{R0B}ObPELO{4I$8(zimOu>l?CKV?}9&EZa-1h_5|`|k^H+BOL0&PVRaXLK4Z+vm1`B|z3^W{kt36&6oG zr?9#n!2g-w4+8iAz}?WQh)jv<0t1j0Q9skN=wBHzRNMn?q~0F^5;xO-2jDKOoPKM# zhy(ch0NWX;{oY?iiY_185Pn|(&tbJjSZgec!f;6xzP+{M9g+pN^(8Q z==U=n4>_wzfS7ToRz-qMM+x$`j@Lf?u+T8(+EJhX>rTlDN3}$mFdL=who!c@c>l7g z7eCtd{$+pZeC5VxG9NYi9Ini6(!k0PoljU(B@wiQ(;)$A_4b0zo9hn##mFO{ zXbift1rfy8Z-7SOnCg0vcD99MUNRX#CW>%u^(g$MY3cBCRfBNyh%xY)S1L1LM!oCd z=<0gBlj(pU*`sL2dQpE6aw@`B$rSdh7=fn1?=eOf02+e=4y+u3sfiRm%4Luc@>Axw z!^ObLZRT-!JHS~09>5M5TVj~x2LSkCKwd0{Q%^q~|M=^jXl!VJ>o^F)5UKJMzWAjx zP*XDkEzJ#{betg`D*+F#*BP{9#8uU(rW&-}*#>e2Q~=0Iff2V?qW$0w@IpsH2wt5Y zU-3NuSTK(WU}d#-78VvmrI#^c)Ko83x5F5vf{6MX+RA{CL@VY!vpApaSf>w{TrQV( z;!YcYk@|j59pZ3l)Ld1JwH$qSt zhRTuB*&VOKs|7YS-w)1Y5ZiTUl$~@uxVsxFok7=4w;}54 z0;N(&?EV=9AI(MTp=dwQ9r?>K3pB z$oeKg642fsZ<>dF6SS%L&W6C4XMzV)F?d|O$^`t&0gMMwFvoNs05S8s6WSPj2eaHz z%**eCCOB3#g!nmAZn#>5#DRwaRG9U}5ZCDdumZs79%tG9Nsxz3n{;3`D;ENo5&4f! zcVZ``Sv3nHr_VBO2h`%TVr9R(-_Je&LQv@HPm;5$?L;L+h*gkalOaLg$SvtUc+s`T zTvB(~G3V9nb$8N9q*}vFoRodEwps^7fV}t)d~M z3mzfM#I*RB>I2HQ*yEtGEzj95G3FQt675mOZw-U&2&X)mjtco-H$QgV70r)5nCAeJ z1fn%GVJDNK>2u}NcHcTSY9WCHgV)<>t-#^4nU9vWM_K+fnomcD1o=(#<4~qA(s7Il{L1HB$c$Ro)hPt9 zVWcRlk`7!FD3k=a$=u&P5~z~yr;PYOf1^wPLPShNe=&a#TlvxRoRmTo3I({Hi&IWN z9e=;;uV`Gc9OV_2IQ6tIqPk`Tve_)8>xtnE8)jlqAkd!cLisi2s1z0G{7V;{8j1Rw zN1)|^PWVlL7h@G6;??4VXcg(WY-q&7;!|?d^D}@~u`bmgRpSrZAwTxY84kL@ko~0a zVK}aU*LXyEKKoXB-KkK3&d!ctbb0mD`GSA4lu|LXBc=5HAlR`iRk3qtXXk6Hmdveu zBTS~skMM(VGbzYthG$QzUZm6M&g#1I*@b+tjg&$$vj;GQ@P1ZVS$SV)XQvv9Qlhv_ zMP=30h}CYr>I>xyA%!0lp6zIF=e2pRpkIUZmI8qHJkR^m$Wdc&3nF@|=taWJju0gL zFgmfSs`}E7j`nD6sz6vp?XDx|VRRWKZ<+-09*y&YG|S(-jATd zF`o|sYz3fwbr0CXg~AJ8j+gla5(D&Zlmdshl-JyQLRZT%v%(-RN`hTHK#*7z(;@-b z&T->)LL{E@3+>KovPl-$=mOUQgsk%UM~x5s=U-iK*ZiRNkRKdgzRiyvNVKbLsFccY z;f*|W$J%j+esjbDXb8F%EeKklndi5>w#;w;s8wZJv&zphz)9gIYGhgMc(;1AKcA333PNg!FG>HOUW6f(ZdBk;`R8GLghdr+g8QKJ*~=*>8W;)Qv(qlZNNGAh4ie zp0O+$AfPkoK+RQ^sEAb}xsQvsL%I+&C{U81vI2sm)l#$#&c}#P`V0_6^c4UuGs}{$ z$nVzm_zNQ11HkUa@K+E~48Wbtylf*r#jByy!4`^xj)-U$Ge2Z|Ih!8JcP-F4j|3`3|WxC_9^0Gl8>J46T-Ie)LK8@XL` zb5pJ~=-ywDteBc)^ysmp110`Kgpk?BC;B3r$*Ag@=r#aX0_+2b3n4;33b#pBRG;11 z*?Ds@b=W{eL2+J9O^syo{(=$%b|T?0vmjRg9;%PlV5q35po;PeCsL{>_Q(uwY-|Wi zQ&sxDkEM$jJUw>8l`maRdGV|K) z2_ZtoeB#JaW2P-%x_B0qsFw#zFvrZOA3bK0=fw|JtO6pE%p6JK#DYTZ!4)f(Ew}01 zEH)`91xh5~zxO`6Z@2}{xG{*Dnjy-{kbU40sP=Yb#tiJR%Gt|H@M(i^1}j_09n8I0)YL@i{^I${0v$RQfK@xo-*@X^I~=TAZ2X_ z;9LL~0=OB#Z0JCL(9etPX#lQ)PB(QQfWuaiAeI4qAKI8b-w(-9N`mYMU>Sg;0KBk@ zmuLZPQ2s9f^+WXi=nr?SLZMO=Q;dWrbB+e^%n&1F+XFZt0)9^bUI+_qv%~0rQ>C+C z4%{yKTr&c23WV-zhzb8KAMw8*WV=rLXCb?^KSLHSAw`OTl;dNGF;^z5?x+@G!YSRY zdpz#vTP+E)>6lp;Q(C>1Tax+0l3RbWU)j`a&a2t$!UK|9URvvxrE)4jOPKZ*b66qc z6L)hap0RgwvomC$jyd%ZD~y06Qf+bavTXD z4zD`efRc$a7z9F&1Dvt!jJjfyvk|53XvqYjz3Xij}a3N?rL6&lSYifu4Pqd zkF+MP+KXYSpU3qp7Qzu?$Rqif8Lkkxp>Z*euCB+pSfa;UWMnLk=eyeQ&(`ILNq4=Q zpfiA&5Ln^oaec!=Ts?7XbcG>8Fyca>TsZjo(vQ&O=Z8!HVdE!3Zo`_Z=>Ys z|Bb8{pKEGtNDuhDFOnZ)#!XVfeGQ7Mddsos+unhnyGuCntBPV+W_ARTA1d55Vd9i! zixNcbjGDo-Rh!M5r(uu#4^P~Ee-^>Cizfft< zUJGoRhE*WXL++_(wP#;40V+!%|KiJlkbsInj)QRiLVeMCF2Y4iK(33F?JKth7zF?8KEpumiq^XaRTpc=&-4X`06 z5AIA7Ro`4*{nEcGYQJ}EEHS!H=%nG1`4qc{qlFYF#uDT1tEm0Kt>x9PlnIO|K&Ahh z8u7yCy!gn_GLxH}Il3^W<jojka$GYFYDMc|)5FsX9FD{+<`uxO%jF-!V_ph$EemviA z&C_a-ABuI@^MBp%y*V?t?4^{5;qI>2FKCalye4D{)&ZF&Nnlyf5uU#6FMBO6w7wc4 zYI`L8-lgqPM$so!SBCbSX%7Q@Va{8)y?H4bg92It9}l!Q;c%Z!zErE|lzkr(>7vr?KZzH23I2%X`tcV#&>EPNG3vt2xckw|!jkYkr z`?)mEoBuBU)UXgq>1-0@$67qWK}-M>Bm_8$kk984h9MjwK*iwBFwOuXLebZY0VolI z0OY&!2nrE;r6dIAK=lOhcnEU1V%LoZPYJRfUdaMxu2D)&hgJjo%-r=!$fub3JIuTp zGhYJjQ|B{tej`6KKQR5r!vM6H{%C`}U^h)c^`9#5Jrmk6>ehgxCxUEtE#Gwm6esPbpNfYsuFc0M^;amH02^i?gCb*6+&3@ zW03Z4X>MweL{}?j&nP8e-i5CyYJgKcjKa1kipJ;t!WO;wY`cY#xcas$}L&f!u6nQD7FQx1fLLlaf(_33xx>l76%);VRO@gG#AgU@g zQK1-=OoCz_D47IY7gUx6l_hmrtFk2EIuJG0`nO_;>S|4fxGqFxB`8$}NU4`er9iQm zE-#UQsHxHABu#m#GE06~z$!r2FQx*xxP;g^2fZnszGXhHnRh32imYv*NsFG?ius-h z;7kCQm_YsS0Xz@jWdLuRmoNcfjM=VlmYoKz)Tn{>#t^HBI~@z)UGuW)3~~-BLugC| zaNR1}!7oWs)pvWt>Gb{(z}L-s1Fb7c>b5h0drRgmD@hSniZp;EfXf*8p#bo=LUzuX z@#NjxI^N-F=4Ppn)5l1Vkf_kby!=4V`|817?3>F~G;=`|z0nXw3j?s9ASxRzRMR(;_Ipi?Lk$BtQ`{Zfm~J=kXR8E zaNfL`sC8qIL}&~OAP|z$8MabG8B*cBY#Jxec@y<+3?VaGg92P3;n9X=Ce8rjLg1Fh z#rRvxa#TAm+QR_3C`3{^HZkkA?Nmn^SIJCYu#8WAl7;1kJ8;m92egsYe-;<4jX*D|PbK~o!%A7%-BaRRfnY@2V{pbn1lR%6a!Vy@I?(Y0^XJ;o9^CDu^ zB!s9c9#1KS$_MJc@e?K;+Sb}ULn+0MSxQd9iRT@zkMGCQ)e4@FbJscv5 zAczS0e012Tu@im?1!AGoR}(lQ5{{osU(wpy(oRGO!w}hY=OtdU@>Hd`mPn`M76heO zjTaP~KW^fbD~Z+ZxmmQb0OG6&bfJnAw2uhK0rnMhg(K;u&i0nS z*|XNd1~^pzfALallg2I~MY3p!QT)zBnJ zauBtJXZFYMGtDpAPY(mDK8D-b%|feMo*ic0ihqyoL&_*~J*3RvKLGG&1~nY zd$plm!vhi_G~@vM8s9pHl1}6kMtnW0p+9YqC4E@6j)I>TLl0moz9ZQ2F&kW*N1@}r zUtLqP$8KjSr+yo#Y@R;tBnS~YJ3AxKiyb0ksduADjhB-0%!n5U$Vnl>APiG}z^4df zjlvv&3qGWjN(xa7##;gW5S}4zZLRsyW8!-dd2_i}3E}~8q!4}>1{DlkB878_R%Z}M zNECtyLWochp-1^a$VU;!Jx4y>)!uYbE|{)AJVPOGFfNa8;2VnjXN4y;a zomNT>kk|4)6R3X&z~;~?wHVr1{61*XBM+VK3D72uakK0L!;B^T7CN=oH!wKSQ0#p4 z?d*H2P?;1{VJ(7IwhU+F&vVeKnqRL*-Bcffq`%Z&PXO2wK=5)<=>Acr>*(JmE1z#< zHCqz7G(bIzq((?o2pBmoF(V{^h)4{mA^{b0%pHF$t9o>lkf;1Hm!4%WolR5`!Yh*@ z#UQZHAzGreYKi&}v=FGrc$y9A5w7EG3_R?3;wP`JSsOkZv=-Gf>(*O_hMf{m-0n19)@9b#3p}ey8G(U=V(;6MUNy#Wa0P%LnY!hNQ8%#)6LkQX4B(z(l;n@O^sFoM>Er52 zY{Oc_7p@|}A_U+_z%71+mW2;`-G!h+ImuDGNbfWDlCe-=M68To2lo&SqR*!opy!h1 zKgnP{o4RgjpS5W=t~Qdm6Jx_sMVJgj8+@4?jdHv; zoQM*A`aJG1%W;S(Nkp+vICYf-km#rWL)md@)=LnPvlflylK^6UPL<)*tvDbjIn?8z z1H~LCqWCb{-(0@O=iPYbX)vE~UF;lchm2I-Op z2}$X9y#Mh$@3TIfbIk|N#o5jB^J$WL^$+b7=h8!*>V`DwGfJeVOKEL>Dd>(>FW+&cz)Z4dzoE2_$Y~0VfXGYlPcdPZ;h7p&p>>X=s32*Up6HbAiMkfxM%aDVN`4@vGhMTF*Fs- zWgtUtS@AFu!f4AB^kU&Hztdb3ra()3X~P9)?*N9|cMpc{oiGsR(O zeWpR@L5H5C|2ggNN8?#dyV6DHhSd01k6o*A7&k%)i1bs6^=ljTLcHXe?h+MGVDb|> zr*6=09{zYI z7(brS-$+2?V5}VB11JV6kSpSe=py+|0x~T6n`nn0xP-;gyX3pmb=>!(x>Ru{;oxAf zY#U4X0HNk<%x}h+Zc>C);{=Kc^acIlU;?6vKg=Uq63|D&i9%V}!7YOz%$}WaSsHSt zy0`_~b)vtaCM?#|9DIp<40FMC$w*yERscY0!H4lR;|X1&z2Spe<9hE{E*0YXXCv?Z z&q1csb*3EcURU~#pB$mOr zznr>?R|A}|@jZv_hXcQyQ)d$8HEaFI_)7=O1(X;!m2vnnS$jXm?PRBPYeEA^0JQEHVM0O`8YNhSKtT z6*5(=O-&xYWbyIxLa)#&QrPREYPvEee|Ga1lL%F^?o9@TQOjZ`L0o`m9@tj~b3HuY zVR%L6bAf>wKpi4lL?VQ}rEc8lyE3-0xEQJ{rH$rSWyFA73#xt(W1&6dn?_V+HaIrc z)YL##LmCJR-34X?C>IJXL3(4HeBqZj2H2{c=TYoHgvHN;>T!qsDpXg?!Sg^G-H6?& zbJn0=kq|5pC9WzbumG79vWW)aPGaCl-%%}GZK?qQRyG2I(ih#p1+J=uHt+_xLjmMV zppU~|ogy6A-Va`ey#hu!$0yEucz&*AgjgVj=jh+cpc*{#oOGJ1?!RP7+XCH6?(Z<} zj0@qVx_mCk8rF4m*H9D#5V$cIiq|Wnfmvey`68U2GkW`iamk3Iyumtn89Ndh3YC5` zNC?{;_e-Xmj)mzdRW!v8#2LYMze;u$zJ%?*2Kos#xx=rZR?)kwOu{4=h)gt!5TrXwify5@H-=ZNz*TCIqB2E+BR?OF&2|%z z)`yUz^3`^R#eIAKtN2zX;P5^r-6QO)nJOd>JmG9G-#;29sK)j4fYVm8u;_q{p+N4# zp?L=tz39O2RZqa|pzhHO)zwLHt>#BO^&;wDJZLWOS?oCWX@`@~Q>c-$LM#5184ZDX z8~7I^{hPd`j8TNi<%&3%92}xRn>j*%dz4-G=O}w8mb}yJ(@!eOQ`{0H7z^ z3Ud^sz>jKkIJS3DPnOr#Eh_ls9X&8u)Vg@H=uDT}zTowv)AiUpdvw&e-?bs1%Biwi z#MDulw8o{8BW?az%}2gM@R7NQ&q2*@a!g-O@94M}$xQjvvoh&MfUtGP!09KiPSRrB z%^s?)?RQWA%}2RyX0kFjuJ`6hdXZI4y>v=RbcJVo**6bcvnT8LqGMO}ac+Kw+}qpB z)KIAQVcUS-4;CLy?B2YTFFSzE%~k%DHGnpESn?T(`HGPZdqI1e*v(&DH zKDwhL-N1--3mJhiXMSs|k(ZB;6jJfqbIFudlzND7zsLHx`Y?OMCym+{>+E9w(etyJ zC*JeCanM^oh#96>2pFOWuVGU|dUj()Lo?G_7Gxl2c^~gT;E|P=$il0C*<3tQJir@` zyLB$TdmQT%+Afs|nQZCzp%h9Xf-u|4qja+5JI)||V2%pjUhz#m22ew(A&0tv{+@yn z{I7CTJBEQfEa?(!|66vI$*2N@H<;#TbuOJUT!dN+RqM2~vYJ&UI>C2O;55o+N68|r zaktk$^~1VTITeZ|Jzk6Z?Lpjqa1^%O5r=eTd1(ynRG|&-EDEH9iBHNzWeUFFz3sZV zJ~!0gX?d9anv|F+UEqj=^AV3rleoh1>F=hHrRUF>dm8b6RwpKfYiZh|OENipiNu>Q z0z%MZJ8);8KyU+{iGf$5#j3m~N$O~!ltnE1S)8ho-64T8lAvlvdeP76W4zbHcC5vt zbOlTRrR@qT{84G20iD&#h5n{VbKx#Eoy&Y%51=N@2G#_T9sCnch`{|fUmuQu2a6!7 zLB!6pRh%w_5qLskI{+Q(W@}phb~EU++NK|1$L)d2M+|*a2o;uR)5XbtdYM2cB8K^g zn=ekl>GGXzMSSLoL&N=E5!_jg(J6*)7CEB<)+X^s#H`>&ie!YlTa-8&bQiojeoEgyHjDrmi znGTKs)gV^ej=1zr{JfdpW32QyXwZ#3?H|tgYpMb7s9%(IoSqbKDX(w=$H+K#%AvF7 zJi$@6P?r1RbEy}3e}0Jk@OLmecgHw!?`PT5oH!#|swtp=d&Ky2uc$|Ay$%S-j0?hS z2^pWCNH~V{X9-jWw6q!(x3=!!UmMgn)~A}vReWr0{CVek>67;)6FJWC)97f#w#nbB z20vq*2$YQH`Y(*8JPMl2zs)SIK7JG2pP5{mwty`(WPH^jc0WAJZ#R16eW9MEON*>^ zP+{gGq!~y+;4!V<{{PMUUI5*DLzV4zcp30J7+h+IN%nG2y z%0_q0B}%m7cHaR_xdqzfX~raoNSh$;G99lfnl}0RDG!+hr6!drTf;mh#bhT*$auQ| zj$iQ?C!EC+GT{usAYf-85c~{HU4m%y_(q;i%*vDmo!bVPPwnouyno_+s#(Mvr(C+t z=$coboY3nZxmLFIEoS+$waS^XW0GTs>X|8l@C=*<;#>vAg$T|*nYw*(S;Lz4V@l4-4Bb829uUm1U-&u!k*qgF1-RBoY4o{ zRkS+)2cMNyY+^Lu76Xz%-(z8F7TZWPXzVNrCsaOd-fmk2!0#Ul(_2BSX$4#I@Rb60 zIB@FTju(7p_U@Or<6Spe3=3O`yO+h&M~Jpt?!UEr<6#aY&_}CnWu;gPd^i$uK?n`D zI+0NY#^H$fVQ@S|;1}?(m$obD{?PFlrtA5Ik>+Xh4rD{)!>94jh``+WD>6`)R;>yg z!S}DJjecYAkDaY#`DWH#N4Xt_XUpQ`!!fX6Iqh-F%?scn-TR(3ji>j{s8A zQxT*2?gW}+fL5L^TgS`X>)pn{?JpCu58g)ol?zIu8_dcg4o?OXkU=;ZMp@VK!KH<~ z0vnG4*DkUAPD}4+n?Hz3)olg|ke28Io%iw+o=}3e4==;HlN9J+US35MhW%1=j{BXJ zL^(S<$A;FeDrWvQ|D^S+&KA7S{yonoxz~EF8sz9Sdw?&)GVbp#&efEniJ3#`rP)#V z^1);N-+r`@Wb2huXwbew;MROYqXS+@^Ai1zapkz)s&U1&TnV)@mOd%Rdwa;lwK(p|4zaRV*0S@H7?g)p7ui0tww#b_lNx; zm!S=cV_z$THSdpdHT7I<#E@W0-3KY@nFnA8c6qEZVf;} zNHP^~GM)~A2Okgv(d0@2o|t7|5f-K9*$GM$!LT%?As{@#m%W!&16`&_I>CUgS1+37 zRrO8S&*-{)3tKTszD`~kJIjXxPykgaYcUC-0IIJG^uhz!=05WTTE8+Ck7GfK@+72I zsI8kHWr=CA;WB_TPu&V*!4%B?314tZI?a*YvRGdGFJFZrj%~S5tNp~xhttvsH;eci zB;jR`y>Vs`@rp?+xNrN5a4@tb*HO<1j=({mAEg?Xq{Uu<-&<%uECTqn1y0FYtuhrO zw@3XhfU(Wt-oG9GKVpsr#=&t+Og}43p8Rmt=jsdgcxn^<@B76&a(-=|jMd#8%GLU( z^2la%2SnK96n4QAFU@@$WRT{8DySwhF(M1ATg?>UKo-Ac|A-YPGD+uWRG83Edo@Aq zOGN}~dV@OUx3;%bNJNNvDkBV)gA9s(v46h))haWtOQG}-aYYGS%e+3AqBVWDn7$@O zIH8_DgFan4V@&_WZd)9h(V%m+XM1~`N3(^kr?#;G%KHHGe!+v)DTDb3)wYVIAPTnY zY}@f8o=w$O%JOuIuShIq@)NB4 zt4F5n39fkU4xza#(E5DiH6!6xbfTh^)ERAY%PDHBiSD~Bk?JknO{Pg@_&EFW-0U1UXr^fD z*;#9hK9_!i`L_RA03(!!4E3z{m(t7&+m)gQX%RxgVuaxH$oPo*vhQVF={Z*0gFqE< zf@iM_(Io!{OFy>Wma^L2)3ajgk(+u>O(6rpK>Z#M!?)V@UA=sgXaC({iJy^>Y2u(% zC~b%tsz#tUQFz%iHIP`UBDcSL!uX70DkDdVDt)Xy%c<=Xd!NY zcVb@)rlN8*C|N>p1lVX;izIQ~Wrle$xMj>F3M^~9^nTIL4e0(8 zfn@LyL|{Gmmj{4MlV-t?j3%74>(>Es|4y#|k$;xyzoaIiLRx1!fo;=tVMzJ+^;0Yt z`nj=#pswwRe*0MVU zgG&6X1jiTf4HJ18OMB6q#X|uN^I$3tk)NC0FB1u|aCyG3#?f850}a6Q^y}vQ#=#6B z0in#sY)b_)YQ_ki=IeGaYQWnig0%8Lg$ZTj^@?)Mc_k|0#qFL^f5*kkKgGLElwb>KlirMP zm~{xjM#__C_Q*IOv`r17g!zrj^LbfAcF8;1HaOx#dJ`y~(zR;+G4ybAQ#3-6>7~Y4{<7yxFRn=LA}T27bD2H^9@a}mXjj8)R;<6e;08x z|DarQcp1(NL+`d_nq8z|n$m{wSLJ%Ww+%94YvAH<-bVf2BT4hWi}N4L@t>s$e5*3i ziW9Uw?{U9GF=bdxgX)K``2zn`9PY)>W-0aKA^rf|D_&qgDJ_Ye^6)+0uaTLx?P|9# zT`*=rOB(Usi_^bB zS;KfHZ1PhZvdQCNiFqsyg4!XP1H}oGEs(I|A<#;NIQ<7XVmfu)T6GNY1-Ea3)C?Y> z3}!yMM&Dg;y?nCby#Sb#$Eg&ydzg6mm;_5fLU>TWrlOrN6Z!#K2N&J{yQ%*R{n-4s z{b*1Q1wM`E3h|wjQ8H&IFdz$Txac^pSWX*J#7N=$nfQ*iFO@aJwB~1p-v%h5;T_x8 zd6+Yt-R*u%N3`*s@4C}K8N$IzY(2zSS?)J`$STR_>7MAwTDjJ|R|N6ugc#k3xJ+6< z%rzH;INrj$0{*!mQDhJ$#SP)(K+c7N%uw+{TLDKrb3duf#qJ{G=lB+>KZay3v;r~L z(0~BczYNe);Ke~5q6%^@U|)AB^=$P6eT!;33-FziM&Cw<`z!%Mg5pac_t^jmN9w20 z+EWKce`{CCL40^TBCcDAX86O`Z%DwYvURM{FthT88E^mS*DOHN#;4kX5G!+HZ&#>vDG%y=@{G>WI zdzid-Hxw-&x?IECFF2_cO|K`}Qi$DP@m+7TH-(3J&IW-aG!3qB1LTHku`Tq8cZZT* zG#Zux=#Y=a;iO;N0LRi5)FgbzF4H8Wka_^_lXmSVNBs$Of(FFLaw51l z;?D1x3m#q1D?}g#UnfRhuXU(sBrGGE70sLz>VoP!QAQujT#%biPsVe0b>mnPoBkP6 zPf94!hnPh%Fy}0DfSelvg(Ge^+hD9>a)26eB_YhlH|GHKWr$^oVY{gaaYM`r%)ICD z6-1yo)DUq;$UpB!OlX2Yh!IT;0}p_zuprHQCPZV4wk)33*X z6zR+_$0`tgP#G`Va**Zm^1s~He}sa<@n2%$pI8dC;(OE9I1PisR;EC0x~7^TpVjZ_ zU&l~I9jTz_B!wNZU(NEqS4f!L)WrrAWjPmP#GG41e$LEqU0s~-$_2tzMK;^kUJR*X z^=Vt!w_6BO9A=8VllHi@Y>(FCGzDc@C$&=tum$IJ>K7F>aNf`roW)pN^zRQMSU>~d zi68G5d0B3+HT5dDC!)3cF1B(np1ttcqAp*ksp zIsC6AU#|AT>pn+ujBNA+86fI>!>0~YRCnH*-oL!4N{r{s$iNT2fCG3RXg5s(cMaB3 zESXMD+F#|r;i)bz_aV%>`jgp-Bf!BH+xJRW`C~kfu=Kbm-2E#}|7Cn5qb64Up!)ST zGO@r}-{WOBAPy40nlR&OZpA{|3!UpAt11Y?52x#a5k9+}yBB;)Pyzh6iGug#VScM| z64~9oZUX%le>+`9jBJ(7E>=$Q*Ag|i1fhW)uyBdPcR4;0()esgqJ-0G242uxqCBCG zrAPTldtS)>DYEE`Ihyd;_h4ANycZ@2lgK>&8G=t8HziNyoZUa^p%TH>u8d2k!1BQg z0+aiI_6wESRNpC>0U8C$0-2MU$OMIvBi}4yoSLpw2BTXJpymr6FHP%JDmMXsW334T zMt};!KpLXXdqzC&P#UR~D+T!0et*gLk{honZ>$y%xTGzRNY+ATMMKjWfMb;Jsp)q3 zuIv14#j)Xfwn&O(E{K~Np@BF{yXLW{iM`{_oNzd>fDa^~l>lz*6v)&dVL~oA<0u68Vb1QP+lnSZ?%_>Z=P< zi)}Q7csA^krN9bMvDNF)x)X%9f&#hWlAKyfRDd})G%VVl4A}5&JrlPT$2^Q8nhR+B zU-$AWO3#0(!~b(-mwtgHW!=loG1t`m?a3v(w6*)MZRG!V{df8|2z;{!`d*Rt(Dna3 z;y*wCrSM9Bxb*fe=o5dhC}%wpMrI!w<$yGyg|_|L=Rv6HLJq&BtKi#0vWrvjQd3*xJAi*e}>-%z@H(S7hc~yR`Q>d_W!uT z|8s{wFDH8OlZp|F^wEJeH2dnjJ)jSmBM==hbxLW+Zx52i0?1@BhMR!AJk%>>20{Q@ z2T-D}F#;gZ@yCzAHt<&*DwWCWgLZ-k-~h%}CCi{SOW+uIX3?nMqXvlP4U&<;smHD1 zDL|jxXY}HWPf9-}#=eqWIE3-3P$8+K=#o`gzNqlHJS^wfc(bQAh)PQNec>YOs@EC zP~a;-;`j$rnv5rY#2LH0{KL~o`mU<$_4rUiFmlg-H9bF!kxP_fcxHeLRpt}|?O%Vq zL6l08mE^pZI8&ZIY?ZuFVGHfLA4=J^TI^;2GVsA0jAtt^TAXNhiv;ip~`F}gforr6Q()1 z$PqJ3p}SJ@UxRj&l$ua>QQ|r0A6$S>k$O5k%Av=-E%mY$tQuOQTF530&>PSnFME1g z)U`s%ix<5gf-a@dD|N^TRAtY5AI2ewVC9mnyeY_g40!P_lhlU`F}L?B$R`1n8Y0whu0Nx=iqxZQ6(m}j)TEB}sRv?$NuCXb-`ZgDXlC^>ooULlYtVo+ z_U0u+AoO0hGK52)gOy`r8ngh>i3-Dk_YL&_E3IIPGOo# z;g~Ib^bZPrPVimb;1Ch^PCKiC4zt}JZy)l*k?T#|?VX}yrXYOdUwzX55fIYs8?Xpt z!>@R_uNgtspe!t^xQ{ZWhW=0A@VF8{t7R*sx6>~A0AEMw-)wFh_}4RLC^``Q#1$L5 zn3s%ug_4Gq-MB{;xTIY~GA7HxX^L{fRA)k#!PpFHhbRO;FtemPalPWYuBowp@Z2Ra zybo zrV`cFH>9yFB*`Afj&ldX0hPPjUls!5dhB)bcWc|J;`n= zBXDnV0cw%5&umd$i0$w*kzd=amBxtxDySfN8i$Rru%5j}naGQmX#`Cfmpde0x8tq8 zVQfMeHX#`y$rH><&DU&&xEA)Zv?v-cKl5v)1aNe#bt-RRY@sG@(hYae@p#6FC&q&Z z+@M-obWD~>x{?)=Z&QD*W(IiW027p*0tUsIoO7vD-V6*EecVEUU5A9V; zaw-bH-$w;(HZ^0zlJ^J@a zD{t3_t6}P}x$SO$(Stu8a$_ckah#c#Oh%lqZDSIbO!}-qeUlwj8L0u}@YbCh~^+vog$JUHuJ2468jMmWaz(~#;* zWHF>$K^vsRVA>g>*F7yqtf`+ah_G5)Z?7sZG%C21p_#g3tp8A&_Q4Sc7?U9uak@P1 zdR=j+VgAYI%kshN_YXr7pS3%B&BI{}4|6wnshlqNt8!))D?Y2a)9)`6Mp?PS!D_gZ z4so@^-f52qZY+8#t2gfiZq58o3Zy7MYoeh;HPs>lJ@53*#2g1NTAgVqv~S1Uc3vE7 zG;7FgbuqHrh+n+%{<9p#*?ff4StfQ`U-#weyCCsa1cP-O$58w2NM!K)qdy3Y7?kj$ zlie=~{W-yoz4)D}VABEi_wxM}W)E&>VONmIk~N}k2=wISFAmL}y8pIIl|#Ty^D_}v zR~XGF;mv`Otd>1H!6m#PIo#>hei?$YG5x`$vCVOl@Ui^qU?mLm zcr#`Bx*;-PGZkJ`;Y8z z-!oySAf}oq(e^%3zs$1I;*91#EEjzA z4hVy!AM}-)u|q6bfKc6s`#kOgUQ#dX=dp8CzsT=zji@;JF9)GsL@-0_6E;P# zVy`C4Gnx2+YZa(rUy}qEqTMgdvp?3yYVqE%Jn*BfTewbEkiH>;qYOl15~W?RizuO=^R+qJ z2`Yo0VNl(feM%nb;mDH^OB~-`a*^SXngmkhFy^HACz2unwFqYZ2P4@5&r5Rl018j` zN@shnggfB&%`Wj~F1jkLIq;rL!tPMBKm#jWJ<9auC%&uH*clK1uxOI;P3pyU2WoGeZ@TO$b{zm3;mWgi?_};4r+b34c<8m3v&bv=I4^T%A9RuJx)y~!A z^JqS1%Qw?;ltib~LndmSeu(nArp&8~1?S|QfWDw>^~GL!n(LUzU}wJ4@wpngz@;K2 zqU=O1StYhPZ~b`o{DRC)v0y<}?e4H_BRAW`fam5L0t4@fd6JQ&+b5%Mcz^h7<7(h+?Mr;juFiYqoeGS| z$ScbAq#2bzZ!am7*k2IwsK<--jV?^P9j5cP=6d!|@mDFnoYM>G?JZA;N_!mwhS z=1FCo`y2qm3Pl%19xtJAuIzjW@$4sb9#Ldn(}S+} zmIfzeEf9y@E!w#!9X+l65^H_Y=7 z9)()4{jiI6V_Ln&S-cPTXfob?gNO)e;X>v_1=S}t@sN2&Sxb88?1u|tHK&FR)n-a| z`?JDEsf)|z&kc+f1n4Ja?GJ#ljCY z)+=e+z%KVG=J&P~k7GM7G{jM2o~G+F3sqncNo*as1JpfYRhDmmp+4OKr<@ z%2#Wi=miDF$NJ#&cvg})kXERNUpr0Me;Xqn3{ll-x$&H*4rdam8AYt@gdNz2`I;&( z8*n)|7;>WYJhE`tZ~*)WHJ1xiKwX;ROD@hKxfWif!C!Pw?3YPk^2dL3WywI|SX4=m z40_De9q5%mk-c71zQm%iPOkd&Tg&s3D1T7KGrC=V?NYhVIMBmU+%*mxw8ijwYN6naUs_i=dS zR6bMiF>ZdA{tM!v(7JivV18%A8(@*nOwYAl;7324ch7Ea98$So6>_@a(k`t71^)cK zBRD>kNMFV6Wbn~c+xuh8lSE89j=*C7+#kb5V(vFf9Nwpf{Uxg|PQIhY%77eyxQU0^ zFxNvGO^kAPcB)CO`ReBM$Eqr+hp=P*6(1JN0S_<{jCW{9-yNp~pdFLBrR?0hNB(G4 zQg|jHWbe1L_dDw;;49E{()6+EZ?A4cEn-=Mr`fi%wtUBh?|5gWAWs5-rbpNuk6dKF z%T4=x(MTbh)46weH~DGTiZbWpU96A$u-W(bzxO`k8+n?Hv__DU+iIc3@&d?d0CFf~ zF~TP$t&3(47deB2t0MFbhv68L$DB`T@7``s1b3F6#n70HyZWfdUuBU7w>FjrZZ+A= zk70v3Y5X>or5plJPRtr4-{3yj;v0!ywfP9OTKD9^#!^O z+L4)={jm*l5Q4M1#-l@FUf7i`Gt|C6Iv32^{bzf6aN-o5I_KM$ku9nhG2JLp*>Vz9 zAHw{Uy!NPL_TLnBZ@0HigO0Upd@1y)zr`O{o*EpP~zgq9B;{%SQ z^{z3YmiVm#6B;l$?$c;J*Y95yc=MuQm?`CD470iT(Yn*0f}-ZR`Q#4)xq&Z?&4s}R zyFrev^aK<%6Hy6U`!jcHE&w#a_ru#-@ zrbHOInU{Q5)A+kqyy79SYhFfFpdjo2IcVL^H0#Lsb2VldnmkmFS_Ps=LOe-zFCcY$ zsutUH?Sk~t6x&gxE)9!QN#e3UtOW4qJin(RavNkP3ju8#f3^&ESZ$2JHu~@~Pv_>n zUV2{VQ$XGjaBYiT7 zqdU@ri~}AZ8&js@@VAbkt;>yN_shxlk~^|xqiSV=)Qn85>A#s&Vcfq?hr!61Mo_KY zjsqIx4C7z$`w8D6a@YO0`R;y6RPMCD0*+$OvxDVFwxndH(s#n%(_{@oe}D_xmP-0U znWO~;?y%rL#@RlT)MNB=jPDhRsxWSHjE5`myqdzcfBd8s2!j9IRjq7@Fx;jsr2U(9 z{SbjwJ9b?6z-zy8_!_{-yg`Pw8WL@&!!8eCav1BK*87-sOV>>0%@D=sY>~_s`y-yb zTU)mOMQaO$nRbiLszTk|zfpm|iG&K%>8l_c9L-re#rcHV$1&oZ4_8WawAMj_VWCT^ZaGqoH&y(hE0vn0`+fSSrk!U4tP+S+a!ds&Z|=i{+iSiCN-ev1~dk_sbzYdt^xG4r8ycD>vw_Pza` z{%W@9ajACg&~Xa*TLIfGPx&FU^Y+Y9^Kj_u?~BYEE2+Dmu&}CDsr#b|U0#{bZ5d&l z?}BzeCO%>0S7d~F%CfOyCOVsBA} zX6~^s*t^rKhR2fy1@Hb^e)Gb%m4SPQA$H+7zSdc(?CD4dOg)=d$P)eHDq4rXWk~(49DTBQYRgtN!k5`PF@F0)EfC9ze?-=u+}3eH8a-k_iokW{|=j@P=u|@gQcgZ z&3lfkAloaq&_yiUkXuA-uHMfW4Y@{oQ_EnKnNjT0+wQjfTm$c_koM-C(7vG zR_gQyHJIkK9alNvEw;@Z-{(4-AUg;ias(ri)49eP`n4iLfiXV!kErnH2tFGk%SGp~ zyG$3lHxrmjJt3@mK$CaUXOvgVTlP~>C-6d?5nlwX zYG(py=7b{W1qD97r#(2(@SY}Ib9@@nWE+YjS&Kw~vn$K~oB@TxuTg#l9D+1;qnD<} z_NEQA<_4%=fNI$+2Yh;bb9M>3=sRxV_u7MJQ8xP0(Y<#oH%*XAvttAH?M=g0C6%OE!n4=z{ zSt(Z~+9;-cEiBabHhlMBaI4T?yEVAUte!Y{8<2BGn_YV)+ABMDbN=2X81GiN?`F4? z3*R;1DVzm$Ddm>yXLHBqUsNoM7P=cFB9qJQ!`(?UV2WJ3-V-|Ok@q4-sBm~C>8Z0R z#hT>VxX{hP%C6R1x6Yk=9nUV)?n!;~vAiq&>#=PYk0(#_5^%z*Wc1vH3&iory&}UG z9IINiTEy;-ygXV{S1e@=-1cBh*DpCnFpREs^#XeWnj>Q?GIK>v*Kcrsnl9f}uq}V7 zQN|`b8wWUyFu~k?X&`*CEdFp+95Ry3PNhG`7Q0=I|SZoy)f^A&ago_u0 zObJREZDe!VkLii=m(L43&;QO4{E<5bSw^}3B)gl(76?>#U)QQhD*D;4h z#d&UM;uMd}&Vm~A32p6JQ$5$dQ3 z88w_z+qi;IzI|EJ60(Y#cljEkWZ@&~Zx8KmM<*6lh_DHj|6V6^wUiT&ezPJjeV4t; z2Ipw4p@UfAF@9U*##K--9;T5(>yf)u z=O&87Op?q+pry!$Gr|3~3q*Z&a!B3Cds##1p;sRXWqn~CWM)IJ&G-<%3W)%xyw2B* zFv$hFL%>Q`my)RuBIs}$w(A7EE^t@fcz1V6P zK57rX6v?2rdPtM)%NCmVD7vey_+l=9S79_>j9R6mkneSK6{+fO77Lr*!lUfnizgt^Ld=x|1T$#jnZ`id!{<{-7yj@uRNCQo+>4 zo>Hye+QDj*$gf`oqxrQ%AmLIoG!g*3x<%@K-Y+?7+1vH2Y{8OB zmJt-}({I%IsMw$Bvb^$elj(9E*oX=w{md`bRg+!o7`=KbG52Ss_zLfF-@SaDy*=Qu zC=}nbGiL~#Pe!IT|HA|)*yAPzV_$zKQ&NIfaFZ5D%QU?_P1Uke&rZ$|7azy&5d5~Y z`bF{xMyKYteC#i2WVA4-<&H2`nX3xz+sizby4l^_t>7#daaSuH6$HUGqj_ecN?(tN zHaq@0RG&3l5*Fa`_qI!OYIhz#KM17yb(rJjv!WF+w1Ksb%(^{!L&s#o(?`Au**V*o zRQ!$E@*`$t)TU`B$D0rE6@|!Plr?c2y6Dkd><%w;1fu|N4&a2yS1fgTZ+??B7qnxh z>2ZFa&EAR!bUdRm;xh4BHQ-1V z+Nf%*POQCjchF@<9Dea#4RmWV`LRgt8&h%VkDuTE3%xz}{UgdSxql2*=1FjVyB^;_ z`2~nYa?k7*8`LtV!SI2J67CsdaJkl%V&aiQmFZeREVj!@_Zh=J;hEFCT+}70of3`! z)Ml23wsMF#`!8A0W)Jg&#t-&7>n#S9ZEXr9!ss=ep_tl zon8D;k#{%)e`i4LOCgDceUO^;>Br=_ZMgoLOE$+c&sFA5i9e2n?DyK>3)z7rE0G`0 zNtXi@OFeWx$`zLoX`#iVG^>xV$acAxuR8E_`04*6HyrsO3&mXxPy~vlJatHm9gY0{ zQeC|G2EVmwPSj83>QE$jsRutD<*#JR(R(nJ&}!)B*UyBFzoq9UQ4Dn>Huvh*T;{QZ zr)(M3gz;;?V2kF;-;&P4jT+GD)1~`In(}s<$=1~}!ly;dUf1~Hyy>{{+LZ+jPTA2d z&7f6$!7k@hm+P`_Q%TFmCC1C6-sIevj47gmFw5Z@rQEZ%k%VJ7L%c&R}t;6IH)j#((8CVTFcJcmdKLc!Rxm z1#m`UA>j?85Jm(kaDxaX<(3K{}QnZHtUZ#3jY4sNh|JyRhY$Vj9r!?Ea| zV^YcKuj3YB*=<^x=k|LBQkm@~BEjg$G){PvhR2EnA8Pk3qs0A;rG?Hnbnvj7B@Y>E zwuhW&I7JS=@WWx2-`q-EW}vW5n$6qI@;-n5V!J>2As9{bvZs1dSyGC}Yt3hs+{0vh zeKdm1tPUbysf6ZN@s^_gdyn54i_sEF52pe>dImUNWH%9YWM1Us5_GYn66 zye7QI$EWRl0JGJ9a$YxTWxR7vlhab}V+6Dy+?8Xv$Y_SB=*KK?MFy2c_Stz*s^gv8 z?Qu{u_w3wt;ib^1zs07FYMdu$+gmS-+r;u{&8e{p@jJ7s`G-4;#ZZ%waA@d?Q){zI z-+l!5Aq&ObW%qBVxELeu?UqQmtxJ&m`{9pGFP^rx-Sn zYxiD1{SyHQn)z0}?MK-D_riN27O7m*7Hhk?LfazzuK`xmO-3C}8_6`DQP+s@p7F4q z>1>Y(FxYMV4Mkg{m(OwFnQDX4O>{Y-N#y_NH36f~(n{d574*_~aK}G{ECESL^NE z`k*r6Ony0rf!909`K#yOw3g8Z=7jsRJsV$Fk;mibVTa6~X!0+Y|J)4U?9Qt>wHER_ zm(Oqd^M>^34cB1?mqNwSFZiNoLHYv8V<>TEU}EH62Vo6lSLmfCdp2=5^_xWbg7)79 z9vfSAEe$nCs6c1UWfPBx*P9mthb!p)<@_=`sFc-$IZlL@%Dg&8+#|EGoM!!-34$>PzJqHi0ZN}=TW|vn0$z!zzUIxWHJLLb187o-8BL_8(bk4m5kG?imk17;9>e zVul-oPq^sbVRTXHpgy4s-;hp8m5ZXwBZ2T*U{j=^ zS%@(|7MlMw{%gh=Cxo_W!TEYyW4u zedC{D*&G*(Zw|BZ@Z{LaLmo;ig=&2DI9p;udbUuFipP0Faw>UDr3iDLQHmkkB&3|m z`H-A8h7uWb`tJEYujl(ue6Js_*Ztdl|8U)}>;1m&>w4da7SFQpJhNBqX)?Skd!{w> z+s|t8KhFBMB)HFPuazgoMFkUmMrS^p7?|XHc$tM0y4`K>xP_JDEN(Hdb_iltnyOD$ zT4l|<_@?xWLIrZ$bO__i;sTdY0E6UJA?tTOu&c`n7ybJ<$$9~bzsY@Sdm|LwNICZ~ z))S1ve(_;A_rGR#SCjnm>OLqa7@lAf(y@yd(mk8Z%8oT6H#aWhwog}C3mwTVm^_7ly2bJ9}@EqF;?c!>ff<3{mZiCY|r*Ye>Tm8t7wdjG`WyWCQ zJZ_llwRzTmhW2=^24JuW=dwE7noBurU8Wlr+jVn=X{px5VLjQNfKexAbg5{aM7P)I5Tw)QYi1*Owq zW#wF8pI*knaVsf84Xi|*LW+m%Rn<%{b~SHwDI+4nlb`oqNm<}}gcMQw%baV=ppDsZ zlvDSbG>>Cl(W_0lF>``z*Yl3N>nu8Z(cX4lZQ&2jmz++A@Emttt1G zCO3Fzy~36R>+vVjzUfg=03W zqfTKJ{X-4FpCyb-7O((}*s!A>F&w5hP~c3H?1sc$4T8?S9Jb%~>n(k#vS&m?z9C*7 z0Hd7pT|99$otq~Ue?&%ZT=L*<`Ba@K!xmv`ohY6b0$w!iB@mmowe>FRg%LR-?MzUg zgRJ?&Ftpgvqv*($OnvHtQkODh+*xm|e_(HZSZ=j`%{x-oj?jqotxD=<&57b14?oj@ zzKpROGMSxMJlzVN!<3P^oR`)W&x71LzpWV7htgbz3S(XgQ9KNYX;rciR&n^)N#^+SxI(5y?soEnJ4P{s0jM1INu78r$(-35oxfn(vnK5so17?u4%f<&Ezxd6?0(T>^81V~&0TiF0gGMz0|r|#%4rR)ka?_`)>@pc4%W2= z+KO6Py^+3e#TXOWPPg6}lSeN2G!!LD{d{Kh zBV);Qr)<3=B6O~6N|%aR>b;PZfG(`_b>rshG z+UOj`pa>NDMWpXjmNE86_OiAdyno{ln9XXh5x-66QOM|06l?RLJhtV~ut z|0##Mx=;#aA==t`Omv02ismVj|sT%P##TEgIOg0WQa5BB?-QgnI z1TsLG=aJQ%#B)Am%RQ)ubJQys39{e|$~!_?2A_`?6^0lg`XpLW<%$&q2|-HAp4?Wg zynYa&7W3nH%JVGE48`q+K@C<48k`u^OdM=|CJ=SjP2Xh0QNQDvmYuP41#LF3oG23{v}OVbdbf#9hCp{Wm|-)D&vLrNyr{AsUPbt zF&Mw+F#28Z@N>WTm#N{yEjWvY{vYmFjd+8mzgjc0_?-Jdm?X;+4JO;*-@3@&F`b?2 z*^ZQOtgY4XC%PQkwPKTwzTlAJiSfE(KY1r1k4NkNLA)FI2sEMjSHmxy<~F(dh~)Tt z1S#D41^o$6A#M&ar!ukf@n7m1n`STfFto!`p;R$jIU1`sdCVq0WJ$~RmhI}G56D*& z>zXup(?C%Vq^8;!Z;Cx4D~)Oy?|M>mKx=*^S>cM06x2t!hhHr zA5PC^iM4&cGv7$u(STiTNI>U}tLRK`YExm$N0XW-Qj|dr!1JmmXaps}04sTBZn+3p zH^BzJ%|nmvYw7+Zi*{EP-E~}NTBnJ_y-63~2#Z8gQ>w6IlVUH(eEfnEe6Nj4>8C`f z)mw(Cd*H8|1!_$E7?Jr1t|mrzE%AA(0x%#AO4ggR-On@LMG3zrQNsE79H$9CZ! zUpF^FNqaXNu6hY}zdn`@k7w>?5rkjqF+0x&(^j}Y46vfX)|j5zKR_5+A*U;=ndHBv z#!BTy=qh2V(OM5JW7`!$LUYV$q`MIa+XF`D*H|Vl86zTE=zp|V3D(>hcQ_}E<&r}0@7V|GnWdTNU?WQNrtP2MK znURi_0~!-`cOt!7H+p{hC(JcPG?|ySW`>2Vivvn*iKiNIuQ4mVy%XEO5_$ijz9!#w z2~k$Kz8d2625*i$j6NOXj0W-Nc$QbF_uOBd5^Q*(-4Vyd(feqy`^l-!`_%QNGwi-g zVaMABp4Gfs!*yk#QIqzowMVh{CZW`2Tp|B6c;X`Pb2I!n;J6i#_@wy+I&UVD)Nq=| zhb-fh4oU+J@`}K21aOlD_m~tYW XHEM!**>HRc5ZRg2_NVA4y<`6am~uev From dc7f40a1db40731731c600c03c8767d4395cbefa Mon Sep 17 00:00:00 2001 From: Creylay Date: Thu, 2 Jul 2026 17:02:47 -0400 Subject: [PATCH 058/308] Update FCFM full name to include Universidad de Chile --- docs/static/institutions/institutions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/static/institutions/institutions.json b/docs/static/institutions/institutions.json index 52e64ea01..0bd2e44dd 100644 --- a/docs/static/institutions/institutions.json +++ b/docs/static/institutions/institutions.json @@ -13,7 +13,7 @@ { "id": "fcfm", "name": "Fcfm", - "fullName": "Facultad de Ciencias Físicas y Matemáticas", + "fullName": "Facultad de Ciencias Físicas y Matemáticas Universidad de Chile", "role": "Leading Institution", "url": "https://www.fcfm.uchile.cl/", "logo": "img/institutions/fcfm-logo.png" From 2ea09b12f7217a1b065d5be2a105297656b2eed1 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 08:58:29 -0400 Subject: [PATCH 059/308] feat: add detailed per-checkpoint descriptions with repo links --- .../models/hugging_face/pixart_sigma_model.py | 84 +++++++- .../hugging_face/stable_diffusion_v2_model.py | 170 +++++++++++++-- .../hugging_face/stable_diffusion_v3_model.py | 200 ++++++++++++++++-- .../hugging_face/stable_diffusion_xl_model.py | 85 +++++++- .../hugging_face/tongyi_z_image_model.py | 78 ++++++- 5 files changed, 547 insertions(+), 70 deletions(-) diff --git a/DashAI/back/models/hugging_face/pixart_sigma_model.py b/DashAI/back/models/hugging_face/pixart_sigma_model.py index b03ecbb96..484228ea8 100644 --- a/DashAI/back/models/hugging_face/pixart_sigma_model.py +++ b/DashAI/back/models/hugging_face/pixart_sigma_model.py @@ -521,11 +521,45 @@ class PixArtSigma1024(PixArtSigmaGenerationModel): zh="PixArt-Sigma 1024", ) DESCRIPTION = MultilingualString( - en="PixArt-Sigma XL 1024px checkpoint.", - es="PixArt-Sigma XL 1024px checkpoint.", - pt="PixArt-Sigma XL 1024px checkpoint.", - de="PixArt-Sigma XL 1024px checkpoint.", - zh="PixArt-Sigma XL 1024px checkpoint.", + en=( + "PixArt-Sigma XL by PixArt-alpha, a diffusion transformer (DiT) " + "text-to-image model that reaches quality comparable to larger diffusion " + "models with far fewer parameters. This checkpoint generates at " + "1024x1024 px. Weights are downloaded into the component's own folder. " + "Model page: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-M" + "S" + ), + es=( + "PixArt-Sigma XL de PixArt-alpha, un modelo de texto a imagen basado en " + "transformer de difusión (DiT) que alcanza una calidad comparable a " + "modelos de difusión más grandes con muchos menos parámetros. Este " + "checkpoint genera a 1024x1024 px. Los pesos se descargan en la carpeta " + "propia del componente. Página del modelo: " + "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" + ), + pt=( + "PixArt-Sigma XL de PixArt-alpha, um modelo de texto para imagem baseado " + "em transformer de difusão (DiT) que atinge qualidade comparável a " + "modelos de difusão maiores com muito menos parâmetros. Este " + "checkpoint gera a 1024x1024 px. Os pesos são baixados na pasta " + "própria do componente. Página do modelo: " + "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" + ), + de=( + "PixArt-Sigma XL von PixArt-alpha, ein Text-zu-Bild-Modell auf Basis " + "eines Diffusion-Transformers (DiT), das mit weit weniger Parametern " + "eine Qualität vergleichbar mit größeren Diffusionsmodellen erreicht. " + "Dieser Checkpoint erzeugt Bilder mit 1024x1024 px. Die Gewichte werden " + "in den eigenen Ordner der Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" + ), + zh=( + "PixArt-alpha 推出的 PixArt-Sigma XL,是一种基于扩散 " + "Transformer(DiT)的文本到图像模型,以远更少的参数量达到可媲美更大扩散模" + "型的质量。该检查点以 1024x1024 " + "像素生成。权重会下载到该组件自己的文件夹中。 模型页面: " + "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" + ), ) @@ -545,9 +579,39 @@ class PixArtSigma512(PixArtSigmaGenerationModel): zh="PixArt-Sigma 512", ) DESCRIPTION = MultilingualString( - en="PixArt-Sigma XL 512px checkpoint (faster).", - es="PixArt-Sigma XL 512px checkpoint (faster).", - pt="PixArt-Sigma XL 512px checkpoint (faster).", - de="PixArt-Sigma XL 512px checkpoint (faster).", - zh="PixArt-Sigma XL 512px checkpoint (faster).", + en=( + "PixArt-Sigma XL by PixArt-alpha at 512x512 px, a diffusion transformer " + "(DiT) text-to-image model. The lower resolution makes it faster and " + "lighter than the 1024 px variant. Weights are downloaded into the " + "component's own folder. Model page: https://huggingface.co/PixArt-alpha/" + "PixArt-Sigma-XL-2-512-MS" + ), + es=( + "PixArt-Sigma XL de PixArt-alpha a 512x512 px, un modelo de texto a " + "imagen basado en transformer de difusión (DiT). La menor resolución " + "lo hace más rápido y ligero que la variante de 1024 px. Los pesos se " + "descargan en la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" + ), + pt=( + "PixArt-Sigma XL de PixArt-alpha a 512x512 px, um modelo de texto para " + "imagem baseado em transformer de difusão (DiT). A menor resolução o " + "torna mais rápido e leve que a variante de 1024 px. Os pesos são " + "baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" + ), + de=( + "PixArt-Sigma XL von PixArt-alpha bei 512x512 px, ein " + "Text-zu-Bild-Modell auf Basis eines Diffusion-Transformers (DiT). Die " + "geringere Auflösung macht es schneller und leichter als die " + "1024-px-Variante. Die Gewichte werden in den eigenen Ordner der " + "Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" + ), + zh=( + "PixArt-alpha 推出的 PixArt-Sigma XL,分辨率为 512x512 " + "像素,是一种基于扩散 Transformer(DiT)的文本到图像模型。较低的分辨率使" + "其比 1024 像素变体更快、更轻量。权重会下载到该组件自己的文件夹中。 " + "模型页面: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" + ), ) diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py b/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py index 7d9230f46..55ff085d9 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py @@ -555,11 +555,46 @@ class StableDiffusion2(StableDiffusion2GenerationModel): zh="Stable Diffusion 2", ) DESCRIPTION = MultilingualString( - en="768px Stable Diffusion 2 checkpoint.", - es="768px Stable Diffusion 2 checkpoint.", - pt="768px Stable Diffusion 2 checkpoint.", - de="768px Stable Diffusion 2 checkpoint.", - zh="768px Stable Diffusion 2 checkpoint.", + en=( + "Stable Diffusion 2 by Stability AI, a latent text-to-image diffusion " + "model conditioned on OpenCLIP text embeddings. This checkpoint is " + "trained at 768x768 px and produces sharp, high-detail images. Weights " + "are downloaded into the component's own folder from the sd2-community " + "mirror. Model page: https://huggingface.co/sd2-community/stable-diffusio" + "n-2" + ), + es=( + "Stable Diffusion 2 de Stability AI, un modelo de difusión latente de " + "texto a imagen condicionado en embeddings de texto OpenCLIP. Este " + "checkpoint se entrena a 768x768 px y produce imágenes nítidas y muy " + "detalladas. Los pesos se descargan en la carpeta propia del componente " + "desde el espejo sd2-community. Página del modelo: " + "https://huggingface.co/sd2-community/stable-diffusion-2" + ), + pt=( + "Stable Diffusion 2 da Stability AI, um modelo de difusão latente de " + "texto para imagem condicionado em embeddings de texto OpenCLIP. Este " + "checkpoint é treinado a 768x768 px e produz imagens nítidas e com " + "muitos detalhes. Os pesos são baixados na pasta própria do componente " + "a partir do espelho sd2-community. Página do modelo: " + "https://huggingface.co/sd2-community/stable-diffusion-2" + ), + de=( + "Stable Diffusion 2 von Stability AI, ein latentes " + "Text-zu-Bild-Diffusionsmodell, das auf OpenCLIP-Texteinbettungen " + "konditioniert ist. Dieser Checkpoint wird bei 768x768 px trainiert und " + "erzeugt scharfe, detailreiche Bilder. Die Gewichte werden aus dem " + "sd2-community-Spiegel in den eigenen Ordner der Komponente " + "heruntergeladen. Modellseite: https://huggingface.co/sd2-community/stabl" + "e-diffusion-2" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 2,是一种以 OpenCLIP " + "文本嵌入为条件的潜在文本到图像扩散模型。该检查点在 768x768 " + "像素下训练,可生成清晰且细节丰富的图像。权重会从 sd2-community " + "镜像下载到该组件自己的文件夹中。 模型页面: https://huggingface.co/sd2-c" + "ommunity/stable-diffusion-2" + ), ) @@ -580,11 +615,41 @@ class StableDiffusion2_512(StableDiffusion2GenerationModel): # noqa: N801 zh="Stable Diffusion 2 (512px)", ) DESCRIPTION = MultilingualString( - en="512px base Stable Diffusion 2 checkpoint (faster).", - es="512px base Stable Diffusion 2 checkpoint (faster).", - pt="512px base Stable Diffusion 2 checkpoint (faster).", - de="512px base Stable Diffusion 2 checkpoint (faster).", - zh="512px base Stable Diffusion 2 checkpoint (faster).", + en=( + "Stable Diffusion 2 base checkpoint by Stability AI, trained at 512x512 " + "px. It is faster and uses less memory than the 768 px variant, making " + "it a good choice for rapid prototyping. Weights are downloaded into the " + "component's own folder from the sd2-community mirror. Model page: " + "https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + es=( + "Checkpoint base de Stable Diffusion 2 de Stability AI, entrenado a " + "512x512 px. Es más rápido y usa menos memoria que la variante de 768 " + "px, ideal para prototipado rápido. Los pesos se descargan en la " + "carpeta propia del componente desde el espejo sd2-community. Página " + "del modelo: https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + pt=( + "Checkpoint base do Stable Diffusion 2 da Stability AI, treinado a " + "512x512 px. É mais rápido e usa menos memória que a variante de 768 " + "px, ideal para prototipagem rápida. Os pesos são baixados na pasta " + "própria do componente a partir do espelho sd2-community. Página do " + "modelo: https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + de=( + "Stable Diffusion 2 Basis-Checkpoint von Stability AI, trainiert bei " + "512x512 px. Er ist schneller und benötigt weniger Speicher als die " + "768-px-Variante und eignet sich gut für schnelles Prototyping. Die " + "Gewichte werden aus dem sd2-community-Spiegel in den eigenen Ordner der " + "Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 2 基础检查点,在 512x512 " + "像素下训练。相比 768 像素变体速度更快、显存占用更低,非常适合快速原型设" + "计。权重会从 sd2-community 镜像下载到该组件自己的文件夹中。 模型页面: " + "https://huggingface.co/sd2-community/stable-diffusion-2-base" + ), ) @@ -605,11 +670,42 @@ class StableDiffusion21(StableDiffusion2GenerationModel): zh="Stable Diffusion 2.1", ) DESCRIPTION = MultilingualString( - en="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", - es="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", - pt="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", - de="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", - zh="768px Stable Diffusion 2.1 checkpoint (further fine-tuned).", + en=( + "Stable Diffusion 2.1 by Stability AI, a further fine-tuned revision of " + "the 2.x family trained at 768x768 px. It generally produces cleaner, " + "more coherent images than the original 2.0. Weights are downloaded into " + "the component's own folder from the sd2-community mirror. Model page: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1" + ), + es=( + "Stable Diffusion 2.1 de Stability AI, una revisión más ajustada de la " + "familia 2.x entrenada a 768x768 px. Suele producir imágenes más " + "limpias y coherentes que la 2.0 original. Los pesos se descargan en la " + "carpeta propia del componente desde el espejo sd2-community. Página " + "del modelo: https://huggingface.co/sd2-community/stable-diffusion-2-1" + ), + pt=( + "Stable Diffusion 2.1 da Stability AI, uma revisão mais ajustada da " + "família 2.x treinada a 768x768 px. Costuma produzir imagens mais " + "limpas e coerentes que a 2.0 original. Os pesos são baixados na pasta " + "própria do componente a partir do espelho sd2-community. Página do " + "modelo: https://huggingface.co/sd2-community/stable-diffusion-2-1" + ), + de=( + "Stable Diffusion 2.1 von Stability AI, eine weiter feinabgestimmte " + "Überarbeitung der 2.x-Familie, trainiert bei 768x768 px. Sie erzeugt " + "in der Regel sauberere, kohärentere Bilder als die ursprüngliche 2.0. " + "Die Gewichte werden aus dem sd2-community-Spiegel in den eigenen Ordner " + "der Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 2.1,是 2.x " + "系列的进一步微调版本,在 768x768 像素下训练。通常比原始的 2.0 " + "生成更干净、更连贯的图像。权重会从 sd2-community " + "镜像下载到该组件自己的文件夹中。 模型页面: https://huggingface.co/sd2-c" + "ommunity/stable-diffusion-2-1" + ), ) @@ -630,9 +726,43 @@ class StableDiffusion21_512(StableDiffusion2GenerationModel): # noqa: N801 zh="Stable Diffusion 2.1 (512px)", ) DESCRIPTION = MultilingualString( - en="512px base Stable Diffusion 2.1 checkpoint.", - es="512px base Stable Diffusion 2.1 checkpoint.", - pt="512px base Stable Diffusion 2.1 checkpoint.", - de="512px base Stable Diffusion 2.1 checkpoint.", - zh="512px base Stable Diffusion 2.1 checkpoint.", + en=( + "Stable Diffusion 2.1 base checkpoint by Stability AI, trained at " + "512x512 px. It combines the 2.1 fine-tuning improvements with the lower " + "memory footprint and faster generation of the 512 px base models. " + "Weights are downloaded into the component's own folder from the " + "sd2-community mirror. Model page: https://huggingface.co/sd2-community/s" + "table-diffusion-2-1-base" + ), + es=( + "Checkpoint base de Stable Diffusion 2.1 de Stability AI, entrenado a " + "512x512 px. Combina las mejoras de ajuste de la 2.1 con el menor " + "consumo de memoria y la generación más rápida de los modelos base de " + "512 px. Los pesos se descargan en la carpeta propia del componente " + "desde el espejo sd2-community. Página del modelo: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1-base" + ), + pt=( + "Checkpoint base do Stable Diffusion 2.1 da Stability AI, treinado a " + "512x512 px. Combina as melhorias de ajuste da 2.1 com o menor uso de " + "memória e a geração mais rápida dos modelos base de 512 px. Os " + "pesos são baixados na pasta própria do componente a partir do espelho " + "sd2-community. Página do modelo: https://huggingface.co/sd2-community/s" + "table-diffusion-2-1-base" + ), + de=( + "Stable Diffusion 2.1 Basis-Checkpoint von Stability AI, trainiert bei " + "512x512 px. Er verbindet die Feinabstimmungs-Verbesserungen von 2.1 mit " + "dem geringeren Speicherbedarf und der schnelleren Generierung der " + "512-px-Basismodelle. Die Gewichte werden aus dem sd2-community-Spiegel " + "in den eigenen Ordner der Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1-base" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 2.1 基础检查点,在 512x512 " + "像素下训练。它将 2.1 的微调改进与 512 " + "像素基础模型更低的显存占用和更快的生成速度相结合。权重会从 " + "sd2-community 镜像下载到该组件自己的文件夹中。 模型页面: " + "https://huggingface.co/sd2-community/stable-diffusion-2-1-base" + ), ) diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py b/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py index 979cc79b9..3913d6134 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py @@ -576,11 +576,52 @@ class StableDiffusion3Medium(StableDiffusion3GenerationModel): zh="Stable Diffusion 3 Medium", ) DESCRIPTION = MultilingualString( - en="Stable Diffusion 3 Medium checkpoint (gated).", - es="Stable Diffusion 3 Medium checkpoint (gated).", - pt="Stable Diffusion 3 Medium checkpoint (gated).", - de="Stable Diffusion 3 Medium checkpoint (gated).", - zh="Stable Diffusion 3 Medium checkpoint (gated).", + en=( + "Stable Diffusion 3 Medium by Stability AI, built on the Multimodal " + "Diffusion Transformer (MMDiT) architecture with markedly improved text " + "rendering and prompt adherence over SD2. This is a gated Hugging Face " + "repo, so downloading requires prior authentication with an access " + "token. Weights are downloaded into the component's own folder. Model " + "page: https://huggingface.co/stabilityai/stable-diffusion-3-medium-diffu" + "sers" + ), + es=( + "Stable Diffusion 3 Medium de Stability AI, construido sobre la " + "arquitectura Multimodal Diffusion Transformer (MMDiT) con una " + "representación de texto y adherencia al prompt notablemente mejores " + "que SD2. Es un repositorio restringido de Hugging Face, por lo que la " + "descarga requiere autenticación previa con un token de acceso. Los " + "pesos se descargan en la carpeta propia del componente. Página del " + "modelo: https://huggingface.co/stabilityai/stable-diffusion-3-medium-dif" + "fusers" + ), + pt=( + "Stable Diffusion 3 Medium da Stability AI, construído sobre a " + "arquitetura Multimodal Diffusion Transformer (MMDiT) com renderização " + "de texto e aderência ao prompt bem melhores que o SD2. É um " + "repositório restrito do Hugging Face, portanto o download requer " + "autenticação prévia com um token de acesso. Os pesos são baixados " + "na pasta própria do componente. Página do modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3-medium-diffusers" + ), + de=( + "Stable Diffusion 3 Medium von Stability AI, basierend auf der " + "Multimodal-Diffusion-Transformer-Architektur (MMDiT) mit deutlich " + "verbesserter Textwiedergabe und Prompt-Treue gegenüber SD2. Dies ist " + "ein zugangsbeschränktes Hugging-Face-Repository, daher erfordert der " + "Download eine vorherige Authentifizierung mit einem Zugriffstoken. Die " + "Gewichte werden in den eigenen Ordner der Komponente heruntergeladen. " + "Modellseite: https://huggingface.co/stabilityai/stable-diffusion-3-mediu" + "m-diffusers" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 3 Medium,基于多模态扩散 " + "Transformer(MMDiT)架构,相比 SD2 " + "在文本渲染和提示词遵循方面有显著提升。这是一个受限的 Hugging Face " + "仓库,因此下载前需要使用访问令牌进行身份验证。权重会下载到该组件自己的文" + "件夹中。 模型页面: https://huggingface.co/stabilityai/stable-diffusion-" + "3-medium-diffusers" + ), ) @@ -602,11 +643,50 @@ class StableDiffusion35Medium(StableDiffusion3GenerationModel): zh="Stable Diffusion 3.5 Medium", ) DESCRIPTION = MultilingualString( - en="Stable Diffusion 3.5 Medium checkpoint (gated).", - es="Stable Diffusion 3.5 Medium checkpoint (gated).", - pt="Stable Diffusion 3.5 Medium checkpoint (gated).", - de="Stable Diffusion 3.5 Medium checkpoint (gated).", - zh="Stable Diffusion 3.5 Medium checkpoint (gated).", + en=( + "Stable Diffusion 3.5 Medium by Stability AI, an updated MMDiT model " + "that balances image quality against hardware requirements, running " + "comfortably on consumer GPUs. This is a gated Hugging Face repo, so " + "downloading requires prior authentication with an access token. Weights " + "are downloaded into the component's own folder. Model page: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-medium" + ), + es=( + "Stable Diffusion 3.5 Medium de Stability AI, un modelo MMDiT " + "actualizado que equilibra la calidad de imagen con los requisitos de " + "hardware y funciona bien en GPUs de consumo. Es un repositorio " + "restringido de Hugging Face, por lo que la descarga requiere " + "autenticación previa con un token de acceso. Los pesos se descargan en " + "la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-medium" + ), + pt=( + "Stable Diffusion 3.5 Medium da Stability AI, um modelo MMDiT atualizado " + "que equilibra a qualidade da imagem com os requisitos de hardware e " + "roda bem em GPUs de consumo. É um repositório restrito do Hugging " + "Face, portanto o download requer autenticação prévia com um token de " + "acesso. Os pesos são baixados na pasta própria do componente. Página " + "do modelo: https://huggingface.co/stabilityai/stable-diffusion-3.5-mediu" + "m" + ), + de=( + "Stable Diffusion 3.5 Medium von Stability AI, ein aktualisiertes " + "MMDiT-Modell, das Bildqualität und Hardwareanforderungen ausbalanciert " + "und komfortabel auf Consumer-GPUs läuft. Dies ist ein " + "zugangsbeschränktes Hugging-Face-Repository, daher erfordert der " + "Download eine vorherige Authentifizierung mit einem Zugriffstoken. Die " + "Gewichte werden in den eigenen Ordner der Komponente heruntergeladen. " + "Modellseite: https://huggingface.co/stabilityai/stable-diffusion-3.5-med" + "ium" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 3.5 Medium,是更新的 MMDiT " + "模型,在图像质量与硬件需求之间取得平衡,可在消费级 GPU " + "上流畅运行。这是一个受限的 Hugging Face " + "仓库,因此下载前需要使用访问令牌进行身份验证。权重会下载到该组件自己的文" + "件夹中。 模型页面: https://huggingface.co/stabilityai/stable-diffusion-" + "3.5-medium" + ), ) @@ -628,11 +708,51 @@ class StableDiffusion35Large(StableDiffusion3GenerationModel): zh="Stable Diffusion 3.5 Large", ) DESCRIPTION = MultilingualString( - en="Stable Diffusion 3.5 Large checkpoint (gated).", - es="Stable Diffusion 3.5 Large checkpoint (gated).", - pt="Stable Diffusion 3.5 Large checkpoint (gated).", - de="Stable Diffusion 3.5 Large checkpoint (gated).", - zh="Stable Diffusion 3.5 Large checkpoint (gated).", + en=( + "Stable Diffusion 3.5 Large by Stability AI, the highest quality MMDiT " + "model in the 3.5 family, offering the strongest detail and prompt " + "adherence at the cost of more memory and slower generation. This is a " + "gated Hugging Face repo, so downloading requires prior authentication " + "with an access token. Weights are downloaded into the component's own " + "folder. Model page: https://huggingface.co/stabilityai/stable-diffusion-" + "3.5-large" + ), + es=( + "Stable Diffusion 3.5 Large de Stability AI, el modelo MMDiT de mayor " + "calidad de la familia 3.5, con el mejor detalle y adherencia al prompt " + "a costa de más memoria y una generación más lenta. Es un repositorio " + "restringido de Hugging Face, por lo que la descarga requiere " + "autenticación previa con un token de acceso. Los pesos se descargan en " + "la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large" + ), + pt=( + "Stable Diffusion 3.5 Large da Stability AI, o modelo MMDiT de maior " + "qualidade da família 3.5, oferecendo o melhor detalhe e aderência ao " + "prompt ao custo de mais memória e geração mais lenta. É um " + "repositório restrito do Hugging Face, portanto o download requer " + "autenticação prévia com um token de acesso. Os pesos são baixados " + "na pasta própria do componente. Página do modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large" + ), + de=( + "Stable Diffusion 3.5 Large von Stability AI, das qualitativ " + "hochwertigste MMDiT-Modell der 3.5-Familie, das beste Detailtreue und " + "Prompt-Treue bietet, allerdings auf Kosten von mehr Speicher und " + "langsamerer Generierung. Dies ist ein zugangsbeschränktes " + "Hugging-Face-Repository, daher erfordert der Download eine vorherige " + "Authentifizierung mit einem Zugriffstoken. Die Gewichte werden in den " + "eigenen Ordner der Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 3.5 Large,是 3.5 系列中质量最高的 " + "MMDiT 模型,提供最强的细节和提示词遵循能力,代价是更高的显存占用和更慢的" + "生成速度。这是一个受限的 Hugging Face " + "仓库,因此下载前需要使用访问令牌进行身份验证。权重会下载到该组件自己的文" + "件夹中。 模型页面: https://huggingface.co/stabilityai/stable-diffusion-" + "3.5-large" + ), ) @@ -654,9 +774,49 @@ class StableDiffusion35LargeTurbo(StableDiffusion3GenerationModel): zh="Stable Diffusion 3.5 Large Turbo", ) DESCRIPTION = MultilingualString( - en="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", - es="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", - pt="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", - de="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", - zh="Stable Diffusion 3.5 Large Turbo checkpoint (gated).", + en=( + "Stable Diffusion 3.5 Large Turbo by Stability AI, a distilled version " + "of 3.5 Large that produces high quality images in only a handful of " + "denoising steps for much faster generation. This is a gated Hugging " + "Face repo, so downloading requires prior authentication with an access " + "token. Weights are downloaded into the component's own folder. Model " + "page: https://huggingface.co/stabilityai/stable-diffusion-3.5-large-turb" + "o" + ), + es=( + "Stable Diffusion 3.5 Large Turbo de Stability AI, una versión " + "destilada de 3.5 Large que produce imágenes de alta calidad en apenas " + "unos pocos pasos de denoising para una generación mucho más rápida. " + "Es un repositorio restringido de Hugging Face, por lo que la descarga " + "requiere autenticación previa con un token de acceso. Los pesos se " + "descargan en la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large-turbo" + ), + pt=( + "Stable Diffusion 3.5 Large Turbo da Stability AI, uma versão destilada " + "do 3.5 Large que produz imagens de alta qualidade em apenas alguns " + "passos de denoising para uma geração muito mais rápida. É um " + "repositório restrito do Hugging Face, portanto o download requer " + "autenticação prévia com um token de acesso. Os pesos são baixados " + "na pasta própria do componente. Página do modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-3.5-large-turbo" + ), + de=( + "Stable Diffusion 3.5 Large Turbo von Stability AI, eine destillierte " + "Version von 3.5 Large, die hochwertige Bilder in nur wenigen " + "Entrauschungsschritten für eine deutlich schnellere Generierung " + "erzeugt. Dies ist ein zugangsbeschränktes Hugging-Face-Repository, " + "daher erfordert der Download eine vorherige Authentifizierung mit einem " + "Zugriffstoken. Die Gewichte werden in den eigenen Ordner der Komponente " + "heruntergeladen. Modellseite: https://huggingface.co/stabilityai/stable-" + "diffusion-3.5-large-turbo" + ), + zh=( + "Stability AI 推出的 Stable Diffusion 3.5 Large Turbo,是 3.5 Large " + "的蒸馏版本,仅需少数几个去噪步骤即可生成高质量图像,从而大幅加快生成速度" + "。这是一个受限的 Hugging Face " + "仓库,因此下载前需要使用访问令牌进行身份验证。权重会下载到该组件自己的文" + "件夹中。 模型页面: https://huggingface.co/stabilityai/stable-diffusion-" + "3.5-large-turbo" + ), ) diff --git a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py index 6a7944911..da0ec4463 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py @@ -503,11 +503,46 @@ class StableDiffusionXL(StableDiffusionXLGenerationModel): zh="Stable Diffusion XL", ) DESCRIPTION = MultilingualString( - en="Stable Diffusion XL base 1.0 checkpoint.", - es="Stable Diffusion XL base 1.0 checkpoint.", - pt="Stable Diffusion XL base 1.0 checkpoint.", - de="Stable Diffusion XL base 1.0 checkpoint.", - zh="Stable Diffusion XL base 1.0 checkpoint.", + en=( + "Stable Diffusion XL base 1.0 by Stability AI, a large latent diffusion " + "model that uses two text encoders and a 1024x1024 px native resolution " + "for high fidelity results. It is well suited to detailed, " + "photorealistic and artistic prompts. Weights are downloaded into the " + "component's own folder. Model page: https://huggingface.co/stabilityai/s" + "table-diffusion-xl-base-1.0" + ), + es=( + "Stable Diffusion XL base 1.0 de Stability AI, un modelo de difusión " + "latente grande que usa dos codificadores de texto y una resolución " + "nativa de 1024x1024 px para resultados de alta fidelidad. Es adecuado " + "para prompts detallados, fotorrealistas y artísticos. Los pesos se " + "descargan en la carpeta propia del componente. Página del modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0" + ), + pt=( + "Stable Diffusion XL base 1.0 da Stability AI, um grande modelo de " + "difusão latente que usa dois codificadores de texto e resolução " + "nativa de 1024x1024 px para resultados de alta fidelidade. É adequado " + "para prompts detalhados, fotorrealistas e artísticos. Os pesos são " + "baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0" + ), + de=( + "Stable Diffusion XL Basis 1.0 von Stability AI, ein großes latentes " + "Diffusionsmodell mit zwei Textencodern und einer nativen Auflösung von " + "1024x1024 px für Ergebnisse mit hoher Detailtreue. Es eignet sich gut " + "für detaillierte, fotorealistische und künstlerische Prompts. Die " + "Gewichte werden in den eigenen Ordner der Komponente heruntergeladen. " + "Modellseite: https://huggingface.co/stabilityai/stable-diffusion-xl-base" + "-1.0" + ), + zh=( + "Stability AI 推出的 Stable Diffusion XL base " + "1.0,是一种大型潜在扩散模型,使用两个文本编码器和 1024x1024 " + "像素的原生分辨率以获得高保真结果。非常适合细致、写实和艺术性的提示词。权" + "重会下载到该组件自己的文件夹中。 模型页面: https://huggingface.co/stabi" + "lityai/stable-diffusion-xl-base-1.0" + ), ) @@ -528,9 +563,39 @@ class RealVisXLV4(StableDiffusionXLGenerationModel): zh="RealVisXL V4.0", ) DESCRIPTION = MultilingualString( - en="RealVisXL V4.0 photorealistic SDXL checkpoint.", - es="RealVisXL V4.0 photorealistic SDXL checkpoint.", - pt="RealVisXL V4.0 photorealistic SDXL checkpoint.", - de="RealVisXL V4.0 photorealistic SDXL checkpoint.", - zh="RealVisXL V4.0 photorealistic SDXL checkpoint.", + en=( + "RealVisXL V4.0 by SG161222, a community fine-tune of Stable Diffusion " + "XL focused on photorealism. It excels at lifelike portraits, lighting " + "and textures while remaining compatible with the SDXL pipeline. Weights " + "are downloaded into the component's own folder. Model page: " + "https://huggingface.co/SG161222/RealVisXL_V4.0" + ), + es=( + "RealVisXL V4.0 de SG161222, un ajuste comunitario de Stable Diffusion " + "XL enfocado en el fotorrealismo. Destaca en retratos, iluminación y " + "texturas realistas, manteniéndose compatible con el pipeline de SDXL. " + "Los pesos se descargan en la carpeta propia del componente. Página del " + "modelo: https://huggingface.co/SG161222/RealVisXL_V4.0" + ), + pt=( + "RealVisXL V4.0 de SG161222, um ajuste comunitário do Stable Diffusion " + "XL focado em fotorrealismo. Destaca-se em retratos, iluminação e " + "texturas realistas, mantendo compatibilidade com o pipeline do SDXL. Os " + "pesos são baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/SG161222/RealVisXL_V4.0" + ), + de=( + "RealVisXL V4.0 von SG161222, eine Community-Feinabstimmung von Stable " + "Diffusion XL mit Fokus auf Fotorealismus. Es glänzt bei lebensechten " + "Porträts, Beleuchtung und Texturen und bleibt mit der SDXL-Pipeline " + "kompatibel. Die Gewichte werden in den eigenen Ordner der Komponente " + "heruntergeladen. Modellseite: https://huggingface.co/SG161222/RealVisXL_" + "V4.0" + ), + zh=( + "SG161222 推出的 RealVisXL V4.0,是 Stable Diffusion XL " + "的社区微调版本,专注于照片级真实感。擅长逼真的人像、光照和纹理,同时保持" + "与 SDXL 流水线的兼容。权重会下载到该组件自己的文件夹中。 模型页面: " + "https://huggingface.co/SG161222/RealVisXL_V4.0" + ), ) diff --git a/DashAI/back/models/hugging_face/tongyi_z_image_model.py b/DashAI/back/models/hugging_face/tongyi_z_image_model.py index 94bb0bebc..4f3cd9bfe 100644 --- a/DashAI/back/models/hugging_face/tongyi_z_image_model.py +++ b/DashAI/back/models/hugging_face/tongyi_z_image_model.py @@ -454,11 +454,40 @@ class TongyiZImage(TongyiZImageGenerationModel): zh="Tongyi Z-Image", ) DESCRIPTION = MultilingualString( - en="Tongyi Z-Image text-to-image checkpoint.", - es="Tongyi Z-Image text-to-image checkpoint.", - pt="Tongyi Z-Image text-to-image checkpoint.", - de="Tongyi Z-Image text-to-image checkpoint.", - zh="Tongyi Z-Image text-to-image checkpoint.", + en=( + "Z-Image by Alibaba's Tongyi lab, a modern text-to-image diffusion model " + "with strong prompt following and multilingual support. This is the " + "standard, full-quality checkpoint. Weights are downloaded into the " + "component's own folder. Model page: https://huggingface.co/Tongyi-MAI/Z-" + "Image" + ), + es=( + "Z-Image del laboratorio Tongyi de Alibaba, un modelo moderno de " + "difusión de texto a imagen con buen seguimiento de prompts y soporte " + "multilingüe. Este es el checkpoint estándar de máxima calidad. Los " + "pesos se descargan en la carpeta propia del componente. Página del " + "modelo: https://huggingface.co/Tongyi-MAI/Z-Image" + ), + pt=( + "Z-Image do laboratório Tongyi da Alibaba, um modelo moderno de " + "difusão de texto para imagem com bom seguimento de prompts e suporte " + "multilíngue. Este é o checkpoint padrão de qualidade máxima. Os " + "pesos são baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/Tongyi-MAI/Z-Image" + ), + de=( + "Z-Image aus Alibabas Tongyi-Labor, ein modernes " + "Text-zu-Bild-Diffusionsmodell mit guter Prompt-Befolgung und " + "mehrsprachiger Unterstützung. Dies ist der standardmäßige Checkpoint " + "in voller Qualität. Die Gewichte werden in den eigenen Ordner der " + "Komponente heruntergeladen. Modellseite: " + "https://huggingface.co/Tongyi-MAI/Z-Image" + ), + zh=( + "阿里巴巴通义实验室推出的 Z-Image,是一种现代文本到图像扩散模型,具有出色" + "的提示词遵循能力和多语言支持。这是标准的全质量检查点。权重会下载到该组件" + "自己的文件夹中。 模型页面: https://huggingface.co/Tongyi-MAI/Z-Image" + ), ) @@ -478,9 +507,38 @@ class TongyiZImageTurbo(TongyiZImageGenerationModel): zh="Tongyi Z-Image Turbo", ) DESCRIPTION = MultilingualString( - en="Tongyi Z-Image Turbo fast checkpoint.", - es="Tongyi Z-Image Turbo fast checkpoint.", - pt="Tongyi Z-Image Turbo fast checkpoint.", - de="Tongyi Z-Image Turbo fast checkpoint.", - zh="Tongyi Z-Image Turbo fast checkpoint.", + en=( + "Z-Image Turbo by Alibaba's Tongyi lab, a distilled variant of Z-Image " + "that generates images in far fewer denoising steps. It trades a little " + "quality for much faster generation. Weights are downloaded into the " + "component's own folder. Model page: https://huggingface.co/Tongyi-MAI/Z-" + "Image-Turbo" + ), + es=( + "Z-Image Turbo del laboratorio Tongyi de Alibaba, una variante destilada " + "de Z-Image que genera imágenes en muchos menos pasos de denoising. " + "Sacrifica algo de calidad a cambio de una generación mucho más " + "rápida. Los pesos se descargan en la carpeta propia del componente. " + "Página del modelo: https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" + ), + pt=( + "Z-Image Turbo do laboratório Tongyi da Alibaba, uma variante destilada " + "do Z-Image que gera imagens em muito menos passos de denoising. Troca " + "um pouco de qualidade por uma geração muito mais rápida. Os pesos " + "são baixados na pasta própria do componente. Página do modelo: " + "https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" + ), + de=( + "Z-Image Turbo aus Alibabas Tongyi-Labor, eine destillierte Variante von " + "Z-Image, die Bilder in weit weniger Entrauschungsschritten erzeugt. Sie " + "opfert etwas Qualität für eine deutlich schnellere Generierung. Die " + "Gewichte werden in den eigenen Ordner der Komponente heruntergeladen. " + "Modellseite: https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" + ), + zh=( + "阿里巴巴通义实验室推出的 Z-Image Turbo,是 Z-Image " + "的蒸馏变体,可用更少的去噪步骤生成图像。以少量质量换取快得多的生成速度。" + "权重会下载到该组件自己的文件夹中。 模型页面: https://huggingface.co/Ton" + "gyi-MAI/Z-Image-Turbo" + ), ) From c63b9c355f67904f88ad9d86e59692dd4b3b2ce9 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 09:07:20 -0400 Subject: [PATCH 060/308] fix: update model download state in place to avoid list scroll reset --- .../src/components/custom/ComponentSelector.jsx | 4 +++- .../components/generative/CreateSessionCenter.jsx | 6 ++++-- .../components/generative/CreateSessionContext.jsx | 12 ++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/DashAI/front/src/components/custom/ComponentSelector.jsx b/DashAI/front/src/components/custom/ComponentSelector.jsx index 997b9790d..d3c00539d 100644 --- a/DashAI/front/src/components/custom/ComponentSelector.jsx +++ b/DashAI/front/src/components/custom/ComponentSelector.jsx @@ -185,7 +185,9 @@ function ComponentSelector({ e.stopPropagation()}> onDownloadChange?.(component)} + onStatusChange={(isDownloaded) => + onDownloadChange?.(component, isDownloaded) + } /> )} diff --git a/DashAI/front/src/components/generative/CreateSessionCenter.jsx b/DashAI/front/src/components/generative/CreateSessionCenter.jsx index f15ce47ea..6a455aa80 100644 --- a/DashAI/front/src/components/generative/CreateSessionCenter.jsx +++ b/DashAI/front/src/components/generative/CreateSessionCenter.jsx @@ -29,7 +29,7 @@ export default function CreateSessionCenter() { step, models, loadingModels, - refetchModels, + markModelDownloaded, selectedModel, handleSelectModel, formik, @@ -127,7 +127,9 @@ export default function CreateSessionCenter() { components={models} selected={selectedModel} onSelect={handleSelectModelWithTour} - onDownloadChange={() => refetchModels()} + onDownloadChange={(model, isDownloaded) => + markModelDownloaded(model.name, isDownloaded) + } categoryKey="task_display_name" searchPlaceholder={t("generative:label.searchModels")} tourDataFor={tourContext?.run ? "model-card-qwen" : null} diff --git a/DashAI/front/src/components/generative/CreateSessionContext.jsx b/DashAI/front/src/components/generative/CreateSessionContext.jsx index 5aa188a24..3706e054c 100644 --- a/DashAI/front/src/components/generative/CreateSessionContext.jsx +++ b/DashAI/front/src/components/generative/CreateSessionContext.jsx @@ -235,11 +235,23 @@ export function CreateSessionProvider({ children }) { formik.submitForm(); }; + // Flip a single model's downloaded flag in place. Used when an inline + // download/delete finishes so the list updates without a full refetch + // (which would swap in the loading spinner and reset the scroll position). + const markModelDownloaded = useCallback((name, isDownloaded) => { + setModels((prev) => + prev.map((m) => + m.name === name ? { ...m, downloaded: isDownloaded } : m, + ), + ); + }, []); + const value = { step, models, loadingModels, refetchModels: loadModels, + markModelDownloaded, selectedModel, handleSelectModel, formik, From eb36dfbe4d7de5c9bb504e51ac93f496bf3411ab Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:18:13 -0400 Subject: [PATCH 061/308] feat: add collector for nested downloadable components Walk a parameters dict for components selected as another component's parameter (at any depth) and report which still need downloading, reusing the same unwrap logic ModelFactory uses to build the model graph. --- DashAI/back/dependencies/downloads/nested.py | 143 ++++++++++++++++++ .../dependencies/test_nested_downloads.py | 103 +++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 DashAI/back/dependencies/downloads/nested.py create mode 100644 tests/back/dependencies/test_nested_downloads.py diff --git a/DashAI/back/dependencies/downloads/nested.py b/DashAI/back/dependencies/downloads/nested.py new file mode 100644 index 000000000..45b65c973 --- /dev/null +++ b/DashAI/back/dependencies/downloads/nested.py @@ -0,0 +1,143 @@ +"""Discover nested downloadable components inside a parameters dict. + +A DashAI component may take another component as a parameter (a +``component_field``). That child may itself require a download, and the child +may in turn nest further components. This module walks a parameters dict the +same way :class:`~DashAI.back.models.model_factory.ModelFactory` does when it +instantiates the model graph, so the set of components it reports matches the +set that would actually be built. + +Two value shapes are handled, mirroring ``ModelFactory._process_param``: + +* the canonical ``{"component": , "params": {...}}`` descriptor, and +* the frontend-wrapped ``{"properties": {"component": ..., "params": ...}}``. +""" + +from typing import Any, Dict, Iterator, List, Optional, Tuple + + +def _unwrap(value: Any) -> Any: + """Strip the single-key ``properties`` wrapper the frontend adds. + + Parameters + ---------- + value : Any + A parameter value as stored in a parameters dict. + + Returns + ------- + Any + ``value["properties"]`` when ``value`` is a ``{"properties": ...}`` + wrapper, otherwise ``value`` unchanged. + """ + if isinstance(value, dict) and "properties" in value and len(value) == 1: + return value["properties"] + return value + + +def _iter_value( + value: Any, parent: Optional[str] +) -> Iterator[Tuple[str, Optional[str]]]: + """Yield ``(component_name, parent_name)`` for a single parameter value. + + Recurses into the selected component's own parameters so components nested + at any depth are reported. + + Parameters + ---------- + value : Any + A parameter value, possibly a nested component descriptor. + parent : str or None + Name of the component that owns this value, used as the ``parent`` of + any component found directly inside it. + + Yields + ------ + tuple of (str, str or None) + The nested component name and the name of its enclosing component. + """ + value = _unwrap(value) + if not (isinstance(value, dict) and "component" in value): + return + + parent_component_name = value["component"] + inner = value.get("params", {}).get("comp", {}) + if inner == {}: + name = parent_component_name + params = value.get("params", {}) + else: + name = inner.get("component") + params = inner.get("params", {}) + + if name: + yield name, parent + if isinstance(params, dict): + for sub_value in params.values(): + yield from _iter_value(sub_value, name) + + +def iter_config_components( + parameters: Dict[str, Any], +) -> Iterator[Tuple[str, Optional[str]]]: + """Yield ``(component_name, parent_name)`` for every nested component. + + Parameters + ---------- + parameters : dict + A parameters dict as produced by the DashAI configuration UI. + + Yields + ------ + tuple of (str, str or None) + Each nested component name paired with its enclosing component name + (``None`` at the top level). + """ + for value in parameters.values(): + yield from _iter_value(value, None) + + +def missing_downloads( + parameters: Dict[str, Any], + component_registry, +) -> List[Dict[str, Any]]: + """Return metadata for nested components that still need downloading. + + Each candidate is reconciled against the filesystem via + ``refresh_download_status`` so a component downloaded after startup (in the + worker process) is recognised without an API restart. + + Parameters + ---------- + parameters : dict + A parameters dict as produced by the DashAI configuration UI. + component_registry : ComponentRegistry + The registry used to resolve component classes and download state. + + Returns + ------- + list of dict + One entry per not-yet-downloaded nested component, each with + ``name``, ``parent``, and ``download_size_bytes`` keys. Empty when + every nested download-required component is already present. + """ + missing: List[Dict[str, Any]] = [] + seen = set() + for name, parent in iter_config_components(parameters): + if name in seen or name not in component_registry: + continue + seen.add(name) + component_class = component_registry[name]["class"] + if not getattr(component_class, "REQUIRES_DOWNLOAD", False): + continue + if component_registry.refresh_download_status(name): + continue + missing.append( + { + "name": name, + "parent": parent, + "download_size_bytes": getattr( + component_class, "DOWNLOAD_SIZE_BYTES", None + ), + } + ) + return missing diff --git a/tests/back/dependencies/test_nested_downloads.py b/tests/back/dependencies/test_nested_downloads.py new file mode 100644 index 000000000..9b196566f --- /dev/null +++ b/tests/back/dependencies/test_nested_downloads.py @@ -0,0 +1,103 @@ +"""Tests for nested downloadable-component discovery.""" + +from DashAI.back.dependencies.downloads.nested import ( + iter_config_components, + missing_downloads, +) + + +class _Comp: + """Stand-in component class carrying only download-related attributes.""" + + def __init__(self, requires, size=None): + self.REQUIRES_DOWNLOAD = requires + self.DOWNLOAD_SIZE_BYTES = size + + +class _FakeRegistry: + """Minimal registry: maps names to component classes and download state.""" + + def __init__(self, classes, downloaded): + self._classes = classes + self._downloaded = downloaded + + def __contains__(self, name): + return name in self._classes + + def __getitem__(self, name): + return {"class": self._classes[name]} + + def refresh_download_status(self, name): + return self._downloaded.get(name, True) + + +def test_iter_flat_component(): + params = {"tabular_classifier": {"component": "SVC", "params": {}}} + assert list(iter_config_components(params)) == [("SVC", None)] + + +def test_iter_unwraps_properties(): + params = { + "tabular_classifier": { + "properties": {"component": "SVC", "params": {}}, + } + } + assert list(iter_config_components(params)) == [("SVC", None)] + + +def test_iter_comp_wrapper(): + params = { + "tabular_classifier": { + "component": "BagOfWords", + "params": {"comp": {"component": "SVC", "params": {}}}, + } + } + assert list(iter_config_components(params)) == [("SVC", None)] + + +def test_iter_nested_depth(): + params = { + "outer": { + "component": "Wrapper", + "params": {"inner": {"component": "SVC", "params": {}}}, + } + } + names = list(iter_config_components(params)) + assert names == [("Wrapper", None), ("SVC", "Wrapper")] + + +def test_iter_ignores_primitives_and_fixed_values(): + params = {"n": 5, "alpha": {"fixed_value": 0.1}} + assert list(iter_config_components(params)) == [] + + +def test_missing_downloads_reports_undownloaded(): + classes = { + "SVC": _Comp(requires=False), + "BigNet": _Comp(requires=True, size=42), + } + downloaded = {"BigNet": False} + reg = _FakeRegistry(classes, downloaded) + params = { + "a": {"component": "SVC", "params": {}}, + "b": {"component": "BigNet", "params": {}}, + } + missing = missing_downloads(params, reg) + assert missing == [{"name": "BigNet", "parent": None, "download_size_bytes": 42}] + + +def test_missing_downloads_empty_when_all_present(): + classes = {"BigNet": _Comp(requires=True, size=1)} + reg = _FakeRegistry(classes, {"BigNet": True}) + params = {"b": {"component": "BigNet", "params": {}}} + assert missing_downloads(params, reg) == [] + + +def test_missing_downloads_dedupes(): + classes = {"BigNet": _Comp(requires=True, size=1)} + reg = _FakeRegistry(classes, {"BigNet": False}) + params = { + "a": {"component": "BigNet", "params": {}}, + "b": {"component": "BigNet", "params": {}}, + } + assert len(missing_downloads(params, reg)) == 1 From 25fa0bca4834316f799187d2ea84f52f8e8515d8 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:18:27 -0400 Subject: [PATCH 062/308] feat: block runs and sessions on undownloaded nested components The run creation, generative session upload, and model training gates now reject with the full list of nested components that still need downloading, not just the top level model. --- .../back/api/api_v1/endpoints/generative_session.py | 13 +++++++++++++ DashAI/back/api/api_v1/endpoints/runs.py | 12 ++++++++++++ DashAI/back/job/model_job.py | 8 ++++++++ 3 files changed, 33 insertions(+) diff --git a/DashAI/back/api/api_v1/endpoints/generative_session.py b/DashAI/back/api/api_v1/endpoints/generative_session.py index 503b8c72d..b67c5359c 100644 --- a/DashAI/back/api/api_v1/endpoints/generative_session.py +++ b/DashAI/back/api/api_v1/endpoints/generative_session.py @@ -15,6 +15,7 @@ GenerativeSessionParameterHistory, ProcessData, ) +from DashAI.back.dependencies.downloads.nested import missing_downloads if TYPE_CHECKING: from sqlalchemy.orm import sessionmaker @@ -60,6 +61,18 @@ async def upload_generative_session( ), ) + # A parameter may select another component that itself needs + # downloading; block until every nested one is present. + nested_missing = missing_downloads(params.parameters, component_registry) + if nested_missing: + names = ", ".join(m["name"] for m in nested_missing) + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=( + f"These components must be downloaded before use: {names}." + ), + ) + # Check if the model is a subclass of GenerativeModel if not issubclass(model_class, BaseGenerativeModel): raise HTTPException( diff --git a/DashAI/back/api/api_v1/endpoints/runs.py b/DashAI/back/api/api_v1/endpoints/runs.py index cdc61958e..c9ea385bb 100644 --- a/DashAI/back/api/api_v1/endpoints/runs.py +++ b/DashAI/back/api/api_v1/endpoints/runs.py @@ -17,6 +17,7 @@ Run, RunStatus, ) +from DashAI.back.dependencies.downloads.nested import missing_downloads from DashAI.back.services.scoring_service import ScoringService if TYPE_CHECKING: @@ -343,6 +344,17 @@ async def upload_run( f"Model {params.model_name} must be downloaded before use." ), ) + # A parameter may select another component (e.g. a classifier) that + # itself needs downloading; block until every nested one is present. + nested_missing = missing_downloads(params.parameters, component_registry) + if nested_missing: + names = ", ".join(m["name"] for m in nested_missing) + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=( + f"These components must be downloaded before use: {names}." + ), + ) run = Run( model_session_id=params.model_session_id, model_name=params.model_name, diff --git a/DashAI/back/job/model_job.py b/DashAI/back/job/model_job.py index b3829f54f..3819f9f34 100644 --- a/DashAI/back/job/model_job.py +++ b/DashAI/back/job/model_job.py @@ -7,6 +7,7 @@ from DashAI.back.core.enums.metrics import LevelEnum, SplitEnum from DashAI.back.dependencies.database.models import Dataset, Metric, ModelSession, Run +from DashAI.back.dependencies.downloads.nested import missing_downloads from DashAI.back.job.base_job import BaseJob, JobError from DashAI.back.metrics.base_metric import BaseMetric from DashAI.back.models.base_model import BaseModel @@ -229,6 +230,13 @@ def run( f"Model {run.model_name} is not downloaded. " "Download it before training." ) + nested_missing = missing_downloads(run.parameters, component_registry) + if nested_missing: + names = ", ".join(m["name"] for m in nested_missing) + raise JobError( + "These components are not downloaded. " + f"Download them before training: {names}." + ) try: factory = ModelFactory( run_model_class, From 990697bea6730e9143bc00432a0c84619695954e Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:18:46 -0400 Subject: [PATCH 063/308] feat: add endpoint to resolve required nested downloads POST /components/downloads/required returns the nested components a configuration still needs downloaded, so the frontend does not re-walk the parameter tree in JS. --- .../back/api/api_v1/endpoints/components.py | 80 +++++++++++++++++++ .../api/test_components_download_required.py | 79 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 tests/back/api/test_components_download_required.py diff --git a/DashAI/back/api/api_v1/endpoints/components.py b/DashAI/back/api/api_v1/endpoints/components.py index 64b59ec84..5d7a4a534 100644 --- a/DashAI/back/api/api_v1/endpoints/components.py +++ b/DashAI/back/api/api_v1/endpoints/components.py @@ -7,6 +7,7 @@ from fastapi.exceptions import HTTPException from fastapi.responses import StreamingResponse from kink import di, inject +from pydantic import BaseModel from typing_extensions import Annotated from DashAI.back.core.utils import MultilingualString @@ -344,6 +345,85 @@ async def delete_component_download( return Response(status_code=status.HTTP_204_NO_CONTENT) +class RequiredDownloadsParams(BaseModel): + """Request body for resolving nested download-required components. + + Attributes + ---------- + model_name : str or None + The parent component being configured. When set and it still needs a + download, it is included in the result so the caller can gate on a + single list. + parameters : dict + The parameters dict as produced by the configuration UI. + """ + + model_name: Union[str, None] = None + parameters: Dict[str, Any] = {} + + +@router.post("/downloads/required") +@inject +async def get_required_downloads( + params: RequiredDownloadsParams, + accept_language: str | None = Header(default=None), + component_registry: "ComponentRegistry" = Depends(lambda: di["component_registry"]), +) -> List[Dict[str, Any]]: + """Return the components a configuration still needs downloaded. + + Walks the ``parameters`` dict for nested components (a component selected as + another component's parameter) and, optionally, checks the parent + ``model_name`` itself. Each component is reconciled against the filesystem so + the answer reflects downloads finished after startup. + + Parameters + ---------- + params : RequiredDownloadsParams + The parent ``model_name`` (optional) and its ``parameters`` dict. + accept_language : str | None + The 'Accept-Language' header used to localize display names. + component_registry : ComponentRegistry + Registry that resolves component classes and download state. + + Returns + ------- + list[dict] + One entry per not-yet-downloaded component, each with ``name``, + ``display_name``, ``parent``, and ``download_size_bytes``. + """ + from DashAI.back.dependencies.downloads.nested import missing_downloads + + missing = missing_downloads(params.parameters, component_registry) + + # Optionally fold in the parent model so callers can gate on one list. + if params.model_name and params.model_name in component_registry: + parent_class = component_registry[params.model_name]["class"] + if getattr( + parent_class, "REQUIRES_DOWNLOAD", False + ) and not component_registry.refresh_download_status(params.model_name): + missing.insert( + 0, + { + "name": params.model_name, + "parent": None, + "download_size_bytes": getattr( + parent_class, "DOWNLOAD_SIZE_BYTES", None + ), + }, + ) + + def _localized_name(name: str) -> str: + display = component_registry[name].get("display_name") + if isinstance(display, MultilingualString): + lang = (accept_language or "en").split("-")[0].lower() + return display.get(lang) + return display or name + + return [ + {**entry, "display_name": _localized_name(entry["name"])} for entry in missing + ] + + @router.get("/{id}/") @inject def get_component_by_id( diff --git a/tests/back/api/test_components_download_required.py b/tests/back/api/test_components_download_required.py new file mode 100644 index 000000000..a7b63544b --- /dev/null +++ b/tests/back/api/test_components_download_required.py @@ -0,0 +1,79 @@ +"""Tests for the nested download-resolution endpoint.""" + +import pytest +from fastapi.testclient import TestClient + +from DashAI.back.dependencies.downloads.downloadable import DownloadableMixin +from DashAI.back.dependencies.registry import ComponentRegistry +from DashAI.back.models.base_model import BaseModel + +URL = "/api/v1/component/downloads/required" + + +class PlainModel(BaseModel): + """A model with no download requirement.""" + + @classmethod + def get_schema(cls) -> dict: + return {} + + def save(self, filename=None): ... + + def load(self, filename): ... + + +class DownloadableModel(DownloadableMixin, BaseModel): + """A nested-selectable model that reports as not downloaded.""" + + DOWNLOAD_SIZE_BYTES = 123 + DESCRIPTION = "Downloadable" + DISPLAY_NAME = "Downloadable Model" + + @classmethod + def is_downloaded(cls) -> bool: + return False + + @classmethod + def get_schema(cls) -> dict: + return {} + + def save(self, filename=None): ... + + def load(self, filename): ... + + +@pytest.fixture(autouse=True) +def _registry(client, monkeypatch): + registry = ComponentRegistry(initial_components=[PlainModel, DownloadableModel]) + monkeypatch.setitem(client.app.container._services, "component_registry", registry) + return registry + + +def test_required_downloads_reports_nested(client: TestClient): + body = { + "parameters": { + "clf": {"component": "DownloadableModel", "params": {}}, + } + } + response = client.post(URL, json=body) + assert response.status_code == 200 + data = response.json() + assert len(data) == 1 + assert data[0]["name"] == "DownloadableModel" + assert data[0]["download_size_bytes"] == 123 + assert data[0]["display_name"] == "Downloadable Model" + + +def test_required_downloads_empty_for_plain(client: TestClient): + body = {"parameters": {"clf": {"component": "PlainModel", "params": {}}}} + response = client.post(URL, json=body) + assert response.status_code == 200 + assert response.json() == [] + + +def test_required_downloads_includes_parent_model(client: TestClient): + body = {"model_name": "DownloadableModel", "parameters": {}} + response = client.post(URL, json=body) + assert response.status_code == 200 + data = response.json() + assert [d["name"] for d in data] == ["DownloadableModel"] From 70ea125fc4a04417d16726ecc8a491e5590c3d97 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:18:59 -0400 Subject: [PATCH 064/308] feat: add dummy downloadable classifier for nested download testing A test-only tabular classifier that requires a download and itself exposes a nested classifier field, used to exercise the nested download flow at depth in the UI. --- DashAI/back/initial_components.py | 4 + .../dummy_downloadable_classifier.py | 144 ++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 DashAI/back/models/scikit_learn/dummy_downloadable_classifier.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 55be38ecb..eaa6d6ee4 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -257,6 +257,9 @@ DecisionTreeRegression, ) from DashAI.back.models.scikit_learn.dummy_classifier import DummyClassifier +from DashAI.back.models.scikit_learn.dummy_downloadable_classifier import ( + DummyDownloadableClassifier, +) from DashAI.back.models.scikit_learn.elastic_net_regression import ElasticNetRegression from DashAI.back.models.scikit_learn.extra_trees_classifier import ExtraTreesClassifier from DashAI.back.models.scikit_learn.extra_trees_regression import ExtraTreesRegression @@ -424,6 +427,7 @@ def get_initial_components(): RealVisXLV4, StableDiffusionXLV1ControlNet, SVC, + DummyDownloadableClassifier, SVR, T5SmallTransformer, TfIdfLogRegTextClassificationModel, diff --git a/DashAI/back/models/scikit_learn/dummy_downloadable_classifier.py b/DashAI/back/models/scikit_learn/dummy_downloadable_classifier.py new file mode 100644 index 000000000..c69909ec3 --- /dev/null +++ b/DashAI/back/models/scikit_learn/dummy_downloadable_classifier.py @@ -0,0 +1,144 @@ +import time +from typing import Optional + +from DashAI.back.core.schema_fields import ( + BaseSchema, + component_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.dependencies.downloads.downloadable import ( + DownloadableMixin, + ProgressReporter, +) +from DashAI.back.models.scikit_learn.svc import SVC + + +class DummyDownloadableClassifierSchema(BaseSchema): + """Schema for the dummy classifier, exposing one nested classifier field. + + The ``nested_classifier`` parameter is a component field so a second + (possibly download-required) tabular classifier can be selected inside + this one, letting the nested-download flow be exercised at depth. + """ + + nested_classifier: schema_field( + component_field(parent="TabularClassificationModel"), + placeholder={"component": "SVC", "params": {}}, + description=MultilingualString( + en="A nested tabular classifier, used to test nested downloads.", + es="Un clasificador tabular anidado, para probar descargas anidadas.", + pt="Um classificador tabular aninhado, para testar downloads aninhados.", + de=( + "Ein verschachtelter tabellarischer Klassifikator zum Testen " + "verschachtelter Downloads." + ), + zh="嵌套的表格分类器,用于测试嵌套下载。", + ), + alias=MultilingualString( + en="Nested classifier", + es="Clasificador anidado", + pt="Classificador aninhado", + de="Verschachtelter Klassifikator", + zh="嵌套分类器", + ), + ) # type: ignore + + +class DummyDownloadableClassifier(DownloadableMixin, SVC): + """A fake download-required tabular classifier for UI testing. + + Behaves exactly like :class:`SVC` at train time but is flagged as + requiring a download so the inline download control appears when it is + selected as another component's parameter (e.g. the Bag-of-Words tabular + classifier). Its ``download`` writes a marker file instead of fetching any + real artifact, so the download/delete flow can be exercised end to end + without network access. + """ + + SCHEMA = DummyDownloadableClassifierSchema + DOWNLOAD_SIZE_BYTES = 256 * 1024 * 1024 + COLOR = "#B39DDB" + ICON = "Timeline" + DISPLAY_NAME = MultilingualString( + en="Dummy Downloadable Classifier", + es="Clasificador Descargable de Prueba", + pt="Classificador Baixavel de Teste", + de="Dummy Herunterladbarer Klassifikator", + zh="虚拟可下载分类器", + ) + DESCRIPTION = MultilingualString( + en=( + "A test-only classifier that requires a download. It trains like an " + "SVM but is used to preview the inline download control when picking " + "a nested component." + ), + es=( + "Un clasificador solo de prueba que requiere descarga. Entrena como " + "una SVM, pero sirve para previsualizar el control de descarga en " + "linea al elegir un componente anidado." + ), + pt=( + "Um classificador apenas de teste que requer download. Treina como " + "uma SVM, mas serve para pre-visualizar o controle de download em " + "linha ao escolher um componente aninhado." + ), + de=( + "Ein reiner Testklassifikator, der einen Download erfordert. Er " + "trainiert wie eine SVM, dient aber zur Vorschau des Inline-" + "Download-Steuerelements bei der Auswahl einer verschachtelten " + "Komponente." + ), + zh=( + "仅用于测试的分类器,需要下载。它像 SVM 一样训练," + "用于在选择嵌套组件时预览内联下载控件。" + ), + ) + + def __init__(self, **kwargs): + """Store the nested classifier and forward the rest to ``SVC``. + + Parameters + ---------- + **kwargs : dict + May include ``nested_classifier`` (an instantiated tabular + classifier), which is kept as an attribute and not passed to the + underlying sklearn estimator. + """ + self.nested_classifier = kwargs.pop("nested_classifier", None) + super().__init__(**kwargs) + + @classmethod + def is_downloaded(cls) -> bool: + """Return whether the marker file is present. + + Returns + ------- + bool + ``True`` when ``component_dir()`` exists and is non-empty. + """ + directory = cls.component_dir() + return directory.is_dir() and any(directory.iterdir()) + + @classmethod + def download(cls, report: Optional[ProgressReporter] = None) -> None: + """Write a marker file to simulate a download. + + A short delay is inserted so the downloading state is visible in the + UI. No real artifact is fetched. + + Parameters + ---------- + report : ProgressReporter, optional + Callback invoked with progress fractions and phase messages. + """ + directory = cls.component_dir() + directory.mkdir(parents=True, exist_ok=True) + steps = 4 + for step in range(steps): + if report is not None: + report(step / steps, "Downloading dummy weights") + time.sleep(1) + (directory / "weights.marker").write_text("dummy", encoding="utf-8") + if report is not None: + report(1.0, "Done") From 9c00e4326a74651e810d5e572d61e8327dc8f719 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:19:24 -0400 Subject: [PATCH 065/308] feat: show inline download control in nested component selectors When a component selected as another component's parameter requires a download, render the download control inline in the selector. Cache the downloaded flag in place so it persists across model switches. --- .../configurableObject/Inputs/ClassInput.jsx | 24 +++++++++++++++++++ .../shared/FormSchemaFieldWithParent.jsx | 19 +++++++++++++-- .../shared/FormSchemaModelSelect.jsx | 19 +++++++++++++-- DashAI/front/src/hooks/useModelParents.js | 20 ++++++++++++++-- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/DashAI/front/src/components/configurableObject/Inputs/ClassInput.jsx b/DashAI/front/src/components/configurableObject/Inputs/ClassInput.jsx index 5750881f8..c46b51fb3 100644 --- a/DashAI/front/src/components/configurableObject/Inputs/ClassInput.jsx +++ b/DashAI/front/src/components/configurableObject/Inputs/ClassInput.jsx @@ -3,6 +3,7 @@ import PropTypes from "prop-types"; import FormTooltip from "../FormTooltip"; import { Input } from "./InputStyles"; import { + Box, IconButton, MenuItem, Dialog, @@ -15,6 +16,7 @@ import { } from "@mui/material"; import SettingsIcon from "@mui/icons-material/Settings"; import Subform from "../Subform"; +import ComponentDownloadControl from "../../models/model/ComponentDownloadControl"; import { getDefaultValues } from "../../../utils/values"; import { getModelSchema as getModelSchemaRequest, @@ -140,6 +142,28 @@ function ClassInput({ + {(() => { + const selectedComponent = options.find( + (option) => option.name === selectedOption, + ); + return selectedComponent?.metadata?.requires_download ? ( + + + setOptions((prev) => + prev.map((option) => + option.name === selectedComponent.name + ? { ...option, downloaded: isDownloaded } + : option, + ), + ) + } + /> + + ) : null; + })()} + {/* Button to show the modal that contains the subform */} diff --git a/DashAI/front/src/components/shared/FormSchemaFieldWithParent.jsx b/DashAI/front/src/components/shared/FormSchemaFieldWithParent.jsx index 5c35bf03c..96f23a9e4 100644 --- a/DashAI/front/src/components/shared/FormSchemaFieldWithParent.jsx +++ b/DashAI/front/src/components/shared/FormSchemaFieldWithParent.jsx @@ -1,4 +1,4 @@ -import { MenuItem, Tooltip, IconButton } from "@mui/material"; +import { Box, MenuItem, Tooltip, IconButton } from "@mui/material"; import PropTypes from "prop-types"; import { Input } from "../configurableObject/Inputs/InputStyles"; import React from "react"; @@ -12,6 +12,7 @@ import useModelParents from "../../hooks/useModelParents"; import { Settings } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; import FormSchemaFieldCard from "./FormSchemaFieldCard"; +import ComponentDownloadControl from "../models/model/ComponentDownloadControl"; /** * Renders a parent-model selector field as a card. @@ -26,11 +27,15 @@ function FormSchemaFieldWithParent({ errorMessage, }) { const { addProperty, getModelFromCurrentProperty } = useFormSchemaStore(); - const { models } = useModelParents({ + const { models, markDownloaded } = useModelParents({ parent: field.value?.properties.component, }); const { t } = useTranslation(["common"]); + const selectedComponent = models?.find( + (model) => model.name === getModelFromCurrentProperty(name), + ); + const handleOnChange = async (event) => { const model = models?.find((model) => model.name === event.target.value); const { initialValues } = generateYupSchema( @@ -79,6 +84,16 @@ function FormSchemaFieldWithParent({ ))} + {selectedComponent?.metadata?.requires_download && ( + + + markDownloaded(selectedComponent.name, isDownloaded) + } + /> + + )} ); } diff --git a/DashAI/front/src/components/shared/FormSchemaModelSelect.jsx b/DashAI/front/src/components/shared/FormSchemaModelSelect.jsx index cb8c3fd89..ad9ab3967 100644 --- a/DashAI/front/src/components/shared/FormSchemaModelSelect.jsx +++ b/DashAI/front/src/components/shared/FormSchemaModelSelect.jsx @@ -1,4 +1,4 @@ -import { FormControl, MenuItem } from "@mui/material"; +import { Box, FormControl, MenuItem } from "@mui/material"; import React from "react"; import useModelParents from "../../hooks/useModelParents"; import { Input } from "../configurableObject/Inputs/InputStyles"; @@ -10,6 +10,7 @@ import { } from "../../utils/schema"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; +import ComponentDownloadControl from "../models/model/ComponentDownloadControl"; /** * This component is a select input for the models of a parent model @@ -19,7 +20,7 @@ import { useTranslation } from "react-i18next"; */ function FormSchemaModelSelect({ parent, selectedModel, onChange }) { - const { models } = useModelParents({ parent }); + const { models, markDownloaded } = useModelParents({ parent }); const { handleUpdateSchema } = useFormSchemaStore(); const { t } = useTranslation(["common"]); @@ -27,6 +28,10 @@ function FormSchemaModelSelect({ parent, selectedModel, onChange }) { return null; } + const selectedComponent = models.find( + (model) => model.name === selectedModel, + ); + const handleOnChange = async (event) => { const model = models.find((model) => model.name === event.target.value); const { initialValues } = generateYupSchema( @@ -52,6 +57,16 @@ function FormSchemaModelSelect({ parent, selectedModel, onChange }) { ))} + {selectedComponent?.metadata?.requires_download && ( + + + markDownloaded(selectedComponent.name, isDownloaded) + } + /> + + )} ); } diff --git a/DashAI/front/src/hooks/useModelParents.js b/DashAI/front/src/hooks/useModelParents.js index 2a6ab93b1..d187552e4 100644 --- a/DashAI/front/src/hooks/useModelParents.js +++ b/DashAI/front/src/hooks/useModelParents.js @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { getComponents } from "../api/component"; /* @@ -30,5 +30,21 @@ export default function useModelParents({ parent }) { } }, [parent]); - return { models, loading }; + // Flip a single model's downloaded flag in place so an inline download/delete + // is reflected in the cached list. Without this, switching models and coming + // back would re-mount the download control from the stale (not-downloaded) + // flag and show the Download button again. + const markDownloaded = useCallback((name, isDownloaded) => { + setModels((prev) => + prev + ? prev.map((model) => + model.name === name + ? { ...model, downloaded: isDownloaded } + : model, + ) + : prev, + ); + }, []); + + return { models, loading, markDownloaded }; } From 0a679d7e8bf9811a681b5d66ca6cc649c1a49a44 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:19:38 -0400 Subject: [PATCH 066/308] fix: sync download state across all controls for a component Download state is a global per-component fact, but the same component can render at several nesting levels. A module level pub/sub plus a status cache keeps every mounted control in sync and lets remounts pick up the latest state. --- .../models/model/ComponentDownloadControl.jsx | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx index 4ef233238..8ad18a8b1 100644 --- a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx @@ -26,6 +26,45 @@ const formatSize = (bytes) => { return `${Math.round(mb)} MB`; }; +// Download state is a global, per-component fact (a component's artifacts are +// either on disk or not). The same component can be rendered by several +// controls at once (e.g. the same model selected at multiple nesting levels). +// This module-level pub/sub keeps every mounted control for a given component +// name in sync, and the cache lets a freshly mounted control pick up the +// latest known state instead of the (possibly stale) prop. +const downloadListeners = new Map(); // name -> Set<(state) => void> +const downloadStateCache = new Map(); // name -> { downloading, downloaded } +const anyChangeListeners = new Set(); // (name, state) => void + +const subscribeDownloadState = (name, listener) => { + let listeners = downloadListeners.get(name); + if (!listeners) { + listeners = new Set(); + downloadListeners.set(name, listeners); + } + listeners.add(listener); + return () => { + listeners.delete(listener); + }; +}; + +// Subscribe to every download/delete regardless of component name. Lets a +// container (e.g. a config dialog) re-check which nested components still need +// downloading after an inline control finishes. +export const subscribeAnyDownloadState = (listener) => { + anyChangeListeners.add(listener); + return () => { + anyChangeListeners.delete(listener); + }; +}; + +const broadcastDownloadState = (name, state) => { + downloadStateCache.set(name, { ...downloadStateCache.get(name), ...state }); + const listeners = downloadListeners.get(name); + if (listeners) listeners.forEach((listener) => listener(state)); + anyChangeListeners.forEach((listener) => listener(name, state)); +}; + const ComponentDownloadControl = ({ component, onStatusChange, @@ -34,14 +73,27 @@ const ComponentDownloadControl = ({ const { t } = useTranslation(["common"]); const { enqueueSnackbar } = useSnackbar(); const meta = component.metadata || {}; - const [downloaded, setDownloaded] = useState(Boolean(component.downloaded)); - const [downloading, setDownloading] = useState(false); + const cached = downloadStateCache.get(component.name); + const [downloaded, setDownloaded] = useState( + cached?.downloaded ?? Boolean(component.downloaded), + ); + const [downloading, setDownloading] = useState(cached?.downloading ?? false); const pollerIdRef = useRef(null); useEffect(() => { - setDownloaded(Boolean(component.downloaded)); + const known = downloadStateCache.get(component.name); + setDownloaded(known?.downloaded ?? Boolean(component.downloaded)); + setDownloading(known?.downloading ?? false); }, [component.name, component.downloaded]); + // Mirror download/delete triggered by any other control for this component. + useEffect(() => { + return subscribeDownloadState(component.name, (state) => { + if (state.downloading !== undefined) setDownloading(state.downloading); + if (state.downloaded !== undefined) setDownloaded(state.downloaded); + }); + }, [component.name]); + useEffect(() => { return () => { if (pollerIdRef.current != null) stopJobPolling(pollerIdRef.current); @@ -51,13 +103,15 @@ const ComponentDownloadControl = ({ if (!meta.requires_download) return null; const finish = (isDownloaded) => { - setDownloading(false); - setDownloaded(isDownloaded); + broadcastDownloadState(component.name, { + downloading: false, + downloaded: isDownloaded, + }); if (onStatusChange) onStatusChange(isDownloaded); }; const handleDownload = async () => { - setDownloading(true); + broadcastDownloadState(component.name, { downloading: true }); try { const { id } = await downloadComponent(component.name); pollerIdRef.current = id; From 9b51b6570277410bac875f1c7dcd24a12574f189 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:19:48 -0400 Subject: [PATCH 067/308] fix: allow selecting a component nested two levels deep getModelFromCurrentProperty did not unwrap the last hop of the property chain, so a component field nested inside another component read undefined and its select never reflected a selection. --- DashAI/front/src/contexts/schema.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/DashAI/front/src/contexts/schema.js b/DashAI/front/src/contexts/schema.js index ab9e4da5f..a42b22c77 100644 --- a/DashAI/front/src/contexts/schema.js +++ b/DashAI/front/src/contexts/schema.js @@ -154,14 +154,17 @@ export const useFormSchemaStore = () => { if (properties.length === 0) return getModelFromSubform(formValues[property]); + // Walk down the property chain, unwrapping each subform into its params + // map. This must unwrap the last property too: the current view's params + // map is what holds the field being rendered. Without unwrapping the last + // hop, a component field nested inside another component (depth >= 2) reads + // undefined and its select never reflects a selection. let params = null; for (const prop of properties) { - if (params === null) { - params = formValues[prop.key]; - continue; - } - - params = getParamsFromSubform(params)[prop.key]; + params = + params === null + ? getParamsFromSubform(formValues[prop.key]) + : getParamsFromSubform(params[prop.key]); } return getModelFromSubform(params[property]); }; From a1ef348a941f8ad7948a71e797491096fb58afd6 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:20:03 -0400 Subject: [PATCH 068/308] fix: add root crumb so nested config can return to the top model The breadcrumbs never rendered a clickable root, so a nested submodel had no direct way back to the top level model. Add a root crumb that pops every nested property. --- .../shared/FormSchemaBreadScrumbs.jsx | 27 +++++++++++++++++-- .../shared/FormSchemaWithSelectedModel.jsx | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/DashAI/front/src/components/shared/FormSchemaBreadScrumbs.jsx b/DashAI/front/src/components/shared/FormSchemaBreadScrumbs.jsx index c6b608302..f0249a24b 100644 --- a/DashAI/front/src/components/shared/FormSchemaBreadScrumbs.jsx +++ b/DashAI/front/src/components/shared/FormSchemaBreadScrumbs.jsx @@ -1,22 +1,40 @@ import React from "react"; +import PropTypes from "prop-types"; import Breadcrumbs from "@mui/material/Breadcrumbs"; import Typography from "@mui/material/Typography"; import Link from "@mui/material/Link"; import { useTheme } from "@mui/material/styles"; +import { useTranslation } from "react-i18next"; import { useFormSchemaStore } from "../../contexts/schema"; /** * This component is the breadcrumbs for the form schema + * @param {string} rootLabel - Label for the root (top level) model crumb */ -function FormSchemaBreadScrumbs() { +function FormSchemaBreadScrumbs({ rootLabel }) { const theme = useTheme(); + const { t } = useTranslation(["common"]); const { properties, removeLastProperty } = useFormSchemaStore(); const handleRemoveLastProperty = (index) => { removeLastProperty(properties.length - 1 - index); }; + // Root crumb: pops every nested property to return to the top level model. + const rootCrumb = ( + removeLastProperty(properties.length)} + sx={{ background: "none", border: "none", cursor: "pointer" }} + > + {rootLabel || t("common:model")} + + ); + const linkedProperties = properties .slice(0, properties.length - 1) .map((property, index) => ( @@ -33,7 +51,8 @@ function FormSchemaBreadScrumbs() { )); return ( - + + {rootCrumb} {linkedProperties} {properties[properties.length - 1]?.label} @@ -42,4 +61,8 @@ function FormSchemaBreadScrumbs() { ); } +FormSchemaBreadScrumbs.propTypes = { + rootLabel: PropTypes.string, +}; + export default FormSchemaBreadScrumbs; diff --git a/DashAI/front/src/components/shared/FormSchemaWithSelectedModel.jsx b/DashAI/front/src/components/shared/FormSchemaWithSelectedModel.jsx index 67357e678..41721ec87 100644 --- a/DashAI/front/src/components/shared/FormSchemaWithSelectedModel.jsx +++ b/DashAI/front/src/components/shared/FormSchemaWithSelectedModel.jsx @@ -74,7 +74,7 @@ function FormSchemaWithSelectedModel({ > {Boolean(propertyData?.parent) && ( <> - + Date: Fri, 3 Jul 2026 11:20:28 -0400 Subject: [PATCH 069/308] feat: disable Next when a nested component is not downloaded The add model dialog resolves required nested downloads and disables the Next button until they are present, with a tooltip listing them. It re-checks after an inline download finishes anywhere. --- DashAI/front/src/api/component.ts | 21 +++++++++ .../src/components/models/AddModelDialog.jsx | 45 ++++++++++++++++++- .../src/utils/i18n/locales/de/common.json | 3 +- .../src/utils/i18n/locales/en/common.json | 3 +- .../src/utils/i18n/locales/es/common.json | 3 +- .../src/utils/i18n/locales/pt/common.json | 3 +- .../src/utils/i18n/locales/zh/common.json | 3 +- 7 files changed, 74 insertions(+), 7 deletions(-) diff --git a/DashAI/front/src/api/component.ts b/DashAI/front/src/api/component.ts index a4008831c..0e1a42fd7 100644 --- a/DashAI/front/src/api/component.ts +++ b/DashAI/front/src/api/component.ts @@ -77,3 +77,24 @@ export const getComponentDownloadStatus = async ( }>(`/v1/component/${name}/download`); return response.data; }; + +export interface RequiredDownload { + name: string; + display_name: string; + parent: string | null; + download_size_bytes: number | null; +} + +// Resolve which components a configuration still needs downloaded. Walks +// nested component parameters server-side (a component selected as another +// component's parameter) and optionally checks the parent model itself. +export const getRequiredDownloads = async ( + parameters: Record, + modelName?: string, +): Promise => { + const response = await api.post( + `/v1/component/downloads/required`, + { model_name: modelName ?? null, parameters }, + ); + return response.data; +}; diff --git a/DashAI/front/src/components/models/AddModelDialog.jsx b/DashAI/front/src/components/models/AddModelDialog.jsx index 2bd46d5c1..144a57e91 100644 --- a/DashAI/front/src/components/models/AddModelDialog.jsx +++ b/DashAI/front/src/components/models/AddModelDialog.jsx @@ -24,6 +24,8 @@ import ModelsTableSelectMetric from "./modelSession/ModelsTableSelectMetric"; import useSchema from "../../hooks/useSchema"; import { generateSequentialName } from "../../utils/nameGenerator"; import { createRun } from "../../api/run"; +import { getRequiredDownloads } from "../../api/component"; +import { subscribeAnyDownloadState } from "./model/ComponentDownloadControl"; import { useTranslation } from "react-i18next"; import { useTourContext } from "../tour/TourProvider"; import { checkIfHaveOptimazers } from "../../utils/schema"; @@ -54,6 +56,7 @@ function AddModelDialog({ const [goalMetric, setGoalMetric] = useState(""); const [hasLoadedInitialParams, setHasLoadedInitialParams] = useState(false); const [modelDownloaded, setModelDownloaded] = useState(true); + const [missingNested, setMissingNested] = useState([]); const { t } = useTranslation(["models", "common"]); const { defaultValues: defaultModelParams } = useSchema({ @@ -106,6 +109,37 @@ function AddModelDialog({ setModelDownloaded(!requiresDownload || isDownloaded); }, [preselectedModelObject]); + // Block advancing while any component selected inside the model parameters + // still needs downloading. The check walks the nested parameters server-side + // and re-runs after an inline download/delete finishes anywhere. + useEffect(() => { + if ( + !open || + activeStep !== 0 || + !modelParameters || + Object.keys(modelParameters).length === 0 + ) { + setMissingNested([]); + return; + } + let cancelled = false; + const check = async () => { + try { + const missing = await getRequiredDownloads(modelParameters); + if (!cancelled) setMissingNested(missing); + } catch { + if (!cancelled) setMissingNested([]); + } + }; + const timer = setTimeout(check, 300); + const unsubscribe = subscribeAnyDownloadState(() => check()); + return () => { + cancelled = true; + clearTimeout(timer); + unsubscribe(); + }; + }, [open, activeStep, JSON.stringify(modelParameters)]); + useEffect(() => { if ( selectedModel && @@ -387,7 +421,13 @@ function AddModelDialog({ preselectedModelObject?.metadata?.requires_download && !modelDownloaded ? t("common:componentDownload.mustDownload") - : "" + : activeStep === 0 && missingNested.length > 0 + ? t("common:componentDownload.mustDownloadNested", { + names: missingNested + .map((m) => m.display_name || m.name) + .join(", "), + }) + : "" } > @@ -403,7 +443,8 @@ function AddModelDialog({ Boolean( preselectedModelObject?.metadata?.requires_download, ) && - !modelDownloaded) + !modelDownloaded) || + (activeStep === 0 && missingNested.length > 0) } > {activeStep === steps.length - 1 diff --git a/DashAI/front/src/utils/i18n/locales/de/common.json b/DashAI/front/src/utils/i18n/locales/de/common.json index 07e9df0e7..06964acb6 100644 --- a/DashAI/front/src/utils/i18n/locales/de/common.json +++ b/DashAI/front/src/utils/i18n/locales/de/common.json @@ -167,7 +167,8 @@ "done": "Komponente heruntergeladen", "deleted": "Download geloescht", "failed": "Download der Komponente fehlgeschlagen", - "mustDownload": "Dieses Modell muss vor der Nutzung heruntergeladen werden" + "mustDownload": "Dieses Modell muss vor der Nutzung heruntergeladen werden", + "mustDownloadNested": "Diese Komponenten müssen zuerst heruntergeladen werden: {{names}}" }, "jobQueue": { "title": "Aufgabenwarteschlange", diff --git a/DashAI/front/src/utils/i18n/locales/en/common.json b/DashAI/front/src/utils/i18n/locales/en/common.json index 478d80af9..2b37fd4b4 100644 --- a/DashAI/front/src/utils/i18n/locales/en/common.json +++ b/DashAI/front/src/utils/i18n/locales/en/common.json @@ -167,7 +167,8 @@ "done": "Component downloaded", "deleted": "Download deleted", "failed": "Component download failed", - "mustDownload": "This model must be downloaded before use" + "mustDownload": "This model must be downloaded before use", + "mustDownloadNested": "These components must be downloaded first: {{names}}" }, "jobQueue": { "title": "Job Queue", diff --git a/DashAI/front/src/utils/i18n/locales/es/common.json b/DashAI/front/src/utils/i18n/locales/es/common.json index f22b31b60..33ea478e0 100644 --- a/DashAI/front/src/utils/i18n/locales/es/common.json +++ b/DashAI/front/src/utils/i18n/locales/es/common.json @@ -167,7 +167,8 @@ "done": "Componente descargado", "deleted": "Descarga eliminada", "failed": "La descarga del componente ha fallado", - "mustDownload": "Este modelo debe descargarse antes de usarlo" + "mustDownload": "Este modelo debe descargarse antes de usarlo", + "mustDownloadNested": "Estos componentes deben descargarse primero: {{names}}" }, "jobQueue": { "title": "Cola de trabajos", diff --git a/DashAI/front/src/utils/i18n/locales/pt/common.json b/DashAI/front/src/utils/i18n/locales/pt/common.json index 583d45dec..827390169 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/common.json +++ b/DashAI/front/src/utils/i18n/locales/pt/common.json @@ -167,7 +167,8 @@ "done": "Componente baixado", "deleted": "Download removido", "failed": "Falha ao baixar o componente", - "mustDownload": "Este modelo precisa ser baixado antes de usar" + "mustDownload": "Este modelo precisa ser baixado antes de usar", + "mustDownloadNested": "Estes componentes precisam ser baixados primeiro: {{names}}" }, "jobQueue": { "title": "Fila de trabalhos", diff --git a/DashAI/front/src/utils/i18n/locales/zh/common.json b/DashAI/front/src/utils/i18n/locales/zh/common.json index 02985d5fb..14b8fcba3 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/common.json +++ b/DashAI/front/src/utils/i18n/locales/zh/common.json @@ -167,7 +167,8 @@ "done": "组件已下载", "deleted": "下载已删除", "failed": "组件下载失败", - "mustDownload": "使用前必须先下载此模型" + "mustDownload": "使用前必须先下载此模型", + "mustDownloadNested": "必须先下载这些组件:{{names}}" }, "jobQueue": { "title": "任务队列", From 3ad94d9904b8fd107983e6b31ed5e14ce1aed936 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 11:23:47 -0400 Subject: [PATCH 070/308] fix: keep download button at normal color on undownloaded cards Dim only the card content, not the whole card, so the inline download control does not inherit the card's dimmed opacity. --- .../src/components/custom/ComponentSelector.jsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/DashAI/front/src/components/custom/ComponentSelector.jsx b/DashAI/front/src/components/custom/ComponentSelector.jsx index d3c00539d..3a1843e90 100644 --- a/DashAI/front/src/components/custom/ComponentSelector.jsx +++ b/DashAI/front/src/components/custom/ComponentSelector.jsx @@ -131,14 +131,23 @@ function ComponentSelector({ border: 1, borderColor: isSelected ? "primary.main" : "divider", bgcolor: isSelected ? "action.selected" : "background.paper", - opacity: needsDownload ? 0.6 : 1, transition: "border-color 0.15s, background 0.15s", "&:hover": { borderColor: needsDownload ? "divider" : "secondary.main", }, }} > - + {/* Dim only the card content while a download is required, so the + download control below keeps its normal color (CSS opacity on the + card would otherwise cap the button's opacity too). */} + {icon && ( Date: Fri, 3 Jul 2026 11:31:46 -0400 Subject: [PATCH 071/308] feat: allow selecting undownloaded components to preview their description Component cards stay clickable when a download is required (keeping the dimmed content and inline download button), so their description can be viewed. The generative Next and Create buttons stay disabled until the selected model is downloaded, and an undownloaded model is no longer auto-deselected. --- .../src/components/custom/ComponentSelector.jsx | 6 +++--- .../components/generative/CreateSessionCenter.jsx | 15 +++++++++++++-- .../generative/CreateSessionContext.jsx | 7 ++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/DashAI/front/src/components/custom/ComponentSelector.jsx b/DashAI/front/src/components/custom/ComponentSelector.jsx index 3a1843e90..c2a399dec 100644 --- a/DashAI/front/src/components/custom/ComponentSelector.jsx +++ b/DashAI/front/src/components/custom/ComponentSelector.jsx @@ -120,20 +120,20 @@ function ComponentSelector({ handleSelect(component)} + onClick={() => handleSelect(component)} data-tour={isCsvComponent ? tourDataFor : undefined} sx={{ p: 3, display: "flex", flexDirection: "column", gap: 3, - cursor: needsDownload ? "not-allowed" : "pointer", + cursor: "pointer", border: 1, borderColor: isSelected ? "primary.main" : "divider", bgcolor: isSelected ? "action.selected" : "background.paper", transition: "border-color 0.15s, background 0.15s", "&:hover": { - borderColor: needsDownload ? "divider" : "secondary.main", + borderColor: "secondary.main", }, }} > diff --git a/DashAI/front/src/components/generative/CreateSessionCenter.jsx b/DashAI/front/src/components/generative/CreateSessionCenter.jsx index 6a455aa80..f43d35f2c 100644 --- a/DashAI/front/src/components/generative/CreateSessionCenter.jsx +++ b/DashAI/front/src/components/generative/CreateSessionCenter.jsx @@ -68,9 +68,20 @@ export default function CreateSessionCenter() { } }, [step]); - const canGoNext = !!selectedModel; + // Read the download status from the (in place updated) models list so the + // gate reacts to an inline download without needing selectedModel to change. + const selectedModelState = + models.find((m) => m.name === selectedModel?.name) || selectedModel; + const selectedNeedsDownload = + Boolean(selectedModelState?.metadata?.requires_download) && + !selectedModelState?.downloaded; + + const canGoNext = !!selectedModel && !selectedNeedsDownload; const canCreate = - !!selectedModel && !!formik.values.name?.trim() && !submitting; + !!selectedModel && + !selectedNeedsDownload && + !!formik.values.name?.trim() && + !submitting; return ( { if (!selectedModel) return; const match = models.find((m) => m.name === selectedModel.name); - if (match && isUnavailable(match)) setSelectedModel(null); + if (!match) setSelectedModel(null); }, [models]); const handleNext = () => { From effdacc6d9320d1951bc80bc00abad421f1b17c2 Mon Sep 17 00:00:00 2001 From: Cristian Tamblay Date: Fri, 3 Jul 2026 12:06:53 -0400 Subject: [PATCH 072/308] Migration to uv, added instructions and fixed workflows --- .github/workflows/build-test.yaml | 18 +- .github/workflows/db-migrations.yaml | 19 +- .github/workflows/docs.yaml | 13 +- .github/workflows/pre-commit.yaml | 9 +- .github/workflows/publish.yml | 57 +- CLAUDE.md | 24 +- Dockerfile | 5 +- Dockerfile.cuda | 20 +- MANIFEST.in | 2 - README.rst | 30 +- docs/docs/build/dev-setup.md | 29 +- .../current/build/dev-setup.md | 29 +- pyproject.toml | 133 + requirements-cpu.txt | 16 - requirements-cuda.txt | 17 - requirements-dev.txt | 8 - requirements-plugins.txt | 2 - requirements.txt | 52 - setup.py | 57 - update-server.sh | 8 +- uv.lock | 8462 +++++++++++++++++ 21 files changed, 8731 insertions(+), 279 deletions(-) delete mode 100644 requirements-cpu.txt delete mode 100644 requirements-cuda.txt delete mode 100644 requirements-dev.txt delete mode 100644 requirements-plugins.txt delete mode 100644 requirements.txt delete mode 100644 setup.py create mode 100644 uv.lock diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 1bf78ec75..d7d5a0b1c 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -40,20 +40,14 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} + enable-cache: true - - uses: actions/cache@v3 - with: - path: ${{ env.pythonLocation }} - key: ${{ env.pythonLocation }}-${{ hashFiles('setup.py') }}-${{ hashFiles('requirements.txt') }}-${{ hashFiles('requirements-dev.txt') }} - - - name: Upgrade pip tools - run: python -m pip install --upgrade pip setuptools wheel - - run: python -m pip install --upgrade --upgrade-strategy eager -r requirements.txt - - run: python -m pip install --upgrade --upgrade-strategy eager -r requirements-dev.txt + - name: Install dependencies + run: uv sync --locked - name: Prepare frontend build run: mkdir -p DashAI/front/build @@ -63,4 +57,4 @@ jobs: name: react-build path: DashAI/front/build - name: Test with pytest - run: pytest -v + run: uv run pytest -v diff --git a/.github/workflows/db-migrations.yaml b/.github/workflows/db-migrations.yaml index b36e9a721..a8449f959 100644 --- a/.github/workflows/db-migrations.yaml +++ b/.github/workflows/db-migrations.yaml @@ -19,15 +19,14 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: ${{ matrix.python-version }} + enable-cache: true - name: Install dependencies - run: | - pip install --upgrade --upgrade-strategy eager -r requirements.txt - pip install --upgrade --upgrade-strategy eager -r requirements-dev.txt + run: uv sync --locked - name: Set DB env vars run: | @@ -37,7 +36,7 @@ jobs: - name: Show Alembic info run: | - alembic --version + uv run alembic --version echo "DB will be at: $DATABASE_URL" - name: Prepare temp dir @@ -48,15 +47,15 @@ jobs: # upgrade to head - name: Upgrade to head run: | - alembic -x url="$DATABASE_URL" upgrade head + uv run alembic -x url="$DATABASE_URL" upgrade head # Checks downgrade and upgrade again (reversibility) - name: Downgrade to base and upgrade again (reversibility check) run: | - alembic -x url="$DATABASE_URL" downgrade base - alembic -x url="$DATABASE_URL" upgrade head + uv run alembic -x url="$DATABASE_URL" downgrade base + uv run alembic -x url="$DATABASE_URL" upgrade head - name: Check for pending autogenerate (python-based) env: PYTHONPATH: "${PYTHONPATH}:." - run: python -m scripts.ci_alembic_check + run: uv run python -m scripts.ci_alembic_check diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 6597713c3..be0549043 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -26,13 +26,16 @@ jobs: uses: actions/setup-python@v4 with: python-version: "3.10" - cache: "pip" - cache-dependency-path: | - requirements.txt - requirements-dev.txt + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + + # Installed into the system Python because the Docusaurus build + # invokes `python scripts/generate_components.py` directly. - name: Install DashAI (needed for component introspection) - run: pip install -e . + run: uv pip install --system -e . - name: Set up Node.js uses: actions/setup-node@v3 diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 756af2aad..149aaa9dd 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -23,10 +23,13 @@ jobs: with: python-version: "3.12" + - name: Install uv + uses: astral-sh/setup-uv@v6 + + # Pillow goes to the system Python because the institutions-readme-sync + # hook is `language: system` and runs `python scripts/render_institutions.py`. - name: Install pre-commit - run: | - python -m pip install --upgrade pip - pip install pre-commit Pillow + run: uv pip install --system pre-commit Pillow - name: Cache pre-commit uses: actions/cache@v3 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a81b38615..78431f7c7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -59,13 +59,11 @@ jobs: with: python-version: "3.12" - - name: Install build tools - run: | - python -m pip install --upgrade pip - pip install build twine + - name: Install uv + uses: astral-sh/setup-uv@v6 - name: Build Python package - run: python -m build + run: uv build - name: Verify frontend is included in wheel run: | @@ -75,13 +73,13 @@ jobs: fi - name: Check distribution metadata - run: twine check dist/* + run: uvx twine check dist/* - name: Publish to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: twine upload --skip-existing dist/* + run: uvx twine upload --skip-existing dist/* # ============================================================ # 3. Build Windows Executable @@ -97,19 +95,19 @@ jobs: with: name: react-build path: DashAI/front/build - - name: Set up Python 3.12 - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: "3.12" + enable-cache: true - name: Install dependencies (CPU-only) run: | - python -m pip install --upgrade pip - pip install -r requirements-cpu.txt - pip install pyinstaller + uv sync --locked --extra cpu --no-dev + uv pip install pyinstaller - name: Build executable shell: cmd run: | - pyinstaller --clean --noconfirm dashai.spec + uv run --no-sync pyinstaller --clean --noconfirm dashai.spec - name: Install Inno Setup run: choco install innosetup -y @@ -138,19 +136,20 @@ jobs: with: name: react-build path: DashAI/front/build - - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: "3.12" + enable-cache: true - name: Install dependencies (CPU-only) run: | - python -m pip install --upgrade pip - pip install -r requirements-cpu.txt - pip install pyinstaller pywebview + uv sync --locked --extra cpu --no-dev + uv pip install pyinstaller brew install create-dmg - name: Build ARM64 App Bundle run: | - pyinstaller --clean --noconfirm dashai.spec + uv run --no-sync pyinstaller --clean --noconfirm dashai.spec - name: Sign App Bundle (ad-hoc) run: | @@ -190,19 +189,20 @@ jobs: with: name: react-build path: DashAI/front/build - - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: python-version: "3.12" + enable-cache: true - name: Install dependencies (CPU-only) run: | - python -m pip install --upgrade pip - pip install -r requirements-cpu.txt - pip install pyinstaller pywebview + uv sync --locked --extra cpu --no-dev + uv pip install pyinstaller brew install create-dmg - name: Build x86_64 App Bundle run: | - pyinstaller --clean --noconfirm dashai.spec + uv run --no-sync pyinstaller --clean --noconfirm dashai.spec - name: Sign App Bundle (ad-hoc) run: | @@ -246,14 +246,17 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.12" + - name: Install uv + uses: astral-sh/setup-uv@v6 + # python-appimage drives its own embedded pip, so it stays pip-based - name: Install build tooling run: | python -m pip install --upgrade pip - pip install build python-appimage + pip install python-appimage sudo apt-get update sudo apt-get install -y libfuse2 imagemagick librsvg2-bin - name: Build wheel (frontend bundled) - run: python -m build --wheel + run: uv build --wheel - name: Verify frontend is included in wheel run: | if ! unzip -l dist/*.whl | grep -q 'DashAI/front/build'; then @@ -304,10 +307,10 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Extract version from setup.py + - name: Extract version from pyproject.toml id: version run: | - VERSION=$(grep -m 1 ' version=' setup.py | sed -E 's/.*version="([^"]+)".*/\1/') + VERSION=$(grep -m 1 '^version = ' pyproject.toml | sed -E 's/version = "([^"]+)"/\1/') echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Download Windows installer diff --git a/CLAUDE.md b/CLAUDE.md index f63722680..dd582196b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,26 +11,30 @@ DashAI is a desktop/web graphical toolbox for training, evaluating, and deployin ### Backend ```bash -# Install -pip install -e . -r requirements-dev.txt -pre-commit install +# Install (uv creates .venv, installs the package editable + dev deps) +uv sync # add --extra cpu on machines without NVIDIA GPU +uv run pre-commit install # Run dev server -python -m DashAI --no-browser --logging-level DEBUG +uv run python -m DashAI --no-browser --logging-level DEBUG # Lint / format -ruff check --fix -ruff format +uv run ruff check --fix +uv run ruff format # Run all tests -pytest tests/ +uv run pytest tests/ # Run a single test file or function -pytest tests/back/api/test_components_api.py -v -pytest tests/back/api/test_components_api.py::test_function_name -v +uv run pytest tests/back/api/test_components_api.py -v +uv run pytest tests/back/api/test_components_api.py::test_function_name -v # Database migrations (auto-runs on startup, but also manually) -alembic upgrade head +uv run alembic upgrade head + +# Add / remove a dependency (updates pyproject.toml + uv.lock) +uv add +uv remove ``` ### Frontend diff --git a/Dockerfile b/Dockerfile index cb6056db5..0cafda4b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,11 +6,12 @@ RUN corepack enable && yarn install --frozen-lockfile && yarn build # Stage 2: Python backend serving the built frontend FROM python:3.11-slim +COPY --from=ghcr.io/astral-sh/uv:0.11 /uv /uvx /bin/ WORKDIR /app COPY . . COPY --from=frontend /app/DashAI/front/build DashAI/front/build -RUN pip install --no-cache-dir -r requirements-cpu.txt && \ - pip install --no-cache-dir --no-deps -e . +RUN uv sync --locked --extra cpu --no-dev --no-cache +ENV PATH="/app/.venv/bin:$PATH" ENV DASHAI_HOST=0.0.0.0 EXPOSE 8000 CMD ["python", "-m", "DashAI", "--no-browser"] diff --git a/Dockerfile.cuda b/Dockerfile.cuda index c819bbcb8..067728548 100644 --- a/Dockerfile.cuda +++ b/Dockerfile.cuda @@ -25,23 +25,25 @@ RUN apt-get update && apt-get install -y \ RUN ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/lib/x86_64-linux-gnu/libcuda.so && \ ln -s /usr/local/cuda/lib64/stubs/libcuda.so /usr/lib/x86_64-linux-gnu/libcuda.so.1 -# requirements-cuda.txt = cu128 torch wheels + CUDA-compiled llama-cpp-python -# (it -r includes requirements.txt, so copy both) -COPY requirements.txt requirements-cuda.txt ./ -RUN pip install --upgrade pip && \ - pip install --no-cache-dir -r requirements-cuda.txt +COPY --from=ghcr.io/astral-sh/uv:0.11 /uv /bin/uv + +# cuda extra = cu128 torch wheels + llama-cpp-python compiled with CUDA offload +COPY pyproject.toml uv.lock ./ +ENV UV_PROJECT_ENVIRONMENT=/opt/venv +ENV CMAKE_ARGS="-DGGML_CUDA=on" +RUN uv sync --locked --extra cuda --no-dev --no-install-project --no-cache # -------- Stage 3: runtime (CUDA runtime, no build tools) -------- FROM nvidia/cuda:12.8.0-runtime-ubuntu22.04 WORKDIR /app RUN apt-get update && apt-get install -y \ - python3 python3-pip \ + python3 \ && rm -rf /var/lib/apt/lists/* -# copy installed python env only (not build tools) -COPY --from=builder /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages -COPY --from=builder /usr/local/bin /usr/local/bin +# copy the resolved virtualenv only (not build tools) +COPY --from=builder /opt/venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" # application code COPY . . diff --git a/MANIFEST.in b/MANIFEST.in index 046baa111..9ce4c8b82 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,3 @@ -include requirements.txt -include requirements-dev.txt include DashAI/alembic.ini recursive-include DashAI/alembic * recursive-include DashAI/front/build * diff --git a/README.rst b/README.rst index 4834973fd..e743b38be 100644 --- a/README.rst +++ b/README.rst @@ -311,39 +311,37 @@ Backend Prepare the environment ~~~~~~~~~~~~~~~~~~~~~~~ -First, set the python enviroment, for that you can use -`conda `_: +Dependencies are managed with `uv `_. Install it +following the `official instructions `_, +then install the project (uv creates the virtualenv, installs the package in +editable mode and all development dependencies): -.. code: bash +.. code:: bash - $ conda create -n dashai python=3.12 - $ conda activate dashai + $ uv sync + $ uv run pre-commit install -Later, install the requirements: +On machines without an NVIDIA GPU you can use the much lighter CPU-only +PyTorch wheels instead: .. code:: bash - $ pip install -r requirements.txt - $ pip install -r requirements-dev.txt - $ pre-commit install + $ uv sync --extra cpu Running the Backend ~~~~~~~~~~~~~~~~~~~ -There are two ways to run dashAI: - -1. By executing dashAI as a module from the root of the repository: +There are two ways to run dashAI from the root of the repository: .. code:: bash - $ python -m DashAI + $ uv run python -m DashAI -2. Or, installing the default build: +Or, through the installed entry point: .. code:: bash - $ pip install . -e - $ dashai + $ uv run dashai Optional Flags diff --git a/docs/docs/build/dev-setup.md b/docs/docs/build/dev-setup.md index ec56fe33e..cf33ec1f0 100644 --- a/docs/docs/build/dev-setup.md +++ b/docs/docs/build/dev-setup.md @@ -8,7 +8,7 @@ sidebar_position: 2 ## Prerequisites -- Python 3.10 to 3.13 +- [uv](https://docs.astral.sh/uv/getting-started/installation/) (manages Python and dependencies; Python 3.10 to 3.13) - Node.js (LTS) and Yarn 3.5.0 - Git @@ -22,20 +22,19 @@ git checkout develop ## 2. Backend Setup -Create and activate a Python environment (conda or venv): +Install all dependencies (uv creates the `.venv` and installs the package +in editable mode, including development dependencies): ```bash -conda create -n dashai python=3.10 -conda activate dashai +uv sync +uv run pre-commit install ``` -Install the package in editable mode with development dependencies: +On machines without an NVIDIA GPU you can use the CPU-only PyTorch wheels, +which are much lighter: ```bash -pip install -r requirements.txt -pip install -e . -pip install -r requirements-dev.txt -pre-commit install +uv sync --extra cpu ``` ## 3. Frontend Setup @@ -50,9 +49,9 @@ yarn install **Backend** (from the repo root): ```bash -python -m DashAI +uv run python -m DashAI # or -dashai --no-browser --logging-level INFO +uv run dashai --no-browser --logging-level INFO ``` **Frontend** (development server with hot reload): @@ -69,8 +68,8 @@ The backend runs at `http://localhost:8000` and the frontend dev server at `http **Python** (using Ruff): ```bash -ruff check . --fix -ruff format . +uv run ruff check . --fix +uv run ruff format . ``` **Frontend** (ESLint + Prettier): @@ -86,10 +85,10 @@ dashAI uses pre-commit hooks for consistent code quality: ```bash # Run all hooks manually -pre-commit run --all-files +uv run pre-commit run --all-files # Run on staged files (happens automatically on git commit) -pre-commit run +uv run pre-commit run ``` ## Project Structure diff --git a/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md b/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md index 4eea6ff2b..bc91d8728 100644 --- a/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md +++ b/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md @@ -8,7 +8,7 @@ sidebar_position: 2 ## Requisitos Previos -- Python 3.10 a 3.13 +- [uv](https://docs.astral.sh/uv/getting-started/installation/) (administra Python y las dependencias; Python 3.10 a 3.13) - Node.js (LTS) y Yarn 3.5.0 - Git @@ -22,20 +22,19 @@ git checkout develop ## 2. Configuración del Backend -Crea y activa un entorno de Python (conda o venv): +Instala todas las dependencias (uv crea el `.venv` e instala el paquete +en modo editable, incluyendo las dependencias de desarrollo): ```bash -conda create -n dashai python=3.10 -conda activate dashai +uv sync +uv run pre-commit install ``` -Instala el paquete en modo editable con las dependencias de desarrollo: +En máquinas sin GPU NVIDIA puedes usar los wheels de PyTorch solo-CPU, +que son mucho más livianos: ```bash -pip install -r requirements.txt -pip install -e . -pip install -r requirements-dev.txt -pre-commit install +uv sync --extra cpu ``` ## 3. Configuración del Frontend @@ -50,9 +49,9 @@ yarn install **Backend** (desde la raíz del repositorio): ```bash -python -m DashAI +uv run python -m DashAI # o -dashai --no-browser --logging-level INFO +uv run dashai --no-browser --logging-level INFO ``` **Frontend** (servidor de desarrollo con recarga en caliente): @@ -69,8 +68,8 @@ El backend corre en `http://localhost:8000` y el servidor de desarrollo del fron **Python** (usando Ruff): ```bash -ruff check . --fix -ruff format . +uv run ruff check . --fix +uv run ruff format . ``` **Frontend** (ESLint + Prettier): @@ -86,10 +85,10 @@ dashAI usa hooks de pre-commit para mantener la calidad del código: ```bash # Ejecutar todos los hooks manualmente -pre-commit run --all-files +uv run pre-commit run --all-files # Ejecutar sobre archivos en staging (ocurre automáticamente en git commit) -pre-commit run +uv run pre-commit run ``` ## Estructura del Proyecto diff --git a/pyproject.toml b/pyproject.toml index 2a849accb..27336ec83 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,136 @@ +[build-system] +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" + +[project] +name = "DashAI" +version = "0.9.6" +description = "DashAI: a graphical toolbox for training, evaluating and deploying state-of-the-art AI models." +readme = "README.rst" +license = "MIT" +authors = [{ name = "DashAI Team", email = "fbravo@dcc.uchile.cl" }] +requires-python = ">=3.10" +classifiers = [ + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Operating System :: OS Independent", +] +dependencies = [ + "setuptools>=65.0.0,<82", + "fastapi[all]", + "SQLAlchemy", + "streaming_form_data", + "alembic", + "kink", + "numpy", + "pandas<3.0.0", + "joblib", + "pydantic", + "pydantic-settings", + "starlette", + "scikit-learn<1.8.0", + "datasets", + "diffusers", + "evaluate", + "accelerate", + "Pillow", + "beartype", + "plotly", + "shap", + "typer", + "rich", + "torch", + "torchvision", + "transformers", + "controlnet_aux", + "sacrebleu", + "sentencepiece", + "optuna", + "cmaes", + "hyperopt", + "nvidia-ml-py", + "openpyxl", + "httpx", + "wordcloud", + "opencv-python", + "protobuf", + "imblearn", + "numba", + "llvmlite", + "huey", + "ijson", + "greenery==3.2", + "xlrd", + "filetype", + "torchmetrics", + "pywebview", + "openml", + "oslo.concurrency", +] + +[project.optional-dependencies] +# PyTorch CPU wheels + precompiled CPU llama-cpp (no CUDA SDK / drivers needed) +cpu = ["torch", "torchvision", "llama-cpp-python"] +# PyTorch CUDA 12.8 wheels; llama-cpp-python needs CMAKE_ARGS="-DGGML_CUDA=on" +# at install time to compile with CUDA offload (see Dockerfile.cuda) +cuda = ["torch", "torchvision", "llama-cpp-python"] + +[project.urls] +Homepage = "https://github.com/DashAISoftware/DashAI" +Documentation = "https://dash-ai.com/" +Changelog = "https://dash-ai.com/changelog.html" +"Issue Tracker" = "https://github.com/DashAISoftware/DashAI/issues" + +[project.scripts] +dashai = "DashAI.__main__:run" +DashAI = "DashAI.__main__:run" + +[dependency-groups] +dev = [ + "pre-commit", + "ruff", + "sphinx_rtd_theme", + "sphinx", + "sqlalchemy-stubs", + "pytest", + "pytest-cov", + "pytest-asyncio", +] + +[tool.setuptools.packages.find] +include = ["DashAI*"] + +[tool.uv] +conflicts = [[{ extra = "cpu" }, { extra = "cuda" }]] + +[tool.uv.sources] +torch = [ + { index = "pytorch-cpu", extra = "cpu" }, + { index = "pytorch-cu128", extra = "cuda" }, +] +torchvision = [ + { index = "pytorch-cpu", extra = "cpu" }, + { index = "pytorch-cu128", extra = "cuda" }, +] +llama-cpp-python = [{ index = "llama-cpp-cpu", extra = "cpu" }] + +[[tool.uv.index]] +name = "pytorch-cpu" +url = "https://download.pytorch.org/whl/cpu" +explicit = true + +[[tool.uv.index]] +name = "pytorch-cu128" +url = "https://download.pytorch.org/whl/cu128" +explicit = true + +[[tool.uv.index]] +name = "llama-cpp-cpu" +url = "https://abetlen.github.io/llama-cpp-python/whl/cpu" +explicit = true + [tool.ruff] line-length = 88 lint.select = [ diff --git a/requirements-cpu.txt b/requirements-cpu.txt deleted file mode 100644 index ca9663e25..000000000 --- a/requirements-cpu.txt +++ /dev/null @@ -1,16 +0,0 @@ -# =================================================================== -# DashAI CPU-only extras (PyTorch CPU wheels + CPU llama-cpp) -# =================================================================== -# Common deps live in requirements.txt; this file only adds the -# packages that need a special index/build for CPU-only hosts. -# Install: pip install -r requirements-cpu.txt -# =================================================================== - --r requirements.txt - -# PyTorch CPU wheels (no CUDA SDK / drivers needed) -torch --index-url https://download.pytorch.org/whl/cpu -torchvision --index-url https://download.pytorch.org/whl/cpu - -# Llama CPU-only (no CUDA) -llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu diff --git a/requirements-cuda.txt b/requirements-cuda.txt deleted file mode 100644 index d1bc6cf9f..000000000 --- a/requirements-cuda.txt +++ /dev/null @@ -1,17 +0,0 @@ -# =================================================================== -# DashAI CUDA extras (CUDA 12.8 PyTorch wheels + CUDA llama-cpp) -# =================================================================== -# Common deps live in requirements.txt; this file only adds the -# packages that need a special index/build for NVIDIA GPU hosts. -# Requiere drivers NVIDIA + NVIDIA Container Toolkit (Dockerfile.cuda). -# Install: pip install -r requirements-cuda.txt -# =================================================================== - --r requirements.txt - -# PyTorch CUDA 12.8 wheels (prebuilt) -torch --index-url https://download.pytorch.org/whl/cu128 -torchvision --index-url https://download.pytorch.org/whl/cu128 - -# Llama compiled with CUDA offload (needs cmake + CUDA devel headers) -llama-cpp-python -C cmake.args="-DGGML_CUDA=on" diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 90f6fee8e..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,8 +0,0 @@ -pre-commit -ruff -sphinx_rtd_theme -sphinx -sqlalchemy-stubs -pytest -pytest-cov -pytest-asyncio diff --git a/requirements-plugins.txt b/requirements-plugins.txt deleted file mode 100644 index 356960f3a..000000000 --- a/requirements-plugins.txt +++ /dev/null @@ -1,2 +0,0 @@ -torch==1.13.0 #+cu116 ---find-links https://download.pytorch.org/whl/torch_stable.html diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 165d4afe6..000000000 --- a/requirements.txt +++ /dev/null @@ -1,52 +0,0 @@ -# Nota: Tuvimos que volver a usar este tipo de requirements en vez de compilar el .in -# pip-compile rompia todo el CI/CD ya que no dejaba elegir bien que version de torch usar -setuptools>=65.0.0,<82 -fastapi[all] -SQLAlchemy -streaming_form_data -alembic -kink -numpy -pandas<3.0.0 -joblib -pydantic -pydantic-settings -starlette -scikit-learn<1.8.0 -datasets -diffusers -evaluate -accelerate -Pillow -beartype -plotly -shap -typer -rich -torch -torchvision -transformers -controlnet_aux -sacrebleu -sentencepiece -optuna -cmaes -hyperopt -nvidia-ml-py -openpyxl -httpx -wordcloud -opencv-python -protobuf -imblearn -numba -llvmlite -huey -ijson -greenery==3.2 -xlrd -filetype -torchmetrics -pywebview -openml -oslo.concurrency diff --git a/setup.py b/setup.py deleted file mode 100644 index 1688e7825..000000000 --- a/setup.py +++ /dev/null @@ -1,57 +0,0 @@ -import os - -from setuptools import find_packages, setup - -with open("README.rst") as f: - long_description = f.read() - - -def load_requirements(filename): - """Load requirements from a file, ignoring comments and empty lines.""" - with open(os.path.join(os.path.dirname(__file__), filename)) as f: - return [line.strip() for line in f if line.strip() and not line.startswith("#")] - - -# Use your existing requirements files -requirements = load_requirements("requirements.txt") -test_requirements = load_requirements("requirements-dev.txt") - - -setup( - name="DashAI", - version="0.9.6", - license="MIT", - description=( - "DashAI: a graphical toolbox for training, evaluating and deploying " - "state-of-the-art AI models." - ), - long_description=long_description, - long_description_content_type="text/x-rst", - url="https://github.com/DashAISoftware/DashAI", - project_urls={ - "Documentation": "https://dash-ai.com/", - "Changelog": "https://dash-ai.com/changelog.html", - "Issue Tracker": "https://github.com/DashAISoftware/DashAI/issues", - }, - author="DashAI Team", - author_email="fbravo@dcc.uchile.cl", - packages=find_packages(), - include_package_data=True, - python_requires=">=3.10", - install_requires=requirements, - tests_require=test_requirements, - classifiers=[ - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - entry_points={ - "console_scripts": [ - "dashai = DashAI.__main__:run", - "DashAI = DashAI.__main__:run", - ] - }, -) diff --git a/update-server.sh b/update-server.sh index b1449f2b5..2b771ea5d 100644 --- a/update-server.sh +++ b/update-server.sh @@ -89,7 +89,13 @@ success "Frontend built." # ── Python dependencies ─────────────────────────────────────────────────────── info "Installing Python dependencies..." -pip install -r requirements.txt +if command -v uv &>/dev/null; then + # uv targets the active conda env and reads deps from pyproject.toml + uv pip install -e . +else + warn "uv not found, falling back to pip (consider installing uv: https://docs.astral.sh/uv/)." + pip install -e . +fi success "Python dependencies installed." # ── Restart service ─────────────────────────────────────────────────────────── diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..6a770caf2 --- /dev/null +++ b/uv.lock @@ -0,0 +1,8462 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +conflicts = [[ + { package = "dashai", extra = "cpu" }, + { package = "dashai", extra = "cuda" }, +]] + +[[package]] +name = "accelerate" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.12.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/75/94cd5d389649578aca399e5aa822637eec18319a1dadc400ffe2f9a7493f/accelerate-1.14.0.tar.gz", hash = "sha256:41b9c4377a54e0b460a959b0defa1b736e4ca0a2373252d9a539964c2afe3c8d", size = 412167, upload-time = "2026-06-11T13:45:52.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/db/253133d7e7cb40d3af384bb2f5c0b4a2b7fdcffbc95c688cc67a20a3c103/accelerate-1.14.0-py3-none-any.whl", hash = "sha256:e94390c2863b873be18f623f9df48a0d8fe5eff13ea7f1a00092b0a7904888c6", size = 389246, upload-time = "2026-06-11T13:45:50.477Z" }, +] + +[[package]] +name = "aiofiles" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/c3/534eac40372d8ee36ef40df62ec129bee4fdb5ad9706e58a29be53b2c970/aiofiles-25.1.0.tar.gz", hash = "sha256:a8d728f0a29de45dc521f18f07297428d56992a742f0cd2701ba86e44d23d5b2", size = 46354, upload-time = "2025-10-09T20:51:04.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/8a/340a1555ae33d7354dbca4faa54948d76d89a27ceef032c8c3bc661d003e/aiofiles-25.1.0-py3-none-any.whl", hash = "sha256:abe311e527c862958650f9438e859c1fa7568a141b22abcd015e120e86a85695", size = 14668, upload-time = "2025-10-09T20:51:03.174Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/f4/eec0465c2f67b2664688d0240b3212d5196fd89e741df67ddb81f8d35658/aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d", size = 24757, upload-time = "2026-07-01T17:11:55.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/43/1947f06babed6b3f1d7f38b0c767f52df66bfb2bc10b468c4a7de9eceff2/aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472", size = 15038, upload-time = "2026-07-01T17:11:54.055Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "async-timeout", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/67/58ded4b3f2e10f94972d8928050c85330e249a31dd45a0e5f3c0e9c3fa05/aiohttp-3.14.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f6bb621e5863cfe8fe5ff5468002d200ec31f30f1280b259dc505b02595099e", size = 766140, upload-time = "2026-06-07T21:05:37.471Z" }, + { url = "https://files.pythonhosted.org/packages/18/68/4ae5b4e08943f316594bb68da89957d3baf5760588fa09509594bd777e4b/aiohttp-3.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f7215cb3933784f79ed20e5f050e15984f390424339b22375d5a53c933a0491", size = 519430, upload-time = "2026-06-07T21:05:40.751Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c1/316c8f3549dbe5245f92bfd523ec6f32dd4d98cafe21df3f6a19b1184c75/aiohttp-3.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9d4e294455b23a68c9b8f042d0e8e377a265bcb15332753695f6e5b6819e0ce", size = 514406, upload-time = "2026-06-07T21:05:42.111Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ee/fb0ac28684e8d753b83c8a4eebc19a5846912aa0a4daaabb6a9936363840/aiohttp-3.14.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b238af795833d5731d049d82bc84b768ae6f8f97f0495963b3ed9935c5901cc3", size = 1703649, upload-time = "2026-06-07T21:05:43.427Z" }, + { url = "https://files.pythonhosted.org/packages/3b/57/aa2beab673331f111885db8a7b69dfe3ab0e53e446a0ace18ca694b4dc58/aiohttp-3.14.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4e5e0ae56914ecdbf446493addefc0159053dd53962cef37d7839f37f73d505", size = 1675126, upload-time = "2026-06-07T21:05:44.897Z" }, + { url = "https://files.pythonhosted.org/packages/47/ea/dad128abe365e79be03b16ed464198ac73e0d257e8260c6f7d6f31cbef26/aiohttp-3.14.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:092e4ce3619a7c6dee52a6bdabda973d9b34b66781f840ce93c7e0cec30cf521", size = 1771558, upload-time = "2026-06-07T21:05:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/63/f3/b5b4e10327cb85d34d24232c6b71b64602f190b3ccb238a043ac6b187dac/aiohttp-3.14.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb33777ea21e8b7ecde0e6fc84f598be0a1192eab1a63bc746d75aa75d38e7bd", size = 1856631, upload-time = "2026-06-07T21:05:47.844Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9d/93294c3045775c708ac8310eb3d3622a11d2951345ad590d532d62a1faa4/aiohttp-3.14.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23119f8fd4f5d16902ed459b63b100bcd269628075162bddac56cc7b5273b3fb", size = 1714139, upload-time = "2026-06-07T21:05:49.982Z" }, + { url = "https://files.pythonhosted.org/packages/29/c4/93067c85a0373492ce8e577435203c5947c454af074ac48ed4f3a1b9dd4a/aiohttp-3.14.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:57fc6745a4b7d0f5a9eb4f40a69718be6c0bc1b8368cc9fe89e90118719f4f42", size = 1588321, upload-time = "2026-06-07T21:05:51.431Z" }, + { url = "https://files.pythonhosted.org/packages/c4/39/9ff91aaf02af8b7b8222a987466da539f154c3e01732c22b5f5a20a8ee66/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6fd35beba67c4183b09375c5fff9accb47524191a244a99f95fd4472f5402c2b", size = 1670375, upload-time = "2026-06-07T21:05:53.109Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e4/77452a3676b8d99ac1375f77691d6bf65ea6e9f4b201b82ef77c916dc767/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:672b9d65f42eb877f5c3f234a4547e4e1a226ca8c2eed879bb34670a0ce51192", size = 1690933, upload-time = "2026-06-07T21:05:54.902Z" }, + { url = "https://files.pythonhosted.org/packages/7d/84/b0059a7c7fc05ea23f3bc1596ba91c12f79588b9450564a24cac37536d0a/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:24ba13339fed9251d9b1a1bec8c7ab84c0d1675d79d33501e11f94f8b9a84e05", size = 1740798, upload-time = "2026-06-07T21:05:56.458Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3a/e2a513ecbfc362591caa51a7f7e011b3bfc8938b388ae44cd95560d36999/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:94da27378da0610e341c4d30de29a191672683cc82b8f9556e8f7c7212a020fe", size = 1576412, upload-time = "2026-06-07T21:05:57.953Z" }, + { url = "https://files.pythonhosted.org/packages/a1/10/08f1654f538f93d36dcac66310a06eefce4641cdafca83f9f0a5317be254/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52cdac9432d8b4a719f35094a818d95adcae0f0b4fe9b9b921909e0c87de9e7d", size = 1750199, upload-time = "2026-06-07T21:05:59.488Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/d91b70c57d8b8e9611e4a2e52238ca3698d3dc1c2efe25b7a9bf594ac584/aiohttp-3.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:672ac254412a24d0d0cf00a9e6c238877e4be5e5fa2d188832c1244f45f31966", size = 1699356, upload-time = "2026-06-07T21:06:01.131Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f1/15340176f35ff61b95dbe34020bcf43f9e624a2d7bbac934715ff97d2033/aiohttp-3.14.1-cp310-cp310-win32.whl", hash = "sha256:2fe3607e71acc6ebb0ec8e492a247bf7a291226192dc0084236dfc12478916f6", size = 458939, upload-time = "2026-06-07T21:06:02.86Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c2/a2f1ec5b37f903109e43ae2862268cfe4a67a60c1b2cf43169fcdff5995f/aiohttp-3.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:30099eda75a53c32efb0920e9c33c195314d2cc1c680fbfd30894932ac5f27df", size = 482583, upload-time = "2026-06-07T21:06:04.666Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/7b56f6732ef79530afaa72aa335d41b67c8d79b946995f0b11ad72985435/aiohttp-3.14.1-cp310-cp310-win_arm64.whl", hash = "sha256:5a837f49d901f9e368651b676912bff1104ed8c1a83b280bcd7b29adccef5c9c", size = 453470, upload-time = "2026-06-07T21:06:06.322Z" }, + { url = "https://files.pythonhosted.org/packages/26/dd/bf526e6f0a1120dd6f2df2e97bacfe4d358f13d17a0ff5847301a1375a51/aiohttp-3.14.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2", size = 765225, upload-time = "2026-06-07T21:06:07.957Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e1/a2872aa55495a70f61310d411541c6ee23812d9a884e000c716e1bc3edbf/aiohttp-3.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f", size = 518743, upload-time = "2026-06-07T21:06:09.749Z" }, + { url = "https://files.pythonhosted.org/packages/5b/e7/c60c7b209e509cc787de3cea0550a518538cfc08003e1c1e14c1c63fff71/aiohttp-3.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8", size = 514139, upload-time = "2026-06-07T21:06:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8d/614ace2f579702c9840ab1e1447fd8509e35b0b904f7196418fa2f57b25d/aiohttp-3.14.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04", size = 1784088, upload-time = "2026-06-07T21:06:12.887Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/726e90f99542bf292f81a96a12cc4847deb86f3ccf62c6f4014a201f4d33/aiohttp-3.14.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8", size = 1737835, upload-time = "2026-06-07T21:06:14.564Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4b/d176d5c4db9d33dacf0543102ea59503bc1d528af4cfd0b719949ca49389/aiohttp-3.14.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6", size = 1842801, upload-time = "2026-06-07T21:06:16.228Z" }, + { url = "https://files.pythonhosted.org/packages/dc/d6/5a99b563690ea0cbed912ae94a2ce33993a5709a651a3a4fe761e7dd973a/aiohttp-3.14.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af", size = 1929992, upload-time = "2026-06-07T21:06:17.947Z" }, + { url = "https://files.pythonhosted.org/packages/76/7f/a987b14a3859094b3cea3f4825219c3e5536242564af6e3f9c2f6c994eb2/aiohttp-3.14.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730", size = 1786989, upload-time = "2026-06-07T21:06:19.677Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1a/420e5c85a3e73349372ed22ce0b6af86bfa6ce16a4b20a64a2e94608c781/aiohttp-3.14.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621", size = 1640129, upload-time = "2026-06-07T21:06:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/a7/80/18a592ed3be0a402cc03670bd72ee1f8563ddbe1d8d5542dbf868f274136/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee", size = 1756576, upload-time = "2026-06-07T21:06:24.8Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0b/8b3d5713373858ff71a617daf6e3b0e81ad63e79d09a3cf2f6b6b983939c/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573", size = 1754668, upload-time = "2026-06-07T21:06:26.528Z" }, + { url = "https://files.pythonhosted.org/packages/9f/49/fd564575cf225821d7ba5a117cb8bc27213d8a7e1811162afb43ae077039/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7", size = 1817019, upload-time = "2026-06-07T21:06:28.297Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/e850c9ae6fc91356552ae668bb6c51e93fa29c8aef13398a10b56678557f/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf", size = 1631638, upload-time = "2026-06-07T21:06:30.242Z" }, + { url = "https://files.pythonhosted.org/packages/eb/94/3c337ba72451a89806ace6f75bddc92bafc5b8d53d90115a512858024b63/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85", size = 1835660, upload-time = "2026-06-07T21:06:31.943Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9c/9c18cf367a0498212d9ba7daf990b504a5e8ae064cda4b504e2647c89c03/aiohttp-3.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3", size = 1775698, upload-time = "2026-06-07T21:06:33.72Z" }, + { url = "https://files.pythonhosted.org/packages/b5/63/a251a9d2a6cb45065b2ddc0bde2b3dd10108740a9a42f632c66405a761a2/aiohttp-3.14.1-cp311-cp311-win32.whl", hash = "sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126", size = 458386, upload-time = "2026-06-07T21:06:35.279Z" }, + { url = "https://files.pythonhosted.org/packages/17/ca/69274c51dcd6e8947d77b2806cf47a4a15f2c846e2cbeb1882547d3da283/aiohttp-3.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5", size = 483406, upload-time = "2026-06-07T21:06:36.824Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8a/c25904f77690c3688ec140f87591ef11a0cfe36bf3d5c0f1f38056fb62b3/aiohttp-3.14.1-cp311-cp311-win_arm64.whl", hash = "sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b", size = 452987, upload-time = "2026-06-07T21:06:38.371Z" }, + { url = "https://files.pythonhosted.org/packages/1d/21/151624b51cd92553d95424daf4bf19f19ce9be9002d19253e7e7ce67197b/aiohttp-3.14.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480", size = 757402, upload-time = "2026-06-07T21:06:40.311Z" }, + { url = "https://files.pythonhosted.org/packages/c2/82/280619e0bd7bf2454987e19282616e84762255dd9c8468f62382e8c191f1/aiohttp-3.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d", size = 512310, upload-time = "2026-06-07T21:06:42.207Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/2aac325583aaa1353045f96dffa586d8a34e8322e14a7ba49cffeb103ab4/aiohttp-3.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2", size = 512448, upload-time = "2026-06-07T21:06:43.813Z" }, + { url = "https://files.pythonhosted.org/packages/8a/72/a60607cb849faa8af8a356c9329ea2eb6f395d49e82cc82ccba1fd8deb8f/aiohttp-3.14.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2", size = 1766854, upload-time = "2026-06-07T21:06:45.391Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d3/d9fe1c9ec7557ab4d0d82bebaa728c6418f0b93295ec2f4ab015f7710cc7/aiohttp-3.14.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a", size = 1740884, upload-time = "2026-06-07T21:06:47.413Z" }, + { url = "https://files.pythonhosted.org/packages/c1/dc/f2cecfaf9337ba3e63f181500814ff502aa3d00d9c7ec93a9d23d10a27b2/aiohttp-3.14.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264", size = 1810034, upload-time = "2026-06-07T21:06:50.165Z" }, + { url = "https://files.pythonhosted.org/packages/66/d7/2ff65c5e65c0d7476daf7e15c032e0805e36811185b9623e3238ad6c763e/aiohttp-3.14.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842", size = 1904054, upload-time = "2026-06-07T21:06:52.035Z" }, + { url = "https://files.pythonhosted.org/packages/20/9c/d445818389df371f56d141d881153ba23183c4735a03f7356ffb43f7757d/aiohttp-3.14.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c", size = 1790278, upload-time = "2026-06-07T21:06:54.049Z" }, + { url = "https://files.pythonhosted.org/packages/4d/aa/bf04cb4d865fc6101c2229a294ad744973b72e513fdc5a6b791e6983d72a/aiohttp-3.14.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95", size = 1591795, upload-time = "2026-06-07T21:06:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b4/4dac0038960427ba832f6609dfb4ea5437d7fd80c72001b9e48f834f428b/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199", size = 1728397, upload-time = "2026-06-07T21:06:57.777Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/7cd4e8ad7aa3b75f17d56bb5498dd604a93d4e6eece822ba0568c413fff0/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817", size = 1766504, upload-time = "2026-06-07T21:07:00.009Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/fc01d9fcad0f73fed3f3d361f1f94f975947b50dff82919f6dc2bf4316cc/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a", size = 1777806, upload-time = "2026-06-07T21:07:02.064Z" }, + { url = "https://files.pythonhosted.org/packages/41/09/47e2d090bddcc8fb4ccb4c314aadc32d7c5d9bb55f50f6ad1c92fc15d501/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4", size = 1580707, upload-time = "2026-06-07T21:07:03.942Z" }, + { url = "https://files.pythonhosted.org/packages/3d/36/f1a4ce904ae0b6930cfe9afc96d0896f7ec1a620c400405d63783bb95a9c/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087", size = 1798121, upload-time = "2026-06-07T21:07:05.987Z" }, + { url = "https://files.pythonhosted.org/packages/70/0a/e0075ce9ca0279ee1d4f0c0b85f54fea02ebc83c3007651a72bece658fec/aiohttp-3.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3", size = 1767580, upload-time = "2026-06-07T21:07:07.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/a0c0a8f327a9c52095cdd8e312391b00d3ed64ab6c72bb5c33d8ec251cf7/aiohttp-3.14.1-cp312-cp312-win32.whl", hash = "sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4", size = 452771, upload-time = "2026-06-07T21:07:09.669Z" }, + { url = "https://files.pythonhosted.org/packages/df/d9/ea367c75f16ac9c6cdc8febb25e8318fa21a2b1bc8d6514d4b2d890bface/aiohttp-3.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271", size = 479873, upload-time = "2026-06-07T21:07:11.538Z" }, + { url = "https://files.pythonhosted.org/packages/03/64/8d96784a7851156db8a4c6c3f6f91042fdf39fb15a4cc38c8b3c14833c45/aiohttp-3.14.1-cp312-cp312-win_arm64.whl", hash = "sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847", size = 448073, upload-time = "2026-06-07T21:07:13.637Z" }, + { url = "https://files.pythonhosted.org/packages/bc/97/bd137012dd97e1649162b099135a80e1fd59aaa807b2430fc448d1029aff/aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178", size = 506882, upload-time = "2026-06-07T21:07:15.501Z" }, + { url = "https://files.pythonhosted.org/packages/ef/79/e5cc690e9d922a66887ceeaca53a8ffd5a7b0be3816142b7abc433742d89/aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf", size = 515270, upload-time = "2026-06-07T21:07:17.53Z" }, + { url = "https://files.pythonhosted.org/packages/fe/22/a73ccbf9dbd6e26dda0b24d5fd5db7da92ee3383a79f47677ffb834c5c5b/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd", size = 485841, upload-time = "2026-06-07T21:07:19.555Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b9/57ed8eaf596321c2ad747bd480fb1700dbd7177c60dfc9e4c187f629662e/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe", size = 492088, upload-time = "2026-06-07T21:07:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/78/c0/5ebe5270a7c140d7c6f79dcb018640225f14d406c149e4eec04a7d82fe71/aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4", size = 501564, upload-time = "2026-06-07T21:07:23.388Z" }, + { url = "https://files.pythonhosted.org/packages/75/7f/8cdaa24fc7983865e0915153b96a9ac5bcdd3548d64c5a27d17cecccad2d/aiohttp-3.14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876", size = 751998, upload-time = "2026-06-07T21:07:25.046Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f4/c4227aacfacc5cb0cc2d119b65301d177912a6842cd64e120c47af76064f/aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da", size = 510918, upload-time = "2026-06-07T21:07:27.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/01/a2d5f96cd4e74424864d30bc0a7e44d0a12dacdcfa91b5b2d1bd3dca6bf3/aiohttp-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6", size = 508657, upload-time = "2026-06-07T21:07:29.252Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ed/3c0fb5c500fdd8e7ebc10d1889c04384fffa1a9163eac1356088ca9da1b1/aiohttp-3.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96", size = 1757907, upload-time = "2026-06-07T21:07:31.03Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ab/d4c924d9bd5be3050c226612413ce68cb54c70d2c31b661bfc8d9a5b6a70/aiohttp-3.14.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f", size = 1737565, upload-time = "2026-06-07T21:07:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/37326821ff779084020cdc33224d20b19f42f4183a500ff92022a739eda7/aiohttp-3.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296", size = 1799018, upload-time = "2026-06-07T21:07:35.003Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4f/6e947ba73e4ce09070761c05ed3a8ceb7c21f5e46798671d8b2aac0e4626/aiohttp-3.14.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa", size = 1894416, upload-time = "2026-06-07T21:07:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6e/dbf1d0625dc711fb2851f4f3c3055c39ed58bae92082d8c627dbe6013736/aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451", size = 1783881, upload-time = "2026-06-07T21:07:39.063Z" }, + { url = "https://files.pythonhosted.org/packages/44/c2/5e25098a67268ed369483ae7d1a58bd0a13d03aab860d2a0e4a6eb25b046/aiohttp-3.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c", size = 1587572, upload-time = "2026-06-07T21:07:41.058Z" }, + { url = "https://files.pythonhosted.org/packages/2a/bd/cf9cee17e140f942a3de73e658a543aa8fbf35a5fc67a9d2538d52d77f0b/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca", size = 1722137, upload-time = "2026-06-07T21:07:43.014Z" }, + { url = "https://files.pythonhosted.org/packages/89/6d/5684f8c59045c96f81a18cefbc1fbbd79d25b88f1c622f2a5c5c08fcb632/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09", size = 1755953, upload-time = "2026-06-07T21:07:45.933Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/35caf3170f8359760740a7d9aa0fff2e344bef98e1d1186f5a0f6dec17e6/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397", size = 1766479, upload-time = "2026-06-07T21:07:48.047Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a1/b0c61e7a137f0d81de49a82023a6df73c3c16d6fefb0f8e4a93d21639002/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080", size = 1580077, upload-time = "2026-06-07T21:07:50.069Z" }, + { url = "https://files.pythonhosted.org/packages/0b/41/194ea4623693009fcefebef7aef63c141754f153e9cd0d39d3b9e36c175c/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345", size = 1791688, upload-time = "2026-06-07T21:07:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/ba/45/4de841f005cfe1fd63e2a2fe011262c515e2a62aa6994b15947e7d717ac9/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588", size = 1761094, upload-time = "2026-06-07T21:07:54.113Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ae/dbce10533d3896d544d5053939ed75b7dc31a1b0973d959b1b5ae21028d6/aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780", size = 452662, upload-time = "2026-06-07T21:07:56.06Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/0bf1a19362c32f06229da5e7ddfcec91f93474d6307f7a2d3135e9c674dc/aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a", size = 479748, upload-time = "2026-06-07T21:07:58.319Z" }, + { url = "https://files.pythonhosted.org/packages/22/0a/62e7232dc9484fbec112ceb32efb6a624cc7994ec6e2b019286f17c4e8f2/aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8", size = 447723, upload-time = "2026-06-07T21:08:00.154Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a1/5fafa04e1ca91ddb47608699d60649c1c6db3cf41c99e78fc4056f9513db/aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15", size = 508531, upload-time = "2026-06-07T21:08:02.093Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/bfa02f699d87ffc86d5959270b28f1cb410add3ccaced8ed2e0b8a5238fc/aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8", size = 514718, upload-time = "2026-06-07T21:08:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/85/a5/9594ad6289eebbc97d167c44213d557807f90e59115caad24de21ad2c3b1/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3", size = 487918, upload-time = "2026-06-07T21:08:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/b4/61/16a32c36c3c49edec122a3dc811f2057df2f94d3b14aa107c8017d981618/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba", size = 494014, upload-time = "2026-06-07T21:08:08.263Z" }, + { url = "https://files.pythonhosted.org/packages/9b/89/3ebcf96ed99c05bec9c434aaac6963fd3cbab4a786ae739908a144d9ce44/aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397", size = 502398, upload-time = "2026-06-07T21:08:10.244Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3d/b74870a0c2d40c355928cd5b96c7a11fa821b8a40fc41365e64479b151fb/aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448", size = 758018, upload-time = "2026-06-07T21:08:12.447Z" }, + { url = "https://files.pythonhosted.org/packages/d3/66/f42f5c984d99e49c6cff5f26f590750f2e2f7ef1fcfb99966ab5be1b632e/aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004", size = 512462, upload-time = "2026-06-07T21:08:14.624Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a7/248e1aebe0c7810b0271e021a0f2a5eb6e78a051885b3c9df49f42a5802d/aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983", size = 512824, upload-time = "2026-06-07T21:08:16.572Z" }, + { url = "https://files.pythonhosted.org/packages/26/97/2aa0e5ba0727dc3bd5aaebb7ccbc510f7dfb7fb961ec87497cd496635ab1/aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe", size = 1749898, upload-time = "2026-06-07T21:08:18.635Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/e97f6c96c891d457c8479d92a514ba194d0412f981d72c70341ee18488ed/aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333", size = 1710114, upload-time = "2026-06-07T21:08:20.892Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e6/aa8d7e863048c8fceb5cd6ce74017311cec3ead07847387e12265fb4444e/aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0", size = 1802541, upload-time = "2026-06-07T21:08:23.044Z" }, + { url = "https://files.pythonhosted.org/packages/83/a8/72193137de57fda4ebfae4563182d082c8856e3b6e9871d0b46f028fb369/aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a", size = 1875776, upload-time = "2026-06-07T21:08:25.288Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/938441025db6769a3464596b2410af3afde0b21eb2f204c6f766f68af4bd/aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602", size = 1760329, upload-time = "2026-06-07T21:08:27.363Z" }, + { url = "https://files.pythonhosted.org/packages/60/29/bf2496b4065e76e09fe48015aaffe5ce161d8f089b06ac6982070f653076/aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca", size = 1587293, upload-time = "2026-06-07T21:08:29.805Z" }, + { url = "https://files.pythonhosted.org/packages/49/a2/2136674d52123b1354bd05dd5753c318db47dc0c927cc70b27bab3755456/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35", size = 1714756, upload-time = "2026-06-07T21:08:32.094Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b9/e5fd2e6f915503081c0f9b1e8540947037929c70c191da2e4d54b31a21a1/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844", size = 1721052, upload-time = "2026-06-07T21:08:34.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/5a/2833e324a2263e104e31e2e91bc5bbee81bc499afd32203faee048a883f0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496", size = 1766888, upload-time = "2026-06-07T21:08:36.95Z" }, + { url = "https://files.pythonhosted.org/packages/57/fa/dea6511870913162f3b2e8c42a7614eb203a4540b8c2da43e0bfb0548f3c/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5", size = 1581679, upload-time = "2026-06-07T21:08:39.292Z" }, + { url = "https://files.pythonhosted.org/packages/14/bd/3cf0d55e71784b33534e9710a67d382d900598b4787fbce6cc7317f8c42a/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95", size = 1782021, upload-time = "2026-06-07T21:08:41.407Z" }, + { url = "https://files.pythonhosted.org/packages/c1/af/14bb5843eccbe234f4dfb78ab73e549d99727247e62ae5d62cbd22eaf5b0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444", size = 1742574, upload-time = "2026-06-07T21:08:43.795Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1e/fbeb7af9210a67ac0f9c9bec0f8f4568497924e33137a3d5b48e1cf85f3f/aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0", size = 457773, upload-time = "2026-06-07T21:08:46.168Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2b/13e8d741a9ec5db7d900c060554cf8352ab85e44e2a4469ebb9d377bda17/aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719", size = 485001, upload-time = "2026-06-07T21:08:48.401Z" }, + { url = "https://files.pythonhosted.org/packages/df/30/491acfa2c4d6c3ff59c49a14fc1b50be3241e25bbb0c84c09e2da4d11395/aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec", size = 453809, upload-time = "2026-06-07T21:08:50.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/19dbe1a1f4cc6230eb9e314de7fe68053b0992f9302b27d12141a0b5db53/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2", size = 793320, upload-time = "2026-06-07T21:08:52.775Z" }, + { url = "https://files.pythonhosted.org/packages/7f/20/1b7182219ba1b108430d6e4dc53d25ae02dcfcf5a045b33af4e8c5167527/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340", size = 529077, upload-time = "2026-06-07T21:08:55Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c8/14ce60ec31a2e5f5274bb17d383a6f7a3aabca31ac04eee05585bbadab16/aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d", size = 532476, upload-time = "2026-06-07T21:08:57.176Z" }, + { url = "https://files.pythonhosted.org/packages/7e/02/9ac85e081e53da2e061b02fa7758fe0a12d17b8ce2d1f5e6c7cb76730328/aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94", size = 1922347, upload-time = "2026-06-07T21:08:59.563Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3e/d3ba07a0ab38b5389e10bec4362d21e10a4f667cba2d79ba30837b3a5059/aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc", size = 1786465, upload-time = "2026-06-07T21:09:01.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/cb/e2ee978a00cfb2df829704a69528b18154eba5939f45bc1efa8f33aee4c5/aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251", size = 1909423, upload-time = "2026-06-07T21:09:04.357Z" }, + { url = "https://files.pythonhosted.org/packages/73/5d/1430334858b1022b58ae50399a918f0bd6fe8fa7fa183598d657ff61e040/aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1", size = 2001906, upload-time = "2026-06-07T21:09:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/66/4e/560c7472d3d198a23aa5c8b19a5115bf6a9b77b7d3e4bb363da320430ad2/aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3", size = 1877095, upload-time = "2026-06-07T21:09:09.011Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f1/4745806578d447db4a784a8591e2dae3afdfc2bcb96f8f81271b13df6543/aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c", size = 1676222, upload-time = "2026-06-07T21:09:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/48255813cca749a229ef0ab476004ec623728ad79a9c0840616f6c076325/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203", size = 1842922, upload-time = "2026-06-07T21:09:14.118Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c0/bbd054e2bee909f529523a5af3891052606af5143c09f5f183ec3b234676/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1", size = 1825035, upload-time = "2026-06-07T21:09:16.447Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ae/90395d4376deceb74e09ec26b6adf7d2015a6f8802d6d84446af860fef04/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665", size = 1849512, upload-time = "2026-06-07T21:09:18.742Z" }, + { url = "https://files.pythonhosted.org/packages/93/bd/fb25f3049957553d4ce0ba6ae480aa2f592a6985497fca590837d16c1be0/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1", size = 1668571, upload-time = "2026-06-07T21:09:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/3f/22/7f73303d64dd567ff3addca90b556690ed1233a47b8f55d242fb90af3681/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d", size = 1881159, upload-time = "2026-06-07T21:09:23.813Z" }, + { url = "https://files.pythonhosted.org/packages/44/be/0474c5a8b5640e1e4aa1923430a91f4151be82e511373fe764189b89aef5/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c", size = 1841409, upload-time = "2026-06-07T21:09:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3c/bb4a7cba26956cb3da4553cc2056cf67be5b5ff6e6d8fa4fbdff73bfb7ae/aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365", size = 494166, upload-time = "2026-06-07T21:09:28.505Z" }, + { url = "https://files.pythonhosted.org/packages/8a/84/ec80c2c1f66a952555a9f86df6b33af65108a6febfa0471b69013a12f807/aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9", size = 530255, upload-time = "2026-06-07T21:09:30.843Z" }, + { url = "https://files.pythonhosted.org/packages/2a/71/6e22be134a4061ada85a92951b842f2657f17d926b727f3f94c56ae963d6/aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6", size = 469640, upload-time = "2026-06-07T21:09:33.028Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "alabaster" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, +] + +[[package]] +name = "alembic" +version = "1.18.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mako" }, + { name = "sqlalchemy" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/cc/ac0bed8e562e7407fe55c3ba85a4dce86e6dbd8730887bd1e406a6c5c18a/alembic-1.18.5.tar.gz", hash = "sha256:1554982221dd17e9a749b53902407578eb305e453f71999e8c7f0a48389fff8e", size = 2060480, upload-time = "2026-06-25T15:20:54.888Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/78/5fe6dc3a3a5b2f5a2a4faef8bfe336d5fa049a38884ab3172e0098160c01/alembic-1.18.5-py3-none-any.whl", hash = "sha256:06d8ba9d04558022f5395e9317de03d270f3dced49cee01f89fe7a13c26f14bc", size = 264664, upload-time = "2026-06-25T15:20:56.673Z" }, +] + +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/ba4e4ca8d149f8dcc0d952ac0967089e1d759c7e5fcf0865a317eb680fbb/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e", size = 24549, upload-time = "2025-07-30T10:02:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/9b2386cc75ac0bd3210e12a44bfc7fd1632065ed8b80d573036eecb10442/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d", size = 25539, upload-time = "2025-07-30T10:02:00.929Z" }, + { url = "https://files.pythonhosted.org/packages/31/db/740de99a37aa727623730c90d92c22c9e12585b3c98c54b7960f7810289f/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584", size = 28467, upload-time = "2025-07-30T10:02:02.08Z" }, + { url = "https://files.pythonhosted.org/packages/71/7a/47c4509ea18d755f44e2b92b7178914f0c113946d11e16e626df8eaa2b0b/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690", size = 27355, upload-time = "2025-07-30T10:02:02.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/82/82745642d3c46e7cea25e1885b014b033f4693346ce46b7f47483cf5d448/argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520", size = 29187, upload-time = "2025-07-30T10:02:03.674Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/ad/0d70a3a2d6e01968d985415259e8ec7ad3f777903f9b1c1f3c8c44642c60/ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", size = 61489, upload-time = "2026-06-30T20:02:55.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/12/3e5f575f156555547c250a8b0d1347517a3a20fc7f4492e9703a69d4f45e/ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", size = 1177640, upload-time = "2026-06-30T20:02:06.708Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a4/921a9e27951627983b0f368859ea00f8330a551dc0bf4c2fdcb11855a98b/ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", size = 1168111, upload-time = "2026-06-30T20:02:08.366Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/950cf404de7b8782cf95e5c1237e25e2aa46177b287f39f9eeddf481fd6f/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", size = 1227656, upload-time = "2026-06-30T20:02:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a8/46f8f6a6479d9d2273980957bb091a506c55f5b95d3c029ee58518a78407/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", size = 1227706, upload-time = "2026-06-30T20:02:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/9ac415bda0a40e49eab8fea3b2741c19c98bb84d57d62c4cfc6230eb67be/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", size = 1431705, upload-time = "2026-06-30T20:02:12.737Z" }, + { url = "https://files.pythonhosted.org/packages/e5/06/8807115d441444879f7561b5eede5ac18fc80392f11826d61ccf31f503b1/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", size = 1249533, upload-time = "2026-06-30T20:02:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/c2ba82ef9618650357d9421a1fdb27ffec862a7f57e8e2de82a3ccd11e12/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", size = 1252619, upload-time = "2026-06-30T20:02:16.219Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a7/fa31d52dd4102cede29fb9634e98d214129b2783b4f95528c6dc6a8f6587/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", size = 1242983, upload-time = "2026-06-30T20:02:17.813Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/ddf742b5ad3c4bafd3466f2265037cfd99bc1b9a5ee46a5d58c90d523242/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", size = 1296148, upload-time = "2026-06-30T20:02:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/24/cb/9f6f217cce8b3b632c5568b478d195a35e79dce4dbe309438cb89ba6ea4f/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", size = 1403826, upload-time = "2026-06-30T20:02:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/9d16d4f0107a183924425cc0e7618d8bf76f96b45afa9ff19f924ed1ad57/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554", size = 1502943, upload-time = "2026-06-30T20:02:22.034Z" }, + { url = "https://files.pythonhosted.org/packages/80/dd/bbc1c38756350dddf7e24acae1c9482ef42051c267417e019aecc1ed4075/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", size = 1497632, upload-time = "2026-06-30T20:02:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/9daffefcf5b97e6bb4c3e0b3c024c1aee9722f23d3cf7cd2ff80d6fb4a40/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", size = 1448858, upload-time = "2026-06-30T20:02:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1f/f9baaab81a677ea0af7d2458cac2f94ebcc85958f8a3c15ba9d9e5dab653/ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", size = 1052600, upload-time = "2026-06-30T20:02:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/41b535866519512d8cf6669cb2cff7823b7672bb6279c0333b4ff89d7d9f/ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", size = 1095570, upload-time = "2026-06-30T20:02:27.639Z" }, + { url = "https://files.pythonhosted.org/packages/50/64/e472fe3e3a2d33d874b987e8518aedf24562919e3b6161a4fa1797e89c0f/ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", size = 1067267, upload-time = "2026-06-30T20:02:28.949Z" }, + { url = "https://files.pythonhosted.org/packages/52/19/ac8348ae8711c9b5ae834634f635780cab62a0f5e6f988882e048b89c2ae/ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", size = 1185367, upload-time = "2026-06-30T20:02:30.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f6/ec7ec652c51db77c2f61d8573338e13e4704303265ccc658cb4031d9f354/ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", size = 1178657, upload-time = "2026-06-30T20:02:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", size = 1238620, upload-time = "2026-06-30T20:02:33.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/21/087957bba486242afc52f49b2d9e21c9dad00289356cf9efe67084015a9d/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", size = 1236075, upload-time = "2026-06-30T20:02:34.936Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/78128bbb170071c2c72a210a181f1c00e11cc1cec60a8beef747b07f9201/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", size = 1441348, upload-time = "2026-06-30T20:02:36.245Z" }, + { url = "https://files.pythonhosted.org/packages/64/64/62fb99d6faf199b4c3e5b08a07136e9a0d7664bb249c6de3670e5b63e9b6/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", size = 1258580, upload-time = "2026-06-30T20:02:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", size = 1261693, upload-time = "2026-06-30T20:02:39.123Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/3676ca2191f39bafb75f93f99b2f429ec464586158fece2165f3572805dc/ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", size = 1252517, upload-time = "2026-06-30T20:02:40.511Z" }, + { url = "https://files.pythonhosted.org/packages/f3/58/494ef8c4b4acb2f4a265ac934caf45f792a08fe27d6b853de35ad991941a/ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", size = 1304843, upload-time = "2026-06-30T20:02:41.961Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f2/13736d920ab3d49bbee80ef1a277dd7b7aaf3b3545efd9d2a8114fe05525/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", size = 1413698, upload-time = "2026-06-30T20:02:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5a/e046f3899e2acba4677d7427b76431443a1aa1a0e583dfb05b55b69d55cf/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", size = 1512209, upload-time = "2026-06-30T20:02:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/e42aaca7bb2d22a7c06d5a8c7930086c5a334e93d716e6fa5e6647a4515f/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", size = 1508464, upload-time = "2026-06-30T20:02:46.942Z" }, + { url = "https://files.pythonhosted.org/packages/95/93/5524a3dc6c3f593de3228ed9cbef73afa047625b7000ec21b7f58e6eb4d4/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", size = 1457164, upload-time = "2026-06-30T20:02:48.294Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c0/36a6ffb4d653cf621427b4c4928671f53ad800c453474de2b82564a44ad9/ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", size = 863014, upload-time = "2026-06-30T20:02:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/09/c7/7d5ad8b49e1278e1c2a1e0274bd7850560b3f09313aa00c13bc8d5544792/ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", size = 1063165, upload-time = "2026-06-30T20:02:50.98Z" }, + { url = "https://files.pythonhosted.org/packages/47/ae/6710c14ecb276031cf10249f6adf5a59e2d3fdb3b5183bd59f70524067ee/ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", size = 1101444, upload-time = "2026-06-30T20:02:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backports-asyncio-runner" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, +] + +[[package]] +name = "beartype" +version = "0.22.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, +] + +[[package]] +name = "bottle" +version = "0.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/71/cca6167c06d00c81375fd668719df245864076d284f7cb46a694cbeb5454/bottle-0.13.4.tar.gz", hash = "sha256:787e78327e12b227938de02248333d788cfe45987edca735f8f88e03472c3f47", size = 98717, upload-time = "2025-06-15T10:08:59.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/f6/b55ec74cfe68c6584163faa311503c20b0da4c09883a41e8e00d6726c954/bottle-0.13.4-py2.py3-none-any.whl", hash = "sha256:045684fbd2764eac9cdeb824861d1551d113e8b683d8d26e296898d3dd99a12e", size = 103807, upload-time = "2025-06-15T10:08:57.691Z" }, +] + +[[package]] +name = "certifi" +version = "2026.6.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, + { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, + { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, + { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, + { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, + { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, + { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, + { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/08/0f303cb0b529e456bb116f2d50565a482694fbb94340bf56d44677e7ed03/charset_normalizer-3.4.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cdd68a1fb318e290a2077696b7eb7a21a49163c455979c639bf5a5dcdc46617d", size = 315182, upload-time = "2026-04-02T09:25:40.673Z" }, + { url = "https://files.pythonhosted.org/packages/24/47/b192933e94b546f1b1fe4df9cc1f84fcdbf2359f8d1081d46dd029b50207/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e17b8d5d6a8c47c85e68ca8379def1303fd360c3e22093a807cd34a71cd082b8", size = 209329, upload-time = "2026-04-02T09:25:42.354Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/01fa81c5ca6141024d89a8fc15968002b71da7f825dd14113207113fabbd/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:511ef87c8aec0783e08ac18565a16d435372bc1ac25a91e6ac7f5ef2b0bff790", size = 231230, upload-time = "2026-04-02T09:25:44.281Z" }, + { url = "https://files.pythonhosted.org/packages/20/f7/7b991776844dfa058017e600e6e55ff01984a063290ca5622c0b63162f68/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:007d05ec7321d12a40227aae9e2bc6dca73f3cb21058999a1df9e193555a9dcc", size = 225890, upload-time = "2026-04-02T09:25:45.475Z" }, + { url = "https://files.pythonhosted.org/packages/20/e7/bed0024a0f4ab0c8a9c64d4445f39b30c99bd1acd228291959e3de664247/charset_normalizer-3.4.7-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf29836da5119f3c8a8a70667b0ef5fdca3bb12f80fd06487cfa575b3909b393", size = 216930, upload-time = "2026-04-02T09:25:46.58Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ab/b18f0ab31cdd7b3ddb8bb76c4a414aeb8160c9810fdf1bc62f269a539d87/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:12d8baf840cc7889b37c7c770f478adea7adce3dcb3944d02ec87508e2dcf153", size = 202109, upload-time = "2026-04-02T09:25:48.031Z" }, + { url = "https://files.pythonhosted.org/packages/82/e5/7e9440768a06dfb3075936490cb82dbf0ee20a133bf0dd8551fa096914ec/charset_normalizer-3.4.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d560742f3c0d62afaccf9f41fe485ed69bd7661a241f86a3ef0f0fb8b1a397af", size = 214684, upload-time = "2026-04-02T09:25:49.245Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/8c61d8da9f062fdf457c80acfa25060ec22bf1d34bbeaca4350f13bcfd07/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b14b2d9dac08e28bb8046a1a0434b1750eb221c8f5b87a68f4fa11a6f97b5e34", size = 212785, upload-time = "2026-04-02T09:25:50.671Z" }, + { url = "https://files.pythonhosted.org/packages/66/cd/6e9889c648e72c0ab2e5967528bb83508f354d706637bc7097190c874e13/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:bc17a677b21b3502a21f66a8cc64f5bfad4df8a0b8434d661666f8ce90ac3af1", size = 203055, upload-time = "2026-04-02T09:25:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/7a951d6a08aefb7eb8e1b54cdfb580b1365afdd9dd484dc4bee9e5d8f258/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:750e02e074872a3fad7f233b47734166440af3cdea0add3e95163110816d6752", size = 232502, upload-time = "2026-04-02T09:25:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/abcf2d83bf8e0a1286df55cd0dc1d49af0da4282aa77e986df343e7de124/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4e5163c14bffd570ef2affbfdd77bba66383890797df43dc8b4cc7d6f500bf53", size = 214295, upload-time = "2026-04-02T09:25:54.765Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/7d4cd7ed54be99973a0dc176032cba5cb1f258082c31fa6df35cff46acfc/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6ed74185b2db44f41ef35fd1617c5888e59792da9bbc9190d6c7300617182616", size = 227145, upload-time = "2026-04-02T09:25:55.904Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/3a45bf8247889cf28262ebd3d0872edff11565b2a1e3064ccb132db3fbb0/charset_normalizer-3.4.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94e1885b270625a9a828c9793b4d52a64445299baa1fea5a173bf1d3dd9a1a5a", size = 218884, upload-time = "2026-04-02T09:25:57.074Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/2e8b7f8915ed5c9ef13aa828d82738e33888c485b65ebf744d615040c7ea/charset_normalizer-3.4.7-cp310-cp310-win32.whl", hash = "sha256:6785f414ae0f3c733c437e0f3929197934f526d19dfaa75e18fdb4f94c6fb374", size = 148343, upload-time = "2026-04-02T09:25:58.199Z" }, + { url = "https://files.pythonhosted.org/packages/35/1b/3b8c8c77184af465ee9ad88b5aea46ea6b2e1f7b9dc9502891e37af21e30/charset_normalizer-3.4.7-cp310-cp310-win_amd64.whl", hash = "sha256:6696b7688f54f5af4462118f0bfa7c1621eeb87154f77fa04b9295ce7a8f2943", size = 159174, upload-time = "2026-04-02T09:25:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/feb40dca40dbb21e0a908801782d9288c64fc8d8e562c2098e9994c8c21b/charset_normalizer-3.4.7-cp310-cp310-win_arm64.whl", hash = "sha256:66671f93accb62ed07da56613636f3641f1a12c13046ce91ffc923721f23c008", size = 147805, upload-time = "2026-04-02T09:26:00.756Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" }, + { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" }, + { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" }, + { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" }, + { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" }, + { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" }, + { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" }, + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "clr-loader" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/46/7eea92b6aa2d68af78e049cbecec5f757f1aad44ecdecdc16bbad7eead51/clr_loader-0.3.1.tar.gz", hash = "sha256:2e073e9aaf49d1ae2f56ecba27987ad5fb68be4bcd9dd34a5bed8f0e4e128366", size = 86805, upload-time = "2026-04-18T17:49:44.287Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/da/ec1a6e36624000b6df0dd61183c42342ee5814c073315e802cadaad04d2f/clr_loader-0.3.1-py3-none-any.whl", hash = "sha256:cbad189de20d202a7d621956b0fc38049e13c9bf7ca2923441eff725cd121aa1", size = 55730, upload-time = "2026-04-18T17:49:42.99Z" }, +] + +[[package]] +name = "cmaes" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/9f/ae4edb7dec820e84fef7a90b753ae5c72c66a05ffa69a7894771024386a7/cmaes-0.13.0.tar.gz", hash = "sha256:69a252b0291d08100351e37c2918c7c6d929b02ab7dcd9dd14fc02c7c98cc1b9", size = 61265, upload-time = "2026-03-28T07:41:55.249Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/98/be3f668f77838b2756ccc78a45e0c62f43d3134003f3f4bad814d37df1b3/cmaes-0.13.0-py3-none-any.whl", hash = "sha256:ccf61c73d5792cf44b50672b63f28c590082d1c1f5ab5155e2dd6e5305427cad", size = 73027, upload-time = "2026-03-28T07:41:53.956Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "colorlog" +version = "6.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/61/f083b5ac52e505dfc1c624eafbf8c7589a0d7f32daa398d2e7590efa5fda/colorlog-6.10.1.tar.gz", hash = "sha256:eb4ae5cb65fe7fec7773c2306061a8e63e02efc2c72eba9d27b0fa23c94f1321", size = 17162, upload-time = "2025-10-16T16:14:11.978Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/c1/e419ef3723a074172b68aaa89c9f3de486ed4c2399e2dbd8113a4fdcaf9e/colorlog-6.10.1-py3-none-any.whl", hash = "sha256:2d7e8348291948af66122cff006c9f8da6255d224e7cf8e37d8de2df3bad8c9c", size = 11743, upload-time = "2025-10-16T16:14:10.512Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/54/eb9bfc647b19f2009dd5c7f5ec51c4e6ca831725f1aea7a993034f483147/contourpy-1.3.2.tar.gz", hash = "sha256:b6945942715a034c671b7fc54f9588126b0b8bf23db2696e3ca8328f3ff0ab54", size = 13466130, upload-time = "2025-04-15T17:47:53.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/a3/da4153ec8fe25d263aa48c1a4cbde7f49b59af86f0b6f7862788c60da737/contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934", size = 268551, upload-time = "2025-04-15T17:34:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6c/330de89ae1087eb622bfca0177d32a7ece50c3ef07b28002de4757d9d875/contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989", size = 253399, upload-time = "2025-04-15T17:34:51.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/bd/20c6726b1b7f81a8bee5271bed5c165f0a8e1f572578a9d27e2ccb763cb2/contourpy-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9be002b31c558d1ddf1b9b415b162c603405414bacd6932d031c5b5a8b757f0d", size = 312061, upload-time = "2025-04-15T17:34:55.961Z" }, + { url = "https://files.pythonhosted.org/packages/22/fc/a9665c88f8a2473f823cf1ec601de9e5375050f1958cbb356cdf06ef1ab6/contourpy-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d2e74acbcba3bfdb6d9d8384cdc4f9260cae86ed9beee8bd5f54fee49a430b9", size = 351956, upload-time = "2025-04-15T17:35:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/25/eb/9f0a0238f305ad8fb7ef42481020d6e20cf15e46be99a1fcf939546a177e/contourpy-1.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e259bced5549ac64410162adc973c5e2fb77f04df4a439d00b478e57a0e65512", size = 320872, upload-time = "2025-04-15T17:35:06.177Z" }, + { url = "https://files.pythonhosted.org/packages/32/5c/1ee32d1c7956923202f00cf8d2a14a62ed7517bdc0ee1e55301227fc273c/contourpy-1.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad687a04bc802cbe8b9c399c07162a3c35e227e2daccf1668eb1f278cb698631", size = 325027, upload-time = "2025-04-15T17:35:11.244Z" }, + { url = "https://files.pythonhosted.org/packages/83/bf/9baed89785ba743ef329c2b07fd0611d12bfecbedbdd3eeecf929d8d3b52/contourpy-1.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cdd22595308f53ef2f891040ab2b93d79192513ffccbd7fe19be7aa773a5e09f", size = 1306641, upload-time = "2025-04-15T17:35:26.701Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cc/74e5e83d1e35de2d28bd97033426b450bc4fd96e092a1f7a63dc7369b55d/contourpy-1.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b4f54d6a2defe9f257327b0f243612dd051cc43825587520b1bf74a31e2f6ef2", size = 1374075, upload-time = "2025-04-15T17:35:43.204Z" }, + { url = "https://files.pythonhosted.org/packages/0c/42/17f3b798fd5e033b46a16f8d9fcb39f1aba051307f5ebf441bad1ecf78f8/contourpy-1.3.2-cp310-cp310-win32.whl", hash = "sha256:f939a054192ddc596e031e50bb13b657ce318cf13d264f095ce9db7dc6ae81c0", size = 177534, upload-time = "2025-04-15T17:35:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/54/ec/5162b8582f2c994721018d0c9ece9dc6ff769d298a8ac6b6a652c307e7df/contourpy-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c440093bbc8fc21c637c03bafcbef95ccd963bc6e0514ad887932c18ca2a759a", size = 221188, upload-time = "2025-04-15T17:35:50.064Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b9/ede788a0b56fc5b071639d06c33cb893f68b1178938f3425debebe2dab78/contourpy-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a37a2fb93d4df3fc4c0e363ea4d16f83195fc09c891bc8ce072b9d084853445", size = 269636, upload-time = "2025-04-15T17:35:54.473Z" }, + { url = "https://files.pythonhosted.org/packages/e6/75/3469f011d64b8bbfa04f709bfc23e1dd71be54d05b1b083be9f5b22750d1/contourpy-1.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b7cd50c38f500bbcc9b6a46643a40e0913673f869315d8e70de0438817cb7773", size = 254636, upload-time = "2025-04-15T17:35:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2f/95adb8dae08ce0ebca4fd8e7ad653159565d9739128b2d5977806656fcd2/contourpy-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6658ccc7251a4433eebd89ed2672c2ed96fba367fd25ca9512aa92a4b46c4f1", size = 313053, upload-time = "2025-04-15T17:36:03.235Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a6/8ccf97a50f31adfa36917707fe39c9a0cbc24b3bbb58185577f119736cc9/contourpy-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:70771a461aaeb335df14deb6c97439973d253ae70660ca085eec25241137ef43", size = 352985, upload-time = "2025-04-15T17:36:08.275Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b6/7925ab9b77386143f39d9c3243fdd101621b4532eb126743201160ffa7e6/contourpy-1.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65a887a6e8c4cd0897507d814b14c54a8c2e2aa4ac9f7686292f9769fcf9a6ab", size = 323750, upload-time = "2025-04-15T17:36:13.29Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/20c5d1ef4f4748e52d60771b8560cf00b69d5c6368b5c2e9311bcfa2a08b/contourpy-1.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3859783aefa2b8355697f16642695a5b9792e7a46ab86da1118a4a23a51a33d7", size = 326246, upload-time = "2025-04-15T17:36:18.329Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e5/9dae809e7e0b2d9d70c52b3d24cba134dd3dad979eb3e5e71f5df22ed1f5/contourpy-1.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eab0f6db315fa4d70f1d8ab514e527f0366ec021ff853d7ed6a2d33605cf4b83", size = 1308728, upload-time = "2025-04-15T17:36:33.878Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/0058ba34aeea35c0b442ae61a4f4d4ca84d6df8f91309bc2d43bb8dd248f/contourpy-1.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d91a3ccc7fea94ca0acab82ceb77f396d50a1f67412efe4c526f5d20264e6ecd", size = 1375762, upload-time = "2025-04-15T17:36:51.295Z" }, + { url = "https://files.pythonhosted.org/packages/09/33/7174bdfc8b7767ef2c08ed81244762d93d5c579336fc0b51ca57b33d1b80/contourpy-1.3.2-cp311-cp311-win32.whl", hash = "sha256:1c48188778d4d2f3d48e4643fb15d8608b1d01e4b4d6b0548d9b336c28fc9b6f", size = 178196, upload-time = "2025-04-15T17:36:55.002Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fe/4029038b4e1c4485cef18e480b0e2cd2d755448bb071eb9977caac80b77b/contourpy-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:5ebac872ba09cb8f2131c46b8739a7ff71de28a24c869bcad554477eb089a878", size = 222017, upload-time = "2025-04-15T17:36:58.576Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/44785876384eff370c251d58fd65f6ad7f39adce4a093c934d4a67a7c6b6/contourpy-1.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4caf2bcd2969402bf77edc4cb6034c7dd7c0803213b3523f111eb7460a51b8d2", size = 271580, upload-time = "2025-04-15T17:37:03.105Z" }, + { url = "https://files.pythonhosted.org/packages/93/3b/0004767622a9826ea3d95f0e9d98cd8729015768075d61f9fea8eeca42a8/contourpy-1.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:82199cb78276249796419fe36b7386bd8d2cc3f28b3bc19fe2454fe2e26c4c15", size = 255530, upload-time = "2025-04-15T17:37:07.026Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7bd49e1f4fa805772d9fd130e0d375554ebc771ed7172f48dfcd4ca61549/contourpy-1.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:106fab697af11456fcba3e352ad50effe493a90f893fca6c2ca5c033820cea92", size = 307688, upload-time = "2025-04-15T17:37:11.481Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e1d5dbbfa170725ef78357a9a0edc996b09ae4af170927ba8ce977e60a5f/contourpy-1.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d14f12932a8d620e307f715857107b1d1845cc44fdb5da2bc8e850f5ceba9f87", size = 347331, upload-time = "2025-04-15T17:37:18.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/66/e69e6e904f5ecf6901be3dd16e7e54d41b6ec6ae3405a535286d4418ffb4/contourpy-1.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:532fd26e715560721bb0d5fc7610fce279b3699b018600ab999d1be895b09415", size = 318963, upload-time = "2025-04-15T17:37:22.76Z" }, + { url = "https://files.pythonhosted.org/packages/a8/32/b8a1c8965e4f72482ff2d1ac2cd670ce0b542f203c8e1d34e7c3e6925da7/contourpy-1.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b383144cf2d2c29f01a1e8170f50dacf0eac02d64139dcd709a8ac4eb3cfe", size = 323681, upload-time = "2025-04-15T17:37:33.001Z" }, + { url = "https://files.pythonhosted.org/packages/30/c6/12a7e6811d08757c7162a541ca4c5c6a34c0f4e98ef2b338791093518e40/contourpy-1.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c49f73e61f1f774650a55d221803b101d966ca0c5a2d6d5e4320ec3997489441", size = 1308674, upload-time = "2025-04-15T17:37:48.64Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8a/bebe5a3f68b484d3a2b8ffaf84704b3e343ef1addea528132ef148e22b3b/contourpy-1.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3d80b2c0300583228ac98d0a927a1ba6a2ba6b8a742463c564f1d419ee5b211e", size = 1380480, upload-time = "2025-04-15T17:38:06.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/fcd325f19b5978fb509a7d55e06d99f5f856294c1991097534360b307cf1/contourpy-1.3.2-cp312-cp312-win32.whl", hash = "sha256:90df94c89a91b7362e1142cbee7568f86514412ab8a2c0d0fca72d7e91b62912", size = 178489, upload-time = "2025-04-15T17:38:10.338Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/fadd0b92ffa7b5eb5949bf340a63a4a496a6930a6c37a7ba0f12acb076d6/contourpy-1.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c942a01d9163e2e5cfb05cb66110121b8d07ad438a17f9e766317bcb62abf73", size = 223042, upload-time = "2025-04-15T17:38:14.239Z" }, + { url = "https://files.pythonhosted.org/packages/2e/61/5673f7e364b31e4e7ef6f61a4b5121c5f170f941895912f773d95270f3a2/contourpy-1.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:de39db2604ae755316cb5967728f4bea92685884b1e767b7c24e983ef5f771cb", size = 271630, upload-time = "2025-04-15T17:38:19.142Z" }, + { url = "https://files.pythonhosted.org/packages/ff/66/a40badddd1223822c95798c55292844b7e871e50f6bfd9f158cb25e0bd39/contourpy-1.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3f9e896f447c5c8618f1edb2bafa9a4030f22a575ec418ad70611450720b5b08", size = 255670, upload-time = "2025-04-15T17:38:23.688Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c7/cf9fdee8200805c9bc3b148f49cb9482a4e3ea2719e772602a425c9b09f8/contourpy-1.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71e2bd4a1c4188f5c2b8d274da78faab884b59df20df63c34f74aa1813c4427c", size = 306694, upload-time = "2025-04-15T17:38:28.238Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e7/ccb9bec80e1ba121efbffad7f38021021cda5be87532ec16fd96533bb2e0/contourpy-1.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de425af81b6cea33101ae95ece1f696af39446db9682a0b56daaa48cfc29f38f", size = 345986, upload-time = "2025-04-15T17:38:33.502Z" }, + { url = "https://files.pythonhosted.org/packages/dc/49/ca13bb2da90391fa4219fdb23b078d6065ada886658ac7818e5441448b78/contourpy-1.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:977e98a0e0480d3fe292246417239d2d45435904afd6d7332d8455981c408b85", size = 318060, upload-time = "2025-04-15T17:38:38.672Z" }, + { url = "https://files.pythonhosted.org/packages/c8/65/5245ce8c548a8422236c13ffcdcdada6a2a812c361e9e0c70548bb40b661/contourpy-1.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:434f0adf84911c924519d2b08fc10491dd282b20bdd3fa8f60fd816ea0b48841", size = 322747, upload-time = "2025-04-15T17:38:43.712Z" }, + { url = "https://files.pythonhosted.org/packages/72/30/669b8eb48e0a01c660ead3752a25b44fdb2e5ebc13a55782f639170772f9/contourpy-1.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c66c4906cdbc50e9cba65978823e6e00b45682eb09adbb78c9775b74eb222422", size = 1308895, upload-time = "2025-04-15T17:39:00.224Z" }, + { url = "https://files.pythonhosted.org/packages/05/5a/b569f4250decee6e8d54498be7bdf29021a4c256e77fe8138c8319ef8eb3/contourpy-1.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8b7fc0cd78ba2f4695fd0a6ad81a19e7e3ab825c31b577f384aa9d7817dc3bef", size = 1379098, upload-time = "2025-04-15T17:43:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/19/ba/b227c3886d120e60e41b28740ac3617b2f2b971b9f601c835661194579f1/contourpy-1.3.2-cp313-cp313-win32.whl", hash = "sha256:15ce6ab60957ca74cff444fe66d9045c1fd3e92c8936894ebd1f3eef2fff075f", size = 178535, upload-time = "2025-04-15T17:44:44.532Z" }, + { url = "https://files.pythonhosted.org/packages/12/6e/2fed56cd47ca739b43e892707ae9a13790a486a3173be063681ca67d2262/contourpy-1.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1578f7eafce927b168752ed7e22646dad6cd9bca673c60bff55889fa236ebf9", size = 223096, upload-time = "2025-04-15T17:44:48.194Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/e76fe2a03014a7c767d79ea35c86a747e9325537a8b7627e0e5b3ba266b4/contourpy-1.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0475b1f6604896bc7c53bb070e355e9321e1bc0d381735421a2d2068ec56531f", size = 285090, upload-time = "2025-04-15T17:43:34.084Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e2/5aba47debd55d668e00baf9651b721e7733975dc9fc27264a62b0dd26eb8/contourpy-1.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c85bb486e9be652314bb5b9e2e3b0d1b2e643d5eec4992c0fbe8ac71775da739", size = 268643, upload-time = "2025-04-15T17:43:38.626Z" }, + { url = "https://files.pythonhosted.org/packages/a1/37/cd45f1f051fe6230f751cc5cdd2728bb3a203f5619510ef11e732109593c/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:745b57db7758f3ffc05a10254edd3182a2a83402a89c00957a8e8a22f5582823", size = 310443, upload-time = "2025-04-15T17:43:44.522Z" }, + { url = "https://files.pythonhosted.org/packages/8b/a2/36ea6140c306c9ff6dd38e3bcec80b3b018474ef4d17eb68ceecd26675f4/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:970e9173dbd7eba9b4e01aab19215a48ee5dd3f43cef736eebde064a171f89a5", size = 349865, upload-time = "2025-04-15T17:43:49.545Z" }, + { url = "https://files.pythonhosted.org/packages/95/b7/2fc76bc539693180488f7b6cc518da7acbbb9e3b931fd9280504128bf956/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6c4639a9c22230276b7bffb6a850dfc8258a2521305e1faefe804d006b2e532", size = 321162, upload-time = "2025-04-15T17:43:54.203Z" }, + { url = "https://files.pythonhosted.org/packages/f4/10/76d4f778458b0aa83f96e59d65ece72a060bacb20cfbee46cf6cd5ceba41/contourpy-1.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc829960f34ba36aad4302e78eabf3ef16a3a100863f0d4eeddf30e8a485a03b", size = 327355, upload-time = "2025-04-15T17:44:01.025Z" }, + { url = "https://files.pythonhosted.org/packages/43/a3/10cf483ea683f9f8ab096c24bad3cce20e0d1dd9a4baa0e2093c1c962d9d/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d32530b534e986374fc19eaa77fcb87e8a99e5431499949b828312bdcd20ac52", size = 1307935, upload-time = "2025-04-15T17:44:17.322Z" }, + { url = "https://files.pythonhosted.org/packages/78/73/69dd9a024444489e22d86108e7b913f3528f56cfc312b5c5727a44188471/contourpy-1.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e298e7e70cf4eb179cc1077be1c725b5fd131ebc81181bf0c03525c8abc297fd", size = 1372168, upload-time = "2025-04-15T17:44:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/0f/1b/96d586ccf1b1a9d2004dd519b25fbf104a11589abfd05484ff12199cca21/contourpy-1.3.2-cp313-cp313t-win32.whl", hash = "sha256:d0e589ae0d55204991450bb5c23f571c64fe43adaa53f93fc902a84c96f52fe1", size = 189550, upload-time = "2025-04-15T17:44:37.092Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e6/6000d0094e8a5e32ad62591c8609e269febb6e4db83a1c75ff8868b42731/contourpy-1.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:78e9253c3de756b3f6a5174d024c4835acd59eb3f8e2ca13e775dbffe1558f69", size = 238214, upload-time = "2025-04-15T17:44:40.827Z" }, + { url = "https://files.pythonhosted.org/packages/33/05/b26e3c6ecc05f349ee0013f0bb850a761016d89cec528a98193a48c34033/contourpy-1.3.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fd93cc7f3139b6dd7aab2f26a90dde0aa9fc264dbf70f6740d498a70b860b82c", size = 265681, upload-time = "2025-04-15T17:44:59.314Z" }, + { url = "https://files.pythonhosted.org/packages/2b/25/ac07d6ad12affa7d1ffed11b77417d0a6308170f44ff20fa1d5aa6333f03/contourpy-1.3.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:107ba8a6a7eec58bb475329e6d3b95deba9440667c4d62b9b6063942b61d7f16", size = 315101, upload-time = "2025-04-15T17:45:04.165Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4d/5bb3192bbe9d3f27e3061a6a8e7733c9120e203cb8515767d30973f71030/contourpy-1.3.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ded1706ed0c1049224531b81128efbd5084598f18d8a2d9efae833edbd2b40ad", size = 220599, upload-time = "2025-04-15T17:45:08.456Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c0/91f1215d0d9f9f343e4773ba6c9b89e8c0cc7a64a6263f21139da639d848/contourpy-1.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5f5964cdad279256c084b69c3f412b7801e15356b16efa9d78aa974041903da0", size = 266807, upload-time = "2025-04-15T17:45:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/6be7e90c955c0487e7712660d6cead01fa17bff98e0ea275737cc2bc8e71/contourpy-1.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49b65a95d642d4efa8f64ba12558fcb83407e58a2dfba9d796d77b63ccfcaff5", size = 318729, upload-time = "2025-04-15T17:45:20.166Z" }, + { url = "https://files.pythonhosted.org/packages/87/68/7f46fb537958e87427d98a4074bcde4b67a70b04900cfc5ce29bc2f556c1/contourpy-1.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8c5acb8dddb0752bf252e01a3035b21443158910ac16a3b0d20e7fed7d534ce5", size = 221791, upload-time = "2025-04-15T17:45:24.794Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "controlnet-aux" +version = "0.0.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "einops" }, + { name = "filelock" }, + { name = "huggingface-hub" }, + { name = "importlib-metadata" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "opencv-python-headless" }, + { name = "pillow" }, + { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-image", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "timm" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.12.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/ad/2eb8cd9a8e17e35b9e5d39ad29afdde8fe810bda85e6e59117050519955d/controlnet_aux-0.0.10.tar.gz", hash = "sha256:31dc265a54448bdcee033a130b47423c80587fa35ccac752113af1b4d48f5183", size = 215016, upload-time = "2025-05-08T10:38:30.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/3b/e1608b5bca98bcdaa2c74286fcc0457f3c0a8c99e25e3e4065c184f92c7f/controlnet_aux-0.0.10-py3-none-any.whl", hash = "sha256:cad3480d62c7df1ae569258a659c88d730873323d2adbb9f6f7aebd87917df3b", size = 290398, upload-time = "2025-05-08T10:38:28.952Z" }, +] + +[[package]] +name = "coverage" +version = "7.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/97/c52dc440c390b6cfa87be9432b141a956e2d56d9b9f5fc8bd71c5f471722/coverage-7.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50913d4bf5ddafa6ca3693da5e4dd833dd1b772e0283c99ca7f7d287db67331a", size = 220539, upload-time = "2026-07-02T13:08:19.252Z" }, + { url = "https://files.pythonhosted.org/packages/3f/26/602de8c2aec7e2e3e99ebfb8e04ba65598f746275396eea5f6794ff4673f/coverage-7.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:359e141ccd33893ce3f1ad5525f8b96083003677c82182e5907d62d4ea5799fc", size = 221058, upload-time = "2026-07-02T13:08:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/fc/13/ebab0743138891c1d646d61e247ec29639afcbb6c4e1905e6a0f0c75291a/coverage-7.15.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3200b6204935f928c64b2ca1f923ab8c1acb7c9de45ec61569711b34d25cccaf", size = 247797, upload-time = "2026-07-02T13:08:22.474Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/b6ffb9e042aa48dc4144a8a65529affaec8dca0685309353614a2a7386ad/coverage-7.15.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:be616bf61346883b2cfdc5178669647e03531d81ab761a7e378558b7e8bcb628", size = 249626, upload-time = "2026-07-02T13:08:23.803Z" }, + { url = "https://files.pythonhosted.org/packages/9c/06/243ff05b652333d8e3d060c11223efc2723b19cacf6605e433fa686ab5d4/coverage-7.15.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc7bafc3fe1059463a8fdd97ca79972d6e2bf819d775c7d54991b5b1971201d6", size = 251493, upload-time = "2026-07-02T13:08:25.397Z" }, + { url = "https://files.pythonhosted.org/packages/d3/2b/867faa17030a806114dae388b32a3fa929d8cd4bf39226fbc11f6e6bb705/coverage-7.15.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b713aa7fcf325a01d4184d848acb46fd84f78fdb0978470c636b23a06a753d91", size = 253406, upload-time = "2026-07-02T13:08:26.842Z" }, + { url = "https://files.pythonhosted.org/packages/94/c0/d789ce18f6605afc4895db75723424be2ef494282f77f61d8e5832923183/coverage-7.15.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e38e6fba2d56652fdfaf0231f8f78aeb805234a912de25dc291ee5cce5b8faa4", size = 248512, upload-time = "2026-07-02T13:08:28.398Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/b2673c30739f4a2e06649a0a38ad8b093c4d865462dc7bab0e9524a2c3b1/coverage-7.15.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:884499f42e382675be80770391983b90e0c0c774d87dbeeebf5f991cf6612b20", size = 249532, upload-time = "2026-07-02T13:08:29.731Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/acd79e9a41beabee92b623afe4f30b549916f48566271475f2907e752828/coverage-7.15.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:840481b12e083dbcbafab14794a8781a958edf327c8d3d70b4eee42f9b8253aa", size = 247537, upload-time = "2026-07-02T13:08:31.173Z" }, + { url = "https://files.pythonhosted.org/packages/12/d4/2d301c4d1b3238d7c88b70ab9d13fd53ed9505662a7ff1b46ba1e2e4e3c3/coverage-7.15.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:276646e9481703d09f854f3b2f018f24e19fd7049ae670a92570043eb97203b1", size = 251348, upload-time = "2026-07-02T13:08:32.63Z" }, + { url = "https://files.pythonhosted.org/packages/35/bb/c67708b2bc00f32e12805ec23d5fa677a0a51652f449341a89f9d6b1b715/coverage-7.15.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4de4b4d3f5545aa6c60dc4efd9c63b5b5dcc3bf00fe83146b2bdfffb8f6613bd", size = 247806, upload-time = "2026-07-02T13:08:33.931Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6c/57c4f653c47a6e917748f8938e389e72fbcae44e3643cd906664f0477a13/coverage-7.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5c504097b2a89b1e85bc6070d920df77daec701337e3aeef2c17775a5dd0ca90", size = 248410, upload-time = "2026-07-02T13:08:35.189Z" }, + { url = "https://files.pythonhosted.org/packages/6c/94/bb083041aef828903668f134273f319f2bd49224962875359c52faa5497f/coverage-7.15.0-cp310-cp310-win32.whl", hash = "sha256:f6e80ed91f98316e86b9c137206b04b2bcfbffccbdff49bd2eb09dddb1cf14e0", size = 222588, upload-time = "2026-07-02T13:08:36.486Z" }, + { url = "https://files.pythonhosted.org/packages/ef/94/a09d8ee618956f626741b0734854bac4425a00e10c0565f5abca64e7e751/coverage-7.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:b3b3e22030f3f6f5e01a5ce69936552a5c0f6992b7698777377b99041961031f", size = 223214, upload-time = "2026-07-02T13:08:37.885Z" }, + { url = "https://files.pythonhosted.org/packages/ae/23/82e910835ef4b8391047025e1d53aa48d66029f444eb8b25373c849bf503/coverage-7.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:003fff99412ea848c0aaebcc78ed2b6ce7d8a1227ed17e68470672770b78a02a", size = 220662, upload-time = "2026-07-02T13:08:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/6d/0d/c7b213dde2f1579de5231062b386d8413f79c11667eb58c39319b25991da/coverage-7.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5cbd804bf2784ce7b45114516050f346ecd50f960c4bb630a7ee9e1d78fa2118", size = 221168, upload-time = "2026-07-02T13:08:40.471Z" }, + { url = "https://files.pythonhosted.org/packages/33/77/d000aeedfac085088337b3c7becdad328474b1f8a9e4c9368a0c99605d68/coverage-7.15.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8773e15c23305b58882a4611fb9b2755977eae0dc2e515366a1b6c98866cc4c2", size = 251587, upload-time = "2026-07-02T13:08:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e0/86787c56b9df17afd370d5e293515dd4d9a107a561d13054873eefad8ecc/coverage-7.15.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f50e40081494c1dc4239ebb202014cbcc3306ea96fb6302a34c8cc0967fc5ae8", size = 253497, upload-time = "2026-07-02T13:08:43.387Z" }, + { url = "https://files.pythonhosted.org/packages/3f/02/181bc917359299c07dead6270f94e411151c8b60cec905c33499da69afe6/coverage-7.15.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daf96f37f5fc3a7b6c6da862eb4aee61c426bd63da236ed4a73ef0e503b4bca5", size = 255607, upload-time = "2026-07-02T13:08:44.897Z" }, + { url = "https://files.pythonhosted.org/packages/b9/35/ca5e7427699913da6788c4f910e73ab16c5f4b59ec5d3a999dce2a45112f/coverage-7.15.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51aa20f6ae2788fd197747766edf4cd8234fd9423309b934257fa6b21a592723", size = 257563, upload-time = "2026-07-02T13:08:46.334Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4d/b8220bacc2fc3c4e9078e27c32e99fb411479a4718a72bdd00036a9891c8/coverage-7.15.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03d1f922757662eb7af586e77834792274cff776bc7b1d1a0b66a49ea9d84735", size = 251726, upload-time = "2026-07-02T13:08:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/c4/e4/2e145da1991d72189b9c3cf7eca05c716ee7080d099aaea6757cfc7df008/coverage-7.15.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a6d6acc9a7666245e6133dd15144ca038a85a9cd5026bb06d6bbae9e77440dc9", size = 253301, upload-time = "2026-07-02T13:08:49.5Z" }, + { url = "https://files.pythonhosted.org/packages/72/28/d2c841d698bf762e481f08bd4839d370246b6d9b61dab085a7b20b201a08/coverage-7.15.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1ac2c4c27c7df851dc9a017c2d7de00b69147e84ba3d96f37a530b0b6fb51035", size = 251361, upload-time = "2026-07-02T13:08:51.304Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ed/55d9ffde994fba3897c0c783f77a7d053b0c18787f6892ed5b0aed73f469/coverage-7.15.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b761a1d504fd4bd1f20f418753964dca9f5862a511fc854dac58296b3b223671", size = 255129, upload-time = "2026-07-02T13:08:52.661Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c0/ecbf33b8c460ea2718aeb813e2df8140d0370e5f67261c31524ceb0a2a8d/coverage-7.15.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e43b045e11c16e897895758ae90e4a90cf99e93d58549e2f90c0e2272e155695", size = 251081, upload-time = "2026-07-02T13:08:54.188Z" }, + { url = "https://files.pythonhosted.org/packages/a9/de/fb87b4261f54448dd2b9504ef19a58be42cef0d9520595fbfe1219b15234/coverage-7.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:589b54513e901739f4b4582c705ce96b80c96f57641b1464607e2367a270e540", size = 251988, upload-time = "2026-07-02T13:08:55.726Z" }, + { url = "https://files.pythonhosted.org/packages/df/27/3494d5f291b9a4cb868f73c11221a8bd2d5bd761a8f9acea61ff57128dd1/coverage-7.15.0-cp311-cp311-win32.whl", hash = "sha256:106781b8482749162d0b47056937ba0933508e5d9447f65a5e7d5c422f0d6bb4", size = 222754, upload-time = "2026-07-02T13:08:57.091Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ee/cd4847ebc9be6a9c0123d763645a6f1f3be6b8c58c962706368b79cbac07/coverage-7.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:821e92b3631d762a339695824cadbbc73020354eba2a23a551a99ad34938fbe6", size = 223225, upload-time = "2026-07-02T13:08:58.594Z" }, + { url = "https://files.pythonhosted.org/packages/57/37/5011581aa7f2be498b97dcc7c9902192442a42f4f9a748aeadb3d6506b42/coverage-7.15.0-cp311-cp311-win_arm64.whl", hash = "sha256:309990eb5fb8014b9f67cb211f7fd41876ec8a88a88d3ae76de0ed1d611e3640", size = 222774, upload-time = "2026-07-02T13:09:00.074Z" }, + { url = "https://files.pythonhosted.org/packages/2a/74/fd4c0901137c4f8d81a76ada99e43c65163b4c94a02ece107a4ec0c6b615/coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d", size = 220838, upload-time = "2026-07-02T13:09:02.084Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2e/2347583467bd7f0402635101a916961915cc68fce652cd0db5f173ea04fc/coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe", size = 221197, upload-time = "2026-07-02T13:09:03.617Z" }, + { url = "https://files.pythonhosted.org/packages/f0/17/99fa688541ae1d6e84543a0e544f83de0c944815b63e9e7b1ed411d15036/coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c", size = 252705, upload-time = "2026-07-02T13:09:05.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/6a95a5cd83b74839017ef9cf48d2d8c9ae60af919e17a3f336e6f9f1b7bd/coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4", size = 255441, upload-time = "2026-07-02T13:09:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/67/f2/406f6c57d600f68185942422c4c00f1a3255d60aee6e5fd961425cd9987e/coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5", size = 256556, upload-time = "2026-07-02T13:09:08.197Z" }, + { url = "https://files.pythonhosted.org/packages/74/8e/d3fa48489c15ecdec1ba48fd61f68798555dddd2f6716f9ad42adeb1a2a9/coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01", size = 258815, upload-time = "2026-07-02T13:09:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/2d40ddd110462c6a2769677cf7f1c119a52b45f568978fc6c98e4cc0dd0f/coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4", size = 253117, upload-time = "2026-07-02T13:09:11.212Z" }, + { url = "https://files.pythonhosted.org/packages/51/c0/310782f0d7c3cb2b5ac05ba8d205fe91f24a36f6bf3256098f1782181c38/coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796", size = 254475, upload-time = "2026-07-02T13:09:13.029Z" }, + { url = "https://files.pythonhosted.org/packages/86/f7/702da6c275f8ae6ade423d2877243122932c9b27f5403003b9ef8c927d12/coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382", size = 252619, upload-time = "2026-07-02T13:09:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/fb/84/c5b15a7e5ecba4e56218d772d99fe80a63e63f8d11f12783723a6005ab45/coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c", size = 256689, upload-time = "2026-07-02T13:09:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/95/2f/c8b07559b57701230c61b23a953858c052890c12ef568d81780c6c46e92e/coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766", size = 252189, upload-time = "2026-07-02T13:09:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/6b/80/6d2f049dd3fd3dbfd60b62ba6b2162a04009e2c002ce70b24cf3878dec7a/coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da", size = 254059, upload-time = "2026-07-02T13:09:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/ce/92/b0287a2c42031d25c628f815f89a3cd9f8268ee78bb1252c9356cda1c689/coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77", size = 222893, upload-time = "2026-07-02T13:09:20.812Z" }, + { url = "https://files.pythonhosted.org/packages/a9/69/e34c481915fecb499b3146975061dac528752e37706edc1804f32c822469/coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6", size = 223429, upload-time = "2026-07-02T13:09:22.315Z" }, + { url = "https://files.pythonhosted.org/packages/fe/98/6e878f0b571d32684ef3f38d7c03db241ca5b82a5da8a5391596a8f209c4/coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b", size = 222810, upload-time = "2026-07-02T13:09:23.812Z" }, + { url = "https://files.pythonhosted.org/packages/76/04/145a3748098bcc86b631a85408d2c3dc5c104e0bd86d605468239b25b6c4/coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782", size = 220863, upload-time = "2026-07-02T13:09:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/4ed55708fed2c64b63c9bc5715daef670872202101938869b7fe5d5fbb8f/coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430", size = 221230, upload-time = "2026-07-02T13:09:26.897Z" }, + { url = "https://files.pythonhosted.org/packages/7b/19/3a80b97d3b2a5c77a01ae359c6bed20c13738fe3d9380f08616d4fec0281/coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5", size = 252227, upload-time = "2026-07-02T13:09:28.543Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/b70062750686bd7da454da27927622f48bbac6990ac7a4c4a4653e7b0036/coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970", size = 254823, upload-time = "2026-07-02T13:09:30.177Z" }, + { url = "https://files.pythonhosted.org/packages/a9/09/dad6a75a2e561b9dc5086a8c5257a7591d584246f67e23e70d2995b89ab6/coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c", size = 256059, upload-time = "2026-07-02T13:09:31.979Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e7/b5d2941fa9564573d44b693a871ff3156f0c42cbefe977a09fa7fdc59971/coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84", size = 258190, upload-time = "2026-07-02T13:09:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1d/8e895bcde3c57ccd46d896dda5f2b3d5df761a1b0c6c9d450d175dedc632/coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389", size = 252456, upload-time = "2026-07-02T13:09:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/14/4c/f6997da343ddeb959be82c3b05322793f92c071ad45f7cb8a96336e2dd5f/coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950", size = 254192, upload-time = "2026-07-02T13:09:37.445Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/a0bc09d032267b9da89d95a2d874cfbef2a5aebbf0e87cf7aba221d79a99/coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e", size = 252153, upload-time = "2026-07-02T13:09:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/54/c0/77fc233d9fba07b244c40948c53fe27308b8f21732fb3417f87fbd6fd992/coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e", size = 256310, upload-time = "2026-07-02T13:09:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/d5/24/601cecfb5825becacb8d45219a018a3b55b9dbaec624efdb0ea249d08be2/coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837", size = 251974, upload-time = "2026-07-02T13:09:42.733Z" }, + { url = "https://files.pythonhosted.org/packages/47/1e/6f45e5a5b3d5484318d368702af6716b5ab8913b0428bec981a562fcf296/coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37", size = 253745, upload-time = "2026-07-02T13:09:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/4df027a77bd11d0e527f44c53557c76e54ad027413d0304252ea3a78d67e/coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b", size = 222902, upload-time = "2026-07-02T13:09:46.122Z" }, + { url = "https://files.pythonhosted.org/packages/a0/10/0355894d34e231f2c5449e71287e81a50793a325df2e2b027b7bcd9dfd19/coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629", size = 223444, upload-time = "2026-07-02T13:09:47.687Z" }, + { url = "https://files.pythonhosted.org/packages/06/ef/bb725f263befaaff851203ab338e68af15e195d7f7b5f323162532d9b6a8/coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21", size = 222839, upload-time = "2026-07-02T13:09:49.717Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9c/1e3ca54f72a3185ece06c58d871099898c48f0ed6430d17b6ab75f0d180a/coverage-7.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:41cb79af843222e11da87127ad0ecbfa878abadd0f770a4a99391a27d3887324", size = 220906, upload-time = "2026-07-02T13:09:51.339Z" }, + { url = "https://files.pythonhosted.org/packages/09/37/f718613d83b274880382f6b67e78f3802549ae39b0b3e65ae5b5974df56e/coverage-7.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d2008989ef8fe54188d3f3bfa2e3099b025af11e90a6a1b9e7dc433d04263d8", size = 221239, upload-time = "2026-07-02T13:09:53.138Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/22bae91e0b75445f68d365c7643ed0aa4880bbf77450ee74ca65bdae53a7/coverage-7.15.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:769e8ece11a596315ebf5aa7ec383aeeed016c091d2bf6363ffb996d41529092", size = 252286, upload-time = "2026-07-02T13:09:54.996Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/bec5e32aa508615d9d7a2790effb25fb4dc28606e995816afe400b25ece3/coverage-7.15.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:65a6b6164ee5c39e2f3803f314292d6c61a607ba7fee253d1e03c42dc3903502", size = 254789, upload-time = "2026-07-02T13:09:56.678Z" }, + { url = "https://files.pythonhosted.org/packages/17/29/0e865435b4354e4a7c03b1b7920046d31d0a273d55decefea27e011cb9bf/coverage-7.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75128817f95a5c45bb01d65fd2d8b9cb54bbe03d81608fb70e3e14b437ad56c2", size = 256135, upload-time = "2026-07-02T13:09:58.343Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/33a870b58a13325d62fc0a6c8f01fa0ff667cef60c7498e2382a147dfa18/coverage-7.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9887bb428fe2d4cd4bee89bac1a6c9932f484afd5b36fbd4ff6ea5f825bb1f5e", size = 258449, upload-time = "2026-07-02T13:10:00.057Z" }, + { url = "https://files.pythonhosted.org/packages/18/7b/6fffe596bf3ddba8462758d02c5dad730fd91055a6634aa2e4226229181a/coverage-7.15.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0bfc0be1f702042207a93a00523b1065ee1fe951e96edf311581c0bbc2e34888", size = 252313, upload-time = "2026-07-02T13:10:01.946Z" }, + { url = "https://files.pythonhosted.org/packages/58/1b/11468dd6c1676ab831a70cb9a8d4e198e8607fa0b7220ab918b73fe9bfbd/coverage-7.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f64627d55def5a43282d70e08396672692f77e4da610a5bb8bb4060b432b6859", size = 254142, upload-time = "2026-07-02T13:10:04.065Z" }, + { url = "https://files.pythonhosted.org/packages/79/41/29328e21d16b1b95092c30dd700e08cf915bd3734f836df8f3bdb0e8fa9f/coverage-7.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2c6f0fa473003905c6d5bac328ee4eba9fbea654f15bc24b8a3274b23363fa99", size = 252108, upload-time = "2026-07-02T13:10:06.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/05ccfb990439655b35afbfd8e0d13fe66677565a7d4eb38c3f5ef2635e1c/coverage-7.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2bcf9afaf064172c6ec3c58a325a9957ad1178c05dd934e25f253321776e0676", size = 256385, upload-time = "2026-07-02T13:10:08.141Z" }, + { url = "https://files.pythonhosted.org/packages/51/0e/486828a3d2695ea7a2609f17ff572f6b01905e608379440a11da4b8dffbe/coverage-7.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:baf06bc987115d6fb938d403f7eab684a057766c490367999a2b71a6883110c6", size = 251923, upload-time = "2026-07-02T13:10:10.179Z" }, + { url = "https://files.pythonhosted.org/packages/18/c7/03582b6715f078e5e558354c87616d945b9894cda2dace8e4009b17035e4/coverage-7.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0405f2ff97b1c4c0e782cb32e02f32369bcf2e6b618b591d67e1ea754575dfe", size = 253580, upload-time = "2026-07-02T13:10:12.052Z" }, + { url = "https://files.pythonhosted.org/packages/db/dc/9e578bbaf2ecb4959a81b7e7601ad8cca772cba2892e8d144cb749b4a71a/coverage-7.15.0-cp314-cp314-win32.whl", hash = "sha256:ab282853ed5fbd64bbb162f19cb8fcb7087187508a6374b4f9c34ec1577c4e8f", size = 223107, upload-time = "2026-07-02T13:10:13.994Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3e/c8c3b75d8dbe0e35f7b0cc3ff5e949fc59500f70b21d0398813f66740664/coverage-7.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bb3040e9f4bbe26fcb0cd7cc85ac63e630d3f3a9c74f027abf4caa27e706663", size = 223597, upload-time = "2026-07-02T13:10:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/cd/bc/3cbc9fb036eb388519bccd521f783499c39b64256013fbc362782f196fe1/coverage-7.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:346771144d34f7fa84ec28386f78e0f31653f33cf35e19d253d5b35f9e8201da", size = 223020, upload-time = "2026-07-02T13:10:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/28/00/199c4a8d656dff63102577a056c0fce2ff6a79e40adac092fc986c49cbf1/coverage-7.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d34a010905fb6401324ba016b5da03d574967f7b21ce48ea41e66f0f1f95f641", size = 221638, upload-time = "2026-07-02T13:10:19.703Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8e/9d0092c96a3d3a26951ecc7020826aa57bcb1b119ca81acbba996884ab13/coverage-7.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bb25d825d885ca8036795dacfc3924d33091fc76d71ebc99420c6b79e77d96fa", size = 221903, upload-time = "2026-07-02T13:10:21.514Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b4/c0ca3028f42c9a08e51feb4561ef1192e5de99797cd1db5b04590c215bda/coverage-7.15.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94c9686bfe8a9a6810297aecbd99beaa3445f9e8dc2f80b1382cca0d86b64461", size = 263267, upload-time = "2026-07-02T13:10:23.261Z" }, + { url = "https://files.pythonhosted.org/packages/5f/aa/a375e3846e5d3c013dc600b2a3231089055c73d77f5393dd2192a8d64da6/coverage-7.15.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bd671c25f9d85f09d7ec481d0e43d5139f486c06a37139847a7ce569788af72", size = 265390, upload-time = "2026-07-02T13:10:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/92/e1/5783cdabb797305e1c9e4809fea496d31834c51fa772514f73dc148bcfc9/coverage-7.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110cbdf8d2e216577312cf06ccf85539c0e5a5420ef747e4a4719b5e483c88cd", size = 267811, upload-time = "2026-07-02T13:10:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/85/31/96d8bbf58b8e9193bc8389574a91a0db48355ee98feb66aa6bf8d1b32eea/coverage-7.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c5d4619214f1d9993e7b00a8600d14614b7e9d84e89507460b126aa5e6559e5", size = 268928, upload-time = "2026-07-02T13:10:29.242Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7a/5294567e811a1cb7eda93140c628fa050d66189da28da320f93d1d815c73/coverage-7.15.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:781a704516e2d8346fbbd5be6c6f3412dd824785146528b3a01816f26c081007", size = 262378, upload-time = "2026-07-02T13:10:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/69/3f/3f48538421f899f28946f90a3d272136a4686e1abf461cc9249a783ee0f3/coverage-7.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd4a1b44bcb65ee29e947ac92bbee04956df3a6bfc6143641bb6cae7ede00fc9", size = 265263, upload-time = "2026-07-02T13:10:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d3/092df15efcab8a9c1467ee960eb8019bbad3f9300d115d89ea6195f369ff/coverage-7.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0e4950c9d6d3e39c64c991814ff315e2d0b9cb8152363594212c9e55208c0a8f", size = 262866, upload-time = "2026-07-02T13:10:35.104Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ab/0254d2b88665efb2c57ad368cc77ab5de3435bd8d5add4729c1b0e79431e/coverage-7.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:fe9c87ff42e5472d80d21704972e1f96e104a0a599d77c5e35db5a3c562e2571", size = 266599, upload-time = "2026-07-02T13:10:37.05Z" }, + { url = "https://files.pythonhosted.org/packages/a8/79/1cfa4023e489ce6fbc7be4a5d442dbc375edb4f4fda39a352cedb53263c2/coverage-7.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f00d5ae1dd2fe13fb8186e3e7d37bcbd8b25c0d764ff7d1b32cef9be058510a8", size = 261714, upload-time = "2026-07-02T13:10:38.966Z" }, + { url = "https://files.pythonhosted.org/packages/b7/eb/fee5c8665656be63f497418d410484637c438172568688e8ac92e06574e7/coverage-7.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:363ab38cc78b615f11c9cac3cf1d7eef950c18b9fdedfb9066f59461dcf84d68", size = 264025, upload-time = "2026-07-02T13:10:40.789Z" }, + { url = "https://files.pythonhosted.org/packages/ab/99/63005db722f91edc81abc16302f9cc2f6228c1679e46e15be9ae144b14d0/coverage-7.15.0-cp314-cp314t-win32.whl", hash = "sha256:54fd9c53a5fafff509195f1b6a3f9be615d8e8362a3629ff1de23d270c03c86b", size = 223413, upload-time = "2026-07-02T13:10:42.597Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e8/2bc6181c4fb06f1a6b981eb85330cc57bfad7e3f710fc9c9d350013ba228/coverage-7.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:87b47553097ba185ed964866078e7e63adea9f5f51b5f39691c34f30afd21080", size = 224245, upload-time = "2026-07-02T13:10:44.47Z" }, + { url = "https://files.pythonhosted.org/packages/79/b8/4d959bf9cc45d0cfed2f4d35cafcab978cdb6ea02eb5100009cd740632a3/coverage-7.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeefb2dd178fe7eee79f0ad25d75855cb35ee9ed472db2c5ea06f5b4fd00cec5", size = 223558, upload-time = "2026-07-02T13:10:46.368Z" }, + { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] + +[[package]] +name = "cuda-bindings" +version = "12.9.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "cuda-pathfinder", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/2e/b1b14be5884519917f9df4d9106d3e16575c19fa847d13a3f6e9d272b5cd/cuda_bindings-12.9.7-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a318075ef3277ca2fdd7df5d4bf671388696b4f2b65e2c4483f1853517692e3b", size = 7127291, upload-time = "2026-05-27T18:44:02.409Z" }, + { url = "https://files.pythonhosted.org/packages/82/1f/0809c53f694693d703c9efee0379875089db17ab50196845e08f6c686fd4/cuda_bindings-12.9.7-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f95debd2c54c5f087462668511bcb2b1295baa389cd6de9e768212f3cab2bbe2", size = 7657751, upload-time = "2026-05-27T18:44:04.365Z" }, + { url = "https://files.pythonhosted.org/packages/19/26/fdd044c00e8f20f783bcb8b9cce0144fbefacf6ed23a64318dc1c4d8db99/cuda_bindings-12.9.7-cp310-cp310-win_amd64.whl", hash = "sha256:62e245bfa4830d473b038d0d7e5ab9cc1b377a09d2bc9afceaf5c5bab96ab1c6", size = 7190277, upload-time = "2026-05-27T18:44:06.044Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/f9d1095f90d2a4df24cfcafe7487fd9444c6dacb94e3722be6fedd8ac26c/cuda_bindings-12.9.7-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16043ef5b15ab88fe9954c5c2061b1d8007591b27f2c916331056de0ebc6187e", size = 7114834, upload-time = "2026-05-27T18:44:07.746Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8a/1251e1794b69865aacd5629936006b18ea0816a495de4ecea9a825556eb3/cuda_bindings-12.9.7-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6496a88d84b1209d6651b0370c19c26319e157c22f6d018bf9a358cd8049041", size = 7647147, upload-time = "2026-05-27T18:44:09.4Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/158392f6572e6e0def70ca39029c46b75e02ea4a43c63ff7320b3d180a29/cuda_bindings-12.9.7-cp311-cp311-win_amd64.whl", hash = "sha256:c392ffa5010ef4073bfd9dfff4d1ae56032094ed52d3d732014f8e41a73e6b59", size = 7218081, upload-time = "2026-05-27T18:44:11.104Z" }, + { url = "https://files.pythonhosted.org/packages/32/45/557d4ed1fa54f0c7db8aee083229f624990d69f7d00f55477eed5c7e169a/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0666d3c082ef8f4b2d670950589373550e9f3bf564d635dd883f24a0b40402ff", size = 7071026, upload-time = "2026-05-27T18:44:13.356Z" }, + { url = "https://files.pythonhosted.org/packages/91/97/e3c6e58ece26a053419ba0a18444b5443cfc64451bbf37f84e8143b8bdca/cuda_bindings-12.9.7-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c7ef48c5e13ae90f3b2ecfb72f8e99ac43c8f4c43e67e1325b8aae331453687", size = 7611059, upload-time = "2026-05-27T18:44:15.252Z" }, + { url = "https://files.pythonhosted.org/packages/6d/39/afaa3de4d491a55af8961081e0b69c08d51bfbe471c359a7bddb4a28ca41/cuda_bindings-12.9.7-cp312-cp312-win_amd64.whl", hash = "sha256:3c089aaf4f5f570ec50244c68f5a2b00a2c9a8e01e04219fd2e36e340be0d88b", size = 7400841, upload-time = "2026-05-27T18:44:17.164Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7b/f1575e41e1a17dc2f2a408b2e8e864c9324e41e3e23f6401e5efc54c152a/cuda_bindings-12.9.7-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:266379e4942051f544a8e7ea1a30ead8d7e8199b6b30fcdc8917cae2bf614e61", size = 6978549, upload-time = "2026-05-27T18:44:18.839Z" }, + { url = "https://files.pythonhosted.org/packages/9d/dc/62d62eb4f91eb721bcf46da51b13e9872ccd8fa7e60eb8ba7b7baeac72c6/cuda_bindings-12.9.7-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59cf4a37b0d662ba15037c9ceebe1a306ebf2c01a8235a09be13cd07094fdb74", size = 7457675, upload-time = "2026-05-27T18:44:20.637Z" }, + { url = "https://files.pythonhosted.org/packages/43/b2/753fe88151001d0dc23f56a8e119fe06b991b0d1a885fa02f9852b12f523/cuda_bindings-12.9.7-cp313-cp313-win_amd64.whl", hash = "sha256:5bd89dcb78475a6d8a4620ea94b74edf0cbbeacee6d1622d8f94452c1e8d3f15", size = 7360097, upload-time = "2026-05-27T18:44:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f9/77/94d9b85f26add6fe9c9cb7c4ec3b96bc598f7ea5cfbd7490cc0a36adf5be/cuda_bindings-12.9.7-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2dbcd4801954eb3508f4dc2fa0d0c8eb93eb3f45326fd61be2731418c371e7a0", size = 6870886, upload-time = "2026-05-27T18:44:24.164Z" }, + { url = "https://files.pythonhosted.org/packages/04/dd/3ec34b569e1b990b11276feba306bf8f446656cc38e8ed0f49b5facfeffa/cuda_bindings-12.9.7-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3747ea132642416786a8e31bf229032df3a7856911ae5426a7be53d032df183d", size = 7345663, upload-time = "2026-05-27T18:44:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c8/d79a20ba396e7ab2dfdd4b72b62356972b25b88aee2ded49a70c797ddea1/cuda_bindings-12.9.7-cp313-cp313t-win_amd64.whl", hash = "sha256:64f7ade7a7a3b69001489753acc21706d9dbda32db8deb68a767a0a0aab30b68", size = 7780136, upload-time = "2026-05-27T18:44:28.121Z" }, + { url = "https://files.pythonhosted.org/packages/68/e4/075052d42872cf8162da53f14447a4b8abc004c3750e4b724ee502428da0/cuda_bindings-12.9.7-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:775960ac9e530717f3b48e165cc6f68684fa9a4141764fd923e4c1a9820acc73", size = 7060090, upload-time = "2026-05-27T18:44:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/ec/cd/3289c810a4d45e5364a3387a74b4c9b6f6f57ee96ae0e5b537cc61dec242/cuda_bindings-12.9.7-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c47ec1a7a441d91aab32339951df7a1be53451121a12c094bba51467717a35a", size = 7504419, upload-time = "2026-05-27T18:44:31.992Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a0/c429fdcfa5aae181415504c5085ea5944f782b417dd16a7f2a14be0da80d/cuda_bindings-12.9.7-cp314-cp314-win_amd64.whl", hash = "sha256:1e2a4f2ec5b67408c04bb4fbed45d214b66de1f00ee2e972865cacb8708d4e1e", size = 7493876, upload-time = "2026-05-27T18:44:33.618Z" }, + { url = "https://files.pythonhosted.org/packages/11/43/472a6281c3d94e71687e27c657a8f60718d3579b4d94c41deea503165f8a/cuda_bindings-12.9.7-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00a833d399b31071fab4cf3de2929840ae462dc4848116eeff033d09219e7116", size = 6899146, upload-time = "2026-05-27T18:44:35.556Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/10c1d0b32a9da65142d213e0733d748457fb3fd066aee4317335266f15c6/cuda_bindings-12.9.7-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11aeafa2b33995f890086b3fb0f062075176d956e9b6a6fe1a699dddc413f6ad", size = 7369087, upload-time = "2026-05-27T18:44:37.359Z" }, + { url = "https://files.pythonhosted.org/packages/33/10/c71a07cd2a1d4db119bada1848b4752a874ccfe4927d419bfdd05f250920/cuda_bindings-12.9.7-cp314-cp314t-win_amd64.whl", hash = "sha256:ece8dfbc22e6de96a26940ab9887eb3cfe1fc1bc3966169391cdb866bb82bb64", size = 8208198, upload-time = "2026-05-27T18:44:39.053Z" }, +] + +[[package]] +name = "cuda-bindings" +version = "13.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "cuda-pathfinder", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/21/8464d133752951c154feafb3b65c297e7d80f301183d220bec4c830f1441/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120fcc53d57903df529c3486962c56528cba5b7d6c57c99537320ed9922c8b86", size = 6073403, upload-time = "2026-05-29T23:11:36.22Z" }, + { url = "https://files.pythonhosted.org/packages/a8/1f/5ef51f5fbaa5d4d3201bb3d7555af028ec1aa4416275ccbf73c9e34e3d2d/cuda_bindings-13.3.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9851b0caa8bfd3bc6fa054eaf57bea7c8e9c3a62db2d2621224677f49f3c53d0", size = 6675244, upload-time = "2026-05-29T23:11:38.664Z" }, + { url = "https://files.pythonhosted.org/packages/fc/64/bb17e4d168569ef7be05c44474fe3dc19278d60a69ba228e45a431c86444/cuda_bindings-13.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:c0c4b1a995098c46695c24257a342dc97d6e6d3f3050b944c9f43bd26d734051", size = 5625597, upload-time = "2026-05-29T23:11:40.808Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/93/f7/0e35987a21914f84068061dcf4b61466ccbce1c62ddc9727596d5ed0c26f/cuda_bindings-13.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:507b0e19e7f934c5e30f30f0244ad70a75812619a7d3a0d742543caae1bd50f1", size = 5664286, upload-time = "2026-05-29T23:11:47.719Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965, upload-time = "2026-05-29T23:11:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/7c/95/872a0392122f1fb43fcb06869790ef3171f37beee9f7db8f441739113570/cuda_bindings-13.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:b134dd8c5c66ae4c4ad814f7aee88fd215353c077010cbc47e3b55ed35ec9eff", size = 5875099, upload-time = "2026-05-29T23:11:54.635Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2f/6a0dd496550c6fafbf6aeb1bf40242eeabb2fd138a43892aabb4be8224c2/cuda_bindings-13.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:18c8c167c8907b8f02531ca810534315c458dabef31f7965095619bf647b9202", size = 5830027, upload-time = "2026-05-29T23:12:01.205Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639, upload-time = "2026-05-29T23:12:03.509Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419, upload-time = "2026-05-29T23:12:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/2734be44dbc80ac082ec23a86b41c8294992dcb90033645ed1bc50aafe4c/cuda_bindings-13.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:8de12ef60bf40756852cb62bbb40460609269f6ece522903d1cc93d73a3ececb", size = 5961055, upload-time = "2026-05-29T23:12:07.971Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771, upload-time = "2026-05-29T23:12:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584, upload-time = "2026-05-29T23:12:12.767Z" }, + { url = "https://files.pythonhosted.org/packages/27/2a/b59bcac016ab9985d6b48a5d05b0d698461a159ca03ee11c4abd54da2ac4/cuda_bindings-13.3.1-cp314-cp314t-win_amd64.whl", hash = "sha256:61120b5e4f4a63f67efd7e7396914cb9ef871bb1f0021e990fb70277be240a4d", size = 6740329, upload-time = "2026-05-29T23:12:15.153Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.5.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/53/8fc9b0cdc5b7f62746e6a01b85b6461e5ae27f871010a5fcf8fa6950766d/cuda_pathfinder-1.5.6-py3-none-any.whl", hash = "sha256:7e4c07c117b78ba1fb35dac4c444d21f3677b1b1ff56175c53a8e3025c5b43c0", size = 52972, upload-time = "2026-06-30T00:58:04.34Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "12.8.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/c8/7dce3a0b15b42a3b58e7d96eb22a687d3bf2c44e01d149a6874629cd9938/cuda_toolkit-12.8.1-py2.py3-none-any.whl", hash = "sha256:adc7906af4ecbf9a352f9dca5734eceb21daec281ccfcf5675e1d2f724fc2cba", size = 2283, upload-time = "2025-08-13T02:03:07.842Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cudart = [ + { name = "nvidia-cuda-runtime-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cufft = [ + { name = "nvidia-cufft-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cufile = [ + { name = "nvidia-cufile-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +curand = [ + { name = "nvidia-curand-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cusolver = [ + { name = "nvidia-cusolver-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cusparse = [ + { name = "nvidia-cusparse-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvtx = [ + { name = "nvidia-nvtx-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, +] + +[package.optional-dependencies] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +curand = [ + { name = "nvidia-curand", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cusolver = [ + { name = "nvidia-cusolver", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "dashai" +version = "0.9.6" +source = { editable = "." } +dependencies = [ + { name = "accelerate" }, + { name = "alembic" }, + { name = "beartype" }, + { name = "cmaes" }, + { name = "controlnet-aux" }, + { name = "datasets" }, + { name = "diffusers" }, + { name = "evaluate" }, + { name = "fastapi", extra = ["all"] }, + { name = "filetype" }, + { name = "greenery" }, + { name = "httpx" }, + { name = "huey" }, + { name = "hyperopt" }, + { name = "ijson" }, + { name = "imblearn" }, + { name = "joblib" }, + { name = "kink" }, + { name = "llvmlite" }, + { name = "numba", version = "0.47.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numba", version = "0.66.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or platform_machine != 'x86_64' or sys_platform != 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-ml-py" }, + { name = "opencv-python" }, + { name = "openml" }, + { name = "openpyxl" }, + { name = "optuna" }, + { name = "oslo-concurrency" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "plotly" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pywebview" }, + { name = "rich" }, + { name = "sacrebleu" }, + { name = "scikit-learn" }, + { name = "sentencepiece" }, + { name = "setuptools" }, + { name = "shap", version = "0.46.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "shap", version = "0.52.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sqlalchemy" }, + { name = "starlette" }, + { name = "streaming-form-data" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.12.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchmetrics" }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "transformers" }, + { name = "typer" }, + { name = "wordcloud" }, + { name = "xlrd" }, +] + +[package.optional-dependencies] +cpu = [ + { name = "llama-cpp-python", version = "0.3.32", source = { registry = "https://abetlen.github.io/llama-cpp-python/whl/cpu" } }, + { name = "torch", version = "2.12.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +cuda = [ + { name = "llama-cpp-python", version = "0.3.32", source = { registry = "https://pypi.org/simple" } }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" } }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" } }, +] + +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx-rtd-theme" }, + { name = "sqlalchemy-stubs" }, +] + +[package.metadata] +requires-dist = [ + { name = "accelerate" }, + { name = "alembic" }, + { name = "beartype" }, + { name = "cmaes" }, + { name = "controlnet-aux" }, + { name = "datasets" }, + { name = "diffusers" }, + { name = "evaluate" }, + { name = "fastapi", extras = ["all"] }, + { name = "filetype" }, + { name = "greenery", specifier = "==3.2" }, + { name = "httpx" }, + { name = "huey" }, + { name = "hyperopt" }, + { name = "ijson" }, + { name = "imblearn" }, + { name = "joblib" }, + { name = "kink" }, + { name = "llama-cpp-python", marker = "extra == 'cpu'", index = "https://abetlen.github.io/llama-cpp-python/whl/cpu", conflict = { package = "dashai", extra = "cpu" } }, + { name = "llama-cpp-python", marker = "extra == 'cuda'" }, + { name = "llvmlite" }, + { name = "numba" }, + { name = "numpy" }, + { name = "nvidia-ml-py" }, + { name = "opencv-python" }, + { name = "openml" }, + { name = "openpyxl" }, + { name = "optuna" }, + { name = "oslo-concurrency" }, + { name = "pandas", specifier = "<3.0.0" }, + { name = "pillow" }, + { name = "plotly" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pywebview" }, + { name = "rich" }, + { name = "sacrebleu" }, + { name = "scikit-learn", specifier = "<1.8.0" }, + { name = "sentencepiece" }, + { name = "setuptools", specifier = ">=65.0.0,<82" }, + { name = "shap" }, + { name = "sqlalchemy" }, + { name = "starlette" }, + { name = "streaming-form-data" }, + { name = "torch" }, + { name = "torch", marker = "extra == 'cpu'", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "dashai", extra = "cpu" } }, + { name = "torch", marker = "extra == 'cuda'", index = "https://download.pytorch.org/whl/cu128", conflict = { package = "dashai", extra = "cuda" } }, + { name = "torchmetrics" }, + { name = "torchvision" }, + { name = "torchvision", marker = "extra == 'cpu'", index = "https://download.pytorch.org/whl/cpu", conflict = { package = "dashai", extra = "cpu" } }, + { name = "torchvision", marker = "extra == 'cuda'", index = "https://download.pytorch.org/whl/cu128", conflict = { package = "dashai", extra = "cuda" } }, + { name = "transformers" }, + { name = "typer" }, + { name = "wordcloud" }, + { name = "xlrd" }, +] +provides-extras = ["cpu", "cuda"] + +[package.metadata.requires-dev] +dev = [ + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "ruff" }, + { name = "sphinx" }, + { name = "sphinx-rtd-theme" }, + { name = "sqlalchemy-stubs" }, +] + +[[package]] +name = "datasets" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, + { name = "filelock" }, + { name = "fsspec", extra = ["http"] }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/85/ce4f780c32f7e36d71257f1c27e8ba898ebe379cb54f211f5f2013f2c219/datasets-5.0.0.tar.gz", hash = "sha256:83dbbbdb07a33b82192b8c419deb18739b138ee2ce1a322d55ce6b100954ec1a", size = 631708, upload-time = "2026-06-05T13:18:26.124Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/66/73034ad30b59f13439b75e620989dacba4c047256e358ba7c2e9ec98ea22/datasets-5.0.0-py3-none-any.whl", hash = "sha256:7dd34927a0fd7046e98aad5cb9430e699c373238a15befa7b9bf22b991a7fee6", size = 555084, upload-time = "2026-06-05T13:18:24.435Z" }, +] + +[[package]] +name = "debtcollector" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/57/1bbe02be744995408d944cf46b8c818cf072873064b1cd3c79c11618b216/debtcollector-3.1.0.tar.gz", hash = "sha256:278a45608cf16e79c0ae10851d869185c6b78f86610df8f27a451a18c1fec732", size = 32951, upload-time = "2026-03-24T10:07:38.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/05/3f36aed56f0e1815fdc2ed4a9f2bd680a7bfe8819f21eacded2dc00fe283/debtcollector-3.1.0-py3-none-any.whl", hash = "sha256:c64e49a66c0b71289620fc2fdf89c03d740bddb20576ddd4f04ddc01da946668", size = 24408, upload-time = "2026-03-24T10:07:37.218Z" }, +] + +[[package]] +name = "detect-installer" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/ce/6897d812825e9d4c53e3c7112726e800cc5231b013b2223bf64f653ff362/detect_installer-0.1.0.tar.gz", hash = "sha256:00ad7ba0a36e3cf7d08a40d3643011746dbc112597c7d475cc91c416710ca4e7", size = 3049, upload-time = "2026-02-23T10:40:22.567Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/34/8cc73273414405086c58852916e4031812a6a30fe04c057e37ad99397b7f/detect_installer-0.1.0-py3-none-any.whl", hash = "sha256:034fb20fd665c36e6ba52b8821525ea07fb4f7f938cac459df889fb33801528a", size = 4539, upload-time = "2026-02-23T10:40:23.807Z" }, +] + +[[package]] +name = "diffusers" +version = "0.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "importlib-metadata" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/81/6095237b86a3116c4789f28c4435d5296c00c0fc74ffde99008fd6b3a36c/diffusers-0.39.0.tar.gz", hash = "sha256:14bb1d98c85a0e463d734c99aaa73b480a7bc9bad22af30fbf730ef8f09c1d67", size = 4651240, upload-time = "2026-07-03T08:48:47.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/3f/7469c46e9d22307ea686bab687d70e6bf328722952f9d10339f5e913e608/diffusers-0.39.0-py3-none-any.whl", hash = "sha256:912aca51b5787365110806e984d5555735bf8a461073bb8459029d0bca7870ef", size = 5631176, upload-time = "2026-07-03T08:48:45.337Z" }, +] + +[[package]] +name = "dill" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/81/e1/56027a71e31b02ddc53c7d65b01e68edf64dea2932122fe7746a516f75d5/dill-0.4.1.tar.gz", hash = "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa", size = 187315, upload-time = "2026-01-19T02:36:56.85Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/77/dc8c558f7593132cf8fefec57c4f60c83b16941c574ac5f619abb3ae7933/dill-0.4.1-py3-none-any.whl", hash = "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", size = 120019, upload-time = "2026-01-19T02:36:55.663Z" }, +] + +[[package]] +name = "diskcache" +version = "5.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "docutils" +version = "0.21.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/ed/aefcc8cd0ba62a0560c3c18c33925362d46c6075480bfa4df87b28e169a9/docutils-0.21.2.tar.gz", hash = "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", size = 2204444, upload-time = "2024-04-23T18:57:18.24Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/d7/9322c609343d929e75e7e5e6255e614fcc67572cfd083959cdef3b7aad79/docutils-0.21.2-py3-none-any.whl", hash = "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2", size = 587408, upload-time = "2024-04-23T18:57:14.835Z" }, +] + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "einops" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/77/850bef8d72ffb9219f0b1aac23fbc1bf7d038ee6ea666f331fa273031aa2/einops-0.8.2.tar.gz", hash = "sha256:609da665570e5e265e27283aab09e7f279ade90c4f01bcfca111f3d3e13f2827", size = 56261, upload-time = "2026-01-26T04:13:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, +] + +[[package]] +name = "email-validator" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "evaluate" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "datasets" }, + { name = "dill" }, + { name = "fsspec", extra = ["http"] }, + { name = "huggingface-hub" }, + { name = "multiprocess" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/d0/0c17a8e6e8dc7245f22dea860557c32bae50fc4d287ae030cb0e8ab8720f/evaluate-0.4.6.tar.gz", hash = "sha256:e07036ca12b3c24331f83ab787f21cc2dbf3631813a1631e63e40897c69a3f21", size = 65716, upload-time = "2025-09-18T13:06:30.581Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/af/3e990d8d4002bbc9342adb4facd59506e653da93b2417de0fa6027cb86b1/evaluate-0.4.6-py3-none-any.whl", hash = "sha256:bca85bc294f338377b7ac2f861e21c308b11b2a285f510d7d5394d5df437db29", size = 84069, upload-time = "2025-09-18T13:06:29.265Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "fastapi" +version = "0.139.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/af/a5f50ccfa659ec1802cb4ca842c23f06d906a8cc9aef6016a2caeea3d4ed/fastapi-0.139.0.tar.gz", hash = "sha256:99ab7b2d92223c76d6cf10757ab3f89d45b38267fc20b2a136cf02f6beac3145", size = 423016, upload-time = "2026-07-01T16:35:33.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/7c/8e3c6ad324ea5cb36604fc3f968554887891c316d9dfde57761611d907ad/fastapi-0.139.0-py3-none-any.whl", hash = "sha256:cf15e1e9e667ddb0ad63811e60bd11390d1aac838ca4a7a23f421807b2308189", size = 130339, upload-time = "2026-07-01T16:35:32.19Z" }, +] + +[package.optional-dependencies] +all = [ + { name = "email-validator" }, + { name = "fastapi-cli", extra = ["standard"] }, + { name = "httpx" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "pydantic-extra-types" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cli" +version = "0.0.27" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "rich-toolkit" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/d0/ee5678346811967b8d096d5d5604e71b50d6bf5a2abfbdb331157e2bbaa9/fastapi_cli-0.0.27.tar.gz", hash = "sha256:1dffb1e40c0c88f2e0171a8a252a2b615c1e63ff8c05626649e4badd6a84336a", size = 23630, upload-time = "2026-06-18T14:48:43.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/ab/0a709f9488fe62647db80f8a277fb0ee62e85adc6746abf477ed373c9eb7/fastapi_cli-0.0.27-py3-none-any.whl", hash = "sha256:2e389a40f318e29fec8cb1e289f267f17c048876fb82dbfa869a10b16740495d", size = 13070, upload-time = "2026-06-18T14:48:44.311Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "fastapi-cloud-cli" }, + { name = "uvicorn", extra = ["standard"] }, +] + +[[package]] +name = "fastapi-cloud-cli" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "detect-installer" }, + { name = "fastar" }, + { name = "httpx" }, + { name = "pydantic", extra = ["email"] }, + { name = "rich-toolkit" }, + { name = "rignore" }, + { name = "sentry-sdk" }, + { name = "typer" }, + { name = "uvicorn", extra = ["standard"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/e5/bee77aa542ec66bcc55b458d606f3356a58f5bb9f2c59006f6ff53a3869b/fastapi_cloud_cli-0.22.1.tar.gz", hash = "sha256:50d80de6ce397a4959e6f3509574edac65d0a6998655215c95d077b18ec2f4b1", size = 94501, upload-time = "2026-07-01T22:09:03.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/0c/9e3069ff571142e571d68156aa4c1ab1d51c0b87f21bbe0f44c10029b19b/fastapi_cloud_cli-0.22.1-py3-none-any.whl", hash = "sha256:4ba307b97b08282d1efb2daef5f1e88af23e02fe2286c25273c1794e399df509", size = 77739, upload-time = "2026-07-01T22:09:02.359Z" }, +] + +[[package]] +name = "fastar" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/0f/0aeb3fc50046617702acc0078b277b58367fd62eb727b9ec733ae0e8bbcc/fastar-0.11.0.tar.gz", hash = "sha256:aa7f100f7313c03fdb20f1385927ba95671071ba308ad0c1763fef295e1895ce", size = 70238, upload-time = "2026-04-13T17:11:17.143Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4a/0d79fe52243a4130aa41d0a3a9eea22e00427db761e1a6782ee817c50222/fastar-0.11.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7c906ad371ca365591ebcb7630009923f3eceb20956814494d15591a78e9e46", size = 709786, upload-time = "2026-04-13T17:09:53.974Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e4/77c94eaafc035e39f5ce5176e32743da4e3fe890f28790e708e53d8f75cd/fastar-0.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6919497b35fa5bd978d2c26ee117cf1771b90ee5073f7518e44b9bc364b57715", size = 632127, upload-time = "2026-04-13T17:09:39.023Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f6/97658dd992f4e45747d35adb24c0b100f6b6d451490685ae3fe8a3a2ee1b/fastar-0.11.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:56b50206aeedd99e22b83289e6fb3ff8f7d7da4407d2419902e4716b4f90585a", size = 869608, upload-time = "2026-04-13T17:09:08.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/fc/81c1ec4d8146a437399e7b95631b51be312f323a9ce64569f932db6c3914/fastar-0.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a1811a69ae81d469720df0c8af3f84f834a93b5e4f8be0e0e8bde6a52fa11f2", size = 762925, upload-time = "2026-04-13T17:07:52.788Z" }, + { url = "https://files.pythonhosted.org/packages/b9/35/49baf480ecb197aea7ce2515c503a2f25061958dd3b4c98e98a3a11cdcc7/fastar-0.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:10486238c55589a3947c38f9cfb88a67d8a608eb8dddc722038237d0278a41d7", size = 759913, upload-time = "2026-04-13T17:08:07.324Z" }, + { url = "https://files.pythonhosted.org/packages/94/eb/946f1980267f2824efb7d7c518d47a49b89c0e9cd7c449301f5a7531558a/fastar-0.11.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1555ef9992d368a6ec39092276990cef8d329c39a1d86ebd847eaa3b10efd472", size = 926054, upload-time = "2026-04-13T17:08:22.196Z" }, + { url = "https://files.pythonhosted.org/packages/0c/19/d5eb611085ce054382570d8d4e24a5e2ff23cd6d2404528a6643841d6059/fastar-0.11.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1f4aca0a9620b76988bbf6225cdea6678a392902444ca18bb8a51495b165a89", size = 818594, upload-time = "2026-04-13T17:08:52.366Z" }, + { url = "https://files.pythonhosted.org/packages/4a/52/18e8d55c0d3d917713f381cb2d0cb793da00c209c802e011d8dc72018cd5/fastar-0.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75beeecac7d11a666a6c4a0b7f7e80842ae5cf523f2f890b99c78fc82b403545", size = 823005, upload-time = "2026-04-13T17:09:23.051Z" }, + { url = "https://files.pythonhosted.org/packages/2c/b4/0fecdcf33e5aaffe777b96a1c10a3204fe0b05bf18e971033a0bfedafc1c/fastar-0.11.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a08cdf5d16daa401c65c9c7493a18db7dc515c52155a17071ec7098bb07da9d3", size = 887115, upload-time = "2026-04-13T17:08:37.385Z" }, + { url = "https://files.pythonhosted.org/packages/08/f8/2a6ad1c2523eb72a4595a9331162fc67ce0f0aee3348728598026c516986/fastar-0.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6e210375e5a7ba53586cbd6017aa417d2d2ceacbe8671682470281bd0a15e8ef", size = 973595, upload-time = "2026-04-13T17:10:09.258Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/2aa48843228673feacc2b80876b8924e63ea9c5f5f607bd7a72416b86bae/fastar-0.11.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a2988eb2604b8e15670f355425e8c800e4dcd4edfbcbfe194397f8f17b7eb19e", size = 1036988, upload-time = "2026-04-13T17:10:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/92/ac/3dd14b21c323e8484f47c910110d1d93139ba44621ac2c4c597dbe9fcdb7/fastar-0.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:34abc857b46068fdf91d157bd0203bfd6791dc7a432d1ed180f5af6c2f5bcce9", size = 1078267, upload-time = "2026-04-13T17:10:43.645Z" }, + { url = "https://files.pythonhosted.org/packages/de/a1/3f89e58d6fa99160c9e7e17220c8ab5040b5cc017c4fac2356c6ed18453d/fastar-0.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0d884be84e37a01053776395441fc960031974e0265801ce574efc3d05e0cdaf", size = 1032551, upload-time = "2026-04-13T17:11:00.667Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ea/24dd3cfc2096933d7d2a80c926e79602cff1fa481124ed2165b60c1dd9ef/fastar-0.11.0-cp310-cp310-win32.whl", hash = "sha256:c721c1ad758e3e4c2c1fd9e96911a0fa58c0a6be5668f1bcfd0b741e72c7cb63", size = 456022, upload-time = "2026-04-13T17:11:41.859Z" }, + { url = "https://files.pythonhosted.org/packages/82/ef/6eb39ee9cdd59822d1c7337c4d28fdc948885bdf455af9e70efa9879e06f/fastar-0.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba4180b7c3080f55f9035fdd7d8c39fe0e1485087a68ff615bb4784a10b8106b", size = 488392, upload-time = "2026-04-13T17:11:27.486Z" }, + { url = "https://files.pythonhosted.org/packages/11/7a/fb367bdaf4efa2c7952a45aeab2e87a564293ecffe150af673ec8edfda46/fastar-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b82fd6f996e65a86f67a6bd64dd22ef3e8ae2dcaed0ae3b550e71f7e1bbb1df5", size = 709869, upload-time = "2026-04-13T17:09:55.62Z" }, + { url = "https://files.pythonhosted.org/packages/80/ff/b87efb0dcfd081c62c7c7601d7681dabe63103cd51fc16f8d57a1ab45961/fastar-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27eed386fd0558e6daa29211111bbd7b740f7c7e881197f8a00ac7c0f3cdb1d7", size = 631668, upload-time = "2026-04-13T17:09:40.537Z" }, + { url = "https://files.pythonhosted.org/packages/24/7c/0ed6dd38b9adc04b3a8ec3b7045908e7c2170ba0ff6e6d2c51bc9fc770f3/fastar-0.11.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a6931bebc1d8e95ddeef55732c195449e6b44ef33aa31b325505097ed3b4d6aa", size = 869663, upload-time = "2026-04-13T17:09:09.78Z" }, + { url = "https://files.pythonhosted.org/packages/58/ce/8b7fb3f23855accebaaf2d2637eac7f261a7a5d936f861a172079f1ef511/fastar-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f72ce42a5e28a74fbd4d5fbf1a3ac1a1163d13cbc200cbd005fb0fabc54bd", size = 762938, upload-time = "2026-04-13T17:07:54.51Z" }, + { url = "https://files.pythonhosted.org/packages/07/cc/5491e2b677bb841f768e3aba052d0344338a5c78aa5d4c18b443831a8e8d/fastar-0.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5b83c1f61f7017d6e1498568038f8745440cfc16ca2f697ec81bac83050108f6", size = 759232, upload-time = "2026-04-13T17:08:08.864Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/643630bdbd179e41e9fae31c03b4cf6061dbf4d6fbbae8425d16eb12545d/fastar-0.11.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db73a9b765a516e73983b25341e7b5e0189733878279e278b2295131b0e3a21e", size = 926271, upload-time = "2026-04-13T17:08:23.68Z" }, + { url = "https://files.pythonhosted.org/packages/09/5d/37ade50003b4540e0a53ef100f6692d7ab2ac1122d5acf39920cc09a3e8b/fastar-0.11.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:625827d52eb4e8fec942e0233f125ff8010fcf6a67c0a974a8e5f4666b771e3c", size = 818634, upload-time = "2026-04-13T17:08:54.268Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ff/135d177de32cc1e837c99019e4643e6e79352bde49544d4ece5b5eebf56b/fastar-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7f5fd8fa21ec0a88296a38dc5d7fc35efd3b26d46a17b8b7c73c5563925ca15", size = 822755, upload-time = "2026-04-13T17:09:25.01Z" }, + { url = "https://files.pythonhosted.org/packages/27/cb/b835dbe76ceac7fa6105851468c259ffd06830eb9c029402e499d0ec153b/fastar-0.11.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8c15af91b8cd87ddf23ea55355ae513c1de3ab67178f26dad017c9e9c0af6096", size = 887101, upload-time = "2026-04-13T17:08:39.248Z" }, + { url = "https://files.pythonhosted.org/packages/9e/54/aa8289eb57fc550535470397cb051f5a58a7c89ca4de31d5502b916dd894/fastar-0.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03a112395a8b0bff251423bd1564c012f0cc058ad8b6bd8fba96f3d7fc117e44", size = 973606, upload-time = "2026-04-13T17:10:10.98Z" }, + { url = "https://files.pythonhosted.org/packages/1f/fd/776d50a0897c01dc6bfd0926772ee913436fdae91b9affaf0a0cbd09f0a1/fastar-0.11.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f2994bb8f5f8c11eb12beae1e6e77a907173c9819236b8a4c8f0573652ceccce", size = 1036696, upload-time = "2026-04-13T17:10:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f1/cf0f9b499fb37ac065c8a01ec642f96a3c5eb849c38ae983b59f3b3245e0/fastar-0.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dcf99e4b5973d842c7f19c776c3a83cdc0977d505edce6206438505c0456b517", size = 1078182, upload-time = "2026-04-13T17:10:45.318Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9e/21e4701aec4a1123d4dc4d31578dc18875582b5710e4725f7ceb752a248b/fastar-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29c9c386dc0d5dda78845a8e6b1480d26ab861c1e0b68f42ae5735cb70ca07f1", size = 1032336, upload-time = "2026-04-13T17:11:02.364Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/5872b28c72c27ec1a00760eace6ff35f714f41ebbd5208cf016b12e29250/fastar-0.11.0-cp311-cp311-win32.whl", hash = "sha256:030b2580fc394f2c9b7890b6735810404e9b9ed5e0344db150b945965b5482b7", size = 457368, upload-time = "2026-04-13T17:11:43.528Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6e/ce6832a16193eb4466f4108be8809c249b51cb1f89dd7894545700d079d5/fastar-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:83ab57ae067969cd0b483ac3b6dccc4b595fc77f5c820760998648d4c42822b5", size = 488605, upload-time = "2026-04-13T17:11:29.161Z" }, + { url = "https://files.pythonhosted.org/packages/15/5a/9cfb80661cf38fd7b0889224beb7d2746784d4ade2a931ed9775a18d8602/fastar-0.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:27b1a4cee2298b704de8151d310462ee7335ed036011ca9aa6e784b30b6c73a9", size = 464580, upload-time = "2026-04-13T17:11:18.583Z" }, + { url = "https://files.pythonhosted.org/packages/0f/06/a5773706afc8bd496769786590bbc56d2d0ee419a299cc12ea3f5717fcf3/fastar-0.11.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3c51f1c2cdddbd1420d2897ace7738e36c65e17f6ae84e0bfe763f8d1068bb97", size = 708394, upload-time = "2026-04-13T17:09:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a6/d5e2a4e48495616440a21eed07558219ca90243ad00b0502586f95bd4833/fastar-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0d9d6b052baf5380baea866675dab6ccd04ec2460d12b1c46f10ce3f4ee6a820", size = 628417, upload-time = "2026-04-13T17:09:42.145Z" }, + { url = "https://files.pythonhosted.org/packages/ab/69/9816d69ac8265c9e50456637a487ccfb7a9c566efd9dbcd673df9c2558c2/fastar-0.11.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd2f05666d4df7e14885b5c38fefd92a785917387513d33d837ff42ec143a22f", size = 863950, upload-time = "2026-04-13T17:09:11.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0d/f88daad53aff2e754b6b5ff2a7113f72447a34f6ef17cc23ca99988117b7/fastar-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e6e74aba1ae77ca4aedcaf1697cd413319f4c88a5ccbe5b42c709517c5097e", size = 760737, upload-time = "2026-04-13T17:07:55.958Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a6/82ef4ecd969d50d92ed3ed9dbd8fe77faa24be5e5736f716edc9f4ce8d62/fastar-0.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38ef77fe940bbc9b37a98bd838727f844b11731cd39358a2640ff864fb385086", size = 757603, upload-time = "2026-04-13T17:08:10.623Z" }, + { url = "https://files.pythonhosted.org/packages/03/35/50249f0d827251f8ac511495e2eacccebda80a00a0ad73e9615b8113b84f/fastar-0.11.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8955e61b32d6aff82c983217abf80933fd823b0e727586fc72f08043d996fd59", size = 923952, upload-time = "2026-04-13T17:08:25.526Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d8/faee41659e9c379d906d24eaee6d6833ac8cfef0a5df480e5c2a8d3efb33/fastar-0.11.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:483532442cdb08fbff0169510224eae0836f2f672cea6aacb52847d90fefdc46", size = 816574, upload-time = "2026-04-13T17:08:56.076Z" }, + { url = "https://files.pythonhosted.org/packages/22/47/0448ea7992b997dad2bf004bfd98eca74b5858630eae080b50c7b17d9ddc/fastar-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef5a6071121e05d8287fc75bccb054bcbac8bb0501200a0c0a8feeace5303ea4", size = 819382, upload-time = "2026-04-13T17:09:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/33/ef/0d63eb43586831b7a6f8b22c4d77125a7c594423af1f4f090fa9541b9b40/fastar-0.11.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:e45e598af5afe8412197d4786efd6cf29be02e7d3d4f6a3461149eae5d7e94f1", size = 885254, upload-time = "2026-04-13T17:08:40.9Z" }, + { url = "https://files.pythonhosted.org/packages/01/25/edd584675d69e49a165052c3ee886df1c5d574f3e7d813c990306387c623/fastar-0.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e160919b1c47ddb8538e7e8eb4cd527281b40f0bf75110a75993838ef61f286", size = 971239, upload-time = "2026-04-13T17:10:12.997Z" }, + { url = "https://files.pythonhosted.org/packages/a5/37/e8bb24f506ba2b08fbaf36c5800e843bd4d542954e9331f00418e2d23349/fastar-0.11.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:4bb4dc0fc8f7a6807febcebce8a2f3626ba4955a9263d81ecc630aad83be84c0", size = 1035185, upload-time = "2026-04-13T17:10:30.207Z" }, + { url = "https://files.pythonhosted.org/packages/9a/bf/be753736296338149ee4cb3e92e2b5423d6ba17c7b951d15218fd7e99bbf/fastar-0.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4ec95af56aa173f6e320e1183001bf108ba59beaf13edd1fc8200648db203588", size = 1072191, upload-time = "2026-04-13T17:10:47.072Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cd/a81c1aaafb5a22ce57c98ae22f39c89413ed53e4ee6e1b1444b0bd666a6c/fastar-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:136cf342735464091c39dc3708168f9fdeb9ebea40b1ead937c61afaf46143d9", size = 1028054, upload-time = "2026-04-13T17:11:04.293Z" }, + { url = "https://files.pythonhosted.org/packages/ec/88/1ce4eed3d70627c95f49ca017f6bbbf2ddcc4b0c601d293259de7689bc20/fastar-0.11.0-cp312-cp312-win32.whl", hash = "sha256:35f23c11b556cc4d3704587faacbc0037f7bdf6c4525cd1d09c70bda4b1c6809", size = 454198, upload-time = "2026-04-13T17:11:45.168Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1d/26ce92f4331cd61a69840db9ca6115829805eec24f285481a854f578e917/fastar-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:920bc56c3c0b8a8ca492904941d1883c1c947c858cd93343356c29122a38f44c", size = 486697, upload-time = "2026-04-13T17:11:31.084Z" }, + { url = "https://files.pythonhosted.org/packages/ed/96/e6eda4480559c69b05d466e7b5ea9170e81fef3795a73e059959a3258319/fastar-0.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:395248faf89e8a6bd5dc1fd544c8465113b627cb6d7c8b296796b60ebea33593", size = 462591, upload-time = "2026-04-13T17:11:20.577Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d6/3be260037e86fb694e88d47f583bac3a0188c99cee1a6b257ac26cb6b53c/fastar-0.11.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:33f544b08b4541b678e53749b4552a44720d96761fb79c172b005b1089c443ed", size = 707975, upload-time = "2026-04-13T17:09:58.866Z" }, + { url = "https://files.pythonhosted.org/packages/e1/cd/7867aefb1784662554a335f2952c75a50f0c70585ed0d2210d6cc15e5627/fastar-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:91c1c792447e4a642745f347ff9847c52af39633071c57ee67ed53c157fc3506", size = 628460, upload-time = "2026-04-13T17:09:43.776Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2b/d11d84bdd5e0e377771b955755771e3460b290da5809cb78c1b735ee2228/fastar-0.11.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:881247e6b6eaea59fc6569f9b61447aa6b9fc2ee864e048b4643d69c52745805", size = 863054, upload-time = "2026-04-13T17:09:13.048Z" }, + { url = "https://files.pythonhosted.org/packages/25/39/d3f428b318fa940b1b6e785b8d54fc895dfb5d5b945ef8d5442ffa904fb2/fastar-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:863b7929845c9fec92ef6c8d59579cf46af5136655e5342f8df5cebe46cab06c", size = 760247, upload-time = "2026-04-13T17:07:57.396Z" }, + { url = "https://files.pythonhosted.org/packages/9e/04/03949aee82aabb8ede06ac5a4a5579ffaf98a8fe59ce958494508ff15513/fastar-0.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:96b4a57df12bf3211662627a3ea29d62ecb314a2434a0d0843f9fc23e47536e5", size = 756512, upload-time = "2026-04-13T17:08:12.415Z" }, + { url = "https://files.pythonhosted.org/packages/3f/0c/2ca1ae0a3828ca51047962d932b80daca2522db73e8cb9d040cb6ebe28d5/fastar-0.11.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceef1c2c4df7b7b8ebd3f5d718bbf457b9bbdf25ce0bd07870211ec4fbd9aff4", size = 922183, upload-time = "2026-04-13T17:08:27.187Z" }, + { url = "https://files.pythonhosted.org/packages/65/68/7fe808b1f73a68e686f25434f538c6dc10ef4dfb3db0ace22cd861744bf8/fastar-0.11.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8e545918441910a779659d4759ad0eef349e935fbdb4668a666d3681567eb05", size = 816394, upload-time = "2026-04-13T17:08:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/1f/17/07d086080f8a83b8d7966955e29bcdbd6a060f5bd949dc9d5abd3658cead/fastar-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28095bb8f821e85fc2764e1a55f03e5e2876dee2abe7cd0ee9420d929905d643", size = 818983, upload-time = "2026-04-13T17:09:28.46Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e2/2c4edf0910af2e814ff6d65b77a91196d472ca8a9fb2033bd983f6856caa/fastar-0.11.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0fafb95ecbe70f666a5e9b35dd63974ccdc9bb3d99ccdbd4014a823ec3e659b5", size = 884689, upload-time = "2026-04-13T17:08:42.763Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/04fdcbd6558e60de4ced3b55230fac47675d181252582b2fcec3c74608e5/fastar-0.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af48fed039b94016629dcdad1c95c90c486326dd068de2b0a4df419ee09b6821", size = 970677, upload-time = "2026-04-13T17:10:15.124Z" }, + { url = "https://files.pythonhosted.org/packages/df/b3/2b860a9658550167dbd5824c85e88d0b4b912bf493e42a6322544d6e483d/fastar-0.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:74cd96163f39b8638ab4e8d49708ca887959672a22871d8170d01f067319533b", size = 1034026, upload-time = "2026-04-13T17:10:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/b7/9b/fa42ea1188b144bac4b1b60753dfd449974a4d5eda132029ee7711569f94/fastar-0.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e8b993cb5613bab495ed482810bedc0986633fcb9a3b55c37ec88e0d6714f6a", size = 1071147, upload-time = "2026-04-13T17:10:48.833Z" }, + { url = "https://files.pythonhosted.org/packages/95/c8/d2e501556dca9f1fbc9246111a31792fb49ad908fa4927f34938a97a3604/fastar-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfe39d91fc28e37e06162d94afe01050220edb7df554acb5b702b5503e564816", size = 1028377, upload-time = "2026-04-13T17:11:06.374Z" }, + { url = "https://files.pythonhosted.org/packages/db/33/5f11f23eca0a569cd052507bc45dda2e5468697f8665728d25be44120f7d/fastar-0.11.0-cp313-cp313-win32.whl", hash = "sha256:c5f63d4d99ff4bfb37c659982ec413358bdee747005348756cc50a04d412d989", size = 454089, upload-time = "2026-04-13T17:11:46.821Z" }, + { url = "https://files.pythonhosted.org/packages/da/2f/35ff03c939cba7a255a9132367873fec6c355fd06a7f84fedcbaf4c8129f/fastar-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:8690ed1928d31ded3ada308e1086525fb3871f5fa81e1b69601a3f7774004583", size = 486312, upload-time = "2026-04-13T17:11:32.86Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/ee9246cbfcbfd4144558f35e7e9a306ffe0a7564730a5188c45f21d2dab8/fastar-0.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:d977ded9d98a0719a305e0a4d5ee811f1d3e856d853a50acb8ae833c3cd6d5d2", size = 461975, upload-time = "2026-04-13T17:11:22.589Z" }, + { url = "https://files.pythonhosted.org/packages/7a/cd/3644c48ecac456f928c12d47ec3bed36c36555b17c3859856f1ff860265d/fastar-0.11.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:71375bd6f03c2a43eb47bd949ea38ff45434917f9cdac79675c5b9f60de4fa73", size = 707860, upload-time = "2026-04-13T17:10:00.371Z" }, + { url = "https://files.pythonhosted.org/packages/69/ca/dee04476ae3626b2b040a60ad84628f77e1ffd8444232f2426b0ca1e0d7e/fastar-0.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:eddfd9cab16e19ae247fe44bf992cb403ccfe27d3931d6de29a4695d95ad386c", size = 628216, upload-time = "2026-04-13T17:09:45.355Z" }, + { url = "https://files.pythonhosted.org/packages/dc/5e/9395c7353d079cb4f5be0f7982ce0dc9f2e7dec5fd175eef466729d6023a/fastar-0.11.0-cp314-cp314-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:7c371f1d4386c699018bb64eb2fa785feacf32785559049d2bb72fe4af023f53", size = 864378, upload-time = "2026-04-13T17:09:14.611Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/1e4f67148223ff219612b6281a6000357abbcc2417964fa5c83f11d68fce/fastar-0.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cad7fa41e3e66554387481c1a09365e4638becd322904932674159d5f4046728", size = 760921, upload-time = "2026-04-13T17:07:59.138Z" }, + { url = "https://files.pythonhosted.org/packages/0f/82/09d11fb6d12f17993ffaf32ffd30c3c121a11e2966e84f19fb6f66430118/fastar-0.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf36652fa71b83761717c9899b98732498f8a2cb6327ff16bbf07f6be85c3437", size = 757012, upload-time = "2026-04-13T17:08:14.186Z" }, + { url = "https://files.pythonhosted.org/packages/52/1f/5aeeacc4cb65615e2c9292cd9c5b0cd6fb6d2e6ee472ca6adc6c1b1b22ef/fastar-0.11.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f68ff8c17833053da4841720e95edde80ce45bb994b6b7d51418dddaac70ee47", size = 924510, upload-time = "2026-04-13T17:08:28.741Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1a/1e5bdabbeaf2e856928956292609f2ff6a650f94480fb8afaca30229e483/fastar-0.11.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4563ed37a12ea1cdc398af8571258d24b988bf342b7b3bf5451bd5891243280c", size = 816602, upload-time = "2026-04-13T17:08:59.461Z" }, + { url = "https://files.pythonhosted.org/packages/87/24/f960147910da3bed41a3adfcb026e17d5f50f4cf467a3324237a7088f61a/fastar-0.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cee63c9875cba3b70dc44338c560facc5d6e763047dcc4a30501f9a68cf5f890", size = 819452, upload-time = "2026-04-13T17:09:29.926Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f4/3e77d7901d5707fd7f8a352e153c8ae09ea974e6fabad0b7c4eb9944b8d4/fastar-0.11.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:bd76bfffae6d0a91f4ac4a612f721e7aec108db97dccdd120ae063cd66959f27", size = 885254, upload-time = "2026-04-13T17:08:44.285Z" }, + { url = "https://files.pythonhosted.org/packages/47/01/1585edd5ec47782ae93cd94edf05828e0ab02ef00aec00aea4194a600464/fastar-0.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f5b707501ec01c1bc0518f741f01d322e50c9adc19a451aa24f67a2316e9397", size = 971496, upload-time = "2026-04-13T17:10:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e9/6874c9d1236ded565a0bed54b320ac9f165f287b1d89490fb70f9f323c81/fastar-0.11.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:37c0b5a88a657839aad98b0a6c9e4ac4c2c15d6b49c44ee3935c6b08e9d3e479", size = 1034685, upload-time = "2026-04-13T17:10:34.063Z" }, + { url = "https://files.pythonhosted.org/packages/14/d8/4ab20613ce2983427aee958e39be878dba874aa227c530a845e32429c4f6/fastar-0.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6c55f536c62a6efb180c1af0d5182948bff576bbfe6276e8e1359c9c7d2215d8", size = 1072675, upload-time = "2026-04-13T17:10:50.53Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/5ac3b7c20ce4b08f011dd2b979f96caabe64f9b10b157f211ea91bdfadca/fastar-0.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3082eeca59e189b9039335862f4c2780c0c8871d656bfdf559db4414a105b251", size = 1029330, upload-time = "2026-04-13T17:11:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e7/37cd6a1d4e288292170b64e19d79ecce2a7de8bb76790323399a2abc4619/fastar-0.11.0-cp314-cp314-win32.whl", hash = "sha256:b201a0a4e29f9fec2a177e13154b8725ec65ab9f83bd6415483efaa2aa18344b", size = 453940, upload-time = "2026-04-13T17:11:48.713Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1c/795c878b1ee29d79021cf8ed81f18f2b25ccde58453b0d34b9bdc7e025ea/fastar-0.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:868fddb26072a43e870a8819134b9f80ee602931be5a76e6fb873e04da343637", size = 486334, upload-time = "2026-04-13T17:11:34.882Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a4/113f104301df8bddcc0b3775b611a30cb7610baa3add933c7ccac9386467/fastar-0.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:3db39c9cc42abb0c780a26b299f24dfbc8be455985e969e15336d70d7b2f833b", size = 461534, upload-time = "2026-04-13T17:11:24.329Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a6/5c5f2c2c8e0c63e56a5636ebc7721589c889e94c0092cec7eb28ae7207e6/fastar-0.11.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:49c3299dec5e125e7ebaa27545714da9c7391777366015427e0ae62d548b442b", size = 707156, upload-time = "2026-04-13T17:10:02.176Z" }, + { url = "https://files.pythonhosted.org/packages/df/f7/982c01b61f0fc135ad2b16d01e6d0ee53cf8791e68827f5f7c5a65b2e5b1/fastar-0.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3328ed1ed56d31f5198350b17dd60449b8d6b9d47abb4688bab6aef4450a165b", size = 627032, upload-time = "2026-04-13T17:09:46.978Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c3/38f1dac77ae0c71c37b176277c96d830796b8ce2fe69705f917829b53829/fastar-0.11.0-cp314-cp314t-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:bd3eca3bbfec84a614bcb4143b4ad4f784d0895babc26cfc88436af88ca23c7a", size = 864403, upload-time = "2026-04-13T17:09:16.58Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f0/e69c363bdb3e5a5848e937b662b5469581ee6682c51bc1c0556494773929/fastar-0.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff86a967acb0d621dd24063dda090daa67bf4993b9570e97fe156de88a9006ca", size = 759480, upload-time = "2026-04-13T17:08:00.599Z" }, + { url = "https://files.pythonhosted.org/packages/3b/29/4d8737590c2a6357d614d7cc7288e8f68e7e449680b8922997cc4349e65e/fastar-0.11.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:86eaf7c0e985d93a7734168be2fb232b2a8cca53e41431c2782d7c12b12c03b1", size = 756219, upload-time = "2026-04-13T17:08:15.699Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ec/400de7b3b7d48801908f19cf5462177104395799472671b3e8152b2b04ca/fastar-0.11.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91f07b0b8eb67e2f177733a1f884edad7dfb9f8977ffef15927b20cb9604027d", size = 923669, upload-time = "2026-04-13T17:08:30.574Z" }, + { url = "https://files.pythonhosted.org/packages/5d/01/8926c53da923fed7ab4b96e7fbf7f73b663beb4f02095b654d6fab46f9ad/fastar-0.11.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f85c896885eb4abf1a635d54dea22cac6ae48d04fc2ea26ae652fcf1febe1220", size = 815729, upload-time = "2026-04-13T17:09:01.204Z" }, + { url = "https://files.pythonhosted.org/packages/89/f0/5fef4c7946e352651b504b1a4235dac3505e7cfd24020788ab50552e84bf/fastar-0.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:075c07095c8de4b774ba8f28b9c0a02b1a2cd254da50cbe464dd3bb2432e9158", size = 819812, upload-time = "2026-04-13T17:09:31.907Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c8/0ebc3298b4a45e7bddc50b169ae6a6f5b80c939394d4befe6e60de535ee7/fastar-0.11.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:07f028933820c65750baf3383b807ecce1cd9385cf00ce192b79d263ad6b856c", size = 884074, upload-time = "2026-04-13T17:08:45.802Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9f/7baa4cdff8d6fbca41fa5c764b48a941fed8a9ec6c4cc92de65895a28299/fastar-0.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:039f875efa0f01fa43c20bf4e2fc7305489c61d0ac76eda991acfba7820a0e63", size = 969450, upload-time = "2026-04-13T17:10:18.667Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dc/1ebbfb58a47056ba866494f19efbcdd2ba2897096b94f36e796594b4d05b/fastar-0.11.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:fff12452a9a5c6814a012445f26365541cc3d99dcca61f09762e6a389f7a32ea", size = 1033775, upload-time = "2026-04-13T17:10:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/c2/5f/ce4e3914066f08c99eb8c32952cc07c1a013e81b1db1b0f598130bf6b974/fastar-0.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2bf733e09f942b6fa876efe30a90508d1f4caef5630c00fb2a84fba355873712", size = 1072158, upload-time = "2026-04-13T17:10:52.497Z" }, + { url = "https://files.pythonhosted.org/packages/03/2a/6bca72992c84151c387cc6558f3867f5ebe5fb3684ee6fa9b76280ba4b8e/fastar-0.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d1531fa848fdd3677d2dce0a4b436ea64d9ae38fb8babe2ddbc180dd153cb7a3", size = 1028577, upload-time = "2026-04-13T17:11:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/83/18/7a7c15657a3da5569b26fc51cde6a80f8d84cb54b3b1aea6d74a103db4ad/fastar-0.11.0-cp314-cp314t-win32.whl", hash = "sha256:5744551bc67c6fc6581cbd0e34a0fd6e2cd0bd30b43e94b1c3119cf35064b162", size = 453601, upload-time = "2026-04-13T17:11:53.726Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d8/331b59a6de279f3ad75c10c02c40a12f21d64a437d9c3d6f1af2dcbd7a76/fastar-0.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f4ce44e3b56c47cf38244b98d29f269b259740a580c47a2552efa5b96a5458fb", size = 486436, upload-time = "2026-04-13T17:11:40.089Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fd/5390ec4f49100f3ecb9968a392f9e6d039f1e3fe0ecd28443716ff01e589/fastar-0.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:76c1359314355eafbc6989f20fb1ad565a3d10200117923b9da765a17e2f6f11", size = 461049, upload-time = "2026-04-13T17:11:25.918Z" }, + { url = "https://files.pythonhosted.org/packages/cc/5c/9bbeffbf1905391446dd98aa520422ce7affde5c9a7c22d757cc5d7c1397/fastar-0.11.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1266d6a004f427b0d61bd6c7b544d84cc964691b2232c2f4d635a1b75f2f6d5e", size = 711644, upload-time = "2026-04-13T17:10:07.663Z" }, + { url = "https://files.pythonhosted.org/packages/7e/af/ae5cf39d4fb82d0c592705f5ec6db1b065be5265c151b108f86126ee8773/fastar-0.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:298a827ec04ade43733f6ca960d0faec38706aa1494175869ea7ea17f5bad5d3", size = 634371, upload-time = "2026-04-13T17:09:52.083Z" }, + { url = "https://files.pythonhosted.org/packages/7e/36/8d4569e26473c72ccb02d1c5df3ed710073f1c06eca09c26d52ea79fd815/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8800e2387e463a0e5799416a1cbe72dd0fde7270a20e4bde684145e7878f6516", size = 870850, upload-time = "2026-04-13T17:09:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/bf/46/724dc796e1756d3977970f820d30d59bb8cab8e3671b285f1d82ab513aec/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7496def0a2befd82d429cb004ef7ca831585cc887947bd6b9abb68a5ef852b0b", size = 764469, upload-time = "2026-04-13T17:08:05.638Z" }, + { url = "https://files.pythonhosted.org/packages/99/e3/74d6859e632e8fb9339a14f652fb9f800c2bd6aa53071e311c0be3fbab8b/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:878eaf15463eb572e3538af7ca3a8534e5e279cf8196db902d24e5725c4af86e", size = 761375, upload-time = "2026-04-13T17:08:20.669Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e7/cc70e2be5ef8731a7525552b1c35c1448cf9eae6a62cb3a56f12c1bf27ea/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0324ed1d1ef0186e1bbd843b17807d6d837d0906899d4c99378b02c5d86bdd9c", size = 928189, upload-time = "2026-04-13T17:08:35.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/33/c9a969e78dca323547276a6fee5f4f9588f7cd5ab45acec3778c67399589/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bdf9bd863205590beaf8ef6e66f315310196632180dceaf674985d01a876cac3", size = 820864, upload-time = "2026-04-13T17:09:06.366Z" }, + { url = "https://files.pythonhosted.org/packages/84/bd/6b9434b541fe55c125b5f2e017a565596a2d215aa09207e4555e4585064f/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59af8dbb683b24b90fb5b506de080faeab0a17a908e6c2a5d93a97260ed75d7b", size = 824060, upload-time = "2026-04-13T17:09:37.377Z" }, + { url = "https://files.pythonhosted.org/packages/24/8d/871d5f8cf4c6f13987119fb0a9ae8be131e34f2756c2524e9974adf33824/fastar-0.11.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:9f3df73a3c4292cfe15696cdf59cdb6c309ab59d30b34c733be13c6e32d9a264", size = 889217, upload-time = "2026-04-13T17:08:50.884Z" }, + { url = "https://files.pythonhosted.org/packages/d0/26/cca0fd2704f3ed20165e5613ed911549aef3aaf3b0b5b02fee0e8e23e6cc/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa3762cbb16e41a76b61f4a6914937a71aab3a7b6c2d82ca233bc686ebaf756b", size = 975418, upload-time = "2026-04-13T17:10:24.307Z" }, + { url = "https://files.pythonhosted.org/packages/99/94/8bbb0b13f5b6cbe2492f0b7cbba5103e6163976a3331466d010e781fa189/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:a8c7bc8ac74cb359bb546b199288c83236372d094b402e557c197e85527495cd", size = 1038492, upload-time = "2026-04-13T17:10:41.939Z" }, + { url = "https://files.pythonhosted.org/packages/ed/d3/5b7df222a30eac2822ffd00f82fd4c2ce84fba4b369d1e1a03732fd177fc/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:587cbd060a2699c5f66281081395bb4657b2b1e0eef5c206b1aabf740019d670", size = 1080210, upload-time = "2026-04-13T17:10:58.462Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/56ef943ea524784598c035ccbd42e564e937da0438ae3f55f0e76cb95571/fastar-0.11.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a1c56957ac82408be37a3f63594bc83e0919e8760492a4475e542f9f1828778", size = 1034886, upload-time = "2026-04-13T17:11:15.617Z" }, +] + +[[package]] +name = "fasteners" +version = "0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/18/7881a99ba5244bfc82f06017316ffe93217dbbbcfa52b887caa1d4f2a6d3/fasteners-0.20.tar.gz", hash = "sha256:55dce8792a41b56f727ba6e123fcaee77fd87e638a6863cec00007bfea84c8d8", size = 25087, upload-time = "2025-08-11T10:19:37.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ac/e5d886f892666d2d1e5cb8c1a41146e1d79ae8896477b1153a21711d3b44/fasteners-0.20-py3-none-any.whl", hash = "sha256:9422c40d1e350e4259f509fb2e608d6bc43c0136f79a00db1b49046029d0b3b7", size = 18702, upload-time = "2025-08-11T10:19:35.716Z" }, +] + +[[package]] +name = "filelock" +version = "3.29.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/ee/29c668c50888588c432a702f7c2e8ee8a0c9e5286028d91f170308d6b2e9/filelock-3.29.5.tar.gz", hash = "sha256:6e6034c57a00a020e767f2614a5539863f056de7e7991d6d1473aef7ff73f156", size = 68927, upload-time = "2026-07-03T03:50:31.818Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/e3/f1fae3647d170919c2cf2a898e77e7d1a4e5c7cae0aed7bb4bd3f5ebff6f/filelock-3.29.5-py3-none-any.whl", hash = "sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693", size = 45073, upload-time = "2026-07-03T03:50:30.445Z" }, +] + +[[package]] +name = "filetype" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/c9/4141c90a90db20f807c7e10bfd689fe53eb8f7f4caff58ee4d4dfe46919f/fonttools-4.63.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e3297a6a4059b4acc3a1e9a8b04741f240a80044eef08ebd32e8b5bcdddce75b", size = 2884632, upload-time = "2026-05-14T12:02:38.56Z" }, + { url = "https://files.pythonhosted.org/packages/b8/46/ad12b5c10eae602d7ef814b02afa08aacbf89da917fed5b071282b7eadc2/fonttools-4.63.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b1cd75a03ad8cb5bc40c90bfde68c0c47de423aa19e5c0f362b43520645eea94", size = 2429441, upload-time = "2026-05-14T12:02:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/90/8f/bdca24a84c81d56fffed052229cdcff368f6e05882e526f4558891481f65/fonttools-4.63.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0425b277a59cff3d80ca42162a8de360f318438a2ac83570842a678d826d579", size = 4946346, upload-time = "2026-05-14T12:02:43.41Z" }, + { url = "https://files.pythonhosted.org/packages/04/59/a639c0e136441ee91a65b56fdf89e5d075927e7a09c559d1b0f5276577db/fonttools-4.63.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7e5c9973aa04c95650c96e5f5ad865fbf42d62079163ecfab1e01cbc2504c22", size = 4903184, upload-time = "2026-05-14T12:02:45.742Z" }, + { url = "https://files.pythonhosted.org/packages/e6/53/91b7e0cb45b536f3da1b29ba8cbab89f27e8b986809e0b1982303a3f4eca/fonttools-4.63.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cb014d58140a38135f16064c74c652ed57aa0b75cbf8bb59cac821f7edb5334e", size = 4922967, upload-time = "2026-05-14T12:02:48.386Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b7/87439bf44e6b97c5538cd29d0b7e366a5b8ce2cc132a4134fb67fa3f2fa2/fonttools-4.63.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:032038247a96c1690f9f31e377c389383c902531b085aa4e4dabd6f57f870e69", size = 5042799, upload-time = "2026-05-14T12:02:50.424Z" }, + { url = "https://files.pythonhosted.org/packages/ad/7c/8b96c3263b89ef99cded544c0f0636686f85dbd3c211c4dceef0231fca23/fonttools-4.63.0-cp310-cp310-win32.whl", hash = "sha256:a8b33a82979e0a6a34ff435cc81317be1f95ec1ebb7a3a2d1c8a6a54f02ae44e", size = 1519704, upload-time = "2026-05-14T12:02:52.523Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4d/2c2f0069970b6907de8fb5b05c5c0193cc22f717df151d1c7aef1c738f58/fonttools-4.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:0c18358a155d75034911c5ee397a5b44cd19dd325dbb8b35fb60bf421d6a72ac", size = 1568666, upload-time = "2026-05-14T12:02:54.917Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/4a/557715d5047da48d54e659203b9335be7bfaafda2c3f627b7c47e0b3aaf3/frozenlist-1.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b37f6d31b3dcea7deb5e9696e529a6aa4a898adc33db82da12e4c60a7c4d2011", size = 86230, upload-time = "2025-10-06T05:35:23.699Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/c85f9fed3ea8fe8740e5b46a59cc141c23b842eca617da8876cfce5f760e/frozenlist-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef2b7b394f208233e471abc541cc6991f907ffd47dc72584acee3147899d6565", size = 49621, upload-time = "2025-10-06T05:35:25.341Z" }, + { url = "https://files.pythonhosted.org/packages/63/70/26ca3f06aace16f2352796b08704338d74b6d1a24ca38f2771afbb7ed915/frozenlist-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a88f062f072d1589b7b46e951698950e7da00442fc1cacbe17e19e025dc327ad", size = 49889, upload-time = "2025-10-06T05:35:26.797Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ed/c7895fd2fde7f3ee70d248175f9b6cdf792fb741ab92dc59cd9ef3bd241b/frozenlist-1.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f57fb59d9f385710aa7060e89410aeb5058b99e62f4d16b08b91986b9a2140c2", size = 219464, upload-time = "2025-10-06T05:35:28.254Z" }, + { url = "https://files.pythonhosted.org/packages/6b/83/4d587dccbfca74cb8b810472392ad62bfa100bf8108c7223eb4c4fa2f7b3/frozenlist-1.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:799345ab092bee59f01a915620b5d014698547afd011e691a208637312db9186", size = 221649, upload-time = "2025-10-06T05:35:29.454Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/fd3b9cd046ec5fff9dab66831083bc2077006a874a2d3d9247dea93ddf7e/frozenlist-1.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c23c3ff005322a6e16f71bf8692fcf4d5a304aaafe1e262c98c6d4adc7be863e", size = 219188, upload-time = "2025-10-06T05:35:30.951Z" }, + { url = "https://files.pythonhosted.org/packages/ce/80/6693f55eb2e085fc8afb28cf611448fb5b90e98e068fa1d1b8d8e66e5c7d/frozenlist-1.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a76ea0f0b9dfa06f254ee06053d93a600865b3274358ca48a352ce4f0798450", size = 231748, upload-time = "2025-10-06T05:35:32.101Z" }, + { url = "https://files.pythonhosted.org/packages/97/d6/e9459f7c5183854abd989ba384fe0cc1a0fb795a83c033f0571ec5933ca4/frozenlist-1.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c7366fe1418a6133d5aa824ee53d406550110984de7637d65a178010f759c6ef", size = 236351, upload-time = "2025-10-06T05:35:33.834Z" }, + { url = "https://files.pythonhosted.org/packages/97/92/24e97474b65c0262e9ecd076e826bfd1d3074adcc165a256e42e7b8a7249/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:13d23a45c4cebade99340c4165bd90eeb4a56c6d8a9d8aa49568cac19a6d0dc4", size = 218767, upload-time = "2025-10-06T05:35:35.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/bf/dc394a097508f15abff383c5108cb8ad880d1f64a725ed3b90d5c2fbf0bb/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4a3408834f65da56c83528fb52ce7911484f0d1eaf7b761fc66001db1646eff", size = 235887, upload-time = "2025-10-06T05:35:36.354Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/25b201b9c015dbc999a5baf475a257010471a1fa8c200c843fd4abbee725/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42145cd2748ca39f32801dad54aeea10039da6f86e303659db90db1c4b614c8c", size = 228785, upload-time = "2025-10-06T05:35:37.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/f4/b5bc148df03082f05d2dd30c089e269acdbe251ac9a9cf4e727b2dbb8a3d/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e2de870d16a7a53901e41b64ffdf26f2fbb8917b3e6ebf398098d72c5b20bd7f", size = 230312, upload-time = "2025-10-06T05:35:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/db/4b/87e95b5d15097c302430e647136b7d7ab2398a702390cf4c8601975709e7/frozenlist-1.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:20e63c9493d33ee48536600d1a5c95eefc870cd71e7ab037763d1fbb89cc51e7", size = 217650, upload-time = "2025-10-06T05:35:40.377Z" }, + { url = "https://files.pythonhosted.org/packages/e5/70/78a0315d1fea97120591a83e0acd644da638c872f142fd72a6cebee825f3/frozenlist-1.8.0-cp310-cp310-win32.whl", hash = "sha256:adbeebaebae3526afc3c96fad434367cafbfd1b25d72369a9e5858453b1bb71a", size = 39659, upload-time = "2025-10-06T05:35:41.863Z" }, + { url = "https://files.pythonhosted.org/packages/66/aa/3f04523fb189a00e147e60c5b2205126118f216b0aa908035c45336e27e4/frozenlist-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:667c3777ca571e5dbeb76f331562ff98b957431df140b54c85fd4d52eea8d8f6", size = 43837, upload-time = "2025-10-06T05:35:43.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/75/1135feecdd7c336938bd55b4dc3b0dfc46d85b9be12ef2628574b28de776/frozenlist-1.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:80f85f0a7cc86e7a54c46d99c9e1318ff01f4687c172ede30fd52d19d1da1c8e", size = 39989, upload-time = "2025-10-06T05:35:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/8d/1c51c094345df128ca4a990d633fe1a0ff28726c9e6b3c41ba65087bba1d/fsspec-2026.4.0.tar.gz", hash = "sha256:301d8ac70ae90ef3ad05dcf94d6c3754a097f9b5fe4667d2787aa359ec7df7e4", size = 312760, upload-time = "2026-04-29T20:42:38.635Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/0c/043d5e551459da400957a1395e0febbf771446ff34291afcbe3d8be2a279/fsspec-2026.4.0-py3-none-any.whl", hash = "sha256:11ef7bb35dab8a394fde6e608221d5cf3e8499401c249bebaeaad760a1a8dec2", size = 203402, upload-time = "2026-04-29T20:42:36.842Z" }, +] + +[package.optional-dependencies] +http = [ + { name = "aiohttp" }, +] + +[[package]] +name = "future" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/b2/4140c69c6a66432916b26158687e821ba631a4c9273c474343badf84d3ba/future-1.0.0.tar.gz", hash = "sha256:bd2968309307861edae1458a4f8a4f3598c03be43b97521076aebf5d94c07b05", size = 1228490, upload-time = "2024-02-21T11:52:38.461Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, +] + +[[package]] +name = "greenery" +version = "3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/c8/9c6989c871b77b093482e641daec9d4a0a45357fa0f2ed3ffa3102a94e7e/greenery-3.2.tar.gz", hash = "sha256:bbfa4fb50316f08fe36e20f98d95115f6006102b73f31d106ff6c9f5ae8cb3a7", size = 43098, upload-time = "2020-06-25T19:20:13.707Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/c8/46a894b803f900489146d7290833e2a43ff1a6e7680f784ae9ff8f591865/greenery-3.2-py3-none-any.whl", hash = "sha256:10099a38c18620b999a2ec647fa5e40c63cac6b63a8fd6b7f92c959623f39d16", size = 40588, upload-time = "2020-06-25T19:20:12.153Z" }, +] + +[[package]] +name = "greenlet" +version = "3.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/f1/fbbfef6af0bad0548f09bc28948ea3c275b4edb19e17fc5ca9900a6a634d/greenlet-3.5.3.tar.gz", hash = "sha256:a61efc018fd3eb317eeca31aba90ee9e7f26f22884a79b6c6ec715bf71bb62f1", size = 200270, upload-time = "2026-06-26T19:28:24.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/a1/1f7f0c555f5858fd2906fe9f7b0a3554fddb85cb70df7a6aaec41dc292c2/greenlet-3.5.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:c180d22d325fb613956b443c3c6f4406eb70e6defc70d3974da2a7b59e06f48c", size = 285838, upload-time = "2026-06-26T18:21:05.167Z" }, + { url = "https://files.pythonhosted.org/packages/0a/29/be9f43ed61677a5759b38c8a9389248133c8c731bbfc0574ecdff66c99fc/greenlet-3.5.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483d08c11181c83a6ce1a7a61df0f624a208ec40817a3bb2302714592eee4f04", size = 602342, upload-time = "2026-06-26T19:07:06.908Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/ba41c97ec36aa4b3ec25e5aa691d79561254805fad7f2f826dd6770587e2/greenlet-3.5.3-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1dae6e0091eae084317e411f047f0b7cb241c6db570f7c45fd6b900a274914ce", size = 615541, upload-time = "2026-06-26T19:10:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/2e/8c/231ca675b0df779816950ca66b40b1fa14dbff4a0ed9814a9a29ec399140/greenlet-3.5.3-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0f6ff50ff8dbd51fae9b37f4101648b04ea0df19b3f50ab2beb5061e7716a5c8", size = 622473, upload-time = "2026-06-26T19:24:12.786Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c7/28747042e1df8a9cd120a1ebe15529fc4be3b486e13e8d551ff307a82412/greenlet-3.5.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bcd2d72ccd70a1ec68ba6ef93e7fbb4420ef9997dabc7010d893bd4015e0bec", size = 615675, upload-time = "2026-06-26T18:32:14.444Z" }, + { url = "https://files.pythonhosted.org/packages/81/fe/dd97c483a3ff82849196ccd07851600edd3ac9de74669ca8a6022ada9ea1/greenlet-3.5.3-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:37bf9c538f5ae6e63d643f88dec37c0c83bdf0e2ebc62961dedcf458822f7b71", size = 418421, upload-time = "2026-06-26T19:25:34.503Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a8/b85525a6c8fba9f009a5f7c8df1545de8fb0f0bf3e0179194ef4e500317f/greenlet-3.5.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:73f152c895e09907e0dbe24f6c2db37beb085cd63db91c3825a0fcd0064124a8", size = 1575057, upload-time = "2026-06-26T19:09:00.264Z" }, + { url = "https://files.pythonhosted.org/packages/03/79/fb76edb218fe6735ab0edeba176c7ab80df9618f7c02ce4208979f3ae7db/greenlet-3.5.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8bdb43e1a1d1873721acab2be99c5befd4d2044ddfd52e4d610801019880a702", size = 1641692, upload-time = "2026-06-26T18:31:41.454Z" }, + { url = "https://files.pythonhosted.org/packages/6b/79/86fe3ee50ed55d9b3907eecd3208b5c3fe8a79515519aae98b4753c3fa1d/greenlet-3.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:0909f9355a9f24845d3299f3112e266a06afb68302041989fd26bd68894933db", size = 238742, upload-time = "2026-06-26T18:20:40.758Z" }, + { url = "https://files.pythonhosted.org/packages/51/58/5404031044f55afad7aad1aff8be3f22b1bed03e237cfeabbc7e5c8cfde0/greenlet-3.5.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:aca9b4ce85b152b5524ef7d88170efdff80dc0032aa8b75f9aaf7f3479ea95b4", size = 287424, upload-time = "2026-06-26T18:20:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bf/1c65e9b94a54d547068fa5b5a8a06f221f3316b48908e08668d29c77cb50/greenlet-3.5.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f71be4920368fe1fabeeaa53d1e3548337e2b223d9565f8ad5e392a75ba23fc", size = 606523, upload-time = "2026-06-26T19:07:08.859Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/b66baacc95775ad511287acb0137b95574a9ce5491902372b7564799d790/greenlet-3.5.3-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d77e67f65f98449e3fb83f795b5d0a8437aead2f874ca89c96576caf4be3af6", size = 618315, upload-time = "2026-06-26T19:10:06.055Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a0/68afd1ebad40db87dac0a28ffa120726b98bf9c7c40c481b0f63c105d298/greenlet-3.5.3-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e18619ba655ac05d78d80fc83cac4ba892bd6927b99e3b8237aee861aaacc8bb", size = 626155, upload-time = "2026-06-26T19:24:14.44Z" }, + { url = "https://files.pythonhosted.org/packages/78/2b/28ed29463522fdbe4c15b1f63922041626a7478316b34ab4adda3f0a4aba/greenlet-3.5.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8540f1e6205bd13ca0ce685581037219ca54a1b41a0a15d228c6c9b8ad5903d7", size = 617381, upload-time = "2026-06-26T18:32:16.077Z" }, + { url = "https://files.pythonhosted.org/packages/07/7f/e327d912239ec4b3b49999e3967389bcf1ee8722b9ee9194d2752ecd558a/greenlet-3.5.3-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:d27c0c653a60d9535f690226474a5cc1036a8b0d7b57504d1c4f89c44a07a80c", size = 421083, upload-time = "2026-06-26T19:25:35.804Z" }, + { url = "https://files.pythonhosted.org/packages/2a/7b/ad04e9d1337fc04965dc9fc616b6a72cb65a24b800a014c011ec812f5489/greenlet-3.5.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ef56fe650f50575bf843acde967b9c567687f3c22340941a899b7bc56e956a8", size = 1577771, upload-time = "2026-06-26T19:09:01.537Z" }, + { url = "https://files.pythonhosted.org/packages/d8/33/6c87ab7ba663f70ca21f3022aad1ffe56d3f3e0521e836c2415e13abcc3c/greenlet-3.5.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5121af01cf911e70056c00d4b46d5e9b5d1415550038573d744138bacb59e6b8", size = 1644048, upload-time = "2026-06-26T18:31:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/1c/35/f0d8ee998b422cf8693b270f098e55d8d4ec8006b061b333f54f177d28d9/greenlet-3.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:0f41e4a05a3c0cb31b17023eff28dd111e1d16bf7d7d00406cd7df23f31398a7", size = 239137, upload-time = "2026-06-26T18:23:21.664Z" }, + { url = "https://files.pythonhosted.org/packages/fb/96/b9820295576ef18c9edc404f10e260ae7215ceaf3781a54b720ed2627862/greenlet-3.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:ec6f1af59f6b5f3fc9678e2ea062d8377d22ac644f7844cb7a292910cf12ff44", size = 237630, upload-time = "2026-06-26T18:24:00.281Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6e/4c37d51a2b7f82d2ff11bb6b5f7d766d9a011726624af255e843727627a3/greenlet-3.5.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:719757059f5a53fd0dde23f78cffeafcdd97b21c850ddb7ca684a3c1a1f122e2", size = 288685, upload-time = "2026-06-26T18:22:08.977Z" }, + { url = "https://files.pythonhosted.org/packages/7a/73/815dd90131c1b71ebdf53dbc7c276cafec2a1173b97559f97aba72724a87/greenlet-3.5.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efa9f765dd09f9d0cdac651ffdf631ee59ec5dc6ee7a73e0c012ba9c52fbdf5b", size = 604761, upload-time = "2026-06-26T19:07:10.114Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/079cfe76bcef36b153b25607ee91c6fcb58f17f8b23c86bbbeabe0c88d72/greenlet-3.5.3-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7faba15ac005376e02a0384504e0243be3370ce010296a44a820feb342b505ab", size = 617044, upload-time = "2026-06-26T19:10:07.25Z" }, + { url = "https://files.pythonhosted.org/packages/fb/fb/d97dc261209c80744b7c8132693a30d70ec6e7315e632cb0a10b3fec94dd/greenlet-3.5.3-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5795cd1101371140551c645f2d408b8d3c01a5a29cf8a9bce6e759c983682d23", size = 622351, upload-time = "2026-06-26T19:24:16.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/87/b4d095775a3fb1bcafbb483fc206b27ebb785724c83051447737085dc54e/greenlet-3.5.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87142215824be6ac05e2e8e2786eec307ccbc27c36723c3881959df654af6861", size = 614244, upload-time = "2026-06-26T18:32:17.594Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/e5fee13cbbd0e8de312d9a146584b8a51891c68847330ef9dc8b5109d23f/greenlet-3.5.3-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:af4923b3096e26a36d7e9cf24ab88083a20f97d191e3b97f253731ce9b41b28c", size = 425395, upload-time = "2026-06-26T19:25:37.144Z" }, + { url = "https://files.pythonhosted.org/packages/8a/70/7559b609683650fa2b95b8ab84b4ab0b26556a635d19675e12aa832d826d/greenlet-3.5.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215275b1b49320987352e6c1b054acca0064f965a2c66992bed9a6f7d913f149", size = 1574210, upload-time = "2026-06-26T19:09:03.077Z" }, + { url = "https://files.pythonhosted.org/packages/ae/73/be55392074c60fc37655ca40fa6022457bfbf6718e9e342a7b0b41f96dd2/greenlet-3.5.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6b1b0eed82364b0e32c4ea0f221452d33e6bb17ae094d9f72aed9851812747ea", size = 1638627, upload-time = "2026-06-26T18:31:44.748Z" }, + { url = "https://files.pythonhosted.org/packages/14/40/c57489acf8e37d74e2913d4eff63aa0dba17acccc4bdeef874dde2dbbec9/greenlet-3.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:cde8adafa2365676f74a979744629589999093bc86e2484214f58e61df08902c", size = 239882, upload-time = "2026-06-26T18:23:27.518Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/6fea0e3d6600f785069481ee637e09378dd4118acdfd38ad88ae2db31c98/greenlet-3.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:c4e7b79d83805475f0102008843f6eb45fd3bb0b2e88c774adab5fbaab27117d", size = 238211, upload-time = "2026-06-26T18:22:37.671Z" }, + { url = "https://files.pythonhosted.org/packages/9b/ff/a620267401db30a50cc8450ee90730e2d4a85658c055c0e760d4ed47fb13/greenlet-3.5.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c8d87c2134d871df96ecdea9cec7cbaab286dadab0f56476e57aaf9e8ac11550", size = 287609, upload-time = "2026-06-26T18:21:14.724Z" }, + { url = "https://files.pythonhosted.org/packages/d6/fa/5401ac78021c826a25b6dde0c705e0a8f29b617509f9185a31dac15fbe1b/greenlet-3.5.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2d185dd1621757e70c3861cceffd5317ab4e7ed7eb09c82994828468527ade5", size = 607435, upload-time = "2026-06-26T19:07:11.412Z" }, + { url = "https://files.pythonhosted.org/packages/e9/76/1dc144a2e56e65d36405078ed774224375ea520a1870a6e46e08bb4ac7bf/greenlet-3.5.3-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1c514a468149bf8fbbab874188a3535cd8a48a3e353eb53a3d424296f8dbacd3", size = 619787, upload-time = "2026-06-26T19:10:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/2f5b1adf256d039f5dab8005de8d3d7ad2b0070a3219c0e036b3fbfeb440/greenlet-3.5.3-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9ad04dd75458c6300b047c61b8639092433d205a25a14e310d6582a480efcca1", size = 625580, upload-time = "2026-06-26T19:24:18.344Z" }, + { url = "https://files.pythonhosted.org/packages/bf/87/c298cee62df1de4ad7fec32abda73526cff347fd143a6ed4ac369246668a/greenlet-3.5.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:915f887cf2682b66419b879423a2e072634aa7b7dce6f3ada4957cfced3f1e9a", size = 616786, upload-time = "2026-06-26T18:32:19.128Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/ab7fc9e543e44d6879b0a6ef9a4b2188940fd180cc65d6f646883ddf7201/greenlet-3.5.3-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:afaabdd554cd7ae9bbb3ca070b0d7fdfd207dbf1d16865f7233837709d354bda", size = 427933, upload-time = "2026-06-26T19:25:38.219Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2e/e6f009885ed0705ccf33fe0583c117cfd03cde77e31a596dd5785a30762b/greenlet-3.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:766cfd421c13e450feb340cd472a3ed9957d438727b7b4593ad7c76c5d2b0deb", size = 1574316, upload-time = "2026-06-26T19:09:04.273Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fe/43fd110b01e40da0adb7c90ac7ea744bef2d43dca00de5095fd2351c2a68/greenlet-3.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2ecda9ec22edf38fa389369eaed8c3d37c05f3c54e69f69438dbb2cc1de1458b", size = 1638614, upload-time = "2026-06-26T18:31:46.297Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7c/062447147a61f8b4337b156fe70d32a165fcf2f89d7ca6255e572806705c/greenlet-3.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:c82304750f057167ff60d188df1d0cc1764ce9567eadf03e6a7443bcedd0b30b", size = 239850, upload-time = "2026-06-26T18:21:54.613Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7e/220a7f5824a64a60443fc03b39dfac4ea63a7fb6d481efa27eafa928e7f4/greenlet-3.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:dc133a1569ee667b2a6ef56ce551084aeefd87a5acbc4736d336d1e2edc6cfc4", size = 238141, upload-time = "2026-06-26T18:22:48.507Z" }, + { url = "https://files.pythonhosted.org/packages/c3/93/43e116ee114b28737ba7e12952a0d4e2f55944d0f84e42bc91ba7192a3c9/greenlet-3.5.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:fd2e02fa07485778536a036222d616ab957b1d533f36b3ed98ce725d9c9d3117", size = 288202, upload-time = "2026-06-26T18:23:49.604Z" }, + { url = "https://files.pythonhosted.org/packages/82/2f/146d218299046a43d1f029fd544b3d110d0f175a09c715c7e8da4a4a345d/greenlet-3.5.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df0a0628d1597eb0897b62f55d1343f772405fd25f3b2a796c76874b0c2e22e8", size = 654096, upload-time = "2026-06-26T19:07:12.71Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cc/04738cafb3f45fa991ea44f9de94c47dcec964f5a972300988a6751f49d9/greenlet-3.5.3-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ebd933a6adabc298bab47731a130fe6bfb888bd934eee37810f151159544540d", size = 666304, upload-time = "2026-06-26T19:10:09.503Z" }, + { url = "https://files.pythonhosted.org/packages/86/a9/73fa62893d5b84b4205544e6b673c654cc43aa5b9899bac00f04d64af73d/greenlet-3.5.3-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8d19fe6c39ebff9259f07bcc685d3290f8fa4ea2278e51dd0008e4d6b0f2d814", size = 670657, upload-time = "2026-06-26T19:24:19.967Z" }, + { url = "https://files.pythonhosted.org/packages/ce/aa/4e0dad5e605c270c784ab911c43da6adb136ccd4d81180f763ca429a723d/greenlet-3.5.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b9d501b40e80b70e32323c799dd9b420a5577a9601469d362ae1ffb690f3a7c", size = 663635, upload-time = "2026-06-26T18:32:20.802Z" }, + { url = "https://files.pythonhosted.org/packages/29/7e/2ffce64929fb3cab7b65d5a0b20aaf9764e227681d731b041077fc9a525a/greenlet-3.5.3-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:962c5df2db8cb446da51edf1ca5296c389d93b99c9d8aa2ee4c7d0d8f1218260", size = 473497, upload-time = "2026-06-26T19:25:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/d1/50/13efdbea246fe3d3b735e191fec08fb50809f53cd2383ebe123d0809e44b/greenlet-3.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a1fad1d11e7d6aab184107baa8e4ece11ccba3ec9599cd7efa5ff4d70d43256a", size = 1621252, upload-time = "2026-06-26T19:09:05.647Z" }, + { url = "https://files.pythonhosted.org/packages/f7/22/c0a336ae4a1410fd5f5121098e5bfbf1865f64c5ef80b4b5412886c4a332/greenlet-3.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fad5aec764399f1b5cc347ad250a59660f20c8f8888ea6bae1f93b769cce1154", size = 1684824, upload-time = "2026-06-26T18:31:47.738Z" }, + { url = "https://files.pythonhosted.org/packages/7a/94/91aec0030bea75c4b3244251d0de60a1f3432d1ecb53ab6c437fb5c3ba61/greenlet-3.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:7669aa24cf2a1041d6f7899575b494a3ab4cf68bfcc8609b1dc0be7272db835e", size = 240754, upload-time = "2026-06-26T18:22:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/e5/06/68d0983e79e02138f64b4d303c500c27ddb48e5e77f3debb80888a921eae/greenlet-3.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:5b4807c4082c9d1b6d9eed56fcd041863e37f2228106eef24c30ca096e238605", size = 239549, upload-time = "2026-06-26T18:22:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/91/95/3e161213d7f1d378d15aa9e792093e9bfe01844680d04b7fd6e0107c9098/greenlet-3.5.3-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:271a8ea7c1024e8a0d7dd2be66dd66dda8a07193f41a17b9e924f7600f5b62be", size = 296389, upload-time = "2026-06-26T18:22:20.657Z" }, + { url = "https://files.pythonhosted.org/packages/00/92/715c44721abe2b4d1ae9abde4179411868a5bff312479f54e105d372f131/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19131729ae0ddc3c2e1ef85e650169b5e37ee32e400f215f78b94d7b0d567310", size = 653382, upload-time = "2026-06-26T19:07:14.209Z" }, + { url = "https://files.pythonhosted.org/packages/a0/83/37a10372a1090a6624cca8e74c12df1a36c2dc36429ed0255b7fb1aeee23/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1540dd8e5fc2a5aec40fbb98ef8e149fa47c89a4b4a1cf2575a14d3d1869d7a8", size = 659401, upload-time = "2026-06-26T19:10:10.876Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/8faec206b851c22b1733545fda900829a1f3f5b1c78ae7e0fb3dba57d9f4/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b897d97759425953f69a9c0fac67f8fe333ec0ce7377ef186fb2b0c3ad5e354d", size = 659582, upload-time = "2026-06-26T19:24:21.357Z" }, + { url = "https://files.pythonhosted.org/packages/db/e2/d1509cad4207da559cc42986ecdd8fc67ad0d1bba2bf03023c467fd5e0f3/greenlet-3.5.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e81fa194a1d20967877bdf9c7794db2bc99063e5be36aee710c08f04c5bb087f", size = 656969, upload-time = "2026-06-26T18:32:22.272Z" }, + { url = "https://files.pythonhosted.org/packages/b4/55/50c19e49f8045834ada71ef12f8ad048eba8517c6aa41161bed676328fae/greenlet-3.5.3-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:3236754d423955ea08e9bb5f6c04a7895f9e22c290b66aa7653fcb922d839eb0", size = 491037, upload-time = "2026-06-26T19:25:40.672Z" }, + { url = "https://files.pythonhosted.org/packages/86/7d/eaf70de20aadca3a5884aec58362861c64ce45e7b277f47ed026926a3b89/greenlet-3.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:55cf4d777485d43110e47133cbba6d74a8885a87ec1227ef0267f9ee80c5aa21", size = 1617822, upload-time = "2026-06-26T19:09:06.893Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f9/414d38fc400ae4350d4185eaad1827676f7cf5287b9136e0ed1cbbe20a7f/greenlet-3.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:12a248ba75f6a9a236375f52296c498c89ff1d8badf32deb9eca7abd5853f7da", size = 1677983, upload-time = "2026-06-26T18:31:49.396Z" }, + { url = "https://files.pythonhosted.org/packages/e4/15/7edb977e08f9bff702fe42d6c902702786ff6b9694058b4e6a2a6ac90e57/greenlet-3.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:efc6bd60ea02e085862c74a3ef64b147ffc6f1a5ea7d9f26e7a939943f68c1e3", size = 243626, upload-time = "2026-06-26T18:24:41.485Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8a/93928dce91e6b3598b5e779e8d1fd6576a504640c58e78627077f6a7a91a/greenlet-3.5.3-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:ea03f2f04367845d6b58eeed276e1e56e51f0b97d8ad5a88a7d20a91dc9056cc", size = 288860, upload-time = "2026-06-26T18:22:48.07Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ca/69db42d447a1378043e2c8f19c09cbbd1263371505053c496b49066d3d16/greenlet-3.5.3-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78dbef602fda6d97d957eb7937f70c9ce9e9527330347f8f6b6f9e554a9e7a47", size = 659747, upload-time = "2026-06-26T19:07:15.565Z" }, + { url = "https://files.pythonhosted.org/packages/a8/0b/af7ac2ef8dd41e3da1a40dda6305c23b9a03e13ba975ec916357b50f8575/greenlet-3.5.3-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f73857adb8fee13fa56c172bd11262f888c0c648f9fea113e777bb2c7904a81", size = 670419, upload-time = "2026-06-26T19:10:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/25/aa/952cf28c2ff949a8c971134fb43854dd7eaa737218723aaef758f8c9aead/greenlet-3.5.3-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cefa9cef4b371f9844c6053db71f1138bc6807bab1578b0dae5149c1f1141357", size = 674261, upload-time = "2026-06-26T19:24:22.79Z" }, + { url = "https://files.pythonhosted.org/packages/51/1e/1d51640cacbfc455dbe9f9a9f594c49e4e244f63b9971a2f4764e46cc53d/greenlet-3.5.3-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:232fec92e823addaf02d9472cf7381e24a1d046a6ced1103c5caa4c21b9dfc1d", size = 668787, upload-time = "2026-06-26T18:32:24.298Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/b00d6f5e63e531a93562b2ec1a4c320fbee91f580fc42e6417af69d706e5/greenlet-3.5.3-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:6219b6d04dbf6ba6084d77dc609e8473060dc55f759cbf626d512122781fa128", size = 480322, upload-time = "2026-06-26T19:25:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/21/66/4030d5b0b5894500023f003bb054d9bb354dfbd1e186c3a296759172f5f5/greenlet-3.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:2421c3564da9429d5586d46ca31ebb26516b5498a802cf65c041a8e8a8980d34", size = 1626305, upload-time = "2026-06-26T19:09:08.281Z" }, + { url = "https://files.pythonhosted.org/packages/0e/50/5221371c7550108dfa3c378debc41d032aa9c78e89abb01d8011cfc93289/greenlet-3.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e0f0d160f0b2e558e6c75f7930967183255dc9735e5f5b8cae58ee09c9576d8b", size = 1688631, upload-time = "2026-06-26T18:31:51.278Z" }, + { url = "https://files.pythonhosted.org/packages/68/5d/00d469daae3c65d2bf620b10eee82eb022127d483c6bc8c69fae6f3fbf17/greenlet-3.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:dd99329bbc15ca78dcc583dba05d0b1b0bae01ab6c2174989f5aaee3e41ac930", size = 241027, upload-time = "2026-06-26T18:22:38.203Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e8/883785b44c5780ed71e83d3e4437e710470be17a2e181e8b601e2da0dc4a/greenlet-3.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:499fef2acede88c1864a57bb586b4bf533c81e1b82df7ab93451cdb47dfec227", size = 240085, upload-time = "2026-06-26T18:23:54.217Z" }, + { url = "https://files.pythonhosted.org/packages/1c/da/4f4a8450962fad137c1c8981a3f1b8919d06c829993d4d476f9c525d5173/greenlet-3.5.3-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:176bc16a721fa5fc294d70b87b4dfa5fbdd251b3da5d5372735ecef9bd7d6d0c", size = 297221, upload-time = "2026-06-26T18:23:27.176Z" }, + { url = "https://files.pythonhosted.org/packages/57/66/b3bfae3e220a9b63ea539a0eea681800c69ab1aada757eae8789f183e7ce/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:629b614d2b786e89c50440e246f33eea78f58a962d0bdbbcc809e6d13605903f", size = 657221, upload-time = "2026-06-26T19:07:16.973Z" }, + { url = "https://files.pythonhosted.org/packages/7b/81/b6d4d73a709684fc77e7fa034d7c2fe82cffa9fc920fadcaa659c2626213/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b2e857ae16f5f72142edf75f9f176fe7526ba19a2841df1420516f83831c9f2", size = 663226, upload-time = "2026-06-26T19:10:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/e9/39/0e0938a75115b939d42733a2a12e1d349653c9531fe6fe563e8a681f04e6/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d192579ed281051396dddd7f7754dac6259e6b1fb26378c87b66622f8e3f91", size = 663706, upload-time = "2026-06-26T19:24:24.312Z" }, + { url = "https://files.pythonhosted.org/packages/f5/07/e210b02b589f16e74ff48b730690e4a34ffe984219fce4f3c1a0e7ec8545/greenlet-3.5.3-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e515757e2e36bcbf1fad09a46e1557e8b1ae1797d4b44d09da7deed88ad28608", size = 660802, upload-time = "2026-06-26T18:32:26.081Z" }, + { url = "https://files.pythonhosted.org/packages/5b/41/35d1c678cdb3c3b9e6bee691728e563cfb294202b23c7a4c3c2ccc343589/greenlet-3.5.3-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:4399eb8d041f20b68d943918bc55502a93d6fdc0a37c14da7881c04139acee9d", size = 498803, upload-time = "2026-06-26T19:25:43.063Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2e/5303eb3fa06bca089060f479707182a93e360683bc252acf846c3090d34e/greenlet-3.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b363d46ed1ea431825fdb01471bb024fc08399bad1572a616e853c7684415adb", size = 1622157, upload-time = "2026-06-26T19:09:09.527Z" }, + { url = "https://files.pythonhosted.org/packages/54/70/50de47a488f14df260b50ae34fb5d56016e308b098eab02c878b5223c26a/greenlet-3.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:e44da2f5bbdaabaf7d80b73dbb430c7035771e9f244e3c8b769715c9d8fa0a16", size = 1681159, upload-time = "2026-06-26T18:31:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/a7/13/1055e1dda7882073eda533e2b96c62e55bbd2db7fda6d5ece992febc7071/greenlet-3.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:8ff8bed3e3baa20a3ea261ce00526f1898ad4801d4886fd2220580ee0ad8fadf", size = 244007, upload-time = "2026-06-26T18:22:04.353Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/ca7d15afbdc397e3401134c9e1800d51d12b829661786187a4ad08fe484f/greenlet-3.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:b7068bd09f761f3f5b4d214c2bed063186b2a86148c740b3873e3f56d79bac31", size = 242586, upload-time = "2026-06-26T18:23:37.93Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/2d/57fd21d84d93efb4bd0b962383790e19dd1bc053501b4264c97903b4e83e/hf_xet-1.5.1.tar.gz", hash = "sha256:51ef4500dab3764b41135ee1381a4b62ce56fc54d4c92b719b59e597d6df5bf6", size = 876636, upload-time = "2026-06-08T23:02:53.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/ee/dd9ba7beae1005e54131b7d45263cc74c8a066d47d354e6d58ae9445a388/hf_xet-1.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:dbf48c0d02cf0b2e568944330c60d9120c272dabe013bd892d48e25bc6797577", size = 4069485, upload-time = "2026-06-08T23:02:13.193Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bc/9cae6cfeb4e03070874e73e5c97c66eb90369d3206b6a2b1ef5f96520888/hf_xet-1.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78e4e5192ad2b674c2e1160b651cb9134db974f8ae1835bdfbfb0166b894a43", size = 3838493, upload-time = "2026-06-08T23:02:15.282Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b4/d5c01e0eb6d9f2ca2dacd84d0d1b71e6cfbb2ef3208c968528e010e9b3d7/hf_xet-1.5.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6f7a04a8ad962422e225bc49fbbac99dc1806764b1f3e54dbd154bffa7593947", size = 4505658, upload-time = "2026-06-08T23:02:17.196Z" }, + { url = "https://files.pythonhosted.org/packages/76/c5/29a7598c0c6383c523dc22186d577f4e04267a626cd95ae60f67c00bfe66/hf_xet-1.5.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d48199c2bf4f8df0adc55d31d1368b6ec0e4d4f45bc86b08038089c23db0bed8", size = 4292822, upload-time = "2026-06-08T23:02:18.608Z" }, + { url = "https://files.pythonhosted.org/packages/04/9a/dceaf6ca69390126b86ea825fb354b93d01163199070b7bd849225de9468/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:97f212a88d14bbf573619a74b7fecb238de77d08fc702e54dec6f78276ca3283", size = 4491255, upload-time = "2026-06-08T23:02:20.124Z" }, + { url = "https://files.pythonhosted.org/packages/48/a7/e5a7afaacf6c1791fdbeeac42951fb81c3d2bc482992b115dedcc86d963e/hf_xet-1.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f61e3665892a6c8c5e765395838b8ddf36185da835253d4bc4509a81e49fb342", size = 4711062, upload-time = "2026-06-08T23:02:21.863Z" }, + { url = "https://files.pythonhosted.org/packages/53/49/2802f8433c9742ce281bddc1e65c02c32268ca3098d66828b05e12e45ee2/hf_xet-1.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f4ad3ebd4c32dd2b27099d69dc7b2df821e30767e46fb6ee6a0713778243b8ff", size = 4017205, upload-time = "2026-06-08T23:02:23.495Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5a/50c71195b9fb883659f596e7252faf4c18c58e753a9013bdbf9bac5d2250/hf_xet-1.5.1-cp313-cp313t-win_arm64.whl", hash = "sha256:8298485c1e36e7e67cbd01eeb1376619b7af43d4f1ec245caae306f890a8a32d", size = 3845426, upload-time = "2026-06-08T23:02:25.124Z" }, + { url = "https://files.pythonhosted.org/packages/05/24/5e0c28f80371c17d49fed004597d9d132cb75c1f6f53db2cb95f459d2312/hf_xet-1.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:3474760d10e3bb6f92ff3f024fcb00c0b3e4001e9b035c7483e49a5dd17aa70f", size = 4069676, upload-time = "2026-06-08T23:02:26.759Z" }, + { url = "https://files.pythonhosted.org/packages/d2/17/261ba565b6a4d960fb478f61fdf919c0be5824645aaf1c319eca660c1611/hf_xet-1.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6762d89b9e3267dfd502b29b2a327b4525f33b17e7b509a78d94e2151a30ce30", size = 3838509, upload-time = "2026-06-08T23:02:28.573Z" }, + { url = "https://files.pythonhosted.org/packages/4e/44/7ffdc2e184b0d41fc0f683ba3936ef669ab63cf242cf36ef50e57d683668/hf_xet-1.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bf67e6ed10260cef62e852789dc91ebb03f382d5bdc4b1dbeb64763ea275e7d6", size = 4505881, upload-time = "2026-06-08T23:02:30.257Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/788060d5aa4d5e671f1a31bf69624c314eb2d8babab3aa562f9e5d53444e/hf_xet-1.5.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c6b6cd08ca095058780b50b8ce4d6cbf6787bcf27841705d58a9d32246e3e47a", size = 4292995, upload-time = "2026-06-08T23:02:31.993Z" }, + { url = "https://files.pythonhosted.org/packages/22/93/c5540cbd6b55529b7dc42f6734e88cebee21aefbea34128b66229df56c57/hf_xet-1.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e1af0de8ca6f190d4294a28b88023db64a1e2d1d719cab044baf75bec569e7a9", size = 4491570, upload-time = "2026-06-08T23:02:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/03/f3/9d8ceab30f44f36c1679b1b8683054c71a0dadc787dbf07421891742d3ca/hf_xet-1.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4f561cbbb92f80960772059864b7fb07eae879adde1b2e781ec6f86f6ac26c59", size = 4711565, upload-time = "2026-06-08T23:02:35.454Z" }, + { url = "https://files.pythonhosted.org/packages/cd/54/27ed9a5e2cc583b4df82f75a03a4df8dbf55f5a9fa1f47f1fadfb20dbeac/hf_xet-1.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:e7dbb40617410f432182d918e37c12303fe6700fd6aa6c5964e30a535a4461d6", size = 4017343, upload-time = "2026-06-08T23:02:37.14Z" }, + { url = "https://files.pythonhosted.org/packages/ae/12/ecb2fc8d45e767580e3a37faa97cb895608b614965567efb4f18cff67e27/hf_xet-1.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:6071d5ccb4d8d2cbd5fea5cc798da4f0ba3f44e25369591c4e89a4987050e61d", size = 3845716, upload-time = "2026-06-08T23:02:39.073Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d8/5e54cf37434759d1f4f2ba9b66077ff9d4c4e1f37b6bd7975da5c40d94ab/hf_xet-1.5.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6abd35c3221eff63836618ddfb954dcf84798603f71d8e33e3ed7b04acfdbe6e", size = 4077794, upload-time = "2026-06-08T23:02:40.656Z" }, + { url = "https://files.pythonhosted.org/packages/35/94/4b2ecfbad8f8b04701a23aefb62f540b9137d058b7e1dbef16a32676f0e9/hf_xet-1.5.1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:94e761bbd266bf4c03cee73753916062665ce8365aa40ed321f45afcb934b41e", size = 3845354, upload-time = "2026-06-08T23:02:42.702Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/f99f4bc7295023d7bd9ebbfd51f75cc530ca262c1227666268b8208f4b77/hf_xet-1.5.1-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:892e3a3a3aecc12aded8b93cf4f9cd059282c7de0732f7d55026f3abdf474350", size = 4514864, upload-time = "2026-06-08T23:02:44.497Z" }, + { url = "https://files.pythonhosted.org/packages/cd/6e/21f7e5a2381278bd3b7b7a5a4d90038518bb6308a0c1daf5d9f8268bb178/hf_xet-1.5.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a93df2039190502835b1db8cd7e178b0b7b889fe9ab51299d5ced26e0dd879a4", size = 4303784, upload-time = "2026-06-08T23:02:46.203Z" }, + { url = "https://files.pythonhosted.org/packages/35/0e/f992bb6927ac1cb30ef74e62268f551f338bc32b2191f7c96a44c6f7283e/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0c97106032ef70467b4f6bc2d0ccc266d7613ee076afc56516c502f87ce1c4a6", size = 4500703, upload-time = "2026-06-08T23:02:47.628Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d1/90a498d05447980b977b1669246eeeeae4cfb0ea3e7a286eaba627f91bf9/hf_xet-1.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6208adb15d192b90e4c2ad2a27ed864359b2cb0f2494eb6d7c7f3699ac02e2bf", size = 4719498, upload-time = "2026-06-08T23:02:49.268Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b6/20f99cfe97cc663a711f7b33cc21d4793e51968e9a26125b4afcd77315ba/hf_xet-1.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:f7b3002f95d1c13e24bcb4537baa8f0eb3838957067c91bb4959bc004a6435f5", size = 4026419, upload-time = "2026-06-08T23:02:50.829Z" }, + { url = "https://files.pythonhosted.org/packages/f9/fa/77453694888f03e5a8c8852d1514a0894d8e81c622d39edbaf308ea0dcf4/hf_xet-1.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:93d090b57b211133f6c0dab0205ef5cb6d89162979ba75a74845045cc3063b8e", size = 3855178, upload-time = "2026-06-08T23:02:52.452Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b9/be66eb0decd730d89b9c94f930e4b8d87787b05724bb84af98bfd825f72c/httptools-0.8.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:bf3b6f807c8541503cecfbb8a8dffb385640d0d96102f3d112aa8740f9b7c826", size = 208805, upload-time = "2026-05-25T22:16:50.434Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f7/b4d41eaae2869d31356bc4bbf546f44fae83ff298af0a043ca0625b06773/httptools-0.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da684f2e1aa2ee9bdcb083f3f3a68c5956750b375bc5df864d3a5f0c42a40b77", size = 113527, upload-time = "2026-05-25T22:16:51.672Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e4/77487e14fc7be47180fd0eb4267c7486d0cc59b74031839a3daf8650136b/httptools-0.8.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6f21e2a3b0067bbe7f67e34cfd16276af556e5e52f4c7503be0cb5f90e905e4", size = 450035, upload-time = "2026-05-25T22:16:53.313Z" }, + { url = "https://files.pythonhosted.org/packages/da/72/5a8f787e323f56fbd86c32a4be92a86776e4cfe8b4317db999f452028362/httptools-0.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea897f0c729581ebf72131a438a7932d9b14efef72d75ada966700cac3caaeb", size = 451101, upload-time = "2026-05-25T22:16:54.696Z" }, + { url = "https://files.pythonhosted.org/packages/ed/41/b44a25560955197674b6744cb903664300e239235a5eaa69df0890d87054/httptools-0.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c0d726cc107fceb7d45f978483b4b70dd8caa836f5914d3434bb18628eb73813", size = 436140, upload-time = "2026-05-25T22:16:56.239Z" }, + { url = "https://files.pythonhosted.org/packages/74/b0/054aac84c03d7e097bf4c605fb7e74eec3d65c0276adf64ee97f3a103ff5/httptools-0.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9878eb2785ba5eb70631ad269b37976f73d647955e26c91d490eb8a4edfda4ba", size = 437041, upload-time = "2026-05-25T22:16:57.716Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e8/86b85bbc0ac7892232f1a99ab96a9aa71936984fa06adfc0afc83ca7789e/httptools-0.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:b205e5f5523fa039679da0dfe5a10132b2a4abeae6a86fdd1ddc035f7f836557", size = 90454, upload-time = "2026-05-25T22:16:58.871Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d2/c3eedaef57de65c3cc5f8dc244cf12d09c84ad258a479055aad6db23206c/httptools-0.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed377e64805bdba4943c82717333f8f8603a13b09aff9cead2717c6c817fb168", size = 208428, upload-time = "2026-05-25T22:16:59.717Z" }, + { url = "https://files.pythonhosted.org/packages/f1/94/dfe435d90d0ef61ec0f2cc3d480eef78c59727c6c2ce039f433882f6131a/httptools-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9518c406d7b310f05adb1a37f80acabac40504a575d7c0da6d3e365c695ac20d", size = 113366, upload-time = "2026-05-25T22:17:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d4/13025f1a56e615dcb331e0bbe2d9a1143212b58c263385fc5d2e558f5bac/httptools-0.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57278e6fa0424c42a8a3e454828ab4f0aff27b40cddf9679579b98c6dce6a376", size = 464676, upload-time = "2026-05-25T22:17:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/bf/95/4c1c26c0b985f8a3331682d802598f14e32dc41bf7509266eb2c04ad4801/httptools-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbb8caadb2b742d293169d2b458b5c001ef70e3158704aa3d3ef9597624c5d1d", size = 464235, upload-time = "2026-05-25T22:17:03.109Z" }, + { url = "https://files.pythonhosted.org/packages/a2/82/6735be2b0ca527718c431cdb8e5f70c3862c0844a687df0f572c51e11497/httptools-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:52dd695b865fe96d9d2b16b64a895f3f57bf3cb064e8383cd3b5713a069e8085", size = 449809, upload-time = "2026-05-25T22:17:04.443Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f9/5811c74f37a758c8a4aa3dc430375119d335947e883efc4664d8f3559a41/httptools-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:20b4aac66ff65f7db06a375808b78f42a94970aa22e826b3cb2b43eb09174124", size = 452174, upload-time = "2026-05-25T22:17:05.476Z" }, + { url = "https://files.pythonhosted.org/packages/cc/94/97b75870dea07b71e3ec535cebe525b08d723152e4c7d13fa887e51f4de2/httptools-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1b4c8e7a489a0d750d91894e9a8cdc295838f1924c0ca903ae993456fddec07", size = 90991, upload-time = "2026-05-25T22:17:06.75Z" }, + { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, + { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, + { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huey" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/77/6ede51ab59bc7b35110e5817eeb47db03d0e5b4b716d6cff81a8cb379a3b/huey-3.1.1.tar.gz", hash = "sha256:156f30e90f0fae81ae2e004f2062e1b18ec9dcbc684564df63159ef546b13502", size = 281531, upload-time = "2026-07-02T22:10:46.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/3a/ee216ca4e7e4fa5bf1dbe7dd502f23981d02252d277d8ee74ea0afa8937f/huey-3.1.1-py3-none-any.whl", hash = "sha256:8773231c7bc7a40b9b9191d72cf1c6580bbf6742a4118c70dde6b5c2e7ccb7ce", size = 99335, upload-time = "2026-07-02T22:10:44.805Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/ea/dc54b4dda5841cb3a7812a178695be776e7c15c597887c2ed892f17d015a/huggingface_hub-1.22.0.tar.gz", hash = "sha256:e2dfe5fe1ec3b87ba2709aa34555b23e3f3f6ad4d7255238e13ddb8348e6bbfa", size = 914232, upload-time = "2026-07-03T09:46:44.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/9c/a1a377265abd8b823a2c661c665028ccb6b9fba1ca9d08e52ff679c20ecd/huggingface_hub-1.22.0-py3-none-any.whl", hash = "sha256:b09e19309ae09ee0a71892701c4fe70af39ab4e00817321dc62f2289a977249b", size = 765085, upload-time = "2026-07-03T09:46:42.832Z" }, +] + +[[package]] +name = "hyperopt" +version = "0.2.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "future" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "py4j" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "six" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/75/0c4712e3f3a21c910778b8f9f4622601a823cefcae24181467674a0352f9/hyperopt-0.2.7.tar.gz", hash = "sha256:1bf89ae58050bbd32c7307199046117feee245c2fd9ab6255c7308522b7ca149", size = 1308240, upload-time = "2021-11-17T10:05:51.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/cd/5b3334d39276067f54618ce0d0b48ed69d91352fbf137468c7095170d0e5/hyperopt-0.2.7-py2.py3-none-any.whl", hash = "sha256:f3046d91fe4167dbf104365016596856b2524a609d22f047a066fc1ac796427c", size = 1583421, upload-time = "2021-11-17T10:05:44.265Z" }, +] + +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "ijson" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/57/60d1a6a512f2f0508d0bc8b4f1cc5616fd3196619b66bd6a01f9155a1292/ijson-3.5.0.tar.gz", hash = "sha256:94688760720e3f5212731b3cb8d30267f9a045fb38fb3870254e7b9504246f31", size = 68658, upload-time = "2026-02-24T03:58:30.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/32/21c1b47a1afb7319944d0b9685c0997a9d574a77b030c82f6a1ac2cef4eb/ijson-3.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ea8dcac10d86adaeead454bc25c97b68d0bda573d5fd6f86f5e21cf8f7906f88", size = 88935, upload-time = "2026-02-24T03:56:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/86/f7/6ac7ebbb3cd767c87cdcbb950a6754afd1c0977756347bfe03eb8e5b866d/ijson-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:92b0495bbb2150bbf14fc5d98fb6d76bcd1c526605a172709e602e6fedc96495", size = 60567, upload-time = "2026-02-24T03:56:41.919Z" }, + { url = "https://files.pythonhosted.org/packages/c4/98/1140de9ae872468a8bc2e87c171228e25e58b1eb696b7fb430f7590fea44/ijson-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7af0c4c8943be8b09a4e57bdc1da6001dae7b36526d4154fe5c8224738d0921f", size = 60620, upload-time = "2026-02-24T03:56:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/60/e1/67dfe0774e4c7ca6ec8702e280e8764d356f3db54358999818cda6df7679/ijson-3.5.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:45887d5e84ff0d2b138c926cebd9071830733968afe8d9d12080b3c178c7f918", size = 126558, upload-time = "2026-02-24T03:56:43.922Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ef/23d614fc773d428caeb6e197218b7e32adcc668ff5b98777039149571208/ijson-3.5.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a70b575be8e57a28c80e90ed349ad3a851c3478524c70e36e07d6092ecd12c9", size = 133091, upload-time = "2026-02-24T03:56:45.291Z" }, + { url = "https://files.pythonhosted.org/packages/b8/80/99727603cd8a1d32edafa4392f4056b2420bf48c15afd34481c68a2d4435/ijson-3.5.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2adeecd45830bfd5580ca79a584154713aabef0b9607e16249133df5d2859813", size = 130249, upload-time = "2026-02-24T03:56:46.333Z" }, + { url = "https://files.pythonhosted.org/packages/0b/94/3a3d623ca80768e834be8a834ef05960e3b9e79af1a911704ff10c9e8792/ijson-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d873e72889e7fc5962ab58909f1adff338d7c2f49e450e5b5fe844eff8155a14", size = 133501, upload-time = "2026-02-24T03:56:47.54Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f6/df2c14ad340834eccee379046f155e4b66a16ddafd445429dee7b3323614/ijson-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9a88c559456a79708592234d697645d92b599718f4cbbeaa6515f83ac63ca0ae", size = 128438, upload-time = "2026-02-24T03:56:48.455Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7e/9ff5b8b5fee113f5607bc4149b707382a898eeb545153189b075e5ec8d59/ijson-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cf83f58ad50dc0d39a2105cb26d4f359b38f42cef68b913170d4d47d97d97ba5", size = 131116, upload-time = "2026-02-24T03:56:49.737Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/954ce0d440d7cf72a3d8361b14406f9cdbf624b1625c10f8488857c769d6/ijson-3.5.0-cp310-cp310-win32.whl", hash = "sha256:aec4580a7712a19b1f95cd41bed260fc6a31266d37ef941827772a4c199e8143", size = 52724, upload-time = "2026-02-24T03:56:50.932Z" }, + { url = "https://files.pythonhosted.org/packages/24/33/ece87d60502c6115642cbabeb8c122fa982212b392bc4f4ff5aab8e02dac/ijson-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a9c4c70501e23e8eb1675330686d1598eebfa14b6f0dbc8f00c2e081cc628fa", size = 55125, upload-time = "2026-02-24T03:56:51.942Z" }, + { url = "https://files.pythonhosted.org/packages/65/da/644343198abca5e0f6e2486063f8d8f3c443ca0ef5e5c890e51ef6032e33/ijson-3.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5616311404b858d32740b7ad8b9a799c62165f5ecb85d0a8ed16c21665a90533", size = 88964, upload-time = "2026-02-24T03:56:53.099Z" }, + { url = "https://files.pythonhosted.org/packages/5b/63/8621190aa2baf96156dfd4c632b6aa9f1464411e50b98750c09acc0505ea/ijson-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9733f94029dd41702d573ef64752e2556e72aea14623d6dbb7a44ca1ccf30fd", size = 60582, upload-time = "2026-02-24T03:56:54.261Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/6a3f041fdd17dacff33b7d7d3ba3df6dca48740108340c6042f974b2ad20/ijson-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db8398c6721b98412a4f618da8022550c8b9c5d9214040646071b5deb4d4a393", size = 60632, upload-time = "2026-02-24T03:56:55.159Z" }, + { url = "https://files.pythonhosted.org/packages/e4/68/474541998abbdecfd46a744536878335de89aceb9f085bff1aaf35575ceb/ijson-3.5.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c061314845c08163b1784b6076ea5f075372461a32e6916f4e5f211fd4130b64", size = 131988, upload-time = "2026-02-24T03:56:56.35Z" }, + { url = "https://files.pythonhosted.org/packages/cd/32/e05ff8b72a44fe9d192f41c5dcbc35cfa87efc280cdbfe539ffaf4a7535e/ijson-3.5.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1111a1c5ac79119c5d6e836f900c1a53844b50a18af38311baa6bb61e2645aca", size = 138669, upload-time = "2026-02-24T03:56:57.555Z" }, + { url = "https://files.pythonhosted.org/packages/49/b5/955a83b031102c7a602e2c06d03aff0a0e584212f09edb94ccc754d203ac/ijson-3.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e74aff8c681c24002b61b1822f9511d4c384f324f7dbc08c78538e01fdc9fcb", size = 135093, upload-time = "2026-02-24T03:56:59.267Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f2/30250cfcb4d2766669b31f6732689aab2bb91de426a15a3ebe482df7ee48/ijson-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:739a7229b1b0cc5f7e2785a6e7a5fc915e850d3fed9588d0e89a09f88a417253", size = 138715, upload-time = "2026-02-24T03:57:00.491Z" }, + { url = "https://files.pythonhosted.org/packages/a2/05/785a145d7e75e04e04480d59b6323cd4b1d9013a6cd8643fa635fbc93490/ijson-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ef88712160360cab3ca6471a4e5418243f8b267cf1fe1620879d1b5558babc71", size = 133194, upload-time = "2026-02-24T03:57:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/14/eb/80d6f8a748dead4034cea0939494a67d10ccf88d6413bf6e860393139676/ijson-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ca0d1b6b5f8166a6248f4309497585fb8553b04bc8179a0260fad636cfdb798", size = 135588, upload-time = "2026-02-24T03:57:03.131Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a8/bbc21f9400ebdbca48fab272593e0d1f875691be1e927d264d90d48b8c47/ijson-3.5.0-cp311-cp311-win32.whl", hash = "sha256:966039cf9047c7967febf7b9a52ec6f38f5464a4c7fbb5565e0224b7376fefff", size = 52721, upload-time = "2026-02-24T03:57:04.365Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2e/4e8c0208b8f920ee80c88c956f93e78318f2cfb646455353b182738b490c/ijson-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bad6a1634cb7c9f3f4c7e52325283b35b565f5b6cc27d42660c6912ce883422", size = 55121, upload-time = "2026-02-24T03:57:05.498Z" }, + { url = "https://files.pythonhosted.org/packages/aa/17/9c63c7688025f3a8c47ea717b8306649c8c7244e49e20a2be4e3515dc75c/ijson-3.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1ebefbe149a6106cc848a3eaf536af51a9b5ccc9082de801389f152dba6ab755", size = 88536, upload-time = "2026-02-24T03:57:06.809Z" }, + { url = "https://files.pythonhosted.org/packages/6f/dd/e15c2400244c117b06585452ebc63ae254f5a6964f712306afd1422daae0/ijson-3.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:19e30d9f00f82e64de689c0b8651b9cfed879c184b139d7e1ea5030cec401c21", size = 60499, upload-time = "2026-02-24T03:57:09.155Z" }, + { url = "https://files.pythonhosted.org/packages/77/a9/bf4fe3538a0c965f16b406f180a06105b875da83f0743e36246be64ef550/ijson-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a04a33ee78a6f27b9b8528c1ca3c207b1df3b8b867a4cf2fcc4109986f35c227", size = 60330, upload-time = "2026-02-24T03:57:10.574Z" }, + { url = "https://files.pythonhosted.org/packages/31/76/6f91bdb019dd978fce1bc5ea1cd620cfc096d258126c91db2c03a20a7f34/ijson-3.5.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d48dc2984af02eb3c56edfb3f13b3f62f2f3e4fe36f058c8cfc75d93adf4fed", size = 138977, upload-time = "2026-02-24T03:57:11.932Z" }, + { url = "https://files.pythonhosted.org/packages/11/be/bbc983059e48a54b0121ee60042979faed7674490bbe7b2c41560db3f436/ijson-3.5.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1e73a44844d9adbca9cf2c4132cd875933e83f3d4b23881fcaf82be83644c7d", size = 149785, upload-time = "2026-02-24T03:57:13.255Z" }, + { url = "https://files.pythonhosted.org/packages/6d/81/2fee58f9024a3449aee83edfa7167fb5ccd7e1af2557300e28531bb68e16/ijson-3.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7389a56b8562a19948bdf1d7bae3a2edc8c7f86fb59834dcb1c4c722818e645a", size = 149729, upload-time = "2026-02-24T03:57:14.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/56/f1706761fcc096c9d414b3dcd000b1e6e5c24364c21cfba429837f98ee8d/ijson-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3176f23f8ebec83f374ed0c3b4e5a0c4db7ede54c005864efebbed46da123608", size = 150697, upload-time = "2026-02-24T03:57:15.855Z" }, + { url = "https://files.pythonhosted.org/packages/d9/6e/ee0d9c875a0193b632b3e9ccd1b22a50685fb510256ad57ba483b6529f77/ijson-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6babd88e508630c6ef86c9bebaaf13bb2fb8ec1d8f8868773a03c20253f599bc", size = 142873, upload-time = "2026-02-24T03:57:16.831Z" }, + { url = "https://files.pythonhosted.org/packages/d2/bf/f9d4399d0e6e3fd615035290a71e97c843f17f329b43638c0a01cf112d73/ijson-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dc1b3836b174b6db2fa8319f1926fb5445abd195dc963368092103f8579cb8ed", size = 151583, upload-time = "2026-02-24T03:57:17.757Z" }, + { url = "https://files.pythonhosted.org/packages/b2/71/a7254a065933c0e2ffd3586f46187d84830d3d7b6f41cfa5901820a4f87d/ijson-3.5.0-cp312-cp312-win32.whl", hash = "sha256:6673de9395fb9893c1c79a43becd8c8fbee0a250be6ea324bfd1487bb5e9ee4c", size = 53079, upload-time = "2026-02-24T03:57:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7b/2edca79b359fc9f95d774616867a03ecccdf333797baf5b3eea79733918c/ijson-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f4f7fabd653459dcb004175235f310435959b1bb5dfa8878578391c6cc9ad944", size = 55500, upload-time = "2026-02-24T03:57:20.428Z" }, + { url = "https://files.pythonhosted.org/packages/a2/71/d67e764a712c3590627480643a3b51efcc3afa4ef3cb54ee4c989073c97e/ijson-3.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e9cedc10e40dd6023c351ed8bfc7dcfce58204f15c321c3c1546b9c7b12562a4", size = 88544, upload-time = "2026-02-24T03:57:21.293Z" }, + { url = "https://files.pythonhosted.org/packages/1a/39/f1c299371686153fa3cf5c0736b96247a87a1bee1b7145e6d21f359c505a/ijson-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3647649f782ee06c97490b43680371186651f3f69bebe64c6083ee7615d185e5", size = 60495, upload-time = "2026-02-24T03:57:22.501Z" }, + { url = "https://files.pythonhosted.org/packages/16/94/b1438e204d75e01541bebe3e668fe3e68612d210e9931ae1611062dd0a56/ijson-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90e74be1dce05fce73451c62d1118671f78f47c9f6be3991c82b91063bf01fc9", size = 60325, upload-time = "2026-02-24T03:57:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/30/e2/4aa9c116fa86cc8b0f574f3c3a47409edc1cd4face05d0e589a5a176b05d/ijson-3.5.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78e9ad73e7be2dd80627504bd5cbf512348c55ce2c06e362ed7683b5220e8568", size = 138774, upload-time = "2026-02-24T03:57:24.683Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d2/738b88752a70c3be1505faa4dcd7110668c2712e582a6a36488ed1e295d4/ijson-3.5.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9577449313cc94be89a4fe4b3e716c65f09cc19636d5a6b2861c4e80dddebd58", size = 149820, upload-time = "2026-02-24T03:57:26.062Z" }, + { url = "https://files.pythonhosted.org/packages/ed/df/0b3ab9f393ca8f72ea03bc896ba9fdc987e90ae08cdb51c32a4ee0c14d5e/ijson-3.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e4c1178fb50aff5f5701a30a5152ead82a14e189ce0f6102fa1b5f10b2f54ff", size = 149747, upload-time = "2026-02-24T03:57:27.308Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a3/b0037119f75131b78cb00acc2657b1a9d0435475f1f2c5f8f5a170b66b9c/ijson-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0eb402ab026ffb37a918d75af2b7260fe6cfbce13232cc83728a714dd30bd81d", size = 151027, upload-time = "2026-02-24T03:57:28.522Z" }, + { url = "https://files.pythonhosted.org/packages/22/a0/cb344de1862bf09d8f769c9d25c944078c87dd59a1b496feec5ad96309a4/ijson-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5b08ee08355f9f729612a8eb9bf69cc14f9310c3b2a487c6f1c3c65d85216ec4", size = 142996, upload-time = "2026-02-24T03:57:29.774Z" }, + { url = "https://files.pythonhosted.org/packages/ca/32/a8ffd67182e02ea61f70f62daf43ded4fa8a830a2520a851d2782460aba8/ijson-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bda62b6d48442903e7bf56152108afb7f0f1293c2b9bef2f2c369defea76ab18", size = 152068, upload-time = "2026-02-24T03:57:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d1/3578df8e75d446aab0ae92e27f641341f586b85e1988536adebc65300cb4/ijson-3.5.0-cp313-cp313-win32.whl", hash = "sha256:8d073d9b13574cfa11083cc7267c238b7a6ed563c2661e79192da4a25f09c82c", size = 53065, upload-time = "2026-02-24T03:57:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a2/f7cdaf5896710da3e69e982e44f015a83d168aa0f3a89b6f074b5426779d/ijson-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:2419f9e32e0968a876b04d8f26aeac042abd16f582810b576936bbc4c6015069", size = 55499, upload-time = "2026-02-24T03:57:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/13e2492d17e19a2084523e18716dc2809159f2287fd2700c735f311e76c4/ijson-3.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4d4b0cd676b8c842f7648c1a783448fac5cd3b98289abd83711b3e275e143524", size = 93019, upload-time = "2026-02-24T03:57:33.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/92/483fc97ece0c3f1cecabf48f6a7a36e89d19369eec462faaeaa34c788992/ijson-3.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:252dec3680a48bb82d475e36b4ae1b3a9d7eb690b951bb98a76c5fe519e30188", size = 62714, upload-time = "2026-02-24T03:57:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/4b/88/793fe020a0fe9d9eed4c285cf4a5cfdb0a935708b3bde0d72f35c794b513/ijson-3.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:aa1b5dca97d323931fde2501172337384c958914d81a9dac7f00f0d4bfc76bc7", size = 62460, upload-time = "2026-02-24T03:57:35.874Z" }, + { url = "https://files.pythonhosted.org/packages/51/69/f1a2690aa8d4df1f4e262b385e65a933ffdc250b091531bac9a449c19e16/ijson-3.5.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7a5ec7fd86d606094bba6f6f8f87494897102fa4584ef653f3005c51a784c320", size = 199273, upload-time = "2026-02-24T03:57:37.07Z" }, + { url = "https://files.pythonhosted.org/packages/ea/a2/f1346d5299e79b988ab472dc773d5381ec2d57c23cb2f1af3ede4a810e62/ijson-3.5.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:009f41443e1521847701c6d87fa3923c0b1961be3c7e7de90947c8cb92ea7c44", size = 216884, upload-time = "2026-02-24T03:57:38.346Z" }, + { url = "https://files.pythonhosted.org/packages/28/3c/8b637e869be87799e6c2c3c275a30a546f086b1aed77e2b7f11512168c5a/ijson-3.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4c3651d1f9fe2839a93fdf8fd1d5ca3a54975349894249f3b1b572bcc4bd577", size = 207306, upload-time = "2026-02-24T03:57:39.718Z" }, + { url = "https://files.pythonhosted.org/packages/7f/7c/18b1c1df6951ca056782d7580ec40cea4ff9a27a0947d92640d1cc8c4ae3/ijson-3.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:945b7abcfcfeae2cde17d8d900870f03536494245dda7ad4f8d056faa303256c", size = 211364, upload-time = "2026-02-24T03:57:40.953Z" }, + { url = "https://files.pythonhosted.org/packages/f3/55/e795812e82851574a9dba8a53fde045378f531ef14110c6fb55dbd23b443/ijson-3.5.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0574b0a841ff97495c13e9d7260fbf3d85358b061f540c52a123db9dbbaa2ed6", size = 200608, upload-time = "2026-02-24T03:57:42.272Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cd/013c85b4749b57a4cb4c2670014d1b32b8db4ab1a7be92ea7aeb5d7fe7b5/ijson-3.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f969ffb2b89c5cdf686652d7fb66252bc72126fa54d416317411497276056a18", size = 205127, upload-time = "2026-02-24T03:57:43.286Z" }, + { url = "https://files.pythonhosted.org/packages/0e/7c/faf643733e3ab677f180018f6a855c4ef70b7c46540987424c563c959e42/ijson-3.5.0-cp313-cp313t-win32.whl", hash = "sha256:59d3f9f46deed1332ad669518b8099920512a78bda64c1f021fcd2aff2b36693", size = 55282, upload-time = "2026-02-24T03:57:44.353Z" }, + { url = "https://files.pythonhosted.org/packages/69/22/94ddb47c24b491377aca06cd8fc9202cad6ab50619842457d2beefde21ea/ijson-3.5.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c2839fa233746d8aad3b8cd2354e441613f5df66d721d59da4a09394bd1db2b", size = 58016, upload-time = "2026-02-24T03:57:45.237Z" }, + { url = "https://files.pythonhosted.org/packages/7a/93/0868efe753dc1df80cc405cf0c1f2527a6991643607c741bff8dcb899b3b/ijson-3.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:25a5a6b2045c90bb83061df27cfa43572afa43ba9408611d7bfe237c20a731a9", size = 89094, upload-time = "2026-02-24T03:57:46.115Z" }, + { url = "https://files.pythonhosted.org/packages/24/94/fd5a832a0df52ef5e4e740f14ac8640725d61034a1b0c561e8b5fb424706/ijson-3.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8976c54c0b864bc82b951bae06567566ac77ef63b90a773a69cd73aab47f4f4f", size = 60715, upload-time = "2026-02-24T03:57:47.552Z" }, + { url = "https://files.pythonhosted.org/packages/70/79/1b9a90af5732491f9eec751ee211b86b11011e1158c555c06576d52c3919/ijson-3.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:859eb2038f7f1b0664df4241957694cc35e6295992d71c98659b22c69b3cbc10", size = 60638, upload-time = "2026-02-24T03:57:48.428Z" }, + { url = "https://files.pythonhosted.org/packages/23/6f/2c551ea980fe56f68710a8d5389cfbd015fc45aaafd17c3c52c346db6aa1/ijson-3.5.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c911aa02991c7c0d3639b6619b93a93210ff1e7f58bf7225d613abea10adc78e", size = 140667, upload-time = "2026-02-24T03:57:49.314Z" }, + { url = "https://files.pythonhosted.org/packages/25/0e/27b887879ba6a5bc29766e3c5af4942638c952220fd63e1e442674f7883a/ijson-3.5.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:903cbdc350173605220edc19796fbea9b2203c8b3951fb7335abfa8ed37afda8", size = 149850, upload-time = "2026-02-24T03:57:50.329Z" }, + { url = "https://files.pythonhosted.org/packages/da/1e/23e10e1bc04bf31193b21e2960dce14b17dbd5d0c62204e8401c59d62c08/ijson-3.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4549d96ded5b8efa71639b2160235415f6bdb8c83367615e2dbabcb72755c33", size = 149206, upload-time = "2026-02-24T03:57:51.261Z" }, + { url = "https://files.pythonhosted.org/packages/8e/90/e552f6495063b235cf7fa2c592f6597c057077195e517b842a0374fd470c/ijson-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b2dcf6349e6042d83f3f8c39ce84823cf7577eba25bac5aae5e39bbbbbe9c1c", size = 150438, upload-time = "2026-02-24T03:57:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/5c/18/45bf8f297c41b42a1c231d261141097babd953d2c28a07be57ae4c3a1a02/ijson-3.5.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e44af39e6f8a17e5627dcd89715d8279bf3474153ff99aae031a936e5c5572e5", size = 144369, upload-time = "2026-02-24T03:57:53.22Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3a/deb9772bb2c0cead7ad64f00c3598eec9072bdf511818e70e2c512eeabbe/ijson-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9260332304b7e7828db56d43f08fc970a3ab741bf84ff10189361ea1b60c395b", size = 151352, upload-time = "2026-02-24T03:57:54.375Z" }, + { url = "https://files.pythonhosted.org/packages/e4/51/67f4d80cd58ad7eab0cd1af5fe28b961886338956b2f88c0979e21914346/ijson-3.5.0-cp314-cp314-win32.whl", hash = "sha256:63bc8121bb422f6969ced270173a3fa692c29d4ae30c860a2309941abd81012a", size = 53610, upload-time = "2026-02-24T03:57:55.655Z" }, + { url = "https://files.pythonhosted.org/packages/70/d3/263672ea22983ba3940f1534316dbc9200952c1c2a2332d7a664e4eaa7ae/ijson-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:01b6dad72b7b7df225ef970d334556dfad46c696a2c6767fb5d9ed8889728bca", size = 56301, upload-time = "2026-02-24T03:57:56.584Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d9/86f7fac35e0835faa188085ae0579e813493d5261ce056484015ad533445/ijson-3.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2ea4b676ec98e374c1df400a47929859e4fa1239274339024df4716e802aa7e4", size = 93069, upload-time = "2026-02-24T03:57:57.849Z" }, + { url = "https://files.pythonhosted.org/packages/33/d2/e7366ed9c6e60228d35baf4404bac01a126e7775ea8ce57f560125ed190a/ijson-3.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:014586eec043e23c80be9a923c56c3a0920a0f1f7d17478ce7bc20ba443968ef", size = 62767, upload-time = "2026-02-24T03:57:58.758Z" }, + { url = "https://files.pythonhosted.org/packages/35/8b/3e703e8cc4b3ada79f13b28070b51d9550c578f76d1968657905857b2ddd/ijson-3.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5b8b886b0248652d437f66e7c5ac318bbdcb2c7137a7e5327a68ca00b286f5f", size = 62467, upload-time = "2026-02-24T03:58:00.261Z" }, + { url = "https://files.pythonhosted.org/packages/21/42/0c91af32c1ee8a957fdac2e051b5780756d05fd34e4b60d94a08d51bac1d/ijson-3.5.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:498fd46ae2349297e43acf97cdc421e711dbd7198418677259393d2acdc62d78", size = 200447, upload-time = "2026-02-24T03:58:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/80/796ea0e391b7e2d45c5b1b451734bba03f81c2984cf955ea5eaa6c4920ad/ijson-3.5.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22a51b4f9b81f12793731cf226266d1de2112c3c04ba4a04117ad4e466897e05", size = 217820, upload-time = "2026-02-24T03:58:02.598Z" }, + { url = "https://files.pythonhosted.org/packages/38/14/52b6613fdda4078c62eb5b4fe3efc724ddc55a4ad524c93de51830107aa3/ijson-3.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9636c710dc4ac4a281baa266a64f323b4cc165cec26836af702c44328b59a515", size = 208310, upload-time = "2026-02-24T03:58:04.759Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ad/8b3105a78774fd4a65e534a21d975ef3a77e189489fe3029ebcaeba5e243/ijson-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f7168a39e8211107666d71b25693fd1b2bac0b33735ef744114c403c6cac21e1", size = 211843, upload-time = "2026-02-24T03:58:05.836Z" }, + { url = "https://files.pythonhosted.org/packages/36/ab/a2739f6072d6e1160581bc3ed32da614c8cced023dcd519d9c5fa66e0425/ijson-3.5.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8696454245415bc617ab03b0dc3ae4c86987df5dc6a90bad378fe72c5409d89e", size = 200906, upload-time = "2026-02-24T03:58:07.788Z" }, + { url = "https://files.pythonhosted.org/packages/6d/5e/e06c2de3c3d4a9cfb655c1ad08a68fb72838d271072cdd3196576ac4431a/ijson-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c21bfb61f71f191565885bf1bc29e0a186292d866b4880637b833848360bdc1b", size = 205495, upload-time = "2026-02-24T03:58:09.163Z" }, + { url = "https://files.pythonhosted.org/packages/7c/11/778201eb2e202ddd76b36b0fb29bf3d8e3c167389d8aa883c62524e49f47/ijson-3.5.0-cp314-cp314t-win32.whl", hash = "sha256:a2619460d6795b70d0155e5bf016200ac8a63ab5397aa33588bb02b6c21759e6", size = 56280, upload-time = "2026-02-24T03:58:10.116Z" }, + { url = "https://files.pythonhosted.org/packages/23/28/96711503245339084c8086b892c47415895eba49782d6cc52d9f4ee50301/ijson-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4f24b78d4ef028d17eb57ad1b16c0aed4a17bdd9badbf232dc5d9305b7e13854", size = 58965, upload-time = "2026-02-24T03:58:11.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3b/d31ecfa63a218978617446159f3d77aab2417a5bd2885c425b176353ff78/ijson-3.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d64c624da0e9d692d6eb0ff63a79656b59d76bf80773a17c5b0f835e4e8ef627", size = 57715, upload-time = "2026-02-24T03:58:24.545Z" }, + { url = "https://files.pythonhosted.org/packages/30/51/b170e646d378e8cccf9637c05edb5419b00c2c4df64b0258c3af5355608e/ijson-3.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:876f7df73b7e0d6474f9caa729b9cdbfc8e76de9075a4887dfd689e29e85c4ca", size = 57205, upload-time = "2026-02-24T03:58:25.681Z" }, + { url = "https://files.pythonhosted.org/packages/ef/83/44dbd0231b0a8c6c14d27473d10c4e27dfbce7d5d9a833c79e3e6c33eb40/ijson-3.5.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e7dbff2c8d9027809b0cde663df44f3210da10ea377121d42896fb6ee405dd31", size = 71229, upload-time = "2026-02-24T03:58:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/cf84048b7c6cec888826e696a31f45bee7ebcac15e532b6be1fc4c2c9608/ijson-3.5.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4217a1edc278660679e1197c83a1a2a2d367792bfbb2a3279577f4b59b93730d", size = 71217, upload-time = "2026-02-24T03:58:28.021Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0a/e34c729a87ff67dc6540f6bcc896626158e691d433ab57db0086d73decd2/ijson-3.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04f0fc740311388ee745ba55a12292b722d6f52000b11acbb913982ba5fbdf87", size = 68618, upload-time = "2026-02-24T03:58:28.918Z" }, + { url = "https://files.pythonhosted.org/packages/c1/0f/e849d072f2e0afe49627de3995fc9dae54b4c804c70c0840f928d95c10e1/ijson-3.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fdeee6957f92e0c114f65c55cf8fe7eabb80cfacab64eea6864060913173f66d", size = 55369, upload-time = "2026-02-24T03:58:29.839Z" }, +] + +[[package]] +name = "imageio" +version = "2.37.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" }, +] + +[[package]] +name = "imagesize" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" }, +] + +[[package]] +name = "imbalanced-learn" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sklearn-compat" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/35/d12fc1e8e2c2d8862104c4527641fe2d839324c50db0e6340dc73513faba/imbalanced_learn-0.14.2.tar.gz", hash = "sha256:f80ce7eafbcece8686e32571bd12978546c729c3f277215bead61a906ce9afe4", size = 19172446, upload-time = "2026-06-07T21:41:16.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/54/760ccac7d8feeea0c191767a6b025d3ca5014084443ff48afb6fb24ef056/imbalanced_learn-0.14.2-py3-none-any.whl", hash = "sha256:f9b81c47231aa1e3a71a1e4b3cc85b42e3b14f85e3a36922f3323c4da23605ef", size = 236073, upload-time = "2026-06-07T21:41:12.384Z" }, +] + +[[package]] +name = "imblearn" +version = "0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imbalanced-learn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/0a/f83099534a77757abf27427d339590c83cc68c3386690d4741d6454e185f/imblearn-0.0.tar.gz", hash = "sha256:d8fbb662919c1b16f438ad91a8256220e53bcf6815c9ad5502c518b798de34f2", size = 945, upload-time = "2017-01-19T11:52:35.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/a7/4179e6ebfd654bd0eac0b9c06125b8b4c96a9d0a8ff9e9507eb2a26d2d7e/imblearn-0.0-py2.py3-none-any.whl", hash = "sha256:d42c2d709d22c00d2b9a91e638d57240a8b79b4014122d92181fcd2549a2f79a", size = 1874, upload-time = "2017-01-19T11:52:37.416Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "9.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl", hash = "sha256:2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7", size = 27789, upload-time = "2026-03-20T06:42:55.665Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "iso8601" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/f3/ef59cee614d5e0accf6fd0cbba025b93b272e626ca89fb70a3e9187c5d15/iso8601-2.1.0.tar.gz", hash = "sha256:6b1d3829ee8921c4301998c909f7829fa9ed3cbdac0d3b16af2d743aed1ba8df", size = 6522, upload-time = "2023-10-03T00:25:39.317Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/0c/f37b6a241f0759b7653ffa7213889d89ad49a2b76eb2ddf3b57b2738c347/iso8601-2.1.0-py3-none-any.whl", hash = "sha256:aac4145c4dcb66ad8b648a02830f5e2ff6c24af20f4f482689be402db2429242", size = 7545, upload-time = "2023-10-03T00:25:32.304Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "kink" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/9b/71623fd68aabf7ee785e2a4032dbccc5121d7e27db0d72125739d1ce602b/kink-0.9.0.tar.gz", hash = "sha256:febe96b17f5e071858595ed536cc046fd0377596db296137773e4eeaa344800e", size = 13269, upload-time = "2026-03-19T08:26:54.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/c4/1c8fa2e8480c846bd9df3b39c53f987ae85575604acf8cd9373a5c860ae4/kink-0.9.0-py3-none-any.whl", hash = "sha256:ae4a7f644ed94ecaf94517d3c95b0f31332e11b9941e90a3f72898067020a494", size = 11627, upload-time = "2026-03-19T08:26:55.837Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/f8/06549565caa026e540b7e7bab5c5a90eb7ca986015f4c48dace243cd24d9/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32cc0a5365239a6ea0c6ed461e8838d053b57e397443c0ca894dcc8e388d4374", size = 122802, upload-time = "2026-03-09T13:12:37.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/eb/8476a0818850c563ff343ea7c9c05dcdcbd689a38e01aa31657df01f91fa/kiwisolver-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cc0b66c1eec9021353a4b4483afb12dfd50e3669ffbb9152d6842eb34c7e29fd", size = 66216, upload-time = "2026-03-09T13:12:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/f9c8a6b4c21aed4198566e45923512986d6cef530e7263b3a5f823546561/kiwisolver-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86e0287879f75621ae85197b0877ed2f8b7aa57b511c7331dce2eb6f4de7d476", size = 63917, upload-time = "2026-03-09T13:12:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/f1/0e/ba4ae25d03722f64de8b2c13e80d82ab537a06b30fc7065183c6439357e3/kiwisolver-1.5.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:62f59da443c4f4849f73a51a193b1d9d258dcad0c41bc4d1b8fb2bcc04bfeb22", size = 1628776, upload-time = "2026-03-09T13:12:41.976Z" }, + { url = "https://files.pythonhosted.org/packages/8a/e4/3f43a011bc8a0860d1c96f84d32fa87439d3feedf66e672fef03bf5e8bac/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9190426b7aa26c5229501fa297b8d0653cfd3f5a36f7990c264e157cbf886b3b", size = 1228164, upload-time = "2026-03-09T13:12:44.002Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/3a901559a1e0c218404f9a61a93be82d45cb8f44453ba43088644980f033/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c8277104ded0a51e699c8c3aff63ce2c56d4ed5519a5f73e0fd7057f959a2b9e", size = 1246656, upload-time = "2026-03-09T13:12:45.557Z" }, + { url = "https://files.pythonhosted.org/packages/87/9e/f78c466ea20527822b95ad38f141f2de1dcd7f23fb8716b002b0d91bbe59/kiwisolver-1.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f9baf6f0a6e7571c45c8863010b45e837c3ee1c2c77fcd6ef423be91b21fedb", size = 1295562, upload-time = "2026-03-09T13:12:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/0a/66/fd0e4a612e3a286c24e6d6f3a5428d11258ed1909bc530ba3b59807fd980/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cff8e5383db4989311f99e814feeb90c4723eb4edca425b9d5d9c3fefcdd9537", size = 2178473, upload-time = "2026-03-09T13:12:50.254Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8e/6cac929e0049539e5ee25c1ee937556f379ba5204840d03008363ced662d/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ebae99ed6764f2b5771c522477b311be313e8841d2e0376db2b10922daebbba4", size = 2274035, upload-time = "2026-03-09T13:12:51.785Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d3/9d0c18f1b52ea8074b792452cf17f1f5a56bd0302a85191f405cfbf9da16/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:d5cd5189fc2b6a538b75ae45433140c4823463918f7b1617c31e68b085c0022c", size = 2443217, upload-time = "2026-03-09T13:12:53.329Z" }, + { url = "https://files.pythonhosted.org/packages/45/2a/6e19368803a038b2a90857bf4ee9e3c7b667216d045866bf22d3439fd75e/kiwisolver-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f42c23db5d1521218a3276bb08666dcb662896a0be7347cba864eca45ff64ede", size = 2249196, upload-time = "2026-03-09T13:12:55.057Z" }, + { url = "https://files.pythonhosted.org/packages/75/2b/3f641dfcbe72e222175d626bacf2f72c3b34312afec949dd1c50afa400f5/kiwisolver-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:94eff26096eb5395136634622515b234ecb6c9979824c1f5004c6e3c3c85ccd2", size = 73389, upload-time = "2026-03-09T13:12:56.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/88/299b137b9e0025d8982e03d2d52c123b0a2b159e84b0ef1501ef446339cf/kiwisolver-1.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:dd952e03bfbb096cfe2dd35cd9e00f269969b67536cb4370994afc20ff2d0875", size = 64782, upload-time = "2026-03-09T13:12:57.609Z" }, + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/17/6f/6fd4f690a40c2582fa34b97d2678f718acf3706b91d270c65ecb455d0a06/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:295d9ffe712caa9f8a3081de8d32fc60191b4b51c76f02f951fd8407253528f4", size = 59606, upload-time = "2026-03-09T13:15:40.81Z" }, + { url = "https://files.pythonhosted.org/packages/82/a0/2355d5e3b338f13ce63f361abb181e3b6ea5fffdb73f739b3e80efa76159/kiwisolver-1.5.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:51e8c4084897de9f05898c2c2a39af6318044ae969d46ff7a34ed3f96274adca", size = 57537, upload-time = "2026-03-09T13:15:42.071Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/1d50e610ecadebe205b71d6728fd224ce0e0ca6aba7b9cbe1da049203ac5/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b83af57bdddef03c01a9138034c6ff03181a3028d9a1003b301eb1a55e161a3f", size = 79888, upload-time = "2026-03-09T13:15:43.317Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ee/b85ffcd75afed0357d74f0e6fc02a4507da441165de1ca4760b9f496390d/kiwisolver-1.5.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf4679a3d71012a7c2bf360e5cd878fbd5e4fcac0896b56393dec239d81529ed", size = 77584, upload-time = "2026-03-09T13:15:44.605Z" }, + { url = "https://files.pythonhosted.org/packages/6b/dd/644d0dde6010a8583b4cd66dd41c5f83f5325464d15c4f490b3340ab73b4/kiwisolver-1.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:41024ed50e44ab1a60d3fe0a9d15a4ccc9f5f2b1d814ff283c8d01134d5b81bc", size = 73390, upload-time = "2026-03-09T13:15:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + +[[package]] +name = "lazy-loader" +version = "0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, +] + +[[package]] +name = "liac-arff" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/43/73944aa5ad2b3185c0f0ba0ee6f73277f2eb51782ca6ccf3e6793caf209a/liac-arff-2.5.0.tar.gz", hash = "sha256:3220d0af6487c5aa71b47579be7ad1d94f3849ff1e224af3bf05ad49a0b5c4da", size = 13358, upload-time = "2020-08-31T18:59:16.878Z" } + +[[package]] +name = "librt" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/e0/dbd0f2a68a1c1a1991eb7921ff6014465d56608cdc9a9fb468a616210a37/librt-0.12.0.tar.gz", hash = "sha256:cb26faedbd09c6130e9c1b64d8000efec5076ffd18d606c6cd1cf02730e6d8b0", size = 203841, upload-time = "2026-06-30T16:14:29.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/66/c9d88366893b4b0df6b5375c27ebc9f14c43419d9e244b493be20e85bc74/librt-0.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fe3547407bbce45c09885591f90168325c5a31a6795b9a13f6b9ff3d25093d93", size = 144398, upload-time = "2026-06-30T16:12:03.947Z" }, + { url = "https://files.pythonhosted.org/packages/bd/f2/9be1c6da204701163ec3aaedbf893d2f656b363d8fa302af536ce6471eb4/librt-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5925eca673207204a3adca040a91bdd3738fc7ba48da647ccd55732692a35736", size = 148924, upload-time = "2026-06-30T16:12:05.583Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f3/256824ee27649c6e0a693db25d391f97b43b52364f8efb466014a564bbc7/librt-0.12.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f9ef097a7711465a204454c69658bbb6b2a6be9bdef0eeeba9a042016d00688", size = 479654, upload-time = "2026-06-30T16:12:07.175Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3f/f4adbb3f293a04bd3dc2eb91d814f5b1e221e6b4522585696ba6901a0b9a/librt-0.12.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:57abc8b65edf1a8e80e5472c81c108a7527202e5febfda9e00a684dbaeae534e", size = 472318, upload-time = "2026-06-30T16:12:08.758Z" }, + { url = "https://files.pythonhosted.org/packages/9d/b5/362c93f7b43d4ef84a3d5f156c8d4eeddb22badcf5529a1281c387abbbd7/librt-0.12.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e6f53732a8ae5012a3b6ae092da2933be74ec4169d16038f4af87a0019afea", size = 501555, upload-time = "2026-06-30T16:12:10.623Z" }, + { url = "https://files.pythonhosted.org/packages/24/1d/2d6abf059c3a4b88a6668e7bb81af332b14463028ac8f2b08a1212eb1ebc/librt-0.12.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:edb5f06cdb38d6ef9fd7ae06d62962d65c881b5f965d5e8a6c53e59c15ae4338", size = 494118, upload-time = "2026-06-30T16:12:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/39/c1/f91f3094be2c76361d88aca613d8b7586d15b6026714d59d2e3dc0e35f44/librt-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1473ef42263dfee7553a5c460f11730a4409acf0d52629b284eb1e6b13eb460a", size = 516318, upload-time = "2026-06-30T16:12:14.192Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e2/5211af94252458cbed7a6250163dff9c5a84aec29609121c828375a3b319/librt-0.12.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1d6f69a06295fb6ad8dcf92b4b2d15d211842005e86eedce64d88e0633592f58", size = 522294, upload-time = "2026-06-30T16:12:15.879Z" }, + { url = "https://files.pythonhosted.org/packages/90/9b/de31f5b9fdf7fa3699c4bbbecf82ebd52013d5d6b500b70b07b0ebacbd51/librt-0.12.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:3275d0270cd07ca9c2e140ae4da34e24a0350e98c6e3815dce96ead67cf0487d", size = 502494, upload-time = "2026-06-30T16:12:17.394Z" }, + { url = "https://files.pythonhosted.org/packages/b1/48/22c18dff89f3900dddb3e470e6f7febcda37ff3667b73097a848c9a608b2/librt-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a4834462ec68613024d063c7efe9b188e350d40fda9ba937372039883d2a8051", size = 543422, upload-time = "2026-06-30T16:12:19.006Z" }, + { url = "https://files.pythonhosted.org/packages/52/7b/74691b4b55944227245fffef063714e3ab9707ab1111eb0068512b428c7c/librt-0.12.0-cp310-cp310-win32.whl", hash = "sha256:bcf9b55ac089e8cf201d2146833e1097812c15dcea61911e84d6a2904cf78893", size = 97642, upload-time = "2026-06-30T16:12:20.386Z" }, + { url = "https://files.pythonhosted.org/packages/c5/dc/7f8fa369a1f7cc9b090fecd373659ada0e9bab1ae4a3ac9f163eabd04977/librt-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0a122002f7e0d5c93e84465c4b3fe86621402b7b92f1e2bc0784ebe67793112", size = 117583, upload-time = "2026-06-30T16:12:21.829Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ab/628490f42d1eba82f3c7e5821aa62013e6df7f525b7a9e92c048f8d1cc1c/librt-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3f13c1e8563102c2b17581cf37fcb2c6dae7ad485ccea93ae46258998c25f9a1", size = 143821, upload-time = "2026-06-30T16:12:23.248Z" }, + { url = "https://files.pythonhosted.org/packages/38/5f/793e8b6f4b6ac16e7d7198478c0af3670606fbb535c768d5f3e954781423/librt-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1ddff067610a122387024c4df527493b909d41e54a6e5b2d0e6c1041d6dfa09", size = 148442, upload-time = "2026-06-30T16:12:24.582Z" }, + { url = "https://files.pythonhosted.org/packages/ad/92/c780fe37a9e0982f3bd8fd9a631d6b95d09a5a7201c6c50366ce843b7e42/librt-0.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8dc7ebb5f3eec062398e9d0ef1938acd21b589e74286c4a8906d0183318d91b", size = 478276, upload-time = "2026-06-30T16:12:26.101Z" }, + { url = "https://files.pythonhosted.org/packages/41/bb/226d444bc20d7dff4a19ec6c1ff2c13a76385eebddb59c9c00c923b67536/librt-0.12.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:198de569ea9d5f6f33808f1c00cc3db9de62bf4d6deafa3b052bd08255083038", size = 472337, upload-time = "2026-06-30T16:12:27.83Z" }, + { url = "https://files.pythonhosted.org/packages/12/79/98ac0840ee90a75d4e1155c79062860b12ccca508587ff2119fc086965f2/librt-0.12.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e958678a8bca56016aedc891b391c0e0813ea382a874b54a2c1b313c1d232720", size = 502087, upload-time = "2026-06-30T16:12:29.443Z" }, + { url = "https://files.pythonhosted.org/packages/6f/72/a6b1a0d080606a7f5f646b79a1496f21d709f8563877759ace9ce5adad73/librt-0.12.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:575a6eca68c8437ed4a8e0f534e31d74b562ba1049a0ee4b5f09e114bcc21be1", size = 493202, upload-time = "2026-06-30T16:12:31.077Z" }, + { url = "https://files.pythonhosted.org/packages/69/cf/e1b036b45f2fc272205ee18bf272b47e8d684bf1a75af26db440c7504359/librt-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:86f241c50dc9e9a3f0db6dbb37a607c8205aa87b920802dabbd50b70d40f6939", size = 514139, upload-time = "2026-06-30T16:12:33.032Z" }, + { url = "https://files.pythonhosted.org/packages/40/34/b193b3e6985469a2f8afa86c90012329c86480b6ff4f2e4bd7b5b937e134/librt-0.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:113417b934fbf38220a9c7fe94578cefbe7dbb047adcb75aa197905af2b13724", size = 519486, upload-time = "2026-06-30T16:12:34.996Z" }, + { url = "https://files.pythonhosted.org/packages/31/9e/7de4947b1695f247c813f833e3c1e7b77b52e52a7dba2c35411cf806b58e/librt-0.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:762f17c0eb6b5d74e269126996cea8a89e35ab6464c5151619163abcd8623ae2", size = 499609, upload-time = "2026-06-30T16:12:36.663Z" }, + { url = "https://files.pythonhosted.org/packages/59/11/f3730e04e758b1fbf215359062ad2d5b6bd0b0ab5ac46b1c140628795be7/librt-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6aa93b3bd7f7588c628f6e9bf66485d3467fd9a1ccdb8975b770178f39f35697", size = 542205, upload-time = "2026-06-30T16:12:38.56Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8f/710453617eabe20e18433864f335534c8aff63fbc68d8cd9dbc70a3d08f6/librt-0.12.0-cp311-cp311-win32.whl", hash = "sha256:aaa04b44d4fe86d824616b1f9c13e34c7c01ec0c96dd2abc4f59423696f788e2", size = 98067, upload-time = "2026-06-30T16:12:40.102Z" }, + { url = "https://files.pythonhosted.org/packages/42/53/401bff50a56e95daf151d911c99adf5732af2190e8f4d11886c9a229103c/librt-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:9aaeeddb8e7e4ae3bb9f944e0e618418cb91c0071d5ddbfcc3584b3cf59d39f0", size = 118346, upload-time = "2026-06-30T16:12:41.388Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9a/a3a9078fe88bfc2d2d99dcf1c18593938ae830089cf84c3b2532a6c49d63/librt-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:18a2402fa3123ab76ecca670e6fb33038fde7c1e91181b885226ec4d30af2c2c", size = 104760, upload-time = "2026-06-30T16:12:43.112Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1a/5bec493821b0e85b91de4f234912b50133d1aedb875048eef27938ec3f96/librt-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9bce19aa7c05f91c989f9da7b567f81d21d57a2e6501e2b811aa0f3f79614c1a", size = 146756, upload-time = "2026-06-30T16:12:44.395Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d0/cc04b48a57c1f275387f5578847214c4a6c21bfb24c6c8c8d6ba753fe403/librt-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0ace09f5bf4d982fe726015f102fb856658b41580597104e301e630ed1d8d86", size = 145537, upload-time = "2026-06-30T16:12:45.95Z" }, + { url = "https://files.pythonhosted.org/packages/9e/10/c02325556beb2aa158c9e549ddade8cc9a23b36cdad14756dbed730c1ff1/librt-0.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d007efe9243ede81ce75990ad7aa172da1e2024144b3eff17ba46a5fff1fff3c", size = 488637, upload-time = "2026-06-30T16:12:47.658Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9e/7b49ca1c30baa9c8df96024aa09a97c35a97455e36004c9b5311703c56f3/librt-0.12.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:ad324a5e4858388a4864915b90a42efc8b374376393f14b9940f2454e791912b", size = 483651, upload-time = "2026-06-30T16:12:49.283Z" }, + { url = "https://files.pythonhosted.org/packages/4d/71/03c8c8cec39645fda451132ff9d6d662fc5aea42a1a188a77a4fddb35906/librt-0.12.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10a40cf74cdd97b6f8f905056db73f5d459783de2ca04c6ebd1bf47652818e7e", size = 518359, upload-time = "2026-06-30T16:12:50.999Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ec/a9f357f94bbcba92277d22af22cff42ef706ae5d9d6d58b69bebf3a67954/librt-0.12.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:92e61c09de95217ae02a9d17f4f66cf073253cdc51bcfdc0f15c62c9a70baa85", size = 509510, upload-time = "2026-06-30T16:12:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/7a/34/717055325d028743aa01a7691ad59a63352a26a8ff2e7eeb0c9249514150/librt-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0461344061d6fc3718940f5855d95647831cef6d03a6c7506897f98222784ad4", size = 527302, upload-time = "2026-06-30T16:12:54.244Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/7612eeedb3395d92f7c6a84dca5f15e282d650483a4dc01aa5b9cffdfda3/librt-0.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e6dfe89074732c9287b3c0f5a6af575c9ede380a788013876cc7b14fe0da0361", size = 532568, upload-time = "2026-06-30T16:12:55.74Z" }, + { url = "https://files.pythonhosted.org/packages/79/1e/a9afe85d5bb8b65dc27be3809ed1d69082079e1e9717fd2c66aa9939600c/librt-0.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9efed79d51ad1383bba0855f613cca7aa91c943e709af2413ac7f4bb9936ce08", size = 521579, upload-time = "2026-06-30T16:12:57.884Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/93aebb219d52c37ea578f83b0588cd7b040974e464d4e435086a48b4dc4d/librt-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1eac6cc0e23e448fb3c1446ed85ff796afb616eed5897c978d35dbec030b7c7c", size = 558743, upload-time = "2026-06-30T16:12:59.577Z" }, + { url = "https://files.pythonhosted.org/packages/3c/85/1680c0ec332f238e3145c5608d313ab0a43281e210a5dd87e3bc3cc25631/librt-0.12.0-cp312-cp312-win32.whl", hash = "sha256:0ab8ee0210047ae86ca023ccfbfe3df82077fd1c9bc021aebbf37d993ef64af0", size = 99200, upload-time = "2026-06-30T16:13:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/30/0e/abca12d8904875aa2ad66327390a3f7b1b75ebc43c0a00fc763cecf32ea5/librt-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:51c8bfa12632c81b94401c101bcedd0c56c3a1f8fa3273ca3472b28cd2f54003", size = 119390, upload-time = "2026-06-30T16:13:02.493Z" }, + { url = "https://files.pythonhosted.org/packages/32/a5/4203481b6d3a3bb348c82ac71abf1fcb4cb3ae8422a24a8dee4cd3ac5bd7/librt-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:5eebd451f5def089369ba6d8ff0291303d035e8154f9f26f7633835c5b029ade", size = 105117, upload-time = "2026-06-30T16:13:03.952Z" }, + { url = "https://files.pythonhosted.org/packages/f2/87/568d948c8079c9ff3c9e8110cf85f1eb70218e1209af29d0b7b89aa4a60c/librt-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8d9a55760a34ae5ce70434aabb6a6c61c6c44a0ec58ca1cfd9cd86e4745d417d", size = 146808, upload-time = "2026-06-30T16:13:05.417Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1d/bea471ecea210088847bb5f3c4b4b424d596518934c06679b78ca85d6e63/librt-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ff0b197e338b4cf432873e0d6ef025213fdea85311ec4d87d2ea88c28adf2409", size = 145503, upload-time = "2026-06-30T16:13:07.023Z" }, + { url = "https://files.pythonhosted.org/packages/eb/9e/984ad422b56de95fdce158f06b051655373784ebea0aba9a7fcbc41614d1/librt-0.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e69f120a20b69e2539d603bbd4d62db38399b10f8bf73a1cf445038a621e8af", size = 488421, upload-time = "2026-06-30T16:13:08.492Z" }, + { url = "https://files.pythonhosted.org/packages/50/03/1a2f94009b07ea71f8e1a4cfe53370565b56da9caa341b89e0699325e9f5/librt-0.12.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:fde3cde595e947fc8e755b0a21f919a1622483d07c662d00496e040773d22591", size = 483488, upload-time = "2026-06-30T16:13:10.169Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3b/084bdc295823fbb6ab91670047adf8f420787f9e8794bf2d140b66dc196b/librt-0.12.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d977447315fa09ea4e8c7ae9b4e22f7659b5128161c1fd55ff786b5349f73503", size = 518428, upload-time = "2026-06-30T16:13:11.681Z" }, + { url = "https://files.pythonhosted.org/packages/c9/22/5a307390b93a115ffbecd95c64eecb4e56269680e45e9415ada7285f2cf4/librt-0.12.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ffac8a67e4143cea9a549d4822b93bc0bbaad73fc25aa0ab0ba5ec27d178677", size = 509744, upload-time = "2026-06-30T16:13:13.217Z" }, + { url = "https://files.pythonhosted.org/packages/b5/90/83f3cb6184f5d669660717b4b2e317c9ddaccf7ca5bb97f2196deac1a3b7/librt-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:94af1ed773ff104ef08ef3d669a0ba9d3a5916c609eb698cffe5d5476d66ff9b", size = 527749, upload-time = "2026-06-30T16:13:15.277Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3b/f162be5cc88d47378e3a20776fe425fa1c2bece755da15e2783ebf06d3d6/librt-0.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:548199d21d22fb26398dfbbe0ba953a52465c66f3a49f38e6fddce1b127faf53", size = 532582, upload-time = "2026-06-30T16:13:17.074Z" }, + { url = "https://files.pythonhosted.org/packages/c9/28/6c5d2f6b7232fd24f284fc4cab37a459fe69a9096a09942f44cc5c55e073/librt-0.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c8f1f413b966a9dd3ecf80cd337b0ad7bb3de2474a4ff448ed3ebabfc3f803fc", size = 522235, upload-time = "2026-06-30T16:13:18.823Z" }, + { url = "https://files.pythonhosted.org/packages/a9/1c/bd115360587fdc22c8ae8fac14c040a556b442e2965d4370d2cf274c8b95/librt-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:55f13f95b629be5b6ab38918e439bf14169d6f9a8deaae55e0c14e12fb0c74b9", size = 559055, upload-time = "2026-06-30T16:13:20.509Z" }, + { url = "https://files.pythonhosted.org/packages/fe/5a/c26f49f576437014825a86faea3cec60c1ed17f976abd567b6c12b8e35a7/librt-0.12.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:8b2dc079dfe29e77a47a19073d2040fa4879aa3656501f1650f8402ddce0313c", size = 79809, upload-time = "2026-06-30T16:13:22.401Z" }, + { url = "https://files.pythonhosted.org/packages/69/0b/a55244261d9ad7375ac039b8af06d42602722e2e8b8d8d6b86e4a3888c02/librt-0.12.0-cp313-cp313-win32.whl", hash = "sha256:da58944be8270f2bfee628a9a2a60c1cf6a12c8bea8e2c9b6edf3e5414ca7793", size = 99308, upload-time = "2026-06-30T16:13:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/c9/bf/ed9465e58d44c5a5637795547d0841c8934aab905ea452cac1adf14672cf/librt-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:1db4be3037e4ce065a071fa7deee93e78ebc25f448340a02a6c1c0b82c37e383", size = 119438, upload-time = "2026-06-30T16:13:25.188Z" }, + { url = "https://files.pythonhosted.org/packages/c0/44/3cad652aeb892e6e8ffe48d0fafa2bc652f28ec7ed3f4403fcbb1be4f948/librt-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:05fd2542892ad770b5dd45003fd080477cf220b611d3ee59b0792097eb0873a9", size = 105118, upload-time = "2026-06-30T16:13:26.533Z" }, + { url = "https://files.pythonhosted.org/packages/0e/51/3a0e05618c12423b6fc5141b590ec02a6efb645833edc8736a6c7b46d1ec/librt-0.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:b37ee42e09722284a6d9288fe44a191f7276060a3195939bb77c6502058dbb34", size = 145579, upload-time = "2026-06-30T16:13:27.909Z" }, + { url = "https://files.pythonhosted.org/packages/77/9e/fd399d099dfb4020f3f7c34e7e6210c389fa89f7d79ca92f5afb0395f278/librt-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ade11988728b3e4768dadc5696e82c60e9b35fc95335a9b4d1f5d69e753ccec7", size = 150139, upload-time = "2026-06-30T16:13:29.357Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ee/610239fbd8c4b005443664c5d4c3bc1717daedd8c71369bf45011aa87194/librt-0.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f351ed425380e39bd86df382578aa5b8c5b98e2e265112de7379e7d030258150", size = 480457, upload-time = "2026-06-30T16:13:30.78Z" }, + { url = "https://files.pythonhosted.org/packages/0c/10/ceddc9010f26c541444be36e1153a79b64626694db2d33a524c719fa3e46/librt-0.12.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:857d2163e088c868967717ace8e980017fd868a735f3de010412af02bdc30319", size = 479002, upload-time = "2026-06-30T16:13:32.398Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/b1523d9718e8192e5403e6b41a02742e17ba554369f0729b9f30ab590e2d/librt-0.12.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2befc80aa5f2f5b93f28abaaf11feff6677931dd548320e44c52deaa9399744", size = 510527, upload-time = "2026-06-30T16:13:34.615Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0e/0f3ff43befb18a531615736791e52fb67eaa71ff7b89e6e5f7004b64cc6e/librt-0.12.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:be3694dcfa97c6715dd19ac73d3e1b21a805514a5785663e57fecacd3ff64e5a", size = 500988, upload-time = "2026-06-30T16:13:36.408Z" }, + { url = "https://files.pythonhosted.org/packages/a8/1a/0278ea4a9e599dc507c43839a87f2c764ad04bf69418e2d763d58659e55f/librt-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2d5f67e86f45638843d025b0828f2e9e55fc45ff9180d2618ccdeaf72a796050", size = 519318, upload-time = "2026-06-30T16:13:37.883Z" }, + { url = "https://files.pythonhosted.org/packages/59/55/090e10e62be2f35265e41601337f83ac9f83be9aca1bf92692e3a82effdd/librt-0.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:64572c85e4ab7d572c9b72cd76b5f90b21181b1459fa6b1aac6f8958c4fcff31", size = 527127, upload-time = "2026-06-30T16:13:39.682Z" }, + { url = "https://files.pythonhosted.org/packages/1f/34/8052c9ec678be6ba751279947831f089aa69b009000b985ce91d1979669a/librt-0.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:8b961912b0e688c1eb4658a46bdb0606b31918d65597fbe7356ca83aa653ffcc", size = 509766, upload-time = "2026-06-30T16:13:41.266Z" }, + { url = "https://files.pythonhosted.org/packages/6f/f8/8761b36189e9ec8dc20b49fa84cef22852c6c41fcda56f760f7fc1360da5/librt-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:722375903e3f079436a7a33da51ce73931536dd041f9feb01536f05d8e010c96", size = 552043, upload-time = "2026-06-30T16:13:43.197Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/7283971ef6b70269938b49c7b25f670ec6325d252265fbcc996f9b364379/librt-0.12.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:a5a96a8f536b65ef1bf910c09e7e71647edde5111f6e1b51f413c6fba5bfe71b", size = 79472, upload-time = "2026-06-30T16:13:44.64Z" }, + { url = "https://files.pythonhosted.org/packages/c3/5e/b30940dea935e8ac5bd0e0abb1985f5274590d557ac3a252ca0d5392ce52/librt-0.12.0-cp314-cp314-win32.whl", hash = "sha256:8ffc99c356f1777c506e1b69dc303879153ae2640ba15b8f3d4448bc87139149", size = 94246, upload-time = "2026-06-30T16:13:45.962Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4e/0af9fe63f35fa304da3b05688f30ff6a329bcc59581b1cc51dc87fd30141/librt-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:1e68fb20798f455cda41d20a306a23c901218883f17a4bab1ed6e1331b265fb7", size = 114951, upload-time = "2026-06-30T16:13:47.279Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8e/843c495d7db35e13b84cd533898fa89145c40dc255da0bc316d53d631464/librt-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:2df534f97916cf38ec9b1ddafeb68ae1a4cd4a54775ff26a797026774c0517cf", size = 100562, upload-time = "2026-06-30T16:13:48.699Z" }, + { url = "https://files.pythonhosted.org/packages/75/30/c686d0f978d5fd6867c5bbad96b015c9445746764d1c228e16a2d30d9382/librt-0.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c09e581b1c2b8a62b809d4f4bd101ca3de93791e5b0ed1a14085d911be3dee3f", size = 153897, upload-time = "2026-06-30T16:13:50.017Z" }, + { url = "https://files.pythonhosted.org/packages/40/46/f6f2d77ce46628b48fb5280709013b5109cf3a2c46a2472093cdfc03519d/librt-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:976888d0d831402086e641018bcc3208e0a38f0835789da91f72894b2cb4161f", size = 156391, upload-time = "2026-06-30T16:13:51.462Z" }, + { url = "https://files.pythonhosted.org/packages/c2/46/cd790c7e19e460779471530ffab454541d6ea4a3b7d338cad7f16ff96995/librt-0.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563c37cdb41d08fe1e3f08b201abac0e317ca18e88b91285466ee0a585797520", size = 564151, upload-time = "2026-06-30T16:13:53.146Z" }, + { url = "https://files.pythonhosted.org/packages/54/12/724559a15fb023cbdef7aee1e81fbfbc3ee22fd09009baa816cea63e3a60/librt-0.12.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:b97eb1a3140e279cc76f85b0fb92b7eb3dfbe0471260ee878bc9dc4bf9a0d649", size = 546002, upload-time = "2026-06-30T16:13:54.665Z" }, + { url = "https://files.pythonhosted.org/packages/4b/7e/f9d8c257ab4909f101c7c13734367749e782fd8625545f0343502c2f09f1/librt-0.12.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:06e0623351ab9904cf628245f99c714586f4dd23dc740b88c8bc670d8401a847", size = 584204, upload-time = "2026-06-30T16:13:56.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/33/64665810575ac23b6cb6ef364de51309b7803620c12885b6e895ebc29591/librt-0.12.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:da12f017b2e404554be14d466cd992459feaa44f252b0f18d909a85266ce1237", size = 573688, upload-time = "2026-06-30T16:13:58.1Z" }, + { url = "https://files.pythonhosted.org/packages/0f/01/27522995c6627455abc7a939d57535fb1a7836d398ccedb3d7585f46039e/librt-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d97f31003a5c86b9e78155a829572c3a26484064fb7ac1d9695fe628bd93d029", size = 604719, upload-time = "2026-06-30T16:13:59.831Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1f/099e61b1b688551d6d2ce9d4d2ae2242a938759db8551e6cbac7f7176ee5/librt-0.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:bd43a6c69876aef4f04eaae3d3b99b0be64755fda274002fa445b92480bf664e", size = 598183, upload-time = "2026-06-30T16:14:01.457Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c1/050400249665503bdd5b83cec518fa7b183b609341c8dcd58161775c4226/librt-0.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c01755c72fca1dc6b8d5c2ed228b8e7b2ffe184675c22f0f05ebd8fe188b9250", size = 582559, upload-time = "2026-06-30T16:14:03.29Z" }, + { url = "https://files.pythonhosted.org/packages/da/d1/eef8f0e6722518b65a3d3bcd9309f9f44e208ce5d6728070820f988e7078/librt-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:625ae561d5fa36400856dcc27464400d047bc2d5e3446be88f437b03fefd72e4", size = 626375, upload-time = "2026-06-30T16:14:04.957Z" }, + { url = "https://files.pythonhosted.org/packages/8b/78/f0bb41a6f2bbd3c77bdcc66980dc0d69ca1192a0ecec25377afcc5e6db73/librt-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8d73191883553ee0739741544bf3b00aba2a1224e45d9580b30cbc29e21dc03b", size = 97752, upload-time = "2026-06-30T16:14:06.555Z" }, + { url = "https://files.pythonhosted.org/packages/92/24/e279c27972ab051a070237cfa45728fa51670c3f22f1a4d391711e9f4c31/librt-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e1cbb037324e759f0afa270229731ff0047772667f3cb38ef5df2cabf0175ede", size = 119562, upload-time = "2026-06-30T16:14:07.908Z" }, + { url = "https://files.pythonhosted.org/packages/06/e6/42a475bfca683b0cd5366f6dd06580062b7e567bb8534d225c877c2f14f3/librt-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bca1472acbd473eff61059b4409f802c5a1bcb4cd0344d06f939df9c4c125d40", size = 104282, upload-time = "2026-06-30T16:14:09.29Z" }, +] + +[[package]] +name = "lightning-utilities" +version = "0.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/45/7fa8f56b17dc0f0a41ec70dd307ecd6787254483549843bef4c30ab5adce/lightning_utilities-0.15.3.tar.gz", hash = "sha256:792ae0204c79f6859721ac7f386c237a33b0ed06ba775009cb894e010a842033", size = 33553, upload-time = "2026-02-22T14:48:53.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906, upload-time = "2026-02-22T14:48:52.488Z" }, +] + +[[package]] +name = "llama-cpp-python" +version = "0.3.32" +source = { registry = "https://abetlen.github.io/llama-cpp-python/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version >= '3.15' and sys_platform != 'darwin'", + "python_full_version == '3.14.*' and sys_platform != 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin'", + "python_full_version == '3.12.*' and sys_platform != 'darwin'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform != 'darwin'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version < '3.11' and sys_platform != 'darwin'", + "python_full_version < '3.11' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "diskcache" }, + { name = "jinja2" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-6-dashai-cpu') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.32/llama_cpp_python-0.3.32-py3-none-linux_riscv64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.32/llama_cpp_python-0.3.32-py3-none-macosx_11_0_arm64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.32/llama_cpp_python-0.3.32-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.32/llama_cpp_python-0.3.32-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.32/llama_cpp_python-0.3.32-py3-none-musllinux_1_2_aarch64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.32/llama_cpp_python-0.3.32-py3-none-musllinux_1_2_x86_64.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.32/llama_cpp_python-0.3.32-py3-none-pyemscripten_2026_0_wasm32.whl" }, + { url = "https://github.com/abetlen/llama-cpp-python/releases/download/v0.3.32/llama_cpp_python-0.3.32-py3-none-win_amd64.whl" }, +] + +[[package]] +name = "llama-cpp-python" +version = "0.3.32" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "diskcache" }, + { name = "jinja2" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/c8/3eb9c10c138eaa9d6148471701476169322eafbd825fdc13ec326552b516/llama_cpp_python-0.3.32.tar.gz", hash = "sha256:b06502361770f82eb08b7f1a192eb084b9ead2b88fe32cda8c397a2782eabde6", size = 70308017, upload-time = "2026-06-29T05:59:48.626Z" } + +[[package]] +name = "llvmlite" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/a0/acc8ffcd5bdc63df0097e22c719bfcd61b604358343089313a8aebbb24ab/llvmlite-0.48.0.tar.gz", hash = "sha256:543b19f9ef8f3c7c60d1468191e4ee1b1537bf9f8a3d56f64c0ddd98de92edd2", size = 184016, upload-time = "2026-07-02T20:20:05.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/4e/32543c42568fb321b3bdfcf9106e4116ab8f5a7bbcfd9ecf5569b0c07d83/llvmlite-0.48.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:614aad57df707e3172efd5165f2aa7da6a0c6897e40dce590bf756396815ba76", size = 40480650, upload-time = "2026-07-01T18:41:01.945Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0d/6aa48abd423067139a129d1434b77bbcc56080db51d12a88510bb491ca3d/llvmlite-0.48.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13532f248960ba888ad5ab8150494e2f3a3d20e5f59f264e63741ea5b0ba844c", size = 59890118, upload-time = "2026-07-01T18:41:10.608Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c7/aa917444d871a79608af49149de1b28764e87d2ab41f933c5cd02431d03d/llvmlite-0.48.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ee0c77685a18f5fca994ae21d0763007fca5c5c64b41de37accc78b69079176", size = 58343459, upload-time = "2026-07-01T18:41:06.21Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2b/ceee1cdc263617109d514ac4d1b31f10a282662740ff7d5777baae25b3b5/llvmlite-0.48.0-cp310-cp310-win_amd64.whl", hash = "sha256:02853fe4214acb3780fc920c3fee10564b61d58a35e1b78afcc8a546c2deaba3", size = 41864734, upload-time = "2026-07-01T18:41:14.746Z" }, + { url = "https://files.pythonhosted.org/packages/9a/55/595981f14fbae9ba966feb12af552b1fe69889e44e64ac883a731ed335e0/llvmlite-0.48.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:56a7e24607d3f02d7b1bae8d29c7e1e423d53143d68b072999777f19678fe77b", size = 40480651, upload-time = "2026-07-01T18:41:18.438Z" }, + { url = "https://files.pythonhosted.org/packages/26/08/0109d1b9cb3f4603f3890e30bc66c65332b79185f12a045343b2ae431f67/llvmlite-0.48.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6fa532d6bb3fd3f0803567c736401c54aecfe1a396d3ad25d2440d220e09f0e7", size = 59890118, upload-time = "2026-07-01T18:41:28.184Z" }, + { url = "https://files.pythonhosted.org/packages/02/eb/c5281be180c789cdffbf45b671884c57d7e61345ef3b0f643a4965e108e8/llvmlite-0.48.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:979a66a3f28a02565383ff463527dce78e9b856298872a361283132488e83591", size = 58343458, upload-time = "2026-07-01T18:41:23.397Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f7/b3222b13f2d424dae3c9e63fde476af25ebccf1f3faf0b52d1b79fc15c70/llvmlite-0.48.0-cp311-cp311-win_amd64.whl", hash = "sha256:efaee0276e5e17c2b99b92e0c974bd484ef5977cf5dbc9168e82b71578edb47f", size = 41864734, upload-time = "2026-07-01T18:41:31.932Z" }, + { url = "https://files.pythonhosted.org/packages/92/a2/28696a9e61e245d1a79816d29d106692a90a2b6e7d78c98b326db70827af/llvmlite-0.48.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:d66c3beb4209087ddd4cf4ed2a0856b6887e6a913bdcf1aacfec9851cf2cba4e", size = 40480651, upload-time = "2026-07-01T18:41:35.694Z" }, + { url = "https://files.pythonhosted.org/packages/80/f2/72409351db66d0a317ec5087e076f31fb7b773a640db8a90ce6b5cac9edd/llvmlite-0.48.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:416fa4c2c66c2c6dc6d0a402648c19206e548efa0aa1eff01ad5cdad0af8217d", size = 59890118, upload-time = "2026-07-01T18:41:44.886Z" }, + { url = "https://files.pythonhosted.org/packages/3a/27/5ae2f3722606360480707adb47f001ad89df8251d06b14ee80336e660b66/llvmlite-0.48.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5e5a5131045b72345c71062ea1a91910dde913792b6c9b28ebb2c1c0a712e98", size = 58343459, upload-time = "2026-07-01T18:41:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/16/78/d824ffff7521cd140dc2006e44ce2bc82e64b48d1b32e90e956308c85a74/llvmlite-0.48.0-cp312-cp312-win_amd64.whl", hash = "sha256:d45c7541a80934ec6d8ab0defe67439494ecd2193cbf852a44ba827808976ac1", size = 41865022, upload-time = "2026-07-01T18:41:48.663Z" }, + { url = "https://files.pythonhosted.org/packages/9c/23/fe9316d14626b42c73ef0b502e724705a6ee9450afe53759c0a99c37c2d7/llvmlite-0.48.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a83a99ef0c05b4ccddf9b6218ed9fe84b653a0caf7c1d9dbe148d6d16c67f518", size = 40480652, upload-time = "2026-07-01T18:41:52.216Z" }, + { url = "https://files.pythonhosted.org/packages/1b/4a/90715fa12006d681270b08d881195b6fab3ec39572e048764a1f7f59fed7/llvmlite-0.48.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8761b9e522f55207e24424fcd98370289eec2710bf8e915c82d1053f642450dc", size = 59890120, upload-time = "2026-07-01T18:42:00.748Z" }, + { url = "https://files.pythonhosted.org/packages/70/5e/7b3e20d64650ca3c80af0cdb664ec4b575ec83d9d4dd05bea8bd31f9bbb6/llvmlite-0.48.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2fe5cb59b2063bfa039dcb8ca6481c0181bf552f340d10dcf61d7996a665556e", size = 58343457, upload-time = "2026-07-01T18:41:56.41Z" }, + { url = "https://files.pythonhosted.org/packages/17/97/5a430055d1838cf1fb7a01cfa943300f5e4c026fc6333a522c5e4a03b0c1/llvmlite-0.48.0-cp313-cp313-win_amd64.whl", hash = "sha256:91c7e24e74cde3f02b88aa5acca678373f9e069f3b98531b3dbb3a142d9d10bb", size = 41865022, upload-time = "2026-07-01T18:42:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8e/8170f2e0c217f88069c333d85bb976e536b332aecfcce606ddbdb249385f/llvmlite-0.48.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:321f1ac39b462603f0b589751aecf2d237d056f6d005749c1752b6f23ec3f074", size = 40480650, upload-time = "2026-07-01T18:42:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e1/05b50692b647cac3c18200ac485b04f342f00ed173c9cc46767274469a15/llvmlite-0.48.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:05f0103c8f2f96a37441337e3643863c01b8e83e530aff38960dcb383c54a065", size = 59890115, upload-time = "2026-07-01T18:42:17.805Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c3/470b8c4ff9ae2db2f9cf5c3e73de76ed908a32788ae9eb5602d43e6a476b/llvmlite-0.48.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37d66fae72802175b0bfe1ea06e624b51e2d7aee6c3c34bbd09739b8f88e8e0b", size = 58343457, upload-time = "2026-07-01T18:42:13.217Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2d/6a5171fb7236ac0895e1a02ccba3735bf291e8597239aa6421894d3c0ba8/llvmlite-0.48.0-cp314-cp314-win_amd64.whl", hash = "sha256:966dcab0a598e2bd8fb5f2cc082cf7b07bae564fc485a3a8692393caf986facf", size = 42986372, upload-time = "2026-07-01T18:42:21.483Z" }, + { url = "https://files.pythonhosted.org/packages/94/e3/7a93e09c9f94e637ca90209ceef0334a9a1d45b0bdb7c92ff922d25d6187/llvmlite-0.48.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7a5c413317050a1d67c34708bde97707f9b2257ef1017f7532d21fe7d9a9ff30", size = 40480654, upload-time = "2026-07-01T18:42:25.076Z" }, + { url = "https://files.pythonhosted.org/packages/27/98/a29133b4728671a175f7d616fab8b1c6e1d8c269d1523581d3160697bfb1/llvmlite-0.48.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d9f952dff6c350c529997423d4fa43abae9722a884ac7ffafc37a3af676e7db", size = 59890119, upload-time = "2026-07-01T18:42:33.88Z" }, + { url = "https://files.pythonhosted.org/packages/1a/cf/7aac11a1f1c7ec54b60c7f6814e87561fb6b55b2f290455d7941eb113420/llvmlite-0.48.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:054aa7d46595935565f276cf0c1659b4f10929c996dd4a606875fae26fba2a23", size = 58343460, upload-time = "2026-07-01T18:42:29.545Z" }, + { url = "https://files.pythonhosted.org/packages/db/41/b96f440c7df5ebba07872cad4e30fbc3560387755b1ea0b629adb76d5ca8/llvmlite-0.48.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d0b3c61aac83b42fb48cc96bffbf57c81b82b2aa92276b7ed6420c814629a99a", size = 42986383, upload-time = "2026-07-01T18:42:37.544Z" }, +] + +[[package]] +name = "lxml" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/3b/aab6728cae887456f409b4d75e8a01856e4f04bd510de38052a47768b680/lxml-6.1.1.tar.gz", hash = "sha256:ba96ae44888e0185281e937633a743ea90d5a196c6000f82565ebb0580012d40", size = 4197430, upload-time = "2026-05-18T19:19:06.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/da/dbe4dfc01ac226fb0504fad035f4d69f3202f3502e20e68537631daddd96/lxml-6.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:09dd5b7075dc2f7709654a46543ba1ea3c2e217b2ed8fbd413a8a945a0f40f60", size = 8541124, upload-time = "2026-05-18T19:17:11.589Z" }, + { url = "https://files.pythonhosted.org/packages/78/20/f7095ed9fc2c025f9cfe71cc6ec9f1feb05624edc1812423b5f1aecf3d4b/lxml-6.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6ac4ef4d82dff54670227a69c67782ae0b811b5cf6b17954f1e8f7502fc0d1d", size = 4602783, upload-time = "2026-05-18T19:17:20.888Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a4/65c63ca98bd129f6cff7b8c2fa48953ab058cc6005b541354e7dd54d8000/lxml-6.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:556e94a63c9b04716f8e4de2abb65775061f846e89331b6c5be79183a24f98ea", size = 5002687, upload-time = "2026-05-18T19:17:01.738Z" }, + { url = "https://files.pythonhosted.org/packages/96/1d/ab7a5c4b5a394d98a94e2d0fc67bab8297597426770dd4978370fbdaf531/lxml-6.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6bf403fbb3b3e348a561a5f4f0b9961835657981c802a1df03653eef8a9074", size = 5155099, upload-time = "2026-05-18T19:17:05.159Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b1/07603bfeeb891a2596d5c2a68f7d2f70f7d11c841ebe391412c69c2857b0/lxml-6.1.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1dde6131244bba38a17c745836ba190bc753fd73c9291666287fd0a3fa3dcf30", size = 5057225, upload-time = "2026-05-18T19:17:08.117Z" }, + { url = "https://files.pythonhosted.org/packages/7a/16/cb391ee4b90186fa16d9ebcbe3ea96c71b8da3b0686386c8dcbcc3c67d44/lxml-6.1.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98fc784c2c1440667aeedf8465bdfe10208acf0ead656a2c68627299f546b315", size = 5287643, upload-time = "2026-05-18T19:17:11.507Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d6/b619717f918fd76747448fdbaee0e769edbc70e659b5b5d0112b7020b7a3/lxml-6.1.1-cp310-cp310-manylinux_2_28_i686.whl", hash = "sha256:add8cf6ddf9a65116119a28ece0f7886e30af27ba724a7594305f1d1b58a92a1", size = 5412445, upload-time = "2026-05-18T19:17:22.182Z" }, + { url = "https://files.pythonhosted.org/packages/c6/80/12bc5390ac0a3edeb579d9535e5049a5dda663438728e179d52fb319c33a/lxml-6.1.1-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cf9d57306d848218f3601fee7601fab1a327c942d56e2e97610583cb4dd74206", size = 4770864, upload-time = "2026-05-18T19:17:26.851Z" }, + { url = "https://files.pythonhosted.org/packages/0b/59/6500c09da3137f54f020e908d81cfc5ee3e8888e908fd380207afad7c2e6/lxml-6.1.1-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:88136950da4d13c318bde414ce10219931937851327f44328f2df4d2c4614067", size = 5359594, upload-time = "2026-05-18T19:17:32.527Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9b/f64b4cc6b7ebcf75d95af3cde934d254b5f2f10d4163928d838d86b6eb48/lxml-6.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cecdd5dfdc87b1fd87dbf81d4b037a544f47f4c744200a67013771682d67686a", size = 5107713, upload-time = "2026-05-18T19:17:04.402Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/c7388ad5d3a72315d2832dc1458cbf4f2af7f2b990b606ff4876efd04511/lxml-6.1.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd312b9692e831d2ffcad61eab31d91d4b4655a962e61de8fb410472cbcd37aa", size = 4803973, upload-time = "2026-05-18T19:17:06.545Z" }, + { url = "https://files.pythonhosted.org/packages/3f/22/76197f0bbf165f0b9e75be59be4997e5259cde973f12f098c1b54c7f5d60/lxml-6.1.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:5b7328b46d49fc9477d91ae8f6d55340347d827b7734ba3ea33faae0efef1383", size = 5349925, upload-time = "2026-05-18T19:17:09.743Z" }, + { url = "https://files.pythonhosted.org/packages/24/52/d2a0cfeccb9bcdc47c7ee05cdae5d69b48c9acf20997790a6338bb0d0b3b/lxml-6.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37a58976370f36d9329d118ad0b953c5aeb9119ac9c6a4e258942a225d0573a1", size = 5309825, upload-time = "2026-05-18T19:17:13.831Z" }, + { url = "https://files.pythonhosted.org/packages/19/4a/b30944266776c2f49749ef2445aa7e78898194134b80ad776386f61b56ae/lxml-6.1.1-cp310-cp310-win32.whl", hash = "sha256:cea3f4c1af79af13cdb2da0c028111d8f8522d4f22a000c82385535f24e5cf3a", size = 3598402, upload-time = "2026-05-18T19:17:08.21Z" }, + { url = "https://files.pythonhosted.org/packages/9e/97/33691c66a4d7ec1a5a98e7c909a5b83ee45c7f7ba4cf92b1c4cf26e98079/lxml-6.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:3abf332af33a74288675d936fe861fd4344da0dd6622193fbc4f2bfbb35536b5", size = 4021295, upload-time = "2026-05-18T19:17:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/d0/5f/26a4dd0e12b9456ff7b12a21af5b491eb6629680d1edd73f4140fd386bcf/lxml-6.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:8dadbe5b217ff35b6a8d16610dd710219b59b76d13f0e3f0d9f36786206e4485", size = 3667717, upload-time = "2026-05-19T19:22:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/62/b0/83f481780d1548750b8ce2ec824073deef2f452d9cd1a6faff8507e3d16d/lxml-6.1.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:53b7d2b7a10b1c35c0a5e21e9224accf60c1bbfba523990732e521b2b73adef2", size = 8526461, upload-time = "2026-05-18T19:17:25.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/30fa0f808002c7329397bfbb24e306789c0b29f04aa5842c07b174b4216f/lxml-6.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ff3f333630ab480244a1bff72043e511a91eb22e7595dead8653ee5612dd8f3d", size = 4595375, upload-time = "2026-05-18T19:17:34.555Z" }, + { url = "https://files.pythonhosted.org/packages/4f/d2/edb71cf0e561581a7c5eb2626244320eb04e9f8ce6d563184fd668b45073/lxml-6.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a4bbea04c97f6d78a48e3fbc1cb9116d2780b1b39e03a23f6eb9b603fd61f510", size = 4923654, upload-time = "2026-05-18T19:17:42.917Z" }, + { url = "https://files.pythonhosted.org/packages/4c/77/1bc7eeb0de4577d783fb625aa092cc9357883bba35845a3666bf1259f3dc/lxml-6.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db1d75f6617a49c1c01bc7023713e0ff59ab32c9579ae62a7674c0e34f3b0b0a", size = 5067921, upload-time = "2026-05-18T19:17:49.175Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3c/c0690d74bd2bc17bc03b5b0d093569ead597dd0bfa088bf99eef8c24e19c/lxml-6.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a12689be69a28ddaa0ab99a5a1137da2afd5f8f16df7b5680b66f616d3eda1d", size = 5002456, upload-time = "2026-05-18T19:17:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/66/8d/d1b3271af0c0f1e27e8472a849e4d2c65bc7766884b9ad2da9e76e145c88/lxml-6.1.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b73c339ae29b90fd2d06e58ebd555a751bde9cd6bbd36cc0281b9a2c94e9d8", size = 5202776, upload-time = "2026-05-18T19:18:08.924Z" }, + { url = "https://files.pythonhosted.org/packages/7a/45/689824ffb237fd10125ad273f32b28ff04dc6203c2822c85ff65a93df65e/lxml-6.1.1-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:752d3bbfe874715ccd0aec7f88d7fc623c0f1fd7aa7b3238a084e017bad2a009", size = 5329945, upload-time = "2026-05-18T19:18:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c0/ef73af53767e958fd87d437c170f272e2f6e6c0f854939f133a895f1e711/lxml-6.1.1-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:6b1761fbf9ec984e2e9d9c589ef5f5fd684b7c19f92aadd567a26c5224958db6", size = 4659237, upload-time = "2026-05-18T19:18:18.657Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/e1158e40397585e91cb0472374a1f63d0926a1ddeaa92f13d1a1ffe306d5/lxml-6.1.1-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d680fbcb768404c601ecb43519ecd8461f6954cb11c06a78962f666832ccfca8", size = 5265904, upload-time = "2026-05-18T19:18:24.883Z" }, + { url = "https://files.pythonhosted.org/packages/a0/16/8687e5d1400ed1c0bc41dace232ebb7553952b618ea1f2e5fb6e2cfbbe23/lxml-6.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:162af1091cd785f2f27e62d3547ae9bc58ec5c86dd314d67021fd02463708d83", size = 5045225, upload-time = "2026-05-18T19:17:20.073Z" }, + { url = "https://files.pythonhosted.org/packages/ca/18/d877bd1ae2e5ffdfd4836565aba350db31feb2f2656d6ce70316ed66a05e/lxml-6.1.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e9308ff8241c532df3f3e570f9a5aeed6c853f888512ba4b75638d7c11c95ef6", size = 4712721, upload-time = "2026-05-18T19:17:40.512Z" }, + { url = "https://files.pythonhosted.org/packages/44/4d/1f44fd1d770b10dacbf6b5c6e520f4d6e0708744930f719dc04e67cab981/lxml-6.1.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5f6994074ebae6ffb04447268e37dc16edc304f9859cf91acb86e0af6c1b395c", size = 5252549, upload-time = "2026-05-18T19:17:51.236Z" }, + { url = "https://files.pythonhosted.org/packages/64/5d/1d66b84f850089254c230ef6ea6b267a5a54e2e179a5d960036a05d501d7/lxml-6.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:80c2dfadb855da477cf73373ad29a333535dedb9b12bad02c9814c8e2b43bf08", size = 5226877, upload-time = "2026-05-18T19:18:00.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/00/84c4b5302d42a2d0184f38d538c8a197f33b52a50bd4f7bcfe990bce3036/lxml-6.1.1-cp311-cp311-win32.whl", hash = "sha256:30a89d3ac8faec007453fb541f3f46807eeec88edd5826f6e3fe001752a2c621", size = 3594072, upload-time = "2026-05-18T19:17:12.714Z" }, + { url = "https://files.pythonhosted.org/packages/61/9d/2e2f7d876349f45e0f3e29f72da311668853d59b58d473a2dea4f0160135/lxml-6.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:abbefa31eee84842140f67acef1c828e28bba8bbf0c3bc6e5492a9af88152c28", size = 4025469, upload-time = "2026-05-18T19:17:50.566Z" }, + { url = "https://files.pythonhosted.org/packages/b0/d5/570e6390e4110331e6208b2ba83d1482cc9146808ee118b22824a34c1070/lxml-6.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:dcb292aa7fe485ceff7af4f92e46c5af397daec5dff64871a528f0fc47a3cc5b", size = 3667640, upload-time = "2026-05-19T19:22:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6e/c4add832b6fc1e887125b96f880d7b9b70aae5248718e046b1704bcac4b9/lxml-6.1.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:104c09bda8d2a562824c0e319d0768ce26a779b7601e0931d33b09b53c392ef7", size = 8570821, upload-time = "2026-05-18T19:17:42.068Z" }, + { url = "https://files.pythonhosted.org/packages/22/00/ff3009c88e65de8011630acf8ab5a09cb2becd2aaf47fba2f3449f6224e9/lxml-6.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:25c6997a9a534e016695a0ba06b2f07945de682731ff01065b6d5a4474179da1", size = 4624252, upload-time = "2026-05-18T19:17:47.897Z" }, + { url = "https://files.pythonhosted.org/packages/42/95/bb63f0fd62e554fe078e1fb3c8fe9083c14ddc7ad7fa178d10e57e071ac7/lxml-6.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c921ba5c51e4e9f63b8b00267d06566e1f63407408a0496da2d1d0bfc819c7fc", size = 4930746, upload-time = "2026-05-18T19:18:29.637Z" }, + { url = "https://files.pythonhosted.org/packages/eb/99/0013e8d9b5960f4f041cf0b73e2f80c23eb5205b1f7bfb20203243651359/lxml-6.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:54a7f95e4de5fb94e2f9f4b9055c6ba33bf3d628fd77a1d647c5923caa2cdcdc", size = 5093723, upload-time = "2026-05-18T19:18:34.168Z" }, + { url = "https://files.pythonhosted.org/packages/29/91/317b332636bfc7bddcff828d41b3307f50043f4b237e40849c333d80fa1a/lxml-6.1.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f2ec43df44b1f76249ee0a615334f9b5b060e1c8bd90e706dad2d14d02f383", size = 5005557, upload-time = "2026-05-18T19:18:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/42/2f/cc9bf06afe70f9c9093ae60855d9759da9db601ec4080f7473319666ffd7/lxml-6.1.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:70ef8a7e102a1508f8121aae5b0867abd663f72c14f0a9c937e6554cb4587b7b", size = 5631036, upload-time = "2026-05-18T19:18:44.858Z" }, + { url = "https://files.pythonhosted.org/packages/08/f6/af32e23e563971ffb0fb86be52bc5be5c2c118858ffc119bf6a9039b173d/lxml-6.1.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebe6af670449830d6d9b752c256a983291c766a1365ba5d5460048f9e33a7818", size = 5240367, upload-time = "2026-05-18T19:18:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/8555d40948b09ce86f1bd0c68a7ac31d07b1929f92cc1b074006c97ef2d2/lxml-6.1.1-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:27acc820660aaffa4f7c087f29120e12980f7779d56d8492d263170111284740", size = 5350171, upload-time = "2026-05-18T19:18:52.779Z" }, + { url = "https://files.pythonhosted.org/packages/63/75/5d92da93729b7bad783689e6496049fa40927b45bec7bf183c981de3ca70/lxml-6.1.1-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:1db753c9115ec7100d073b744d17e25e88a8f90f5c39b2f5dd878149af59671f", size = 4694874, upload-time = "2026-05-18T19:18:55.139Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b5/3aad415a9a25b822e783f15deeb4dffccf5113030f1afa2222dd929313d9/lxml-6.1.1-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4f469aebd783bb741c2ecb2a681008fd26bfe5c16a9a72ed5467f834e810df2", size = 5244492, upload-time = "2026-05-18T19:19:01.28Z" }, + { url = "https://files.pythonhosted.org/packages/f1/a1/5fcf7eb9904b80086aa47dcf0027de07b1bb990afad2e6823144c368ae04/lxml-6.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:766b010012d59470072c1816b5b6c69f1d243e5db36ea5968e94accf430a4635", size = 5048232, upload-time = "2026-05-18T19:18:12.67Z" }, + { url = "https://files.pythonhosted.org/packages/77/74/1f601b63c7a69fcdf10fa9b148c81da8442204194f6c55509cc485c786b9/lxml-6.1.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b8d812c6011c08b8111a15e54dd990b8923692d80adf35488bee34026c35accf", size = 4777023, upload-time = "2026-05-18T19:18:15.928Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b9/7a78f51aec95b1bf780d78e12705a9f6533284f8693dc5c0e6724fa53d3f/lxml-6.1.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0306bd29505a9177aac19f1877174b0e7422c222a59f70b2cd41633448c3dc", size = 5645773, upload-time = "2026-05-18T19:18:23.223Z" }, + { url = "https://files.pythonhosted.org/packages/a5/6e/98a7b7ad54e4e74fa1f20fff776913980619d0ebe5558232d7da6580bdd8/lxml-6.1.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5ba186ad207446c65d3bb3d3e0412b032b1d9f595e59861e2354798c5703d955", size = 5233088, upload-time = "2026-05-18T19:18:31.433Z" }, + { url = "https://files.pythonhosted.org/packages/65/d1/bc0ed2427bf609f2ee10da303a6a226f9c8bce94f945dc29a32ce55de6e4/lxml-6.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aa366a1e55b8ebfe8ca8ddc3cfe75c8ebade181aeb0f661d0cb05986b647f72a", size = 5260995, upload-time = "2026-05-18T19:18:37.091Z" }, + { url = "https://files.pythonhosted.org/packages/69/8b/6772e1a4b513fc50a8d931f19edde0e13ae6918510a1e13ff67864f3e5ed/lxml-6.1.1-cp312-cp312-win32.whl", hash = "sha256:126c93f7f56f0eda92f6d8c619edc463a4f23d9252f1c9d0405a76f25fa9f11a", size = 3596382, upload-time = "2026-05-18T19:17:18.37Z" }, + { url = "https://files.pythonhosted.org/packages/1b/89/45198e9624762af2dfd2cb8782598477ceb29f6e59caab560388ae1f4ec1/lxml-6.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:26e6eda8d38c1fcab1090dd196ee87cbd13788e531937610e2589085de074e77", size = 3997255, upload-time = "2026-05-18T19:17:56.781Z" }, + { url = "https://files.pythonhosted.org/packages/90/a9/7a54b6834088d9ae528a7b780584ba6a39a9457b0ac330479f20ffbc9449/lxml-6.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:6540377fbd53fe1b629172288c464fb18db11ce1fa7dc15891da10aa9dcc3e7f", size = 3659610, upload-time = "2026-05-19T19:22:50.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/7e6f37c5584ccbb2ff267f56fd0339016938c1c8684cfefab9b33ffc2f36/lxml-6.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:68a9198d0fc122d14bb76837de9aa80cf84caed990b5b237f532ed87d3706736", size = 8559780, upload-time = "2026-05-18T19:17:57.661Z" }, + { url = "https://files.pythonhosted.org/packages/a1/36/587c2521cf23a2cd6c9c22108aa7528f683a1f195ed7ccd23a4b1786ad36/lxml-6.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d47866cb32fb503450b6edc9df355d10dc49836af2e89901bd6ac6b0896d9d9", size = 4618006, upload-time = "2026-05-18T19:18:04.452Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ca/ab7bfe2bf4c972af5e7878262845ead3a24a929a9b04bc11c7c1ece6c82a/lxml-6.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb7c9811bfaa8b1ed5ed319f5d370dfbcaa59d52ea64be2a5a85e18195930354", size = 4924139, upload-time = "2026-05-18T19:19:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6b/55/a0c72851dfee5ecc689f949723a73dea457758912542cb955b108eaf0d8f/lxml-6.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:762ff394d5bd56da0cf034a23dcce4e13923f15321a2adfa2ac00201dc6d3fca", size = 5082329, upload-time = "2026-05-18T19:19:09.728Z" }, + { url = "https://files.pythonhosted.org/packages/f0/b6/0608f7d61a3b96cc67e5648a3d906e31a5082093e10e7be65b3886289938/lxml-6.1.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a088f287f7d8275a33c07f2cac6c50b9319309a0200a39e7e75d80c707723099", size = 4993564, upload-time = "2026-05-18T19:19:13.608Z" }, + { url = "https://files.pythonhosted.org/packages/4c/66/ae227524b066d29d55bf0b453d93d2d793c40218657d643dcbbca13b8faf/lxml-6.1.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e902da4b04e6b52e5893900d4b8ab46068f75f3561f01bf1080957f9fd932ed6", size = 5613467, upload-time = "2026-05-18T19:19:16.228Z" }, + { url = "https://files.pythonhosted.org/packages/a6/76/dbe4a00b50385e40194231dcfe5a12c059de7cf90e89c83407d2b085b719/lxml-6.1.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d4962d4c66bf830a7e59ed6cfc17d148149898a3aefa8ec6e59763e6e3ed085", size = 5228304, upload-time = "2026-05-18T19:19:19.354Z" }, + { url = "https://files.pythonhosted.org/packages/1c/01/00b1b8442ed2041793336868ba0b9ea4b13d7da7c085c6404c207a63bf79/lxml-6.1.1-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:581d4c8ae690a6609e64862dd6b7c2489635c2d13907fc2b20f2bc200ff1d21e", size = 5341607, upload-time = "2026-05-18T19:19:22.297Z" }, + { url = "https://files.pythonhosted.org/packages/63/36/1ad29931e9a4638bb707869f01d423a6c815f82152138d1a40dfcfde2b95/lxml-6.1.1-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:876e1ff5930ed8bf295ec5ef9a8155e9b6b1876bbf1deed8b3a8069311875a8f", size = 4700168, upload-time = "2026-05-18T19:19:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d1/a9536cecf9be18a0dc72d32bead283a2332d1ffebd2dd3ac70ce444686e5/lxml-6.1.1-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9eb9b5a968f6e0f6d640092a567e14529ff8cea2e29d00da6f78a79fa49f013c", size = 5232487, upload-time = "2026-05-18T19:19:28.603Z" }, + { url = "https://files.pythonhosted.org/packages/0e/77/b4fb1e03bf5d130e879214d3100092e386418807fb74dd0adc4b0a48f351/lxml-6.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:aa49e06d94aba782c6a02eecb7e507969e7e7a41b267f1b359bb35585f295d5b", size = 5044231, upload-time = "2026-05-18T19:18:42.246Z" }, + { url = "https://files.pythonhosted.org/packages/26/4c/d00daeeb0a5530c4028a9232aa1b93db3ef4ed2158c116ea73c79a9765b3/lxml-6.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:70cdfd80589d59e43e18005dd7244e8895e93db8ab6a620b7e23df5445a4e3d2", size = 4769450, upload-time = "2026-05-18T19:18:48.013Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6a/715a3a8d156ce42f29cf014706f5410c2ff3b02267774110fc23266409fe/lxml-6.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:aad9aa39483ed8ec44d6d2e59e5b98a0d80676ef0d92f44bfc374836111f62f5", size = 5635874, upload-time = "2026-05-18T19:18:51.914Z" }, + { url = "https://files.pythonhosted.org/packages/45/37/0544bc21dde2a88f3a17b504e6fc79c0e01d25a33c2f6079724e9e72b9c7/lxml-6.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:d49514be2f28d895c38cf9d2b72d7b9a07d00314519f456c0b50b53cfcf4c785", size = 5223987, upload-time = "2026-05-18T19:18:59.715Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f8/f6a5e8185bcb28c2befae3d31f8e3df3b811cb0f47746517a81279fcafe1/lxml-6.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:47402e62c52ff5988c1e8c6c63177f5708bccf48e366dea4e3dcf1e645e04947", size = 5250276, upload-time = "2026-05-18T19:19:03.834Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f2/1a2b9f1b7a49d45495369be7ef9ad05b262930f2eab3e3145706fca8083f/lxml-6.1.1-cp313-cp313-win32.whl", hash = "sha256:3483644525531e1d5762b0c44a8e18b6efba321b6dcf8a8952de10b037618bca", size = 3596903, upload-time = "2026-05-18T19:17:29.863Z" }, + { url = "https://files.pythonhosted.org/packages/e6/99/f4ffb024f238eec2131aaa09f3278fb6129cf892741bf68e1fc1afb8c100/lxml-6.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:a10bd2fd62e8ce916ececb342f348f190724a098c1faa056fdfb2a22ad5e8660", size = 3995869, upload-time = "2026-05-18T19:18:02.596Z" }, + { url = "https://files.pythonhosted.org/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" }, + { url = "https://files.pythonhosted.org/packages/13/e2/2e325795566de01d0d7c3bb57d3c370616b2d07b01214e84eec5d3b10963/lxml-6.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:19b7ab10b210b0b3ad7985d9ac4eb66ab09a90b20fe6e2f7ba55d01a234345d0", size = 8577146, upload-time = "2026-05-18T19:18:17.765Z" }, + { url = "https://files.pythonhosted.org/packages/93/cf/5630b5e4be7d2e6bee8efe83865c925221103cf0221303b104ce134b01e2/lxml-6.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c08e5c694306507275f2290073350c4f32e383db15213b2c69e7ff39c1193840", size = 4623866, upload-time = "2026-05-18T19:18:30.669Z" }, + { url = "https://files.pythonhosted.org/packages/d2/51/3904907c063451cf8d4a5c9fe0cad95fa1f4ec57f4e3884fa0731bd7a305/lxml-6.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:74a9717fd0d82effef5c2854f0d917231d5324b5a3eb7275c43ac9fa32f97a14", size = 4950022, upload-time = "2026-05-18T19:19:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/94/cd/9c7611a51c37a2830928405817cc5d56a97f64fab83cc3f628748b135749/lxml-6.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efe0374196335f93b53269acd811b944f2e6bdc88e8894f214bd636455484909", size = 5086695, upload-time = "2026-05-18T19:19:34.764Z" }, + { url = "https://files.pythonhosted.org/packages/da/d6/24e3b5906abb0b674ff2ae195bc3ce59708df2bcd17cf17703b2d7dd643a/lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac931cdc9442c1763b8a8f6cd62c0c938737eafc5be75eff88df55fc73bc0d00", size = 5031642, upload-time = "2026-05-18T19:19:37.771Z" }, + { url = "https://files.pythonhosted.org/packages/2d/db/6ec54f99019838bff54785c51da07f189eb4676861c5f2730962b0d8d665/lxml-6.1.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:aee395f5d0927f947758b4ec119fd5fc8ec71f07a1c5c52077b30b04c0fa6955", size = 5647338, upload-time = "2026-05-18T19:19:40.553Z" }, + { url = "https://files.pythonhosted.org/packages/42/3d/ef4dcfffd22d27a61805d8ed9f7fb888495bc6aa88648fa07c1eaa5586b6/lxml-6.1.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9395002973c827b3ed67db77e6ec09f092919a587022174554096a269378fb13", size = 5239528, upload-time = "2026-05-18T19:19:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/62/bb/37fb3f0dff146bdcfa78eec47879273820b2a0bf350ec236ce14bd0b1c26/lxml-6.1.1-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:73bc2086f141224ebddb7fc5c6a36ca58b31b94b561e1dfe8e073e3270fad1e7", size = 5350730, upload-time = "2026-05-18T19:19:46.307Z" }, + { url = "https://files.pythonhosted.org/packages/90/42/43253f168388df4fae1f38c01df36ddb9bee39e2048167b54cdcbae85ea3/lxml-6.1.1-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3779def59032b81e44a5f70096ef6bf2082f8d901937dca354474ba09782e245", size = 4697530, upload-time = "2026-05-18T19:19:49.889Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a8/c5a8504f81bbdfc8e7094c2c850cdb4ed6777fc4d5ddd9e5ab819f3b0d54/lxml-6.1.1-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:86c89b9d55ebf820ad7c90bc533410f0d098054f293351f10603c0c46ff598f5", size = 5250670, upload-time = "2026-05-18T19:19:53.199Z" }, + { url = "https://files.pythonhosted.org/packages/77/b7/c7e76ab18744d75e21f320ebf9ff9d1ceae2b54dd431ea5a64caf26c9672/lxml-6.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19607c6bbff2a44cf3fe8250abccd20942d3462473e0a721d01d379ed017e462", size = 5084485, upload-time = "2026-05-18T19:19:08.422Z" }, + { url = "https://files.pythonhosted.org/packages/31/31/b35c53f8ef7b7c31cacd23d3638652fff7bcd1deb6eedb709ab43b685908/lxml-6.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:c6ed5141a5c7507cf3ee76bd363b0d6f801e3321adc35b5d825a23115faa5465", size = 4737635, upload-time = "2026-05-18T19:19:12.321Z" }, + { url = "https://files.pythonhosted.org/packages/d9/06/31f23c813a7fe8e0cb1b175e915b08c9bf4e86d225b210feadbdbe519667/lxml-6.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:62aeb7e85b5d60320b9d77eef2e773994e2c0ce10121b277e0a19804e1654a5a", size = 5670681, upload-time = "2026-05-18T19:19:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bc/ce619bccc89b1fd9ad8a8e1330ee3f3beff9f2ff95b712d7bbcdd6e22fc3/lxml-6.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b1b963fd8f5caa68e99dfae060d54de1fe9cba899b8718b44a00cdca53c3e590", size = 5238229, upload-time = "2026-05-18T19:19:18.131Z" }, + { url = "https://files.pythonhosted.org/packages/2f/5d/b329acbbedc0b619ebc2be6cf7ee9ed07e80892c88d4dfd612c33805789a/lxml-6.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63876be28efefa04a1df615b46770e82042cce445cfdce55160522f57b231ccb", size = 5264191, upload-time = "2026-05-18T19:19:21.118Z" }, + { url = "https://files.pythonhosted.org/packages/d6/85/be36fb1425b30db3c3f9df75fe86343ebffb79e6320bd7f588e25bfeac39/lxml-6.1.1-cp314-cp314-win32.whl", hash = "sha256:7f7a92e8583f06b1fd49d01158143b8461cfcd135dcb10ec807270a3051bd603", size = 3657202, upload-time = "2026-05-18T19:17:39.509Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ce/3cf9a827342269f54d405a6202397de63f07c69cbd6ce7d183a3f0cba1e9/lxml-6.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:b2d444f2e66624d68e9c6b211e28a76e22fff5fcabcfff4deac18b529b7d4137", size = 4064497, upload-time = "2026-05-18T19:18:14.662Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3e/1a957bde8f0760039e627f94699f82caa782c9d838d86c3d28245ee67212/lxml-6.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:3fd9728a2735fda14f4e8235830c86b539e9661e849665bf926d3f867943b4bf", size = 3741991, upload-time = "2026-05-19T19:22:59.111Z" }, + { url = "https://files.pythonhosted.org/packages/78/b2/00ed55b3a2efa4658fb795c38d1090ec9b3e8a6c3683d4441fa517f09c3b/lxml-6.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:787b2496d0dbe8cd180984e8d29e3a6f76e7ea34db781cb3bd55e4ba1ef8b4ee", size = 8827545, upload-time = "2026-05-18T19:18:41.193Z" }, + { url = "https://files.pythonhosted.org/packages/c0/73/74573db19baa618d5f266f2407898b087ff6927115b00b71e5fc1b700847/lxml-6.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2c8daa471358dc2d6fcf02165e80ec68f77871a286df95bc5cc3816153b0fd2c", size = 4735736, upload-time = "2026-05-18T19:18:46.761Z" }, + { url = "https://files.pythonhosted.org/packages/16/02/6f7061f4f95f51e545d48e87647c54791d204a4e881be4156e7a26ba5338/lxml-6.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acd7d70b64c0aae0c7922cca83d288a16f5f6da523637697872253415269baef", size = 4970291, upload-time = "2026-05-18T19:19:56.215Z" }, + { url = "https://files.pythonhosted.org/packages/b0/02/55fc057d8283427dea7d6edb102e7a840239c77a64a983d92f62a304c0e9/lxml-6.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4f0dd2f01f9f8a89f565d000e03abcf0a13d692a346c8d22f628d49af098777a", size = 5102822, upload-time = "2026-05-18T19:19:59.223Z" }, + { url = "https://files.pythonhosted.org/packages/e4/48/8e1cf78d89d66850121d9255a2a24414c98f775da93b90cf976956c24b14/lxml-6.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b7e8a14c8634bf6f7a568634cb395305a6d964aeb5b7ee32248094bed3a7e2c", size = 5027923, upload-time = "2026-05-18T19:20:01.549Z" }, + { url = "https://files.pythonhosted.org/packages/ed/00/0632a0647612c8af24d26997b3b961397daa9d5b2581444805933629a4cb/lxml-6.1.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:86281fbdd6a8162756f8d603f37e3435bfa38043adb79c6dc6a2dfee065e7525", size = 5595843, upload-time = "2026-05-18T19:20:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/bc/86/ab008a7dc360711b66858d61c80a5979a70a09f2aa2b05d9698df80b803d/lxml-6.1.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5d7152ec39ca7c402d8fb9bad86140a15b9503bd0c54484e3f1bbe3dd37ceca", size = 5224515, upload-time = "2026-05-18T19:20:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/75/c6/2702ff375e728e34f56d9a45339a9cf7e4427e917f542225242d63a05afa/lxml-6.1.1-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:88d8cb75b9d82858497a5393e3c63cfbf03035225e4b35a49ed7ccb151e4dc0e", size = 5312511, upload-time = "2026-05-18T19:20:09.308Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/a5807c98f87a86f10ef9ffab35516df7c0f0c4b6d5d33e9f608ab9c04a31/lxml-6.1.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f64ec5397ea6a41fc1b4af0380d79b44a755b5531dcaccd9940fb260dca93038", size = 4639206, upload-time = "2026-05-18T19:20:11.704Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e1/8a0a2c35734812395f4da4eaf33748a7e5705bfb2a58b128da764339d5ec/lxml-6.1.1-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d34bbf07dbc7ca5970671b1512e928991fb5e9d95365636c9b2d8b4f53af405e", size = 5232404, upload-time = "2026-05-18T19:20:14.064Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e2/0e6a4dd5ad84d01d99aa7bae7cfefd4a760a0e0f8176818241de17d9b6c0/lxml-6.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:17e0e18d4ad8adbd0399291bc44845b69d9dd68439a3cdebdf35ff902ec05072", size = 5083769, upload-time = "2026-05-18T19:19:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7e/161f33d463f6ffc1c7679104b65086dea120080d49dde4d238f015aaee2f/lxml-6.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:3ab541146f1f6968c462d6c2ac495148e8cdba2f8347700b2141b6ec5a75bf52", size = 4758936, upload-time = "2026-05-18T19:19:27.256Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fb/2369825e3f6ca99305bf9f7b7085fda91c8b0922a89e54d900974aa3ef85/lxml-6.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2a0217714657e023ef4293500f65aa20fce6164c8fd6b08fa5bd4a859fb14b9b", size = 5620296, upload-time = "2026-05-18T19:19:29.993Z" }, + { url = "https://files.pythonhosted.org/packages/30/90/d61e383146f74c5ab683947ea14dc7b82778838ab9b95ea73a23b60d0191/lxml-6.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:05a82eb6e1530a64f26225b55cbd178113bd0b5af1c2b625f25e5296742c26d2", size = 5228598, upload-time = "2026-05-18T19:19:33.523Z" }, + { url = "https://files.pythonhosted.org/packages/76/2d/2dafd8149e94b05bb070690efd5bb2680720681e03ff03fc57d2b70a1105/lxml-6.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9e36f163528fc50cbef305f02a5fd66d404edf7049cdaff211dbc2cba5a7013e", size = 5247845, upload-time = "2026-05-18T19:19:36.649Z" }, + { url = "https://files.pythonhosted.org/packages/ce/68/b30e913340c380ddac9580c6e6230991fc37240ec4f64704833e4f3e2769/lxml-6.1.1-cp314-cp314t-win32.whl", hash = "sha256:649dda677cf3bd6ac9ae14007ba0c824ded8ce5808b53fc7431d9140399118c1", size = 3897345, upload-time = "2026-05-18T19:17:33.562Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4e/9eb2af5335545f9fbcd7af57bcf87c6025d31eaa31b14ec184a6c8675328/lxml-6.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:793033d6c5cdf33a573f910d9bea14ef8f5771820411d118da8e1182edb53d5e", size = 4393350, upload-time = "2026-05-18T19:18:10.076Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2c/0f1e93c636720e8a3eb59af2bfda99d98b55891e1c53bc30c2e0e865f01b/lxml-6.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:58bb955caba94e467d2a96da17660d2d704e0675894cba21ab8a775b8621fd1c", size = 3817223, upload-time = "2026-05-19T19:22:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/b5/32/86a3f0f724a3a402d4627937a7fc27b160e45e7012b4adf47f6e1e844511/lxml-6.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:31033dc34636ea6b7d5cc11b1ddbda78a14de858ba9d3e1ed4b69a3085bc521e", size = 3930127, upload-time = "2026-05-18T19:19:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/d832e82af08723761556d004b1d04d281c09f9a8cecd7d3148548c9941a3/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3893c14c4b6ac5b2d54ba8cf03e99fe5104e592de491f19bd6b82756c09f8004", size = 4210769, upload-time = "2026-05-18T19:20:41.427Z" }, + { url = "https://files.pythonhosted.org/packages/6d/39/0dc5949f759ed7d951e0bb8c2f2d9d7aca1908d22352fa84a8afd2ea54af/lxml-6.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c07da4cebf6889f03ebac8d238f62318e29f495de0aa18a51ea14e61ae907e2e", size = 4318163, upload-time = "2026-05-18T19:20:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/e6/fb/8ab3845fe046ba4cbf74536bcf6801a774b7caf4350de1c5d37f1f0a9e90/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6f0ce10945fab9c4c06ce14e22af9059d1a87493a9af4501a5b0b9187e21cf2", size = 4250945, upload-time = "2026-05-18T19:20:47.385Z" }, + { url = "https://files.pythonhosted.org/packages/68/1b/7553ab136894374ffae8851ec06f98f511cd8e66246e41b6be059d0a7289/lxml-6.1.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f8844cd288697c6425c9beba919302241e3278871dc6519515e72b04e987abcf", size = 4401664, upload-time = "2026-05-18T19:20:50.489Z" }, + { url = "https://files.pythonhosted.org/packages/db/a4/441aee36c6f6b249823d20fd91f9be9ab89d7c5a8ae542a4a4ca6d342d56/lxml-6.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:ed21202aec73cda4d55d1ce57b389aadb90ffb044e6cd1080b8347efe1b1ec84", size = 3508989, upload-time = "2026-05-18T19:18:38.158Z" }, +] + +[[package]] +name = "mako" +version = "1.3.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/62/791b31e69ae182791ec67f04850f2f062716bbd205483d63a215f3e062d3/mako-1.3.12.tar.gz", hash = "sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a", size = 400219, upload-time = "2026-04-28T19:01:08.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/b1/a0ec7a5a9db730a08daef1fdfb8090435b82465abbf758a596f0ea88727e/mako-1.3.12-py3-none-any.whl", hash = "sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9", size = 78521, upload-time = "2026-04-28T19:01:10.393Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "contourpy", version = "1.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cycler", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "fonttools", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "kiwisolver", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyparsing", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "python-dateutil", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/6f/340b04986e67aac6f66c5145ce68bf72c64bed30f92c8913499a6e6b8f99/matplotlib-3.10.9-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77210dce9cb8153dffc967efaae990543392563d5a376d4dd8539bebcb0ed217", size = 8296625, upload-time = "2026-04-24T00:11:43.376Z" }, + { url = "https://files.pythonhosted.org/packages/bb/2f/127081eb83162053ebb9678ceac64220b93a663e0167432566e9c7c82aab/matplotlib-3.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1e7698ac9868428e84d2c967424803b2472ff7167d9d6590d4204ed775343c3b", size = 8188790, upload-time = "2026-04-24T00:11:46.556Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b7/d8bcec2626c35f96972bff656299fef4578113ea6193c8fdad324710410c/matplotlib-3.10.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aa972116abb4c9d201bf245620b433726cb6856f3bef6a78f776a00f5c92d37", size = 8769389, upload-time = "2026-04-24T00:11:48.959Z" }, + { url = "https://files.pythonhosted.org/packages/12/49/b78e214a527ea732033b7f4d37f7afb504d74ba9d134bd47938230dfb8b1/matplotlib-3.10.9-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae2f11957b27ce53497dd4d7b235c4d4f1faf383dfb39d0c5beb833bff883294", size = 9589657, upload-time = "2026-04-24T00:11:51.915Z" }, + { url = "https://files.pythonhosted.org/packages/5f/15/5246f7b43beae19c74dfee651d58d6cc8112e06f77adb4e88cc04f2e3a23/matplotlib-3.10.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b049278ddce116aaa1c1377ebf58adea909132dfce0281cf7e3a1ea9fc2e2c65", size = 9651983, upload-time = "2026-04-24T00:11:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/75/77/5acecfe672ba0fa1b8c0454f69ce155d1e6fc5852fa7206bf9afaf767121/matplotlib-3.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:82834c3c292d24d3a8aae77cd2d20019de69d692a34a970e4fdb8d33e2ea3dda", size = 8199701, upload-time = "2026-04-24T00:11:58.389Z" }, + { url = "https://files.pythonhosted.org/packages/4c/8c/290f021104741fea63769c31494f5324c0cd249bf536a65a4350767b1f22/matplotlib-3.10.9-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:68cfdcede415f7c8f5577b03303dd94526cdb6d11036cecdc205e08733b2d2bb", size = 8306860, upload-time = "2026-04-24T00:12:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/51/18/325cd32ece1120d1da51cc4e4294c6580190699490183fc2fe8cb6d61ec5/matplotlib-3.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfca0129678bd56379db26c52b5d77ed7de314c047492fbdc763aa7501710cfb", size = 8199254, upload-time = "2026-04-24T00:12:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/79/db/e28c1b83e3680740aa78925f5fb2ae4d16207207419ad75ea9fe604f8676/matplotlib-3.10.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e436d155fa8a3399dc62683f8f5d0e2e50d25d0144a73edd73f82eec8f4abfb", size = 8777092, upload-time = "2026-04-24T00:12:06.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/fa/3ce7adfe9ba101748f465211660d9c6374c876b671bdb8c2bb6d347e8b94/matplotlib-3.10.9-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56fc0bd271b00025c6edfdc7c2dcd247372c8e1544971d62e1dc7c17367e8bf9", size = 9595691, upload-time = "2026-04-24T00:12:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/36/c4/6960a76686ed668f2c60f84e9799ba4c0d56abdb36b1577b60c1d061d1ec/matplotlib-3.10.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5a6104ed666402ba5106d7f36e0e0cdca4e8d7fa4d39708ca88019e2835a2eb", size = 9659771, upload-time = "2026-04-24T00:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0d/271aace3342157c64700c9ff4c59c7b392f3dbab393692e8db6fbe7ab96c/matplotlib-3.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:d730e984eddf56974c3e72b6129c7ca462ac38dc624338f4b0b23eb23ecba00f", size = 8205112, upload-time = "2026-04-24T00:12:15.773Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ee/cb57ad4754f3e7b9174ce6ce66d9205fb827067e48a9f58ac09d7e7d6b77/matplotlib-3.10.9-cp311-cp311-win_arm64.whl", hash = "sha256:51bf0ddbdc598e060d46c16b5590708f81a1624cefbaaf62f6a81bf9285b8c80", size = 8132310, upload-time = "2026-04-24T00:12:18.645Z" }, + { url = "https://files.pythonhosted.org/packages/35/c6/5581e26c72233ebb2a2a6fed2d24fb7c66b4700120b813f51b0555acf0b6/matplotlib-3.10.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0c3c28d9fbcc1fe7a03be236d73430cf6409c41fb2383a7ac52fe932b072cb1", size = 8319908, upload-time = "2026-04-24T00:12:21.323Z" }, + { url = "https://files.pythonhosted.org/packages/b7/18/4880dd762e40cd360c1bf06e890c5a97b997e91cb324602b1a19950ad5ce/matplotlib-3.10.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cb28c2bd769aa3e98322c6ab09854cbcc52ab69d2759d681bba3e327b2b320", size = 8216016, upload-time = "2026-04-24T00:12:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/32/91/d024616abdba99e83120e07a20658976f6a343646710760c4a51df126029/matplotlib-3.10.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae20801130378b82d647ff5047c07316295b68dc054ca6b3c13519d0ea624285", size = 8789336, upload-time = "2026-04-24T00:12:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/5c/04/030a2f61ef2158f5e4c259487a92ac877732499fb33d871585d89e03c42d/matplotlib-3.10.9-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c63ebcd8b4b169eb2f5c200552ae6b8be8999a005b6b507ed76fb8d7d674fe2", size = 9604602, upload-time = "2026-04-24T00:12:29.052Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/541e4d09d87bb6b5830fc28b4c887a9a8cf4e1c6cee698a8c05552ae2003/matplotlib-3.10.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d75d11c949914165976c621b2324f9ef162af7ebf4b057ddf95dd1dba7e5edcf", size = 9670966, upload-time = "2026-04-24T00:12:32.131Z" }, + { url = "https://files.pythonhosted.org/packages/04/a1/4571fc46e7702de8d0c2dc54ad1b2f8e29328dea3ee90831181f7353d93c/matplotlib-3.10.9-cp312-cp312-win_amd64.whl", hash = "sha256:d091f9d758b34aaaaa6331d13574bf01891d903b3dec59bfff458ef7551de5d6", size = 8217462, upload-time = "2026-04-24T00:12:35.226Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d0/2269edb12aa30c13c8bcc9382892e39943ce1d28aab4ec296e0381798e81/matplotlib-3.10.9-cp312-cp312-win_arm64.whl", hash = "sha256:10cc5ce06d10231c36f40e875f3c7e8050362a4ee8f0ee5d29a6b3277d57bb42", size = 8136688, upload-time = "2026-04-24T00:12:37.442Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, + { url = "https://files.pythonhosted.org/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, + { url = "https://files.pythonhosted.org/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, + { url = "https://files.pythonhosted.org/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, + { url = "https://files.pythonhosted.org/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, + { url = "https://files.pythonhosted.org/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, + { url = "https://files.pythonhosted.org/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, + { url = "https://files.pythonhosted.org/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, + { url = "https://files.pythonhosted.org/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, + { url = "https://files.pythonhosted.org/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, + { url = "https://files.pythonhosted.org/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, + { url = "https://files.pythonhosted.org/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, + { url = "https://files.pythonhosted.org/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, + { url = "https://files.pythonhosted.org/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, + { url = "https://files.pythonhosted.org/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, + { url = "https://files.pythonhosted.org/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, + { url = "https://files.pythonhosted.org/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, + { url = "https://files.pythonhosted.org/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, + { url = "https://files.pythonhosted.org/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, + { url = "https://files.pythonhosted.org/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, + { url = "https://files.pythonhosted.org/packages/2c/2b/0e92ad0ac446633f928a1563db4aa8add407e1924faf0ded5b95b35afb27/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1872fb212a05b729e649754a72d5da61d03e0554d76e80303b6f83d1d2c0552b", size = 8293058, upload-time = "2026-04-24T00:13:56.339Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/74682fd369f5299ceda438fea2a0662e6383b85c9383fb9cdfcf04713e07/matplotlib-3.10.9-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:985f2238880e2e69093f588f5fe2e46771747febf0649f3cf7f7b7480875317f", size = 8186627, upload-time = "2026-04-24T00:13:58.623Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e8/368aab88f3c4cd8992800f31abfe0670c3e47540ba20a97e9fdbcde594b3/matplotlib-3.10.9-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6640f75af2c6148293caa0a2b39dd806a492dd66c8a8b04035813e33d0fd2585", size = 8764117, upload-time = "2026-04-24T00:14:01.684Z" }, + { url = "https://files.pythonhosted.org/packages/63/e2/9f66ca6a651a52abfe0d4964ce01439ed34f3f1e119de10ff3a07f403043/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:42fb814efabe95c06c1994d8ab5a8385f43a249e23badd3ba931d4308e5bca20", size = 8304420, upload-time = "2026-04-24T00:14:04.57Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e8/467c03568218792906aa87b5e7bb379b605e056ed0c74fe00c051786d925/matplotlib-3.10.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f76e640a5268850bfda54b5131b1b1941cc685e42c5fa98ed9f2d64038308cba", size = 8197981, upload-time = "2026-04-24T00:14:07.233Z" }, + { url = "https://files.pythonhosted.org/packages/6f/87/afead29192170917537934c6aff4b008c805fff7b1ccea0c79120d96beda/matplotlib-3.10.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3fc0364dfbe1d07f6d15c5ebd0c5bf89e126916e5a8667dd4a7a6e84c36653d4", size = 8774002, upload-time = "2026-04-24T00:14:09.816Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "contourpy", version = "1.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cycler", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "fonttools", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "kiwisolver", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyparsing", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "python-dateutil", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/24/080c99d223d158d3a8902769269ab6da5b50f7a0e6e072513907e02b7a6c/matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57", size = 33251176, upload-time = "2026-06-12T02:29:15.508Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/a2/78f662f1b18968531f67d3fcde1b7ea8496920bacd4f16ddb5b79d112e46/matplotlib-3.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f857524b442f0f36e641868ce2171aafa88cb0bc0644f4e1d8a5df9b32649fef", size = 9436261, upload-time = "2026-06-12T02:27:34.161Z" }, + { url = "https://files.pythonhosted.org/packages/5e/92/044f1de43901310202f4c79acf4f141be53b2ca8d8380e2fcefb3d523a75/matplotlib-3.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57baa92fdc82948ed716eae6d2579d4d6f40965cd8d2f416755b4a72580a3233", size = 9264669, upload-time = "2026-06-12T02:27:37.413Z" }, + { url = "https://files.pythonhosted.org/packages/53/f4/f0b4f9ba7ec14a7af8151f3ad71ecfe3561e6ba38cfab1db3681ba4ca112/matplotlib-3.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:630eee0e67d35cce2019a0e670719f4816e3b86aff0fa72729f6c69786fceb45", size = 10021076, upload-time = "2026-06-12T02:27:39.926Z" }, + { url = "https://files.pythonhosted.org/packages/d7/33/4d679c6dcd594a156542080ac907ddccf7b09ca11655c4b28eca8e9ee5da/matplotlib-3.11.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5106c444d0bf966eee2853548c03772af4ab7199118e086c62fbac8ccb07c055", size = 10828999, upload-time = "2026-06-12T02:27:42.433Z" }, + { url = "https://files.pythonhosted.org/packages/07/74/0a3683802037d8cd013144d77c247219b47f2aabace6fdde74faa12bacf7/matplotlib-3.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d7aea652b58e686444079be3376ef546bffa1eee9b9bb9c472b9fcf6cf410d3", size = 10913103, upload-time = "2026-06-12T02:27:44.827Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9f/970fcbf381e82ec66fdf5da8ea76e2e9240f61a24011ce9fd1d42c37ac2d/matplotlib-3.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:70a5b3e9a5dab708c0f039709ae7c68d5b4d254e291ef76492cdba230c8bb5e4", size = 9310945, upload-time = "2026-06-12T02:27:46.867Z" }, + { url = "https://files.pythonhosted.org/packages/14/4e/6e7cfed23611265ded53806852343b5c59339e506e84c474a9b5afc3b249/matplotlib-3.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d68266213e73823ac3be90615bab0cf31f88851e114cdb1dd25dacf3b01e1a7", size = 8999304, upload-time = "2026-06-12T02:27:48.798Z" }, + { url = "https://files.pythonhosted.org/packages/da/17/f5276b496c61477a6c4fc5e7401f4bfe1c2e5ef7c6cd67896f2ade3809cb/matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918", size = 9449976, upload-time = "2026-06-12T02:27:50.989Z" }, + { url = "https://files.pythonhosted.org/packages/82/34/bdd77418adb2178a1d59f044bd67bfebb115896e91b840b8a197eb3f4f4e/matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97", size = 9279307, upload-time = "2026-06-12T02:27:53.247Z" }, + { url = "https://files.pythonhosted.org/packages/94/95/7f522393c88313336b20d70fc849555757b2e5febc22b83b3a3f0fd4bce9/matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344", size = 10031353, upload-time = "2026-06-12T02:27:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/87/ce/8f25a0e3186aefd61913e7467d1b999465bcd0d0c03ac695c1b26ca559b7/matplotlib-3.11.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41635d7909d19e52e924a521dde6d8f670b0f53ab1d0e8c331fa831554f681d1", size = 10839232, upload-time = "2026-06-12T02:27:57.746Z" }, + { url = "https://files.pythonhosted.org/packages/85/c2/db15da2bbdf9e3ca66df7db8e2c33a1dfed67be24a24d2c878efaaff01d6/matplotlib-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94f5000f67ca9faa300863ea17f8bce9175cb67b88bec4bc7780502d53dd7c9e", size = 10923899, upload-time = "2026-06-12T02:28:00.223Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/a58a4443a4d052a4ea77557478336aefc26c7981f6408d37adba763aa758/matplotlib-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e", size = 9329528, upload-time = "2026-06-12T02:28:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/61/0f/4b669589d47733b97ab9df4b58d6fc1e68acb5ea42a928dc7cbdd6bf5871/matplotlib-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:9dd11fb612ce7bc60b1de5b4fc87ff959d22317b5de42aabf392f66f97af22eb", size = 9003413, upload-time = "2026-06-12T02:28:04.49Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/aa47f156b061d14c98b906f76c428507397708ec63ff94f410ae1752b426/matplotlib-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce3b839b34ae1f430b4616893a2945a2999debaa7e94e7e29a2a8bbf286f7b5", size = 9450532, upload-time = "2026-06-12T02:28:06.769Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4f/5a9eb0375e81413953febf8af7b012a6b6357f53438a15c4f5ad86c6bbb5/matplotlib-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373db8f91214e8ccaf35ac833cc1dd59dd961e148bbd55dd027141591dde1313", size = 9279760, upload-time = "2026-06-12T02:28:09.152Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c0/1117d53077e3ac3152503a84e9cf7a5c239576805ee71276e80c2aaa7471/matplotlib-3.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be152b7570324dc8d01574cc9474dd2d803237acf528bcbb5b211fa347461a09", size = 10031623, upload-time = "2026-06-12T02:28:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/92/7e/e937138daffad65b71bf831a377809dcbc830fb4f31a31e067dc1faa2575/matplotlib-3.11.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:126f256df600652d7e4b394cf3164ff75210a00038f287c95a012a6f58d0e83f", size = 10839372, upload-time = "2026-06-12T02:28:14.102Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c2/438ecc197ffb8023b6b9922915542f2172f5fd45b76703b0b4fc47322243/matplotlib-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:03acfeddf87b0dddb11b081ef7740ad445a3ca8bcb6b8e3011b08f2cf802b75c", size = 10924099, upload-time = "2026-06-12T02:28:16.383Z" }, + { url = "https://files.pythonhosted.org/packages/40/2e/395883da416f378b3ed2c9f3e843ac477eae1ce731b671b79adaa6f0bacd/matplotlib-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ab3722f04f3ff34c23b5012c5873d2894174e06c3822fcdac3610965a5ac7d06", size = 9329727, upload-time = "2026-06-12T02:28:18.581Z" }, + { url = "https://files.pythonhosted.org/packages/61/82/2c388956abf8bf392dfb5b8917c502f1082df6a941b781ab8c8e5ba2474b/matplotlib-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c945824670fb8915b4ac879e5e61f3c58e0913022f70a0de4c082b17372f8771", size = 9003506, upload-time = "2026-06-12T02:28:20.474Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c1/34454baa44da7975ada82e9aea37105ec47059514dc967d3be14426ba8dc/matplotlib-3.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3489c3dc487669b4a980bc3068f87856de7a1564248d3f6c629efb2a58b03f24", size = 9499838, upload-time = "2026-06-12T02:28:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c3/98fe79a398cf232219f090163a7fa7e6766e9f2e0ad26df54d6f8934d8ee/matplotlib-3.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6a98f5476ce784a50ce09998f4ae1e6a9f25043cef8a480c98949902eda74620", size = 9332298, upload-time = "2026-06-12T02:28:24.796Z" }, + { url = "https://files.pythonhosted.org/packages/95/e4/b4b7c33151e74e5c802f3cde1ba807ebfc38401e329b44e215a5888dd76d/matplotlib-3.11.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:565af866fd63e4bd3f987d580afe27c44c2552a3b3305f4ecbb85133601ea6f3", size = 10045491, upload-time = "2026-06-12T02:28:27.141Z" }, + { url = "https://files.pythonhosted.org/packages/71/28/394548efd68354110c1a1be11fe6b6e559e06d1a23da35908a0e316c55a9/matplotlib-3.11.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b3e64dea5062c570f04358e2711859f3531b459f29516274fbad889079e4f3", size = 10857059, upload-time = "2026-06-12T02:28:29.222Z" }, + { url = "https://files.pythonhosted.org/packages/c8/44/e7922e6e2a4d63bdfbc9dc4a53e3850ab438d46cf42e6779bb15ec92c948/matplotlib-3.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:942b37c5db1899610bd1543ce8e13e4ecff9a4633e7f63bb6aa9205d2644ebd1", size = 10939576, upload-time = "2026-06-12T02:28:31.66Z" }, + { url = "https://files.pythonhosted.org/packages/3d/be/b1ca96003a441d619b727fee21d671fdff7a5ce2f1bb797b2521aa2f679a/matplotlib-3.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c08e649a6313e1291e713623b97a38e5bb4aa580b2a100a94a3309bc6b9c8eb3", size = 9379519, upload-time = "2026-06-12T02:28:33.888Z" }, + { url = "https://files.pythonhosted.org/packages/e3/72/4bf3b91821c34596dd6a7bdac5836d94f744144c8208939ef49d8ec43f7e/matplotlib-3.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2746cd2c113742ff6ce37a864c5ac5fd7aa644568f445e66166e457ac78e40e0", size = 9055456, upload-time = "2026-06-12T02:28:35.878Z" }, + { url = "https://files.pythonhosted.org/packages/57/52/a94102ac99eb78e2fe9b826674f9ef9ee23327110ea6ab4776c1b4eb6209/matplotlib-3.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3338e3e3de128cf50d0d2fb92a122815daf9c755bd882a474343c05f8fd7ec79", size = 9452137, upload-time = "2026-06-12T02:28:37.93Z" }, + { url = "https://files.pythonhosted.org/packages/7c/03/b8cdb625a21f710dfa11bbca1f48fb4057d2c0286975f8b415bf80942c99/matplotlib-3.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:25c2e5455efd8d99f41fb79871a31feb7d301569642e332ec58d72cfe9282bc3", size = 9281514, upload-time = "2026-06-12T02:28:40.028Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2d/4e1240ea82ee197dfb3851e71f71c87eeeb975f1753b56a0588e4e80739a/matplotlib-3.11.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9695457a467ff86d23f35037a43deb6f1134dd6d3e2ac8ce1e2087cff09ffb9", size = 10843005, upload-time = "2026-06-12T02:28:42.39Z" }, + { url = "https://files.pythonhosted.org/packages/29/dc/6377ecfaa5fef79430f74a1a16638b4e2aa30d4692bae2c19f9d76fe3b01/matplotlib-3.11.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19c16c61dea63b3582918503e6b294193961261d9daa806d4ae2151f1ad05430", size = 11127459, upload-time = "2026-06-12T02:28:44.483Z" }, + { url = "https://files.pythonhosted.org/packages/6f/41/795c405aa7560443a3b01309424cde4a1113b85c90b8a63417444a749617/matplotlib-3.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2d72ea8b7924f3cb955e61518d21e43b3df1e6c8a793b480a0c1214f185d30ba", size = 10925160, upload-time = "2026-06-12T02:28:46.564Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f7/3a9e6389a7cfaeff76c56e40c2dabcb13110e21e82f837228c834ebe748c/matplotlib-3.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:1c02da0a629dfa9debf52725ea06866b74c1fb70a895bae05e4493d34074f9f2", size = 9485186, upload-time = "2026-06-12T02:28:49.344Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c0/396478ee7cf2091d182db8b4a8695f6a37f1ddb978989cf9dbb84cd5c123/matplotlib-3.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aa55d73b3117d4b07f959cd9eb6f69b375d8df3414139c479388e551aa5d999d", size = 9160349, upload-time = "2026-06-12T02:28:51.382Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6f/1c3bd51bb2b34eaacdcf3c3d859dbb357f952fc8020c617dc118ad7c9e38/matplotlib-3.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9d8c6e7cd2f0ddf11d8d92e520dd1d9d2abb0cf6ac8831e338666c81e905847", size = 9500921, upload-time = "2026-06-12T02:28:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/4d861d0121840cb1a3fd4a10deb211efd6fccd481ed23e553f31f4f4da4a/matplotlib-3.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:be050fcf32f729eda99f7f75a80bf67612ce16ab9ac1c23a387dcaede95cb70e", size = 9332190, upload-time = "2026-06-12T02:28:55.623Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cb/22f6bc35711a0b5639a784e74e653e77c86210bd4304449dd399a482f74e/matplotlib-3.11.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfabef0230d0697aa0d717385194dd41162e00207a68bf4abf94c2bf4c27dca0", size = 10854181, upload-time = "2026-06-12T02:28:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/3f/7e/9a9eaca731a2939589da520f0ebe8fd8753d0f51fca98c7d20af6dbe261a/matplotlib-3.11.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1644db30e759199443493ac5e5caec24fdb775a8f6123021f85ba47c4133c3cb", size = 11137715, upload-time = "2026-06-12T02:29:00.555Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f9/9b030b6088354acb0296871bb624b25befc1c42509d3c6cd17420c83a5b8/matplotlib-3.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15b0d160079cb10699a0e98b5989c70677b2df7cacdc62af67c30f2facec46d9", size = 10939427, upload-time = "2026-06-12T02:29:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/59/94/6b273eaee4ee250863567d100865da61a5c1527fa67f527b7ed22e0dd29c/matplotlib-3.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:446307e6b04b57b1f1239e228a1ec2af0d589a1008cebc3dfa3f5441d095cfb6", size = 9535809, upload-time = "2026-06-12T02:29:04.994Z" }, + { url = "https://files.pythonhosted.org/packages/60/95/1d36bddf2b7e2692c1540e78a6e5bc88bc1496b137e3e35a611f91b65ac3/matplotlib-3.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:652fb5696271d4c50f196d22a5ff4f8e4444c74f847423570d7dc0aa2bbd0159", size = 9209226, upload-time = "2026-06-12T02:29:07.033Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c2/f5da6cd37ed6871f5c9b3c0507ddb69f14d6c36fac4541e4e0c60cb8cdfc/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81ae77077a1e16d37a5b61096ccb07c8d90a99b518fa8256b8f21578932f2f62", size = 9434094, upload-time = "2026-06-12T02:29:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/f8/07/56f66906e0f87a0c6d0d0acbd34dbc9432b1931d8f26ef618bd6f92932a9/matplotlib-3.11.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ddef37840695f5eef65f9f070fe2d2f510f584c2156203f9f622a5b0584efffd", size = 9262183, upload-time = "2026-06-12T02:29:11.283Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/c4ecab06b7ea36a570c4f3bd2d48d1799fd5d9174470e45c2194199431e7/matplotlib-3.11.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf662e5ac5707658cb931e19972c4bd99f7b4f8b7bf79d3c821d239fa6b71e64", size = 10015653, upload-time = "2026-06-12T02:29:13.251Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "minio" +version = "7.2.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi" }, + { name = "certifi" }, + { name = "pycryptodome" }, + { name = "typing-extensions" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/df/6dfc6540f96a74125a11653cce717603fd5b7d0001a8e847b3e54e72d238/minio-7.2.20.tar.gz", hash = "sha256:95898b7a023fbbfde375985aa77e2cd6a0762268db79cf886f002a9ea8e68598", size = 136113, upload-time = "2025-11-27T00:37:15.569Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/9a/b697530a882588a84db616580f2ba5d1d515c815e11c30d219145afeec87/minio-7.2.20-py3-none-any.whl", hash = "sha256:eb33dd2fb80e04c3726a76b13241c6be3c4c46f8d81e1d58e757786f6501897e", size = 93751, upload-time = "2025-11-27T00:37:13.993Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/0b/19348d4c98980c4851d2f943f8ebafdece2ae7ef737adcfa5994ce8e5f10/multidict-6.7.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c93c3db7ea657dd4637d57e74ab73de31bccefe144d3d4ce370052035bc85fb5", size = 77176, upload-time = "2026-01-26T02:42:59.784Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/9de3f8077852e3d438215c81e9b691244532d2e05b4270e89ce67b7d103c/multidict-6.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:974e72a2474600827abaeda71af0c53d9ebbc3c2eb7da37b37d7829ae31232d8", size = 44996, upload-time = "2026-01-26T02:43:01.674Z" }, + { url = "https://files.pythonhosted.org/packages/31/5c/08c7f7fe311f32e83f7621cd3f99d805f45519cd06fafb247628b861da7d/multidict-6.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdea2e7b2456cfb6694fb113066fd0ec7ea4d67e3a35e1f4cbeea0b448bf5872", size = 44631, upload-time = "2026-01-26T02:43:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/b7/7f/0e3b1390ae772f27501199996b94b52ceeb64fe6f9120a32c6c3f6b781be/multidict-6.7.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17207077e29342fdc2c9a82e4b306f1127bf1ea91f8b71e02d4798a70bb99991", size = 242561, upload-time = "2026-01-26T02:43:04.733Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f4/8719f4f167586af317b69dd3e90f913416c91ca610cac79a45c53f590312/multidict-6.7.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4f49cb5661344764e4c7c7973e92a47a59b8fc19b6523649ec9dc4960e58a03", size = 242223, upload-time = "2026-01-26T02:43:06.695Z" }, + { url = "https://files.pythonhosted.org/packages/47/ab/7c36164cce64a6ad19c6d9a85377b7178ecf3b89f8fd589c73381a5eedfd/multidict-6.7.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a9fc4caa29e2e6ae408d1c450ac8bf19892c5fca83ee634ecd88a53332c59981", size = 222322, upload-time = "2026-01-26T02:43:08.472Z" }, + { url = "https://files.pythonhosted.org/packages/f5/79/a25add6fb38035b5337bc5734f296d9afc99163403bbcf56d4170f97eb62/multidict-6.7.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c5f0c21549ab432b57dcc82130f388d84ad8179824cc3f223d5e7cfbfd4143f6", size = 254005, upload-time = "2026-01-26T02:43:10.127Z" }, + { url = "https://files.pythonhosted.org/packages/4a/7b/64a87cf98e12f756fc8bd444b001232ffff2be37288f018ad0d3f0aae931/multidict-6.7.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7dfb78d966b2c906ae1d28ccf6e6712a3cd04407ee5088cd276fe8cb42186190", size = 251173, upload-time = "2026-01-26T02:43:11.731Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/b605473de2bb404e742f2cc3583d12aedb2352a70e49ae8fce455b50c5aa/multidict-6.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9b0d9b91d1aa44db9c1f1ecd0d9d2ae610b2f4f856448664e01a3b35899f3f92", size = 243273, upload-time = "2026-01-26T02:43:13.063Z" }, + { url = "https://files.pythonhosted.org/packages/03/65/11492d6a0e259783720f3bc1d9ea55579a76f1407e31ed44045c99542004/multidict-6.7.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dd96c01a9dcd4889dcfcf9eb5544ca0c77603f239e3ffab0524ec17aea9a93ee", size = 238956, upload-time = "2026-01-26T02:43:14.843Z" }, + { url = "https://files.pythonhosted.org/packages/5f/a7/7ee591302af64e7c196fb63fe856c788993c1372df765102bd0448e7e165/multidict-6.7.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:067343c68cd6612d375710f895337b3a98a033c94f14b9a99eff902f205424e2", size = 233477, upload-time = "2026-01-26T02:43:16.025Z" }, + { url = "https://files.pythonhosted.org/packages/9c/99/c109962d58756c35fd9992fed7f2355303846ea2ff054bb5f5e9d6b888de/multidict-6.7.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5884a04f4ff56c6120f6ccf703bdeb8b5079d808ba604d4d53aec0d55dc33568", size = 243615, upload-time = "2026-01-26T02:43:17.84Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5f/1973e7c771c86e93dcfe1c9cc55a5481b610f6614acfc28c0d326fe6bfad/multidict-6.7.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8affcf1c98b82bc901702eb73b6947a1bfa170823c153fe8a47b5f5f02e48e40", size = 249930, upload-time = "2026-01-26T02:43:19.06Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a5/f170fc2268c3243853580203378cd522446b2df632061e0a5409817854c7/multidict-6.7.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0d17522c37d03e85c8098ec8431636309b2682cf12e58f4dbc76121fb50e4962", size = 243807, upload-time = "2026-01-26T02:43:20.286Z" }, + { url = "https://files.pythonhosted.org/packages/de/01/73856fab6d125e5bc652c3986b90e8699a95e84b48d72f39ade6c0e74a8c/multidict-6.7.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:24c0cf81544ca5e17cfcb6e482e7a82cd475925242b308b890c9452a074d4505", size = 239103, upload-time = "2026-01-26T02:43:21.508Z" }, + { url = "https://files.pythonhosted.org/packages/e7/46/f1220bd9944d8aa40d8ccff100eeeee19b505b857b6f603d6078cb5315b0/multidict-6.7.1-cp310-cp310-win32.whl", hash = "sha256:d82dd730a95e6643802f4454b8fdecdf08667881a9c5670db85bc5a56693f122", size = 41416, upload-time = "2026-01-26T02:43:22.703Z" }, + { url = "https://files.pythonhosted.org/packages/68/00/9b38e272a770303692fc406c36e1a4c740f401522d5787691eb38a8925a8/multidict-6.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:cf37cbe5ced48d417ba045aca1b21bafca67489452debcde94778a576666a1df", size = 46022, upload-time = "2026-01-26T02:43:23.77Z" }, + { url = "https://files.pythonhosted.org/packages/64/65/d8d42490c02ee07b6bbe00f7190d70bb4738b3cce7629aaf9f213ef730dd/multidict-6.7.1-cp310-cp310-win_arm64.whl", hash = "sha256:59bc83d3f66b41dac1e7460aac1d196edc70c9ba3094965c467715a70ecb46db", size = 43238, upload-time = "2026-01-26T02:43:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "multiprocess" +version = "0.70.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dill" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a2/f2/e783ac7f2aeeed14e9e12801f22529cc7e6b7ab80928d6dcce4e9f00922d/multiprocess-0.70.19.tar.gz", hash = "sha256:952021e0e6c55a4a9fe4cd787895b86e239a40e76802a789d6305398d3975897", size = 2079989, upload-time = "2026-01-19T06:47:39.744Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/b6/10832f96b499690854e574360be342a282f5f7dba58eff791299ff6c0637/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:02e5c35d7d6cd2bdc89c1858867f7bde4012837411023a4696c148c1bdd7c80e", size = 135131, upload-time = "2026-01-19T06:47:20.479Z" }, + { url = "https://files.pythonhosted.org/packages/99/50/faef2d8106534b0dc4a0b772668a1a99682696ebf17d3c0f13f2ed6a656a/multiprocess-0.70.19-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:79576c02d1207ec405b00cabf2c643c36070800cca433860e14539df7818b2aa", size = 135131, upload-time = "2026-01-19T06:47:21.879Z" }, + { url = "https://files.pythonhosted.org/packages/94/b1/0b71d18b76bf423c2e8ee00b31db37d17297ab3b4db44e188692afdca628/multiprocess-0.70.19-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6b6d78d43a03b68014ca1f0b7937d965393a670c5de7c29026beb2258f2f896", size = 135134, upload-time = "2026-01-19T06:47:23.262Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/714635c727dbfc251139226fa4eaf1b07f00dc12d9cd2eb25f931adaf873/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1bbf1b69af1cf64cd05f65337d9215b88079ec819cd0ea7bac4dab84e162efe7", size = 144743, upload-time = "2026-01-19T06:47:24.562Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e1/155f6abf5e6b5d9cef29b6d0167c180846157a4aca9b9bee1a217f67c959/multiprocess-0.70.19-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:5be9ec7f0c1c49a4f4a6fd20d5dda4aeabc2d39a50f4ad53720f1cd02b3a7c2e", size = 144738, upload-time = "2026-01-19T06:47:26.636Z" }, + { url = "https://files.pythonhosted.org/packages/af/cb/f421c2869d75750a4f32301cc20c4b63fab6376e9a75c8e5e655bdeb3d9b/multiprocess-0.70.19-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1c3dce098845a0db43b32a0b76a228ca059a668071cfeaa0f40c36c0b1585d45", size = 144741, upload-time = "2026-01-19T06:47:27.985Z" }, + { url = "https://files.pythonhosted.org/packages/e3/45/8004d1e6b9185c1a444d6b55ac5682acf9d98035e54386d967366035a03a/multiprocess-0.70.19-py310-none-any.whl", hash = "sha256:97404393419dcb2a8385910864eedf47a3cadf82c66345b44f036420eb0b5d87", size = 134948, upload-time = "2026-01-19T06:47:32.325Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/dec9722dc3474c164a0b6bcd9a7ed7da542c98af8cabce05374abab35edd/multiprocess-0.70.19-py311-none-any.whl", hash = "sha256:928851ae7973aea4ce0eaf330bbdafb2e01398a91518d5c8818802845564f45c", size = 144457, upload-time = "2026-01-19T06:47:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/71/70/38998b950a97ea279e6bd657575d22d1a2047256caf707d9a10fbce4f065/multiprocess-0.70.19-py312-none-any.whl", hash = "sha256:3a56c0e85dd5025161bac5ce138dcac1e49174c7d8e74596537e729fd5c53c28", size = 150281, upload-time = "2026-01-19T06:47:35.037Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/d2c27e03cb84251dfe7249b8e82923643c6d48fa4883b9476b025e7dc7eb/multiprocess-0.70.19-py313-none-any.whl", hash = "sha256:8d5eb4ec5017ba2fab4e34a747c6d2c2b6fecfe9e7236e77988db91580ada952", size = 156414, upload-time = "2026-01-19T06:47:35.915Z" }, + { url = "https://files.pythonhosted.org/packages/a0/61/af9115673a5870fd885247e2f1b68c4f1197737da315b520a91c757a861a/multiprocess-0.70.19-py314-none-any.whl", hash = "sha256:e8cc7fbdff15c0613f0a1f1f8744bef961b0a164c0ca29bdff53e9d2d93c5e5f", size = 160318, upload-time = "2026-01-19T06:47:37.497Z" }, + { url = "https://files.pythonhosted.org/packages/7e/82/69e539c4c2027f1e1697e09aaa2449243085a0edf81ae2c6341e84d769b6/multiprocess-0.70.19-py39-none-any.whl", hash = "sha256:0d4b4397ed669d371c81dcd1ef33fd384a44d6c3de1bd0ca7ac06d837720d3c5", size = 133477, upload-time = "2026-01-19T06:47:38.619Z" }, +] + +[[package]] +name = "mypy" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/15/cca9d88503549ed6fedeaa1d448cdddd542ee8a490232d732e278036fbf2/mypy-2.1.0.tar.gz", hash = "sha256:81e76ad12c2d804512e9b13240d1588316531bfba07558286078bfbce9613633", size = 3898359, upload-time = "2026-05-11T18:37:36.237Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/71/d351dca3e9b30da2328ee9d445c88b8388072808ebfbc49eb69d30b67749/mypy-2.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:11a6beb180257a805961aea9ec591bbd0bd17f1e18d35b8456d57aee5bedfedc", size = 14778792, upload-time = "2026-05-11T18:36:23.605Z" }, + { url = "https://files.pythonhosted.org/packages/2f/45/7d51594b644c17c0bcf74ed8cd5fc33b324276d708e8506f220b70dab9d9/mypy-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ef78c1d306bbf9a8a12f526c44902c9c28dffd6c52c52bf6a72641ce18d3849", size = 13645739, upload-time = "2026-05-11T18:37:22.752Z" }, + { url = "https://files.pythonhosted.org/packages/65/01/455c31b170e9468265074840bf18863a8482a24103fdaabe4e199392aa5f/mypy-2.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c209a90853081ff01d01ee895cafe10f7db1474e0d95beaeef0f6c1db9119bbd", size = 14074199, upload-time = "2026-05-11T18:35:09.292Z" }, + { url = "https://files.pythonhosted.org/packages/41/5a/93093f0b29a9e982deafde698f740a2eb2e05886e79ccf0594c7fd5413a3/mypy-2.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47cebf61abde7c088a4e27718a8b13a81655686b2e9c251f5c0915a802248166", size = 14953128, upload-time = "2026-05-11T18:31:57.678Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2f/a196f5331d96170ad3d28f144d2aba690d4b2911381f68d51e489c7ab82a/mypy-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d57a90ae5e872138a425ec328edbc9b235d1934c4377881a33ec05b341acc9a8", size = 15249378, upload-time = "2026-05-11T18:33:00.101Z" }, + { url = "https://files.pythonhosted.org/packages/54/de/94d321cc12da9f71341ac0c270efbed5c725750c7b4c334d957de9a087d9/mypy-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:aea7f7a8a55b459c34275fc468ada6ca7c173a5e43a68f5dbe588a563d8a06b8", size = 11060994, upload-time = "2026-05-11T18:33:18.848Z" }, + { url = "https://files.pythonhosted.org/packages/e1/62/0c27ca55219a7c764a7fb88c7bb2b7b2f9780ade8bbf16bc8ed8400eef6b/mypy-2.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:c989640253f0d76843e9c6c1bbf4bd48c5e85ada61bde4beb37cb3eca035685e", size = 9976743, upload-time = "2026-05-11T18:31:25.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a1/639f3024794a2a15899cb90707fe02e044c4412794c39c5769fd3df2e2ef/mypy-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a683016b16fe2f572dc04c72be7ee0504ac1605a265d0200f5cea695fb788f41", size = 14691685, upload-time = "2026-05-11T18:33:27.973Z" }, + { url = "https://files.pythonhosted.org/packages/3b/08/9a585dea4325f20d8b80dc78623fa50d1fd2173b710f6237afd6ba6ab39b/mypy-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a293c534adb55271fef24a26da04b855540a8c13cc07bc5917b9fd2c394f2ca", size = 13555165, upload-time = "2026-05-11T18:32:16.107Z" }, + { url = "https://files.pythonhosted.org/packages/81/dc/7c42cc9c6cb01e8eb09961f1f738741d3e9c7e9d5c5b30ec69222625cd5f/mypy-2.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7406f4d048e71e576f5356d317e5b0a9e666dfd966bd99f9d14ca06e1a341538", size = 13994376, upload-time = "2026-05-11T18:32:39.256Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/285946c33bce716e082c11dfeee9ee196eaf1f5042efb3581a31f9f205e4/mypy-2.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0210d626fc8b31ccc90233754c7bc90e1f43205e85d96387f7db1285b55c398", size = 14864618, upload-time = "2026-05-11T18:34:49.765Z" }, + { url = "https://files.pythonhosted.org/packages/2b/83/82397f48af6c27e295d57979ded8490c9829040152cf7571b2f026aeb9a0/mypy-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3712c20deed54e814eaaa825603bada8ea1c390670a397c95b98405347acc563", size = 15102063, upload-time = "2026-05-11T18:34:05.855Z" }, + { url = "https://files.pythonhosted.org/packages/40/68/b02dec39057b88eb03dc0aa854732e26e8361f34f9d0e20c7614967d1eba/mypy-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fcaa0e479066e31f7cceb6a3bea39cb22b2ff51a6b2f24f193d19179ba17c389", size = 11060564, upload-time = "2026-05-11T18:35:36.494Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a8/ea3dcbef31f99b634f2ee23bb0321cbc8c1b388b76a861eb849f13c347dc/mypy-2.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:0b1a5260c95aa443083f9ed3592662941951bca3d4ca224a5dc517c38b7cf666", size = 9966983, upload-time = "2026-05-11T18:37:14.139Z" }, + { url = "https://files.pythonhosted.org/packages/95/b1/55861beb5c339b44f9a2ba92df9e2cb1eeb4ae1eee674cdf7772c797778b/mypy-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:244358bf1c0da7722230bce60683d52e8e9fd030554926f15b747a84efb5b3af", size = 14874381, upload-time = "2026-05-11T18:37:31.784Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b3/b7f770114b7d0ac92d0f76e8d93c2780844a70488a90e91821927850da86/mypy-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ec7c57657493c7a75534df2751c8ae2cda383c16ecc55d2106c54476b1b16f6", size = 13665501, upload-time = "2026-05-11T18:34:23.063Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/8ae2037967e2126689a0c11d99e2b707134a565191e92c60ca2572aec60a/mypy-2.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8161b6ff4392410023224f0969d17db93e1e154bc3e4ba62598e720723ae211", size = 14045750, upload-time = "2026-05-11T18:31:48.151Z" }, + { url = "https://files.pythonhosted.org/packages/a0/32/615eb5911859e43d054941b0d0a7d06cfa2870eba86529cf385b052b111c/mypy-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf03e12003084a67395184d3eb8cbd6a489dc3655b5664b28c210a9e2403ab0b", size = 15061630, upload-time = "2026-05-11T18:37:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/d4/03/4eafbfff8bfab1b87082741eae6e6a624028c984e6708b73bce2a8570c9d/mypy-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:20509760fd791c51579d573153407d226385ec1f8bcce55d730b354f3336bc22", size = 15288831, upload-time = "2026-05-11T18:31:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/919661478e5891a3c96e549c036e467e64563ab85995b10c53c8358e16a3/mypy-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:6753d0c1fdd6b1a23b9e4f283ce80b2153b724adcb2653b20b85a8a28ac6436b", size = 11135228, upload-time = "2026-05-11T18:34:31.23Z" }, + { url = "https://files.pythonhosted.org/packages/24/0a/6a12b9782ca0831a553192f351679f4548abc9d19a7cc93bb7feb02084c7/mypy-2.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:98ebb6589bb3b6d0c6f0c459d53ca55b8091fbc13d277c4041c885392e8195e8", size = 10040684, upload-time = "2026-05-11T18:36:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/dd/c7191469c777f07689c032a8f7326e393ea34c92d6d76eb7ce5ba57ea66d/mypy-2.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35aac3bb114e03888f535d5eb51b8bafbb3266586b599da1940f9b1be3ec5bd5", size = 14852174, upload-time = "2026-05-11T18:31:38.929Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/aed55408879043d72bb9135f4d0d19a02b886dd569631e113e3d2706cb8d/mypy-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8de55a8c861f2a49331f807be98d90caeceeef520bde13d43a160207f8af613e", size = 13651542, upload-time = "2026-05-11T18:36:04.636Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8e/f371a824b1f1fa8ea6e3dbb8703d232977d572be2329554a3bc4d960302f/mypy-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fdf2941a07434af755837d9880f7d7d25f1dacb1af9dcd4b9b66f2220a3024e", size = 14033929, upload-time = "2026-05-11T18:35:55.742Z" }, + { url = "https://files.pythonhosted.org/packages/94/21/f54be870d6dd53a82c674407e0f8eed7174b05ec78d42e5abd7b42e84fd5/mypy-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e195b817c13f02352a9c124301f9f30f078405444679b6753c1b96b6eed37285", size = 15039200, upload-time = "2026-05-11T18:33:10.281Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/bf21748626a40ce59fd29a39386ab46afec88b7bd2f0fa6c3a97c995523f/mypy-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5431d42af987ebd92ba2f71d45c85ed41d8e6ca9f5fd209a69f68f707d2469e5", size = 15272690, upload-time = "2026-05-11T18:32:07.205Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d7/9e90d2cf47100bea550ed2bc7b0d4de3a62181d84d5e37da0003e8462637/mypy-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:767fe8c66dc3e01e19e1737d4c38ebefead16125e1b8e58ad421903b376f5c65", size = 11147435, upload-time = "2026-05-11T18:33:56.477Z" }, + { url = "https://files.pythonhosted.org/packages/ec/46/e5c449e858798e35ffc90946282a27c62a77be743fe17480e4977374eb91/mypy-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:ecfe70d43775ab99562ab128ce49854a362044c9f894961f68f898c23cb7429d", size = 10035052, upload-time = "2026-05-11T18:32:30.049Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ca/b279a672e874aedd5498ae25f722dacc8aa86bbffb939b3f97cbb1cf6686/mypy-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7354c5a7f69d9345c3d6e69921d57088eea3ddeeb6b20d34c1b3855b02c36ec2", size = 14848422, upload-time = "2026-05-11T18:35:45.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/e6/3efe56c631d959b9b4454e208b0ac4b7f4f58b404c89f8bec7b49efdfc21/mypy-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:49890d4f76ac9e06ec117f9e09f3174da70a620a0c300953d8595c926e80947f", size = 13677374, upload-time = "2026-05-11T18:36:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/84/7f/8107ea87a44fd1f1b59882442f033c9c3488c127201b1d1d15f1cbd6022e/mypy-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:761be68e023ef5d94678772396a8af1220030f80837a3afd8d0aef3b419666f4", size = 14055743, upload-time = "2026-05-11T18:35:18.361Z" }, + { url = "https://files.pythonhosted.org/packages/51/4d/b6d34db183133b83761b9199a82d31557cdbb70a380d8c3b3438e11882a3/mypy-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c90345fc182dc363b891350457ec69c35140858538f38b4540845afcc32b1aef", size = 15020937, upload-time = "2026-05-11T18:34:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d7/f08360c691d758acb02f45022c34d98b92892f4ea756644e1000d4b9f3d8/mypy-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b84802e7b5a6daf1f5e15bc9fcd7ddae77be13981ffab037f1c67bb84d67d135", size = 15253371, upload-time = "2026-05-11T18:36:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/67/1b/09460a13719530a19bce27bd3bc8449e83569dd2ba7faf51c9c3c30c0b61/mypy-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:022c771234936ceac541ebaf836fe9e2abeb3f5e09aff21588fe543ff006fe21", size = 11326429, upload-time = "2026-05-11T18:34:13.526Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/75dbf0f82f7b6680340efc614af29dd0b3c17b8a4f1cd09b8bd2fd6bc814/mypy-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:498207db725cec88829a6a5c2fc771205fd043719ef98bc49aba8fb9fc4e6d57", size = 10218799, upload-time = "2026-05-11T18:32:23.491Z" }, + { url = "https://files.pythonhosted.org/packages/b2/66/caca04ed7d972fb6eb6dd1ccd6df1de5c38fae8c5b3dc1c4e8e0d85ee6b9/mypy-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d5e5cad0efeba72b93cd17490cc0d69c5ac9ca132994fe3fb0314808aeeb83e", size = 15923458, upload-time = "2026-05-11T18:35:28.64Z" }, + { url = "https://files.pythonhosted.org/packages/ed/52/2d90cbe49d014b13ed7ff337930c30bad35893fe38a1e4641e756bb62191/mypy-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ff715050c127d724fd260a2e666e7747fdd83511c0c47d449d98238970aef780", size = 14757697, upload-time = "2026-05-11T18:36:14.208Z" }, + { url = "https://files.pythonhosted.org/packages/ac/37/d98f4a14e081b238992d0ed96b6d39c7cc0148c9699eb71eaa68629665ea/mypy-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82208da9e09414d520e912d3e462d454854bed0810b71540bb016dcbca7308fd", size = 15405638, upload-time = "2026-05-11T18:33:48.249Z" }, + { url = "https://files.pythonhosted.org/packages/a3/c2/15c46613b24a84fad2aea1248bf9619b99c2767ae9071fe224c179a0b7d4/mypy-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e79ebc1b904b84f0310dff7469655a9c36c7a68bddb37bdd42b67a332df61d08", size = 16215852, upload-time = "2026-05-11T18:32:50.296Z" }, + { url = "https://files.pythonhosted.org/packages/5c/90/9c16a57f482c76d25f6379762b56bbf65c711d8158cf271fb2802cfb0640/mypy-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e583edc957cfb0deb142079162ae826f58449b116c1d442f2d91c69d9fced081", size = 16452695, upload-time = "2026-05-11T18:33:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/4c/215a4eeb63cacc5f17f516691ea7285d11e249802b942476bff15922a314/mypy-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b33b6cd332695bba180d55e717a79d3038e479a2c49cc5eb3d53603409b9a5d7", size = 12866622, upload-time = "2026-05-11T18:34:39.945Z" }, + { url = "https://files.pythonhosted.org/packages/4b/50/1043e1db5f455ffe4c9ab22747cd8ca2bc492b1e4f4e21b130a44ee2b217/mypy-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:4f910fe825376a7b66ef7ca8c98e5a149e8cd64c19ae71d84047a74ee060d4e6", size = 10610798, upload-time = "2026-05-11T18:36:31.444Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2a/13ca1f292f6db1b98ff495ef3467736b331621c5917cad984b7043e7348d/mypy-2.1.0-py3-none-any.whl", hash = "sha256:a663814603a5c563fb87a4f96fb473eeb30d1f5a4885afcf44f9db000a366289", size = 2693302, upload-time = "2026-05-11T18:31:29.246Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "narwhals" +version = "2.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/66ed1fc6e38a0c0f330627ec5c5d597990d6159b6712b82af0ad2c65f06c/narwhals-2.23.0.tar.gz", hash = "sha256:13e7ff5b4bb4a2f77b907c2e4d8a76e273dfc1323a3c997440a2f9fd26aed408", size = 656209, upload-time = "2026-07-01T11:21:53.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/4e/afc8c31605cb8be1d3bb4438c4d979daa104dab6306cd2b87abe9c3a7299/narwhals-2.23.0-py3-none-any.whl", hash = "sha256:769e7b9ab102c93d8fa019f6b4cd1a657909b04a20bf6210e5a35aae06814ae9", size = 458938, upload-time = "2026-07-01T11:21:51.677Z" }, +] + +[[package]] +name = "netaddr" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/90/188b2a69654f27b221fba92fda7217778208532c962509e959a9cee5229d/netaddr-1.3.0.tar.gz", hash = "sha256:5c3c3d9895b551b763779ba7db7a03487dc1f8e3b385af819af341ae9ef6e48a", size = 2260504, upload-time = "2024-05-28T21:30:37.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/cc/f4fe2c7ce68b92cbf5b2d379ca366e1edae38cccaad00f69f529b460c3ef/netaddr-1.3.0-py3-none-any.whl", hash = "sha256:c2c6a8ebe5554ce33b7d5b3a306b71bbb373e000bbbf2350dd5213cc56e3dbbe", size = 2262023, upload-time = "2024-05-28T21:30:34.191Z" }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263, upload-time = "2024-10-21T12:39:36.247Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "numba" +version = "0.47.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "llvmlite", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/2a/975f49e156dae4edd3ab5afc60e2b3d65add014db2ddbbc23b9bb89882a4/numba-0.47.0.tar.gz", hash = "sha256:c0703df0a0ea2e29fbef7937d9849cc4734253066cb5820c5d6e0851876e3b0a", size = 1935290, upload-time = "2020-01-03T17:03:47.391Z" } + +[[package]] +name = "numba" +version = "0.66.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "llvmlite", marker = "python_full_version < '3.12' or platform_machine != 'x86_64' or sys_platform != 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/a0/570e3dc53e5602b49108f62a13e529f1eec8bfc7ef37d49c825924dcf546/numba-0.66.0.tar.gz", hash = "sha256:b900e63a0e26c05ea9a6d5a3a5a0a177cb64c5011887bf43edb8c3ed2c38d363", size = 2806181, upload-time = "2026-07-01T23:12:46.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/48/d139bde40f2359351bfe26ee1b261937f458ac177ab810d4f045ae1c9d92/numba-0.66.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:27951c47e0def9bf8afe580eb961102902e2fd23cb77924b7d9d7cc0f8b444cb", size = 2727368, upload-time = "2026-07-01T23:12:04.282Z" }, + { url = "https://files.pythonhosted.org/packages/36/e4/b780bfa9191410da50ba249cb3248a75014e17f611e72709cbddcb21f42d/numba-0.66.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc408c54b450f41582f4be1608f8981c1dcc44c7f40355cc150dd93015753407", size = 3803554, upload-time = "2026-07-01T23:12:06.379Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b2/a051b96626bdf5c4d8fa6b8d450605c09638d85dc872ab63ef9a67096dca/numba-0.66.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c14c044c06b453ec3fa7715dfe75425e2ba72c73377a7ffde6d9ec511dfd94c", size = 3510065, upload-time = "2026-07-01T23:12:08.051Z" }, + { url = "https://files.pythonhosted.org/packages/34/01/24dcdc3e919522e2efbd92969c281ff40deb1d5f8a994bcd0057081c158c/numba-0.66.0-cp310-cp310-win_amd64.whl", hash = "sha256:2338cc0d43609fe448930848fd35a5bc688761b986f81b597a6f45cc0f8c9577", size = 2780379, upload-time = "2026-07-01T23:12:09.772Z" }, + { url = "https://files.pythonhosted.org/packages/9e/02/970796b4daa709604cde22e87a7cda9bde473c278ea4a75f59fe38cee47f/numba-0.66.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bbd531c327557a9004507fa6bff06c53ab51a7a5776b75261bb9cef1efe2b2ea", size = 2727049, upload-time = "2026-07-01T23:12:11.296Z" }, + { url = "https://files.pythonhosted.org/packages/8c/99/33a6ed9c1a0b5e42efa98eb0edf617d61dca576c82625947377b1d4540c9/numba-0.66.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc6629becb21a867d85401ec89f426dd24c484a4193ade8a38309debfd1529ca", size = 3808870, upload-time = "2026-07-01T23:12:12.944Z" }, + { url = "https://files.pythonhosted.org/packages/04/20/8c51126025211659235b8de2866dfa226984ae0c8273461a3cf374716741/numba-0.66.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aac69f3ccb8af100f5913c1241edc9692bad1cdd2508721713f426eb06c9a659", size = 3514498, upload-time = "2026-07-01T23:12:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/5e/c9/9476940bc6d5caf5c0cf2e4c5feecbf01244bbe6f914614082dd7a3e520e/numba-0.66.0-cp311-cp311-win_amd64.whl", hash = "sha256:fb601841d9e02e6237bb6522e36d0741614be3cfe2b482a6f00a41b5ba209443", size = 2780225, upload-time = "2026-07-01T23:12:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/62/a3/70deb7f88461c1cd5d16aa990c2380604102661a427667b8950dcdccc27f/numba-0.66.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:53ca5900b7cab15109796030113a6b28576bae5ad7bb507ad6dd1360ddd81ba4", size = 2727264, upload-time = "2026-07-01T23:12:18.669Z" }, + { url = "https://files.pythonhosted.org/packages/2d/55/25c319845e9a4e08f16611ddbda56a192eb7b6ed13e1a2bff2da272ffb97/numba-0.66.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0999e3ee1b18c48e1fb51d11af35ef59852c7f4f50569c9550c25faef0616ad1", size = 3866252, upload-time = "2026-07-01T23:12:20.429Z" }, + { url = "https://files.pythonhosted.org/packages/71/ef/a82d6fd6bf1b0fe461651e924d3647eeec9ac17f8eee4896264bf7480930/numba-0.66.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efe0d2d5099790df945e0cb6e1b3104bd965d7bbfac50d62f1d5d1d6ade0825d", size = 3566974, upload-time = "2026-07-01T23:12:22.116Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/9e6171e378822ab191c7abcfd3d8cfc8644516f6c7834c22e210e4acc070/numba-0.66.0-cp312-cp312-win_amd64.whl", hash = "sha256:b075a4e7ebc43dc6294f223e2821659656209fd5e0ce53245877c23d66d6e1a9", size = 2797403, upload-time = "2026-07-01T23:12:23.724Z" }, + { url = "https://files.pythonhosted.org/packages/03/52/176c02d005c5c5143cde10a85bbcdcb6236d9e34c3aac089380e0506cd1d/numba-0.66.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:380b2556a2019ccd1e956ae77dd257eaa39403f7520768b626d44b755112785e", size = 2727084, upload-time = "2026-07-01T23:12:25.434Z" }, + { url = "https://files.pythonhosted.org/packages/44/b5/e930010965568fe7f2c6c962fd2849d458cb9f62c3ab7584af8a19a2b40a/numba-0.66.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:939316d5d8619751207b8972a67852b5a7646665298cb4de693cd6bf135152f4", size = 3873663, upload-time = "2026-07-01T23:12:27.308Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ec/5b51457cbe96e4831141d83e892e65191b23a1b78728456c62909d231ace/numba-0.66.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdf506775d9f02eb92a87bf5c5b1e0d25506fd18cafd769f4ed914a8feac73e7", size = 3573529, upload-time = "2026-07-01T23:12:28.944Z" }, + { url = "https://files.pythonhosted.org/packages/83/7e/cea7710e96913d3c7f2999f16db1b28e6c5be5171cbf40f77f98333a7243/numba-0.66.0-cp313-cp313-win_amd64.whl", hash = "sha256:c5bfe5350284509ab0474390321454c3a8627a188af5b68c910e83df3e2db4a7", size = 2797247, upload-time = "2026-07-01T23:12:30.774Z" }, + { url = "https://files.pythonhosted.org/packages/96/7a/7e0e73550eb4e41ede6e72fb5371f4539537a4d770a3b73fa9b61aea0622/numba-0.66.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:46ae5f2b19e2af3c33c2df100306a90ea2f981c8158b0390f8bf6c20eee7357e", size = 2727296, upload-time = "2026-07-01T23:12:32.39Z" }, + { url = "https://files.pythonhosted.org/packages/0f/26/885774c006de6620ed3d10f45d8e20fe0b8e6aad6d573211a2cbc8b3e528/numba-0.66.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e2b101f23b8b63978d334574d2039f27f0dccfe1d891756f33a2e2f3e4c88cf4", size = 3842720, upload-time = "2026-07-01T23:12:33.938Z" }, + { url = "https://files.pythonhosted.org/packages/93/99/edebf7de890b73973d839dd971cf73734adfb81ffa1b4504f84b9059c3e5/numba-0.66.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:63b943eb2c9ba371908ce2cd6dfc643db51fc40f7966993376a1701bc922f537", size = 3543537, upload-time = "2026-07-01T23:12:35.566Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/b46ad28ac3681d035ea21365c5e052149062e1a0a9affd0563d2760ea6ff/numba-0.66.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd57790acd20f6a468e0ad333ef6b82355e309a92310fb7dff80e919f01a21a9", size = 2799250, upload-time = "2026-07-01T23:12:37.154Z" }, + { url = "https://files.pythonhosted.org/packages/10/6f/5e77a7397a37dd16f57a7b72e7e470db5227b68e3639df0d13a8e674883d/numba-0.66.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:db7735d15ea17a283d6485b9fa3504769f78fd86e5146638ad5e8da57c031b9e", size = 2730342, upload-time = "2026-07-01T23:12:38.758Z" }, + { url = "https://files.pythonhosted.org/packages/39/fd/e9c9680a3813f3d781c20e5d53c1074801b787d4feecca0472fdd7c05ce1/numba-0.66.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:651a2b53298340956db26ecbe7ab043106b50a40c2807e66d617a4917245a4ab", size = 3878695, upload-time = "2026-07-01T23:12:40.302Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/9b363287b85fcd4537ea3878793822878b2ac1008a78159d2096fea628de/numba-0.66.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c1144ba1720ea59ad79f4f488ed54d149b2613b357f7e445678b7d0739c70e9", size = 3596323, upload-time = "2026-07-01T23:12:42.805Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f2/dca53d50b8f2289dd01954ace9da261e0487d5b74b188b4304e4ecc3492c/numba-0.66.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d426178fb991a85714c43112a8ea7b9d9579ea856ad8dcdb9c1c3941903ba5be", size = 2804772, upload-time = "2026-07-01T23:12:44.399Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" }, + { url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" }, + { url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" }, + { url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" }, + { url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" }, + { url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" }, + { url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" }, + { url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" }, + { url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" }, + { url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" }, + { url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, + { url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" }, + { url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" }, + { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/45/9e/2f562daf80eb8f7a685fb7bea4fda71f6048e4f359d6fdd1b6e70206cb2f/nvidia_cublas-13.1.1.3-py3-none-win_amd64.whl", hash = "sha256:b6cdce694e47ff6aadf0a69df1cab6628d696f5ff56e8d16af50309d855fa20f", size = 404358158, upload-time = "2026-04-08T18:47:26.987Z" }, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.8.4.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/29/99/db44d685f0e257ff0e213ade1964fc459b4a690a73293220e98feb3307cf/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b86f6dd8935884615a0683b663891d43781b819ac4f2ba2b0c9604676af346d0", size = 590537124, upload-time = "2025-03-07T01:43:53.556Z" }, + { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/70/61/7d7b3c70186fb651d0fbd35b01dbfc8e755f69fd58f817f3d0f642df20c3/nvidia_cublas_cu12-12.8.4.1-py3-none-win_amd64.whl", hash = "sha256:47e9b82132fa8d2b4944e708049229601448aaad7e6f296f630f2d1a32de35af", size = 567544208, upload-time = "2025-03-07T01:53:30.535Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, + { url = "https://files.pythonhosted.org/packages/ad/df/b74b10025c1205695c5676373f2edd3e87a7202cc62ead0dfbc373b0f6ea/nvidia_cuda_cupti-13.0.85-py3-none-win_amd64.whl", hash = "sha256:683f58d301548deeefcb8f6fac1b8d907691b9d8b18eccab417f51e362102f00", size = 7736776, upload-time = "2025-09-04T08:38:08.38Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/1f/b3bd73445e5cb342727fd24fe1f7b748f690b460acadc27ea22f904502c8/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4412396548808ddfed3f17a467b104ba7751e6b58678a4b840675c56d21cf7ed", size = 9533318, upload-time = "2025-03-07T01:40:10.421Z" }, + { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { url = "https://files.pythonhosted.org/packages/41/bc/83f5426095d93694ae39fe1311431b5d5a9bb82e48bf0dd8e19be2765942/nvidia_cuda_cupti_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:bb479dcdf7e6d4f8b0b01b115260399bf34154a1a2e9fe11c85c517d87efd98e", size = 7015759, upload-time = "2025-03-07T01:51:11.355Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, + { url = "https://files.pythonhosted.org/packages/4a/af/345fedb9f4c76c84ab4fa445b36bd4048a4d9db60e6bc76b4f913ff4b852/nvidia_cuda_nvrtc-13.0.88-py3-none-win_amd64.whl", hash = "sha256:6bcd4e7f8e205cbe644f5a98f2f799bef9556fefc89dd786e79a16312ce49872", size = 76807835, upload-time = "2025-09-04T08:39:15.274Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d1/e50d0acaab360482034b84b6e27ee83c6738f7d32182b987f9c7a4e32962/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc1fec1e1637854b4c0a65fb9a8346b51dd9ee69e61ebaccc82058441f15bce8", size = 43106076, upload-time = "2025-03-07T01:41:59.817Z" }, + { url = "https://files.pythonhosted.org/packages/45/51/52a3d84baa2136cc8df15500ad731d74d3a1114d4c123e043cb608d4a32b/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-win_amd64.whl", hash = "sha256:7a4b6b2904850fe78e0bd179c4b655c404d4bb799ef03ddc60804247099ae909", size = 73586838, upload-time = "2025-03-07T01:52:13.483Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, + { url = "https://files.pythonhosted.org/packages/b7/94/6b867483bec07da24ffa32736c79fabb94ef3a7af4d787a9d4a974868576/nvidia_cuda_runtime-13.0.96-py3-none-win_amd64.whl", hash = "sha256:f79298c8a098cec150a597c8eba58ecdab96e3bdc4b9bc4f9983635031740492", size = 2927037, upload-time = "2025-10-09T09:04:23.782Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/75/f865a3b236e4647605ea34cc450900854ba123834a5f1598e160b9530c3a/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:52bf7bbee900262ffefe5e9d5a2a69a30d97e2bc5bb6cc866688caa976966e3d", size = 965265, upload-time = "2025-03-07T01:39:43.533Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/30/a5/a515b7600ad361ea14bfa13fb4d6687abf500adc270f19e89849c0590492/nvidia_cuda_runtime_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:c0c6027f01505bfed6c3b21ec546f69c687689aad5f1a377554bc6ca4aa993a8", size = 944318, upload-time = "2025-03-07T01:51:01.794Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.19.0.56" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/b8/277c51962ee46fa3e5b203ac5f76107c650f781d6891e681e28e6f3e9fe6/nvidia_cudnn_cu12-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:08caaf27fe556aca82a3ee3b5aa49a77e7de0cfcb7ff4e5c29da426387a8267e", size = 656910700, upload-time = "2026-02-03T20:40:25.508Z" }, + { url = "https://files.pythonhosted.org/packages/c5/41/65225d42fba06fb3dd3972485ea258e7dd07a40d6e01c95da6766ad87354/nvidia_cudnn_cu12-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ac6ad90a075bb33a94f2b4cf4622eac13dd4dc65cf6dd9c7572a318516a36625", size = 657906812, upload-time = "2026-02-03T20:44:12.638Z" }, + { url = "https://files.pythonhosted.org/packages/a7/a5/48f07449fc9c6cc146dcafe6149fa5d69630137d2ec5b7d9e09f255fadd7/nvidia_cudnn_cu12-9.19.0.56-py3-none-win_amd64.whl", hash = "sha256:cec70596b9ce878fab83810c3f5a2e606d35f510e5fee579759e4cbc68a23750", size = 644003014, upload-time = "2026-02-03T20:46:25.768Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/78/39/21507455b1bca8b5702a9e9fc6ce73735f216f558dac2c9ede58e4d456b8/nvidia_cudnn_cu13-9.20.0.48-py3-none-win_amd64.whl", hash = "sha256:af8139732b99c0118be65ea5aac97f0d46018f8c552889e49d2fb0c6261a4a24", size = 350712614, upload-time = "2026-03-09T19:31:11.398Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/f8af21a2ed1beed337a6a02c5a28aeb85441f4d578ec3d529543c775ea4b/nvidia_cufft-12.0.0.61-py3-none-win_amd64.whl", hash = "sha256:2abce5b39d2f5ae12730fb7e5db6696533e36c26e2d3e8fd1750bdd2853364eb", size = 213342123, upload-time = "2025-09-04T08:40:51.145Z" }, +] + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.3.3.83" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/bc/7771846d3a0272026c416fbb7e5f4c1f146d6d80704534d0b187dd6f4800/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:848ef7224d6305cdb2a4df928759dca7b1201874787083b6e7550dd6765ce69a", size = 193109211, upload-time = "2025-03-07T01:44:56.873Z" }, + { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, + { url = "https://files.pythonhosted.org/packages/7d/ec/ce1629f1e478bb5ccd208986b5f9e0316a78538dd6ab1d0484f012f8e2a1/nvidia_cufft_cu12-11.3.3.83-py3-none-win_amd64.whl", hash = "sha256:7a64a98ef2a7c47f905aaf8931b69a3a43f27c55530c698bb2ed7c75c0b42cb7", size = 192216559, upload-time = "2025-03-07T01:53:57.106Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-cufile-cu12" +version = "1.13.1.3" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f5/5607710447a6fe9fd9b3283956fceeee8a06cda1d2f56ce31371f595db2a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:4beb6d4cce47c1a0f1013d72e02b0994730359e17801d395bdcbf20cfb3bb00a", size = 1120705, upload-time = "2025-03-07T01:45:41.434Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, + { url = "https://files.pythonhosted.org/packages/99/27/72103153b1ffc00e09fdc40ac970235343dcd1ea8bd762e84d2d73219ffa/nvidia_curand-10.4.0.35-py3-none-win_amd64.whl", hash = "sha256:65b1710aa6961d326b411e314b374290904c5ddf41dc3f766ebc3f1d7d4ca69f", size = 55242481, upload-time = "2025-08-04T10:30:41.831Z" }, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.9.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/5e/92aa15eca622a388b80fbf8375d4760738df6285b1e92c43d37390a33a9a/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dfab99248034673b779bc6decafdc3404a8a6f502462201f2f31f11354204acd", size = 63625754, upload-time = "2025-03-07T01:46:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/b9/75/70c05b2f3ed5be3bb30b7102b6eb78e100da4bbf6944fd6725c012831cab/nvidia_curand_cu12-10.3.9.90-py3-none-win_amd64.whl", hash = "sha256:f149a8ca457277da854f89cf282d6ef43176861926c7ac85b2a0fbd237c587ec", size = 62765309, upload-time = "2025-03-07T01:54:20.478Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparse", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, + { url = "https://files.pythonhosted.org/packages/99/ef/332a0101260ca78a1daef046bf0b06199e8ed4dac1d2aa698289c358169c/nvidia_cusolver-12.0.4.66-py3-none-win_amd64.whl", hash = "sha256:16515bd33a8e76bb54d024cfa068fa68d30e80fc34b9e1090813ea9362e0cb65", size = 193551444, upload-time = "2025-09-04T08:41:46.813Z" }, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.7.3.90" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparse-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/32/f7cd6ce8a7690544d084ea21c26e910a97e077c9b7f07bf5de623ee19981/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:db9ed69dbef9715071232caa9b69c52ac7de3a95773c2db65bdba85916e4e5c0", size = 267229841, upload-time = "2025-03-07T01:46:54.356Z" }, + { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, + { url = "https://files.pythonhosted.org/packages/13/c0/76ca8551b8a84146ffa189fec81c26d04adba4bc0dbe09cd6e6fd9b7de04/nvidia_cusolver_cu12-11.7.3.90-py3-none-win_amd64.whl", hash = "sha256:4a550db115fcabc4d495eb7d39ac8b58d4ab5d8e63274d3754df1c0ad6a22d34", size = 256720438, upload-time = "2025-03-07T01:54:39.898Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink", marker = "(python_full_version < '3.12' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, + { url = "https://files.pythonhosted.org/packages/02/b0/b043d6f3480f102f885cf87fc3ffd3edcb5e23b855025a50e2ef4d059185/nvidia_cusparse-12.6.3.3-py3-none-win_amd64.whl", hash = "sha256:cbcf42feb737bd7ec15b4c0a63e62351886bd3f975027b8815d7f720a2b5ea79", size = 143783033, upload-time = "2025-09-04T08:42:12.391Z" }, +] + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.5.8.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink-cu12", marker = "(python_full_version < '3.12' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/f7/cd777c4109681367721b00a106f491e0d0d15cfa1fd59672ce580ce42a97/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b6c161cb130be1a07a27ea6923df8141f3c295852f4b260c65f18f3e0a091dc", size = 288117129, upload-time = "2025-03-07T01:47:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, + { url = "https://files.pythonhosted.org/packages/62/07/f3b2ad63f8e3d257a599f422ae34eb565e70c41031aecefa3d18b62cabd1/nvidia_cusparse_cu12-12.5.8.93-py3-none-win_amd64.whl", hash = "sha256:9a33604331cb2cac199f2e7f5104dfbb8a5a898c367a53dfda9ff2acb6b6b4dd", size = 284937404, upload-time = "2025-03-07T01:55:07.742Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu12" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/b9/598f6ff36faaece4b3c50d26f50e38661499ff34346f00e057760b35cc9d/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8878dce784d0fac90131b6817b607e803c36e629ba34dc5b433471382196b6a5", size = 283835557, upload-time = "2025-02-26T00:16:54.265Z" }, + { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d8/a6b0d0d0c2435e9310f3e2bb0d9c9dd4c33daef86aa5f30b3681defd37ea/nvidia_cusparselt_cu12-0.7.1-py3-none-win_amd64.whl", hash = "sha256:f67fbb5831940ec829c9117b7f33807db9f9678dc2a617fbe781cac17b4e1075", size = 271020911, upload-time = "2025-02-26T00:14:47.204Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/31/83/f3647ce26916c94a6ca4ff1810623e2c405cff2dea6e78d29516b2514df9/nvidia_cusparselt_cu13-0.8.1-py3-none-win_amd64.whl", hash = "sha256:dccbd362f91a7b9024d1f55ee9f548ac065027ff15d8c8b0db889ab3a8f31215", size = 156885108, upload-time = "2025-09-05T18:51:35.958Z" }, +] + +[[package]] +name = "nvidia-ml-py" +version = "13.610.43" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/b5/a8fbc356f768fa5c9cfd646668fd7d34bf55bdd1c6e20754642a64d930d4/nvidia_ml_py-13.610.43.tar.gz", hash = "sha256:65437eb73d68d0c62c931ca4d45038472faff03bd0b8729abba4b899f70d60f2", size = 52109, upload-time = "2026-06-01T18:54:08.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/45/caa600acfab94560807a20a64b5830d2cd3c3202b7f1328644d70b7d6bd8/nvidia_ml_py-13.610.43-py3-none-any.whl", hash = "sha256:f13c72698edef492f985cc225f14faafe68ae065a2e407f45bdf6f4b9b43fde8", size = 53163, upload-time = "2026-06-01T18:54:07.704Z" }, +] + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.28.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/c4/120d2dfd92dff2c776d68f361ff8705fdea2ca64e20b612fab0fd3f581ac/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:50a36e01c4a090b9f9c47d92cec54964de6b9fcb3362d0e19b8ffc6323c21b60", size = 296766525, upload-time = "2025-11-18T05:49:16.094Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/44dbb46b3d1b0ec61afda8e84837870f2f9ace33c564317d59b70bc19d3e/nvidia_nccl_cu12-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:485776daa8447da5da39681af455aa3b2c2586ddcf4af8772495e7c532c7e5ab", size = 296782137, upload-time = "2025-11-18T05:49:34.248Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, + { url = "https://files.pythonhosted.org/packages/e4/01/07530b0e37546231052e30234540289c42eaffa486f1a34a87fed340157b/nvidia_nvjitlink-13.0.88-py3-none-win_amd64.whl", hash = "sha256:634e96e3da9ef845ae744097a1f289238ecf946ce0b82e93cdce14b9782e682f", size = 36035115, upload-time = "2025-09-04T08:43:03.001Z" }, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.8.93" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a2/8cee5da30d13430e87bf99bb33455d2724d0a4a9cb5d7926d80ccb96d008/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:adccd7161ace7261e01bb91e44e88da350895c270d23f744f0820c818b7229e7", size = 38386204, upload-time = "2025-03-07T01:49:43.612Z" }, + { url = "https://files.pythonhosted.org/packages/ed/d7/34f02dad2e30c31b10a51f6b04e025e5dd60e5f936af9045a9b858a05383/nvidia_nvjitlink_cu12-12.8.93-py3-none-win_amd64.whl", hash = "sha256:bd93fbeeee850917903583587f4fc3a4eafa022e34572251368238ab5e6bd67f", size = 268553710, upload-time = "2025-03-07T01:56:24.13Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu12" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/6a/03aa43cc9bd3ad91553a88b5f6fb25ed6a3752ae86ce2180221962bc2aa5/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b48363fc6964dede448029434c6abed6c5e37f823cb43c3bcde7ecfc0457e15", size = 138936938, upload-time = "2025-09-06T00:32:05.589Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, + { url = "https://files.pythonhosted.org/packages/d2/50/0e2220f8620a177de994211186ffc5bfa9f2ce1e1282797f8f90096f9f88/nvidia_nvtx-13.0.85-py3-none-win_amd64.whl", hash = "sha256:d66ea44254dd3c6eacc300047af6e1288d2269dd072b417e0adffbf479e18519", size = 137066, upload-time = "2025-09-04T08:39:25.649Z" }, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.8.90" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/c0/1b303feea90d296f6176f32a2a70b5ef230f9bdeb3a72bddb0dc922dc137/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d7ad891da111ebafbf7e015d34879f7112832fc239ff0d7d776b6cb685274615", size = 91161, upload-time = "2025-03-07T01:42:23.922Z" }, + { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/9f/99/4c9c0c329bf9fc125008c3b54c7c94c0023518d06fc025ae36431375e1fe/nvidia_nvtx_cu12-12.8.90-py3-none-win_amd64.whl", hash = "sha256:619c8304aedc69f02ea82dd244541a83c3d9d40993381b3b590f1adaed3db41e", size = 56492, upload-time = "2025-03-07T01:52:24.69Z" }, +] + +[[package]] +name = "opencv-python" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4c/a438d23e09ce2033c09f7b784ad2fbdb0adf529e434101ed28f142226f98/opencv_python-5.0.0.93.tar.gz", hash = "sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2", size = 81802749, upload-time = "2026-07-02T06:59:53.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/75/76f6ade78f6102c61034f828e2a22616708df2c9504bc8d6af9dd8f73dc5/opencv_python-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898", size = 48322443, upload-time = "2026-07-02T05:50:25.466Z" }, + { url = "https://files.pythonhosted.org/packages/15/8c/bc1bda6aae69a32e9d84fc34153ba104cd25226861eb4aea33b2cea4860d/opencv_python-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b", size = 34782755, upload-time = "2026-07-02T05:51:30.556Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8a/b04776ec45d2dea08a1b176f1829201db3515d4ed16c35f8fcc9fa7beb16/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac", size = 50614064, upload-time = "2026-07-02T06:53:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/95/54/eb47866b94f2b5b42dde17644b78055ef1ee05aae59962c7290e55270803/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881", size = 71064711, upload-time = "2026-07-02T06:54:13.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/da/962579f1e703cbf8c5422fd1f576467dcb3b5b0b0b81c1471c979764353a/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2", size = 49798576, upload-time = "2026-07-02T06:54:33.781Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4c/c73f828fdbcd37eaf21d08fa852544a3ca7c2dbb3ea76873d64f2ea413d1/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039", size = 73783032, upload-time = "2026-07-02T06:55:03.415Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4b/edaf83b996ca5a1a3d8ccad485706b9c6d4742b13b9c4586bf1c1e7d9423/opencv_python-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157", size = 35564734, upload-time = "2026-07-02T05:49:57.704Z" }, + { url = "https://files.pythonhosted.org/packages/21/f0/9fa6e85cb10c8eb36a0222d27e50fe381b86ce49a55446bf39f491727564/opencv_python-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2", size = 44000345, upload-time = "2026-07-02T05:49:54.971Z" }, +] + +[[package]] +name = "opencv-python-headless" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/99/76b7c80252aa83c1af16393454aafd125a0287101afe8deb0a6821af0e30/opencv_python_headless-5.0.0.93.tar.gz", hash = "sha256:b82f9831daab90b725c7c1ee1b36cb5732c367096ac76d119e64e14eb70d5f3c", size = 81817738, upload-time = "2026-07-02T07:01:06.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/7c/8c8097891c509d98cd128493835c95631c80be6a8f37ed9d25716c2e16f1/opencv_python_headless-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:030ca5e0837a2963ab36ef896baa9767eb8d2b83353fb28af5a521e40dd8756f", size = 48322581, upload-time = "2026-07-02T05:50:34.207Z" }, + { url = "https://files.pythonhosted.org/packages/90/8c/eab2ad388c3cbab2a350c10c2ef19ce6bd099240afc31789032c996bab52/opencv_python_headless-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:1e55af3abfb462eeeabe5c775f12bdb36216d8a93a3583d69e6bd6e1d6ba7d00", size = 34782894, upload-time = "2026-07-02T05:51:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/ec/78/afca939f40ffe2b2380bfa86f812b2f7d4acc5a27b27dc41b49cad7ce7b4/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10818d91510e05c04568ae12b5cd120779c70c01bf897b001a6221fe430df80f", size = 36521085, upload-time = "2026-07-02T06:55:24.429Z" }, + { url = "https://files.pythonhosted.org/packages/2b/97/8170e9819764c47e436c130d3ff6cfb73b58f923eae9d3a03d8982b04aec/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09a872a157c1376ab922a69bbf22f9a95bcc7b658a9d8b436a60212b02b2eeb4", size = 56563598, upload-time = "2026-07-02T06:55:47.355Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/1a28a7101e31801042b3098871a74b76c61581d328ef40774ff4edb53a56/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:840bd717c21e5c11cadadc022a823315ea417f961213d06b4df010e019eb16f4", size = 39648433, upload-time = "2026-07-02T06:56:04.255Z" }, + { url = "https://files.pythonhosted.org/packages/9b/21/f6ef335f6e65724aa78b8d792b48d40a48c381715f1e62f5a5049e09d07e/opencv_python_headless-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ed709fdf9aa0bd1f2ed8549e71d19449b03a675bb581eb292285f6861953be37", size = 61204038, upload-time = "2026-07-02T06:56:41.823Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8f/b8756467ea991449a293797f6b3fa80fcfdd29598a0a60d1cd5715b96e61/opencv_python_headless-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:c6bcd96b185975ea240d22cfdb15a1f6d080cc95264cfbe2621f21bb144d89b9", size = 35411237, upload-time = "2026-07-02T05:50:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/b8/88/763b967f7efd7226b82c9fae16d560cba049b1f0c036647e65c610fd636e/opencv_python_headless-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:829717b6a95554f273e49e357cee3b3a2a26b6f4842fbc1bed2b45bdd8f87e0e", size = 43825962, upload-time = "2026-07-02T05:50:09.627Z" }, +] + +[[package]] +name = "openml" +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "liac-arff" }, + { name = "minio" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tqdm" }, + { name = "xmltodict" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/88/9b/729dc6377bbfdbf0828d5567a335670d4e7c2866065ca4593ab525a5809c/openml-0.15.1.tar.gz", hash = "sha256:58ae3840b6ea736bb6c69bcbb30d587b817f64db070dc691adb9e09b99018816", size = 146141, upload-time = "2025-01-25T10:56:28.351Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/f3/462c16d9e80177d22a036abd3df0f114bf311e566ff906adbb3d82790c20/openml-0.15.1-py3-none-any.whl", hash = "sha256:14d25afb7a3007a70da26b0d1f46cf93df6f5784c31dd76d6a415066c6051961", size = 160384, upload-time = "2025-01-25T10:56:24.84Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, +] + +[[package]] +name = "optuna" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alembic" }, + { name = "colorlog" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "sqlalchemy" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/aa/05f5e3f662cc96a4c478fc3446b8ed6359825a2b504ecb614a9ac84e4a4d/optuna-4.9.0.tar.gz", hash = "sha256:b322e5cbdf1655fb84c37646c4a7a1f391de1b47806bbe222e015825d0a82b87", size = 485834, upload-time = "2026-06-01T06:23:30.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/f3/e5fcd5d9b15771ed6dc10e3a7eeddc672e418f4f4c4653d216cc1d857e2d/optuna-4.9.0-py3-none-any.whl", hash = "sha256:f52f3be6148654850c92a5860d398fd88ec6b2c84ab68d9c3d07dcff02e7afee", size = 425553, upload-time = "2026-06-01T06:23:28.804Z" }, +] + +[[package]] +name = "oslo-concurrency" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "debtcollector" }, + { name = "fasteners" }, + { name = "oslo-config", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-config", version = "10.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-i18n" }, + { name = "oslo-utils" }, + { name = "pbr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/4b/dd78b7309433a3086abb12d07326f3d7760e81142c45fc589636979f0c34/oslo_concurrency-7.5.0.tar.gz", hash = "sha256:091ce0a27c5f347e393cf4776176389fe23c0b863ff0539d121619d0ec691367", size = 62621, upload-time = "2026-05-18T09:30:28.639Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/5a/04a381ab5fac98aa87c662fe9c693c8dd47e03d45c44821d70f2df060eaa/oslo_concurrency-7.5.0-py3-none-any.whl", hash = "sha256:11dfa769f330de64cad0aca2736f75239265e571b5e17edcb23f08f29b917aef", size = 48574, upload-time = "2026-05-18T09:30:27.51Z" }, +] + +[[package]] +name = "oslo-config" +version = "10.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "netaddr", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-i18n", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyyaml", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rfc3986", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "stevedore", version = "5.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/a9/a1295eceb3a79ad46f32d145bade3119dc20636e2fda62adaba19c61195c/oslo_config-10.4.0.tar.gz", hash = "sha256:2ae3e02593474ecd7b64ec4eb11482adb4c928a78267bc820f5c3f80240b197a", size = 168943, upload-time = "2026-05-18T09:31:19.554Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/49/87743b93ea01f9cf667e78c1ceea1058f816ef733399f4e45469472cf2fe/oslo_config-10.4.0-py3-none-any.whl", hash = "sha256:0429c7b312114fb796005bdb718bba51e66de9c0814fb2cee22e754afbfe253b", size = 137504, upload-time = "2026-05-18T09:31:17.809Z" }, +] + +[[package]] +name = "oslo-config" +version = "10.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "netaddr", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "oslo-i18n", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyyaml", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rfc3986", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "stevedore", version = "5.9.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/12/7aa270611a106994d79610157c348216971d6e5a91300acdc1cae9a64081/oslo_config-10.5.0.tar.gz", hash = "sha256:8eea3356c93828c2d61bea1eb19b8cd7860a3edaff4ad2678d774dd353730dfa", size = 169305, upload-time = "2026-06-11T13:24:40.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/b3/720b781b80f9a4bf962ac3ff7608ef97cafd021bb073aa29e21221e918ad/oslo_config-10.5.0-py3-none-any.whl", hash = "sha256:f21b985d28e607e22dd9d12b615cb93bf6fdb890d8ff165a9dde0521464d672f", size = 137580, upload-time = "2026-06-11T13:24:39.082Z" }, +] + +[[package]] +name = "oslo-i18n" +version = "6.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pbr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/26/85800d24c3aa7650bbd5fa0398aca78a84e8a8693f9c6a852148a196ddac/oslo_i18n-6.8.0.tar.gz", hash = "sha256:a0b4c64c1396869d7144dca60ad97c7eb028f78f61f91c7007531238051997df", size = 50114, upload-time = "2026-05-18T09:16:54.09Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/78/35f3022c3e80605c96a9caad2c850d0904e37c9a74b57982f79002f09f66/oslo_i18n-6.8.0-py3-none-any.whl", hash = "sha256:77a6729535ec5a49f72bc64d7795b673a88c1b9c753e0fca45d4e6b14e892bb9", size = 47809, upload-time = "2026-05-18T09:16:52.437Z" }, +] + +[[package]] +name = "oslo-utils" +version = "10.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "iso8601" }, + { name = "netaddr" }, + { name = "oslo-i18n" }, + { name = "packaging" }, + { name = "pbr" }, + { name = "psutil" }, + { name = "pyparsing" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/16/8cb5305abd34606bd9a5ee1c6fbe5db97981d323c8454f1d872c1781dcc8/oslo_utils-10.1.1.tar.gz", hash = "sha256:c8ac3ee295303cc5776c4d8e1d4ef10078ece60ede4931177e4f07aca58f81ab", size = 159381, upload-time = "2026-06-09T13:13:27.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/b3/c7afc60b9392b335ce0ad121977f91df9706f47f2c518b27e4b3007e9afb/oslo_utils-10.1.1-py3-none-any.whl", hash = "sha256:dcc7aa6668aa84fc14a18a4064b389b2992e1ee793ba38dadac327dfba69dadd", size = 154564, upload-time = "2026-06-09T13:13:26.365Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pbr" +version = "7.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/ab/1de9a4f730edde1bdbbc2b8d19f8fa326f036b4f18b2f72cfbea7dc53c26/pbr-7.0.3.tar.gz", hash = "sha256:b46004ec30a5324672683ec848aed9e8fc500b0d261d40a3229c2d2bbfcedc29", size = 135625, upload-time = "2025-11-03T17:04:56.274Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/db/61efa0d08a99f897ef98256b03e563092d36cc38dc4ebe4a85020fe40b31/pbr-7.0.3-py2.py3-none-any.whl", hash = "sha256:ff223894eb1cd271a98076b13d3badff3bb36c424074d26334cd25aebeecea6b", size = 131898, upload-time = "2025-11-03T17:04:54.875Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/c2/669d88644cddb1485bd9534e63e8cf476c8e51cb3c3a1297677023505c0e/pillow-12.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6c0016e7b354317c4e9e525b937ac8596c38d2d232b419529b9cd7a1cd46e39a", size = 5392418, upload-time = "2026-07-01T11:53:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ba/3762f376a2948e3036488d773a146e0ae6ecc2ca03ac20e2615bd0b2ba02/pillow-12.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bcc33feacfaefce60c12fd500a277533bdc02b10a19f7f6d348763d8140bbba7", size = 4785287, upload-time = "2026-07-01T11:53:29.761Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/b5d688cc9c52d4482f3d5bcab6ce20bc2a74a85d2343841c907444a3be2c/pillow-12.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5594fc43d548a7ed94949d139aa1341b270f1863f11cfd37f5a6c8b778a6b67f", size = 6253754, upload-time = "2026-07-01T11:53:32.298Z" }, + { url = "https://files.pythonhosted.org/packages/4e/89/36f4cd76cf4baf05c50ababb976249153f18c959171c7f6ba09a6f217260/pillow-12.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0606c8bf2cdefea14a43530f7657cbbb7ecf1c4222512492ef4a4434a9501ec", size = 6925605, upload-time = "2026-07-01T11:53:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c0/4de58cf6633b9e3a6061ef4be6fb91fc3c90b812ece886f531e3c523d777/pillow-12.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:85f998ea1848bc6757289e739cfbdda3a04adfd58b02fc018ce54d754a5ce468", size = 6327788, upload-time = "2026-07-01T11:53:36.433Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/14d53682a19550dbbaf3b598f807d5457646c510805a44c7d7891cd1cd1a/pillow-12.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:25b9b82bb22e6e2b3cd07b39c68b7b862001226cb3dff7130d1cb914121b39ed", size = 7036288, upload-time = "2026-07-01T11:53:38.712Z" }, + { url = "https://files.pythonhosted.org/packages/38/1d/36279e3c77efe034e4cc2b0393ee74ffdb5a62391dacbf9b916154f5f0b8/pillow-12.3.0-cp310-cp310-win32.whl", hash = "sha256:37dc8f7bbb66efe481bb60defacef820c950c24713fb44962ed6aa2a50966de1", size = 6472396, upload-time = "2026-07-01T11:53:40.781Z" }, + { url = "https://files.pythonhosted.org/packages/48/7c/8fa0039574c476d7c6fa57dd7c32a130436877c6ec1e5ce1cc8ec44878c1/pillow-12.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:300557495eb45ebb8aec96c2da9c4be642fbf7cd937278b4013ba894ea8eb0eb", size = 7226887, upload-time = "2026-07-01T11:53:42.764Z" }, + { url = "https://files.pythonhosted.org/packages/fa/17/e324be141d173c1c919428066c3259f21c1b8982e564e01a4a81e96dbdcf/pillow-12.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:514435a37670e3e5e08f3945b68718b6ed329bb84367777e16f9f4dfe1e61a0f", size = 2568039, upload-time = "2026-07-01T11:53:45.372Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, +] + +[[package]] +name = "plotly" +version = "6.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "narwhals" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/fd/d72c292d78aadb93d1a9bcd76bf3c678271040c7cf10abe5788b33040a39/plotly-6.8.0.tar.gz", hash = "sha256:e088e7ddc68d4f70e3d66659224727a45296d71d2b8284181862d3d8f1f0d88f", size = 6915161, upload-time = "2026-06-03T18:33:40.226Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/14/abe5ce876ab5b66ee3c691bf537fcd43d037aea55d447aacf74630a8f31e/plotly-6.8.0-py3-none-any.whl", hash = "sha256:13c5c4a0f70b74cab1913eda0de49b826df5931708eb6f9c3010040614700ec8", size = 9902055, upload-time = "2026-06-03T18:33:34.26Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "portalocker" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/77/65b857a69ed876e1951e88aaba60f5ce6120c33703f7cb61a3c894b8c1b6/portalocker-3.2.0.tar.gz", hash = "sha256:1f3002956a54a8c3730586c5c77bf18fae4149e07eaf1c29fc3faf4d5a3f89ac", size = 95644, upload-time = "2025-06-14T13:20:40.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/a6/38c8e2f318bf67d338f4d629e93b0b4b9af331f455f0390ea8ce4a099b26/portalocker-3.2.0-py3-none-any.whl", hash = "sha256:3cdc5f565312224bc570c49337bd21428bba0ef363bbcf58b9ef4a9f11779968", size = 22424, upload-time = "2025-06-14T13:20:38.083Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, +] + +[[package]] +name = "propcache" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/44/c87281c333769159c50594f22610f77398a47ccbfbbf23074e744e86f87c/propcache-0.5.2.tar.gz", hash = "sha256:01c4fc7480cd0598bb4b57022df55b9ca296da7fc5a8760bd8451a7e63a7d427", size = 50208, upload-time = "2026-05-08T21:02:12.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/56/030b7b4719d53085722893e0009dffb9236aa10bca1b12121bdc5626ef16/propcache-0.5.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a81be28596d6559f6131ef33e10200de6e17643b3c74ce03f9eb103be6ae8b", size = 93417, upload-time = "2026-05-08T20:59:15.597Z" }, + { url = "https://files.pythonhosted.org/packages/1a/55/1140a8e067b8ec093a18a4ae7bb0045d9db65da38a08618ddc5e2f1994aa/propcache-0.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29cbaac5ea0212663e6845e04b5e188d5a6ae6dd919810ac835bf1d3b42c3f4c", size = 53847, upload-time = "2026-05-08T20:59:17.096Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/0e7443c90310498561addf346e7d57fe3c6ba1914e1ba938b5464c7bbfd2/propcache-0.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bf3be92233808fcd338eba0fb4d0b59ec5772af4f4ecfcec450d1bfc0f8b5eb", size = 53512, upload-time = "2026-05-08T20:59:18.64Z" }, + { url = "https://files.pythonhosted.org/packages/b7/db/cf51a71bab2009517d1a7f0ee07657e3bd446c4d69f67e6966cf17bcf956/propcache-0.5.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f8ea531c794b9d6274acd4e8d2c2ebcac590a4361d27482edd3010b79f1325e", size = 58068, upload-time = "2026-05-08T20:59:20.683Z" }, + { url = "https://files.pythonhosted.org/packages/b7/43/39b6bdee9699fa1e1641c519feeb64a67e2a9f93bb465c70776b37a7333f/propcache-0.5.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:decfca4c79dd53ebab484b00cc4b6717d8c369f86e74aa4ca395a64ac651495e", size = 61020, upload-time = "2026-05-08T20:59:22.112Z" }, + { url = "https://files.pythonhosted.org/packages/26/0b/843726fbb0a29a8c5684fdb25971823638399f31e52e9d1f06a02dc9aa6b/propcache-0.5.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4621064bbf28fa77ff64dd5d94367c04684c67d3a5bf1dff25f0cd0d98a38f3b", size = 62732, upload-time = "2026-05-08T20:59:23.805Z" }, + { url = "https://files.pythonhosted.org/packages/39/6e/899fed76dc1942b8a64193a4f059d7f1a2c7ef65085e8a9366ed8ec0d199/propcache-0.5.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b96db7141a592cbc968daf1feea83a118e6ab378af4abbc72b248c895414c22d", size = 60140, upload-time = "2026-05-08T20:59:25.389Z" }, + { url = "https://files.pythonhosted.org/packages/ab/09/3da4be9b5b879219ad234aa535b3dd4a080ed1ad48d3a73ca07a9e798f22/propcache-0.5.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1ca071adabaab6e9219924bbe00af821f1ee7de113a9eca1cdc292de3d120f4d", size = 60400, upload-time = "2026-05-08T20:59:27.238Z" }, + { url = "https://files.pythonhosted.org/packages/60/2f/09b72b874a9aa0044faf52a69807a6ed618e267ceaa9ec4a63195fa5b504/propcache-0.5.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e4294d04a94dcab1b3bccd8b66d962dcad411a1d19414b2a41d1445f1de32ad0", size = 58155, upload-time = "2026-05-08T20:59:28.48Z" }, + { url = "https://files.pythonhosted.org/packages/8a/37/97489848c54c95578045473954f10956d619ce6a09e7ac137b71cdcb698b/propcache-0.5.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a0e399a2eccb91ed18721f86aa85757727400b6865c89e88934781deb9c8498b", size = 57037, upload-time = "2026-05-08T20:59:30.146Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/6c695285ccfc49012743ee9c98212b8c5dd0aed7b63cfd816d4a0f7a1601/propcache-0.5.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:823581fd5cb08b12a48bfa11fe962a7916766b6170c17b028fbdf762b85eb9bf", size = 61103, upload-time = "2026-05-08T20:59:31.626Z" }, + { url = "https://files.pythonhosted.org/packages/98/a9/1e500401ca593b0bdb6bf75a70bc2d723835fd53360edff6af70692c7546/propcache-0.5.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:949c91d1a990cf3b2e8188dfcfb25005e0b834a06c63fa4ef9f360878ce21ecf", size = 60394, upload-time = "2026-05-08T20:59:32.829Z" }, + { url = "https://files.pythonhosted.org/packages/1f/87/f638b6e375eae0f30a1a2325d8b34fd85fdc785bb9960cf805f3bf1ec69a/propcache-0.5.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:cc1177027eda740fdb152706bd215a3f124e3eea15afc39f2cb9fe351b50619e", size = 63084, upload-time = "2026-05-08T20:59:35.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/18/884573f5d97b6d9eba68de759a82c901b7e39d7904d30f7b8d58d42d2a12/propcache-0.5.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b05d643f944a8c3c4bd86d65ffd87bf3264b617f87791940302bc474d2ff5274", size = 60999, upload-time = "2026-05-08T20:59:38.481Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1a/c3915eb059ceec9e758a56e4cfd955292bc0f201be2176a46b76d94b303a/propcache-0.5.2-cp310-cp310-win32.whl", hash = "sha256:8114f28879e0904748e831c3a7774261bd9e75f49be089f389a76f959dcd13fe", size = 39036, upload-time = "2026-05-08T20:59:40.323Z" }, + { url = "https://files.pythonhosted.org/packages/5b/02/1dfd5607501a602d19c1c449d2d193b7d1c611f9246b4059026a1189a80e/propcache-0.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:5fcb98e7598b1ee0addab320d90f65b530297a867dbfe9de52ea838077e16e3d", size = 42190, upload-time = "2026-05-08T20:59:42.232Z" }, + { url = "https://files.pythonhosted.org/packages/57/93/f71588ad08b3e6f4b555b5ef215808a3c02b042d0151ad82fa6f15be677a/propcache-0.5.2-cp310-cp310-win_arm64.whl", hash = "sha256:04dc2390d9edbbaef7461f33322555976ffddf0b650a038649d026358714e6c5", size = 38545, upload-time = "2026-05-08T20:59:44.087Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f1/8a8cc1c2c7e7934ab77e0163414f736fadbc0f5e8dd9673b952355ac175b/propcache-0.5.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74b70780220e2dd89175ca24b81b68b67c83db499ae611e7f2313cb329801c78", size = 90744, upload-time = "2026-05-08T20:59:45.799Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f4/651b1225e976bd1a2ba5cfba0c29d096581c2636b437e3a9a7ab6276270a/propcache-0.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4840ab0ae0216d952f4b53dc6d0b992bfc2bedbfe360bdd9b548bc184c08959", size = 52033, upload-time = "2026-05-08T20:59:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/15/a8/8ede85d6aa1f79fc7dc2f8fd2c8d65920b8272c3892903c8a1affde48cfb/propcache-0.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6844ba6364fb12f403928a82cfd295ab103a2b315c77c747b2dbe4a41894ea7", size = 52754, upload-time = "2026-05-08T20:59:49.202Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/b3551b41bbc2f5b5bb088fc6920567cd43101253e68fbaa261339eb96fe1/propcache-0.5.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2293949b855ce597f2826452d17c2d545fb5622379c4ea6fdf525e9b8e8a2511", size = 57573, upload-time = "2026-05-08T20:59:50.778Z" }, + { url = "https://files.pythonhosted.org/packages/83/27/ab851ebd1b7172e3e161f5f8d39e315d54a91bea246f01f4d872d3376aef/propcache-0.5.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0fd59b5af35f74da48d905dcbad55449ba13be91823cb05a9bd590bbf5b61660", size = 60645, upload-time = "2026-05-08T20:59:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/95/7d/466b3d18022e9897cbda9c735c493c5bd747d7a4c6f5ea1480b4cec434b6/propcache-0.5.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29f9309a2e42b0d273be006fdb4be2d6c39a47f6f57d8fb1cf9f81481df81b66", size = 61563, upload-time = "2026-05-08T20:59:53.866Z" }, + { url = "https://files.pythonhosted.org/packages/27/1b/16ab7f2cf2041da2f60d156ba64c2484eadf9168075b4ff43c3ef60045af/propcache-0.5.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5aaa2b923c1944ac8febd6609cb373540a5563e7cbcb0fd770f75dace2eb817b", size = 58888, upload-time = "2026-05-08T20:59:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/0a/67/bb777ffd907633563bf35fd859c4ce97b0512c32f4633cf5d1eb7c33512b/propcache-0.5.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66ea454f095ddf5b6b14f56c064c0941c4788be11e18d2464cf643bf7203ff67", size = 59253, upload-time = "2026-05-08T20:59:57.075Z" }, + { url = "https://files.pythonhosted.org/packages/b9/42/64f8d90b73fd9cdc1499b48057ff6d9cd2a98a25734c9bb62ecf07e87061/propcache-0.5.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:95f1e3f4760d404b13c9976c0229b2b49a3c8e2c62a9ce92efdd2b11ada75e3f", size = 57558, upload-time = "2026-05-08T20:59:58.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/02/dba5bc03c9041f2092ea55a449caf5dfe68352c6654511b29ba0654ddb69/propcache-0.5.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:85341b12b9d55bad0bded24cac341bb34289469e03a11f3f583ea1cc1db0326c", size = 55007, upload-time = "2026-05-08T20:59:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/14/c0/43f649c7aa2a77a3b100d84e9dea3a483120ecb608bfe36ce49eaff517fe/propcache-0.5.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:26a4dca084132874e639895c3135dfad5eb20bae209f62d1aeb31b03e601c3c0", size = 60355, upload-time = "2026-05-08T21:00:01.144Z" }, + { url = "https://files.pythonhosted.org/packages/83/c0/435dafd27f1cb4a495381dae60e25883ccfe4020bb72818e8184c1678092/propcache-0.5.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:3b199b9b2b3d6a7edf3183ba8a9a137a22b97f7df525feb5ae1eccf026d2a9c6", size = 59057, upload-time = "2026-05-08T21:00:02.401Z" }, + { url = "https://files.pythonhosted.org/packages/53/ae/6e292df9135d659944e96cb3389258e4a663e5b2b5f6c217ef0ddc8d2f73/propcache-0.5.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e59bc9e66329185b93dab73f210f1a37f81cb40f321501db8017c9aea15dba27", size = 61938, upload-time = "2026-05-08T21:00:03.638Z" }, + { url = "https://files.pythonhosted.org/packages/0b/42/314ebc50d8159055411fd6b0bda322ff510e4b1f7d2e4927940ad0f6af20/propcache-0.5.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:552ffadf6ad409844bc5919c42a0a83d88314cedddaea0e41e80a8b8fffe881f", size = 59731, upload-time = "2026-05-08T21:00:04.881Z" }, + { url = "https://files.pythonhosted.org/packages/b8/9b/2da6dee38871c3c8772fabc2758325a5c9077d6d18c597737dc04dd884cd/propcache-0.5.2-cp311-cp311-win32.whl", hash = "sha256:cd416c1de191973c52ff1a12a57446bfc7642797b282d7caf2162d7d1b8aa9a0", size = 38966, upload-time = "2026-05-08T21:00:06.511Z" }, + { url = "https://files.pythonhosted.org/packages/42/4e/f17363fb58c0afe05b067361cb6d86ed2d29de6506779a27547c4d183075/propcache-0.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:44e488ef40dbb452700b2b1f8188934121f6648f52c295055662d2191959ff82", size = 42135, upload-time = "2026-05-08T21:00:08.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/eb/6af6685077d22e8b33358d3c548e3282706a0b3cd85044ffba4e5dd08e3b/propcache-0.5.2-cp311-cp311-win_arm64.whl", hash = "sha256:54adaa85a22078d1e306304a40984dc5be99d599bf3dc0a24dc98f7daeab89ab", size = 38381, upload-time = "2026-05-08T21:00:09.692Z" }, + { url = "https://files.pythonhosted.org/packages/4a/cb/e27bc2b2737a0bb49962b275efa051e8f1c35a936df7d5139b6b658b7dc9/propcache-0.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806719138ecd720339a12410fb9614ac9b2b2d3a5fdf8235d56981c36f4039ba", size = 95887, upload-time = "2026-05-08T21:00:11.277Z" }, + { url = "https://files.pythonhosted.org/packages/e6/13/b8ae04c59392f8d11c6cd9fb4011d1dc7c86b81225c770280300e259ffe1/propcache-0.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2b80ea58eab4f86b2beec3cc8b39e8ff9276ac20e96b7cce43c8ae84cd6b5a", size = 54654, upload-time = "2026-05-08T21:00:12.604Z" }, + { url = "https://files.pythonhosted.org/packages/2c/7d/49777a3e20b55863d4794384a38acd460c04157b0a00f8602b0d508b8431/propcache-0.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e5cbfac9f61484f7e9f3597775500cd3ebe8274e9b050c38f9525c77c97520bf", size = 55190, upload-time = "2026-05-08T21:00:13.935Z" }, + { url = "https://files.pythonhosted.org/packages/44/c7/085d0cd63062e84044e3f05797749c3f8e3938ff3aeb0eb2f69d43fafc91/propcache-0.5.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbc581d2814337da56222fab8dc5f161cd798a434e49bac27930aaef798e144", size = 59995, upload-time = "2026-05-08T21:00:15.526Z" }, + { url = "https://files.pythonhosted.org/packages/9c/42/32cf8e3009e92b2645cf1e944f701e8ea4e924dffde1ee26db860bcbf7e4/propcache-0.5.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:857187f381f88c8e2fa2fe56ab94879d011b883d5a2ee5a1b60a8cd2a06846d9", size = 63422, upload-time = "2026-05-08T21:00:16.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/f112433f99fc979431b87a39ef169e3f8df070d99a72792c56d6937ac48b/propcache-0.5.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:178b4a2cdaac1818e2bf1c5a99b94383fa73ea5382e032a48dec07dc5668dc42", size = 64342, upload-time = "2026-05-08T21:00:18.362Z" }, + { url = "https://files.pythonhosted.org/packages/14/15/5574111ae50dd6e879456888c0eadd4c5a869959775854e18e18a6b345f3/propcache-0.5.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f328175a2cde1f0ff2c4ed8ce968b9dcfb55f3a7153f39e2957ed994da13476", size = 61639, upload-time = "2026-05-08T21:00:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/cc/da/4d775080b1490c0ae604acda868bd71aabe3a89ed16f2aa4339eb8a283e7/propcache-0.5.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5671d09a36b06d0fd4a3da0fccbcae360e9b1570924171a15e9e0997f0249fba", size = 61588, upload-time = "2026-05-08T21:00:21.155Z" }, + { url = "https://files.pythonhosted.org/packages/04/ac/f076982cbe2195ee9cf32de5a1e46951d9fb399fc207f390562dd0fd8fb2/propcache-0.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80168e2ebe4d3ec6599d10ad8f520304ae1cad9b6c5a95372aef1b66b7bfb53a", size = 60029, upload-time = "2026-05-08T21:00:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/189be62e0dd898dce3b331e1b8c7a543cd3a405ac0c81fe8ee8a9d5d77e1/propcache-0.5.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:45f11346f884bc47444f6e6647131055844134c3175b629f84952e2b5cd62b64", size = 56774, upload-time = "2026-05-08T21:00:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/ea/9e/93377b9c7939c1ffae98f878dee955efadfd638078bc86dbc21f9d52f651/propcache-0.5.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e778ebd44ef4f66ed60a0416b06b489687db264a9c0b3620362f26489492913", size = 63532, upload-time = "2026-05-08T21:00:25.545Z" }, + { url = "https://files.pythonhosted.org/packages/14/f9/590ef6cfb9b8028d516d287812ece32bb0bc5f11fbb9c8bf6b2e6313fec8/propcache-0.5.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c0cb9ed24c8964e172768d455a38254c2dd8a552905729ce006cad3d3dda59b1", size = 61592, upload-time = "2026-05-08T21:00:27.186Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5e/70958b3034c297a630bba2f17ca7abc2d5f39a803ad7e370ab79d1ecd022/propcache-0.5.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1d1ad32d9d4355e2be65574fd0bfd3677e7066b009cd5b9b2dee8aa6a6393b33", size = 64788, upload-time = "2026-05-08T21:00:28.8Z" }, + { url = "https://files.pythonhosted.org/packages/12/fd/77fe5936d8c3086ca9048f7f415f122ed82e53884a9ec193646b42deef06/propcache-0.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c80f4ba3e8f00189165999a742ee526ebeccedf6c3f7beb0c7df821e9772435a", size = 62514, upload-time = "2026-05-08T21:00:30.098Z" }, + { url = "https://files.pythonhosted.org/packages/cf/74/66bd798b5b3be70aa1b391f5cc9d6a0a5532d7fd3b19ec0b213e72e6ad9d/propcache-0.5.2-cp312-cp312-win32.whl", hash = "sha256:8c7972d8f193740d9175f0998ab38717e6cd322d5935c5b0fef8c0d323fd9031", size = 39018, upload-time = "2026-05-08T21:00:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/61/7c/5c0d34aa3024694d6dcb9271cdbdd08c4e47c1c0ad95ec7e7bc74cdea145/propcache-0.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:d9ee8826a7d47863a08ac44e1a5f611a462eefc3a194b492da242128bec75b42", size = 42322, upload-time = "2026-05-08T21:00:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/4d/91/875812f1a3feb20ceba818ef39fbe4d92f1081e04ac815c822496d0d038b/propcache-0.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:2800a4a8ead6b28cccd1ec54b59346f0def7922ee1c7598e8499c733cfbb7c84", size = 38172, upload-time = "2026-05-08T21:00:35.124Z" }, + { url = "https://files.pythonhosted.org/packages/c5/09/f049e45385503fe67db75a6b6186a7b9f0c3930366dc960522c312a825b1/propcache-0.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:099aaf4b4d1a02265b92a977edf00b5c4f63b3b17ac6de39b0d637c9cac0188a", size = 94457, upload-time = "2026-05-08T21:00:36.355Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/83d1d05655baf63113731bd5a1008435e14f8d1e5a06cbe4ec5b23ad7a31/propcache-0.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68ce1c44c7a813a7f71ea04315a8c7b330b63db99d059a797a4651bb6f69f117", size = 53835, upload-time = "2026-05-08T21:00:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/a9/12/a6ba6482bb5ea3260c000c9b20881c95fa11c6b30173715668259f844ed7/propcache-0.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fc299c129490f55f254cd90be0deca4764e36e9a7c08b4aa588479a3bbed3098", size = 54545, upload-time = "2026-05-08T21:00:39.319Z" }, + { url = "https://files.pythonhosted.org/packages/a9/19/7fa086f5764c59ec8a8e157cd93aa8497acc00aba9dcdec56bfffb32602d/propcache-0.5.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6ae2198be502c10f09b2516e7b5d019816924bc3183a43ce792a7bd6625e6f4", size = 59886, upload-time = "2026-05-08T21:00:40.621Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/5d7663dc8235956c8f5281698a3af1d351d8820341ddd890f59d9a9127f2/propcache-0.5.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6041d31504dc1779d700e1edcfb08eea334b357620b06681a4eabb57a74e574e", size = 63261, upload-time = "2026-05-08T21:00:41.775Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/15a03adee24d6350da4292caeac44c34c033d2afe5e87eb370f38854560f/propcache-0.5.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7eabc04151c78a9f4d5bbb5f1faf571e4defeb4b585e0fe95b60ff2dbe4d3d7", size = 64184, upload-time = "2026-05-08T21:00:43.018Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c6/979176efdaa3d239e36d503d5af63a0a773b36662ed8f52e5b6a6d9fd40e/propcache-0.5.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4db0ba63d693afd40d249bd93f842b5f144f8fcbb83de05660373bcf30517b1d", size = 61534, upload-time = "2026-05-08T21:00:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/c8/22/63e8cd1bae4c2d2be6493b6b7d10566ddafad88137cfbc99964a1119853c/propcache-0.5.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1dbcf7675229b35d31abb6547d8ebc8c27a830ac3f9a794edff6254873ec7c0a", size = 61500, upload-time = "2026-05-08T21:00:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/60/5a/28e5d9acbac1cc9ccb67045e8c1b943aa8d79fdf39c93bd73cacd68008ea/propcache-0.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d310c013aad2c72f1c3f2f8dd3279d460a858c551f97aeb8c63e4693cca7b4d2", size = 59994, upload-time = "2026-05-08T21:00:47.093Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/db650677f554a95b9c01a7c9d93d629e93a15562f5deb4573c9ee136fed2/propcache-0.5.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:06187263ddad280d05b4d8a8b3bb7d164cbebd469236544a42e6d9b28ac6a4fa", size = 56884, upload-time = "2026-05-08T21:00:48.376Z" }, + { url = "https://files.pythonhosted.org/packages/80/45/70b39b89516ff8b96bf732fa6fded8cef20f293cb1508690101c3c07ec51/propcache-0.5.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3115559b8effafd63b142ea5ed53d63a16ea6469cbc63dce4ee194b42db5d853", size = 63464, upload-time = "2026-05-08T21:00:49.954Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e2/fa59d3a89eac5534293124af4f1d0d0ada091ce4a0ab4610ce03fd2bdd8d/propcache-0.5.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c60462af8e6dc30c35407c7237ea908d777b22862bbee27bc4699c0d8bcdc45a", size = 61588, upload-time = "2026-05-08T21:00:51.281Z" }, + { url = "https://files.pythonhosted.org/packages/0b/97/efb547a55c4bc7381cfb202d6a2239ac621045277bc1ea5dfd3a7f0516c0/propcache-0.5.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40314bca9ac559716fe374094fc81c11dcc34b64fd6c585360f5775690505704", size = 64667, upload-time = "2026-05-08T21:00:52.602Z" }, + { url = "https://files.pythonhosted.org/packages/92/56/f5c7d9b4b7595d5127da38974d791b2153f3d1eae6c674af3583ace92ad3/propcache-0.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cfa21e036ce1e1db2be04ba3b85d2df1bb1702fa01932d984c5464c665228ff4", size = 62463, upload-time = "2026-05-08T21:00:54.303Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3b/484a3a65fc9f9f60c41dcd17b428bace5389544e2c680994534a20755066/propcache-0.5.2-cp313-cp313-win32.whl", hash = "sha256:f156a3529f38063b6dbaf356e15602a7f95f8055b1295a438433a6386f10463d", size = 38621, upload-time = "2026-05-08T21:00:55.808Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fd/3f0f10dba4dabad3bf53102be007abf55481067952bde0fdddff439e7c61/propcache-0.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfed59d0a5aeb01e242e66ff0300bc4a265a7c05f612d30016f0b60b1017d757", size = 41649, upload-time = "2026-05-08T21:00:57.061Z" }, + { url = "https://files.pythonhosted.org/packages/90/ec/6ce619cc32bb500a482f811f9cd509368b4e58e638d13f2c68f370d6b475/propcache-0.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:ba338430e87ceb9c8f0cf754de38a9860560261e56c00376debd628698a7364f", size = 37636, upload-time = "2026-05-08T21:00:58.646Z" }, + { url = "https://files.pythonhosted.org/packages/1b/82/c1d268bbbf2ef981c5bf0fbbe746db617c66e3bcefe431a1aa8943fbe23a/propcache-0.5.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a592f5f3da71c8691c788c13cb6734b6d17663d2e1cb8caddf0673d01ef8847d", size = 98872, upload-time = "2026-05-08T21:00:59.889Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d4/52c871e73e864e6b34c0e2d58ac1ec5ccd149497ddc7ad2137ae98323a35/propcache-0.5.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6a997d0489e9668a384fcfd5061b857aa5361de73191cac204d04b889cfbbafa", size = 56257, upload-time = "2026-05-08T21:01:01.195Z" }, + { url = "https://files.pythonhosted.org/packages/67/f0/9b90ca2a210b3d09bcfcd96ecd0f55545c091535abce2a45de2775cfd357/propcache-0.5.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:10734b5484ea113152ee25a91dccedf81631791805d2c9ccb054958e51842c94", size = 56696, upload-time = "2026-05-08T21:01:02.941Z" }, + { url = "https://files.pythonhosted.org/packages/9d/0e/6e9d4ba07c8e56e21ddec1e75f12148142b21ca83a51871babce095334f4/propcache-0.5.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cafca7e56c12bb02ae16d283742bef25a61122e9dab2b5b3f2ccbe589ce32164", size = 62378, upload-time = "2026-05-08T21:01:04.475Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/c10badaa463dde8a27ce884f8ee2ec37e6035b7c9f5ff0c8f74f06f08dac/propcache-0.5.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f064f8d2b59177878b7615df1735cd8fe3462ed6be8c7b217d17a276489c2b7f", size = 65283, upload-time = "2026-05-08T21:01:05.959Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/93bea99ca80e19cef6512a8580e5b7857bbe09422d9daa7fd4ef5723306c/propcache-0.5.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f78abfa8dfc32376fd1aacf597b2f2fbbe0ea751419aee718af5d4f82537ef8c", size = 66616, upload-time = "2026-05-08T21:01:07.228Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/5c7462e50625f051f37fb38b8224f7639f667184bbd34424ec83819bb1b7/propcache-0.5.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7467da8a9822bf1a55336f877340c5bcbd3c482afc43a99771169f74a26dedc", size = 63773, upload-time = "2026-05-08T21:01:08.514Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b6/99238894047b13c823be25027e736626cd414a52a5e30d2c3347c2733529/propcache-0.5.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a6ddc6ac9e25de626c1f129c1b467d7ecd33ce2237d3fd0c4e429feef0a7ee1f", size = 63664, upload-time = "2026-05-08T21:01:09.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/1e/a3a1a63116a2b8edb415a8bb9a6f0c34bd03830b1e18e8ce2904e1dc1cf4/propcache-0.5.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2f22cbbac9e26a8e864c0985ff1268d5d939d53d9d9411a9824279097e03a2cb", size = 62643, upload-time = "2026-05-08T21:01:11.132Z" }, + { url = "https://files.pythonhosted.org/packages/e4/03/893cf147de2fc6543c5eaa07ad833170e7e2a2385725bbebe8c0503723bb/propcache-0.5.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:fc76378c62a0f04d0cd82fbb1a2cd2d7e28fcb40d5873f28a6c44e388aaa2751", size = 59595, upload-time = "2026-05-08T21:01:12.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/3b/04c1a2e12c57766568ba75ba72b3bf2042818d4c1425fab6fc07155c7cff/propcache-0.5.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:acd2c8edba48e31e58a363b8cf4e5c7db3b04b3f9e371f601df30d9b0d244836", size = 65711, upload-time = "2026-05-08T21:01:13.676Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/80f8d0099f8d6bacc4de1624c85672681c8cd1149ca2da0e38fd120b817f/propcache-0.5.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:452b5065457eb9991ec5eb38ff41d6cd4c991c9ac7c531c4d5849ae473a9a13f", size = 64247, upload-time = "2026-05-08T21:01:14.936Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1a/8b08f3a5f1037e9e370c55883ceeeee0f6dd0416fb2d2d67b8bfc91f2a79/propcache-0.5.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3430bb2bfe1331885c427745a751e774ee679fd4344f80b97bf879815fe8fa55", size = 67102, upload-time = "2026-05-08T21:01:16.281Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/8bdb7bb7756d76e005490649d10e4a8369e610c74d619f71e1aedf889e9c/propcache-0.5.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cef6cea3922890dd6c9654971001fa797b526c16ab5e1e46c05fd6f877be7568", size = 64964, upload-time = "2026-05-08T21:01:17.57Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/50fb0b5d3968b61a510926ff8b8465f1d6e976b3ab74496d7a4b9fc42515/propcache-0.5.2-cp313-cp313t-win32.whl", hash = "sha256:72d61e16dd78228b58c5d47be830ff3da7e5f139abdf0aef9d86cde1c5cf2191", size = 42546, upload-time = "2026-05-08T21:01:18.946Z" }, + { url = "https://files.pythonhosted.org/packages/ae/4c/0ddbae64321bd4a95bcbfc19307238016b5b1fee645c84626c8d539e5b74/propcache-0.5.2-cp313-cp313t-win_amd64.whl", hash = "sha256:0958834041a0166d343b8d2cedcd8bcbaeb4fdbe0cf08320c5379f143c3be6e7", size = 46330, upload-time = "2026-05-08T21:01:20.162Z" }, + { url = "https://files.pythonhosted.org/packages/00/d9/9cddc8efb78d8af264c5ec9f6d10b62f57c515feda8d321595f56010fb23/propcache-0.5.2-cp313-cp313t-win_arm64.whl", hash = "sha256:6de8bd93ddde9b992cf2b2e0d796d501a19026b5b9fd87356d7d0779531a8d96", size = 40521, upload-time = "2026-05-08T21:01:21.399Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ea/23ee535d90ce8bcc465a3028eb3cc0ce3bd1005f4bb27710b30587de798d/propcache-0.5.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:46088abff4cba581dea21ae0467a480526cb25aa5f3c269e909f800328bc3999", size = 94662, upload-time = "2026-05-08T21:01:22.683Z" }, + { url = "https://files.pythonhosted.org/packages/b5/06/c5a52f419b5d8972f8d46a7577476090d8e3263ff589ce40b5ca4968d5be/propcache-0.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fc88b26f08d634f7bc819a7852e5214f5802641ab8d9fd5326892292eee1993e", size = 53928, upload-time = "2026-05-08T21:01:23.986Z" }, + { url = "https://files.pythonhosted.org/packages/63/b1/4260d67d6bd85e58a66b72d54ce15d5de789b6f3870cc6bedf8ff9667401/propcache-0.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97797ebb098e670a2f92dd66f32897e30d7615b14e7f59711de23e30a9072539", size = 54650, upload-time = "2026-05-08T21:01:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/70/06/2f46c318e3307cd7a6a7481def374ce838c0fe20084b39dd54b0879d0e99/propcache-0.5.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba57fffe4ac99c5d30076161b5866336d97600769bad35cc68f7774b15298a4e", size = 59912, upload-time = "2026-05-08T21:01:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/4c/29/fe1aebec2ce57ab985a9c382bded1124431f85078113aa222c5d278430d4/propcache-0.5.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:583c19759d9eec1e5b69e2fbef36a7d9c326041be9746cb822d335c8cedc2979", size = 63300, upload-time = "2026-05-08T21:01:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/b4/18/2334b26768b6c82be8c69e83671b767d5ef426aa09b0cba6c2ea47816774/propcache-0.5.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d0326e2e5e1f3163fa306c834e48e8d490e5fae607a097a40c0648109b47ba80", size = 64208, upload-time = "2026-05-08T21:01:29.484Z" }, + { url = "https://files.pythonhosted.org/packages/2b/76/7f1bfd6afff4c5e38e36a3c6d68eb5f4b7311ea80baf693db78d95b603c4/propcache-0.5.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e00820e192c8dbebcafb383ebbf99030895f09905e7a0eb2e0340a0bcc2bc825", size = 61633, upload-time = "2026-05-08T21:01:31.068Z" }, + { url = "https://files.pythonhosted.org/packages/c4/46/b3ff8aba2b4953a3e50de2cf72f1b5748b8eca93b15f3dc2c84339084c09/propcache-0.5.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c66afea89b1e43725731d2004732a046fe6fe955d51f952c3e95a7314a284a39", size = 61724, upload-time = "2026-05-08T21:01:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/c5/01/814cfcafbcff954f94c01cf30e097ddc88a076b5440fbcf4570753437d40/propcache-0.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc37dec6c6cdad0b57881a5658fd14fbf53e333b1a86cf86559f190e1d9ec4", size = 60069, upload-time = "2026-05-08T21:01:33.67Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/5c6f7622d510cc666a300687e06fd060c1a43361c0c9b20d284f06d8096a/propcache-0.5.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5570dbcc97571c15f68068e529c92715a12f8d54030e272d264b377e22bd17a5", size = 57099, upload-time = "2026-05-08T21:01:34.915Z" }, + { url = "https://files.pythonhosted.org/packages/55/27/9cb0b4c679124085327957d42521c99dba04c88c90c3e55a6f0b633ebccc/propcache-0.5.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f814362777a9f841adddb200ecdf8f5cb1e5a3c4b7a86378edbd6ccb26edd702", size = 63391, upload-time = "2026-05-08T21:01:36.231Z" }, + { url = "https://files.pythonhosted.org/packages/f0/9d/7258aaa5bdf60fc6f27591eef6fe52768cb0beda7140be477c8b12c9794a/propcache-0.5.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:196913dea116aeb5a2ba95af4ddcb7ea85559ae07d8eee8751688310d09168c3", size = 61626, upload-time = "2026-05-08T21:01:37.545Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/41c602003e8a9b16fe1e7eadf62c7bfba9d5474370b24200bf48b315f45f/propcache-0.5.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6e7b8719005dd1175be4ab1cd25e9b98659a5e0347331506ec6760d2773a7fb5", size = 64781, upload-time = "2026-05-08T21:01:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f3/38e66b1856e9bd079deea015bc4a55f7767c0e4db2f7dcf69e7e680ba4ce/propcache-0.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:51f96d685ab16e88cab128cd37a52c5da540809c8b879fa047731bfcb4ad35a4", size = 62570, upload-time = "2026-05-08T21:01:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/bbfe9b910ce57dde8bb4876b4520fc02a4e89497c10de26be936758a3aaa/propcache-0.5.2-cp314-cp314-win32.whl", hash = "sha256:cc6fc3cc62e8501d3ed62894425040d2728ecddb1ed072737a5c70bd537aa9f0", size = 39436, upload-time = "2026-05-08T21:01:41.654Z" }, + { url = "https://files.pythonhosted.org/packages/61/d2/45c9defbaa1ea297035d9d4cce9e8f80daafbf19319c6007f157c6256ea9/propcache-0.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:81e3a30b0bb60caa22033dd0f8a3618d1d67356212514f62c57db75cb0ef410c", size = 42373, upload-time = "2026-05-08T21:01:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/44/68/9ea5103f41d5217d7d6ec24db90018e23aebec070c3f9a6e54d12b841fd8/propcache-0.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:0d2c9bf8528f135dbb805ce027567e09164f7efa51a2be07458a2c0420f292d0", size = 38554, upload-time = "2026-05-08T21:01:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/fadf555f42d3b762eea8a53950b0489fdc0aa9da5f8ed9e10ce0a4e01b48/propcache-0.5.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bc8ff1feffc6a61c7002ffe84634c41b822e104990ae009f44a0834430070bb", size = 99395, upload-time = "2026-05-08T21:01:45.883Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c9/c61e134a686949cf7971af3a390148b1156f7be81c73bc0cd12c873e2d48/propcache-0.5.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:79aa3ff0a9b566633b642fa9caf7e21ed1c13d6feca718187873f199e1514078", size = 56653, upload-time = "2026-05-08T21:01:47.307Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/daf935ea7048ddd7ec8eec5345b4a40b619d2d178b3c0a0900796bc3c794/propcache-0.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1b31822f4474c4036bae62de9402710051d431a606d6a0f907fec79935a071aa", size = 56914, upload-time = "2026-05-08T21:01:48.573Z" }, + { url = "https://files.pythonhosted.org/packages/79/9f/aba959b435ea18617edd7cf0a7ad0b9c574b8fc7e3d2cd55fb59cb255d33/propcache-0.5.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fef48778b5a2a756523fdb781326b028ca75e32858b04f2cdd19f394564917", size = 62567, upload-time = "2026-05-08T21:01:49.903Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a1/859942de9a791ff42f6141736f5b37749b8f53e65edfa49638c67dd67e6a/propcache-0.5.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8b73ab70f1a3351fbc71f663b3e645af6dd0329100c353081cf69c37433fc6fe", size = 65542, upload-time = "2026-05-08T21:01:51.204Z" }, + { url = "https://files.pythonhosted.org/packages/b5/61/315bc0fd6c0fc7f80a528b8afd209e5fc4a875ea79571b91b8f50f442907/propcache-0.5.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5538d2c13d93e4698af7e092b57bc7298fd35d1d58e656ae18f23ee0d0378e03", size = 66845, upload-time = "2026-05-08T21:01:52.539Z" }, + { url = "https://files.pythonhosted.org/packages/47/f7/9f8122e3132e8e354ac41975ef8f1099be7d5a16bc7ae562734e993665c0/propcache-0.5.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd645f03898405cabe694fb8bc35241e3a9c332ec85627584fe3de201452b335", size = 63985, upload-time = "2026-05-08T21:01:53.847Z" }, + { url = "https://files.pythonhosted.org/packages/c8/54/c317819ec157cbf6f35df9df9657a6f82daf34d5faf15948b2f639c2192e/propcache-0.5.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a473b3440261e0c60706e732b2ed2f517857344fc21bf48fdfe211e2d98eb285", size = 63999, upload-time = "2026-05-08T21:01:55.179Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/387e3f7dfce0a9233df41fb888aa1c30222cb4bbbf09537c02dd9bd85fe2/propcache-0.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7afa37062e6650640e932e4cc9297d81f9f42d9944029cc386b8247dea4da837", size = 62779, upload-time = "2026-05-08T21:01:57.489Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9c/596784cb5824ed61ee960d3f8655a3f0993e107c6e98ab6c818b7fb92ccb/propcache-0.5.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:8a90efd5777e996e42d568db9ac740b944d691e565cbfd31b2f7832f9184b2b8", size = 59796, upload-time = "2026-05-08T21:01:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3d/1a6cfa1726a48542c1e8784a0761421476a5b68e09b7f36bf95eb954aaba/propcache-0.5.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:f19bb891234d72535764d703bfed1153cc34f4214d5bd7150aee1eec9e8f4366", size = 66023, upload-time = "2026-05-08T21:02:00.228Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0e/05fd6990369477076e4e280bcb970de760fddf0161a46e988bc95f7940ec/propcache-0.5.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:32775082acd2d807ee3db715c7770d38767b817870acfa08c29e057f3c4d5b56", size = 64448, upload-time = "2026-05-08T21:02:01.888Z" }, + { url = "https://files.pythonhosted.org/packages/cd/86/5f8da315a4309c62c10c0b2516b17492d5d3bbe1bb862b96604db67e2a37/propcache-0.5.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9282fb1a3bccd038da9f768b927b24a0c753e466c086b7c4f3c6982851eefb2d", size = 67329, upload-time = "2026-05-08T21:02:03.484Z" }, + { url = "https://files.pythonhosted.org/packages/da/d3/3368efe79ab21f0cdf86ef49895811c9cc933131d4cde1f28a624e22e712/propcache-0.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc49723e2f60d6b32a0f0b08a3fd6d13203c07f1cd9566cfce0f12a917c967a2", size = 65172, upload-time = "2026-05-08T21:02:04.745Z" }, + { url = "https://files.pythonhosted.org/packages/d5/07/127e8b0bacfb325396196f9d976a22453049b89b9b2b08477cc3145faa44/propcache-0.5.2-cp314-cp314t-win32.whl", hash = "sha256:2d7aa89ebca5acc98cba9d1472d976e394782f587bad6661003602a619fd1821", size = 43813, upload-time = "2026-05-08T21:02:06.025Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/46dad6c0ae49ed230ab1b16c890c2b6314e2403e6c412976f4a72d64a527/propcache-0.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d447bb0b3054be5818458fbb171208b1d9ff11eba14e18ca18b90cbb45767370", size = 47764, upload-time = "2026-05-08T21:02:07.353Z" }, + { url = "https://files.pythonhosted.org/packages/e7/c4/a47d0a63aa309d10d59ede6e9d4cff03a344a79d1f0f4cd0cd74997b53e0/propcache-0.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:fe67a3d11cd9b4efabfa45c3d00ffba2b26811442a73a581a94b67c2b5faccf6", size = 41140, upload-time = "2026-05-08T21:02:09.065Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ed/1cdcab6ba3d6ab7feca11fc14f0eeea80755bb53ef4e892079f31b10a25f/propcache-0.5.2-py3-none-any.whl", hash = "sha256:be1ddfcbb376e3de5d2e2db1d58d6d67463e6b4f9f040c000de8e300295465fe", size = 14036, upload-time = "2026-05-08T21:02:10.673Z" }, +] + +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + +[[package]] +name = "proxy-tools" +version = "0.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/cf/77d3e19b7fabd03895caca7857ef51e4c409e0ca6b37ee6e9f7daa50b642/proxy_tools-0.1.0.tar.gz", hash = "sha256:ccb3751f529c047e2d8a58440d86b205303cf0fe8146f784d1cbcd94f0a28010", size = 2978, upload-time = "2014-05-05T21:02:24.606Z" } + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "py4j" +version = "0.10.9.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/31/0b210511177070c8d5d3059556194352e5753602fa64b85b7ab81ec1a009/py4j-0.10.9.9.tar.gz", hash = "sha256:f694cad19efa5bd1dee4f3e5270eb406613c974394035e5bfc4ec1aba870b879", size = 761089, upload-time = "2025-01-15T03:53:18.624Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/db/ea0203e495be491c85af87b66e37acfd3bf756fd985f87e46fc5e3bf022c/py4j-0.10.9.9-py2.py3-none-any.whl", hash = "sha256:c7c26e4158defb37b0bb124933163641a2ff6e3a3913f7811b0ddbe07ed61533", size = 203008, upload-time = "2025-01-15T03:53:15.648Z" }, +] + +[[package]] +name = "pyarrow" +version = "24.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/13/13e1069b351bdc3881266e11147ffccf687505dbb0ea74036237f5d454a5/pyarrow-24.0.0.tar.gz", hash = "sha256:85fe721a14dd823aca09127acbb06c3ca723efbd436c004f16bca601b04dcc83", size = 1180261, upload-time = "2026-04-21T10:51:25.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/bf/a34fee1d624152124fa8355c42f34195ad5fe5233ce5bb87946432047d52/pyarrow-24.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:7c2b98645d576a0b9616892ead22b64a83a5f043c5e2ca15ebcefcb5b70c80cb", size = 35076681, upload-time = "2026-04-21T08:51:46.845Z" }, + { url = "https://files.pythonhosted.org/packages/1d/41/64180033d7027afce12dc96d0fe1f504c6fa112190582b458acea2399530/pyarrow-24.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:644a246325b8c69c595ad1dd4b463eba4b0cdb731370e4a86137d433208d6147", size = 36684260, upload-time = "2026-04-21T08:51:53.642Z" }, + { url = "https://files.pythonhosted.org/packages/57/02/9b9320e673dd8a99411fac78690f3df92f6dd6f59754c750110bca66d64e/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3a577bd840ca83f646f0a625dbc571dba7044c43c2d1503afc378b570954345c", size = 45698566, upload-time = "2026-04-21T10:46:02.133Z" }, + { url = "https://files.pythonhosted.org/packages/67/33/f75e91b9a64c3f33c787e263c93b871ad91b8a4a68c1d5cebddd9840e835/pyarrow-24.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:e3268e43984d0b1a185c89b4cfff282a7ead12fc93f56cfd7088bdbcbe727041", size = 48835562, upload-time = "2026-04-21T10:46:10.278Z" }, + { url = "https://files.pythonhosted.org/packages/a5/63/097510448e47e4091faa41c43ba92f97cecaab8f4535b56a3d149578f634/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2392d954fcb920f42d230284b677605e4e2fbb11f2821e823e642abd67fbb491", size = 49394997, upload-time = "2026-04-21T10:46:18.08Z" }, + { url = "https://files.pythonhosted.org/packages/60/6b/c047d6222ab279024a062742d1807e2fbaf27bba88a98637299ff47b9236/pyarrow-24.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bec9373df11544592b0ba7ec2af0e35059e5f0e7647c6183a854dedd193298f1", size = 51911424, upload-time = "2026-04-21T10:46:25.347Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ba/464cc70761c2a525d97ebd84e21c31ebd47f3ef4bdcee117009f51c46f24/pyarrow-24.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:c42ab9439498270139cc63e18847a02afe5c8b3ed9c931266533cfe378bd3591", size = 27251730, upload-time = "2026-04-21T10:46:30.913Z" }, + { url = "https://files.pythonhosted.org/packages/62/c9/a47ab7ece0d86cbe6678418a0fbd1ac4bb493b9184a3891dfa0e7f287ae0/pyarrow-24.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b0e131f880cda8d04e076cee175a46fc0e8bc8b65c99c6c09dff6669335fde74", size = 35068898, upload-time = "2026-04-21T10:46:36.599Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bc/8db86617a9a58008acf8913d6fed68ea2a46acb6de928db28d724c891a68/pyarrow-24.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:1b2fe7f9a5566401a0ef2571f197eb92358925c1f0c8dba305d6e43ea0871bb3", size = 36679915, upload-time = "2026-04-21T10:46:42.602Z" }, + { url = "https://files.pythonhosted.org/packages/eb/8e/fb178720400ef69db251eb4a9c3ccf4af269bc1feb5055529b8fc87170d1/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0b3537c00fb8d384f15ac1e79b6eb6db04a16514c8c1d22e59a9b95c8ba42868", size = 45697931, upload-time = "2026-04-21T10:46:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/27/99c42abe8e21b44f4917f62631f3aa31404882a2c41d8a4cd5c110e13d52/pyarrow-24.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:14e31a3c9e35f1ab6356c6378f6f72830e6d2d5f1791df3774a7b097d18a6a1e", size = 48837449, upload-time = "2026-04-21T10:46:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/36/b6/333749e2666e9032891125bf9c691146e92901bece62030ac1430e2e7c88/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7d9a514e73bc42711e6a35aaccf3587c520024fe0a25d830a1a8a27c15f4f57", size = 49395949, upload-time = "2026-04-21T10:47:01.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/25/c5201706a2dd374e8ba6ee3fd7a8c89fb7ffc16eed5217a91fd2bd7f7626/pyarrow-24.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b196eb3f931862af3fa84c2a253514d859c08e0d8fe020e07be12e75a5a9780c", size = 51912986, upload-time = "2026-04-21T10:47:09.872Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d2/4d1bbba65320b21a49678d6fbdc6ff7c649251359fdcfc03568c4136231d/pyarrow-24.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:35405aecb474e683fb36af650618fd5340ee5471fc65a21b36076a18bbc6c981", size = 27255371, upload-time = "2026-04-21T10:47:15.943Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a9/9686d9f07837f91f775e8932659192e02c74f9d8920524b480b85212cc68/pyarrow-24.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:6233c9ed9ab9d1db47de57d9753256d9dcffbf42db341576099f0fd9f6bf4810", size = 34981559, upload-time = "2026-04-21T10:47:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/80/b6/0ddf0e9b6ead3474ab087ae598c76b031fc45532bf6a63f3a553440fb258/pyarrow-24.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:f7616236ec1bc2b15bfdec22a71ab38851c86f8f05ff64f379e1278cf20c634a", size = 36663654, upload-time = "2026-04-21T10:47:28.315Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3b/926382efe8ce27ba729071d3566ade6dfb86bdf112f366000196b2f5780a/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:1617043b99bd33e5318ae18eb2919af09c71322ef1ca46566cdafc6e6712fb66", size = 45679394, upload-time = "2026-04-21T10:47:34.821Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/829f7d9dfd37c207206081d6dad474d81dde29952401f07f2ba507814818/pyarrow-24.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:6165461f55ef6314f026de6638d661188e3455d3ec49834556a0ebbdbace18bb", size = 48863122, upload-time = "2026-04-21T10:47:42.056Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e8/f88ce625fe8babaae64e8db2d417c7653adb3019b08aae85c5ed787dc816/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3b13dedfe76a0ad2d1d859b0811b53827a4e9d93a0bcb05cf59333ab4980cc7e", size = 49376032, upload-time = "2026-04-21T10:47:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/36/7a/82c363caa145fff88fb475da50d3bf52bb024f61917be5424c3392eaf878/pyarrow-24.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:25ea65d868eb04015cd18e6df2fbe98f07e5bda2abefabcb88fce39a947716f6", size = 51929490, upload-time = "2026-04-21T10:47:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/66/1c/e3e72c8014ad2743ca64a701652c733cc5cbcee15c0463a32a8c55518d9e/pyarrow-24.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:295f0a7f2e242dabd513737cf076007dc5b2d59237e3eca37b05c0c6446f3826", size = 27355660, upload-time = "2026-04-21T10:48:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a1abf004482026ddc17f4503db227787fa3cfe41ec5091ff20e4fea55e57/pyarrow-24.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:02b001b3ed4723caa44f6cd1af2d5c86aa2cf9971dacc2ffa55b21237713dfba", size = 34976759, upload-time = "2026-04-21T10:48:07.258Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4a/34f0a36d28a2dd32225301b79daad44e243dc1a2bb77d43b60749be255c4/pyarrow-24.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:04920d6a71aabd08a0417709efce97d45ea8e6fb733d9ca9ecffb13c67839f68", size = 36658471, upload-time = "2026-04-21T10:48:13.347Z" }, + { url = "https://files.pythonhosted.org/packages/1f/78/543b94712ae8bb1a6023bcc1acf1a740fbff8286747c289cd9468fced2a5/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a964266397740257f16f7bb2e4f08a0c81454004beab8ff59dd531b73610e9f2", size = 45675981, upload-time = "2026-04-21T10:48:20.201Z" }, + { url = "https://files.pythonhosted.org/packages/84/9f/8fb7c222b100d314137fa40ec050de56cd8c6d957d1cfff685ce72f15b17/pyarrow-24.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6f066b179d68c413374294bc1735f68475457c933258df594443bb9d88ddc2a0", size = 48859172, upload-time = "2026-04-21T10:48:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/a7/d3/1ea72538e6c8b3b475ed78d1049a2c518e655761ea50fe1171fc855fcab7/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1183baeb14c5f587b1ec52831e665718ce632caab84b7cd6b85fd44f96114495", size = 49385733, upload-time = "2026-04-21T10:48:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/c3/be/c3d8b06a1ba35f2260f8e1f771abbee7d5e345c0937aab90675706b1690a/pyarrow-24.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:806f24b4085453c197a5078218d1ee08783ebbba271badd153d1ae22a3ee804f", size = 51934335, upload-time = "2026-04-21T10:48:42.099Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/89e07a1e7329d2cde3e3c6994ba0839a24977a2beda8be6005ea3d860b99/pyarrow-24.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:e4505fc6583f7b05ab854934896bcac8253b04ac1171a77dfb73efef92076d91", size = 27271748, upload-time = "2026-04-21T10:49:42.532Z" }, + { url = "https://files.pythonhosted.org/packages/17/1a/cff3a59f80b5b1658549d46611b67163f65e0664431c076ad728bf9d5af4/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:1a4e45017efbf115032e4475ee876d525e0e36c742214fbe405332480ecd6275", size = 35238554, upload-time = "2026-04-21T10:48:48.526Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/cce0f42a327bfef2c420fb6078a3eb834826e5d6697bf3009fe11d2ad051/pyarrow-24.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:7986f1fa71cee060ad00758bcc79d3a93bab8559bf978fab9e53472a2e25a17b", size = 36782301, upload-time = "2026-04-21T10:48:55.181Z" }, + { url = "https://files.pythonhosted.org/packages/2a/66/8e560d5ff6793ca29aca213c53eec0dd482dd46cb93b2819e5aab52e4252/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:d3e0b61e8efb24ed38898e5cdc5fffa9124be480008d401a1f8071500494ae42", size = 45721929, upload-time = "2026-04-21T10:49:03.676Z" }, + { url = "https://files.pythonhosted.org/packages/27/0c/a26e25505d030716e078d9f16eb74973cbf0b33b672884e9f9da1c83b871/pyarrow-24.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:55a3bc1e3df3b5567b7d27ef551b2283f0c68a5e86f1cd56abc569da4f31335b", size = 48825365, upload-time = "2026-04-21T10:49:11.714Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/771f9ecb0c65e73fe9dccdd1717901b9594f08c4515d000c7c62df573811/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:641f795b361874ac9da5294f8f443dfdbee355cf2bd9e3b8d97aaac2306b9b37", size = 49451819, upload-time = "2026-04-21T10:49:21.474Z" }, + { url = "https://files.pythonhosted.org/packages/48/da/61ae89a88732f5a785646f3ec6125dbb640fa98a540eb2b9889caa561403/pyarrow-24.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8adc8e6ce5fccf5dc707046ae4914fd537def529709cc0d285d37a7f9cd442ca", size = 51909252, upload-time = "2026-04-21T10:49:31.164Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1a/8dd5cafab7b66573fa91c03d06d213356ad4edd71813aa75e08ce2b3a844/pyarrow-24.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:9b18371ad2f44044b81a8d23bc2d8a9b6a6226dca775e8e16cfee640473d6c5d", size = 27388127, upload-time = "2026-04-21T10:49:37.334Z" }, + { url = "https://files.pythonhosted.org/packages/ad/80/d022a34ff05d2cbedd8ccf841fc1f532ecfa9eb5ed1711b56d0e0ea71fc9/pyarrow-24.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:1cc9057f0319e26333b357e17f3c2c022f1a83739b48a88b25bfd5fa2dc18838", size = 35007997, upload-time = "2026-04-21T10:49:48.796Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ff/f01485fda6f4e5d441afb8dd5e7681e4db18826c1e271852f5d3957d6a80/pyarrow-24.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:e6f1278ee4785b6db21229374a1c9e54ec7c549de5d1efc9630b6207de7e170b", size = 36678720, upload-time = "2026-04-21T10:49:55.858Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c2/2d2d5fea814237923f71b36495211f20b43a1576f9a4d6da7e751a64ec6f/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:adbbedc55506cbdabb830890444fb856bfb0060c46c6f8026c6c2f2cf86ae795", size = 45741852, upload-time = "2026-04-21T10:50:04.624Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3a/28ba9c1c1ebdbb5f1b94dfebb46f207e52e6a554b7fe4132540fde29a3a0/pyarrow-24.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ae8a1145af31d903fa9bb166824d7abe9b4681a000b0159c9fb99c11bc11ad26", size = 48889852, upload-time = "2026-04-21T10:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/df/51/4a389acfd31dca009f8fb82d7f510bb4130f2b3a8e18cf00194d0687d8ac/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d7027eba1df3b2069e2e8d80f644fa0918b68c46432af3d088ddd390d063ecde", size = 49445207, upload-time = "2026-04-21T10:50:20.677Z" }, + { url = "https://files.pythonhosted.org/packages/19/4b/0bab2b23d2ae901b1b9a03c0efd4b2d070256f8ce3fc43f6e58c167b2081/pyarrow-24.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e56a1ffe9bf7b727432b89104cc0849c21582949dd7bdcb34f17b2001a351a76", size = 51954117, upload-time = "2026-04-21T10:50:29.14Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/f4e9145da0417b3d2c12035a8492b35ff4a3dbc653e614fcfb51d9dedb38/pyarrow-24.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:38be1808cdd068605b787e6ca9119b27eb275a0234e50212c3492331680c3b1e", size = 28001155, upload-time = "2026-04-21T10:51:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/79/4f/46a49a63f43526da895b1a45bbb51d5baf8e4d77159f8528fc3e5490007f/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:418e48ce50a45a6a6c73c454677203a9c75c966cb1e92ca3370959185f197a05", size = 35250387, upload-time = "2026-04-21T10:50:35.552Z" }, + { url = "https://files.pythonhosted.org/packages/a0/da/d5e0cd5ef00796922404806d5f00325cdadc3441ce2c13fe7115f2df9a64/pyarrow-24.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:2f16197705a230a78270cdd4ea8a1d57e86b2fdcbc34a1f6aebc72e65c986f9a", size = 36797102, upload-time = "2026-04-21T10:50:42.417Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/5904145b0a593a05236c882933d439b5720f0a145381179063722fbfc123/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:fb24ac194bfc5e86839d7dcd52092ee31e5fe6733fe11f5e3b06ef0812b20072", size = 45745118, upload-time = "2026-04-21T10:50:49.324Z" }, + { url = "https://files.pythonhosted.org/packages/13/d3/cca42fe166d1c6e4d5b80e530b7949104d10e17508a90ae202dac205ce2a/pyarrow-24.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:9700ebd9a51f5895ce75ff4ac4b3c47a7d4b42bc618be8e713e5d56bacf5f931", size = 48844765, upload-time = "2026-04-21T10:50:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/942c3b79878ba928324d1e17c274ed84581db8c0a749b24bcf4cbdf15bd3/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d8ddd2768da81d3ee08cfea9b597f4abb4e8e1dc8ae7e204b608d23a0d3ab699", size = 49471890, upload-time = "2026-04-21T10:51:02.439Z" }, + { url = "https://files.pythonhosted.org/packages/76/97/ff71431000a75d84135a1ace5ca4ba11726a231a8007bbb320a4c54075d5/pyarrow-24.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:61a3d7eaa97a14768b542f3d284dc6400dd2470d9f080708b13cd46b6ae18136", size = 51932250, upload-time = "2026-04-21T10:51:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/51/be/6f79d55816d5c22557cf27533543d5d70dfe692adfbee4b99f2760674f38/pyarrow-24.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c91d00057f23b8d353039520dc3a6c09d8608164c692e9f59a175a42b2ae0c19", size = 28131282, upload-time = "2026-04-21T10:51:16.815Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pycryptodome" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, + { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, + { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, + { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, + { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, + { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, + { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, + { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, + { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379", size = 1623886, upload-time = "2025-05-17T17:21:20.614Z" }, + { url = "https://files.pythonhosted.org/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4", size = 1672151, upload-time = "2025-05-17T17:21:22.666Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630", size = 1664461, upload-time = "2025-05-17T17:21:25.225Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/608fbdad566ebe499297a86aae5f2a5263818ceeecd16733006f1600403c/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7fc76bf273353dc7e5207d172b83f569540fc9a28d63171061c42e361d22353", size = 1702440, upload-time = "2025-05-17T17:21:27.991Z" }, + { url = "https://files.pythonhosted.org/packages/d1/92/2eadd1341abd2989cce2e2740b4423608ee2014acb8110438244ee97d7ff/pycryptodome-3.23.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:45c69ad715ca1a94f778215a11e66b7ff989d792a4d63b68dc586a1da1392ff5", size = 1803005, upload-time = "2025-05-17T17:21:31.37Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[package.optional-dependencies] +email = [ + { name = "email-validator" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pydantic-extra-types" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/71/dba38ee2651f84f7842206adbd2233d8bbdb59fb85e9fa14232486a8c471/pydantic_extra_types-2.11.1.tar.gz", hash = "sha256:46792d2307383859e923d8fcefa82108b1a141f8a9c0198982b3832ab5ef1049", size = 172002, upload-time = "2026-03-16T08:08:03.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/c1/3226e6d7f5a4f736f38ac11a6fbb262d701889802595cdb0f53a885ac2e0/pydantic_extra_types-2.11.1-py3-none-any.whl", hash = "sha256:1722ea2bddae5628ace25f2aa685b69978ef533123e5638cfbddb999e0100ec1", size = 79526, upload-time = "2026-03-16T08:08:02.533Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyobjc-core" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/b1/729f7458a63758bd21716648a8abcd9a0c8f2d2e9897763c8a1a1c7fd31b/pyobjc_core-12.2.1.tar.gz", hash = "sha256:7a7b9b018402342cf32bf1956366896350fbe5c0478cb3ef59778f77abed7f07", size = 1063383, upload-time = "2026-06-19T16:19:39.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/62/80fe6b6bea9e9e7abe2e7a91bfd22115219b2263e435d2da09cf480b6f49/pyobjc_core-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:aa5c889961d79d7704f17eeb27f80ee791335819c1bb14babeff7b9ea665b5f0", size = 6486390, upload-time = "2026-06-19T13:29:49.457Z" }, + { url = "https://files.pythonhosted.org/packages/92/87/16564ef5e4568ee0edd9e712d8111dc8b67621d6bb6ff430646ee2d637dd/pyobjc_core-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:24b76a63caf0b5369d4a377c7c0438cd70df81539057af3db839bfaa3579e04a", size = 6484662, upload-time = "2026-06-19T16:04:44.979Z" }, + { url = "https://files.pythonhosted.org/packages/8c/88/300ad283bed0c971c52dcac6f70113e138169d4ce6d856ddd03d16081e51/pyobjc_core-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a64232bb27ed101d4adc7d42b0e64a6d3331aac7bee7861c037a6777a163f10b", size = 6433347, upload-time = "2026-06-19T16:04:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1e/b9b0ddffae66996b8779f1f7958adc9f21c13a0448cd3be8d7fe589b5b0f/pyobjc_core-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:af101222762665a4125157906cb4b23f5d5a63d3851d5e0504f72a1eaaa2cfd2", size = 6436004, upload-time = "2026-06-19T16:04:53.257Z" }, + { url = "https://files.pythonhosted.org/packages/8f/26/bd309ede07784c6e5fac4b440c90a5f72a66da7859ed303a9392fe8a5f3f/pyobjc_core-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:efe465e3ecc6fc73f7c7622620345d134a8d34564ab1c29d8247e45f4ed55071", size = 6687044, upload-time = "2026-06-19T16:04:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8a/cfa4f56939d554dbb342ec6e5226a441e2f552bc2002a0ddf7705bb11bef/pyobjc_core-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2b8fc0531c27277325e113ac00b8a72a82e6145f0a88175b9425d8de814ff69a", size = 6429289, upload-time = "2026-06-19T16:05:02.191Z" }, + { url = "https://files.pythonhosted.org/packages/42/74/446c89bc18103aaa4a00d1fb85ff8acace9a0dc3f362d9678ebf7571e275/pyobjc_core-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9bef500f979e22d54f9da3aaebf6a48f873234b324858bd69256055a318955c7", size = 6690181, upload-time = "2026-06-19T16:05:06.201Z" }, + { url = "https://files.pythonhosted.org/packages/99/c7/0121ee4c616af07ad2de8cd1a286f6978dc9a227eb58b7c2e875cb68a1df/pyobjc_core-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:047c226eeb58a2993ace5e8904e71cc9426ee20d064c617f8fbf32717d37093e", size = 6487078, upload-time = "2026-06-19T16:05:10.093Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a8/cb9fcc150f97d0bf22a2028f88b24cc35949beb1bcc7b8bc5c17d4401677/pyobjc_core-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:1188613805336270279570467e4455b74cb6c0f60913ac74c917ee1c37cfaecb", size = 6733064, upload-time = "2026-06-19T16:05:14.313Z" }, +] + +[[package]] +name = "pyobjc-framework-cocoa" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/34/fbe38a204643aa4e1b91391cdce07a34da565a69171ebcad08de7438a556/pyobjc_framework_cocoa-12.2.1.tar.gz", hash = "sha256:b94b37fe5730e5ae1fb0052912cd174e6ec329b0bfba4a012ae5db1014b5864b", size = 3125751, upload-time = "2026-06-19T16:20:05.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/c5/f6a5458cc5a598baa7a79ffe86248560c631f5a86f4a3c678fb6a815e78b/pyobjc_framework_cocoa-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:05443c1494532779cccff83e9792fdd6f0a4800cac56620132bf28e0bf966e9d", size = 387303, upload-time = "2026-06-19T16:07:35.831Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d6/dc66ea8519a0475efbccf73f82cc28066339bb300a27f5e1bf91ab1d7002/pyobjc_framework_cocoa-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc6da84f4fc62cc25463bbb85e77a57b8d5ac6caf9a60702daf2edb601332f15", size = 387298, upload-time = "2026-06-19T16:07:37.412Z" }, + { url = "https://files.pythonhosted.org/packages/f7/cf/1b3b32b2f28f66cc053c3438ef4e6df36a1591945bf05e7399da18d74553/pyobjc_framework_cocoa-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:28b9b8bab1c36efb94744786918752d0c1842f5fbb67e7d5ca97b5f736512080", size = 388113, upload-time = "2026-06-19T16:07:38.9Z" }, + { url = "https://files.pythonhosted.org/packages/cc/46/68e8e4d926a2f70fed0437047bc3f9fe08af8fe620d94d80656ebc3cfa9b/pyobjc_framework_cocoa-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3b74a78fa7803e547b32e5e8ec1b49987b52fe318383e793bc6cd49b80efbd9f", size = 388183, upload-time = "2026-06-19T16:07:40.483Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f3/dfc9af4c9eb2e5389c860ad5ef252be9fe456db09f39d537555dc5057aa1/pyobjc_framework_cocoa-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dc2eaca2f13c7bcd8e41e51a372e47825dea9dd3126108760eed7ba883d2945c", size = 392275, upload-time = "2026-06-19T16:07:42.078Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c8/b90baa8f3592eded79b4be98fb59d2b8dc16b62361e34292bd95806ebd9f/pyobjc_framework_cocoa-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b386c324d64ae565c1f6b7dfb77be68f640a1c7c23caa6966ab661131f519561", size = 388357, upload-time = "2026-06-19T16:07:43.364Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/64a94651b9294702d55e748d94de30e25bc59d0784526be7643f4467eccd/pyobjc_framework_cocoa-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a6c584e2af0813cb2f6103b184e632665a26f58c1bd5b08ffd6e95a19c617f7b", size = 392404, upload-time = "2026-06-19T16:07:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/5c/cc/26e8a7bf1f5e8caa38b7f80d486296f9fd3c97e71ad7e5444ef22e802758/pyobjc_framework_cocoa-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:b6023657b8d6cc049a21bd6b4752425f2f53c42f9f0b02d64c7608cc484bf103", size = 388589, upload-time = "2026-06-19T16:07:46.276Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eedf743a303ea742b8e082afe3613fb4d6618bc1a48cf2568b004ce906f7/pyobjc_framework_cocoa-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:c685ccd8e266a07cf912a2c5a13b1f2eff2a868a1aff163b4801b4687bd425e1", size = 392691, upload-time = "2026-06-19T16:07:47.477Z" }, +] + +[[package]] +name = "pyobjc-framework-quartz" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/f6/2a8b84dbf1fe7c04dd96ea73d991678d4e09a909f51971ecc51629bb2ab4/pyobjc_framework_quartz-12.2.1.tar.gz", hash = "sha256:b3b8b6f71e66147f8ff9e6213864cc8527e3a0b1ee90835b93ce221f4802d9b0", size = 3215521, upload-time = "2026-06-19T16:21:30.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/8f/7b2abbade50ed918e59c3dfe14aece0f0f8a4dc43a3884fd4f2508c7787a/pyobjc_framework_quartz-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c8fed57ac8a1927e4fe4f48ab4042cbc1087d881743182b6f3f2e6e227a9209f", size = 218013, upload-time = "2026-06-19T16:16:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/b9/08/527d1ff856e2f2446b5887be01989cc08f9adaf3de7d4eb13d07826c362f/pyobjc_framework_quartz-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60f29408b4f9ed5391a29c6b63e2aa56ddfb8b66b3fb47962930427981e14462", size = 217998, upload-time = "2026-06-19T16:16:02.978Z" }, + { url = "https://files.pythonhosted.org/packages/14/fc/d7c7b3134cdbd1a487f3f77b5be125d87a6c9e7d9411035739d99335cc0c/pyobjc_framework_quartz-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:de9c8cca7e95290c8d540466af11c7cdfe3a5458e6f56c34006d5b45243f9ed9", size = 219000, upload-time = "2026-06-19T16:16:04.29Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4b/861f91a1565d3189ee899e177b915551fb9a7e2ca25414025a8974f04e74/pyobjc_framework_quartz-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:54c9bc7f507192691841ee4eba5bf36990b259df83ac728efed2d7ea1cd021e4", size = 219403, upload-time = "2026-06-19T16:16:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/b27010d2f288737f627f74be6d5549f49c841542365c84b9a3011fe39ce7/pyobjc_framework_quartz-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:bfc0d2badd819823d21df8069dcf9544ce360ed747a8895c51bdb25d8d125f45", size = 224458, upload-time = "2026-06-19T16:16:07.252Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/85ffd9d433989205d572a50d625c63b29c05e0c5235a725f15ae1023672c/pyobjc_framework_quartz-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ceb56939c337b36d9d81185ade31f77dc52c85cf79bb16e53e9b32f54b6bb3f5", size = 219769, upload-time = "2026-06-19T16:16:08.814Z" }, + { url = "https://files.pythonhosted.org/packages/e2/d6/b917e4b63d72ea84a27121076f3033f23f6497c0e6ce8d304766c899897f/pyobjc_framework_quartz-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8105c98b798f2bf81c05c54bddeeadbf62f0b5dfec13bd6e719dd2cdf7e1cddf", size = 224717, upload-time = "2026-06-19T16:16:10.215Z" }, + { url = "https://files.pythonhosted.org/packages/04/e2/f3c1ed3228f7430ef5ade23db6f1fcbae99290f177ce5653348fd9e05f4d/pyobjc_framework_quartz-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:bbc214f1a216b5d3651bc832d0ac4589f029f3f37cd6cbb370aac12a7c77942c", size = 219825, upload-time = "2026-06-19T16:16:11.433Z" }, + { url = "https://files.pythonhosted.org/packages/66/2a/2c99a5ad2fe0a11600ea123b8e9a08ff138fcb2ad1e13e376f4bd4aa1d96/pyobjc_framework_quartz-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:ca61624a0b0e6286d8a0f97f47eb9011e4e81e9a339db436d48af527e7065bb1", size = 224770, upload-time = "2026-06-19T16:16:13.035Z" }, +] + +[[package]] +name = "pyobjc-framework-security" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/b8/4267b802d8dba6de468e7d0765b05cc4e146fa376ed9f55e0b6461016bef/pyobjc_framework_security-12.2.1.tar.gz", hash = "sha256:d7831b1537f4346892e7f2f0e2b09d79bee98919b0767f4061278d0e03028f2d", size = 181065, upload-time = "2026-06-19T16:21:40.151Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/6d/5de5ba240d815ec0292db63f733b485f59ee7fd683375786c7b940ba7984/pyobjc_framework_security-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b490fb0d46275f165b1214785fec732bc7b8e06c99b6ba045382404d44400d9c", size = 41303, upload-time = "2026-06-19T16:17:15.804Z" }, + { url = "https://files.pythonhosted.org/packages/be/ac/f2ff946edfaf16b4ce5e31afac5e519f83705c0f4842fd25134ecb8f2f4a/pyobjc_framework_security-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce461296b003b2ba17c8b65f6339f9d2fd5dcfa2b3b52ddc0a696334cc8974c5", size = 41306, upload-time = "2026-06-19T16:17:16.816Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5b/2719bc4062e6c27083191fd20e365ae02d0bf1c22f4d1a88211e3d96b369/pyobjc_framework_security-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:76ff6e44e62d3e15651540493879bf16687d862c4f10f3cadade757811c8b8d0", size = 41300, upload-time = "2026-06-19T16:17:17.702Z" }, + { url = "https://files.pythonhosted.org/packages/15/90/dccd4cd6877ef208957dc1f3675287d8614a4dcd2a3ee0a5e56f5fb5a1ba/pyobjc_framework_security-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:990013baba29d6f985d8950b23701129b2597b3d16f628b785fe97596d8a8de3", size = 41299, upload-time = "2026-06-19T16:17:18.511Z" }, + { url = "https://files.pythonhosted.org/packages/ce/af/f9e8040e0c3ef6a50392a46ad1df482a666aa615180d40730b00282ff81f/pyobjc_framework_security-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:066a3e5e9d368e7a6ba8dd52be2077a634ef12a54fbfcc78b3b8154a8f988a1d", size = 42179, upload-time = "2026-06-19T16:17:19.48Z" }, + { url = "https://files.pythonhosted.org/packages/c9/3c/76e2a8bb8d5fe48f0e8e25c6abec1609f3667cc39935017badfe9e9603f2/pyobjc_framework_security-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5319ae49b8874363ab51c6ff4d85d4ea0cfa6d836fe0306e901ba9ae560b880d", size = 41370, upload-time = "2026-06-19T16:17:20.501Z" }, + { url = "https://files.pythonhosted.org/packages/14/6e/7120956e9833b2c70757eec1f65f57c191e00662cf74c4545d88315643fa/pyobjc_framework_security-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:21618431e0dbfbd3d4029445e3118af88e5d7e52ddecf9a2d17c759c51628d85", size = 42926, upload-time = "2026-06-19T16:17:21.425Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ff/0bafc557523e5755f74dd5363386a1e9b03f611e2e36df0737a508cd5ab4/pyobjc_framework_security-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:fa192e9df479375e6242adcadb9a44f32907dd7fe1207608710cd3af65fe3c84", size = 41376, upload-time = "2026-06-19T16:17:22.337Z" }, + { url = "https://files.pythonhosted.org/packages/47/33/33d266117e46fef148caa4f986b3d896cb9bfd76bef48bd761cb60c758ee/pyobjc_framework_security-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:07cd044a7996f9a897040c49055fa3bdf565acac4a25b834a72e60602376146d", size = 42944, upload-time = "2026-06-19T16:17:23.371Z" }, +] + +[[package]] +name = "pyobjc-framework-uniformtypeidentifiers" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/a1/108fa1e5a3dd8aff626f98fb97de370323b290404b04ffa2ef9420665ed3/pyobjc_framework_uniformtypeidentifiers-12.2.1.tar.gz", hash = "sha256:1fb89d13aa3c2df8e6d6536f6df3493fe5a6caefd2a5adebf17c5af3b29ed4a2", size = 20679, upload-time = "2026-06-19T16:21:55.739Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/44/18a7b3c3b4f9f6784fddf64ed5a2c148577d0300705a50e8ab81da8fc71d/pyobjc_framework_uniformtypeidentifiers-12.2.1-py2.py3-none-any.whl", hash = "sha256:ea08413ad895a7dfea13670e26548bcf5b00154084cdfb5d8f96603320e77cf3", size = 5042, upload-time = "2026-06-19T16:18:54.085Z" }, +] + +[[package]] +name = "pyobjc-framework-webkit" +version = "12.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/11/d2/b230c594f70ecb970b4cef67bae2648d1bfa5b381e9b7e3710bf24ec8887/pyobjc_framework_webkit-12.2.1.tar.gz", hash = "sha256:a56acae55b50d549b20dff2921ad1099add8fbc377d0de09ddc2ba50957f7def", size = 332374, upload-time = "2026-06-19T16:22:01.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/34/ec01564ac00165736c76481ea31a0a644c2b9fafac39e7fca827da2c4a34/pyobjc_framework_webkit-12.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ad59030aeb9bb11d28a939760cb64b93108b6a8508c6001cf87789fd7a9312bd", size = 50260, upload-time = "2026-06-19T16:19:29.532Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/2ab99d3975dd4624dd943e5a7c8d37e40258d3c9fcf4f26baf09a24e6c9b/pyobjc_framework_webkit-12.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af5c4ccdf03845adac082823a3b4341b5b2fe62d2d664550afa705b5286a06fc", size = 50264, upload-time = "2026-06-19T16:19:30.607Z" }, + { url = "https://files.pythonhosted.org/packages/84/47/7a2099eb2e062c6230a9440f1795cf34056ca5e16ef25c8aad7c059b8734/pyobjc_framework_webkit-12.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e04dcc08cdc59380113ea1232af75a0a04c2426418ebe967b4c0045c973f776", size = 50372, upload-time = "2026-06-19T16:19:31.581Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a4/202ec288808011d3f459d000d593e88b1118f2d1d5a4dfaaf5232f2c2ac2/pyobjc_framework_webkit-12.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:23bee8bf7077f91da4e3ae54a00c7f5e4414319e15f98be8584dbd67c4043fae", size = 50387, upload-time = "2026-06-19T16:19:32.522Z" }, + { url = "https://files.pythonhosted.org/packages/95/a4/f796e94b43a66704b6ae17c747c7b97fd4b79348f1cfa9bef7b008aaa718/pyobjc_framework_webkit-12.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:00ffb254f97e9ffdd0a82c1faa61a07f6072ba900fa8aba70c83c21198b52e4e", size = 50853, upload-time = "2026-06-19T16:19:33.43Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f6/d24716fef19ccc3d880e99029458803f0174c05df310d991eb97ea3a0799/pyobjc_framework_webkit-12.2.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:67030258c3cd66e8495ccfccef3d2d58010ff0209284c5115e5afdb0e9fd6de1", size = 50499, upload-time = "2026-06-19T16:19:34.45Z" }, + { url = "https://files.pythonhosted.org/packages/a8/6c/817119a52efcc229a30ceff56a0641005a431806a1f555e0571626ba313a/pyobjc_framework_webkit-12.2.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5d91527c9950c79269dd0d70f2bb8668c298dd06930637c1c063ce5f274a87e5", size = 50967, upload-time = "2026-06-19T16:19:35.474Z" }, + { url = "https://files.pythonhosted.org/packages/2d/59/5fac0754d53b2a72aed6f424dfc72e5fa245f83cb57c2e00d02e45390fca/pyobjc_framework_webkit-12.2.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:657825081484c9920c50b76b469b9583f116225b0449c9d95c46cbc8c640adc8", size = 50498, upload-time = "2026-06-19T16:19:36.397Z" }, + { url = "https://files.pythonhosted.org/packages/da/0c/e997e33d99d4ad91da2cf70f0e51ac39b03c58ba210548e9e944bbb421be/pyobjc_framework_webkit-12.2.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:f46adcc6227873f2b14d74b2e789c937f227722274ab59b9fa3c04c6ecb46dd5", size = 50958, upload-time = "2026-06-19T16:19:37.424Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backports-asyncio-runner", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/26/8b004cc36f430345136f6f00fa1aa9ed596c8ed1e8504625fa79522ff39c/python_discovery-1.4.3.tar.gz", hash = "sha256:ad57d7045a862460d4a235986c33f13ed707d3aeb9153fa47eb7dfd0d4673289", size = 70438, upload-time = "2026-07-03T13:21:51.621Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/78/9b77ecb4644d1bbea94d29abf78f21c47eca6eb79e9745b702ec0bed2e19/python_discovery-1.4.3-py3-none-any.whl", hash = "sha256:b6e1e4a7d9e3f6948c39746ffe8218225162d738ba39d05ab1d2f6c1cac4878c", size = 33885, upload-time = "2026-07-03T13:21:50.174Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + +[[package]] +name = "pythonnet" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "clr-loader" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/57/da1992e44663b71365c6e842c8d7fa453d4ec45fb99a68cfee5b7e944d3c/pythonnet-3.1.0.tar.gz", hash = "sha256:7b34c382905d10a371509ffafd64cae0416305c28817738a9cd138336f4e9991", size = 250599, upload-time = "2026-05-23T20:30:21.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/4b/52414f442624d2589f5374a48c08d5ae94f24bea67fc13a20a752884e5b7/pythonnet-3.1.0-cp310.cp311.cp312.cp313.cp314-none-any.whl", hash = "sha256:698dd88edc198819ad63b624a6ebe76208c7b46e4fe13626f65e484f0358d6ba", size = 217578, upload-time = "2026-05-23T20:30:19.527Z" }, + { url = "https://files.pythonhosted.org/packages/db/67/031124fdcb937c266a3265118525bbf6dc13b8c79786d6a7290aecb6e7bb/pythonnet-3.1.0-cp310.cp311.cp312.cp313.cp314-none-win32.win_amd64.whl", hash = "sha256:7bdd4de03df3547a48122a3989265c8b31d5be0d19dadffa009eec7df8085e0b", size = 1644898, upload-time = "2026-05-23T20:30:16.213Z" }, +] + +[[package]] +name = "pytz" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, +] + +[[package]] +name = "pywebview" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "bottle" }, + { name = "proxy-tools" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pythonnet", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "qtpy", marker = "sys_platform == 'openbsd6' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/4a/05307135dafba67778669d194bd1a3822a7685ec9ee8a6d7e70856c1a551/pywebview-6.2.1.tar.gz", hash = "sha256:71b7136752e40824655304d938efb62014218d1a90bd8e87e1cbdb1ce9c466af", size = 513126, upload-time = "2026-04-15T09:02:16.595Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/25/9491695c22c4842c5b3903b4dc172e0eecf67a27c0af34a71512c9b76a0a/pywebview-6.2.1-py3-none-any.whl", hash = "sha256:9d07275f53894ab4d5e2e0e996227193e7187dec276d9b624dccbce029216b46", size = 525463, upload-time = "2026-04-15T09:02:10.186Z" }, +] + +[[package]] +name = "pywin32" +version = "312" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/1b/9cfdeac80ee45bebbbcb31f1b7b99a0d81a1c72de48d837be984e0e88b1d/pywin32-312-cp310-cp310-win32.whl", hash = "sha256:772235332b5d1024c696f11cea1ae4be7930f0a8b894bb43db14e3f435f1ff7e", size = 6361387, upload-time = "2026-06-04T07:49:14.329Z" }, + { url = "https://files.pythonhosted.org/packages/33/b1/7afc96d041d982c27bc2df6f853d43f01fd273e3d39d04be3647ddeb533d/pywin32-312-cp310-cp310-win_amd64.whl", hash = "sha256:5dbc35d2b5320dc07f25fa31269cfb767471002b17de5eb067d03da68c7cb2db", size = 6926780, upload-time = "2026-06-04T07:49:16.881Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/4140da9ad54108e517f4a16b2d83da3033e08662144623e1239587cb7db6/pywin32-312-cp310-cp310-win_arm64.whl", hash = "sha256:3020656e34f1cf7faeb7bccd2b84653a607c6ff0c55ada85e6487d61716deabd", size = 4307203, upload-time = "2026-06-04T07:49:18.993Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f5/10a6e845a00fc5e7afd0a988b744f403d4d57162a28d160a093c4d9322f0/pywin32-312-cp311-cp311-win32.whl", hash = "sha256:17948aeadbdb091f0ced6ef0841620794e68327b94ee415571c1203594b7215c", size = 6362659, upload-time = "2026-06-04T07:49:21.349Z" }, + { url = "https://files.pythonhosted.org/packages/35/c4/dcd2d62b5944b6d5db53413a5899016ccd57ffcb7278f3f81655d25d2027/pywin32-312-cp311-cp311-win_amd64.whl", hash = "sha256:d11417d84412f859b722fad0841b3614459ed0047f7542d8362e77884f6b6e8a", size = 6928825, upload-time = "2026-06-04T07:49:23.934Z" }, + { url = "https://files.pythonhosted.org/packages/b7/56/3cbb433fe4501cdba2eb9040f56a4e1a8243faa4186b25295564d1a7a79d/pywin32-312-cp311-cp311-win_arm64.whl", hash = "sha256:b2200a054ca6d6625c4842fc56a4976a4b47f96b73dbe5538c3f813a80359f47", size = 6721875, upload-time = "2026-06-04T07:49:26.416Z" }, + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "qtpy" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045, upload-time = "2025-02-11T15:09:24.162Z" }, +] + +[[package]] +name = "regex" +version = "2026.6.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/05/e4f219230e11e774a6c9987d2ab0d0c6b8573e13a17e143d0015bee710ef/regex-2026.6.28.tar.gz", hash = "sha256:3cb4b6c5cb3060cc31efdc1fbb27c25fb9b29044afd87e40601a1c4d9db54342", size = 416101, upload-time = "2026-06-28T19:56:55.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/dc/f7a8c9cf0768f704153d358fae2bc883199bc4ea1e4aa458f1be9d0ef2ce/regex-2026.6.28-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b83932645630965fd860fdb70ebbf964bf3e8007f08851ea424d01f8d35454a8", size = 489471, upload-time = "2026-06-28T19:53:06.385Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/9786a4a2133e2f1cc5897ed3d2da3da29ff54b775ffa38bc5935fc24be82/regex-2026.6.28-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e81f1952355042e517dc9861ce65c676e4a098f42402993c40461786d1f794d4", size = 291294, upload-time = "2026-06-28T19:53:09.232Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1f/bfe5b529257f0853aa6b94146e0f6462f4d45aa4f3c05d5a828f415dfd40/regex-2026.6.28-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2097591101d70bcc108af64c46f6066bb698ee067fec5f75beac0be317639311", size = 289216, upload-time = "2026-06-28T19:53:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/25/56/f615165e90ac5f3b72b249240643439520bbac0ac60a9de06868528eba4c/regex-2026.6.28-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31d7538a614b5842bf53ce329d07b43f97754ca7e6db8d69f347e071bce1c953", size = 784787, upload-time = "2026-06-28T19:53:12.393Z" }, + { url = "https://files.pythonhosted.org/packages/04/94/c9e3ad31b3d5fbe1228fee8319e0c02a5460296624f220d08764547fe6ae/regex-2026.6.28-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5561e47bbe2b75373b695326507743fcdd4d2cc7f5022312024ccf39fa094e0", size = 852137, upload-time = "2026-06-28T19:53:14.287Z" }, + { url = "https://files.pythonhosted.org/packages/c0/77/d506a428e446466ee298f5425a774737d0671d070425ed794bb3314d60c6/regex-2026.6.28-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c10f2c5a55ab3dd8318d8ad5f11b530e2691c0edebebde7713066f484902c3fb", size = 899525, upload-time = "2026-06-28T19:53:15.987Z" }, + { url = "https://files.pythonhosted.org/packages/aa/72/becc00d839f19401f10a20168b44711c7b02f7f62bba875b2d8f98417435/regex-2026.6.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23f7e0cc60c72486b42a685f1ff4eec90d50d4fb05e4f9c7d5363b03aa02600d", size = 794116, upload-time = "2026-06-28T19:53:17.372Z" }, + { url = "https://files.pythonhosted.org/packages/fa/11/ea2ca423eeaac2e18077a18b058614e9201f130750df2126d444e39acab2/regex-2026.6.28-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:76493755f79a88d5ed2c9e63a41d3c05997e0a7ffbe76ed8c4ded8be35b8b14c", size = 786257, upload-time = "2026-06-28T19:53:18.712Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9e/f5bf7ecbd14ff2086f015c54dc24fd0d74ba5327fef0de479213f8128615/regex-2026.6.28-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ff0f41a00f23ea5054acb61901380c41813d813eee3f80f800995710bcc52ecd", size = 769914, upload-time = "2026-06-28T19:53:20.564Z" }, + { url = "https://files.pythonhosted.org/packages/43/04/f9040a5360a06241ba5b7f2e6f1c6184e104a84e6f6522535700e94bf8e2/regex-2026.6.28-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c60b297292e7e1ef5d02a4759f9e452ee4c8bb95e168d8fd0b5db01bd806f9f", size = 775013, upload-time = "2026-06-28T19:53:22.067Z" }, + { url = "https://files.pythonhosted.org/packages/73/97/4e46f7abf2f864319d2bcac609af3c0532968c66a3364337778fd232b83c/regex-2026.6.28-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a7cf03c87f7b9cbc25a8894cf9be83818406677b6b391b003ec7c884923387b5", size = 848814, upload-time = "2026-06-28T19:53:24.575Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b8/3d1f995727799a1e2e693e397acb7358094606e5591b6b5fd3128d2d1409/regex-2026.6.28-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:418208ea0af51cfed4f46eb9b1ea7cfc990ca284f0084ecbd951460fb089421e", size = 757702, upload-time = "2026-06-28T19:53:26.215Z" }, + { url = "https://files.pythonhosted.org/packages/20/10/fd5653b8572910a4fe9055f8959b070d7d9443c94ce986529fcdb5fb2a3c/regex-2026.6.28-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:7635fa2cddb917a6bbfac7890602573d2d8c4e470703b0640e6f86a988817ec3", size = 837140, upload-time = "2026-06-28T19:53:27.655Z" }, + { url = "https://files.pythonhosted.org/packages/5d/31/da77e3ef7b594a2aacbd03ce3d0050f33ab3e021df50c6901467c9006511/regex-2026.6.28-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7bb96c13d6cf5880d31bbef84ca701a64d738aa491c2b79975cc33f8ad00a31e", size = 782105, upload-time = "2026-06-28T19:53:29.375Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4d/c379001448d0f58b6946f168d4af96ad60a16c1553259c27b0df8701b640/regex-2026.6.28-cp310-cp310-win32.whl", hash = "sha256:56f05194c4843957dd8b3af87eb0c52d8cf0509e7f18e172d727f5f8ff840646", size = 266728, upload-time = "2026-06-28T19:53:31.813Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/cb656529efa87d74cce0d69e606c745537016da3bdfae78f342af2242ee3/regex-2026.6.28-cp310-cp310-win_amd64.whl", hash = "sha256:70710927033af3b54369f17aaba1343b97a23d0b1aa994fa1512b08b1b8c136a", size = 277901, upload-time = "2026-06-28T19:53:33.293Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ac/d35ccc309c9409406445ab2ef0b56f6a341a916ccff49ff9ac5cc6bb8e9b/regex-2026.6.28-cp310-cp310-win_arm64.whl", hash = "sha256:ed7b30185ee3f8b9b053b0be567b4d226016e2afbebc17fde1c6a4580937b688", size = 276880, upload-time = "2026-06-28T19:53:35.029Z" }, + { url = "https://files.pythonhosted.org/packages/72/db/9051b36294bdbabaa9c7db57db0fbcdfbd17f7a106c539bb423d0323faea/regex-2026.6.28-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a71b51dd08b9b62f055fafab3dee8af8bd2ec81b373a44caef18d6c5ca28f43a", size = 489481, upload-time = "2026-06-28T19:53:36.684Z" }, + { url = "https://files.pythonhosted.org/packages/35/3f/24097a3c3ff30f9a639888900faaecabcf5f54a5bc9c851c297e11b349ef/regex-2026.6.28-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c26a47770d30a0f85c01e261d2a3ebc342c4af6fd666dbd8c1fe4cbf3adf726", size = 291292, upload-time = "2026-06-28T19:53:38.39Z" }, + { url = "https://files.pythonhosted.org/packages/5e/cc/e0d762a189cfb4e8926d16e691720690d139a977b38fdb80230c259332ab/regex-2026.6.28-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e5efbc1af38f97e300d43028e5a92e752d924bcfb7f465d8669d5d5a6e78c233", size = 289232, upload-time = "2026-06-28T19:53:40.181Z" }, + { url = "https://files.pythonhosted.org/packages/4b/c8/ca0ac7f09cc88ca61e0c61c53f7db29334f660ffba5d0b52378e7c44723c/regex-2026.6.28-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1758df6fdd8c800620a5638958720e8a635e1da49a2f09df2dd63e94a24ec4a", size = 792332, upload-time = "2026-06-28T19:53:41.782Z" }, + { url = "https://files.pythonhosted.org/packages/8e/92/04ae94cbe0dd1f478b2aef6c46f995bb6946d3e338d4b28605478b66a2b7/regex-2026.6.28-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ad73ecf20c1ef5c975639f8bf845a9370fcf7dada7edc1e3b0bca20e2f8202f6", size = 861743, upload-time = "2026-06-28T19:53:43.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ec/024d7638c807679ff8a0e6081d01d66c7762339af1cac71e45911587ff9a/regex-2026.6.28-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d80c798b0eec6ea3d45f8816a1e8886c5664615d347d89e8c075b576a1b5a5d", size = 906481, upload-time = "2026-06-28T19:53:44.948Z" }, + { url = "https://files.pythonhosted.org/packages/cd/fd/93bfe5af45f0be4fa8983945455c0e6924e1aeb879cde227958869c1e71c/regex-2026.6.28-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a361feeaf1b6ba1df060f2ff5c5947092edf537a35ce78e76387ac56d3e0f4a4", size = 799867, upload-time = "2026-06-28T19:53:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/ee/fd/e5d965d41f2398c8ce0f37a4652f03bb297fd009bb796d390134225dda12/regex-2026.6.28-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b92366d9c8bba9642989534073662abdd9b41faf7603a7ae71597833f3b88f0", size = 773632, upload-time = "2026-06-28T19:53:48.892Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d9/ff39afaec92b9ee2dba0302a4783976005091681069808938c31cf8df3b6/regex-2026.6.28-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:11251768cc23f097dd61b18f67966e70f74da822784d17e12a444eb6b29d4288", size = 781669, upload-time = "2026-06-28T19:53:50.693Z" }, + { url = "https://files.pythonhosted.org/packages/45/4e/e2fd4bb8228e10c24af2d7ff867182372190e498eab9fd29cbe54c403c95/regex-2026.6.28-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ad5c67786145ec28a71a267d9f9d92bdc8d70d65541eea852c253f520a01f918", size = 854497, upload-time = "2026-06-28T19:53:52.323Z" }, + { url = "https://files.pythonhosted.org/packages/72/7c/f0340384a973082979064156d05f3d2cc1dced7371efcd7a1b45726a1a8a/regex-2026.6.28-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f1da438e739765c3e85175ede05816cbede3caaacb1e0680568bda6119bfdfca", size = 763335, upload-time = "2026-06-28T19:53:54.024Z" }, + { url = "https://files.pythonhosted.org/packages/e1/32/90ce0d0898e205506cc22b9c81cfb16b722e06ca5f50fad51c053c2a727b/regex-2026.6.28-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d98b639046e51c5de64d9f77351532105e99ca271cb6f7640e1f903d6ab63032", size = 844615, upload-time = "2026-06-28T19:53:56.216Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ef/55abb149599dce1ade687170557129524011eeb3d92afe02429cea7754a2/regex-2026.6.28-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e164ace4dbab5c6ad4a4ac7c41a2638fe226d0c770a86f2eb041f594bac6ee7", size = 789193, upload-time = "2026-06-28T19:53:57.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ea/cf7f6f6f152e52fdad978b913bf24c14df647eca0f81ef31f3aee0be8982/regex-2026.6.28-cp311-cp311-win32.whl", hash = "sha256:3169a3159e4d99d9ae85ff0ed90ef3b8906cc3152653b6078b842ace6c8f72c3", size = 266731, upload-time = "2026-06-28T19:53:59.938Z" }, + { url = "https://files.pythonhosted.org/packages/c6/cf/a48d8e8d406b22481cad146f48fa0dfca3c5f402b91f26d8e5a0fe4f513d/regex-2026.6.28-cp311-cp311-win_amd64.whl", hash = "sha256:5977295b0a74e8241df8a4b3b27b12412a831f6fa32ee8b755039592cd768c3d", size = 277918, upload-time = "2026-06-28T19:54:01.502Z" }, + { url = "https://files.pythonhosted.org/packages/89/b2/a222392207db7ed86281a732a99f7cf7f2bb35d332799e892b8510be000e/regex-2026.6.28-cp311-cp311-win_arm64.whl", hash = "sha256:f5fbaef40c3e9282ccee4b075f5600a0d858aa0c34147732f1baa69c8188a95d", size = 276876, upload-time = "2026-06-28T19:54:03.411Z" }, + { url = "https://files.pythonhosted.org/packages/da/21/44aa415873032056c43eac21c67285deb2cf66cddb2a964c3cdc8f803efc/regex-2026.6.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:81cc5793ad33a10444445e8d29d3c73e752c8fb2e120772d70fcb6d41df40fe1", size = 490480, upload-time = "2026-06-28T19:54:05.392Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5f/30d4116093c2128099f78b6990dfc1698fdbf3ee528f1e1c647378034c79/regex-2026.6.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e18225243250a1f7d7e5e5d883f3b96465cd79031acf5c6db902b7025f2125d9", size = 292137, upload-time = "2026-06-28T19:54:07.088Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/ca20a0e0de49837e6337603a91ab77556aa27033ac5b975615d98698cfb3/regex-2026.6.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd1638b1c2db1f2d01c182a4b0d3e2e88b0e99910320a745c1727ee3638ddab", size = 289623, upload-time = "2026-06-28T19:54:08.762Z" }, + { url = "https://files.pythonhosted.org/packages/50/11/c013422a7e2c59946df8ac93e792a4922c98287f2a2181341603c78a5d98/regex-2026.6.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4303ebe16b74eeb3fe2715745023266fea92fd44a23f3e7bb2fb48c7a7bbc195", size = 796756, upload-time = "2026-06-28T19:54:10.616Z" }, + { url = "https://files.pythonhosted.org/packages/b0/95/1309645a0e1ee6fb91d954501da57a0b33d50ad2a9acb313702851a7054e/regex-2026.6.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56b856b70b96c381d837f609eee442a1bd320cd2159f5c294b679552fb1a7eaf", size = 865465, upload-time = "2026-06-28T19:54:12.742Z" }, + { url = "https://files.pythonhosted.org/packages/20/06/491802db47c6f5e2904ffa2518ad3ac27fe6bbf5a66d73210a95cc080d47/regex-2026.6.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f74675ab76ab1d005ffba4dee308e53e89efc22be6e9f9fae5b539a3f81bdff2", size = 912350, upload-time = "2026-06-28T19:54:14.508Z" }, + { url = "https://files.pythonhosted.org/packages/5e/60/3ba57840bcc7e2367090360de0c15a5ba6ad22be89314251105f2e943f43/regex-2026.6.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90581684565a93f7258af1e5d3f41ef20d7d7c61f2a428183a342bcb65485e38", size = 801261, upload-time = "2026-06-28T19:54:16.432Z" }, + { url = "https://files.pythonhosted.org/packages/eb/27/af1eb74e9a78c782b3e450b611a595e44906da8a5107e1227f4a7fd0480b/regex-2026.6.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:28f9e6c28f9b90f6f784595a33240a57e181e61b6ee3dc259b25c61e356d1aa3", size = 777072, upload-time = "2026-06-28T19:54:18.128Z" }, + { url = "https://files.pythonhosted.org/packages/20/18/fdd4c883a39e3ed00d669062af1135809bfd3281bf528150849fbd68825b/regex-2026.6.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:378a71d861fc7c8806b04ac5b133d53c0e774f92f5d9663a539872d3fa2b0417", size = 785119, upload-time = "2026-06-28T19:54:20.314Z" }, + { url = "https://files.pythonhosted.org/packages/1c/79/0aabe34b8482dcadf64355f70f96e22eba5ec6c1efb33563f89654f4061c/regex-2026.6.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4cc199874ecd6267a49b111052250825bfe19b5101b23b2ba80f54efa3e0994e", size = 860118, upload-time = "2026-06-28T19:54:22.368Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2c/c973323306a27c9db7d160e9584eb7e0ece2a96224ccb0d39060558b31f9/regex-2026.6.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b916a10431494ef4b4d62c6c89cab6426af7873125b8cd6c15811bf5fc58eec8", size = 765786, upload-time = "2026-06-28T19:54:24.265Z" }, + { url = "https://files.pythonhosted.org/packages/e3/df/9ca3e378e352242a4cb45573a5e9162c3ee791507702a23966fa559e36b5/regex-2026.6.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e27727fba075f1e4409416d2f537d4c30fc11f012ea507f7bd74d3e19ecb57a", size = 852120, upload-time = "2026-06-28T19:54:25.972Z" }, + { url = "https://files.pythonhosted.org/packages/a2/3e/3e31e255c4971f53cbce6306b5e3c76cbd3735a54f419bb3b2f194e9f68c/regex-2026.6.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:700fc6a7844bb2c4149292ac79d1df8841a00acd4d45cd32c1ebc7bcc1fd0da8", size = 789503, upload-time = "2026-06-28T19:54:27.678Z" }, + { url = "https://files.pythonhosted.org/packages/72/01/d36561c21c3033d7eeb31d51b491916817de7861acefccc5fc9db8a5037c/regex-2026.6.28-cp312-cp312-win32.whl", hash = "sha256:03376d60b6a11aecb88a79fa2be06b40faa01c6693bc31ef69435cd4818b9463", size = 267109, upload-time = "2026-06-28T19:54:29.316Z" }, + { url = "https://files.pythonhosted.org/packages/a0/59/bbbb0591f38b18c65977cd65ce64749eba1c1996c99ac04e900fc30c0dcb/regex-2026.6.28-cp312-cp312-win_amd64.whl", hash = "sha256:fbd2ded482bf99e6651992bbfcde460272724d4bbc49ef3d6b46d9312867ec84", size = 277711, upload-time = "2026-06-28T19:54:31.143Z" }, + { url = "https://files.pythonhosted.org/packages/86/06/be4f6b337d773ae5739a1bc238f97c16926e72017243735853c030f4c628/regex-2026.6.28-cp312-cp312-win_arm64.whl", hash = "sha256:37294d3d7ddb64c7e89184b2894e0f8f0a19c514bc59513d71fe692c3a8d5fc6", size = 277022, upload-time = "2026-06-28T19:54:32.97Z" }, + { url = "https://files.pythonhosted.org/packages/b6/53/d5c1b3cc0b5a0c985563ad6fac93d73ff2b300cb84342d89f044625d6bc7/regex-2026.6.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b295a83426e0e44e9e60fde99789e181bd26788a1890ae7fe2a24c69bb6246ca", size = 490329, upload-time = "2026-06-28T19:54:35.775Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9f/0c3503e819e91ca0e7a901a8e989ebf840ac7c7aea20b1fc7f31b6759f77/regex-2026.6.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c31665c0deb5c111557a1cac8c27bd5629e2f9e7fd5058900a03576c33b601c", size = 292039, upload-time = "2026-06-28T19:54:37.977Z" }, + { url = "https://files.pythonhosted.org/packages/bb/7f/cd004e13fcad23b3794a82307dfd222e6365eb7f598bd3caab148a830bff/regex-2026.6.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6bf295f2c59de77d1ea7de053607ae4dc9ceb3d57bbb6c7ec51ef4acc4ccff94", size = 289488, upload-time = "2026-06-28T19:54:39.545Z" }, + { url = "https://files.pythonhosted.org/packages/73/4c/293fb34586fbcdc47eac436069e9c11f71fae5dadfd4889b475d7d2e5f7a/regex-2026.6.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17c077586770f67e05bbffeba07fbee6b2b22244f4d4caf8d94e59d574befe04", size = 796772, upload-time = "2026-06-28T19:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/92/fa/c0cd1a90b7d12d9dc155cfc8bdea8df9720988ea5b07e8fa1eccbd0ab2dd/regex-2026.6.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e6cb5a61486f9062397d2e189573b39d38ecfaed698fd9fb6e2756a8ebb8762", size = 865467, upload-time = "2026-06-28T19:54:43.485Z" }, + { url = "https://files.pythonhosted.org/packages/4e/db/0b479973046d005a1eaea299d5d536aeecb9488a16d9cbb8286338102e2d/regex-2026.6.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e86e91a2664f44c3a4e363a7d78fb17c27d5046882e30ea5a877f5e89b28d2ba", size = 912345, upload-time = "2026-06-28T19:54:46.091Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5b/d65adfbd02f32212431bca1f06d1e2eb763a20b12978b454bafaf23dacb7/regex-2026.6.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dfd1331c49233998d84fc5f1f4436cf7a435a7655f6cf0f490229bb5c7254e5", size = 801291, upload-time = "2026-06-28T19:54:48.3Z" }, + { url = "https://files.pythonhosted.org/packages/fc/09/2103686defaf9a0a31c1663782359d5b45f42524c64cca681f5481e44a5e/regex-2026.6.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cadea12805a1bce0b091c302b814207be26fb60a9c0e7f9ad2f9e21790a429fe", size = 777106, upload-time = "2026-06-28T19:54:50.326Z" }, + { url = "https://files.pythonhosted.org/packages/85/5a/b57593c0aa23ed269ec332fbcf07852abcb6b746e811d9464e0d09b4e25f/regex-2026.6.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f2c1682b67ad5d2376498f2a5a2a8f782fa2e4a06d0465b5e357799806e8a20", size = 785175, upload-time = "2026-06-28T19:54:52.172Z" }, + { url = "https://files.pythonhosted.org/packages/79/59/c36e756ad29bf14d7b6c6d7138952476b21f6160286cedb98ac13481c993/regex-2026.6.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:64e142eb55e84868087da1375d7c36ff97d55010951849f515322a91d5fef1b4", size = 860186, upload-time = "2026-06-28T19:54:54.11Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/49808aea0da9649c300139360708fb91b7144be1f962fcebf96755fde948/regex-2026.6.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:abb4daabe7be63273787a62dfd6164dadf8f7a63fbec3d2730e5e5e7126d858c", size = 765754, upload-time = "2026-06-28T19:54:56.04Z" }, + { url = "https://files.pythonhosted.org/packages/be/c5/52bbd436cf2200decdf48825fa38363eaaeebb77011ea9928a1ef9e0b9f2/regex-2026.6.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec2b2ad00ab8c16a2798cc8db80c53c4d5b8b3a2441f6cbaef06625f5ca25854", size = 852085, upload-time = "2026-06-28T19:54:57.988Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c3/0390b66e3019497143fe768b3ba567b64d8b24f3812d09506deb86f4a0f0/regex-2026.6.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bfc9677982c914d9085b8e1c3b3ae6e88f139fb56531c2416d6c8f338093c22b", size = 789600, upload-time = "2026-06-28T19:54:59.977Z" }, + { url = "https://files.pythonhosted.org/packages/88/fd/ab5b03653a244975069fed93d73f4f5f7484c03a84cedb238292510d7182/regex-2026.6.28-cp313-cp313-win32.whl", hash = "sha256:bf54bc693fc4e0530e666ba5ec4bcba14dbe8f66b7cfc15c27317d1a6e40b9a5", size = 267088, upload-time = "2026-06-28T19:55:02.159Z" }, + { url = "https://files.pythonhosted.org/packages/68/55/21022f7d3143210ae8d4ff905c45306237b657375cc0b97883f49db3d423/regex-2026.6.28-cp313-cp313-win_amd64.whl", hash = "sha256:e128feaf65bf3d9eb91bec92322a8f7e4835e9c798f3e9ea4b69f4def85620e3", size = 277680, upload-time = "2026-06-28T19:55:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/b6/99/7f664804f1aef924542b0b233996b78b3e4d0a52d9951358aac99f129f51/regex-2026.6.28-cp313-cp313-win_arm64.whl", hash = "sha256:695873e0ea8d3815ea9e92e2c68faf039cc450e2c0a62a31afe2049eb11be767", size = 277017, upload-time = "2026-06-28T19:55:06.29Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e1/9eb83518e159d719fd681c4932dc2aaff855ce72451e1d05d69466f25a96/regex-2026.6.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:189dbf9fc4252d9f1352bf4bd1bef885edb6cc4b7341df202a65f821aaa3891c", size = 494195, upload-time = "2026-06-28T19:55:08.292Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e2/e259c5f2f7be269d0e2fb54275c1fa6a13fb47019f389c3f3ae457447825/regex-2026.6.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9277a4c6503390aa39cb4483b87ec0384faee0850a23b5cea33d008b5d8d83f1", size = 293976, upload-time = "2026-06-28T19:55:10.014Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/9bdf444014d22b045d0c82ca114fac7e07a597b5b5331b7c4ce6328426e2/regex-2026.6.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:17eddca4e8ea9af0b5739314776cdf0172a49731ab61f2e1ea66e066ddd46c97", size = 292340, upload-time = "2026-06-28T19:55:11.88Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3a/f49b11e59cbfe187ace0053a460bd72a0169b8cd52e7db9421a074ce7a43/regex-2026.6.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4466b8641e00c697aab5a73150150d2b2ea96b131c595691f42031abafd9f4d", size = 811704, upload-time = "2026-06-28T19:55:13.612Z" }, + { url = "https://files.pythonhosted.org/packages/2f/fb/ad04c39e149bf8b6cf357df5fff78341733ec366780a00c803a36735818c/regex-2026.6.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9cfcd4b0bdcf768c498415c170d1ed2a25a99bf0b65fa253bbd02f68ceba6475", size = 871157, upload-time = "2026-06-28T19:55:15.797Z" }, + { url = "https://files.pythonhosted.org/packages/7f/64/0e5ba31c11eb8ef7aac19a690c1211fc9aa9990caf09565785ebb0081b9a/regex-2026.6.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:80c7adf1ef647f6b1e8aa2ca280e517174cd08bdf7a2e412cdfb68bd6a0917cb", size = 917287, upload-time = "2026-06-28T19:55:18.692Z" }, + { url = "https://files.pythonhosted.org/packages/11/75/6b78df2b858c2fcbbc4858fdc3f2975cf2703be374b2842db7d2c32591a7/regex-2026.6.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a043f5770e82283a22aed4cefef1a4e0f9dd8fd7184cb6ce0ad2e579e2134a9e", size = 816333, upload-time = "2026-06-28T19:55:20.973Z" }, + { url = "https://files.pythonhosted.org/packages/b4/01/ecfe665a3694d5eda9f3ec686c856438ada0943947b6005e90556a1e2cdf/regex-2026.6.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3bd630a8dba06b55254ea5ee862194edab52ec783100d2ef1cd15a9c512fee27", size = 785518, upload-time = "2026-06-28T19:55:23.003Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0a/88f9cd88ff1e82881605c4ffd62d77ee67d051232cfe6f8e9a64b86cf0e8/regex-2026.6.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b77207e3cee13086f1906a6a2a12b41244c577e8ad9370d4b35ae1d548d354f3", size = 801371, upload-time = "2026-06-28T19:55:24.888Z" }, + { url = "https://files.pythonhosted.org/packages/a8/97/601483732f93275482ceb9fed57813dfed7c47d3a019db6ec4a3bb6e23e0/regex-2026.6.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:6de82c268e5d101ee9e3ffd869924aa9a371e3a21e752cf4fa17b6ce50d219f7", size = 866517, upload-time = "2026-06-28T19:55:27.232Z" }, + { url = "https://files.pythonhosted.org/packages/81/ed/385c2a0351b994a693453c1d1a6e9af9eb35db3c9460d76b5078acd70c62/regex-2026.6.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b15859e3908544fb99cf47341dcf0bfd089147d258c4c4d8a29e5b087f8085cb", size = 772834, upload-time = "2026-06-28T19:55:29.154Z" }, + { url = "https://files.pythonhosted.org/packages/06/bc/bbf4a5b3b29770d7f307d3c28b5b1bca0105b0cb424be0a4eb1339bc92cf/regex-2026.6.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c91487a917edd48a1ea646fdf60d7936d304f0e686fa7ea8326e47efca51d816", size = 856606, upload-time = "2026-06-28T19:55:32.186Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/51d74fff82f682819979249f8d700267108ba5dc4eb284b0e11b9c85e4b3/regex-2026.6.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4ac65f3e3a99fd8f3a4a74e7a6610acd1ce9dfe9b8a03d346a4922380d68aeb", size = 803475, upload-time = "2026-06-28T19:55:34.328Z" }, + { url = "https://files.pythonhosted.org/packages/7c/3e/6be10cefdc813533fe604dbf5d3c77d2638e7ee658b2749ebadc113b6b2e/regex-2026.6.28-cp313-cp313t-win32.whl", hash = "sha256:3f6316f258bc7e6c9c2acbe9954947bbd397a81be3742a637a555f1855d6618d", size = 269126, upload-time = "2026-06-28T19:55:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/3c/3c/32cda905ea1a6eeeb798291c294d8ec66ee0efe0cdba28b061e248b1d396/regex-2026.6.28-cp313-cp313t-win_amd64.whl", hash = "sha256:1484bdd6fba28422df9b5ebb04055b2e1b680e8e4f08490bb21ff0f3cc50d0ab", size = 279961, upload-time = "2026-06-28T19:55:38.456Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b9/69f4e5cd6fbe0bb420cb2dbae441ca118f2495bdda522a74da75aa9829e7/regex-2026.6.28-cp313-cp313t-win_arm64.whl", hash = "sha256:3f15020f0b69cafe57baa067ff65b29acef68ff6b1670a53bef1ca11d708e02d", size = 279266, upload-time = "2026-06-28T19:55:40.62Z" }, + { url = "https://files.pythonhosted.org/packages/3b/fb/fad3b810a5bb1e09b9e5d6913fc6ba88cab738fdf283196827a3c59a4c10/regex-2026.6.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:f7c032b0c8a73739ff8ff1aaf30c281fa19c17bf7f1543256c8507390db7807c", size = 490407, upload-time = "2026-06-28T19:55:42.724Z" }, + { url = "https://files.pythonhosted.org/packages/d6/52/b8c79d12276d93e90e707e939b396034c04980caf1235312ef790f8e11fc/regex-2026.6.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f6710f512c57b84f127a23d0f59560a03b64136eff419ae1be5ab557577fe5e3", size = 291988, upload-time = "2026-06-28T19:55:44.549Z" }, + { url = "https://files.pythonhosted.org/packages/23/d2/6a911f18279daa8d7bb8b20d771ddb6ef31fabd35f5921f9d3ba21640e80/regex-2026.6.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c0013958f427bd82509a186b9ff206d66cb8d60a81fc797a4c717afd18c5b0ba", size = 289704, upload-time = "2026-06-28T19:55:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/fd/22/ad1955c47c669291a05804d53d7071cc0732dfdf166857be38003cedc2d1/regex-2026.6.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f06cdcd6421f8e194ad312ea608020381250df9b8a57661c1b57e9e5273878", size = 797017, upload-time = "2026-06-28T19:55:48.166Z" }, + { url = "https://files.pythonhosted.org/packages/e5/67/a83159ff8703ab4d0c2cf99e76ebf289b7b4a501623241d09f88f3614f80/regex-2026.6.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec9689392f7494ff4e3f8e7e8522f9158f11023f337eaaf04a64542fc45bbf26", size = 866112, upload-time = "2026-06-28T19:55:51.047Z" }, + { url = "https://files.pythonhosted.org/packages/b9/09/7bff2d6dbbd77421b3274aa51db1c887381cbc5b6eda93598c3e882ea345/regex-2026.6.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aa084684e6d2078bf6139e374d1fc2af5ddc1ac7122759a2db716d68169f6fd0", size = 911554, upload-time = "2026-06-28T19:55:53.707Z" }, + { url = "https://files.pythonhosted.org/packages/29/44/ae59c3826e7ba492e56795cdf74ea2a7b5b7c5ea116afb79ee4956a5dff1/regex-2026.6.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:40455e6840dc4e96a6fe50f4cedc957de2752c954d91e789812be55d49be199a", size = 800665, upload-time = "2026-06-28T19:55:55.875Z" }, + { url = "https://files.pythonhosted.org/packages/d6/19/6fd033d2ab00f35d445aaeaf3307c1e721424dcbfd48f6f65c857cb939cf/regex-2026.6.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:530b5c223b9ca5dd8370ac502e080aee0e4ded32be987c6564b425fb5523d581", size = 777243, upload-time = "2026-06-28T19:55:57.909Z" }, + { url = "https://files.pythonhosted.org/packages/4e/9d/99730f26df4938049ab1e652ca75e967b4c6739444e18d9707bfdb8af20c/regex-2026.6.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e0ed273ecd1a89be84466c1749bfe58609cc2a32b5d5e05006c4625ba96411b", size = 785784, upload-time = "2026-06-28T19:56:00.072Z" }, + { url = "https://files.pythonhosted.org/packages/48/49/105cd57162f5fc5c04cc917a1388a060cf8427e5c14353cd9044660fbf4d/regex-2026.6.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0ab0d5344311fc8e8667078942056c3b9c9b4a4b1cc99f2eb8a5af54554f4acc", size = 860914, upload-time = "2026-06-28T19:56:02.017Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a5/788245a95b69018f58bff2f4fd27d007cacaea088cdb390979743f1b2571/regex-2026.6.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:eacb79625323d9f7e7925366b917f492b8356fad58f5dc4fa12ff8c21d8f4ca9", size = 765915, upload-time = "2026-06-28T19:56:05.021Z" }, + { url = "https://files.pythonhosted.org/packages/ca/01/292065a39a004b05e67a337b18213670a7cb919d6856ac2d7df7f1a10dbb/regex-2026.6.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:20f4d87702702aa1d572721e146f301660c50eef6fd6cb596e48a22b0ace17db", size = 851404, upload-time = "2026-06-28T19:56:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/98/9e/a93d865db0e13483ae1a01d81e2ce16d4a7fe2f9b9fe4aac4cc08590b136/regex-2026.6.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e693940a3b9e6d6e4dc2a54ecaa74b74934f77af1ef95f518a74261ef7cc1bc", size = 789373, upload-time = "2026-06-28T19:56:09.894Z" }, + { url = "https://files.pythonhosted.org/packages/82/0c/38b1685ad4017d78efbc8fa7dbbf96d8113b53750c8aa2d3609defd46605/regex-2026.6.28-cp314-cp314-win32.whl", hash = "sha256:234a51e20ebc18ab83b2c0600cf28f2e884560a0e00f743878f0b7d8e7c4cf03", size = 272496, upload-time = "2026-06-28T19:56:11.83Z" }, + { url = "https://files.pythonhosted.org/packages/55/50/e19f261ff9ba9b50722a529e09b1743ecf65eb348be99d0fd2cd7fcede1c/regex-2026.6.28-cp314-cp314-win_amd64.whl", hash = "sha256:7b15c437bc4604f03ceb3f8d37eae2f8930e320e1bc556b259848c639d9eec1a", size = 280754, upload-time = "2026-06-28T19:56:13.758Z" }, + { url = "https://files.pythonhosted.org/packages/36/b8/c9e68f3a9e33be73f20990b2c065b144ff2d0aa242608a950d8c4f3b56e8/regex-2026.6.28-cp314-cp314-win_arm64.whl", hash = "sha256:c6e6f790d01380a74ad564f216c533b86504afb61bf66f2b2e11e7f1a3e287a7", size = 280979, upload-time = "2026-06-28T19:56:15.928Z" }, + { url = "https://files.pythonhosted.org/packages/03/e6/21c425a37880c650d007c4171c6a80325446d830d85f5fbf335e7205b1e7/regex-2026.6.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3527a72adcbe9e3600f1553b497d397c1a371d227580d41d96c3c5964109b65c", size = 494282, upload-time = "2026-06-28T19:56:18.049Z" }, + { url = "https://files.pythonhosted.org/packages/07/50/6647a7ccf5ffff995ba955a0b7d766440f4e58ce1666549c8ee998f2b972/regex-2026.6.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a644f6408692812f5ead82519eed680e08d5d546fddbd9f7d9514e3c73899aa5", size = 293977, upload-time = "2026-06-28T19:56:20.145Z" }, + { url = "https://files.pythonhosted.org/packages/8c/dc/a3e141a4eaf125e50f63105570c01fa477c06ac5259dcfa95e9b90760e84/regex-2026.6.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8e2fae6bb883648346f84db270dc9aafc29d8e895f62b88a75ccc83b09519820", size = 292432, upload-time = "2026-06-28T19:56:22.345Z" }, + { url = "https://files.pythonhosted.org/packages/35/ee/2ac1a6b9f167f8ff69f5a789938cc103b60cff41b24a6990daced8b88e34/regex-2026.6.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debe623e09cee97ef9404575e936c610aac9bb08358c5099aaef14644a6871f2", size = 811877, upload-time = "2026-06-28T19:56:25.056Z" }, + { url = "https://files.pythonhosted.org/packages/df/7b/9a5505ee92180bcae300b1018b9ff3d3c19962436e66f2505f255e9fde35/regex-2026.6.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc579c91fb4605773483a8d940b136bcc5b854fff44fa14a1572a038f46563f1", size = 871212, upload-time = "2026-06-28T19:56:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/24/4d/d61a702a9f9d1bd29b22cbef1aed6d477baa961232a7eb4d91b7775b0b3e/regex-2026.6.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e7c42be203d84ecf7d487ff23f8a61ef0eb0534fa0fc317a2fce8c065d20618f", size = 917507, upload-time = "2026-06-28T19:56:29.762Z" }, + { url = "https://files.pythonhosted.org/packages/d4/60/1308066f5966b65fbb6905b99ba37e9f1cd753dd0ac08485f8257334ee92/regex-2026.6.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8184b4e2fdaf9cdfe77e38f15a4d9dc149168c9c29eb0ea17c5481d3bb80546", size = 816389, upload-time = "2026-06-28T19:56:32.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/5c/57ce2cb8d714ee0b7f11c7ee4cfe2af66df2b90f147feadcb538609a3a02/regex-2026.6.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:697f103104f5872d64078d8eeac59979960be8ee76115a2d3f31096312e2a400", size = 785890, upload-time = "2026-06-28T19:56:34.492Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fd/1d5350d3a8a327bff0fccacb911732baf7b5b6f5529c0e3fa602a23e7dad/regex-2026.6.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:714d2b1aa29beef0ddfcdc72ad0771c05326551a8bb0680b0ddf74bfaad87387", size = 801451, upload-time = "2026-06-28T19:56:36.749Z" }, + { url = "https://files.pythonhosted.org/packages/f3/79/3c9e4f8a0306e030ad5a43bbbc01625fb28d58a813bc52d42fd1cc63fb2e/regex-2026.6.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0f09f62e450cc2f113018cc8412aeea3a120a04e1ca7e801a0d441583f9a3b06", size = 866504, upload-time = "2026-06-28T19:56:38.994Z" }, + { url = "https://files.pythonhosted.org/packages/65/12/f747de475b54f4709efb24dd0fbc8467c64cec91f5db0d047b079646ee78/regex-2026.6.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:731ea12d5aeb2577eaef2393d6428b995f76eb35f68a89e03e15a97719d1de19", size = 773047, upload-time = "2026-06-28T19:56:41.061Z" }, + { url = "https://files.pythonhosted.org/packages/58/3c/f02f860e0500c1b2d61a79dec7e214b37fb9656281dcddc92397edf96678/regex-2026.6.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:51e952c8783eabd4706d0f63922f219bcfc1bef9b8cb35941c0d1a0396578858", size = 856665, upload-time = "2026-06-28T19:56:43.466Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6c/28b3fa222513484be9dee26b7222bda109056c43ea28aa2314262ca48816/regex-2026.6.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43248fe4c0ab8fbb223588a0795b11268940072c97bba30ea8f9b49d8cdfde34", size = 803573, upload-time = "2026-06-28T19:56:45.791Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/8f86cf1a1fd85c5ab0c503c9fe4607ad4ad48978b2d8b435d94465e134c7/regex-2026.6.28-cp314-cp314t-win32.whl", hash = "sha256:fc1eddc25ad23c0f1344ab280d961ac595ead48292d7c779497975942373f493", size = 274515, upload-time = "2026-06-28T19:56:47.948Z" }, + { url = "https://files.pythonhosted.org/packages/0f/de/f8613c03b36786ddef2c930d28f9bcae861fcd541cc9203a870956cf1e83/regex-2026.6.28-cp314-cp314t-win_amd64.whl", hash = "sha256:ede8d8e53b6dde0a50f7eca902f0af76d87ab02a55aba7542da68ae3e5dfe83d", size = 283650, upload-time = "2026-06-28T19:56:50.614Z" }, + { url = "https://files.pythonhosted.org/packages/4d/f3/f5ec86839bbabe33b6dee649b62ff9a445d43de6b0ad780cf6b83c56f61e/regex-2026.6.28-cp314-cp314t-win_arm64.whl", hash = "sha256:4da6f6a72f8700b97a1a765e837fb7d5750bfd9f13acea7bae498f573e3a70a8", size = 283338, upload-time = "2026-06-28T19:56:52.879Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rfc3986" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "rich-toolkit" +version = "0.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/63/3e427c62f1992945c997d4ec31e2fcb37d26aadbe5aa44ae5b29f7f64d26/rich_toolkit-0.20.1.tar.gz", hash = "sha256:c7336ae281f435c785acecaedc4b71d4b663dc73d9c8079fea96372527e822a4", size = 203473, upload-time = "2026-06-05T08:56:57.679Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/88/309f07d08155da2ba1d5ceb42d270fb42fbe34a807684543e3ffc10fe713/rich_toolkit-0.20.1-py3-none-any.whl", hash = "sha256:2a6d5f8e15759b9eba5a9ee63da10b275359ead20e5a0fc92bd5b4dbae8ce4bf", size = 35525, upload-time = "2026-06-05T08:56:58.586Z" }, +] + +[[package]] +name = "rignore" +version = "0.7.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/f5/8bed2310abe4ae04b67a38374a4d311dd85220f5d8da56f47ae9361be0b0/rignore-0.7.6.tar.gz", hash = "sha256:00d3546cd793c30cb17921ce674d2c8f3a4b00501cb0e3dd0e82217dbeba2671", size = 57140, upload-time = "2025-11-05T21:41:21.968Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/7a/b970cd0138b0ece72eb28f086e933f9ed75b795716ad3de5ab22994b3b54/rignore-0.7.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f3c74a7e5ee77aea669c95fdb3933f2a6c7549893700082e759128a29cf67e45", size = 884999, upload-time = "2025-11-05T20:42:38.373Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/23faca29616d8966ada63fb0e13c214107811fa9a0aba2275e4c7ca63bd5/rignore-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7202404958f5fe3474bac91f65350f0b1dde1a5e05089f2946549b7e91e79ec", size = 824824, upload-time = "2025-11-05T20:42:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/05a1e61f04cf2548524224f0b5f21ca19ea58f7273a863bac10846b8ff69/rignore-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bde7c5835fa3905bfb7e329a4f1d7eccb676de63da7a3f934ddd5c06df20597", size = 899121, upload-time = "2025-11-05T20:40:48.94Z" }, + { url = "https://files.pythonhosted.org/packages/ff/35/71518847e10bdbf359badad8800e4681757a01f4777b3c5e03dbde8a42d8/rignore-0.7.6-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:626c3d4ba03af266694d25101bc1d8d16eda49c5feb86cedfec31c614fceca7d", size = 873813, upload-time = "2025-11-05T20:41:04.71Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c8/32ae405d3e7fd4d9f9b7838f2fcca0a5005bb87fa514b83f83fd81c0df22/rignore-0.7.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a43841e651e7a05a4274b9026cc408d1912e64016ede8cd4c145dae5d0635be", size = 1168019, upload-time = "2025-11-05T20:41:20.723Z" }, + { url = "https://files.pythonhosted.org/packages/25/98/013c955982bc5b4719bf9a5bea58be317eea28aa12bfd004025e3cd7c000/rignore-0.7.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7978c498dbf7f74d30cdb8859fe612167d8247f0acd377ae85180e34490725da", size = 942822, upload-time = "2025-11-05T20:41:36.99Z" }, + { url = "https://files.pythonhosted.org/packages/90/fb/9a3f3156c6ed30bcd597e63690353edac1fcffe9d382ad517722b56ac195/rignore-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d22f72ab695c07d2d96d2a645208daff17084441b5d58c07378c9dd6f9c4c87", size = 959820, upload-time = "2025-11-05T20:42:06.364Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b2/93bf609633021e9658acaff24cfb055d8cdaf7f5855d10ebb35307900dda/rignore-0.7.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d5bd8e1a91ed1a789b2cbe39eeea9204a6719d4f2cf443a9544b521a285a295f", size = 985050, upload-time = "2025-11-05T20:41:51.124Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/ec2d040469bdfd7b743df10f2201c5d285009a4263d506edbf7a06a090bb/rignore-0.7.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fc03efad5789365018e94ac4079f851a999bc154d1551c45179f7fcf45322", size = 1079164, upload-time = "2025-11-05T21:40:10.368Z" }, + { url = "https://files.pythonhosted.org/packages/df/26/4b635f4ea5baf4baa8ba8eee06163f6af6e76dfbe72deb57da34bb24b19d/rignore-0.7.6-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ce2617fe28c51367fd8abfd4eeea9e61664af63c17d4ea00353d8ef56dfb95fa", size = 1139028, upload-time = "2025-11-05T21:40:27.977Z" }, + { url = "https://files.pythonhosted.org/packages/6a/54/a3147ebd1e477b06eb24e2c2c56d951ae5faa9045b7b36d7892fec5080d9/rignore-0.7.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7c4ad2cee85068408e7819a38243043214e2c3047e9bd4c506f8de01c302709e", size = 1119024, upload-time = "2025-11-05T21:40:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f4/27475db769a57cff18fe7e7267b36e6cdb5b1281caa185ba544171106cba/rignore-0.7.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:02cd240bfd59ecc3907766f4839cbba20530a2e470abca09eaa82225e4d946fb", size = 1128531, upload-time = "2025-11-05T21:41:02.734Z" }, + { url = "https://files.pythonhosted.org/packages/97/32/6e782d3b352e4349fa0e90bf75b13cb7f11d8908b36d9e2b262224b65d9a/rignore-0.7.6-cp310-cp310-win32.whl", hash = "sha256:fe2bd8fa1ff555259df54c376abc73855cb02628a474a40d51b358c3a1ddc55b", size = 646817, upload-time = "2025-11-05T21:41:47.51Z" }, + { url = "https://files.pythonhosted.org/packages/c0/8a/53185c69abb3bb362e8a46b8089999f820bf15655629ff8395107633c8ab/rignore-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:d80afd6071c78baf3765ec698841071b19e41c326f994cfa69b5a1df676f5d39", size = 727001, upload-time = "2025-11-05T21:41:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/25/41/b6e2be3069ef3b7f24e35d2911bd6deb83d20ed5642ad81d5a6d1c015473/rignore-0.7.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:40be8226e12d6653abbebaffaea2885f80374c1c8f76fe5ca9e0cadd120a272c", size = 885285, upload-time = "2025-11-05T20:42:39.763Z" }, + { url = "https://files.pythonhosted.org/packages/52/66/ba7f561b6062402022887706a7f2b2c2e2e2a28f1e3839202b0a2f77e36d/rignore-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:182f4e5e4064d947c756819446a7d4cdede8e756b8c81cf9e509683fe38778d7", size = 823882, upload-time = "2025-11-05T20:42:23.488Z" }, + { url = "https://files.pythonhosted.org/packages/f5/81/4087453df35a90b07370647b19017029324950c1b9137d54bf1f33843f17/rignore-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16b63047648a916a87be1e51bb5c009063f1b8b6f5afe4f04f875525507e63dc", size = 899362, upload-time = "2025-11-05T20:40:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c9/390a8fdfabb76d71416be773bd9f162977bd483084f68daf19da1dec88a6/rignore-0.7.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ba5524f5178deca4d7695e936604ebc742acb8958f9395776e1fcb8133f8257a", size = 873633, upload-time = "2025-11-05T20:41:06.193Z" }, + { url = "https://files.pythonhosted.org/packages/df/c9/79404fcb0faa76edfbc9df0901f8ef18568d1104919ebbbad6d608c888d1/rignore-0.7.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62020dbb89a1dd4b84ab3d60547b3b2eb2723641d5fb198463643f71eaaed57d", size = 1167633, upload-time = "2025-11-05T20:41:22.491Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/b3466d32d445d158a0aceb80919085baaae495b1f540fb942f91d93b5e5b/rignore-0.7.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b34acd532769d5a6f153a52a98dcb81615c949ab11697ce26b2eb776af2e174d", size = 941434, upload-time = "2025-11-05T20:41:38.151Z" }, + { url = "https://files.pythonhosted.org/packages/e8/40/9cd949761a7af5bc27022a939c91ff622d29c7a0b66d0c13a863097dde2d/rignore-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c5e53b752f9de44dff7b3be3c98455ce3bf88e69d6dc0cf4f213346c5e3416c", size = 959461, upload-time = "2025-11-05T20:42:08.476Z" }, + { url = "https://files.pythonhosted.org/packages/b5/87/1e1a145731f73bdb7835e11f80da06f79a00d68b370d9a847de979575e6d/rignore-0.7.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25b3536d13a5d6409ce85f23936f044576eeebf7b6db1d078051b288410fc049", size = 985323, upload-time = "2025-11-05T20:41:52.735Z" }, + { url = "https://files.pythonhosted.org/packages/6c/31/1ecff992fc3f59c4fcdcb6c07d5f6c1e6dfb55ccda19c083aca9d86fa1c6/rignore-0.7.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e01cad2b0b92f6b1993f29fc01f23f2d78caf4bf93b11096d28e9d578eb08ce", size = 1079173, upload-time = "2025-11-05T21:40:12.007Z" }, + { url = "https://files.pythonhosted.org/packages/17/18/162eedadb4c2282fa4c521700dbf93c9b14b8842e8354f7d72b445b8d593/rignore-0.7.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5991e46ab9b4868334c9e372ab0892b0150f3f586ff2b1e314272caeb38aaedb", size = 1139012, upload-time = "2025-11-05T21:40:29.399Z" }, + { url = "https://files.pythonhosted.org/packages/78/96/a9ca398a8af74bb143ad66c2a31303c894111977e28b0d0eab03867f1b43/rignore-0.7.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6c8ae562e5d1246cba5eaeb92a47b2a279e7637102828dde41dcbe291f529a3e", size = 1118827, upload-time = "2025-11-05T21:40:46.6Z" }, + { url = "https://files.pythonhosted.org/packages/9f/22/1c1a65047df864def9a047dbb40bc0b580b8289a4280e62779cd61ae21f2/rignore-0.7.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aaf938530dcc0b47c4cfa52807aa2e5bfd5ca6d57a621125fe293098692f6345", size = 1128182, upload-time = "2025-11-05T21:41:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/bd/f4/1526eb01fdc2235aca1fd9d0189bee4021d009a8dcb0161540238c24166e/rignore-0.7.6-cp311-cp311-win32.whl", hash = "sha256:166ebce373105dd485ec213a6a2695986346e60c94ff3d84eb532a237b24a4d5", size = 646547, upload-time = "2025-11-05T21:41:49.439Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c8/dda0983e1845706beb5826459781549a840fe5a7eb934abc523e8cd17814/rignore-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:44f35ee844b1a8cea50d056e6a595190ce9d42d3cccf9f19d280ae5f3058973a", size = 727139, upload-time = "2025-11-05T21:41:34.367Z" }, + { url = "https://files.pythonhosted.org/packages/e3/47/eb1206b7bf65970d41190b879e1723fc6bbdb2d45e53565f28991a8d9d96/rignore-0.7.6-cp311-cp311-win_arm64.whl", hash = "sha256:14b58f3da4fa3d5c3fa865cab49821675371f5e979281c683e131ae29159a581", size = 657598, upload-time = "2025-11-05T21:41:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/0b/0e/012556ef3047a2628842b44e753bb15f4dc46806780ff090f1e8fe4bf1eb/rignore-0.7.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:03e82348cb7234f8d9b2834f854400ddbbd04c0f8f35495119e66adbd37827a8", size = 883488, upload-time = "2025-11-05T20:42:41.359Z" }, + { url = "https://files.pythonhosted.org/packages/93/b0/d4f1f3fe9eb3f8e382d45ce5b0547ea01c4b7e0b4b4eb87bcd66a1d2b888/rignore-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9e624f6be6116ea682e76c5feb71ea91255c67c86cb75befe774365b2931961", size = 820411, upload-time = "2025-11-05T20:42:24.782Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c8/dea564b36dedac8de21c18e1851789545bc52a0c22ece9843444d5608a6a/rignore-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bda49950d405aa8d0ebe26af807c4e662dd281d926530f03f29690a2e07d649a", size = 897821, upload-time = "2025-11-05T20:40:52.613Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/ee96db17ac1835e024c5d0742eefb7e46de60020385ac883dd3d1cde2c1f/rignore-0.7.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5fd5ab3840b8c16851d327ed06e9b8be6459702a53e5ab1fc4073b684b3789e", size = 873963, upload-time = "2025-11-05T20:41:07.49Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8c/ad5a57bbb9d14d5c7e5960f712a8a0b902472ea3f4a2138cbf70d1777b75/rignore-0.7.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ced2a248352636a5c77504cb755dc02c2eef9a820a44d3f33061ce1bb8a7f2d2", size = 1169216, upload-time = "2025-11-05T20:41:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/5b00bc2a6bc1701e6878fca798cf5d9125eb3113193e33078b6fc0d99123/rignore-0.7.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a04a3b73b75ddc12c9c9b21efcdaab33ca3832941d6f1d67bffd860941cd448a", size = 942942, upload-time = "2025-11-05T20:41:39.393Z" }, + { url = "https://files.pythonhosted.org/packages/85/e5/7f99bd0cc9818a91d0e8b9acc65b792e35750e3bdccd15a7ee75e64efca4/rignore-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24321efac92140b7ec910ac7c53ab0f0c86a41133d2bb4b0e6a7c94967f44dd", size = 959787, upload-time = "2025-11-05T20:42:09.765Z" }, + { url = "https://files.pythonhosted.org/packages/55/54/2ffea79a7c1eabcede1926347ebc2a81bc6b81f447d05b52af9af14948b9/rignore-0.7.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c7aa109d41e593785c55fdaa89ad80b10330affa9f9d3e3a51fa695f739b20", size = 984245, upload-time = "2025-11-05T20:41:54.062Z" }, + { url = "https://files.pythonhosted.org/packages/41/f7/e80f55dfe0f35787fa482aa18689b9c8251e045076c35477deb0007b3277/rignore-0.7.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1734dc49d1e9501b07852ef44421f84d9f378da9fbeda729e77db71f49cac28b", size = 1078647, upload-time = "2025-11-05T21:40:13.463Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cf/2c64f0b6725149f7c6e7e5a909d14354889b4beaadddaa5fff023ec71084/rignore-0.7.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5719ea14ea2b652c0c0894be5dfde954e1853a80dea27dd2fbaa749618d837f5", size = 1139186, upload-time = "2025-11-05T21:40:31.27Z" }, + { url = "https://files.pythonhosted.org/packages/75/95/a86c84909ccc24af0d094b50d54697951e576c252a4d9f21b47b52af9598/rignore-0.7.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8e23424fc7ce35726854f639cb7968151a792c0c3d9d082f7f67e0c362cfecca", size = 1117604, upload-time = "2025-11-05T21:40:48.07Z" }, + { url = "https://files.pythonhosted.org/packages/7f/5e/13b249613fd5d18d58662490ab910a9f0be758981d1797789913adb4e918/rignore-0.7.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3efdcf1dd84d45f3e2bd2f93303d9be103888f56dfa7c3349b5bf4f0657ec696", size = 1127725, upload-time = "2025-11-05T21:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/c7/28/fa5dcd1e2e16982c359128664e3785f202d3eca9b22dd0b2f91c4b3d242f/rignore-0.7.6-cp312-cp312-win32.whl", hash = "sha256:ccca9d1a8b5234c76b71546fc3c134533b013f40495f394a65614a81f7387046", size = 646145, upload-time = "2025-11-05T21:41:51.096Z" }, + { url = "https://files.pythonhosted.org/packages/26/87/69387fb5dd81a0f771936381431780b8cf66fcd2cfe9495e1aaf41548931/rignore-0.7.6-cp312-cp312-win_amd64.whl", hash = "sha256:c96a285e4a8bfec0652e0bfcf42b1aabcdda1e7625f5006d188e3b1c87fdb543", size = 726090, upload-time = "2025-11-05T21:41:36.485Z" }, + { url = "https://files.pythonhosted.org/packages/24/5f/e8418108dcda8087fb198a6f81caadbcda9fd115d61154bf0df4d6d3619b/rignore-0.7.6-cp312-cp312-win_arm64.whl", hash = "sha256:a64a750e7a8277a323f01ca50b7784a764845f6cce2fe38831cb93f0508d0051", size = 656317, upload-time = "2025-11-05T21:41:25.305Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8a/a4078f6e14932ac7edb171149c481de29969d96ddee3ece5dc4c26f9e0c3/rignore-0.7.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2bdab1d31ec9b4fb1331980ee49ea051c0d7f7bb6baa28b3125ef03cdc48fdaf", size = 883057, upload-time = "2025-11-05T20:42:42.741Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8f/f8daacd177db4bf7c2223bab41e630c52711f8af9ed279be2058d2fe4982/rignore-0.7.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:90f0a00ce0c866c275bf888271f1dc0d2140f29b82fcf33cdbda1e1a6af01010", size = 820150, upload-time = "2025-11-05T20:42:26.545Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/b65b837e39c3f7064c426754714ac633b66b8c2290978af9d7f513e14aa9/rignore-0.7.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1ad295537041dc2ed4b540fb1a3906bd9ede6ccdad3fe79770cd89e04e3c73c", size = 897406, upload-time = "2025-11-05T20:40:53.854Z" }, + { url = "https://files.pythonhosted.org/packages/ca/58/1970ce006c427e202ac7c081435719a076c478f07b3a23f469227788dc23/rignore-0.7.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f782dbd3a65a5ac85adfff69e5c6b101285ef3f845c3a3cae56a54bebf9fe116", size = 874050, upload-time = "2025-11-05T20:41:08.922Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/eb45db9f90137329072a732273be0d383cb7d7f50ddc8e0bceea34c1dfdf/rignore-0.7.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65cece3b36e5b0826d946494734c0e6aaf5a0337e18ff55b071438efe13d559e", size = 1167835, upload-time = "2025-11-05T20:41:24.997Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f1/6f1d72ddca41a64eed569680587a1236633587cc9f78136477ae69e2c88a/rignore-0.7.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7e4bb66c13cd7602dc8931822c02dfbbd5252015c750ac5d6152b186f0a8be0", size = 941945, upload-time = "2025-11-05T20:41:40.628Z" }, + { url = "https://files.pythonhosted.org/packages/48/6f/2f178af1c1a276a065f563ec1e11e7a9e23d4996fd0465516afce4b5c636/rignore-0.7.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297e500c15766e196f68aaaa70e8b6db85fa23fdc075b880d8231fdfba738cd7", size = 959067, upload-time = "2025-11-05T20:42:11.09Z" }, + { url = "https://files.pythonhosted.org/packages/5b/db/423a81c4c1e173877c7f9b5767dcaf1ab50484a94f60a0b2ed78be3fa765/rignore-0.7.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a07084211a8d35e1a5b1d32b9661a5ed20669970b369df0cf77da3adea3405de", size = 984438, upload-time = "2025-11-05T20:41:55.443Z" }, + { url = "https://files.pythonhosted.org/packages/31/eb/c4f92cc3f2825d501d3c46a244a671eb737fc1bcf7b05a3ecd34abb3e0d7/rignore-0.7.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:181eb2a975a22256a1441a9d2f15eb1292839ea3f05606620bd9e1938302cf79", size = 1078365, upload-time = "2025-11-05T21:40:15.148Z" }, + { url = "https://files.pythonhosted.org/packages/26/09/99442f02794bd7441bfc8ed1c7319e890449b816a7493b2db0e30af39095/rignore-0.7.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:7bbcdc52b5bf9f054b34ce4af5269df5d863d9c2456243338bc193c28022bd7b", size = 1139066, upload-time = "2025-11-05T21:40:32.771Z" }, + { url = "https://files.pythonhosted.org/packages/2c/88/bcfc21e520bba975410e9419450f4b90a2ac8236b9a80fd8130e87d098af/rignore-0.7.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f2e027a6da21a7c8c0d87553c24ca5cc4364def18d146057862c23a96546238e", size = 1118036, upload-time = "2025-11-05T21:40:49.646Z" }, + { url = "https://files.pythonhosted.org/packages/e2/25/d37215e4562cda5c13312636393aea0bafe38d54d4e0517520a4cc0753ec/rignore-0.7.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee4a18b82cbbc648e4aac1510066682fe62beb5dc88e2c67c53a83954e541360", size = 1127550, upload-time = "2025-11-05T21:41:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/dc/76/a264ab38bfa1620ec12a8ff1c07778da89e16d8c0f3450b0333020d3d6dc/rignore-0.7.6-cp313-cp313-win32.whl", hash = "sha256:a7d7148b6e5e95035d4390396895adc384d37ff4e06781a36fe573bba7c283e5", size = 646097, upload-time = "2025-11-05T21:41:53.201Z" }, + { url = "https://files.pythonhosted.org/packages/62/44/3c31b8983c29ea8832b6082ddb1d07b90379c2d993bd20fce4487b71b4f4/rignore-0.7.6-cp313-cp313-win_amd64.whl", hash = "sha256:b037c4b15a64dced08fc12310ee844ec2284c4c5c1ca77bc37d0a04f7bff386e", size = 726170, upload-time = "2025-11-05T21:41:38.131Z" }, + { url = "https://files.pythonhosted.org/packages/aa/41/e26a075cab83debe41a42661262f606166157df84e0e02e2d904d134c0d8/rignore-0.7.6-cp313-cp313-win_arm64.whl", hash = "sha256:e47443de9b12fe569889bdbe020abe0e0b667516ee2ab435443f6d0869bd2804", size = 656184, upload-time = "2025-11-05T21:41:27.396Z" }, + { url = "https://files.pythonhosted.org/packages/9a/b9/1f5bd82b87e5550cd843ceb3768b4a8ef274eb63f29333cf2f29644b3d75/rignore-0.7.6-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:8e41be9fa8f2f47239ded8920cc283699a052ac4c371f77f5ac017ebeed75732", size = 882632, upload-time = "2025-11-05T20:42:44.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/6b/07714a3efe4a8048864e8a5b7db311ba51b921e15268b17defaebf56d3db/rignore-0.7.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6dc1e171e52cefa6c20e60c05394a71165663b48bca6c7666dee4f778f2a7d90", size = 820760, upload-time = "2025-11-05T20:42:27.885Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0f/348c829ea2d8d596e856371b14b9092f8a5dfbb62674ec9b3f67e4939a9d/rignore-0.7.6-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ce2268837c3600f82ab8db58f5834009dc638ee17103582960da668963bebc5", size = 899044, upload-time = "2025-11-05T20:40:55.336Z" }, + { url = "https://files.pythonhosted.org/packages/f0/30/2e1841a19b4dd23878d73edd5d82e998a83d5ed9570a89675f140ca8b2ad/rignore-0.7.6-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:690a3e1b54bfe77e89c4bacb13f046e642f8baadafc61d68f5a726f324a76ab6", size = 874144, upload-time = "2025-11-05T20:41:10.195Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bf/0ce9beb2e5f64c30e3580bef09f5829236889f01511a125f98b83169b993/rignore-0.7.6-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09d12ac7a0b6210c07bcd145007117ebd8abe99c8eeb383e9e4673910c2754b2", size = 1168062, upload-time = "2025-11-05T20:41:26.511Z" }, + { url = "https://files.pythonhosted.org/packages/b9/8b/571c178414eb4014969865317da8a02ce4cf5241a41676ef91a59aab24de/rignore-0.7.6-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a2b2b74a8c60203b08452479b90e5ce3dbe96a916214bc9eb2e5af0b6a9beb0", size = 942542, upload-time = "2025-11-05T20:41:41.838Z" }, + { url = "https://files.pythonhosted.org/packages/19/62/7a3cf601d5a45137a7e2b89d10c05b5b86499190c4b7ca5c3c47d79ee519/rignore-0.7.6-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fc5a531ef02131e44359419a366bfac57f773ea58f5278c2cdd915f7d10ea94", size = 958739, upload-time = "2025-11-05T20:42:12.463Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1f/4261f6a0d7caf2058a5cde2f5045f565ab91aa7badc972b57d19ce58b14e/rignore-0.7.6-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7a1f77d9c4cd7e76229e252614d963442686bfe12c787a49f4fe481df49e7a9", size = 984138, upload-time = "2025-11-05T20:41:56.775Z" }, + { url = "https://files.pythonhosted.org/packages/2b/bf/628dfe19c75e8ce1f45f7c248f5148b17dfa89a817f8e3552ab74c3ae812/rignore-0.7.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ead81f728682ba72b5b1c3d5846b011d3e0174da978de87c61645f2ed36659a7", size = 1079299, upload-time = "2025-11-05T21:40:16.639Z" }, + { url = "https://files.pythonhosted.org/packages/af/a5/be29c50f5c0c25c637ed32db8758fdf5b901a99e08b608971cda8afb293b/rignore-0.7.6-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:12ffd50f520c22ffdabed8cd8bfb567d9ac165b2b854d3e679f4bcaef11a9441", size = 1139618, upload-time = "2025-11-05T21:40:34.507Z" }, + { url = "https://files.pythonhosted.org/packages/2a/40/3c46cd7ce4fa05c20b525fd60f599165e820af66e66f2c371cd50644558f/rignore-0.7.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e5a16890fbe3c894f8ca34b0fcacc2c200398d4d46ae654e03bc9b3dbf2a0a72", size = 1117626, upload-time = "2025-11-05T21:40:51.494Z" }, + { url = "https://files.pythonhosted.org/packages/8c/b9/aea926f263b8a29a23c75c2e0d8447965eb1879d3feb53cfcf84db67ed58/rignore-0.7.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3abab3bf99e8a77488ef6c7c9a799fac22224c28fe9f25cc21aa7cc2b72bfc0b", size = 1128144, upload-time = "2025-11-05T21:41:09.169Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f6/0d6242f8d0df7f2ecbe91679fefc1f75e7cd2072cb4f497abaab3f0f8523/rignore-0.7.6-cp314-cp314-win32.whl", hash = "sha256:eeef421c1782953c4375aa32f06ecae470c1285c6381eee2a30d2e02a5633001", size = 646385, upload-time = "2025-11-05T21:41:55.105Z" }, + { url = "https://files.pythonhosted.org/packages/d5/38/c0dcd7b10064f084343d6af26fe9414e46e9619c5f3224b5272e8e5d9956/rignore-0.7.6-cp314-cp314-win_amd64.whl", hash = "sha256:6aeed503b3b3d5af939b21d72a82521701a4bd3b89cd761da1e7dc78621af304", size = 725738, upload-time = "2025-11-05T21:41:39.736Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7a/290f868296c1ece914d565757ab363b04730a728b544beb567ceb3b2d96f/rignore-0.7.6-cp314-cp314-win_arm64.whl", hash = "sha256:104f215b60b3c984c386c3e747d6ab4376d5656478694e22c7bd2f788ddd8304", size = 656008, upload-time = "2025-11-05T21:41:29.028Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d2/3c74e3cd81fe8ea08a8dcd2d755c09ac2e8ad8fe409508904557b58383d3/rignore-0.7.6-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bb24a5b947656dd94cb9e41c4bc8b23cec0c435b58be0d74a874f63c259549e8", size = 882835, upload-time = "2025-11-05T20:42:45.443Z" }, + { url = "https://files.pythonhosted.org/packages/77/61/a772a34b6b63154877433ac2d048364815b24c2dd308f76b212c408101a2/rignore-0.7.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b1e33c9501cefe24b70a1eafd9821acfd0ebf0b35c3a379430a14df089993e3", size = 820301, upload-time = "2025-11-05T20:42:29.226Z" }, + { url = "https://files.pythonhosted.org/packages/71/30/054880b09c0b1b61d17eeb15279d8bf729c0ba52b36c3ada52fb827cbb3c/rignore-0.7.6-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bec3994665a44454df86deb762061e05cd4b61e3772f5b07d1882a8a0d2748d5", size = 897611, upload-time = "2025-11-05T20:40:56.475Z" }, + { url = "https://files.pythonhosted.org/packages/1e/40/b2d1c169f833d69931bf232600eaa3c7998ba4f9a402e43a822dad2ea9f2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26cba2edfe3cff1dfa72bddf65d316ddebf182f011f2f61538705d6dbaf54986", size = 873875, upload-time = "2025-11-05T20:41:11.561Z" }, + { url = "https://files.pythonhosted.org/packages/55/59/ca5ae93d83a1a60e44b21d87deb48b177a8db1b85e82fc8a9abb24a8986d/rignore-0.7.6-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffa86694fec604c613696cb91e43892aa22e1fec5f9870e48f111c603e5ec4e9", size = 1167245, upload-time = "2025-11-05T20:41:28.29Z" }, + { url = "https://files.pythonhosted.org/packages/a5/52/cf3dce392ba2af806cba265aad6bcd9c48bb2a6cb5eee448d3319f6e505b/rignore-0.7.6-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48efe2ed95aa8104145004afb15cdfa02bea5cdde8b0344afeb0434f0d989aa2", size = 941750, upload-time = "2025-11-05T20:41:43.111Z" }, + { url = "https://files.pythonhosted.org/packages/ec/be/3f344c6218d779395e785091d05396dfd8b625f6aafbe502746fcd880af2/rignore-0.7.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dcae43eb44b7f2457fef7cc87f103f9a0013017a6f4e62182c565e924948f21", size = 958896, upload-time = "2025-11-05T20:42:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/34/d3fa71938aed7d00dcad87f0f9bcb02ad66c85d6ffc83ba31078ce53646a/rignore-0.7.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2cd649a7091c0dad2f11ef65630d30c698d505cbe8660dd395268e7c099cc99f", size = 983992, upload-time = "2025-11-05T20:41:58.022Z" }, + { url = "https://files.pythonhosted.org/packages/24/a4/52a697158e9920705bdbd0748d59fa63e0f3233fb92e9df9a71afbead6ca/rignore-0.7.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42de84b0289d478d30ceb7ae59023f7b0527786a9a5b490830e080f0e4ea5aeb", size = 1078181, upload-time = "2025-11-05T21:40:18.151Z" }, + { url = "https://files.pythonhosted.org/packages/ac/65/aa76dbcdabf3787a6f0fd61b5cc8ed1e88580590556d6c0207960d2384bb/rignore-0.7.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:875a617e57b53b4acbc5a91de418233849711c02e29cc1f4f9febb2f928af013", size = 1139232, upload-time = "2025-11-05T21:40:35.966Z" }, + { url = "https://files.pythonhosted.org/packages/08/44/31b31a49b3233c6842acc1c0731aa1e7fb322a7170612acf30327f700b44/rignore-0.7.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8703998902771e96e49968105207719f22926e4431b108450f3f430b4e268b7c", size = 1117349, upload-time = "2025-11-05T21:40:53.013Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ae/1b199a2302c19c658cf74e5ee1427605234e8c91787cfba0015f2ace145b/rignore-0.7.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:602ef33f3e1b04c1e9a10a3c03f8bc3cef2d2383dcc250d309be42b49923cabc", size = 1127702, upload-time = "2025-11-05T21:41:10.881Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d3/18210222b37e87e36357f7b300b7d98c6dd62b133771e71ae27acba83a4f/rignore-0.7.6-cp314-cp314t-win32.whl", hash = "sha256:c1d8f117f7da0a4a96a8daef3da75bc090e3792d30b8b12cfadc240c631353f9", size = 647033, upload-time = "2025-11-05T21:42:00.095Z" }, + { url = "https://files.pythonhosted.org/packages/3e/87/033eebfbee3ec7d92b3bb1717d8f68c88e6fc7de54537040f3b3a405726f/rignore-0.7.6-cp314-cp314t-win_amd64.whl", hash = "sha256:ca36e59408bec81de75d307c568c2d0d410fb880b1769be43611472c61e85c96", size = 725647, upload-time = "2025-11-05T21:41:44.449Z" }, + { url = "https://files.pythonhosted.org/packages/79/62/b88e5879512c55b8ee979c666ee6902adc4ed05007226de266410ae27965/rignore-0.7.6-cp314-cp314t-win_arm64.whl", hash = "sha256:b83adabeb3e8cf662cabe1931b83e165b88c526fa6af6b3aa90429686e474896", size = 656035, upload-time = "2025-11-05T21:41:31.13Z" }, + { url = "https://files.pythonhosted.org/packages/85/12/62d690b4644c330d7ac0f739b7f078190ab4308faa909a60842d0e4af5b2/rignore-0.7.6-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3d3a523af1cd4ed2c0cba8d277a32d329b0c96ef9901fb7ca45c8cfaccf31a5", size = 887462, upload-time = "2025-11-05T20:42:50.804Z" }, + { url = "https://files.pythonhosted.org/packages/05/bc/6528a0e97ed2bd7a7c329183367d1ffbc5b9762ae8348d88dae72cc9d1f5/rignore-0.7.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:990853566e65184a506e1e2af2d15045afad3ebaebb8859cb85b882081915110", size = 826918, upload-time = "2025-11-05T20:42:33.689Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2c/7d7bad116e09a04e9e1688c6f891fa2d4fd33f11b69ac0bd92419ddebeae/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cab9ff2e436ce7240d7ee301c8ef806ed77c1fd6b8a8239ff65f9bbbcb5b8a3", size = 900922, upload-time = "2025-11-05T20:41:00.361Z" }, + { url = "https://files.pythonhosted.org/packages/09/ba/e5ea89fbde8e37a90ce456e31c5e9d85512cef5ae38e0f4d2426eb776a19/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1a6671b2082c13bfd9a5cf4ce64670f832a6d41470556112c4ab0b6519b2fc4", size = 876987, upload-time = "2025-11-05T20:41:16.219Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fb/93d14193f0ec0c3d35b763f0a000e9780f63b2031f3d3756442c2152622d/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2468729b4c5295c199d084ab88a40afcb7c8b974276805105239c07855bbacee", size = 1171110, upload-time = "2025-11-05T20:41:32.631Z" }, + { url = "https://files.pythonhosted.org/packages/9e/46/08436312ff96ffa29cfa4e1a987efc37e094531db46ba5e9fda9bb792afd/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:775710777fd71e5fdf54df69cdc249996a1d6f447a2b5bfb86dbf033fddd9cf9", size = 943339, upload-time = "2025-11-05T20:41:47.128Z" }, + { url = "https://files.pythonhosted.org/packages/34/28/3b3c51328f505cfaf7e53f408f78a1e955d561135d02f9cb0341ea99f69a/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4565407f4a77f72cf9d91469e75d15d375f755f0a01236bb8aaa176278cc7085", size = 961680, upload-time = "2025-11-05T20:42:18.061Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9e/cbff75c8676d4f4a90bd58a1581249d255c7305141b0868f0abc0324836b/rignore-0.7.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc44c33f8fb2d5c9da748de7a6e6653a78aa740655e7409895e94a247ffa97c8", size = 987045, upload-time = "2025-11-05T20:42:02.315Z" }, + { url = "https://files.pythonhosted.org/packages/8c/25/d802d1d369502a7ddb8816059e7c79d2d913e17df975b863418e0aca4d8a/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8f32478f05540513c11923e8838afab9efef0131d66dca7f67f0e1bbd118af6a", size = 1080310, upload-time = "2025-11-05T21:40:23.184Z" }, + { url = "https://files.pythonhosted.org/packages/43/f0/250b785c2e473b1ab763eaf2be820934c2a5409a722e94b279dddac21c7d/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:1b63a3dd76225ea35b01dd6596aa90b275b5d0f71d6dc28fce6dd295d98614aa", size = 1140998, upload-time = "2025-11-05T21:40:40.603Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d6/bb42fd2a8bba6aea327962656e20621fd495523259db40cfb4c5f760f05c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:fe6c41175c36554a4ef0994cd1b4dbd6d73156fca779066456b781707402048e", size = 1121178, upload-time = "2025-11-05T21:40:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/97/f4/aeb548374129dce3dc191a4bb598c944d9ed663f467b9af830315d86059c/rignore-0.7.6-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a0c6792406ae36f4e7664dc772da909451d46432ff8485774526232d4885063", size = 1130190, upload-time = "2025-11-05T21:41:16.403Z" }, + { url = "https://files.pythonhosted.org/packages/82/78/a6250ff0c49a3cdb943910ada4116e708118e9b901c878cfae616c80a904/rignore-0.7.6-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a20b6fb61bcced9a83dfcca6599ad45182b06ba720cff7c8d891e5b78db5b65f", size = 886470, upload-time = "2025-11-05T20:42:52.314Z" }, + { url = "https://files.pythonhosted.org/packages/35/af/c69c0c51b8f9f7914d95c4ea91c29a2ac067572048cae95dd6d2efdbe05d/rignore-0.7.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:392dcabfecbe176c9ebbcb40d85a5e86a5989559c4f988c2741da7daf1b5be25", size = 825976, upload-time = "2025-11-05T20:42:35.118Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d2/1b264f56132264ea609d3213ab603d6a27016b19559a1a1ede1a66a03dcd/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22baa462abdc36fdd5a5e2dae423107723351b85ff093762f9261148b9d0a04a", size = 899739, upload-time = "2025-11-05T20:41:01.518Z" }, + { url = "https://files.pythonhosted.org/packages/55/e4/b3c5dfdd8d8a10741dfe7199ef45d19a0e42d0c13aa377c83bd6caf65d90/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53fb28882d2538cb2d231972146c4927a9d9455e62b209f85d634408c4103538", size = 874843, upload-time = "2025-11-05T20:41:17.687Z" }, + { url = "https://files.pythonhosted.org/packages/cc/10/d6f3750233881a2a154cefc9a6a0a9b19da526b19f7f08221b552c6f827d/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87409f7eeb1103d6b77f3472a3a0d9a5953e3ae804a55080bdcb0120ee43995b", size = 1170348, upload-time = "2025-11-05T20:41:34.21Z" }, + { url = "https://files.pythonhosted.org/packages/6e/10/ad98ca05c9771c15af734cee18114a3c280914b6e34fde9ffea2e61e88aa/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684014e42e4341ab3ea23a203551857fcc03a7f8ae96ca3aefb824663f55db32", size = 942315, upload-time = "2025-11-05T20:41:48.508Z" }, + { url = "https://files.pythonhosted.org/packages/de/00/ab5c0f872acb60d534e687e629c17e0896c62da9b389c66d3aa16b817aa8/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77356ebb01ba13f8a425c3d30fcad40e57719c0e37670d022d560884a30e4767", size = 961047, upload-time = "2025-11-05T20:42:19.403Z" }, + { url = "https://files.pythonhosted.org/packages/b8/86/3030fdc363a8f0d1cd155b4c453d6db9bab47a24fcc64d03f61d9d78fe6a/rignore-0.7.6-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6cbd8a48abbd3747a6c830393cd578782fab5d43f4deea48c5f5e344b8fed2b0", size = 986090, upload-time = "2025-11-05T20:42:03.581Z" }, + { url = "https://files.pythonhosted.org/packages/33/b8/133aa4002cee0ebbb39362f94e4898eec7fbd09cec9fcbce1cd65b355b7f/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2673225dcec7f90497e79438c35e34638d0d0391ccea3cbb79bfb9adc0dc5bd7", size = 1079656, upload-time = "2025-11-05T21:40:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/67/56/36d5d34210e5e7dfcd134eed8335b19e80ae940ee758f493e4f2b344dd70/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_armv7l.whl", hash = "sha256:c081f17290d8a2b96052b79207622aa635686ea39d502b976836384ede3d303c", size = 1139789, upload-time = "2025-11-05T21:40:42.119Z" }, + { url = "https://files.pythonhosted.org/packages/6b/5b/bb4f9420802bf73678033a4a55ab1bede36ce2e9b41fec5f966d83d932b3/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:57e8327aacc27f921968cb2a174f9e47b084ce9a7dd0122c8132d22358f6bd79", size = 1120308, upload-time = "2025-11-05T21:40:59.402Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8b/a1299085b28a2f6135e30370b126e3c5055b61908622f2488ade67641479/rignore-0.7.6-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:d8955b57e42f2a5434670d5aa7b75eaf6e74602ccd8955dddf7045379cd762fb", size = 1129444, upload-time = "2025-11-05T21:41:17.906Z" }, +] + +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +] + +[[package]] +name = "sacrebleu" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama" }, + { name = "lxml" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "portalocker" }, + { name = "regex" }, + { name = "tabulate" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d3/ed/d7acddcff74d690c56fe26a1f7828bdde548262828d0743414ea916c40c1/sacrebleu-2.6.0.tar.gz", hash = "sha256:91499b6cd46138d95154fff1e863c2f9be57e82f0c719d8dd718d0006cf6c566", size = 1893419, upload-time = "2026-01-12T17:17:20.799Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/f2/6c90ccf3ad1d09a7d662a405b274f3c93b92df59c8d6a025d26aaf34d302/sacrebleu-2.6.0-py3-none-any.whl", hash = "sha256:3edc1531575cfe4ad04ce53491a9307e234af1c3f805a1f491cbec844229a8a8", size = 100785, upload-time = "2026-01-12T17:17:18.868Z" }, +] + +[[package]] +name = "safetensors" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.25.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "imageio", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "lazy-loader", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tifffile", version = "2025.5.10", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/cb/016c63f16065c2d333c8ed0337e18a5cdf9bc32d402e4f26b0db362eb0e2/scikit_image-0.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d3278f586793176599df6a4cf48cb6beadae35c31e58dc01a98023af3dc31c78", size = 13988922, upload-time = "2025-02-18T18:04:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/30/ca/ff4731289cbed63c94a0c9a5b672976603118de78ed21910d9060c82e859/scikit_image-0.25.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:5c311069899ce757d7dbf1d03e32acb38bb06153236ae77fcd820fd62044c063", size = 13192698, upload-time = "2025-02-18T18:04:15.362Z" }, + { url = "https://files.pythonhosted.org/packages/39/6d/a2aadb1be6d8e149199bb9b540ccde9e9622826e1ab42fe01de4c35ab918/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be455aa7039a6afa54e84f9e38293733a2622b8c2fb3362b822d459cc5605e99", size = 14153634, upload-time = "2025-02-18T18:04:18.496Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/916e7d9ee4721031b2f625db54b11d8379bd51707afaa3e5a29aecf10bc4/scikit_image-0.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4c464b90e978d137330be433df4e76d92ad3c5f46a22f159520ce0fdbea8a09", size = 14767545, upload-time = "2025-02-18T18:04:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ee/c53a009e3997dda9d285402f19226fbd17b5b3cb215da391c4ed084a1424/scikit_image-0.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:60516257c5a2d2f74387c502aa2f15a0ef3498fbeaa749f730ab18f0a40fd054", size = 12812908, upload-time = "2025-02-18T18:04:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, + { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, + { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, + { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, + { url = "https://files.pythonhosted.org/packages/35/8c/5df82881284459f6eec796a5ac2a0a304bb3384eec2e73f35cfdfcfbf20c/scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb", size = 13986000, upload-time = "2025-02-18T18:04:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e6/93bebe1abcdce9513ffec01d8af02528b4c41fb3c1e46336d70b9ed4ef0d/scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed", size = 13235893, upload-time = "2025-02-18T18:04:51.049Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/eda616e33f67129e5979a9eb33c710013caa3aa8a921991e6cc0b22cea33/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d", size = 14178389, upload-time = "2025-02-18T18:04:54.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/b5/b75527c0f9532dd8a93e8e7cd8e62e547b9f207d4c11e24f0006e8646b36/scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824", size = 15003435, upload-time = "2025-02-18T18:04:57.586Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/49beb08ebccda3c21e871b607c1cb2f258c3fa0d2f609fed0a5ba741b92d/scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2", size = 12899474, upload-time = "2025-02-18T18:05:01.166Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7c/9814dd1c637f7a0e44342985a76f95a55dd04be60154247679fd96c7169f/scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da", size = 13921841, upload-time = "2025-02-18T18:05:03.963Z" }, + { url = "https://files.pythonhosted.org/packages/84/06/66a2e7661d6f526740c309e9717d3bd07b473661d5cdddef4dd978edab25/scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc", size = 13196862, upload-time = "2025-02-18T18:05:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/4e/63/3368902ed79305f74c2ca8c297dfeb4307269cbe6402412668e322837143/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341", size = 14117785, upload-time = "2025-02-18T18:05:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9b/c3da56a145f52cd61a68b8465d6a29d9503bc45bc993bb45e84371c97d94/scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147", size = 14977119, upload-time = "2025-02-18T18:05:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/8a/97/5fcf332e1753831abb99a2525180d3fb0d70918d461ebda9873f66dcc12f/scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f", size = 12885116, upload-time = "2025-02-18T18:05:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/75e9f17e3670b5ed93c32456fda823333c6279b144cd93e2c03aa06aa472/scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd", size = 13862801, upload-time = "2025-02-18T18:05:20.783Z" }, +] + +[[package]] +name = "scikit-image" +version = "0.26.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "imageio", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "lazy-loader", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tifffile", version = "2026.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tifffile", version = "2026.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/b4/2528bb43c67d48053a7a649a9666432dc307d66ba02e3a6d5c40f46655df/scikit_image-0.26.0.tar.gz", hash = "sha256:f5f970ab04efad85c24714321fcc91613fcb64ef2a892a13167df2f3e59199fa", size = 22729739, upload-time = "2025-12-20T17:12:21.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/16/8a407688b607f86f81f8c649bf0d68a2a6d67375f18c2d660aba20f5b648/scikit_image-0.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b1ede33a0fb3731457eaf53af6361e73dd510f449dac437ab54573b26788baf0", size = 12355510, upload-time = "2025-12-20T17:10:31.628Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f9/7efc088ececb6f6868fd4475e16cfafc11f242ce9ab5fc3557d78b5da0d4/scikit_image-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7af7aa331c6846bd03fa28b164c18d0c3fd419dbb888fb05e958ac4257a78fdd", size = 12056334, upload-time = "2025-12-20T17:10:34.559Z" }, + { url = "https://files.pythonhosted.org/packages/9f/1e/bc7fb91fb5ff65ef42346c8b7ee8b09b04eabf89235ab7dbfdfd96cbd1ea/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ea6207d9e9d21c3f464efe733121c0504e494dbdc7728649ff3e23c3c5a4953", size = 13297768, upload-time = "2025-12-20T17:10:37.733Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2a/e71c1a7d90e70da67b88ccc609bd6ae54798d5847369b15d3a8052232f9d/scikit_image-0.26.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74aa5518ccea28121f57a95374581d3b979839adc25bb03f289b1bc9b99c58af", size = 13711217, upload-time = "2025-12-20T17:10:40.935Z" }, + { url = "https://files.pythonhosted.org/packages/d4/59/9637ee12c23726266b91296791465218973ce1ad3e4c56fc81e4d8e7d6e1/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d5c244656de905e195a904e36dbc18585e06ecf67d90f0482cbde63d7f9ad59d", size = 14337782, upload-time = "2025-12-20T17:10:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/e7/5c/a3e1e0860f9294663f540c117e4bf83d55e5b47c281d475cc06227e88411/scikit_image-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21a818ee6ca2f2131b9e04d8eb7637b5c18773ebe7b399ad23dcc5afaa226d2d", size = 14805997, upload-time = "2025-12-20T17:10:45.93Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c6/2eeacf173da041a9e388975f54e5c49df750757fcfc3ee293cdbbae1ea0a/scikit_image-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:9490360c8d3f9a7e85c8de87daf7c0c66507960cf4947bb9610d1751928721c7", size = 11878486, upload-time = "2025-12-20T17:10:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/c3/a4/a852c4949b9058d585e762a66bf7e9a2cd3be4795cd940413dfbfbb0ce79/scikit_image-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:0baa0108d2d027f34d748e84e592b78acc23e965a5de0e4bb03cf371de5c0581", size = 11346518, upload-time = "2025-12-20T17:10:50.575Z" }, + { url = "https://files.pythonhosted.org/packages/99/e8/e13757982264b33a1621628f86b587e9a73a13f5256dad49b19ba7dc9083/scikit_image-0.26.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d454b93a6fa770ac5ae2d33570f8e7a321bb80d29511ce4b6b78058ebe176e8c", size = 12376452, upload-time = "2025-12-20T17:10:52.796Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/f8dd17d0510f9911f9f17ba301f7455328bf13dae416560126d428de9568/scikit_image-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3409e89d66eff5734cd2b672d1c48d2759360057e714e1d92a11df82c87cba37", size = 12061567, upload-time = "2025-12-20T17:10:55.207Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/c70120a6880579fb42b91567ad79feb4772f7be72e8d52fec403a3dde0c6/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c717490cec9e276afb0438dd165b7c3072d6c416709cc0f9f5a4c1070d23a44", size = 13084214, upload-time = "2025-12-20T17:10:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a2/70401a107d6d7466d64b466927e6b96fcefa99d57494b972608e2f8be50f/scikit_image-0.26.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df650e79031634ac90b11e64a9eedaf5a5e06fcd09bcd03a34be01745744466", size = 13561683, upload-time = "2025-12-20T17:10:59.49Z" }, + { url = "https://files.pythonhosted.org/packages/13/a5/48bdfd92794c5002d664e0910a349d0a1504671ef5ad358150f21643c79a/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cefd85033e66d4ea35b525bb0937d7f42d4cdcfed2d1888e1570d5ce450d3932", size = 14112147, upload-time = "2025-12-20T17:11:02.083Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b5/ac71694da92f5def5953ca99f18a10fe98eac2dd0a34079389b70b4d0394/scikit_image-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f5bf622d7c0435884e1e141ebbe4b2804e16b2dd23ae4c6183e2ea99233be70", size = 14661625, upload-time = "2025-12-20T17:11:04.528Z" }, + { url = "https://files.pythonhosted.org/packages/23/4d/a3cc1e96f080e253dad2251bfae7587cf2b7912bcd76fd43fd366ff35a87/scikit_image-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:abed017474593cd3056ae0fe948d07d0747b27a085e92df5474f4955dd65aec0", size = 11911059, upload-time = "2025-12-20T17:11:06.61Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/d1b8055f584acc937478abf4550d122936f420352422a1a625eef2c605d8/scikit_image-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d57e39ef67a95d26860c8caf9b14b8fb130f83b34c6656a77f191fa6d1d04d8", size = 11348740, upload-time = "2025-12-20T17:11:09.118Z" }, + { url = "https://files.pythonhosted.org/packages/4f/48/02357ffb2cca35640f33f2cfe054a4d6d5d7a229b88880a64f1e45c11f4e/scikit_image-0.26.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a2e852eccf41d2d322b8e60144e124802873a92b8d43a6f96331aa42888491c7", size = 12346329, upload-time = "2025-12-20T17:11:11.599Z" }, + { url = "https://files.pythonhosted.org/packages/67/b9/b792c577cea2c1e94cda83b135a656924fc57c428e8a6d302cd69aac1b60/scikit_image-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98329aab3bc87db352b9887f64ce8cdb8e75f7c2daa19927f2e121b797b678d5", size = 12031726, upload-time = "2025-12-20T17:11:13.871Z" }, + { url = "https://files.pythonhosted.org/packages/07/a9/9564250dfd65cb20404a611016db52afc6268b2b371cd19c7538ea47580f/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915bb3ba66455cf8adac00dc8fdf18a4cd29656aec7ddd38cb4dda90289a6f21", size = 13094910, upload-time = "2025-12-20T17:11:16.2Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b8/0d8eeb5a9fd7d34ba84f8a55753a0a3e2b5b51b2a5a0ade648a8db4a62f7/scikit_image-0.26.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b36ab5e778bf50af5ff386c3ac508027dc3aaeccf2161bdf96bde6848f44d21b", size = 13660939, upload-time = "2025-12-20T17:11:18.464Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d6/91d8973584d4793d4c1a847d388e34ef1218d835eeddecfc9108d735b467/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09bad6a5d5949c7896c8347424c4cca899f1d11668030e5548813ab9c2865dcb", size = 14138938, upload-time = "2025-12-20T17:11:20.919Z" }, + { url = "https://files.pythonhosted.org/packages/39/9a/7e15d8dc10d6bbf212195fb39bdeb7f226c46dd53f9c63c312e111e2e175/scikit_image-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:aeb14db1ed09ad4bee4ceb9e635547a8d5f3549be67fc6c768c7f923e027e6cd", size = 14752243, upload-time = "2025-12-20T17:11:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/8f/58/2b11b933097bc427e42b4a8b15f7de8f24f2bac1fd2779d2aea1431b2c31/scikit_image-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:ac529eb9dbd5954f9aaa2e3fe9a3fd9661bfe24e134c688587d811a0233127f1", size = 11906770, upload-time = "2025-12-20T17:11:25.297Z" }, + { url = "https://files.pythonhosted.org/packages/ad/ec/96941474a18a04b69b6f6562a5bd79bd68049fa3728d3b350976eccb8b93/scikit_image-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:a2d211bc355f59725efdcae699b93b30348a19416cc9e017f7b2fb599faf7219", size = 11342506, upload-time = "2025-12-20T17:11:27.399Z" }, + { url = "https://files.pythonhosted.org/packages/03/e5/c1a9962b0cf1952f42d32b4a2e48eed520320dbc4d2ff0b981c6fa508b6b/scikit_image-0.26.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9eefb4adad066da408a7601c4c24b07af3b472d90e08c3e7483d4e9e829d8c49", size = 12663278, upload-time = "2025-12-20T17:11:29.358Z" }, + { url = "https://files.pythonhosted.org/packages/ae/97/c1a276a59ce8e4e24482d65c1a3940d69c6b3873279193b7ebd04e5ee56b/scikit_image-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6caec76e16c970c528d15d1c757363334d5cb3069f9cea93d2bead31820511f3", size = 12405142, upload-time = "2025-12-20T17:11:31.282Z" }, + { url = "https://files.pythonhosted.org/packages/d4/4a/f1cbd1357caef6c7993f7efd514d6e53d8fd6f7fe01c4714d51614c53289/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a07200fe09b9d99fcdab959859fe0f7db8df6333d6204344425d476850ce3604", size = 12942086, upload-time = "2025-12-20T17:11:33.683Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/74d9fb87c5655bd64cf00b0c44dc3d6206d9002e5f6ba1c9aeb13236f6bf/scikit_image-0.26.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92242351bccf391fc5df2d1529d15470019496d2498d615beb68da85fe7fdf37", size = 13265667, upload-time = "2025-12-20T17:11:36.11Z" }, + { url = "https://files.pythonhosted.org/packages/a7/73/faddc2413ae98d863f6fa2e3e14da4467dd38e788e1c23346cf1a2b06b97/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:52c496f75a7e45844d951557f13c08c81487c6a1da2e3c9c8a39fcde958e02cc", size = 14001966, upload-time = "2025-12-20T17:11:38.55Z" }, + { url = "https://files.pythonhosted.org/packages/02/94/9f46966fa042b5d57c8cd641045372b4e0df0047dd400e77ea9952674110/scikit_image-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20ef4a155e2e78b8ab973998e04d8a361d49d719e65412405f4dadd9155a61d9", size = 14359526, upload-time = "2025-12-20T17:11:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b4/2840fe38f10057f40b1c9f8fb98a187a370936bf144a4ac23452c5ef1baf/scikit_image-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c9087cf7d0e7f33ab5c46d2068d86d785e70b05400a891f73a13400f1e1faf6a", size = 12287629, upload-time = "2025-12-20T17:11:43.11Z" }, + { url = "https://files.pythonhosted.org/packages/22/ba/73b6ca70796e71f83ab222690e35a79612f0117e5aaf167151b7d46f5f2c/scikit_image-0.26.0-cp313-cp313t-win_arm64.whl", hash = "sha256:27d58bc8b2acd351f972c6508c1b557cfed80299826080a4d803dd29c51b707e", size = 11647755, upload-time = "2025-12-20T17:11:45.279Z" }, + { url = "https://files.pythonhosted.org/packages/51/44/6b744f92b37ae2833fd423cce8f806d2368859ec325a699dc30389e090b9/scikit_image-0.26.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:63af3d3a26125f796f01052052f86806da5b5e54c6abef152edb752683075a9c", size = 12365810, upload-time = "2025-12-20T17:11:47.357Z" }, + { url = "https://files.pythonhosted.org/packages/40/f5/83590d9355191f86ac663420fec741b82cc547a4afe7c4c1d986bf46e4db/scikit_image-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ce00600cd70d4562ed59f80523e18cdcc1fae0e10676498a01f73c255774aefd", size = 12075717, upload-time = "2025-12-20T17:11:49.483Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/253e7cf5aee6190459fe136c614e2cbccc562deceb4af96e0863f1b8ee29/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6381edf972b32e4f54085449afde64365a57316637496c1325a736987083e2ab", size = 13161520, upload-time = "2025-12-20T17:11:51.58Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/cec6a3cbaadfdcc02bd6ff02f3abfe09eaa7f4d4e0a525a1e3a3f4bce49c/scikit_image-0.26.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6624a76c6085218248154cc7e1500e6b488edcd9499004dd0d35040607d7505", size = 13684340, upload-time = "2025-12-20T17:11:53.708Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0d/39a776f675d24164b3a267aa0db9f677a4cb20127660d8bf4fd7fef66817/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f775f0e420faac9c2aa6757135f4eb468fb7b70e0b67fa77a5e79be3c30ee331", size = 14203839, upload-time = "2025-12-20T17:11:55.89Z" }, + { url = "https://files.pythonhosted.org/packages/ee/25/2514df226bbcedfe9b2caafa1ba7bc87231a0c339066981b182b08340e06/scikit_image-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede4d6d255cc5da9faeb2f9ba7fedbc990abbc652db429f40a16b22e770bb578", size = 14770021, upload-time = "2025-12-20T17:11:58.014Z" }, + { url = "https://files.pythonhosted.org/packages/8d/5b/0671dc91c0c79340c3fe202f0549c7d3681eb7640fe34ab68a5f090a7c7f/scikit_image-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:0660b83968c15293fd9135e8d860053ee19500d52bf55ca4fb09de595a1af650", size = 12023490, upload-time = "2025-12-20T17:12:00.013Z" }, + { url = "https://files.pythonhosted.org/packages/65/08/7c4cb59f91721f3de07719085212a0b3962e3e3f2d1818cbac4eeb1ea53e/scikit_image-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:b8d14d3181c21c11170477a42542c1addc7072a90b986675a71266ad17abc37f", size = 11473782, upload-time = "2025-12-20T17:12:01.983Z" }, + { url = "https://files.pythonhosted.org/packages/49/41/65c4258137acef3d73cb561ac55512eacd7b30bb4f4a11474cad526bc5db/scikit_image-0.26.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:cde0bbd57e6795eba83cb10f71a677f7239271121dc950bc060482834a668ad1", size = 12686060, upload-time = "2025-12-20T17:12:03.886Z" }, + { url = "https://files.pythonhosted.org/packages/e7/32/76971f8727b87f1420a962406388a50e26667c31756126444baf6668f559/scikit_image-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:163e9afb5b879562b9aeda0dd45208a35316f26cc7a3aed54fd601604e5cf46f", size = 12422628, upload-time = "2025-12-20T17:12:05.921Z" }, + { url = "https://files.pythonhosted.org/packages/37/0d/996febd39f757c40ee7b01cdb861867327e5c8e5f595a634e8201462d958/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724f79fd9b6cb6f4a37864fe09f81f9f5d5b9646b6868109e1b100d1a7019e59", size = 12962369, upload-time = "2025-12-20T17:12:07.912Z" }, + { url = "https://files.pythonhosted.org/packages/48/b4/612d354f946c9600e7dea012723c11d47e8d455384e530f6daaaeb9bf62c/scikit_image-0.26.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3268f13310e6857508bd87202620df996199a016a1d281b309441d227c822394", size = 13272431, upload-time = "2025-12-20T17:12:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/26c00b466e06055a086de2c6e2145fe189ccdc9a1d11ccc7de020f2591ad/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fac96a1f9b06cd771cbbb3cd96c5332f36d4efd839b1d8b053f79e5887acde62", size = 14016362, upload-time = "2025-12-20T17:12:12.793Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/00a90402e1775634043c2a0af8a3c76ad450866d9fa444efcc43b553ba2d/scikit_image-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c1e7bd342f43e7a97e571b3f03ba4c1293ea1a35c3f13f41efdc8a81c1dc8f2", size = 14364151, upload-time = "2025-12-20T17:12:14.909Z" }, + { url = "https://files.pythonhosted.org/packages/da/ca/918d8d306bd43beacff3b835c6d96fac0ae64c0857092f068b88db531a7c/scikit_image-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b702c3bb115e1dcf4abf5297429b5c90f2189655888cbed14921f3d26f81d3a4", size = 12413484, upload-time = "2025-12-20T17:12:17.046Z" }, + { url = "https://files.pythonhosted.org/packages/dc/cd/4da01329b5a8d47ff7ec3c99a2b02465a8017b186027590dc7425cee0b56/scikit_image-0.26.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0608aa4a9ec39e0843de10d60edb2785a30c1c47819b67866dd223ebd149acaf", size = 11769501, upload-time = "2025-12-20T17:12:19.339Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/3e/daed796fd69cce768b8788401cc464ea90b306fb196ae1ffed0b98182859/scikit_learn-1.7.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b33579c10a3081d076ab403df4a4190da4f4432d443521674637677dc91e61f", size = 9336221, upload-time = "2025-09-09T08:20:19.328Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ce/af9d99533b24c55ff4e18d9b7b4d9919bbc6cd8f22fe7a7be01519a347d5/scikit_learn-1.7.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:36749fb62b3d961b1ce4fedf08fa57a1986cd409eff2d783bca5d4b9b5fce51c", size = 8653834, upload-time = "2025-09-09T08:20:22.073Z" }, + { url = "https://files.pythonhosted.org/packages/58/0e/8c2a03d518fb6bd0b6b0d4b114c63d5f1db01ff0f9925d8eb10960d01c01/scikit_learn-1.7.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7a58814265dfc52b3295b1900cfb5701589d30a8bb026c7540f1e9d3499d5ec8", size = 9660938, upload-time = "2025-09-09T08:20:24.327Z" }, + { url = "https://files.pythonhosted.org/packages/2b/75/4311605069b5d220e7cf5adabb38535bd96f0079313cdbb04b291479b22a/scikit_learn-1.7.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a847fea807e278f821a0406ca01e387f97653e284ecbd9750e3ee7c90347f18", size = 9477818, upload-time = "2025-09-09T08:20:26.845Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9b/87961813c34adbca21a6b3f6b2bea344c43b30217a6d24cc437c6147f3e8/scikit_learn-1.7.2-cp310-cp310-win_amd64.whl", hash = "sha256:ca250e6836d10e6f402436d6463d6c0e4d8e0234cfb6a9a47835bd392b852ce5", size = 8886969, upload-time = "2025-09-09T08:20:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/2f/4966032c5f8cc7e6a60f1b2e0ad686293b9474b65246b0c642e3ef3badd0/scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c", size = 38702770, upload-time = "2025-05-08T16:04:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/0c3bf90fae0e910c274db43304ebe25a6b391327f3f10b5dcc638c090795/scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253", size = 30094511, upload-time = "2025-05-08T16:04:27.103Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b1/4deb37252311c1acff7f101f6453f0440794f51b6eacb1aad4459a134081/scipy-1.15.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:aef683a9ae6eb00728a542b796f52a5477b78252edede72b8327a886ab63293f", size = 22368151, upload-time = "2025-05-08T16:04:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/f457626e3cd3c29b3a49ca115a304cebb8cc6f31b04678f03b216899d3c6/scipy-1.15.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:1c832e1bd78dea67d5c16f786681b28dd695a8cb1fb90af2e27580d3d0967e92", size = 25121732, upload-time = "2025-05-08T16:04:36.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/0a/92b1de4a7adc7a15dcf5bddc6e191f6f29ee663b30511ce20467ef9b82e4/scipy-1.15.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:263961f658ce2165bbd7b99fa5135195c3a12d9bef045345016b8b50c315cb82", size = 35547617, upload-time = "2025-05-08T16:04:43.546Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6d/41991e503e51fc1134502694c5fa7a1671501a17ffa12716a4a9151af3df/scipy-1.15.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2abc762b0811e09a0d3258abee2d98e0c703eee49464ce0069590846f31d40", size = 37662964, upload-time = "2025-05-08T16:04:49.431Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/3df8f83cb15f3500478c889be8fb18700813b95e9e087328230b98d547ff/scipy-1.15.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed7284b21a7a0c8f1b6e5977ac05396c0d008b89e05498c8b7e8f4a1423bba0e", size = 37238749, upload-time = "2025-05-08T16:04:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/b3257cf446f2a3533ed7809757039016b74cd6f38271de91682aa844cfc5/scipy-1.15.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5380741e53df2c566f4d234b100a484b420af85deb39ea35a1cc1be84ff53a5c", size = 40022383, upload-time = "2025-05-08T16:05:01.914Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/55bc4881973d3f79b479a5a2e2df61c8c9a04fcb986a213ac9c02cfb659b/scipy-1.15.3-cp310-cp310-win_amd64.whl", hash = "sha256:9d61e97b186a57350f6d6fd72640f9e99d5a4a2b8fbf4b9ee9a841eab327dc13", size = 41259201, upload-time = "2025-05-08T16:05:08.166Z" }, + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "scipy" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, +] + +[[package]] +name = "sentencepiece" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/15/2e7a025fc62d764b151ae6d0f2a92f8081755ebe8d4a64099accc6f77ba6/sentencepiece-0.2.1.tar.gz", hash = "sha256:8138cec27c2f2282f4a34d9a016e3374cd40e5c6e9cb335063db66a0a3b71fad", size = 3228515, upload-time = "2025-08-12T07:00:51.718Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/31/5b7cccb307b485db1a2372d6d2980b0a65d067f8be5ca943a103b4acd5b3/sentencepiece-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e10fa50bdbaa5e2445dbd387979980d391760faf0ec99a09bd7780ff37eaec44", size = 1942557, upload-time = "2025-08-12T06:59:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/1f/41/0ac923a8e685ad290c5afc8ae55c5844977b8d75076fcc04302b9a324274/sentencepiece-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f27ae6deea72efdb6f361750c92f6c21fd0ad087445082770cc34015213c526", size = 1325384, upload-time = "2025-08-12T06:59:14.334Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ef/3751555d67daf9003384978f169d31c775cb5c7baf28633caaf1eb2b2b4d/sentencepiece-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60937c959e6f44159fdd9f56fbdd302501f96114a5ba436829496d5f32d8de3f", size = 1253317, upload-time = "2025-08-12T06:59:16.247Z" }, + { url = "https://files.pythonhosted.org/packages/46/a5/742c69b7bd144eb32b6e5fd50dbd8abbbc7a95fce2fe16e50156fa400e3b/sentencepiece-0.2.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8b1d91545578852f128650b8cce4ec20f93d39b378ff554ebe66290f2dabb92", size = 1316379, upload-time = "2025-08-12T06:59:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/8deeafbba2871e8fa10f20f17447786f4ac38085925335728d360eaf4cae/sentencepiece-0.2.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27e38eee653abc3d387862e67bc5c8b6f428cd604e688b85d29170b7e725c26c", size = 1387926, upload-time = "2025-08-12T06:59:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ca/67fe73005f0ab617c6a970b199754e28e524b6873aa7025224fad3cda252/sentencepiece-0.2.1-cp310-cp310-win32.whl", hash = "sha256:251874d720ac7f28024a168501f3c7bb15d1802245f6e66de565f18bbb9b5eaa", size = 999550, upload-time = "2025-08-12T06:59:20.844Z" }, + { url = "https://files.pythonhosted.org/packages/6d/33/dc5b54042050d2dda4229c3ce1f862541c99966390b6aa20f54d520d2dc2/sentencepiece-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:e52144670738b4b477fade6c2a9b6af71a8d0094514c9853ac9f6fc1fcfabae7", size = 1054613, upload-time = "2025-08-12T06:59:22.255Z" }, + { url = "https://files.pythonhosted.org/packages/fa/19/1ea47f46ff97fe04422b78997da1a37cd632f414aae042d27a9009c5b733/sentencepiece-0.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:9076430ac25dfa7147d9d05751dbc66a04bc1aaac371c07f84952979ea59f0d0", size = 1033884, upload-time = "2025-08-12T06:59:24.194Z" }, + { url = "https://files.pythonhosted.org/packages/d8/15/46afbab00733d81788b64be430ca1b93011bb9388527958e26cc31832de5/sentencepiece-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6356d0986b8b8dc351b943150fcd81a1c6e6e4d439772e8584c64230e58ca987", size = 1942560, upload-time = "2025-08-12T06:59:25.82Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/7c01b8ef98a0567e9d84a4e7a910f8e7074fcbf398a5cd76f93f4b9316f9/sentencepiece-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f8ba89a3acb3dc1ae90f65ec1894b0b9596fdb98ab003ff38e058f898b39bc7", size = 1325385, upload-time = "2025-08-12T06:59:27.722Z" }, + { url = "https://files.pythonhosted.org/packages/bb/88/2b41e07bd24f33dcf2f18ec3b74247aa4af3526bad8907b8727ea3caba03/sentencepiece-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02593eca45440ef39247cee8c47322a34bdcc1d8ae83ad28ba5a899a2cf8d79a", size = 1253319, upload-time = "2025-08-12T06:59:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/a0/54/38a1af0c6210a3c6f95aa46d23d6640636d020fba7135cd0d9a84ada05a7/sentencepiece-0.2.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a0d15781a171d188b661ae4bde1d998c303f6bd8621498c50c671bd45a4798e", size = 1316162, upload-time = "2025-08-12T06:59:30.914Z" }, + { url = "https://files.pythonhosted.org/packages/ef/66/fb191403ade791ad2c3c1e72fe8413e63781b08cfa3aa4c9dfc536d6e795/sentencepiece-0.2.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f5a3e0d9f445ed9d66c0fec47d4b23d12cfc858b407a03c194c1b26c2ac2a63", size = 1387785, upload-time = "2025-08-12T06:59:32.491Z" }, + { url = "https://files.pythonhosted.org/packages/a9/2d/3bd9b08e70067b2124518b308db6a84a4f8901cc8a4317e2e4288cdd9b4d/sentencepiece-0.2.1-cp311-cp311-win32.whl", hash = "sha256:6d297a1748d429ba8534eebe5535448d78b8acc32d00a29b49acf28102eeb094", size = 999555, upload-time = "2025-08-12T06:59:34.475Z" }, + { url = "https://files.pythonhosted.org/packages/32/b8/f709977f5fda195ae1ea24f24e7c581163b6f142b1005bc3d0bbfe4d7082/sentencepiece-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:82d9ead6591015f009cb1be1cb1c015d5e6f04046dbb8c9588b931e869a29728", size = 1054617, upload-time = "2025-08-12T06:59:36.461Z" }, + { url = "https://files.pythonhosted.org/packages/7a/40/a1fc23be23067da0f703709797b464e8a30a1c78cc8a687120cd58d4d509/sentencepiece-0.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:39f8651bd10974eafb9834ce30d9bcf5b73e1fc798a7f7d2528f9820ca86e119", size = 1033877, upload-time = "2025-08-12T06:59:38.391Z" }, + { url = "https://files.pythonhosted.org/packages/4a/be/32ce495aa1d0e0c323dcb1ba87096037358edee539cac5baf8755a6bd396/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57cae326c8727de58c85977b175af132a7138d84c764635d7e71bbee7e774133", size = 1943152, upload-time = "2025-08-12T06:59:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/88/7e/ff23008899a58678e98c6ff592bf4d368eee5a71af96d0df6b38a039dd4f/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56dd39a3c4d6493db3cdca7e8cc68c6b633f0d4195495cbadfcf5af8a22d05a6", size = 1325651, upload-time = "2025-08-12T06:59:41.536Z" }, + { url = "https://files.pythonhosted.org/packages/19/84/42eb3ce4796777a1b5d3699dfd4dca85113e68b637f194a6c8d786f16a04/sentencepiece-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9381351182ff9888cc80e41c632e7e274b106f450de33d67a9e8f6043da6f76", size = 1253645, upload-time = "2025-08-12T06:59:42.903Z" }, + { url = "https://files.pythonhosted.org/packages/89/fa/d3d5ebcba3cb9e6d3775a096251860c41a6bc53a1b9461151df83fe93255/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99f955df238021bf11f0fc37cdb54fd5e5b5f7fd30ecc3d93fb48b6815437167", size = 1316273, upload-time = "2025-08-12T06:59:44.476Z" }, + { url = "https://files.pythonhosted.org/packages/04/88/14f2f4a2b922d8b39be45bf63d79e6cd3a9b2f248b2fcb98a69b12af12f5/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdfecef430d985f1c2bcbfff3defd1d95dae876fbd0173376012d2d7d24044b", size = 1387881, upload-time = "2025-08-12T06:59:46.09Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b8/903e5ccb77b4ef140605d5d71b4f9e0ad95d456d6184688073ed11712809/sentencepiece-0.2.1-cp312-cp312-win32.whl", hash = "sha256:a483fd29a34c3e34c39ac5556b0a90942bec253d260235729e50976f5dba1068", size = 999540, upload-time = "2025-08-12T06:59:48.023Z" }, + { url = "https://files.pythonhosted.org/packages/2d/81/92df5673c067148c2545b1bfe49adfd775bcc3a169a047f5a0e6575ddaca/sentencepiece-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4cdc7c36234fda305e85c32949c5211faaf8dd886096c7cea289ddc12a2d02de", size = 1054671, upload-time = "2025-08-12T06:59:49.895Z" }, + { url = "https://files.pythonhosted.org/packages/fe/02/c5e3bc518655d714622bec87d83db9cdba1cd0619a4a04e2109751c4f47f/sentencepiece-0.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:daeb5e9e9fcad012324807856113708614d534f596d5008638eb9b40112cd9e4", size = 1033923, upload-time = "2025-08-12T06:59:51.952Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4a/85fbe1706d4d04a7e826b53f327c4b80f849cf1c7b7c5e31a20a97d8f28b/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dcd8161eee7b41aae57ded06272905dbd680a0a04b91edd0f64790c796b2f706", size = 1943150, upload-time = "2025-08-12T06:59:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/c2/83/4cfb393e287509fc2155480b9d184706ef8d9fa8cbf5505d02a5792bf220/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c6c8f42949f419ff8c7e9960dbadcfbc982d7b5efc2f6748210d3dd53a7de062", size = 1325651, upload-time = "2025-08-12T06:59:55.073Z" }, + { url = "https://files.pythonhosted.org/packages/8d/de/5a007fb53b1ab0aafc69d11a5a3dd72a289d5a3e78dcf2c3a3d9b14ffe93/sentencepiece-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:097f3394e99456e9e4efba1737c3749d7e23563dd1588ce71a3d007f25475fff", size = 1253641, upload-time = "2025-08-12T06:59:56.562Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d2/f552be5928105588f4f4d66ee37dd4c61460d8097e62d0e2e0eec41bc61d/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b670879c370d350557edabadbad1f6561a9e6968126e6debca4029e5547820", size = 1316271, upload-time = "2025-08-12T06:59:58.109Z" }, + { url = "https://files.pythonhosted.org/packages/96/df/0cfe748ace5485be740fed9476dee7877f109da32ed0d280312c94ec259f/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7f0fd2f2693309e6628aeeb2e2faf6edd221134dfccac3308ca0de01f8dab47", size = 1387882, upload-time = "2025-08-12T07:00:00.701Z" }, + { url = "https://files.pythonhosted.org/packages/ac/dd/f7774d42a881ced8e1739f393ab1e82ece39fc9abd4779e28050c2e975b5/sentencepiece-0.2.1-cp313-cp313-win32.whl", hash = "sha256:92b3816aa2339355fda2c8c4e021a5de92180b00aaccaf5e2808972e77a4b22f", size = 999541, upload-time = "2025-08-12T07:00:02.709Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e9/932b9eae6fd7019548321eee1ab8d5e3b3d1294df9d9a0c9ac517c7b636d/sentencepiece-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:10ed3dab2044c47f7a2e7b4969b0c430420cdd45735d78c8f853191fa0e3148b", size = 1054669, upload-time = "2025-08-12T07:00:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/c9/3a/76488a00ea7d6931689cda28726a1447d66bf1a4837943489314593d5596/sentencepiece-0.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac650534e2251083c5f75dde4ff28896ce7c8904133dc8fef42780f4d5588fcd", size = 1033922, upload-time = "2025-08-12T07:00:06.496Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b6/08fe2ce819e02ccb0296f4843e3f195764ce9829cbda61b7513f29b95718/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8dd4b477a7b069648d19363aad0cab9bad2f4e83b2d179be668efa672500dc94", size = 1946052, upload-time = "2025-08-12T07:00:08.136Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d9/1ea0e740591ff4c6fc2b6eb1d7510d02f3fb885093f19b2f3abd1363b402/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c0f672da370cc490e4c59d89e12289778310a0e71d176c541e4834759e1ae07", size = 1327408, upload-time = "2025-08-12T07:00:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/1fb26e8a21613f6200e1ab88824d5d203714162cf2883248b517deb500b7/sentencepiece-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad8493bea8432dae8d6830365352350f3b4144415a1d09c4c8cb8d30cf3b6c3c", size = 1254857, upload-time = "2025-08-12T07:00:11.021Z" }, + { url = "https://files.pythonhosted.org/packages/bc/85/c72fd1f3c7a6010544d6ae07f8ddb38b5e2a7e33bd4318f87266c0bbafbf/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b81a24733726e3678d2db63619acc5a8dccd074f7aa7a54ecd5ca33ca6d2d596", size = 1315722, upload-time = "2025-08-12T07:00:12.989Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e8/661e5bd82a8aa641fd6c1020bd0e890ef73230a2b7215ddf9c8cd8e941c2/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a81799d0a68d618e89063fb423c3001a034c893069135ffe51fee439ae474d6", size = 1387452, upload-time = "2025-08-12T07:00:15.088Z" }, + { url = "https://files.pythonhosted.org/packages/99/5e/ae66c361023a470afcbc1fbb8da722c72ea678a2fcd9a18f1a12598c7501/sentencepiece-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:89a3ea015517c42c0341d0d962f3e6aaf2cf10d71b1932d475c44ba48d00aa2b", size = 1002501, upload-time = "2025-08-12T07:00:16.966Z" }, + { url = "https://files.pythonhosted.org/packages/c1/03/d332828c4ff764e16c1b56c2c8f9a33488bbe796b53fb6b9c4205ddbf167/sentencepiece-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:33f068c9382dc2e7c228eedfd8163b52baa86bb92f50d0488bf2b7da7032e484", size = 1057555, upload-time = "2025-08-12T07:00:18.573Z" }, + { url = "https://files.pythonhosted.org/packages/88/14/5aee0bf0864df9bd82bd59e7711362908e4935e3f9cdc1f57246b5d5c9b9/sentencepiece-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b3616ad246f360e52c85781e47682d31abfb6554c779e42b65333d4b5f44ecc0", size = 1036042, upload-time = "2025-08-12T07:00:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/24/9c/89eb8b2052f720a612478baf11c8227dcf1dc28cd4ea4c0c19506b5af2a2/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5d0350b686c320068702116276cfb26c066dc7e65cfef173980b11bb4d606719", size = 1943147, upload-time = "2025-08-12T07:00:21.809Z" }, + { url = "https://files.pythonhosted.org/packages/82/0b/a1432bc87f97c2ace36386ca23e8bd3b91fb40581b5e6148d24b24186419/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c7f54a31cde6fa5cb030370566f68152a742f433f8d2be458463d06c208aef33", size = 1325624, upload-time = "2025-08-12T07:00:23.289Z" }, + { url = "https://files.pythonhosted.org/packages/ea/99/bbe054ebb5a5039457c590e0a4156ed073fb0fe9ce4f7523404dd5b37463/sentencepiece-0.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c83b85ab2d6576607f31df77ff86f28182be4a8de6d175d2c33ca609925f5da1", size = 1253670, upload-time = "2025-08-12T07:00:24.69Z" }, + { url = "https://files.pythonhosted.org/packages/19/ad/d5c7075f701bd97971d7c2ac2904f227566f51ef0838dfbdfdccb58cd212/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1855f57db07b51fb51ed6c9c452f570624d2b169b36f0f79ef71a6e6c618cd8b", size = 1316247, upload-time = "2025-08-12T07:00:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/fb/03/35fbe5f3d9a7435eebd0b473e09584bd3cc354ce118b960445b060d33781/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01e6912125cb45d3792f530a4d38f8e21bf884d6b4d4ade1b2de5cf7a8d2a52b", size = 1387894, upload-time = "2025-08-12T07:00:28.339Z" }, + { url = "https://files.pythonhosted.org/packages/dc/aa/956ef729aafb6c8f9c443104c9636489093bb5c61d6b90fc27aa1a865574/sentencepiece-0.2.1-cp314-cp314-win32.whl", hash = "sha256:c415c9de1447e0a74ae3fdb2e52f967cb544113a3a5ce3a194df185cbc1f962f", size = 1096698, upload-time = "2025-08-12T07:00:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cb/fe400d8836952cc535c81a0ce47dc6875160e5fedb71d2d9ff0e9894c2a6/sentencepiece-0.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:881b2e44b14fc19feade3cbed314be37de639fc415375cefaa5bc81a4be137fd", size = 1155115, upload-time = "2025-08-12T07:00:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/32/89/047921cf70f36c7b6b6390876b2399b3633ab73b8d0cb857e5a964238941/sentencepiece-0.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:2005242a16d2dc3ac5fe18aa7667549134d37854823df4c4db244752453b78a8", size = 1133890, upload-time = "2025-08-12T07:00:34.763Z" }, + { url = "https://files.pythonhosted.org/packages/a1/11/5b414b9fae6255b5fb1e22e2ed3dc3a72d3a694e5703910e640ac78346bb/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a19adcec27c524cb7069a1c741060add95f942d1cbf7ad0d104dffa0a7d28a2b", size = 1946081, upload-time = "2025-08-12T07:00:36.97Z" }, + { url = "https://files.pythonhosted.org/packages/77/eb/7a5682bb25824db8545f8e5662e7f3e32d72a508fdce086029d89695106b/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e37e4b4c4a11662b5db521def4e44d4d30ae69a1743241412a93ae40fdcab4bb", size = 1327406, upload-time = "2025-08-12T07:00:38.669Z" }, + { url = "https://files.pythonhosted.org/packages/03/b0/811dae8fb9f2784e138785d481469788f2e0d0c109c5737372454415f55f/sentencepiece-0.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:477c81505db072b3ab627e7eab972ea1025331bd3a92bacbf798df2b75ea86ec", size = 1254846, upload-time = "2025-08-12T07:00:40.611Z" }, + { url = "https://files.pythonhosted.org/packages/ef/23/195b2e7ec85ebb6a547969f60b723c7aca5a75800ece6cc3f41da872d14e/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:010f025a544ef770bb395091d57cb94deb9652d8972e0d09f71d85d5a0816c8c", size = 1315721, upload-time = "2025-08-12T07:00:42.914Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/553dbe4178b5f23eb28e59393dddd64186178b56b81d9b8d5c3ff1c28395/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:733e59ff1794d26db706cd41fc2d7ca5f6c64a820709cb801dc0ea31780d64ab", size = 1387458, upload-time = "2025-08-12T07:00:44.56Z" }, + { url = "https://files.pythonhosted.org/packages/66/7c/08ff0012507297a4dd74a5420fdc0eb9e3e80f4e88cab1538d7f28db303d/sentencepiece-0.2.1-cp314-cp314t-win32.whl", hash = "sha256:d3233770f78e637dc8b1fda2cd7c3b99ec77e7505041934188a4e7fe751de3b0", size = 1099765, upload-time = "2025-08-12T07:00:46.058Z" }, + { url = "https://files.pythonhosted.org/packages/91/d5/2a69e1ce15881beb9ddfc7e3f998322f5cedcd5e4d244cb74dade9441663/sentencepiece-0.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e4366c97b68218fd30ea72d70c525e6e78a6c0a88650f57ac4c43c63b234a9d", size = 1157807, upload-time = "2025-08-12T07:00:47.673Z" }, + { url = "https://files.pythonhosted.org/packages/f3/16/54f611fcfc2d1c46cbe3ec4169780b2cfa7cf63708ef2b71611136db7513/sentencepiece-0.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:105e36e75cbac1292642045458e8da677b2342dcd33df503e640f0b457cb6751", size = 1136264, upload-time = "2025-08-12T07:00:49.485Z" }, +] + +[[package]] +name = "sentry-sdk" +version = "2.64.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/31/b7341f156a5f6f36f0b4845d6f1c28a2ae4799171dba7007f3a1e9b234b4/sentry_sdk-2.64.0.tar.gz", hash = "sha256:68be2c29e14ae310f8a39e1a79916b6d85c6cb41dcce789d14ff05fe293e4c55", size = 921020, upload-time = "2026-06-30T08:13:47.682Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/a8/3fb9a4319efa3b26f5be0e90e6d8918df43fa7c7e977d26390f589501d82/sentry_sdk-2.64.0-py3-none-any.whl", hash = "sha256:715ea91ca860a819e8d8a50a7bde3a80d0df3b4ed7b6660a20fb9a2d084188f1", size = 498901, upload-time = "2026-06-30T08:13:45.566Z" }, +] + +[[package]] +name = "setuptools" +version = "81.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, +] + +[[package]] +name = "shap" +version = "0.46.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "cloudpickle", marker = "(python_full_version < '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numba", version = "0.47.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numba", version = "0.66.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "(python_full_version < '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas", marker = "(python_full_version < '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-learn", marker = "(python_full_version < '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "slicer", marker = "(python_full_version < '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tqdm", marker = "(python_full_version < '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/46/1b497452be642e19af56044814dfe32ee795805b443378821136729017a0/shap-0.46.0.tar.gz", hash = "sha256:bdaa5b098be5a958348015e940f6fd264339b5db1e651f9898a3117be95b05a0", size = 1214102, upload-time = "2024-06-27T10:17:22.263Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a8/97442ec8e7aaad01d860768232b3b7051adb0560a9c79e52ce5e1222cbf1/shap-0.46.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:905b2d7a0262ef820785a7c0e3c7f24c9d281e6f934edb65cbe811fe0e971187", size = 459332, upload-time = "2024-06-27T10:16:34.71Z" }, + { url = "https://files.pythonhosted.org/packages/00/b3/2795a586a4446c8cbf04b6e8f15c19b4a6fb867e5c6cf9fcbca97d56a20b/shap-0.46.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bccbb30ffbf8b9ed53e476d0c1319fdfcbeac455fe9df277fb0d570d92790e80", size = 455839, upload-time = "2024-06-27T10:16:37.654Z" }, + { url = "https://files.pythonhosted.org/packages/13/a6/b75760a52664dd82d530f9e232918bb74d1d6c39abcf34523c4f75cd4264/shap-0.46.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9633d3d7174acc01455538169ca6e6344f570530384548631aeadcf7bfdaaaea", size = 540067, upload-time = "2024-06-27T10:16:39.713Z" }, + { url = "https://files.pythonhosted.org/packages/35/13/70e07364855b05d8aa628ec5aec4f038444ede0e26eee2be00c38077ee72/shap-0.46.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6097eb2ab7e8c194254bac3e462266490fbdd43bfe35a1014e9ee21c4ef10ee", size = 537808, upload-time = "2024-06-27T10:16:41.955Z" }, + { url = "https://files.pythonhosted.org/packages/b4/fc/dd28e6838630cd436914116aa07a019753a40b956a05831b71bd3f7ce914/shap-0.46.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0cf7c6e3f056cf3bfd16bcfd5744d0cc25b851555b1e750a3ab889b3077d2d05", size = 1538235, upload-time = "2024-06-27T10:16:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/ae/fe/f9e4d5e002bb58047c81edb6448579c179925c3807c98589ee70953587ab/shap-0.46.0-cp310-cp310-win_amd64.whl", hash = "sha256:949bd7fa40371c3f1885a30ae0611dd481bf4ac90066ff726c73cb5bb393032b", size = 456103, upload-time = "2024-06-27T10:16:46.764Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a1/43bd69f32ddf381a09de18ea94d4b215d5ced3a24ff1a7b7d1a9401b5b85/shap-0.46.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f18217c98f39fd485d541f6aab0b860b3be74b69b21d4faf11959e3fcba765c5", size = 459333, upload-time = "2024-06-27T10:16:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9e/dce41d5ec9e79add65faf4381d8d4492247b29daaa6cc7d7fd0298abc1e2/shap-0.46.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5bbdae4489577c6fce1cfe2d9d8f3d5b96d69284d29645fe651f78f6e965aeb4", size = 455835, upload-time = "2024-06-27T10:16:51.074Z" }, + { url = "https://files.pythonhosted.org/packages/06/6a/09e3cb9864118337c0f3c2a0dc5add6b642e9f672665062e186d67ba992d/shap-0.46.0-cp311-cp311-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13d36dc58d1e8c010feb4e7da71c77d23626a52d12d16b02869e793b11be4695", size = 540163, upload-time = "2024-06-27T10:16:53.179Z" }, + { url = "https://files.pythonhosted.org/packages/c3/74/440eacbdf21c1b2e0a5b6962b79d4435e56a88588043d144a16c7785a596/shap-0.46.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70e06fdfdf53d5fb932c82f4529397552b262e0ccce734f5226fb1e1eab2bc3e", size = 537765, upload-time = "2024-06-27T10:16:54.763Z" }, + { url = "https://files.pythonhosted.org/packages/08/e6/027ca36efcc8871eda4084bde5e4658a90e84006086186e39588fd03b396/shap-0.46.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:943f0806fa00b4fafb174f172a73d88de2d8600e6d69c2e2bff833f00e6c4c21", size = 1538290, upload-time = "2024-06-27T10:16:56.819Z" }, + { url = "https://files.pythonhosted.org/packages/82/29/923869e92c74bf07ec2b9a52ad5ac67d4184c873ba33ada7d4584356463a/shap-0.46.0-cp311-cp311-win_amd64.whl", hash = "sha256:c972a2efdc9fc00d543efaa55805eca947b8c418d065962d967824c2d5d295d0", size = 456103, upload-time = "2024-06-27T10:16:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/05/c5/3c4fe600dd71fd2785d21f86a3e7f1f13de60c9b434052e05ba17598f81e/shap-0.46.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a9cc9be191562bea1a782baff912854d267c6f4831bbf454d8d7bb7df7ddb214", size = 459316, upload-time = "2024-06-27T10:17:00.313Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1a/c00a1e7a68a4af29f2b40c8a8740dd241cba6cc58cd6ac266956a954a41d/shap-0.46.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab1fecfb43604605be17e26ae12bde4406c451c46b54b980d9570cec03fbc239", size = 455333, upload-time = "2024-06-27T10:17:02.719Z" }, + { url = "https://files.pythonhosted.org/packages/7f/0a/e3ab0dcddf4db1158fbf0d6c96348ba5f3031275f59088e0e3b7630cdcde/shap-0.46.0-cp312-cp312-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b216adf2a17b0e0694f17965ac29354ca8c4f27ac3c66f68bf6fc4cb2aa28207", size = 543894, upload-time = "2024-06-27T10:17:04.941Z" }, + { url = "https://files.pythonhosted.org/packages/8f/8f/ca077689b76161b51b420031b88948ef92ade55730e85490215222734729/shap-0.46.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b6e5dc5257b747a784f7a9b3acb64216a9011f01734f3c96b27fe5e15ae5f99f", size = 540735, upload-time = "2024-06-27T10:17:06.61Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b6/169de0d8971c91decd3dacfd63edeeedfc1bba30bfc6abf8480142aafd48/shap-0.46.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1230bf973463041dfa15734f290fbf3ab9c6e4e8222339c76f68fc355b940d80", size = 1537953, upload-time = "2024-06-27T10:17:08.225Z" }, + { url = "https://files.pythonhosted.org/packages/04/58/b2ea558ec8d9ed3728e83dfacb1b920c54a1a1f6feee2632c04676c3c1e9/shap-0.46.0-cp312-cp312-win_amd64.whl", hash = "sha256:0cbbf996537b2a42d3bc7f2a13492988822ee1bfd7220700989408dfb9e1c5ad", size = 456226, upload-time = "2024-06-27T10:17:10.589Z" }, +] + +[[package]] +name = "shap" +version = "0.52.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", +] +dependencies = [ + { name = "cloudpickle", marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "llvmlite", marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numba", version = "0.66.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas", marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-learn", marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "slicer", marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tqdm", marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/0a/aa278f42c08cb47f2bb503085be0c521da2886929c6605b6105748a7590f/shap-0.52.0.tar.gz", hash = "sha256:81d4ae478f67f8122de1bb411dc4e3ddff0604cbc27dc9cb8ea66d5c73462fd2", size = 4192842, upload-time = "2026-05-28T14:17:49.011Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/09/61/ddbe6fb40120fc7d3dbd702f5f4e0ef1c0795e39f208afbc928ce46a0246/shap-0.52.0-cp312-abi3-macosx_10_13_x86_64.whl", hash = "sha256:334cdc36a925db2242875f69267b88eb6108ec47a6c259f4f87a6b022b6188dc", size = 496146, upload-time = "2026-05-28T14:17:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/19/d1/b020cb524513496d046a9711ec466c0fdd479b722c09ceb1162c138d0db7/shap-0.52.0-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a1116361c01fc5a045cf34681673f6c79100e2e648a6fbde7da084d18193d2e", size = 490868, upload-time = "2026-05-28T14:17:35.075Z" }, + { url = "https://files.pythonhosted.org/packages/88/58/6e7f8d13b6078485a4bc3c5e6ae97ef52c9208315206982fd0fedabe2db4/shap-0.52.0-cp312-abi3-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61e431aed9de5f2deae1b7847b00edd739b8d85df9c4d04b137230d20dbbd4d3", size = 495268, upload-time = "2026-05-28T14:17:36.312Z" }, + { url = "https://files.pythonhosted.org/packages/fe/1c/8aad9c7cc4c09a3841496def5434f56d1b172ad55dbb42fc839c25798f1c/shap-0.52.0-cp312-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b3d09fdff3e94e418abec1f6033522189e4ca554f1014a2b320f80b5454ad2", size = 498042, upload-time = "2026-05-28T14:17:37.664Z" }, + { url = "https://files.pythonhosted.org/packages/8e/78/f8f86c768a2fae213d99721666eb01653347b1398cf6e24afd84743899a8/shap-0.52.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6b843d61e18ad4659584e004a4e681fff0648d4cf371830e3019709722190467", size = 1570560, upload-time = "2026-05-28T14:17:39.058Z" }, + { url = "https://files.pythonhosted.org/packages/5d/20/f5824640d8e7bf6bffb2ed8f6221c8c6fb2d39b638ba72f01b60e934e40f/shap-0.52.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2828d18366db812599a5d8340ce93fed9c95d97337d90568597b2e96d01ce516", size = 1627401, upload-time = "2026-05-28T14:17:40.435Z" }, + { url = "https://files.pythonhosted.org/packages/58/bf/6be16d28ef1b6ff69078a1d7ea58892e9d40a4680c1077563f74ebd31c9e/shap-0.52.0-cp312-abi3-win_amd64.whl", hash = "sha256:07d44ace491ca6204dac6ce4fda128bcaeff27553a279bca67c55a14987cc957", size = 499853, upload-time = "2026-05-28T14:17:41.786Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bc/677a58515255ed9d418f35406f42bb73434481d533b3408f9aba787e7a39/shap-0.52.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:514140e2b89f8d4600637cdff8242ac194b96f8ef820b06ab6584d105c1f9b68", size = 494655, upload-time = "2026-05-28T14:17:43.24Z" }, + { url = "https://files.pythonhosted.org/packages/28/59/e59a449a53217840fa21c7072d6535d85805b77c9f27ab10cedba8e3d5ca/shap-0.52.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb34bf01572048213d60d75d9329c62112d77a5190c18c05f572044dda8e0d83", size = 499315, upload-time = "2026-05-28T14:17:44.528Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7f/60c047319846fd144bb9ed1adcb1121a3ff1f36158847d0dfe9c94c7f241/shap-0.52.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:09fca6cc9d8d4b9921bf6b56d381a5df0e26cb1b3cd572b2b2f6e48cf9c5d4ad", size = 1576710, upload-time = "2026-05-28T14:17:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/63/90/e676b9315bee9f6278ac6662c276710c41a3c5b8fb6c21aa89240e04356e/shap-0.52.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59b201ef00ce38359997eb9363ff561c89a98461bd81d9fdae8b5350a176eee5", size = 1633807, upload-time = "2026-05-28T14:17:47.544Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sklearn-compat" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "scikit-learn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/7e/302cb51f8735bad67f5ce088d027a1e299789d8555967c9642656fa36da0/sklearn_compat-0.1.6.tar.gz", hash = "sha256:8fd4731b4f709b66641b8f49c954dafec7e3b60afc48f2cfd298356c713277c6", size = 178018, upload-time = "2026-06-07T19:00:28.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/20/47a7e947757008be1b77f1c6a6861d26a84ef4b4ea3e6ebf4eff24f24d5d/sklearn_compat-0.1.6-py3-none-any.whl", hash = "sha256:b555db6c09d21eb50ee4a767dc08478a865f33f0e42b3ff8fc33f33c616bd7c1", size = 22868, upload-time = "2026-06-07T19:00:27.242Z" }, +] + +[[package]] +name = "slicer" +version = "0.0.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/f9/b4bce2825b39b57760b361e6131a3dacee3d8951c58cb97ad120abb90317/slicer-0.0.8.tar.gz", hash = "sha256:2e7553af73f0c0c2d355f4afcc3ecf97c6f2156fcf4593955c3f56cf6c4d6eb7", size = 14894, upload-time = "2024-03-09T23:35:26.826Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/81/9ef641ff4e12cbcca30e54e72fb0951a2ba195d0cda0ba4100e532d929db/slicer-0.0.8-py3-none-any.whl", hash = "sha256:6c206258543aecd010d497dc2eca9d2805860a0b3758673903456b7df7934dc3", size = 15251, upload-time = "2024-03-09T07:03:07.708Z" }, +] + +[[package]] +name = "smart-open" +version = "8.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/3e/79fd5fd2375a8a500b9ec2f6a0762fc1ac33e35582d4a87483a78d19408f/smart_open-8.0.0.tar.gz", hash = "sha256:5a2008d60688bd3b33c52e2ef666d3c60cf956e73e215de8c7b242cf56fdd1b2", size = 61520, upload-time = "2026-06-27T16:28:11.894Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/bd/1c92e69a1daff70118f21e18ef3a100c114f00f08b64a1074484f12d9020/smart_open-8.0.0-py3-none-any.whl", hash = "sha256:ff4f395c9e86f23e27771dc4ba756ad4bd145f181859a782c50d64168485761b", size = 73029, upload-time = "2026-06-27T16:28:10.589Z" }, +] + +[[package]] +name = "snowballstemmer" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/f8/0a71edf031f03c40db17503cb8ca78a69a171254e568e7db241b0ab57ea1/snowballstemmer-3.1.1.tar.gz", hash = "sha256:e07bbc54a0d798fe6010a12398422e62a8bfbba95c394fd0956ef58cb4d3e260", size = 123314, upload-time = "2026-06-03T00:56:40.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/07/2ebca9b11fb9be7340a818d8d6f63feaebb146be2c4afbd6061701d6df6e/snowballstemmer-3.1.1-py3-none-any.whl", hash = "sha256:7e207fa178741da09cdee59d3ecec3827ad5f92b1fc5c9ff3755b639f71f5752", size = 104164, upload-time = "2026-06-03T00:56:38.614Z" }, +] + +[[package]] +name = "sphinx" +version = "8.1.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "babel", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "colorama", marker = "(python_full_version < '3.11' and sys_platform == 'win32') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "imagesize", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pygments", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "snowballstemmer", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tomli", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/be0b61178fe2cdcb67e2a92fc9ebb488e3c51c4f74a36a7824c0adf23425/sphinx-8.1.3.tar.gz", hash = "sha256:43c1911eecb0d3e161ad78611bc905d1ad0e523e4ddc202a58a821773dc4c927", size = 8184611, upload-time = "2024-10-13T20:27:13.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/60/1ddff83a56d33aaf6f10ec8ce84b4c007d9368b21008876fceda7e7381ef/sphinx-8.1.3-py3-none-any.whl", hash = "sha256:09719015511837b76bf6e03e42eb7595ac8c2e41eeb9c29c5b755c6b677992a2", size = 3487125, upload-time = "2024-10-13T20:27:10.448Z" }, +] + +[[package]] +name = "sphinx" +version = "9.0.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "babel", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "colorama", marker = "(python_full_version == '3.11.*' and sys_platform == 'win32') or (python_full_version != '3.11.*' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "imagesize", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pygments", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "roman-numerals", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "snowballstemmer", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" }, +] + +[[package]] +name = "sphinx" +version = "9.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", +] +dependencies = [ + { name = "alabaster", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "babel", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "colorama", marker = "(python_full_version >= '3.12' and sys_platform == 'win32') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "imagesize", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pygments", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "requests", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "roman-numerals", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "snowballstemmer", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" }, +] + +[[package]] +name = "sphinx-rtd-theme" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils", version = "0.21.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "docutils", version = "0.22.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinxcontrib-jquery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/68/a1bfbf38c0f7bccc9b10bbf76b94606f64acb1552ae394f0b8285bfaea25/sphinx_rtd_theme-3.1.0.tar.gz", hash = "sha256:b44276f2c276e909239a4f6c955aa667aaafeb78597923b1c60babc76db78e4c", size = 7620915, upload-time = "2026-01-12T16:03:31.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/c7/b5c8015d823bfda1a346adb2c634a2101d50bb75d421eb6dcb31acd25ebc/sphinx_rtd_theme-3.1.0-py2.py3-none-any.whl", hash = "sha256:1785824ae8e6632060490f67cf3a72d404a85d2d9fc26bce3619944de5682b89", size = 7655617, upload-time = "2026-01-12T16:03:28.101Z" }, +] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" }, +] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" }, +] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" }, +] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/f3/aa67467e051df70a6330fe7770894b3e4f09436dea6881ae0b4f3d87cad8/sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", size = 122331, upload-time = "2023-03-14T15:01:01.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/85/749bd22d1a68db7291c89e2ebca53f4306c3f205853cf31e9de279034c3c/sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae", size = 121104, upload-time = "2023-03-14T15:01:00.356Z" }, +] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" }, +] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" }, +] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/76/b3ea1d8842e7b62c718a88d302809003d65ed82011460ca48907dde658c4/sqlalchemy-2.0.51-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e8203d2fbd5c6254692ef0a72c740d75b2f3c7ca345404f4c1a4604813c77c0", size = 2162087, upload-time = "2026-06-15T16:05:15.795Z" }, + { url = "https://files.pythonhosted.org/packages/6c/22/f19552eb7876774d50cfd025337ef5d67acc10cd8f29adab7716cf47c352/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af05726b3d0cdba1c55284bf408fd3b792e690fe2399bfb8304565551cda652", size = 3244579, upload-time = "2026-06-15T16:10:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/e4a2eb5a8ec5cd3c2a0615a2f15f0afca89ac039229599b9ed0c0ed28e5e/sqlalchemy-2.0.51-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e54ff2dd657f2e3e0fbf2b097db1182f7bfea263eca4353f00065bae2a67c3d", size = 3243515, upload-time = "2026-06-15T16:12:22.627Z" }, + { url = "https://files.pythonhosted.org/packages/74/c6/5900ec624fab3360aa2ec59b99bb2046dd79799e310bb78a0514eaa4038e/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1e47b1199c2e832e325eacabc8d32d2487f58c9358f97e9a00f5eb93c5680d84", size = 3195492, upload-time = "2026-06-15T16:10:38.097Z" }, + { url = "https://files.pythonhosted.org/packages/8f/41/2ee3c4e1ac4fd22309349823fe13f33febeab1a71db1d7e9d60293a07dcb/sqlalchemy-2.0.51-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c68568f3facf8f66fa76c60e0ced69b67666ffa9941d1d0a3756fda196049080", size = 3215782, upload-time = "2026-06-15T16:12:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/ce/1c/3bd72c341f1cb5faed5a7457ea840228a46be51cfbaf31a9db72fc963f11/sqlalchemy-2.0.51-cp310-cp310-win32.whl", hash = "sha256:0592bdadf86ddcabfd72d9ab66ea8a5d8d2cc6be1cc51fa7e66c03868ac5eac1", size = 2122119, upload-time = "2026-06-15T16:13:26.915Z" }, + { url = "https://files.pythonhosted.org/packages/2a/63/b6dfdd646abf91c3bedb13727226a5e765e5f8365e898d43818e6672fa46/sqlalchemy-2.0.51-cp310-cp310-win_amd64.whl", hash = "sha256:740cf6f35351b1ac3d82369152acf1d51d37e3dcf85d4dc0a22ca01410eabe2a", size = 2145158, upload-time = "2026-06-15T16:13:28.386Z" }, + { url = "https://files.pythonhosted.org/packages/3a/69/a67c69e5f28fc9c99d6f7bd60bd50e91f2fed2423e3b30fb228fa00e51f3/sqlalchemy-2.0.51-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa10c0daee6705294d181daadaa793221e1a59ed55000a3fab1d42b088ce4ba", size = 2161838, upload-time = "2026-06-15T16:05:17.144Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/c8c22b8438bddc0a030157c6ec0f6ef97b3c38effa444bdab2a27af04090/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5b2ed6d828f1f09bd812861f4f59ca3bc3803f9df871f4555187f0faf018604", size = 3319402, upload-time = "2026-06-15T16:10:40.002Z" }, + { url = "https://files.pythonhosted.org/packages/90/54/44012d32fd77d991256d2ff793ba3807c51d40cb27a85b4796224f6744df/sqlalchemy-2.0.51-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:436728ce18a80f6951a1e11cc6112c2ede9faf20766f1a26195a7c441ca12dbd", size = 3319675, upload-time = "2026-06-15T16:12:25.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/de0592acaf5906cd7430874392d6f7e8b4a7c8437610953ee2d1501c0b44/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc261707bf5739aea8a541593f3cc1d463c2701fb05fbcbba0ce031b69a21260", size = 3270777, upload-time = "2026-06-15T16:10:42.125Z" }, + { url = "https://files.pythonhosted.org/packages/cb/14/a44c90739c780b362238e4ac3cb19dd0ca40d13e6ddc5daa112166ddab4f/sqlalchemy-2.0.51-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a6d26094615306d116dd5e4a51b0304c99dd2356fc569eed6922a80a6bd3b265", size = 3293940, upload-time = "2026-06-15T16:12:27.156Z" }, + { url = "https://files.pythonhosted.org/packages/65/eb/fbd0f206a330e66f8c602a99c37c4e731f107faed62954b41b01f16dd9d9/sqlalchemy-2.0.51-cp311-cp311-win32.whl", hash = "sha256:ca8435d13829b92f4a97362d91975154a4015db3a2634154e1754e9a915e6b86", size = 2121183, upload-time = "2026-06-15T16:13:29.905Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fd/005bf80f3cf6e5c62b5dd68616280f51cd012c60840fa74781b3ed7b1623/sqlalchemy-2.0.51-cp311-cp311-win_amd64.whl", hash = "sha256:4a011ea4510683319ce4ed274b56ee05194b39b6da9d09ca7a39388f0fa84dcc", size = 2145796, upload-time = "2026-06-15T16:13:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/d5/70/e868bc5412acd101a8280f25c95f10eeae0771c4eb806b02491142810ee8/sqlalchemy-2.0.51-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d78702b26ba1c18b2d0fb2ea940ba7f17a9581b42e8361ff93920ebbee1235a", size = 2160291, upload-time = "2026-06-15T16:08:48.918Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1c/71ee0f8a6b9d7316a1ccd30430b4c62b6c2e36adc96017a4e3a72dce49d6/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581921d849d6e6f994d560389192955e80e2950e18fcdfe2ccea863e01158e6e", size = 3343835, upload-time = "2026-06-15T16:19:42.613Z" }, + { url = "https://files.pythonhosted.org/packages/2b/7c/7ab9f9aadc5944fdd06612484ed7918fe376ad871a5f50404dc1536e0194/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d21ce524ab86c23046e992a5b81cb54c21079c6df6e78b8fc77d77cac70a6b9", size = 3358470, upload-time = "2026-06-15T16:26:38.011Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7d/ff77169fee6186de145a7f2b87006c39638391130abbab2b1f63ac6ea583/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c5d98a2709840027f5a347c3af0a7c3d5f6c1ff93af2ca1c54494e23cba8f389", size = 3289874, upload-time = "2026-06-15T16:19:45.212Z" }, + { url = "https://files.pythonhosted.org/packages/6f/3b/6c505903710d781b55bc3141ee34a062bf9745a6b5bc7333305b9ed63b33/sqlalchemy-2.0.51-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1181256e0f16479691b5616d36375dc2620ad8332b25978763c3d206ad3f3f1d", size = 3321692, upload-time = "2026-06-15T16:26:39.747Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/c5ffe50aa2f4d947c9250e1519d939260329a07fe6272edfccd784b3d007/sqlalchemy-2.0.51-cp312-cp312-win32.whl", hash = "sha256:9f380393be5abeb6815f68fd39271b95127173511b6706b0a630a9995d53f8f5", size = 2119674, upload-time = "2026-06-15T16:23:09.543Z" }, + { url = "https://files.pythonhosted.org/packages/25/dc/46a65916af68a06ef6b972c6050ba4c8f97070fe3fb33097d34229d9bef6/sqlalchemy-2.0.51-cp312-cp312-win_amd64.whl", hash = "sha256:2cf39aabdf48e87c1c2c2ed6d20d33ffa0733b3071ce9c5f66357947dd009080", size = 2146670, upload-time = "2026-06-15T16:23:11.048Z" }, + { url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" }, + { url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" }, + { url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" }, + { url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" }, + { url = "https://files.pythonhosted.org/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" }, + { url = "https://files.pythonhosted.org/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" }, + { url = "https://files.pythonhosted.org/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" }, + { url = "https://files.pythonhosted.org/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" }, + { url = "https://files.pythonhosted.org/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" }, + { url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" }, +] + +[[package]] +name = "sqlalchemy-stubs" +version = "0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/60/db082788267740b17eac2c00666bbea1c8c5a94b569e8b1ea76b0cf42d57/sqlalchemy-stubs-0.4.tar.gz", hash = "sha256:c665d6dd4482ef642f01027fa06c3d5e91befabb219dc71fc2a09e7d7695f7ae", size = 70682, upload-time = "2021-01-12T14:02:04.438Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/ae/cb215ab25b76228bc90c90444b87e323ffba58c212321a53d5bc92903098/sqlalchemy_stubs-0.4-py3-none-any.whl", hash = "sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5", size = 116067, upload-time = "2021-01-12T14:02:02.723Z" }, +] + +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + +[[package]] +name = "stevedore" +version = "5.8.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/88/35e4d27d9177d7df76d060e0a18f69c6c5794c96960c94042e20a12c8ba2/stevedore-5.8.0.tar.gz", hash = "sha256:b49867b32ca3016e94100e68dbf26e72aa7b8708d0a3f73c08aeb220370ac715", size = 514710, upload-time = "2026-05-18T09:15:27.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/ac/19f9941c74add59d17694930ec8105d5eddeee4ce56dd8632b765ca16d6c/stevedore-5.8.0-py3-none-any.whl", hash = "sha256:88eede9e66ca80e34085b9174e2327da2c61ac91f24f70e41c3ad76e4bb4872b", size = 54553, upload-time = "2026-05-18T09:15:25.82Z" }, +] + +[[package]] +name = "stevedore" +version = "5.9.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/dd/04d56c2a5232358df41f3d0f0e31833d378b6c8ed7803a6b1b7867b0eba6/stevedore-5.9.0.tar.gz", hash = "sha256:abbd0af7a38a8bbb1d6adea2e35b17609cf004eaac323e88a8d8963640dd2b3c", size = 514850, upload-time = "2026-07-02T11:38:08.509Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8d/008761f6e1000600e5303db30d05724bdcf3d2d186cbb59fac79b52e39ed/stevedore-5.9.0-py3-none-any.whl", hash = "sha256:e520945d4c257700eddc1eb1d79df04b2ea578eef185e0e3fa5b442fc848d3f7", size = 54463, upload-time = "2026-07-02T11:38:07.43Z" }, +] + +[[package]] +name = "streaming-form-data" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "smart-open" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/fd/d49f3b4e6258e865566fd8aa3da9966f47ca5a7d7fd8ca181f8209010605/streaming_form_data-2.1.0.tar.gz", hash = "sha256:2c5c81fc9c451ea133083bc6da959f87e9b91fba3effe99411f1f90461ea7c5b", size = 150867, upload-time = "2026-06-10T19:35:59.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/18/9bf597fd18a2a16c24981afa6ded6dde18d329c5959bf2d060d695d9a144/streaming_form_data-2.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7efb67a2bf91419468f8c84d89fc997f0443ca7640efd2e4ebf31656253627a6", size = 221853, upload-time = "2026-06-10T19:35:36.694Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/b71dd002d62a80e3f4ebcd6dba42a07ee180b7d800625e4e101b227b5013/streaming_form_data-2.1.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b2269ddc234673d8b99863d0203f12d6127eda96d4c4155ae5728b9862f05c5e", size = 626881, upload-time = "2026-06-10T19:35:38.143Z" }, + { url = "https://files.pythonhosted.org/packages/a9/77/421e4437fb8eaee7c8396ea5a5d2d54ff22b96279dbae4cb251539824933/streaming_form_data-2.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:233026ea931b4043ddec64dc774753073a03caee5f76be450ceb73e15c31d016", size = 617009, upload-time = "2026-06-10T19:35:39.308Z" }, + { url = "https://files.pythonhosted.org/packages/b3/96/e94f0ace935a23011ff44e3d0f9b88950159fd02b45a1528c3b57ef73876/streaming_form_data-2.1.0-cp310-cp310-win32.whl", hash = "sha256:0ef2778e554bc7db29f6493d229d2eea647f5df8b104cf017516a9bbf5e869fc", size = 198327, upload-time = "2026-06-10T19:35:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e2/abd0b2b1772a91bfb979d52d5ddd5a70a1ed62506b0cd23c8450dd2c1f4d/streaming_form_data-2.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5b46ea82530e4f9be08396b388e38e3b89f26345a19e2f84ade0302d92cc5dc", size = 208117, upload-time = "2026-06-10T19:35:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/90/9a/9239a3e8c6fb10e0367c3aec387eed816cc9fe411a43cd998203d269e3f5/streaming_form_data-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a94d5eb98399fa9bd69741a4fb784d92ea8a774850e099a4bb6bb5812d773ed7", size = 223149, upload-time = "2026-06-10T19:35:42.814Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/0e3490b9ff2dc14dbff8baacf1c23c15f24f8ad3434327022b5f59e50e2a/streaming_form_data-2.1.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:97934de76c520182e8536748c8f07544d646777174a41215ee15c3eeca0de479", size = 664319, upload-time = "2026-06-10T19:35:43.915Z" }, + { url = "https://files.pythonhosted.org/packages/ef/69/e50cd2c4fc8e216d7a6a073eea4239f744db8bf556b93fd8671b23e47358/streaming_form_data-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab74a306ac7db0fc8a4539c62b55db0488d2f81928648a66ccfa0770051cc4f7", size = 657767, upload-time = "2026-06-10T19:35:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/55/b5/2bb7a12abdd81bccd311a12fbabe8c715774e2d99a12cedf0c294179154b/streaming_form_data-2.1.0-cp311-cp311-win32.whl", hash = "sha256:c9d17aaae0a171f74611cd2bead3dc39bf3cd5f02887af67aa8d4da5b3647022", size = 198458, upload-time = "2026-06-10T19:35:46.198Z" }, + { url = "https://files.pythonhosted.org/packages/bc/6b/2cd860cec26b65d1d65a1e371cb1fb094aa15a0cee6235f996d32c49fe22/streaming_form_data-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:582912c9f488569ec8d7930d73abedbeb96dd74ea447b7d6fa4691e730276884", size = 208623, upload-time = "2026-06-10T19:35:47.158Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b2/3123dc2b39ff69a5cf7bea5fb2a0a7aa2b41c4c43d3c489eada7cc249873/streaming_form_data-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:109390324580f0bab0777f9f347843c29895aa78028aed86e5931158008ef369", size = 223269, upload-time = "2026-06-10T19:35:48.32Z" }, + { url = "https://files.pythonhosted.org/packages/09/31/335732ff6f370eeb42391505a2d08c32ec5381b846cd619a4e58b2cbdad2/streaming_form_data-2.1.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c10cc7dc41c79ea270ad93d1f1dc982750eb5b13f719d9a978f125c0b3b86371", size = 664217, upload-time = "2026-06-10T19:35:49.445Z" }, + { url = "https://files.pythonhosted.org/packages/06/3b/7c69ce4977a81a4e02221abd73c7de8a2e2f34a53987f64c041d2920d706/streaming_form_data-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:055d40c7a03d56de9751167a95b62176f9b2283c808d4818f78b0ae4b872d166", size = 651437, upload-time = "2026-06-10T19:35:50.581Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0f/80db74a30563758768550276cb6b07d6e9248c24176cfb1f021abf47860c/streaming_form_data-2.1.0-cp312-cp312-win32.whl", hash = "sha256:a08266f5328071d2b57c43448cacefbaf80ab5e33e3020401f064f688e748dbc", size = 198099, upload-time = "2026-06-10T19:35:51.647Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a5/53c01f6d0474d53bfdb9f32ffe6946101b499f6c698dd61ac560eace72be/streaming_form_data-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:76c36952a7399167984e0146b1dcd50fcd58e4adf58c28cb8150bd2973c5f8d5", size = 208512, upload-time = "2026-06-10T19:35:52.683Z" }, + { url = "https://files.pythonhosted.org/packages/13/4b/6da0657b08df77c9b3399273976e7bde90b9156254bf6237d0d84dd440bf/streaming_form_data-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a7841684f9ac6476cfb0288ab670c2b08b1f1a06ddcac67b851843c5e53b27b7", size = 222265, upload-time = "2026-06-10T19:35:53.592Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b4/0db7ffb320710b851ec290eedbbc5875a3e2b82fae3418632ac860c25b31/streaming_form_data-2.1.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a917c93e45df1e7296964f46a98ef4a73ab477c10cebe1abb2667c90983f4d73", size = 660321, upload-time = "2026-06-10T19:35:54.766Z" }, + { url = "https://files.pythonhosted.org/packages/7e/1f/c8cffb5d4ce2d9fb02bd0190f66b682405e972e09a22517dd11a0f08f6bf/streaming_form_data-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:28209064b60d86ff065b2a0776adccebd849beb2507e7f9cb995597ae2d30980", size = 647626, upload-time = "2026-06-10T19:35:55.967Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cb/1ea4254bc0cf107a0d853ccb3aca5f2db41f238de2e8b0dc7b55b51d114a/streaming_form_data-2.1.0-cp313-cp313-win32.whl", hash = "sha256:0d92b76a51ef0621b37c437deae8641589e21ff3b132a407b146753a7b7f6576", size = 197919, upload-time = "2026-06-10T19:35:57.06Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3d/77b35bfca81c6cc4546c35b38998c5fde2d5783e3b3a14ceebced1415ed9/streaming_form_data-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:2d688a0205d44441fdd38010f84b32a29668d81537909b2832d0ecdf02b43a2d", size = 207883, upload-time = "2026-06-10T19:35:58.091Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tifffile" +version = "2025.5.10" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/44/d0/18fed0fc0916578a4463f775b0fbd9c5fed2392152d039df2fb533bfdd5d/tifffile-2025.5.10.tar.gz", hash = "sha256:018335d34283aa3fd8c263bae5c3c2b661ebc45548fde31504016fcae7bf1103", size = 365290, upload-time = "2025-05-10T19:22:34.386Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/06/bd0a6097da704a7a7c34a94cfd771c3ea3c2f405dd214e790d22c93f6be1/tifffile-2025.5.10-py3-none-any.whl", hash = "sha256:e37147123c0542d67bc37ba5cdd67e12ea6fbe6e86c52bee037a9eb6a064e5ad", size = 226533, upload-time = "2025-05-10T19:22:27.279Z" }, +] + +[[package]] +name = "tifffile" +version = "2026.3.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/cb/2f6d79c7576e22c116352a801f4c3c8ace5957e9aced862012430b62e14f/tifffile-2026.3.3.tar.gz", hash = "sha256:d9a1266bed6f2ee1dd0abde2018a38b4f8b2935cb843df381d70ac4eac5458b7", size = 388745, upload-time = "2026-03-03T19:14:38.134Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/e4/e804505f87627cd8cdae9c010c47c4485fd8c1ce31a7dd0ab7fcc4707377/tifffile-2026.3.3-py3-none-any.whl", hash = "sha256:e8be15c94273113d31ecb7aa3a39822189dd11c4967e3cc88c178f1ad2fd1170", size = 243960, upload-time = "2026-03-03T19:14:35.808Z" }, +] + +[[package]] +name = "tifffile" +version = "2026.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/38/5e2ecef5af2f4fd4a89bb8d6240de9458bab4d51a4cbd97aeb3a0cd618e2/tifffile-2026.6.1.tar.gz", hash = "sha256:626c892c0e899d959b9438e7c0e1491dc154a7fead1f1f37a991724a50eceba9", size = 429694, upload-time = "2026-05-31T23:57:12.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/59/208f71d70ddc6184f79b8c6d87d46eb7d7b12c19186a817dec9c9c3f3693/tifffile-2026.6.1-py3-none-any.whl", hash = "sha256:0d7382d2769b855b81ce358528e2b40c16d48aa39031746efa81215205332a8d", size = 267108, upload-time = "2026-05-31T23:57:10.597Z" }, +] + +[[package]] +name = "timm" +version = "1.0.27" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.12.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/54/ece85b0eef3700c90db8271a43669b05a0ebbe2edb1962329c34374a297e/timm-1.0.27.tar.gz", hash = "sha256:315dfe63186ca9fb7ff941268941231fd5be259f2b4bb4afa28560ae1015cb9a", size = 2439861, upload-time = "2026-05-08T19:38:36.844Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/2e/26bab7686ff4aed48f8f5f6c23e2aa37b7a37ddd9effe3aa61e908fd518f/timm-1.0.27-py3-none-any.whl", hash = "sha256:5ff07c9ddf53cbada88eab1c93ff175c64cab683b5a2fddf863bcee985926f89", size = 2589280, upload-time = "2026-05-08T19:38:35.034Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/84/04/655b79dbcc9b3ac5f1479f18e931a344af67e5b7d3b251d2dcdcd7558592/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:753d47ebd4542742ef9261d9da92cd545b2cacbb48349a1225466745bb866ec4", size = 3282301, upload-time = "2026-01-05T10:40:34.858Z" }, + { url = "https://files.pythonhosted.org/packages/46/cd/e4851401f3d8f6f45d8480262ab6a5c8cb9c4302a790a35aa14eeed6d2fd/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e10bf9113d209be7cd046d40fbabbaf3278ff6d18eb4da4c500443185dc1896c", size = 3161308, upload-time = "2026-01-05T10:40:40.737Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6e/55553992a89982cd12d4a66dddb5e02126c58677ea3931efcbe601d419db/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64d94e84f6660764e64e7e0b22baa72f6cd942279fdbb21d46abd70d179f0195", size = 3718964, upload-time = "2026-01-05T10:40:46.56Z" }, + { url = "https://files.pythonhosted.org/packages/59/8c/b1c87148aa15e099243ec9f0cf9d0e970cc2234c3257d558c25a2c5304e6/tokenizers-0.22.2-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f01a9c019878532f98927d2bacb79bbb404b43d3437455522a00a30718cdedb5", size = 3373542, upload-time = "2026-01-05T10:40:52.803Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "torch" +version = "2.11.0+cu128" +source = { registry = "https://download.pytorch.org/whl/cu128" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "cuda-bindings", version = "12.9.7", source = { registry = "https://pypi.org/simple" }, marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cuda-toolkit", version = "12.8.1", source = { registry = "https://pypi.org/simple" }, extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "filelock", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "fsspec", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "jinja2", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cudnn-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparselt-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nccl-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvshmem-cu12", marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "sympy", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "triton", version = "3.6.0", source = { registry = "https://pypi.org/simple" }, marker = "(sys_platform == 'linux' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "extra == 'extra-6-dashai-cuda'" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5ac6e34681d5a0e527edb741b38254899cd03087a7dd7e841791a4ee0a5e7011", upload-time = "2026-04-27T17:32:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:72d53f3176a69cc20710c4ecb95f7dc4c6ba10c4e4eda45b8396ee79ee40f75a", upload-time = "2026-04-27T17:33:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp310-cp310-win_amd64.whl", hash = "sha256:7c792fe95ad5edaf622cf9e4f5573f5aecf2bc0654c7e866eda6134088f95d72", upload-time = "2026-04-27T17:34:55Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d76f08e212285bd84c4c5a3472417f8eb4ee72e4067a604f7508dbfa2119771f", upload-time = "2026-04-27T17:36:45Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c9a7ca4c74fae10a58e6175b4b2cea953f9322bb6562bbf339ad6a05f52190ad", upload-time = "2026-04-27T17:37:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp311-cp311-win_amd64.whl", hash = "sha256:90ef0c2454e5296a9fb021ddd42252e4ce1abe2c0a4988a173ef90a6cded0bf5", upload-time = "2026-04-27T17:39:29Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9c8f38efee365cb9d334de8a83ce52fc7e5fc9e5a7b0853285efa1b69e00b0f2", upload-time = "2026-04-27T17:41:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d252cf975fb18c94a85336323ad425f473df56dab35a44b00399bd70c7a3b997", upload-time = "2026-04-27T17:42:06Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:7c78215c3af4f62e63f2b2e360f1722fc719b0853c7ac22666483d9810613a4c", upload-time = "2026-04-27T17:43:49Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7db3580106bba044da5b8950f3fb8fe5f31999eaab3f6a3aa2ac5d202c3684d2", upload-time = "2026-04-27T17:45:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:db964b33c55035a72ab3e2162287af8f1cc276039c65d015740cc88c26dcedf7", upload-time = "2026-04-27T17:46:18Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313-win_amd64.whl", hash = "sha256:6f367e62fd81b75cdf23ca4b75ced834d2db2cf98d1588ac935bde345de9de23", upload-time = "2026-04-27T17:48:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd1cf1005c5fe419194ee294b7b584ba5ad0f2fb1778b3fe5a7b9c3f4617ddbc", upload-time = "2026-04-27T17:50:01Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:74b628dbc71603977b09f4e140792c6e997081a35ef3421555f3f6e201b81210", upload-time = "2026-04-27T17:50:42Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp313-cp313t-win_amd64.whl", hash = "sha256:c2a5984deba8e001d166bf9cb83b8351f63a28b009e1a2fa0e4bbf08c90b259b", upload-time = "2026-04-27T17:52:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:baa52f7b8a53cab16587b10f1c27d1000ca033f97236878b685b75d5a1b92408", upload-time = "2026-04-27T17:54:24Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d389a850677f0d24dafae1573644034428d8d3b9c80b51d55ba62fed7e6c8777", upload-time = "2026-04-27T17:55:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314-win_amd64.whl", hash = "sha256:d6c21797ff75271b4fbdd905e2d703be4ecea5ea5bbdde4d1c201e9c71bc411d", upload-time = "2026-04-27T17:56:46Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:06849e9311dbb0617c97557d9c26c99a9e1c4f2ac9cb8e9b6d9b420d522acb91", upload-time = "2026-04-27T17:58:48Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:169a9987e1f84f0c5eee07544b3a34827a163ac9180e23abf0c3548f1335762c", upload-time = "2026-04-27T17:59:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torch-2.11.0%2Bcu128-cp314-cp314t-win_amd64.whl", hash = "sha256:d86c125d720c2c368c53bd1a4ef062916d91fa965c10448c74c78b5d039faf2d", upload-time = "2026-04-27T18:01:14Z" }, +] + +[[package]] +name = "torch" +version = "2.12.1" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version < '3.11' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "filelock", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "fsspec", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "sympy", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ec56e82be6a8b0c036771a77f7d32ad3c299770571af9815b3dafe61434389d5", upload-time = "2026-06-17T15:43:50Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ef81f503912effea2ce3d9b12a2e3a6ed488943e91271c90c7a829f60baf6aa2", upload-time = "2026-06-17T15:43:57Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d2dd0f2c5f7ccbddaf34cade0deaf476808368f902b9cdb7f36a2ab42301bc0e", upload-time = "2026-06-17T15:44:03Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c75e93173c700bccd6bfcc4a9d19ce242ab6dacd1f1781483027a16239b9e650", upload-time = "2026-06-17T15:44:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e9b6f7d2dd66ea87a3ae620069d31335d594c06effb1a383bdd21cfe61e44ece", upload-time = "2026-06-17T15:44:16Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:2afbb2bdaa8a95040e733f05492ddf133c3967c9b7ce0abd218d704b6cab437d", upload-time = "2026-06-17T15:44:24Z" }, +] + +[[package]] +name = "torch" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "cuda-bindings", version = "13.3.1", source = { registry = "https://pypi.org/simple" }, marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "cuda-toolkit", version = "13.0.2", source = { registry = "https://pypi.org/simple" }, extra = ["cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "filelock", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "fsspec", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "jinja2", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cublas", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cudnn-cu13", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-cusparselt-cu13", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nccl-cu13", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nvshmem-cu13", marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "sympy", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "triton", version = "3.7.1", source = { registry = "https://pypi.org/simple" }, marker = "(sys_platform == 'linux' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/ed/ff0c4f8cef63977a646dc80e40c05cae873f4097b12dc87e1cd7e1cecf42/torch-2.12.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ec56e82be6a8b0c036771a77f7d32ad3c299770571af9815b3dafe61434389d5", size = 87967927, upload-time = "2026-06-17T21:08:43.16Z" }, + { url = "https://files.pythonhosted.org/packages/85/1b/c8ecf60c9dba535f9ea341c359c600c0bd877a7ca14b3296f13316321847/torch-2.12.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:42cd7339bf266f14944710e8274be63e7e012bb937834a8d85a8327a9860eba6", size = 426366829, upload-time = "2026-06-17T21:07:18.574Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d6/73d4a3f27e00526e98086f3a64ab609af1345cca62367749fbc3c8e4b83c/torch-2.12.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a7817f0f89a796d9de239d06f69faf5d7e19a6a5db6710a5ead777c912f9f50a", size = 532144834, upload-time = "2026-06-17T21:08:00.633Z" }, + { url = "https://files.pythonhosted.org/packages/e3/51/4010c8fa6f9d1f42c054a321970ca95ec58e4e4494f5b53a34c3f3c9e310/torch-2.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:2af3d9cc866e0a15ae7635ff0a9c61d6624a353ad657f5bcd8d86c26cdc64693", size = 122949863, upload-time = "2026-06-17T21:08:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/59/38/7028d3be540f1dcdf41660a2b01d0c51d2cb73915fe370d84e4d277a6d47/torch-2.12.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ef81f503912effea2ce3d9b12a2e3a6ed488943e91271c90c7a829f60baf6aa2", size = 87975425, upload-time = "2026-06-17T21:08:34.094Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e3/750b3e3548635ceac03ba255daa26dbc7ed66ca3484dc4b4d955ab7f4501/torch-2.12.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:107df6888624bdea41508f9aeb6149d9333c737a5530ceecb56c904e811369ae", size = 426379894, upload-time = "2026-06-17T21:06:55.077Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ca/ed24783da629ff3e640ba3f70a7639e9045d3d88b93ee6bc47b8a28a1f2c/torch-2.12.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:6e29e7e74d05bda7d955c75e99459f878ebd970ef851b4057edbd3b34a5eb4a3", size = 532169264, upload-time = "2026-06-17T21:08:17.65Z" }, + { url = "https://files.pythonhosted.org/packages/46/61/c63f0158446f3a98ea672b004d761b848911eba567ea4a624c7db5aadc04/torch-2.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:a513506cfda3c1c78dabeb6574c1597538c0254b3d39af174dde35d8177f4ce3", size = 122953086, upload-time = "2026-06-17T21:08:27.69Z" }, + { url = "https://files.pythonhosted.org/packages/f0/54/efb7ebca77970012b0cc21687a55d70eb2ba514b2c2b8e18d9fb1222f3be/torch-2.12.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:d2dd0f2c5f7ccbddaf34cade0deaf476808368f902b9cdb7f36a2ab42301bc0e", size = 87991951, upload-time = "2026-06-17T21:07:49.309Z" }, + { url = "https://files.pythonhosted.org/packages/1e/00/4210d76ca7424981f04033ebe7e48816ab83287a62538747a58825db770c/torch-2.12.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2de4e19b88a481482c6c75291f2d6a52eda3ce51f311b29aa9b68499c830c07c", size = 426382721, upload-time = "2026-06-17T21:06:41.842Z" }, + { url = "https://files.pythonhosted.org/packages/76/1f/bc9f5a5aa569307076365f25afcebacb22e9c754b1bcfbaaa146627c7fda/torch-2.12.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:649e4ced014ba646f76f8cb9c9726735a6323eb321b7919f942790a923f90921", size = 532261322, upload-time = "2026-06-17T21:06:06.673Z" }, + { url = "https://files.pythonhosted.org/packages/9e/49/c549461daa008159d006a76a991fbc2f26fa8bac27a4030c858463dcb20f/torch-2.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e86550597877fb272ddc52db2f85b82cb601ea7bd932576a0340152cae2200b3", size = 122988095, upload-time = "2026-06-17T21:07:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4a/0300261818e1560d72cc160ac826005507e8b7ca0a35788b591436d05b4a/torch-2.12.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:c75e93173c700bccd6bfcc4a9d19ce242ab6dacd1f1781483027a16239b9e650", size = 87992358, upload-time = "2026-06-17T21:07:40.299Z" }, + { url = "https://files.pythonhosted.org/packages/30/a7/874a5ca05e8f159211dca7921060f7057acc1adb26431e119fd150623efc/torch-2.12.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:fcb61ccd20784b62bdd78ec84238a5cfb383b4994902e03bac95505ab360884c", size = 426386134, upload-time = "2026-06-17T21:07:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/e1/75/20bb8fe9c1ad6538cce8cd0391b51927ae5af0b17ed1eab44b8824465dc1/torch-2.12.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f4afc8083dff08719edbea346644476e3cec0cf40ebe256be0ee5d5b7c7e8c0d", size = 532268019, upload-time = "2026-06-17T21:05:37.925Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fa/824ddb662af55b2eabc0dbb7b57c7c0b1bcd93693754a2b8509ec4d16490/torch-2.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:f92609e3b3ce72f25e2eb780d043ced2480c1a86c47c852604fc7a9108648386", size = 122987777, upload-time = "2026-06-17T21:07:09.49Z" }, + { url = "https://files.pythonhosted.org/packages/63/b7/1b49fe7086ea36839cc80abc43174c43d0ab6f676c0891c871c162f44fe3/torch-2.12.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e9b6f7d2dd66ea87a3ae620069d31335d594c06effb1a383bdd21cfe61e44ece", size = 88010025, upload-time = "2026-06-17T21:07:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/d7/06/5b44063a6545036dcc680d2d303b137d9176cfb2cc1e1863e3ef94abeb52/torch-2.12.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:7973ccd3d2cd35c74449213f7bded199bec6c6247e705cbeda7407af79703d91", size = 426392891, upload-time = "2026-06-17T21:05:52.261Z" }, + { url = "https://files.pythonhosted.org/packages/f8/dd/c9ce9a4b0eb3c5bb92d9ea56766e2c22559f0b45171149188494edcce80f/torch-2.12.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:c64ac4aac16be5e296dcd912305605804b203333c690bf98c55bc09494ee92ad", size = 532272494, upload-time = "2026-06-17T21:06:22.72Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/f3a601fc1b1f663ff269bfe553654e638651939aa6563e8daa7167c33098/torch-2.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:f6dc4caf7eb4adb38a2d9f536b51db56310fdd1254e69a2d96767e1367c892b3", size = 122987254, upload-time = "2026-06-17T21:06:33.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/b8087556cf81ddd808dbeb34afb8396d7ae7a1694ab489f08b1a0004e7d0/torch-2.12.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:2afbb2bdaa8a95040e733f05492ddf133c3967c9b7ce0abd218d704b6cab437d", size = 88303173, upload-time = "2026-06-17T21:05:06.603Z" }, + { url = "https://files.pythonhosted.org/packages/4a/07/fe09d1699fbed2afa10ebc692ff2b99d113f2605b6748cea633989e2789a/torch-2.12.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:97eba061fcb042fed191400b15568990073d67eaacaa6ee9b7ca01dd8b790fe9", size = 426404009, upload-time = "2026-06-17T21:04:57.557Z" }, + { url = "https://files.pythonhosted.org/packages/2e/f7/0ce4f6c1962c60ded7270e0a9eb560fb615c92b89d332cf9e3dff36d5ecc/torch-2.12.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3867b861391701012adb2df93360efb88494dca245a185e3bb7624495cfe3f33", size = 532184292, upload-time = "2026-06-17T21:05:17.526Z" }, + { url = "https://files.pythonhosted.org/packages/70/db/e384c12aba30320ca92aaaf557456cbcb26f04b4df307728bb8f019f5000/torch-2.12.1-cp314-cp314t-win_amd64.whl", hash = "sha256:dd15595f8fc764cffde8c6361a3beb6ef69a028c851b1b3e70e077f615980d4e", size = 123231142, upload-time = "2026-06-17T21:05:27.061Z" }, +] + +[[package]] +name = "torch" +version = "2.12.1+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version >= '3.15' and sys_platform != 'darwin'", + "python_full_version == '3.14.*' and sys_platform != 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin'", + "python_full_version == '3.12.*' and sys_platform != 'darwin'", + "python_full_version == '3.11.*' and sys_platform != 'darwin'", + "python_full_version < '3.11' and sys_platform != 'darwin'", +] +dependencies = [ + { name = "filelock", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "fsspec", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "jinja2", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "setuptools", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "sympy", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "typing-extensions", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp310-cp310-linux_s390x.whl", hash = "sha256:a73cb40dddadd56bd40722ad896f17838ec754fc17509c16d9639aa77b116e00", upload-time = "2026-06-18T01:56:33Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:93b8ceb3689e34a92465d4cac7d08e65dfcf49d10d6d47d15db5f3917baa1152", upload-time = "2026-06-18T01:56:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:41c4824c535315dc8a4f92a4a61203df2074d052dbf8f75fda8bfe472fe3cdde", upload-time = "2026-06-18T01:56:45Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp310-cp310-win_amd64.whl", hash = "sha256:bc5335453723e03f92603d98b05ad9416edd2d47fc401d29a98c20008bf74268", upload-time = "2026-06-18T01:56:51Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp311-cp311-linux_s390x.whl", hash = "sha256:c6cf299bad55b045abdc0969f298385845c25bc8ace587215e5538d6a73e7712", upload-time = "2026-06-18T01:56:56Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:6d1b61e53a2c000e1e5cc49fc88aebc665bdf02c63910c243116d395d7cbc164", upload-time = "2026-06-18T01:57:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:04cd8b002c03dd6a246fbb4ae5abf1edd42adf0a9929ad82162c973e5737b5ac", upload-time = "2026-06-18T01:57:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:cf9ef28ef8d1a7d56aba206780cbb09e8a643a942f26a2374ab21b9a02b9d9fd", upload-time = "2026-06-18T01:57:15Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp311-cp311-win_arm64.whl", hash = "sha256:9b4f554e5e461545b1b42551e27f97b4fbc58e05f2bad0603120aa929d4562e1", upload-time = "2026-06-18T01:57:19Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp312-cp312-linux_s390x.whl", hash = "sha256:900b253fc8c739bebffb63d7f75abff4cd53d79947265a00c16cb53b68ecdb91", upload-time = "2026-06-18T01:57:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d1620bc7bcf8087f3e48821b5db994a03e32ddb083d58c000b8e032f8a6e2d15", upload-time = "2026-06-18T01:57:29Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ae4bb28409f5370852bd71af221066236c38d647f780d9b0a7240c330a9c12df", upload-time = "2026-06-18T01:57:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:51c6c6e26eaa0d64ed439ebdc9ce3b8cc2d5cfcc7cdd4e72f17831d80886b7f4", upload-time = "2026-06-18T01:57:42Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp313-cp313-linux_s390x.whl", hash = "sha256:4ca206a35f2aeba7da944a69ef857103358e8d4bc6b06b49b9e554dcfa57777d", upload-time = "2026-06-18T01:57:47Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:8d47e0cfc59679d4e367646c3df4cf433c7d1595b31307e0e7b2391c58ca2160", upload-time = "2026-06-18T01:57:52Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:77d049968c7817456dbdebf0aadcac0813e302218b0e857a8723463331339d55", upload-time = "2026-06-18T01:57:59Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:06d13c354df07953ae0d150da22a071496cbbedd22d1b644961813c0f0fc3b23", upload-time = "2026-06-18T01:58:05Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp314-cp314-linux_s390x.whl", hash = "sha256:ef228ec70f5e73762c213f145c0fdb672e5770a6de760801c58b933b0d5b6957", upload-time = "2026-06-18T01:58:09Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:1b9ad70d0a300d6bd883fffc153a6c0f9bc64bf4190519aeeed0373807bffbcc", upload-time = "2026-06-18T01:58:15Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:618120d79360688595e61162147d37e1d69fc3f2e13d60584a1eb6a74c74735a", upload-time = "2026-06-18T01:58:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:6354069774ce2d9c26d6d8612fc252586428467b7e4ced7c8bbbfb961fafb783", upload-time = "2026-06-18T01:58:29Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp314-cp314t-linux_s390x.whl", hash = "sha256:0706eb7b53c4b1381b2acc418f6d89d6ed18a308677125ced7ca30519c57dc42", upload-time = "2026-06-18T01:58:33Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:21ed774c10a0fd24ab2cfaabfa98a9260fc28d518946c98c0151b6e447730250", upload-time = "2026-06-18T01:58:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:e3c3b5c275dd57cff1aa34a0ab8f84beab8976c63c8dc1d84f29430eac0590ea", upload-time = "2026-06-18T01:58:46Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.12.1%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:03094411b20e85a221125a332211d59c099dc556afb1423e04193fcf1b4c1cbd", upload-time = "2026-06-18T01:58:52Z" }, +] + +[[package]] +name = "torchmetrics" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lightning-utilities" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.12.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/34/39b8b749333db56c0585d7a11fa62a283c087bb1dfc897d69fb8cedbefb1/torchmetrics-1.9.0.tar.gz", hash = "sha256:a488609948600df52d3db4fcdab02e62aab2a85ef34da67037dc3e65b8512faa", size = 581765, upload-time = "2026-03-09T17:41:22.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/a2/c7f6ebf546f8f644edf0f999aa98ece106986a77a7b922316bf6414ff825/torchmetrics-1.9.0-py3-none-any.whl", hash = "sha256:bfdcbff3dd1d96b3374bb2496eb39f23c4b28b8a845b6a18c313688e0d2d9ca1", size = 983384, upload-time = "2026-03-09T17:41:19.756Z" }, +] + +[[package]] +name = "torchvision" +version = "0.26.0+cu128" +source = { registry = "https://download.pytorch.org/whl/cu128" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra == 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2a04bf491eb22e6487defe6cead6ffaabbce13fb5981b8eb3540050f96cb0599", upload-time = "2026-04-09T23:21:33Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:f44bfc61b9be80bcf52a762d34da363cea3125d10c01f37e271583803c7bb97b", upload-time = "2026-03-23T15:36:21Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp310-cp310-win_amd64.whl", hash = "sha256:ed0770e00b96d8aa675718e20db69c740c927f027c9c8b1330251f8a973221b6", upload-time = "2026-04-09T23:21:33Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ed1324dbbbecb5a0149ed4ce8f9308465a1eef85ca2d2370dbb14805bf1c90aa", upload-time = "2026-04-09T23:21:34Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f2629d056570c929b0a1d5473d9cb0320b90bda1764bda353553a72cc6b2069", upload-time = "2026-03-23T15:36:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp311-cp311-win_amd64.whl", hash = "sha256:d26091b15cd6e3c74c148d9b68c9a901ad6fb9b0f66fa3ea3ab09f04132a07d3", upload-time = "2026-04-09T23:21:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:63e35234aed13b6edda37056f417b5c281249669db631e706811917af36b21d7", upload-time = "2026-04-09T23:21:35Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ccf26b4b659cfce6f2208cb8326071d51c70219a34856dfdf468d1e19af52c0d", upload-time = "2026-03-23T15:36:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp312-cp312-win_amd64.whl", hash = "sha256:8c0d1c4fbb2c9a4d5d41d0aaa87da20e525bcb2a154ce405725b0be59456804b", upload-time = "2026-04-09T23:21:36Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c4a9cacd521f2a4df0bcd9d8e96704771b928f478f1f3067e4085bb53a1da298", upload-time = "2026-04-09T23:21:37Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cb1f6184a7ba30fba40580e1a01a6604a86c55e79fdda187f40116ee680441ec", upload-time = "2026-03-23T15:36:22Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313-win_amd64.whl", hash = "sha256:0232cb219927a52d6c98ff202f32d1cdf4802c2195a85fc1f1a0c1b0b4983a4d", upload-time = "2026-04-09T23:21:38Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e594732552a8c2fee2ace9c6475c6c6904fc44ccca622ee6765a89a045416a44", upload-time = "2026-04-09T23:21:38Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6168abc019803ac9e97efce27eafd2fdb33db04dcc54a86039537729e5047b29", upload-time = "2026-03-23T15:36:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp313-cp313t-win_amd64.whl", hash = "sha256:367d42ea703844ecdb516e9d5eb09929012a58705d2622cf4e9e3c37f278cb85", upload-time = "2026-04-09T23:21:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:b3865fa227661dd75b7b28c96d3d14e739bd08bf0614132758922fe0e7206f91", upload-time = "2026-04-09T23:21:39Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:aac647c9130f1f25f5c8f5bca3d95cfd96bdfac93ab54529690b088e64e4fa64", upload-time = "2026-03-23T15:36:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314-win_amd64.whl", hash = "sha256:6319e1ba49c6f62ac9902f73d0eab207b8a4dc6b4d3392fe9edd9903fff1be0a", upload-time = "2026-04-09T23:21:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:e2ee9e16ee4518292694537fcbd20d2d27044e381d92b864f637e82795796a84", upload-time = "2026-04-09T23:21:40Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:b5772c55bfda4377df8f1930d43c4e0231ef231b0228eade4b227c8d3ba6e34e", upload-time = "2026-03-23T15:36:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cu128/torchvision-0.26.0%2Bcu128-cp314-cp314t-win_amd64.whl", hash = "sha256:f160dc552a086244f7102c898f7be8ef46a41b36bce5ea80a4f2493cb30ca1fc", upload-time = "2026-04-09T23:21:41Z" }, +] + +[[package]] +name = "torchvision" +version = "0.27.1" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and sys_platform == 'darwin'", + "python_full_version < '3.11' and sys_platform == 'darwin'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:68ba63b48af92f06db995adb23d8411993dba1dee705a4e92411b83a00930b7f", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ad8743a9c12c8c124ad0a1491e54c3ca0c749e91e374e3d92136060b22c9e0f4", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:448abfc3baba984da4577f737209e445da6be93e3b5f4799d90162bf61e3f485", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d60311a6d08df905f9656a3a312f0a8f55f0d46321bc737bad30a8dec9644309", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9f5ef59ad60e695796eca6b64e97cb9b21b9d5463cac5ac0ef86cfb72b6e5db9", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c2fd9902f23b56b6ac667213171672fb6c89287ff011918b04af053852a2c4eb", upload-time = "2026-06-17T15:44:32Z" }, +] + +[[package]] +name = "torchvision" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/10/8e3e5a70dded1f86368bc987d93fa0436e73a79060aead75a8783b040ebd/torchvision-0.27.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:68ba63b48af92f06db995adb23d8411993dba1dee705a4e92411b83a00930b7f", size = 1852109, upload-time = "2026-06-17T21:09:34.966Z" }, + { url = "https://files.pythonhosted.org/packages/4c/32/1a3eddb92e6d8ae69f38a20c60b7788c67c6ef32d6d4d1dc5d5fbd1f109c/torchvision-0.27.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:aabe47970a00a3c0574360bfbd4ca3d6162a51f3fa1283a29cd528018dd13088", size = 7829870, upload-time = "2026-06-17T21:09:18.695Z" }, + { url = "https://files.pythonhosted.org/packages/13/1c/6b45992279b4177d26b5b851f554ee5d99115e7bd02eeb99459ad41027bf/torchvision-0.27.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a9ea9a8abdd23466a5f1c09523cb27dc61e36c16fc7e5e88b337ca54f530b1ef", size = 7658441, upload-time = "2026-06-17T21:09:23.803Z" }, + { url = "https://files.pythonhosted.org/packages/7e/6b/09d2d6f04c3465a346202624a09cfcfb954f2b334d19de886a63056f86f9/torchvision-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:d0b00ae58379e6c936ce4816b1b8b94cefb1bd1c2a88eccbadfc993de7a430ca", size = 3493503, upload-time = "2026-06-17T21:09:28.426Z" }, + { url = "https://files.pythonhosted.org/packages/64/46/bc0ebd93282aeedc1759f054a252c6fadf14b42a0535db3233c85cce4ae5/torchvision-0.27.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ad8743a9c12c8c124ad0a1491e54c3ca0c749e91e374e3d92136060b22c9e0f4", size = 1852118, upload-time = "2026-06-17T21:09:32.448Z" }, + { url = "https://files.pythonhosted.org/packages/b2/00/752adc57b6aa8bb833f5b0672acb9538aa5535d64998b9d8dd48ee51fa80/torchvision-0.27.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a726707e4cbe438fcc507d787af7acf6bca52de30bf4b03579f1dfc0675da829", size = 7831256, upload-time = "2026-06-17T21:09:26.767Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8a/c474fb27faba02e84dc40e0ac9ea1aa828d6d3557a378f7d0a22468bb2a3/torchvision-0.27.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a1d6a123009af59ad288459f579f67a65cbe8f59372dc7b97e41bc01a6a9b767", size = 7659995, upload-time = "2026-06-17T21:09:25.325Z" }, + { url = "https://files.pythonhosted.org/packages/eb/7c/e254f8e242a921adc2cc62c11674fa8a16d33e0a1b6c6f5436cb91628ee7/torchvision-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:f3b57a984283896f15c9698562418282f828332886c77315bf269936e6ba0280", size = 3807497, upload-time = "2026-06-17T21:09:31.234Z" }, + { url = "https://files.pythonhosted.org/packages/88/82/2e8fdc19e4f0bbe31d403a55d78318bcea4afcd3083e1e4700ef61ebb893/torchvision-0.27.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:448abfc3baba984da4577f737209e445da6be93e3b5f4799d90162bf61e3f485", size = 1852105, upload-time = "2026-06-17T21:09:33.695Z" }, + { url = "https://files.pythonhosted.org/packages/43/42/103fa8f9366cfd1329fe449d6b1a25a640c0c17862ed48f21c4af94af322/torchvision-0.27.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:9edfb5a549fc2f30ccadb24eca907901e92e426c91a59316be6703a9360e5098", size = 7830902, upload-time = "2026-06-17T21:09:29.739Z" }, + { url = "https://files.pythonhosted.org/packages/97/70/fa6052a42110a3657fc94073648da6171220469f4bf9f27e6a0b9378075c/torchvision-0.27.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ae3d49e57c4abc8eafc1a1971f80fc4948a6268fa69340737ca4466936def080", size = 7664211, upload-time = "2026-06-17T21:09:17.206Z" }, + { url = "https://files.pythonhosted.org/packages/d0/95/27aca854da7e536a339f46bab1ef67823ac2ac97c59ab2b3203b373d46cf/torchvision-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:0b6e3aa98b7433506bbce1d0d05cb13ec787fc6eb8c5fbd998b26ce05f047543", size = 4079076, upload-time = "2026-06-17T21:09:15.907Z" }, + { url = "https://files.pythonhosted.org/packages/32/bb/b21e0f598ca191bb2a9e9fda2fee37c06ad113313b43c6769dbefa0e921d/torchvision-0.27.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d60311a6d08df905f9656a3a312f0a8f55f0d46321bc737bad30a8dec9644309", size = 1852110, upload-time = "2026-06-17T21:09:22.577Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/d61171daa5d6cd5f9315f84f9ef947b047a9fdf283d53241327045a8dd6d/torchvision-0.27.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:08aa33bc8e062cca32aefa90ac714916c5a855cbe1ab4c6148fc0453eb40ca5a", size = 7789476, upload-time = "2026-06-17T21:09:13.105Z" }, + { url = "https://files.pythonhosted.org/packages/b8/dc/b21d7801562c23a770e7037989814582f22ca4db479204293561de4b62e8/torchvision-0.27.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:916448be4b19676677b0dbf47d08f68b7955ea0abec7fc79340c31e217a824ba", size = 7664256, upload-time = "2026-06-17T21:09:07.549Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b3/4386976ff77eda55f0aed504a288564f3ff8d170b6db49ee22e172eddfac/torchvision-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:18bc906235bfa901c135acd239f05b8c8ab90d502830cf1ef2cba3301e1f8a23", size = 4150710, upload-time = "2026-06-17T21:09:14.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/74/1d237c61f665bf46d02e15f67c9d40be42b1b634f87164b9cefd257450e7/torchvision-0.27.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9f5ef59ad60e695796eca6b64e97cb9b21b9d5463cac5ac0ef86cfb72b6e5db9", size = 1852112, upload-time = "2026-06-17T21:09:21.445Z" }, + { url = "https://files.pythonhosted.org/packages/24/84/f0d772e7ed85891f084755bd5d7f6f7fd279992a02652c653c1c8429dd84/torchvision-0.27.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:ab2f8047c2da5bf6742fec6da86840e5feaeb0cea76930d0536f3520df31e166", size = 7789751, upload-time = "2026-06-17T21:09:11.51Z" }, + { url = "https://files.pythonhosted.org/packages/76/68/3febd41b6eef453a83fb7a0178446334fbb0405eb4b0c40b00efaf99a2dc/torchvision-0.27.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b44ef28ad1963f8cba5bf82f3564c454c74be300df9f79efa43f773312d17d6c", size = 7664350, upload-time = "2026-06-17T21:09:04.486Z" }, + { url = "https://files.pythonhosted.org/packages/52/49/a23e199faf29e42a90f7d6b76437ade5d17e3185da3c64d368973ba8243e/torchvision-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:b3e9bc71854fddbf94ddb69ed8d88983945f3f28f78ee104214b0088669af66a", size = 4177297, upload-time = "2026-06-17T21:09:10.273Z" }, + { url = "https://files.pythonhosted.org/packages/ba/48/b3240eaf0fe3676dcf677ce8930ef477fe77d7f69ebe58ca8d0941384952/torchvision-0.27.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c2fd9902f23b56b6ac667213171672fb6c89287ff011918b04af053852a2c4eb", size = 1852118, upload-time = "2026-06-17T21:09:20.223Z" }, + { url = "https://files.pythonhosted.org/packages/73/01/6c8f3158994a9e5bb0c7b1bacc361d60e015ad79487af88fa4d7ce72c2b6/torchvision-0.27.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:8abb6d5cacd56486ca2240e5580750e53ac559412e472ea6a3cee83231a77ca7", size = 7791242, upload-time = "2026-06-17T21:09:02.062Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e6/f66733fc411a9ce070c0d899c1ae562ff11654a0bc708511e23efe9d6872/torchvision-0.27.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:d11da1ce8a5cc7fc527f2d5e0fe25efba93687897fe9339382b593910b1d1c6e", size = 7664934, upload-time = "2026-06-17T21:09:06.221Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/d6179812ec52b70a7a8f5e99fe7937895d28c535106df1ca0d03f5f51425/torchvision-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:12deaee20d0d9dec6302025d3f93354266befeb692f5c50bca0137b395598b9e", size = 4284412, upload-time = "2026-06-17T21:09:08.989Z" }, +] + +[[package]] +name = "torchvision" +version = "0.27.1+cpu" +source = { registry = "https://download.pytorch.org/whl/cpu" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin'", + "python_full_version >= '3.15' and sys_platform != 'darwin'", + "python_full_version == '3.14.*' and sys_platform != 'darwin'", + "python_full_version == '3.13.*' and sys_platform != 'darwin'", + "python_full_version == '3.12.*' and sys_platform != 'darwin'", + "python_full_version == '3.11.*' and sys_platform != 'darwin'", + "python_full_version < '3.11' and sys_platform != 'darwin'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.11' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow", marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torch", version = "2.12.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, +] +wheels = [ + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:3323edb900ca46d29dba267ee809e55fe5fe09cd3d9f54124a7c5ed33a6a2dbe", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b77bf33c8eba2a7845ca77cad131e812d3205bd04b8cc24acf1fcf37852e0b05", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp310-cp310-win_amd64.whl", hash = "sha256:3d437d8dbd0319774dbfffb6722cd74ea3595b0a48af523e1ed75138c0717518", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3fa34174671a934f166f9527acb77872d8ca16cd5ec21efaa385646033e0506e", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ee7d80d1ff34ef369253013bb2b60d8c67e55bcf2331ff5e8367d046ff76c01d", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:bdf72febd34b7a69697ac11add8ae3cd97c6880050a497158fd218fd156220ae", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:637838338b06082f21fd3f40d4bc70f87f8cb5c417530c74e89d823952804bc7", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:8428615cca9065fa64f01f804c57d890e3aec240292ecfd22a25de6fb50e0ee9", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp312-cp312-win_amd64.whl", hash = "sha256:1dc25560e97dc0c604c6f53e7a392ed1b62255e1ccfaed0e14f9ae79b2ff69b0", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:7d5d09a0e98441111d026ea924c202e3752d62425b84b0768d98c0bb074747ff", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:373c5c5b123922a139d75f49d094ab89e81a2c5b01a7c8ad9c6fc62916e2c16e", upload-time = "2026-06-17T15:44:31Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp313-cp313-win_amd64.whl", hash = "sha256:aa3c60d768cc4e840f96a92ec82db6b03cfeab4cce3fe7ebbd28d50229be6fed", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:290ebb142880dfcaa1feec3b448dcd3c704c2f0391232a1eed9eff5ea5c65b35", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:a1b6e90fe0beeac762acdea272eee40f64f8766106632b7c4b6f66038d60550f", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp314-cp314-win_amd64.whl", hash = "sha256:6145a34ddf050b18ac792404b2370c536aae60515b397060b761f0565c1619ab", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:42852e2682f89b8ceee109bc9c2fe73a803ba290764285b114fb5de191d7852b", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:71163a26902dd0945953f3261cadd39af2899b7a89cf5f0faa3b692fe11637bf", upload-time = "2026-06-17T15:44:32Z" }, + { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.27.1%2Bcpu-cp314-cp314t-win_amd64.whl", hash = "sha256:a02e933dc5ccfdb78286d2bde9e6eabf3207f631f8444ef87592a3504a1e17c0", upload-time = "2026-06-17T15:44:32Z" }, +] + +[[package]] +name = "tqdm" +version = "4.68.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, +] + +[[package]] +name = "transformers" +version = "5.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "regex" }, + { name = "safetensors" }, + { name = "tokenizers" }, + { name = "tqdm" }, + { name = "typer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/7c/8240f612819718100a9346dc28dea6a11370c3ca9c8c6eabadd3dea4ef29/transformers-5.12.1.tar.gz", hash = "sha256:679ee731c8225347889ad4fb3b2c926a62e9da3b7d284e9d12c791da7272466b", size = 8924054, upload-time = "2026-06-15T17:27:50.604Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/56/bbd60dd8668055803bf8ba55a81f9b8a8b31497f620109a9671d26a2076d/transformers-5.12.1-py3-none-any.whl", hash = "sha256:2a5e109d2021265df7098ffbb738295acaf5ad256f12cbc586db2ea4dcbb1a8a", size = 11150587, upload-time = "2026-06-15T17:27:46.679Z" }, +] + +[[package]] +name = "triton" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/ba/b1b04f4b291a3205d95ebd24465de0e5bf010a2df27a4e58a9b5f039d8f2/triton-3.6.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c723cfb12f6842a0ae94ac307dba7e7a44741d720a40cf0e270ed4a4e3be781", size = 175972180, upload-time = "2026-01-20T16:15:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f7/f1c9d3424ab199ac53c2da567b859bcddbb9c9e7154805119f8bd95ec36f/triton-3.6.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6550fae429e0667e397e5de64b332d1e5695b73650ee75a6146e2e902770bea", size = 188105201, upload-time = "2026-01-20T16:00:29.272Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190, upload-time = "2026-01-20T16:16:00.523Z" }, + { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "(python_full_version >= '3.15' and platform_machine != 'x86_64') or (python_full_version >= '3.15' and sys_platform != 'darwin')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64') or (python_full_version == '3.14.*' and sys_platform != 'darwin')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64') or (python_full_version == '3.13.*' and sys_platform != 'darwin')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64') or (python_full_version == '3.12.*' and sys_platform != 'darwin')", + "python_full_version == '3.11.*'", + "python_full_version < '3.11'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/ea/629cc37436ca5df93ce98956d09cd2ca1498bfee8ef4972d2fe48b9f958c/triton-3.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3daf64305d6cea88d3334c65ebc9bcd0c64c9564a977084366aa768d57cbcf64", size = 184551013, upload-time = "2026-06-17T20:03:37.551Z" }, + { url = "https://files.pythonhosted.org/packages/15/76/c79c34311625227a288df3e483fc5cdf3d596624cbd4b4758c4cbdc14af3/triton-3.7.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee89fbf782ec2ad50391dd1cf26cbea4f4467154c37f4773026da8fc31c0f58e", size = 197596267, upload-time = "2026-06-17T19:53:06.898Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, +] + +[[package]] +name = "typer" +version = "0.26.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rich" }, + { name = "shellingham" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/f7/68adc395201b20b872d68e975386832e8005ffeacedd43a1d837a32815be/typer-0.26.8.tar.gz", hash = "sha256:c244a6bd558886fe3f8780efb6bdd28bb9aff005a94eedebaa5cb32926fe2f7e", size = 202097, upload-time = "2026-06-26T09:22:45.705Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/87/b9fd69c92c6102a066e1b86a35243f53e70bd4c709f2a26d9f4fee4f4dc0/typer-0.26.8-py3-none-any.whl", hash = "sha256:3512ca79ac5c11113414b36e80281b872884477722440691c89d1112e321a49c", size = 122564, upload-time = "2026-06-26T09:22:44.72Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.49.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/1f/fa18009dea8469069cca78a4e877a008ab78f08b064bfc9ab891579077ff/uvicorn-0.49.0.tar.gz", hash = "sha256:ebf4271aa580d9de97f93192d4595176df6e91f9aae919ca73e4fc07df1e66a3", size = 91284, upload-time = "2026-06-03T22:01:30.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/fa/e1388bbcf24ef3274f45c0c1c7b501fd14971037c1b6ee23610553307497/uvicorn-0.49.0-py3-none-any.whl", hash = "sha256:ba3d14c3ee7e41c6c654c46c9eb489d33213cdd30aa1696eab1374337c13f68f", size = 71376, upload-time = "2026-06-03T22:01:29.037Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "(platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32') or (platform_python_implementation == 'PyPy' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'cygwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform == 'win32' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/14/ecceb239b65adaaf7fde510aa8bd534075695d1e5f8dadfa32b5723d9cfb/uvloop-0.22.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef6f0d4cc8a9fa1f6a910230cd53545d9a14479311e87e3cb225495952eb672c", size = 1343335, upload-time = "2025-10-16T22:16:11.43Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/6f6f9af7f590b319c94532b9567409ba11f4fa71af1148cab1bf48a07048/uvloop-0.22.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7cd375a12b71d33d46af85a3343b35d98e8116134ba404bd657b3b1d15988792", size = 742903, upload-time = "2025-10-16T22:16:12.979Z" }, + { url = "https://files.pythonhosted.org/packages/09/bd/3667151ad0702282a1f4d5d29288fce8a13c8b6858bf0978c219cd52b231/uvloop-0.22.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac33ed96229b7790eb729702751c0e93ac5bc3bcf52ae9eccbff30da09194b86", size = 3648499, upload-time = "2025-10-16T22:16:14.451Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f6/21657bb3beb5f8c57ce8be3b83f653dd7933c2fd00545ed1b092d464799a/uvloop-0.22.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:481c990a7abe2c6f4fc3d98781cc9426ebd7f03a9aaa7eb03d3bfc68ac2a46bd", size = 3700133, upload-time = "2025-10-16T22:16:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/09/e0/604f61d004ded805f24974c87ddd8374ef675644f476f01f1df90e4cdf72/uvloop-0.22.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a592b043a47ad17911add5fbd087c76716d7c9ccc1d64ec9249ceafd735f03c2", size = 3512681, upload-time = "2025-10-16T22:16:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ce/8491fd370b0230deb5eac69c7aae35b3be527e25a911c0acdffb922dc1cd/uvloop-0.22.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1489cf791aa7b6e8c8be1c5a080bae3a672791fcb4e9e12249b05862a2ca9cec", size = 3615261, upload-time = "2025-10-16T22:16:19.596Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/a5/81f987504738e6defeed61ec1c47e2aefab3c35d8eeb87e1b3f38cf28254/virtualenv-21.5.1.tar.gz", hash = "sha256:dca3bf98275a59c652b69d68e73433e597d977c2da9198882479d1a7188009c8", size = 4578798, upload-time = "2026-06-16T16:23:58.603Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/5a/2bf22ecb24916983bf1cc0095e7dea2741d14d6553b0d6a2ac8bc96eca93/watchfiles-1.2.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bb68bf4df85abebe5efddc53cf2075520f243a59868d9b3973278b23e76962a9", size = 400471, upload-time = "2026-05-18T04:31:08.908Z" }, + { url = "https://files.pythonhosted.org/packages/55/70/dea1f6a0e76607841a60fb51af150e70124864673f61704abb62b90cdcc7/watchfiles-1.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c16cb06dd17d43b9d185094268459eac92c9538356f050e55b54e82cf700e1d4", size = 394599, upload-time = "2026-05-18T04:30:19.845Z" }, + { url = "https://files.pythonhosted.org/packages/18/52/752dcc7dc817baef5e89518732925795ce52e36a683a9a3c9fb68b21504e/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77a0feab9af4c021c581f695258c642b3d10c5fd4c676e33a0d8606425d82631", size = 455458, upload-time = "2026-05-18T04:30:29.126Z" }, + { url = "https://files.pythonhosted.org/packages/12/48/366ebbb22fcc504c2f72b45f0b7e72f40a18795cc01752c16066d597b67a/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a16ffe19bf5cf9f5edaa1ad1dd830c5a816e8feec430c522302ab55483a4b994", size = 460513, upload-time = "2026-05-18T04:31:40.85Z" }, + { url = "https://files.pythonhosted.org/packages/ad/44/1f9e1b15e7a729062e0d0c3d0d7225ea4ab98b2267ef87287153be2495fc/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204f299afcbd65918ab78dbc52626b0ae45e9d8cef403fdbf33ecf9e40eac66e", size = 493616, upload-time = "2026-05-18T04:30:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/7e/55/8b1086dcc8a1d6a697a62767bd7ea368e74c61c6fd171683cfe24a3fe5d2/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:11743adfa510bfffebe97659fb280182b5c9b238708f667e866f308c3430dc19", size = 573154, upload-time = "2026-05-18T04:30:37.903Z" }, + { url = "https://files.pythonhosted.org/packages/14/7a/242f400cc77fafa7b18d53d19d9cb64fc6a6f61f28c55913bae7c674d92a/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb72919d93e3a16fc451d3aa3d4b1698423daca1b382d3d959c9ac51297c12a8", size = 467046, upload-time = "2026-05-18T04:30:41.869Z" }, + { url = "https://files.pythonhosted.org/packages/02/c8/79eee650c62d2c186598489814468e389b5def0ebe755399ff645b35b1b2/watchfiles-1.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62f042afde2dde21ec1d2c1a74361e804673df86f51e418a999c9acfe671b07", size = 457100, upload-time = "2026-05-18T04:31:13.064Z" }, + { url = "https://files.pythonhosted.org/packages/81/36/519f6dbb7a95e4fe7c1513ed25b1520295ef9905a27f1f2226a73892bfb7/watchfiles-1.2.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:027ae72bfdfd254862065d8b3e2a815c6ab9b1853ce41e6648ece84afd34a551", size = 467038, upload-time = "2026-05-18T04:30:32.915Z" }, + { url = "https://files.pythonhosted.org/packages/2f/12/951af6b9f89097e02511122258402cb3578443021930b70cf968d6310dc0/watchfiles-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e1cfd51e97e13ff3bd047c140764d277fc9b95b7cb5da59e46a47d167adab310", size = 632563, upload-time = "2026-05-18T04:30:11.539Z" }, + { url = "https://files.pythonhosted.org/packages/28/cc/0cba1f0a6117b7ec117271bdc3cb3a5a252005959755a2c09a745e0942cc/watchfiles-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:24b2405c0a46738dd9e1cf7135aa5dbdb9d42d024628651b3b13d5117e99f8df", size = 660851, upload-time = "2026-05-18T04:31:53.186Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f2/26347558cc8bf6877845e66b315f644d03c173906aa09e233a3f4fd23928/watchfiles-1.2.0-cp310-cp310-win32.whl", hash = "sha256:8c520725602756229f045b032a1ff33d7ef0f7404189d62f6c2438cb6d8ef6a1", size = 277023, upload-time = "2026-05-18T04:30:18.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/68/a5e67b6b68e94f4c1511d61c46c55eba0737583620b6febf194c7b9cc23f/watchfiles-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:03b14855c6f35539e2d95c442ae9530a75762f1e26567152b9ed05f96534a74d", size = 290107, upload-time = "2026-05-18T04:32:09.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3d/8024c801df84d1587740d0359e7fdd80afeae3d159011f3d5376dd82f18e/watchfiles-1.2.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:704fd259e332e01f9b9c178f4bce9e49027e5587cc2600eeeaf8e76e1c846201", size = 400242, upload-time = "2026-05-18T04:31:19.014Z" }, + { url = "https://files.pythonhosted.org/packages/87/5b/f4dfd45323e949984a3a7f9dc31d1cbb049921e7d98253488dda72ccdaa9/watchfiles-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6543cf55d170003296d185c0af981f3e1311564907e1f4e08671fc7693a890a5", size = 394562, upload-time = "2026-05-18T04:30:08.46Z" }, + { url = "https://files.pythonhosted.org/packages/98/d8/19483ef075d601c409bce8bcbb5c0f81a10876fff870400568f08ce484a1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89d8c2394a065ca86f5d2910ff263ae67c127e1376ccc4f9fc35c71db879f80a", size = 456611, upload-time = "2026-05-18T04:30:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6a/cc81fbe7ee42f2f22e661a6e12def7807e01b14b2f39e0ff83fd373fd307/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:772b80df316480d894a0e3165fdd19cf77f5d17f9a787f94029465ad0e3529d1", size = 461379, upload-time = "2026-05-18T04:31:29.292Z" }, + { url = "https://files.pythonhosted.org/packages/b1/57/7e669002082c0a0f4fb5113bb70125f7110124b846b0a11bc5ae8e90eac1/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d158cd89df6053823533e06fb1d73c549133bff5f0396170c0e53d9559340717", size = 493556, upload-time = "2026-05-18T04:30:05.44Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/f60a2b19807b21fe8281f3a8da4f59eef0d5f96825ac4680ba2d4f2ebf91/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d516b3283a758e087841aedb8031549fb41ced08f3db10aa6d2bf32dc042525b", size = 575255, upload-time = "2026-05-18T04:30:40.568Z" }, + { url = "https://files.pythonhosted.org/packages/bd/49/77f5b5e6efbcd57482f74948ebb1b97e5c0046d6b61475042d830c84b3ff/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:53b2290c92e0506d102cd448fbc610d87079553f86caa39d67440856a8b8bba5", size = 467052, upload-time = "2026-05-18T04:31:17.942Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5a/73e2959af1b97fd5d556f9a8bdba017be23ceeef731869d5eaa0a753d5a3/watchfiles-1.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a711b51aec4370d0dcda5b6c09463206f133a5759341d7744b953a7b62e1100e", size = 456858, upload-time = "2026-05-18T04:30:30.182Z" }, + { url = "https://files.pythonhosted.org/packages/50/57/1bc8c27fad7e6c19bddee15d276dbb6ab72480ec01c127afff1673aee417/watchfiles-1.2.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:e2ca07fa7d89195ec0865d3d285666286740bfa83d83e5cee204043a31ecc165", size = 467579, upload-time = "2026-05-18T04:32:15.897Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/3c2e44edba3553c5e3c3b8c8a2a6dee6b9e12ae2cf4bd2378bebf9dc3038/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e0618518f282c4ebff60f5e5b1247b6d91bb8b9f4476947563a1e74acc66f3c6", size = 633253, upload-time = "2026-05-18T04:31:37.123Z" }, + { url = "https://files.pythonhosted.org/packages/30/c2/d8c84a882ab39bbefcc4915ab3e91830b7a7e990c5570b0b69075aba3faf/watchfiles-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0d191c054d0715c3c95c99df9b8dbf6fd096d8c1e021e8f212e1bd8bc444ccb5", size = 660713, upload-time = "2026-05-18T04:31:24.62Z" }, + { url = "https://files.pythonhosted.org/packages/a9/07/f97736a5fc605364fe67b25e9fa4a6965dfd4840d50c406ada507e9d735f/watchfiles-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9342472aff9b093c5acd4f6d8f70ae0937964ab56542502bcf5579782da69ae8", size = 277222, upload-time = "2026-05-18T04:31:21.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/99/2b04981977fc2608afd60360d928c6aecf6b950292ca221d98f4005f6694/watchfiles-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:dbd6c97045dad81227c8d040173da044c1de08de64a5ea8b555da4aee1d5fa22", size = 290274, upload-time = "2026-05-18T04:31:45.966Z" }, + { url = "https://files.pythonhosted.org/packages/3c/74/f7f58a7075ee9cf612b0cfcddb78b8cd8234f0742d6f0075cf0da2dde1c6/watchfiles-1.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:57a2d9fa4fb4c2ecae57b13dfff2c7ab53e21a2ba674fe9f05506680fcdcc0d7", size = 283460, upload-time = "2026-05-18T04:31:39.126Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/23/f4/7513ef1e85fc4c6331b59479d6d72661fc391fbe543678052ac72c8b6c19/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4674d49eb94706dfe666c069fc0a1b646ffcf920473492e209f6d5f60d3f0cc2", size = 403050, upload-time = "2026-05-18T04:30:36.753Z" }, + { url = "https://files.pythonhosted.org/packages/27/0b/a54103cfd732bb703c7a749222011a0483ef3705948dae3b203158601119/watchfiles-1.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:094b9b70103d4e963499bdea001ee3c2697b144cd9ae6218a62c0f89ec9e31db", size = 396629, upload-time = "2026-05-18T04:32:03.268Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/73f31a3b893886206c3f54d73e8ad8dee58cdb2f69ad2622e0a8a9e07f4e/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0ef001f8c25ad0fa9529f914c1600647ecd0f542d11c19b7894768c67b6acb7", size = 457318, upload-time = "2026-05-18T04:31:01.932Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f9/45d021e4a5cc7b9dd567f7cbb06d3b75f751a690063fb6cc7ec60f4e46b7/watchfiles-1.2.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a88fc94e647bc4eec523f1caa540258eb71d14278b9daf72fa1e2658a98df0f0", size = 457771, upload-time = "2026-05-18T04:30:56.331Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/74/221f58decd852f4b59cc3354cccaf87e8ef695fede361d03dc9a7396573b/websockets-16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:04cdd5d2d1dacbad0a7bf36ccbcd3ccd5a30ee188f2560b7a62a30d14107b31a", size = 177343, upload-time = "2026-01-10T09:22:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/22ef6107ee52ab7f0b710d55d36f5a5d3ef19e8a205541a6d7ffa7994e5a/websockets-16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8ff32bb86522a9e5e31439a58addbb0166f0204d64066fb955265c4e214160f0", size = 175021, upload-time = "2026-01-10T09:22:22.696Z" }, + { url = "https://files.pythonhosted.org/packages/10/40/904a4cb30d9b61c0e278899bf36342e9b0208eb3c470324a9ecbaac2a30f/websockets-16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:583b7c42688636f930688d712885cf1531326ee05effd982028212ccc13e5957", size = 175320, upload-time = "2026-01-10T09:22:23.94Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2f/4b3ca7e106bc608744b1cdae041e005e446124bebb037b18799c2d356864/websockets-16.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d837379b647c0c4c2355c2499723f82f1635fd2c26510e1f587d89bc2199e72", size = 183815, upload-time = "2026-01-10T09:22:25.469Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/d40eaa2a46d4302becec8d15b0fc5e45bdde05191e7628405a19cf491ccd/websockets-16.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df57afc692e517a85e65b72e165356ed1df12386ecb879ad5693be08fac65dde", size = 185054, upload-time = "2026-01-10T09:22:27.101Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ba/6500a0efc94f7373ee8fefa8c271acdfd4dca8bd49a90d4be7ccabfc397e/websockets-16.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2b9f1e0d69bc60a4a87349d50c09a037a2607918746f07de04df9e43252c77a3", size = 184565, upload-time = "2026-01-10T09:22:28.293Z" }, + { url = "https://files.pythonhosted.org/packages/04/b4/96bf2cee7c8d8102389374a2616200574f5f01128d1082f44102140344cc/websockets-16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:335c23addf3d5e6a8633f9f8eda77efad001671e80b95c491dd0924587ece0b3", size = 183848, upload-time = "2026-01-10T09:22:30.394Z" }, + { url = "https://files.pythonhosted.org/packages/02/8e/81f40fb00fd125357814e8c3025738fc4ffc3da4b6b4a4472a82ba304b41/websockets-16.0-cp310-cp310-win32.whl", hash = "sha256:37b31c1623c6605e4c00d466c9d633f9b812ea430c11c8a278774a1fde1acfa9", size = 178249, upload-time = "2026-01-10T09:22:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/b4/5f/7e40efe8df57db9b91c88a43690ac66f7b7aa73a11aa6a66b927e44f26fa/websockets-16.0-cp310-cp310-win_amd64.whl", hash = "sha256:8e1dab317b6e77424356e11e99a432b7cb2f3ec8c5ab4dabbcee6add48f72b35", size = 178685, upload-time = "2026-01-10T09:22:33.345Z" }, + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[package]] +name = "wordcloud" +version = "1.9.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pillow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/04/a3d3c4b94a35586ddb97c6a3c508913159161cd558b34f315b382b924bf7/wordcloud-1.9.6.tar.gz", hash = "sha256:df17c468ff903bd0aba4f87c6540745d13a4931220dd4937cb363ad85a4771b9", size = 27563741, upload-time = "2026-01-22T02:08:52.976Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/c8/ff2453f332f7e61bfaebefb1b1967c4f05cee15c3b1e5f3edea36fb7c351/wordcloud-1.9.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:41dfe1c6b30f731225ce67697b0589df5539032b82a4c334f94ead3151d589aa", size = 169057, upload-time = "2026-01-22T02:07:36.597Z" }, + { url = "https://files.pythonhosted.org/packages/0e/87/cd17e9ff9014c5328e3191c7832d18bd3b010eb25a493dd2ec7ca2cf6296/wordcloud-1.9.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6c4677c4b70e800ce3ff84b904db2ab9a30a6ad03898c668a71507da9ce14ad7", size = 168470, upload-time = "2026-01-22T02:07:38.063Z" }, + { url = "https://files.pythonhosted.org/packages/1d/55/583ad9135080934f391f76f97c80ea68f0e85ef667f7297cc55a5ae05bb1/wordcloud-1.9.6-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b200b26bc746fa97c52dc8eba2bdca95c95366ddca51df167bd35a16c48a678", size = 522915, upload-time = "2026-01-22T02:07:39.409Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/6d3b313d98012b1a6e8f194a0116e6f92f7d30934cf7d7848db59989c969/wordcloud-1.9.6-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:682fd4bfc2f0c262c1ccf2bbc0ed503361f40c3dcd2e3dbe9ec3c233916cada6", size = 526203, upload-time = "2026-01-22T02:07:40.58Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5b/e5b1b492ae795345c4984b7944cd5c5db00d95b35b0b74d1d39388e311fa/wordcloud-1.9.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f41dcda0ca3a5b3c220ee9a928db7fe3b6bcfc534e5b9fcdac8afebb59a18f1", size = 518357, upload-time = "2026-01-22T02:07:42.097Z" }, + { url = "https://files.pythonhosted.org/packages/54/0b/7d0d433dc309ac82f15a911bb3f9f7bea05e9560861586b8cc5e498bc148/wordcloud-1.9.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d63b1dcd3bc3c5f68ef591f454cae13c0d613a91b86db3d62f5f420b333fbedb", size = 529531, upload-time = "2026-01-22T02:07:43.928Z" }, + { url = "https://files.pythonhosted.org/packages/29/14/e822d6b96b9552b97dd3db8231ef57d33b63325eca4c3eb39499ff56707d/wordcloud-1.9.6-cp310-cp310-win32.whl", hash = "sha256:5746286fb0506fd9731ec35c25046e6437db9f1e85155cdba113ac184625b3f8", size = 295856, upload-time = "2026-01-22T02:07:45.352Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2a/16bab06c328f1b0fd4c0067ebcb73f31cc1c7dd3a5699f99fb8bf8e4530a/wordcloud-1.9.6-cp310-cp310-win_amd64.whl", hash = "sha256:2c9702fa654c9305d1bff86043147ee19661da145218e7b0114cb4f6d1459462", size = 306215, upload-time = "2026-01-22T02:07:46.635Z" }, + { url = "https://files.pythonhosted.org/packages/54/6b/369ba57a28b4233ff517eb18633e9f0b35f0b9851afe2a0dcb84b05739d3/wordcloud-1.9.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6eab5eb4caa4bda125dd3acc8f71697617c26c2a2203d8fcdaf2a92a7d12a4a8", size = 168799, upload-time = "2026-01-22T02:07:48.129Z" }, + { url = "https://files.pythonhosted.org/packages/d0/e9/259b1ea381d866bc56963945d494da1589a64cda5443d3989fe8926548ab/wordcloud-1.9.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bc9ac1ad23ef76c1d1fbbeafbfb6125d2d63a9346bac642ca201cbc457da8f8a", size = 168419, upload-time = "2026-01-22T02:07:49.443Z" }, + { url = "https://files.pythonhosted.org/packages/6f/84/bb64813cd1ffc255a9d8f4a4faf9daa283e9ad0ac127366df716ee2b1719/wordcloud-1.9.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33e3c214381244043d5921ba653ccf7e7407c3f97915dc99c639ebc4acd47dce", size = 547809, upload-time = "2026-01-22T02:07:51.077Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7e/773bcc351664eb980ea36e56e8f1ecb62ce657e0936b9971e66f17065819/wordcloud-1.9.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1c31741b7612d9f408551434d4164e75d2e6b8542b59ec53ea77f773286713a", size = 551314, upload-time = "2026-01-22T02:07:52.151Z" }, + { url = "https://files.pythonhosted.org/packages/93/62/4f4e6ae70bad5ff39fff4cbfd97b7a6e0807db8557ea1faf09643a828cf3/wordcloud-1.9.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec394e02202e84550f50ab88cba5683f54775f89998dd46b8e41e297a9f1735c", size = 544345, upload-time = "2026-01-22T02:07:53.64Z" }, + { url = "https://files.pythonhosted.org/packages/b7/44/2ab3357b1f161e317c83f4e179a576f9c247c1cce41a9bef1fe89e01e6b1/wordcloud-1.9.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dc80e742a95af65bae9556b5ec77837a104b12aeadb4772dedbd352f9dfc3e7a", size = 555174, upload-time = "2026-01-22T02:07:55.256Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c9/663e9468b34a402e0861b5c2a2eeb4d71e5c60a81c35b2f4dcc9a9af8649/wordcloud-1.9.6-cp311-cp311-win32.whl", hash = "sha256:c96ba0b5d7194322e88bc2bfbaf653cd3de9a5dbe3f78e07ea365ac40cd2f987", size = 295601, upload-time = "2026-01-22T02:07:56.271Z" }, + { url = "https://files.pythonhosted.org/packages/45/70/0041966d469dec79036ad3962b83b007004b842531ee7c41bdba61310eb6/wordcloud-1.9.6-cp311-cp311-win_amd64.whl", hash = "sha256:8a1b3b15509e05c1c3322a205108f7da31ca06bbcf979c104e1a7b9b9b76fff2", size = 306051, upload-time = "2026-01-22T02:07:57.662Z" }, + { url = "https://files.pythonhosted.org/packages/7d/0c/1df77d67d1cc990f83b70708b002fc8378779c94b5d0a80e570c5ead04b2/wordcloud-1.9.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e51ebaeb6ce337b26ce4ba7e5eb3359981a8a648713301a252f49dbab5fe56cb", size = 170137, upload-time = "2026-01-22T02:07:59.172Z" }, + { url = "https://files.pythonhosted.org/packages/04/72/1aeb291fd5965826e478b0efd8bcb4351e8a2434f366416537096cd41a0d/wordcloud-1.9.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba607e25f7ab78085e6c7a9b3d9cb5eb637e73560e5a8b4f6924705d64a76b0e", size = 168932, upload-time = "2026-01-22T02:08:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/f2/28/a011d949b6cba617a6aaf31994afc81d38a467510bc76be4e96a37808a62/wordcloud-1.9.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fce2f0fb0469623db85e1e974ea64f51e78758c4d8e84ed0b4344530d1ba8ab", size = 547540, upload-time = "2026-01-22T02:08:01.744Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b9/916484ac803dbdbcd0f8669a6363264a438801feff938d5f3f209521ee2b/wordcloud-1.9.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f534204038811676890bc91c10bcca0b04c6933011c250b4b09323d2a0b0a8c1", size = 554869, upload-time = "2026-01-22T02:08:03.054Z" }, + { url = "https://files.pythonhosted.org/packages/d0/84/a1e23051927588e9567da232adfd54485a7def7957bb23287b89398e5050/wordcloud-1.9.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:dfea5803c6e2b540da04f9693da93fd90d9babeed284950ab720487eb7b44942", size = 538205, upload-time = "2026-01-22T02:08:04.293Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ae/4926fa61265cd492ee504ec1ac9880b8840eda2c104c06e57f516f883f90/wordcloud-1.9.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11b55fbcb2fa5db7e876887858fdf6480f21300b4384ff610c3aa9c0ef420ae9", size = 554626, upload-time = "2026-01-22T02:08:05.363Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fc/f388ba4a7ec09a2d2c2c0f1cb2cbf158fe5850aa1f03831e854782a31c03/wordcloud-1.9.6-cp312-cp312-win32.whl", hash = "sha256:1200af0c9be744c9e70fd7305c80d4b317fbcb1d41cf9b72a24d648e70ad598c", size = 296150, upload-time = "2026-01-22T02:08:06.423Z" }, + { url = "https://files.pythonhosted.org/packages/b1/6a/47d0d8c5ca74400750797ae8fd13f200204294e008e1235e51814e732b09/wordcloud-1.9.6-cp312-cp312-win_amd64.whl", hash = "sha256:7977a1727e059d6ba0a679dacbab57a966ab28913fc1764079efdfdc67f8e4d2", size = 307222, upload-time = "2026-01-22T02:08:07.786Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a5/067c0a7c75db885c573b80834deb16b63a8a145146916438b640439eaa46/wordcloud-1.9.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:706d19a085170151b1deade56fcc1b367e809b4ebea76f2ee39ecb95d45b2fcc", size = 169349, upload-time = "2026-01-22T02:08:09.01Z" }, + { url = "https://files.pythonhosted.org/packages/65/bd/54e8ef889a73f47ac0216b8acc774bf7b260dbad4cd0a62f8638d43730ca/wordcloud-1.9.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ec02d1a44040d32a21acdeaf3fe7d1c4f4ad6d42fd417b13c7c60b41403c6978", size = 168343, upload-time = "2026-01-22T02:08:10.055Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/e306dc81577e540b14e88f44fae111a3dd2542f04bcc770660a22b03da7a/wordcloud-1.9.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:122d01c617cac7a6620acdcf182ff117df1e08dc31d1947ac669c5af1946c9bf", size = 543612, upload-time = "2026-01-22T02:08:11.829Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/b70f403be0a6fb722fd6654b0905d0b4914fec6a5c5a11525715dc5facd9/wordcloud-1.9.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cad69c24ae6b3f33eae5ebff012ad6c5dd7e79c0f6f768646676722281acf8f", size = 550736, upload-time = "2026-01-22T02:08:13.383Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1d/1c5aef5da9a90fb4859ca014d4068a5d5d2a310868e2f94b0c1cbac965fa/wordcloud-1.9.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7f5fef15420e4fd1c3ba18766cd7225f6310a780602798cdd35046b004e6adb8", size = 536274, upload-time = "2026-01-22T02:08:14.467Z" }, + { url = "https://files.pythonhosted.org/packages/23/af/fb4e76467a8c992d873da67e2e2fef6d63d47d2424fcb426ba7c28dadee3/wordcloud-1.9.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b82ee3bf54d1f324346c7eebe4d0146748b913e790ddb661679d7559fcc0769", size = 551984, upload-time = "2026-01-22T02:08:16.499Z" }, + { url = "https://files.pythonhosted.org/packages/0c/94/1a9760be65dcd2f9152a36b3935182d748d06195c5cd535baf4498fc8d94/wordcloud-1.9.6-cp313-cp313-win32.whl", hash = "sha256:3a245498f429b4b37e909e5410887da8aed697f1d8b70c0a6d7f37ac7188654d", size = 296019, upload-time = "2026-01-22T02:08:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4a/a9cd73b01af02fa84265cf35e19ce91c31f9f7c538325115fe258bf0ada3/wordcloud-1.9.6-cp313-cp313-win_amd64.whl", hash = "sha256:3d3c5b0b5f66a385300dacb5ba2c2dba67c18c332b934fdac08261c1c7ee7d7a", size = 306987, upload-time = "2026-01-22T02:08:19.821Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a4/da39308bffd24e82761d804797f04d428011b4bb3be51135177a5b884842/wordcloud-1.9.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5cd785127483b835d22c7e7a235fbd925d01fcd2846c2eacf45d715e32775563", size = 169670, upload-time = "2026-01-22T02:08:21.127Z" }, + { url = "https://files.pythonhosted.org/packages/64/72/a703bd2fbc79fa6ae78aaee34d01e24d0324e5874c0a7918c73f27857f5c/wordcloud-1.9.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f9bfe99fde048e109343858a7a866bdccd95f70cabe171aa7fd9fb7609bff12b", size = 168911, upload-time = "2026-01-22T02:08:22.238Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f8/fc2b3f5689a91aeab55bd47f0022371e41ee41e2a705eebbc2a0981a8c60/wordcloud-1.9.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bfc67c14d5af51a8ddf2a36be85f4ef017c835f3f37a11ab7ef1a898c950f8b4", size = 542948, upload-time = "2026-01-22T02:08:23.548Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cf/f15a13027b0d976ebcbb1c1f3c0a52aaa93a06a84952859e12d0cb7079f8/wordcloud-1.9.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2998a9a6ea9dc18c81a980403d03ed97822c971bd971b2faa3b5ee2da047f237", size = 546260, upload-time = "2026-01-22T02:08:24.612Z" }, + { url = "https://files.pythonhosted.org/packages/59/f5/290bd0b7e039f3b94e9961fff6acabcb761bef27d0a65516423e014bbfec/wordcloud-1.9.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e11c048f9056a9dda20627c29c578cb34f6778b152d0f0eb0dd33581faf03f65", size = 535535, upload-time = "2026-01-22T02:08:25.828Z" }, + { url = "https://files.pythonhosted.org/packages/1c/93/6eb7bae66bc2b34e4e2f5bd1c5cae8ddc789255a163c15a816b602519fc2/wordcloud-1.9.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aaaebdc056052fc3f21e3aebc6653fc76ee6c4fdcd5785a9991ce186eb15a776", size = 548616, upload-time = "2026-01-22T02:08:27.367Z" }, + { url = "https://files.pythonhosted.org/packages/63/89/8001d176085d6d31b107b58c29b7648898d6af9cb23b9d76d67d83cf88f3/wordcloud-1.9.6-cp314-cp314-win32.whl", hash = "sha256:eabd94c69435b19163991da8e71310bb4873e31982e54ff4cf62317f0e1223b0", size = 297137, upload-time = "2026-01-22T02:08:28.391Z" }, + { url = "https://files.pythonhosted.org/packages/74/8e/b9ff7ab3dc030cbf7b2737adc5eddc847b99c8665a45007b25e558cfff8b/wordcloud-1.9.6-cp314-cp314-win_amd64.whl", hash = "sha256:8549f85a93626f5d03c06e63106ce228910008becd1e1f3b49693d13e33a5873", size = 308629, upload-time = "2026-01-22T02:08:29.662Z" }, + { url = "https://files.pythonhosted.org/packages/e7/64/2183079c0eca58a211f1456f524bcb283fafc65f1ccae54f412b07efab52/wordcloud-1.9.6-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a4070ef896396d3fbb7a71c775c08138d61b85ea4d414e86b4e132f8736f20f6", size = 174014, upload-time = "2026-01-22T02:08:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e1/397cb2e0e2c9424841ace579edbe96d133291496f8312f24d70a855b36d3/wordcloud-1.9.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:436b99261ef31369019b989137a940835de887de52dce5b63786ef13fb82e18a", size = 174152, upload-time = "2026-01-22T02:08:32.053Z" }, + { url = "https://files.pythonhosted.org/packages/bf/5c/a0570972c5951c6586dbe9b25b87914f5376406b4da9ec877bce9b0fcf47/wordcloud-1.9.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4804bee501b85b6f6015c434879a3e9ef2795b97c00aec387a0d6adfcaa2ca5", size = 559434, upload-time = "2026-01-22T02:08:33.192Z" }, + { url = "https://files.pythonhosted.org/packages/89/00/0d5d6731c98312c5ceef83dbdc50a34479b63377c8258e17b613e37fead0/wordcloud-1.9.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:33a4b3dcbd9095a968dd3201bc6667d9788ec7b949f6d8356985a8fc64a0590b", size = 551605, upload-time = "2026-01-22T02:08:34.633Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f9/7089b537fe791447abce3bbbf89edf9cd6585a25f80764bcf386b2a245b4/wordcloud-1.9.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74c9e5cb52c35aa822ff7f3251a633aa52b830ba206be275c1da743de255a15c", size = 542705, upload-time = "2026-01-22T02:08:35.726Z" }, + { url = "https://files.pythonhosted.org/packages/78/77/a14ab3680c08ca585b25c1549ca9b3ca52a078e82450ad513475d9edffbc/wordcloud-1.9.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:337b73155c60fc536cab8a998bd8312f81c99524716c86e3e43f3b6408f42bdc", size = 545800, upload-time = "2026-01-22T02:08:37.105Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a7/bb2bbc36739472e8328dbe4246c5b7593b3fa4f8b77a1214dc3ad8e0a7fd/wordcloud-1.9.6-cp314-cp314t-win32.whl", hash = "sha256:32f93e42b44dca992eb5f692ddd258f043465f49d9a53e6aa3d4853fca615e23", size = 306383, upload-time = "2026-01-22T02:08:38.627Z" }, + { url = "https://files.pythonhosted.org/packages/8c/fd/2704f0be5f4913c623b283a1c92016b9ce93cab5ea0f6e86e8517c617c32/wordcloud-1.9.6-cp314-cp314t-win_amd64.whl", hash = "sha256:22cf91490bcc0fa23585acbab1906a44a438fa7dd4d9a2b2663f39c8650634a6", size = 320391, upload-time = "2026-01-22T02:08:40.094Z" }, +] + +[[package]] +name = "wrapt" +version = "2.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/a4/282c8e64300a59fc834518a54bf0afabb4ff9218b5fa76958b450459a844/wrapt-2.2.2.tar.gz", hash = "sha256:0788e321027c999bf221b667bd4a54aaefd1a36283749a860ac3eb77daed0302", size = 129068, upload-time = "2026-06-20T23:49:44.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/8b/59781d0fe7b0adfbea37f600857de4be68921e454aeecf1a11bda35cdccc/wrapt-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:055e6fcfaa28e58c6a8c247d48b92be9d56f818b7068aa4f22b15b3343a09931", size = 80556, upload-time = "2026-06-20T23:47:28.473Z" }, + { url = "https://files.pythonhosted.org/packages/94/dc/66c61aca927230c9cf97a3cb005c803971a1076ff9f7d61085d035c20085/wrapt-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8374eb6b1a58809211e84ff835a182bb17ab2807a5bfef23204c8cff38178a00", size = 81648, upload-time = "2026-06-20T23:47:30.504Z" }, + { url = "https://files.pythonhosted.org/packages/23/1b/545eee1c18f3af4cf140bb5822b6ef81ebe569df0a63ac109973103a30a5/wrapt-2.2.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:656593bb3f5529f03d27af4136c4d7b11990e470bcbc6fefa5ef218695bece55", size = 152956, upload-time = "2026-06-20T23:47:31.867Z" }, + { url = "https://files.pythonhosted.org/packages/44/a7/6f42a3d03e44dc612a5dcff324e7366075a7857f0be2d49a8cb8a68279b8/wrapt-2.2.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfb00cb7bb22099e2f64b7340fb96113639aa7260c0972af3797ace2297b936c", size = 154771, upload-time = "2026-06-20T23:47:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/bf/55/4d76175aaa97523c38f1d28f79d18ab41a1b116814158a818bc0eba00571/wrapt-2.2.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e7f10ee0bd53673bfd52b67cbce83336fe6cad90d2377b03baf66491d2bbfb91", size = 149460, upload-time = "2026-06-20T23:47:34.712Z" }, + { url = "https://files.pythonhosted.org/packages/84/9b/12e23264d8f4735e8483262f95c5a6b03c3665fd2a84bdf99a45b6a2f4ec/wrapt-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4402f57c5f0d0579599858ffbdd9bf4e3f0972f51096f2bd6cc7dab6b76ee49e", size = 153648, upload-time = "2026-06-20T23:47:36.092Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a3/bcd5ec37289dcd85ecd4d15395a6a6063d60bc45ff94a9d77814e1e54d64/wrapt-2.2.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:3a4eb7964ff4643d333c84f880bcf554652b2a1050aebc54ae696327f61acfaf", size = 148502, upload-time = "2026-06-20T23:47:37.623Z" }, + { url = "https://files.pythonhosted.org/packages/f2/be/716d708f607fa70f8a6eb47dff8ee945d5278dfc89ffeeff33039d052e63/wrapt-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e542b7c5af91e2123a8aabf19894319d5ec4268d2a9ffd2f239386133fc47746", size = 152238, upload-time = "2026-06-20T23:47:39.118Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c0/1a48e7e54501274f5d906f18372221b13183b0afbb5b8bb4c7ca0392c0b4/wrapt-2.2.2-cp310-cp310-win32.whl", hash = "sha256:6e7e45b43d3c774d244fe7264378f5a3f0f383bc55a54a9866434e524540110f", size = 77278, upload-time = "2026-06-20T23:47:40.476Z" }, + { url = "https://files.pythonhosted.org/packages/b0/82/9cd69a1af288fbdedf01a10e3c8a0b6890b08c7f3f96d36a213699dbcd94/wrapt-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:955f1d6e72a352e478de8d8b503abe301c5e139a141b62eb0923bd694995025f", size = 80131, upload-time = "2026-06-20T23:47:41.785Z" }, + { url = "https://files.pythonhosted.org/packages/7f/73/8db7e27daef37ae70a53ea62bef7fe80cc51a8b5e9e9181a8be6eb9a999c/wrapt-2.2.2-cp310-cp310-win_arm64.whl", hash = "sha256:b89d8d73c82db2bb7e6090b3afd7973f980d24e905cc34394eab60b884b3bf67", size = 79615, upload-time = "2026-06-20T23:47:43.109Z" }, + { url = "https://files.pythonhosted.org/packages/27/15/0c2d55168707465abfc41f33c0b23d792a5fa9b65c26983606940900a120/wrapt-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f1a2ff355ece6a111ca7a20dc86df6659c9205d3fcee674ca34f2a2854fd4e73", size = 80782, upload-time = "2026-06-20T23:47:44.367Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b5/5c0b093eb48f8a062ef6267d3cb36e9bb1b88440181f6545a383c60efdf8/wrapt-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55b9a899e6fff5444f229d30aa6e9ac92d2216d9d60f33c771b5d76a760d5f8e", size = 81678, upload-time = "2026-06-20T23:47:45.857Z" }, + { url = "https://files.pythonhosted.org/packages/34/f3/de70937472dd3e8a4e6811192f9c6075efdffd4a2cd9b4596bf160f89668/wrapt-2.2.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a2d78c363f97d8bd718ee40432c66395685e9e98528ccaa423c3355d1715a26d", size = 159671, upload-time = "2026-06-20T23:47:47.345Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ec/40aed2330e7f02ecf74386ffcfef9ccb7108c6a430f15b6a252b663b1bed/wrapt-2.2.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d619e1eed9bd4f6ed9f24cd61971aa086fa86505289628d464bcf8a2c2e3f328", size = 160785, upload-time = "2026-06-20T23:47:48.759Z" }, + { url = "https://files.pythonhosted.org/packages/45/04/aa5309beed5344b00220ae6b3b24055852192656194c27947bee1736306a/wrapt-2.2.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:518b0c5e323511ec56a38894802ddd5e1222626484e68efe63f201854ad788e5", size = 153699, upload-time = "2026-06-20T23:47:50.177Z" }, + { url = "https://files.pythonhosted.org/packages/01/df/2def7e99d1fe87eea413f95f671924cdddcb08823b1ffd212748dfa6d062/wrapt-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4bccea5cdecffa9dd70e343741f0e41e0a16619313d04b72f78bb525162ebcd0", size = 159695, upload-time = "2026-06-20T23:47:51.602Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f6/a906d01a2ce12157bad2404957b3e2140da354b8a70b2fa48bbf282871c0/wrapt-2.2.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:209112cafd963710a05d199aae431d79a28bc76eb8e6d1bbbb8ad24340722cae", size = 152813, upload-time = "2026-06-20T23:47:53.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/49/bc0086292d239575b4c08f4cf8a4079fa58abbad58ec23abf84833a283ed/wrapt-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5a5290e4bf2f332fc29ce72ffb9a2fff678aaac047e2e9f5f7165cd7792e099", size = 158809, upload-time = "2026-06-20T23:47:54.391Z" }, + { url = "https://files.pythonhosted.org/packages/55/83/8fbd034de1f3e907edaa18786d5dd8f6932874edee0826c7cecb5cab03a1/wrapt-2.2.2-cp311-cp311-win32.whl", hash = "sha256:5499236ad1dc116012e2a5dd943f3f31af12fce452128e2bbcbd55a7d3d4d14c", size = 77414, upload-time = "2026-06-20T23:47:55.882Z" }, + { url = "https://files.pythonhosted.org/packages/7e/9c/23695baa331c6de4e874c3d78b8e0bed92e1d2a274e665b29858f6841672/wrapt-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:8636809939152be6ae20a6cef0fed9fe60f411b47847d0426a826884b469e971", size = 80368, upload-time = "2026-06-20T23:47:57.237Z" }, + { url = "https://files.pythonhosted.org/packages/08/49/40cefc342bf89b234a4490d741290fce781774b831aefb39c25471da96c9/wrapt-2.2.2-cp311-cp311-win_arm64.whl", hash = "sha256:5d0a142f7af07caeb5e5da87493162a7b8efa19ba919e550a746f7446e13fb30", size = 79489, upload-time = "2026-06-20T23:47:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/2a/85/180b40628b23772692a0c76e8030114e1c0ae068470ed531919f0a5f2a4a/wrapt-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8417fd3c674d3c8023d080292d29301531a12daf8bd938dd419710dd2f464f2b", size = 81484, upload-time = "2026-06-20T23:47:59.924Z" }, + { url = "https://files.pythonhosted.org/packages/94/f2/21c90f2a16689702e2aaff45795b11018dff2c9b1242bac10d225483f676/wrapt-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e7070c7472582e31af3dfc2622b2381a0df7435110a9388ed8db5ffbce67efb", size = 82151, upload-time = "2026-06-20T23:48:01.303Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b3/7e6e9fcf4fe7e1b69a49fe6cc5a44e8224bab6283c5233c97e132f14908e/wrapt-2.2.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2e096c9d39a59b35b63c9aacfbbbec2088ff51ff1fc31051acc60a07f42f273a", size = 169828, upload-time = "2026-06-20T23:48:02.719Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/894f132d857ed5a9904d937baf368badcbe5ea9e436e2f1930fe21c9f1f0/wrapt-2.2.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d1a6050405bf334be33bf66296f113563622972a34900ae6fa60fd283a1a900", size = 171544, upload-time = "2026-06-20T23:48:04.266Z" }, + { url = "https://files.pythonhosted.org/packages/29/de/3c833e03725b477e9ea34028224dd21a48781830101e4e036f77e8b6b102/wrapt-2.2.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10adb01371408c6de504a6658b9886480f1a4919a83752748a387a504a21df79", size = 160663, upload-time = "2026-06-20T23:48:05.708Z" }, + { url = "https://files.pythonhosted.org/packages/33/be/27edce350b24e3054d9d047f65f16d4c4d4c1f3f31c4278a1f8a95c723c8/wrapt-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3442eee2a5798f9b451f1b2cd7518ce8b7e28a2a364696c414460a0e295c012a", size = 169387, upload-time = "2026-06-20T23:48:07.243Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c4/9fd9679af8bf38e146652c7f47b6b352c3e5795b4ad1c0b7f94e15ac2aa7/wrapt-2.2.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:6c99012a22f735a85eed7c4b86a3e99c30fdd57d9e115b2b45f796264b58d0bf", size = 158849, upload-time = "2026-06-20T23:48:08.91Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c2/aa6c0c2206803068c6859dabe01f8c84c43744da93d4c67b8946d21655ee/wrapt-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b686cfc008776a3952d6213cb296ed7f45d782a8453936406faa89eac0835ab", size = 168147, upload-time = "2026-06-20T23:48:10.374Z" }, + { url = "https://files.pythonhosted.org/packages/42/63/3eb25da41049d20ae18fcab2dd8b056e02387c4bfa626cbdfb7c3b872e4f/wrapt-2.2.2-cp312-cp312-win32.whl", hash = "sha256:ef2cce266b5b0b07e19fa82e59673b81142b7a3607c8ed1254113d048ed668da", size = 77734, upload-time = "2026-06-20T23:48:11.769Z" }, + { url = "https://files.pythonhosted.org/packages/da/09/0390e008a305360948fa9ce69507d041ac12cb2ee5d28e34467e2ee79391/wrapt-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:abf8c20a2d72ee69e16328b3c91342c446e723bfe48bfcc4dded3b9722ac027f", size = 80585, upload-time = "2026-06-20T23:48:13.117Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/84c445c66969f2d3457276b183a48c91097d59bbef9af6c075366b0f8c36/wrapt-2.2.2-cp312-cp312-win_arm64.whl", hash = "sha256:c6c64c5d02578bc4c4bca4f0aef1504de933c1d5b4ac2710b9131111459506c8", size = 79553, upload-time = "2026-06-20T23:48:14.5Z" }, + { url = "https://files.pythonhosted.org/packages/43/fc/f32f4b22c6511173c11d9e541ab4e7d8467a0f1b3455acaf784115d31ff8/wrapt-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9e8b648270c613720a202d9a45ebabc33261b22c3a839b115ac5bce8c0bb0d69", size = 81296, upload-time = "2026-06-20T23:48:15.881Z" }, + { url = "https://files.pythonhosted.org/packages/72/06/4d117d5d77a9344776c0248b24dae3d3dd2f58e5f765fa08cf887072e719/wrapt-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6fb7e94e8fe3e4c3067bb1653a91cce7c5e83acc119fdd41501b1bf74654617", size = 81841, upload-time = "2026-06-20T23:48:17.262Z" }, + { url = "https://files.pythonhosted.org/packages/15/ff/63ad96f98eb58a742b1a20d80f21da88924405910149950b912368150468/wrapt-2.2.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb18fc51e813df0d9c98049e3bf2298a5495a648602040e21fa3c7329371159e", size = 167882, upload-time = "2026-06-20T23:48:18.764Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/8bb62d8933df7acf3247194e6e9fc68edf9d2fa203252c89c94b319dd472/wrapt-2.2.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b00b00f806eb3ef2abe9049ed45994a81ee9284884d96e6b8314927c6cea3d", size = 167411, upload-time = "2026-06-20T23:48:20.315Z" }, + { url = "https://files.pythonhosted.org/packages/17/09/8789dcb09ee1de715727db7521aabbb68ffa68dfade3a49468440cfced49/wrapt-2.2.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:62415fd095bc590b842b6d092f2b5d9ccbaeb7e0b28535c03dcea2718b48636b", size = 158607, upload-time = "2026-06-20T23:48:21.728Z" }, + { url = "https://files.pythonhosted.org/packages/9c/20/66e02562d53ee67d841f175e38e3c993c2d78a3e104c576cad61c028b43c/wrapt-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a41e758d80dc0ab8c210f641ac892009d356cf1f955d97db544c8dd317b4d14c", size = 166367, upload-time = "2026-06-20T23:48:23.177Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a3/832ac4e41222fb263b3042d42c2f08d305db7d0f0c9b1d3a271a9eede8f6/wrapt-2.2.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b84cd4058001c9727b0e9980b7a9e66325b5ca748b1b578e822cade1bc6b304f", size = 157176, upload-time = "2026-06-20T23:48:24.711Z" }, + { url = "https://files.pythonhosted.org/packages/b7/01/1bd5e4d2df9c0178989ac8da9186543465388588ee2ef153e2591accebef/wrapt-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:26fc73a1b15e0946d2942b9a4426d162b51676338327dc067ccd8d2d76385f94", size = 167025, upload-time = "2026-06-20T23:48:26.118Z" }, + { url = "https://files.pythonhosted.org/packages/1c/69/583ed25291ab53e1ec117135fb1c33425e2f46d2bc8f29c17f7a94cf4274/wrapt-2.2.2-cp313-cp313-win32.whl", hash = "sha256:3c4095803491f6ef72128914c28ec05bbad9758433bb35f6715a3e9c8e46fb2d", size = 77605, upload-time = "2026-06-20T23:48:27.643Z" }, + { url = "https://files.pythonhosted.org/packages/29/68/e69fc6d06e1523c68e0d00f95c9aed1158ce9908ee41603f7f2eae3d5db6/wrapt-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:2cb07f414fab25dbe6b5c7398e1491423a5c81a6209533639969a6c928d474a4", size = 80508, upload-time = "2026-06-20T23:48:29.013Z" }, + { url = "https://files.pythonhosted.org/packages/55/21/fe7a393d9e5dc0923bed8f5d857e9dcff210f1fa0888c02cc8f3ffaa55aa/wrapt-2.2.2-cp313-cp313-win_arm64.whl", hash = "sha256:1fc7691f070220215cccb2a20836b9adbaecb8ff22ad47abe63de5f110994fac", size = 79565, upload-time = "2026-06-20T23:48:30.429Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e5/c120d13bf5091164f68c3c1657e84f16f57e71d978421b626393ac5bd7eb/wrapt-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ec8f83949028366531383603139403cac7a826e4011955813cdd640017845ce5", size = 83264, upload-time = "2026-06-20T23:48:31.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b0/d4a1eb97e0e286625bdf21bc7f702637f9607787ffbbdb5ec14d50c79dbf/wrapt-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b481fb0c40d9fd90a5809911208da700987d373a20a4709dc9e3944af7a6bec", size = 83791, upload-time = "2026-06-20T23:48:33.482Z" }, + { url = "https://files.pythonhosted.org/packages/18/1e/f060df47755e87b57684cee7bfc1362b204df55fac96ffebc0631b697b79/wrapt-2.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0065a3b657cec06813b4241d2462ccec287f6863103d7445b725fb3a889736f9", size = 203399, upload-time = "2026-06-20T23:48:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/c4/de/2316a757a1abb6453700b79d83e532146dcef2611348282d4d8889792161/wrapt-2.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30f7424af5c5c345b7f26490e097f74a2ef45b3d08b664dc33571aee3bd3b56c", size = 210461, upload-time = "2026-06-20T23:48:36.569Z" }, + { url = "https://files.pythonhosted.org/packages/ed/29/d1160785ae18ca2495a6d82a21154103d74f656c9fd457fb35f6b11b965a/wrapt-2.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:07fdcb012821859168641acf68afad61ef9783cf37100af85f152550e9677194", size = 195313, upload-time = "2026-06-20T23:48:38.175Z" }, + { url = "https://files.pythonhosted.org/packages/f5/2d/7caa9598ae61a9cf0989cc501739cbeeb7d650ab3193cca1407b9af0c6ab/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f90038ab58fafb584801ca62d72384d7d5225d93c76f7b773c22fae545bd8066", size = 206116, upload-time = "2026-06-20T23:48:39.804Z" }, + { url = "https://files.pythonhosted.org/packages/ac/02/281ea1088b8650d865f311b35cf86fd21df89128e2909714f1161e01c9d0/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c5d7825491bfa2d08b97e9557768987952c7b9ae687d06c3320b40a37ccb7f20", size = 192668, upload-time = "2026-06-20T23:48:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/be/7d/976e2d5b4b5c5babda40974edd54d0a5585cb60132ed86b46f4b80239b16/wrapt-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ad520e6daa9bbf136f14de735474dbec7dcc0891f718e1d274ce8dc92e645af", size = 198891, upload-time = "2026-06-20T23:48:43.056Z" }, + { url = "https://files.pythonhosted.org/packages/59/b7/e47651797c097f75a37e2ce86dcf04048ff576f3a674f7c558df7b5e9622/wrapt-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:25904acb9475f46c24fe0423dbc8fda8cc5fbc282ab3dc6e72e919748c53f4e9", size = 78537, upload-time = "2026-06-20T23:48:44.509Z" }, + { url = "https://files.pythonhosted.org/packages/d1/6f/9fa5d59fb06d890defb5a8f727ce6a14d2932c8760153f96956628559fee/wrapt-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:305d4c247d61c4115794a169141823c62f719525ddb90b23aa332741c77d2c28", size = 82005, upload-time = "2026-06-20T23:48:46.391Z" }, + { url = "https://files.pythonhosted.org/packages/15/80/4c7bd9873d1f9f7d138d93556b500469dbe24f42710b877519c2b9eb380d/wrapt-2.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c20279cd1a29800815d7b2d6338b60a6c6e78263f9d6e62e0eda251ba9cae2d0", size = 80762, upload-time = "2026-06-20T23:48:47.964Z" }, + { url = "https://files.pythonhosted.org/packages/24/05/7fd9c3f83b2c74cbfc572a0b88aa37431e04bd8aed70d2c0efd3464206de/wrapt-2.2.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0e64826f920c42d9d9f87e8cc09ffae66c51ede12d59061a5a426deb9aa71745", size = 81341, upload-time = "2026-06-20T23:48:49.39Z" }, + { url = "https://files.pythonhosted.org/packages/4b/68/1bfa43100dd90d4ef74a05897b86275cf57e1313ca14aae2545bc9f872c9/wrapt-2.2.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dcaa5e1451bd8751d7bd1568dfa3321c78092a52a7ecb5d1a0f18a5791e1fd00", size = 81921, upload-time = "2026-06-20T23:48:50.986Z" }, + { url = "https://files.pythonhosted.org/packages/74/eb/df7b7f0b631dbbc750f39be27d8b55f65777d8ac86da80e12be41a644c4b/wrapt-2.2.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0abfd648dac9ac9c5b3aa9b523d27f1789046640b58dcd5652a720ddb325e1fc", size = 167713, upload-time = "2026-06-20T23:48:52.598Z" }, + { url = "https://files.pythonhosted.org/packages/4d/9a/d1bd36f6d088c8e652a9383cabbd49af30b8c576302a7eccddbab6963e3f/wrapt-2.2.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4bfd8d1eb438153eff8b8cfe87f032ba65731e1ce06138b5090f745a33f6f95", size = 166779, upload-time = "2026-06-20T23:48:54.33Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ae/24ffacd4187fac2740a1972093929e836dea092d42c87d728cd98fee11a6/wrapt-2.2.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c427c9d06d859848a69f0d928fe28b5c33a941b2265d10a0e1f15cd244f1ee33", size = 158407, upload-time = "2026-06-20T23:48:55.944Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ed/974427668249a356051e8d67d47fa54ef6c777f0fcf3bae9d292c047d4b6/wrapt-2.2.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4250b43d1a129d947e083c4dc6baf333c9bb34edd26f912d5b0457841fc858ab", size = 166594, upload-time = "2026-06-20T23:48:57.617Z" }, + { url = "https://files.pythonhosted.org/packages/fb/5f/e1d7c6e4523f78db2fbd7826babd0348da1d5e0834c4f918b9ab5757dfae/wrapt-2.2.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:173e5bb5ca350a6e0abab60b7ec7cdd7992a814cb14b4de670a28f067f105663", size = 157068, upload-time = "2026-06-20T23:48:59.171Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c1/7ebd1027f00700c0b0233b20aceef2b4784294ed64971424c4a78e069e34/wrapt-2.2.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aa14b01804bce36c6d63d7b6a4f55df390f29f8648cc13a1f40b166f4d54680d", size = 166470, upload-time = "2026-06-20T23:49:00.737Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/974e471a6a978b8180186b8a9dc5ae3361ce269a967190b709b8ce17abfb/wrapt-2.2.2-cp314-cp314-win32.whl", hash = "sha256:58f9f8d637c9a6e245c6ef5b109b67ec187d2faed23d1405656b51d96e0a5b56", size = 78062, upload-time = "2026-06-20T23:49:02.327Z" }, + { url = "https://files.pythonhosted.org/packages/49/ec/e1281156cdc7a66693838ad7a0865ad641c74abd337a957d668b575aaffb/wrapt-2.2.2-cp314-cp314-win_amd64.whl", hash = "sha256:385cb1866f20479e83299af585375bfa0a4b0c6c9907a981483ea782ea8ae406", size = 80832, upload-time = "2026-06-20T23:49:03.837Z" }, + { url = "https://files.pythonhosted.org/packages/45/7d/1b6b5ddd94005a2dac97a4490c9838f3154977850d633abcb65b30089437/wrapt-2.2.2-cp314-cp314-win_arm64.whl", hash = "sha256:8ffbeaea6771a6eba6e6eeb09767864995726bc8240bb54baf88a9bb1db34d5c", size = 80029, upload-time = "2026-06-20T23:49:05.237Z" }, + { url = "https://files.pythonhosted.org/packages/b0/33/9ebcf8aafe91c601127cbd93708c16aa8f688f34a10bf004046803ecdc4f/wrapt-2.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:09f811d43f6f33ec7515f0be76b159569f4057ab54d3e079c3204dddb90afa2a", size = 83357, upload-time = "2026-06-20T23:49:06.632Z" }, + { url = "https://files.pythonhosted.org/packages/39/38/ec45b635153327b52e52732a0ea980e5f00b7efba65f9e018828f1e69daa/wrapt-2.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a795d3c06e5fbf9ea2f13196180b77aeab1b4685917256ee0d014cc163d90063", size = 83794, upload-time = "2026-06-20T23:49:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ea/1a89e6d3b7a83c3affe5c09cde77792c947e63e4bc85ad84cd5bb9abb0d8/wrapt-2.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:45c2f2768e790c9f8db90f239ef23a2af8e7570f25a35619ef902df4a738447f", size = 203362, upload-time = "2026-06-20T23:49:09.811Z" }, + { url = "https://files.pythonhosted.org/packages/19/d8/3b58763d9863b5a73771c0d97110f9595d248db454009e07e1535ee905a4/wrapt-2.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bbf00ee0cb55ec24e2b0995a71942b85b21a066db8f3f46e1dbfdb9433ffba81", size = 210449, upload-time = "2026-06-20T23:49:11.521Z" }, + { url = "https://files.pythonhosted.org/packages/2d/6f/17fd9e053103d8be148d20d5d7505facc72d5fe1f9127973904ceaed79cf/wrapt-2.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2252f77663651b89255895f58cc6ac08fcb206d4371813e5af61bb62d4f7689c", size = 195349, upload-time = "2026-06-20T23:49:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/ef/04/d0d1ccaaa12cb7dccf28a23f0279a608ba498f71e81d949d5ed54bcfd5c1/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2cd7181ab1c31192ff5219269830744b5a62020b3a6d433588c4f1c95b8f8bff", size = 206099, upload-time = "2026-06-20T23:49:15.051Z" }, + { url = "https://files.pythonhosted.org/packages/44/b3/e8aa07b619890a2aa6cde1931b1887abb08820721b564a5f80b7ca3f3aa0/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6fe35fd51b74867d8b80174c277bd6bbf6a73e443f908129dc531c4b688a20d5", size = 192728, upload-time = "2026-06-20T23:49:16.854Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f0/1819fb50f0d3c9bd758d8a83b56f1b470dee8b5b8eac8702b7c137cea9d4/wrapt-2.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:11d95fc2fbad3163596c39d440e6f21ca9fccece74b56e30a37ac2fca786a07c", size = 198842, upload-time = "2026-06-20T23:49:18.504Z" }, + { url = "https://files.pythonhosted.org/packages/67/7c/e88313f16a99930b899ef970d91c281544a470749a359decad994483bbda/wrapt-2.2.2-cp314-cp314t-win32.whl", hash = "sha256:d8a15813215f33fa83667bfc978b300e35669ea8bb424e970a1426bcb7bc6cca", size = 79059, upload-time = "2026-06-20T23:49:20.107Z" }, + { url = "https://files.pythonhosted.org/packages/a0/4f/ac12fda57a55068a094ec42851fb0a40e8489d8941863d517452de62e507/wrapt-2.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:d09db0f7e8357060d3c38fc22a018aba683a796bf184360fd1a58f6fc180dc77", size = 82462, upload-time = "2026-06-20T23:49:21.631Z" }, + { url = "https://files.pythonhosted.org/packages/48/a7/df732dac86d9b2027c56bd163dbc883e037b16c3469614752e148d219c61/wrapt-2.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:f32fe639c39561ccc187bcae17e9271be0eb45f1c2952510d2f29b33ab577347", size = 81182, upload-time = "2026-06-20T23:49:23.199Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d2/6317eb6d4554855bbf12d61857774af34747bf88a42c19bf306de67e2fa3/wrapt-2.2.2-py3-none-any.whl", hash = "sha256:5bad217350f19ce99ca5b5e71d406765ea86fe541628426772b657375ee1c048", size = 61460, upload-time = "2026-06-20T23:49:42.966Z" }, +] + +[[package]] +name = "xlrd" +version = "2.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/07/5a/377161c2d3538d1990d7af382c79f3b2372e880b65de21b01b1a2b78691e/xlrd-2.0.2.tar.gz", hash = "sha256:08b5e25de58f21ce71dc7db3b3b8106c1fa776f3024c54e45b45b374e89234c9", size = 100167, upload-time = "2025-06-14T08:46:39.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/62/c8d562e7766786ba6587d09c5a8ba9f718ed3fa8af7f4553e8f91c36f302/xlrd-2.0.2-py2.py3-none-any.whl", hash = "sha256:ea762c3d29f4cca48d82df517b6d89fbce4db3107f9d78713e48cd321d5c9aa9", size = 96555, upload-time = "2025-06-14T08:46:37.766Z" }, +] + +[[package]] +name = "xmltodict" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", size = 26124, upload-time = "2026-02-22T02:21:22.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, +] + +[[package]] +name = "xxhash" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/78/ed/07e560876a4458987511461187b285071f53cde49dd5b25cd8c51091522b/xxhash-3.8.0.tar.gz", hash = "sha256:d72b2204f37840b0f16f34192c09b994b97bd25823d723d47a1eddfacf06eb43", size = 86107, upload-time = "2026-06-27T08:17:28.798Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d1/36cdfc7d9a5cdcebb2ff3eeeaebae2c51a7aca50de27a44520af4d6923fa/xxhash-3.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2289857ab90ebb2408d4ac2b7cf7e9ff29bba9d2cb21020c9d11fbbaef78eea", size = 34638, upload-time = "2026-06-27T08:12:17.753Z" }, + { url = "https://files.pythonhosted.org/packages/8e/37/f3439475537ca4c59e9b8cbc2b934672d1965b13b6e5fb32b1796c76e517/xxhash-3.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d211cfa927a107df09359d1f31070883a11121ddc88fd6dd27eda3a497a88f3d", size = 32316, upload-time = "2026-06-27T08:12:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3e/3878d943d9169fd8f5ad8d2bffa7dfec14430f8240ef20213772a7ef3dce/xxhash-3.8.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ba02f4cc4e71e1315ecac0468189b49bf3970da05ddf0b6965b4a9b1fe147e62", size = 217379, upload-time = "2026-06-27T08:12:20.742Z" }, + { url = "https://files.pythonhosted.org/packages/af/8a/096f0bf4e4d33b5afcb27e7907d54f84ae3c581509188dca1083995aefd9/xxhash-3.8.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:342d1a6f161741f8612dc38d940ec0019ae3362c0ede2d16554c1b4e3f1d5444", size = 237734, upload-time = "2026-06-27T08:12:22.312Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5b/811939d5d3fdf9b4a9cad7591759cc82c3c4734afb0138917ec3b3fc4fd5/xxhash-3.8.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75feec84a48cafd3b2446cb41910bebaf9a8150e2313c1f42887435818fb7b4c", size = 262522, upload-time = "2026-06-27T08:12:23.869Z" }, + { url = "https://files.pythonhosted.org/packages/3e/e4/50e2b55b1390895214bdd9dc6a75d4c31e0283d646d2cae424962585427a/xxhash-3.8.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:53e8f6cc0cc24283d98e9c742a0f0a5ded7a810abc4038b9e885e419fcd44e43", size = 238441, upload-time = "2026-06-27T08:12:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/725fbd70cc69b2738599c3e1b499941663b6ccef92aec7c78a4c9968f2d0/xxhash-3.8.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:73d04a4520cc7313acf4ff2122f783056d0592c71fc3a59e90fe0baeb499d124", size = 469833, upload-time = "2026-06-27T08:12:27.678Z" }, + { url = "https://files.pythonhosted.org/packages/86/e6/d80a2fcbd80f024d8e74a579aad538c5a24c6b672e6ce8180a9a8bfc2231/xxhash-3.8.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5a7fdfde5022f5000c8e6565db954580d19a8aa497ef80875f461e4546ed182", size = 217094, upload-time = "2026-06-27T08:12:29.221Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c0/6d85ebdc1e488df9e37c3a2267a8b98a936a36d968560cfb0389307fd19f/xxhash-3.8.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6a667f0dd160ec0ff6dddf42f2d75ad82660074285855f6037d6ecb57d40d0f8", size = 307502, upload-time = "2026-06-27T08:12:30.782Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a2/4b97a5e4fb3450fe0c4b361399f74679a491b3b0bed914bff6d00e70425f/xxhash-3.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aaaf53eb633205f01bb5fb807f6244bd34af121bfb1e21eedc925374aff5723e", size = 234622, upload-time = "2026-06-27T08:12:32.075Z" }, + { url = "https://files.pythonhosted.org/packages/e7/7e/5a227460f92ec7309219730ddfb7451e09e8aa3e0704cfb0f24746686a0f/xxhash-3.8.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:71b2e99a02fd5275b7ecab0b01130395beed4c6f027b6ce9f0730025634e7091", size = 265697, upload-time = "2026-06-27T08:12:33.559Z" }, + { url = "https://files.pythonhosted.org/packages/4e/31/56327a7b39dd3c605034f9b51c89d66aad022aacbe12aabeb6e335652d48/xxhash-3.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b25437ffd781d4cb98acef87f4bc32e27682f603ffd27ed5962948b516e777ff", size = 221932, upload-time = "2026-06-27T08:12:34.997Z" }, + { url = "https://files.pythonhosted.org/packages/41/a0/312504d1851969c62e3f2836eec5b16f3682edfae19aa60e6d69ee80d111/xxhash-3.8.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fe0ee773fd6c211b3b0134ee5d6fd6348411bd7bd79cdb4151d0aaf732179571", size = 236819, upload-time = "2026-06-27T08:12:36.66Z" }, + { url = "https://files.pythonhosted.org/packages/5d/23/d8f80cb1b1acede29ce76a39e013e5782712ab895bbffb32fe2e42b8eadd/xxhash-3.8.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:06c74e537f45c2f71010738d4d20741186cac29a035ec5c1c621c723d656c2fd", size = 297860, upload-time = "2026-06-27T08:12:38.103Z" }, + { url = "https://files.pythonhosted.org/packages/34/e9/4fdc697dcff5a73157ee34331e37849ada645448d4e47a38cb8a4044eafd/xxhash-3.8.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:718162a608eb85a22470725f95d63d834b1d7db98a2008b10309cd5a552d91ad", size = 439263, upload-time = "2026-06-27T08:12:39.808Z" }, + { url = "https://files.pythonhosted.org/packages/60/07/41a5144d7fd1c1f2b380de36521f7f34d624eef0374736515087ead7b925/xxhash-3.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:934cd5008d86e201818ca4416a4202039ea29edd89047166fea5c49999677bea", size = 213953, upload-time = "2026-06-27T08:12:41.528Z" }, + { url = "https://files.pythonhosted.org/packages/44/e4/7bc12b2fc9f340c446054b6f0e90e5b54c8021a4f9f6b1650054796009e9/xxhash-3.8.0-cp310-cp310-win32.whl", hash = "sha256:1f2c243a385e2c2ce72f5b7d68f3a621cc7d2ee2d0f35e0ca6bf5427ef1922a4", size = 31858, upload-time = "2026-06-27T08:12:43.139Z" }, + { url = "https://files.pythonhosted.org/packages/46/6a/3a61102925bf65ad81827a4586553a357f8a5316a25b938ef435e0bfabf8/xxhash-3.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:cb4996d43a42d825e2aa6f2b6a978b2a7779397b6a28e4fab5eb9505457023e4", size = 32659, upload-time = "2026-06-27T08:12:45.029Z" }, + { url = "https://files.pythonhosted.org/packages/06/c6/39d915926f45f72059519688b538a068efbea0307a294eba1ddb18887c0e/xxhash-3.8.0-cp310-cp310-win_arm64.whl", hash = "sha256:b3a79d694adcfd70d118c73d244eaece7f5f5ab424feb44573bd1d377e1bf0ea", size = 29128, upload-time = "2026-06-27T08:12:46.447Z" }, + { url = "https://files.pythonhosted.org/packages/e3/1b/73aaae7755372ff0cd5788c9955abb64b34d519dd84f2f4f081e2082119b/xxhash-3.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:08c34553cd7ceb3bfcfca344dc70305a45430429b5d58a67750f2a58364f638f", size = 34641, upload-time = "2026-06-27T08:12:47.579Z" }, + { url = "https://files.pythonhosted.org/packages/53/08/fdb1cb1001ed15b1f74a8eb70457dbdcd6df8375e27e3fe0d0225dbab170/xxhash-3.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:842d147983110e5a4f533f98f4f5bc851a08c7ca00aaa30649e8d5f9a6d4e47a", size = 32316, upload-time = "2026-06-27T08:12:48.695Z" }, + { url = "https://files.pythonhosted.org/packages/d7/05/c004e99c4292a9dde76c9157e8e51c73c6db2dd7e4a876712e6a6113e3b0/xxhash-3.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:37c9943e18f569f76a8b7d5d01bfe0716f7762c396096ceb42a47eb3d5ecf641", size = 220196, upload-time = "2026-06-27T08:12:49.964Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b2/8696a2008d59c3dc9346b26f7d64f5ec342cacc4051664e3b0201354fe58/xxhash-3.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21f6797afdc7abb0ffae059a0d1619c84a5368115bc0abd48f9803ab56a5d35e", size = 240908, upload-time = "2026-06-27T08:12:51.544Z" }, + { url = "https://files.pythonhosted.org/packages/da/ee/2415c55a17f525bcfa38b5b51d69381d6485b1c320eff373b263403b5e6b/xxhash-3.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5875d99d3540367d43779551dd22c813420b84a103e418d791095b9808fdca57", size = 264445, upload-time = "2026-06-27T08:12:53.08Z" }, + { url = "https://files.pythonhosted.org/packages/23/25/056d30ed2e500d0a993e4589da8cdbe50cbf4809c1b1ac84f6f9559d99ba/xxhash-3.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1a54ad5a2a96cdf1ee7a935d38bc63daa6095530095a916f644f1ab76604ced5", size = 241295, upload-time = "2026-06-27T08:12:54.703Z" }, + { url = "https://files.pythonhosted.org/packages/7e/70/5d8c9b65ae05725c2ea8f331705e1382fc4817911eb159450aecb2905c6b/xxhash-3.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b32e50dd85f0b67b2b95eb59cd3242052f6b27b70e9e73b27629686c592e3ea3", size = 473113, upload-time = "2026-06-27T08:12:56.159Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d4/734dd8e6eaa03b0c4e3044127755221ebf153260a3c5de0382430486fcaf/xxhash-3.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e4208fb85c950ddf7118b040bca15179c3bf9b7eb8bebe5e6ef067fc8af16a7", size = 220001, upload-time = "2026-06-27T08:12:57.869Z" }, + { url = "https://files.pythonhosted.org/packages/5b/cc/a0d92359d499db55f83fe6de13188125515319b968bd627b591a0984c454/xxhash-3.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9f17e09b035f2a0139536da53deb392b62ee259dc2a2189be12b06a7dd50489b", size = 309757, upload-time = "2026-06-27T08:12:59.438Z" }, + { url = "https://files.pythonhosted.org/packages/bc/dd/a20949401cfb9c940ef858d93b41ded90382ff4be0f7e8a5249edd95ff18/xxhash-3.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7d6dbb976d6e3b3be51bad16b13de7f4980e6aebd0aa51c5a14dfcc0fedd495e", size = 237596, upload-time = "2026-06-27T08:13:00.992Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/6963ee0c245a69d9c4a2583da603915f9288f1df23700a0ec705239ef014/xxhash-3.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:281897e5c516769694c999f5c50fd1e9acb27acbff187282a8ac77c38b6a9be5", size = 268683, upload-time = "2026-06-27T08:13:02.577Z" }, + { url = "https://files.pythonhosted.org/packages/db/ea/3489cde91ccd91230efbb2351a6d9358e8a63a9954cb8f071fa9c32a2558/xxhash-3.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8fba3d08c246201a1a0a6cece53a0b3b0890fc16adbe1edb245fcfcbf4eb0ce2", size = 224882, upload-time = "2026-06-27T08:13:04.21Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f6/179847064c92a07bba7381e9cd7132c380a17aad31e176a2d6f6e73eed48/xxhash-3.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:14ebc1559e8a9a481d0d5506b87678942fcdfa794d4aa55cdd2a0fb175d4245a", size = 239563, upload-time = "2026-06-27T08:13:05.96Z" }, + { url = "https://files.pythonhosted.org/packages/2d/83/dd599670efd161d31fba4149e20694f140ae5707068d38ac480dac1c8cd5/xxhash-3.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5e7a3e3bbe3a56bff70acc9b72576670e793b0184de3d1b9cda2bf697d17f630", size = 300148, upload-time = "2026-06-27T08:13:07.495Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/a474f136610594b464ad813f6badf00b931211a69fc86542c21daf5d2a4d/xxhash-3.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c71e3755a8320d29c351126d550930349be22b44bac1a559caf12ab78b53e9f", size = 442448, upload-time = "2026-06-27T08:13:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/75/86/054032919fc73b72917054cf731be76be3a984e8f53b1d0ba6f22fb9cffc/xxhash-3.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:715c611582004e75010517b919776c5dbc00aae03054dc9fd72484a23fd1862f", size = 216755, upload-time = "2026-06-27T08:13:10.902Z" }, + { url = "https://files.pythonhosted.org/packages/3c/16/2eb382a78f12e3fde1c735b57607498c0efe897e8859484d69d9446bba55/xxhash-3.8.0-cp311-cp311-win32.whl", hash = "sha256:41a30a1d0ba978238742a374875c15979e0faed0a65294f3ff4d9410057ee8b6", size = 31851, upload-time = "2026-06-27T08:13:12.281Z" }, + { url = "https://files.pythonhosted.org/packages/dd/53/a07ad4dbdc32118b3bd190f5d54ee2ed28c1a0a994b52ae493435cfb4de7/xxhash-3.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:43705f917b8b817d6994851bf3725b98b4c95e64186404d9a6dbc1acf12fd140", size = 32655, upload-time = "2026-06-27T08:13:13.394Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/d76bef62a288a1f2441404b33cb757047cf555cd5956b36ed718a38b81e9/xxhash-3.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:35c5d843bb7ac1dfdb125ef4181fe4c2e01c2275856e6b699de89e9eb5c69c8d", size = 29128, upload-time = "2026-06-27T08:13:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/17/2e/4b7c3ab28b7a54ac17eae7e02471c49609d6fc5900856a455feeb847a2a3/xxhash-3.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fc4bd14f873cd0b420f6f1ff5b5cd0dbfeb05b044a11bb9345bcbbf9749636e3", size = 34623, upload-time = "2026-06-27T08:13:16.696Z" }, + { url = "https://files.pythonhosted.org/packages/43/e4/09eea3e1bba6a59d64599cb8fba39f2a0872d06e85420eae989a4da61a9d/xxhash-3.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31904979198e913239cb61b49f5b849696aeb3b03340da815d1491ec74dcc602", size = 32318, upload-time = "2026-06-27T08:13:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/01/59/688bbae31e4e2d6d6eb92acbd3837c0e44ff8c7d435e6da922844ff6efda/xxhash-3.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7338ad13f2b273a1ef0ea97b2db0a059fdb3a1a29298bfa145937c0e4152d341", size = 220461, upload-time = "2026-06-27T08:13:19.311Z" }, + { url = "https://files.pythonhosted.org/packages/2d/de/71484ce0dab2fa4a475705d1ebc37a17ff02d40e5df6767b3255cc53120e/xxhash-3.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54e80e803cb34c8a1d278b491e543af40a588d288589c3e6becc991d5328b46b", size = 241110, upload-time = "2026-06-27T08:13:20.844Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f9/1ac88f02e7df7898541490260b21f2b7f7bd2b233038a0cbd3a3b1bffdc2/xxhash-3.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:353953ea18f5c3fbdd13936fb536aacfb47d5bc06eef0919b1a355df61f7cc31", size = 264779, upload-time = "2026-06-27T08:13:22.485Z" }, + { url = "https://files.pythonhosted.org/packages/25/49/7ea1f128d2fe948ed679020f97a0896cdc6c975da5cc69b53a4a9c4a5def/xxhash-3.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d761f983a315630eff18c2fec7360c6b6946f82748026e779336eb8141ef3eba", size = 242609, upload-time = "2026-06-27T08:13:24.277Z" }, + { url = "https://files.pythonhosted.org/packages/a0/da/7d237278dfa1c48722c31010c84a328a317b8885429c8cb6ae4a8fa3e3db/xxhash-3.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f3786a9beb9a3b76241cb7db5f5388b460682c12204236389e3221963fc626a6", size = 473472, upload-time = "2026-06-27T08:13:25.877Z" }, + { url = "https://files.pythonhosted.org/packages/9b/5f/980fda82620a07d80026b4df371cbca12fca0fd94d7087c4ec5d898da76f/xxhash-3.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c94f5a9a775f36cc522fa2a7e8e2cec512e252d2ac056759f753dc68a79ffc", size = 220374, upload-time = "2026-06-27T08:13:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/14/71/efa37bc3e91e1c801972bcef99eab877fcbd17ec10aca16c550ee2951107/xxhash-3.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:55ce59f9af37ac861947b43ea3ce7b294b5de77a1234b558d0f07ffad0197624", size = 310220, upload-time = "2026-06-27T08:13:28.804Z" }, + { url = "https://files.pythonhosted.org/packages/9d/48/19e40320044dc7051e8446505f18557d5661853b87a8770ad399325bb3c8/xxhash-3.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3afa1422a32c7c8e79ad5121dc21eaa5cee9e9e67bffca3f15d15d220d371908", size = 238100, upload-time = "2026-06-27T08:13:30.378Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0d/588499f4d7cd064864ada7adfb9e8785f88a988f1332ed4c1be73d249c15/xxhash-3.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:551fda694938be910529452a89175137c58b4739e41fadff3c047e24b1d74a3b", size = 268937, upload-time = "2026-06-27T08:13:31.867Z" }, + { url = "https://files.pythonhosted.org/packages/54/18/fb2ad593572a33d1b6864b33047b8ca7269273a3c56107b5fd33e0b9c8fb/xxhash-3.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512eb937c9457e6057e230e005c4709dd2ab63a5989f854d69f31db905750a62", size = 224910, upload-time = "2026-06-27T08:13:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/63/9e/b880f9ed61b73492e24bb962d76aeb63f18ccb895f0edfb52e20d45ed6f2/xxhash-3.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4931ea93840f750a908efebaf23c71004feacc1a4649ef601b96d400a505c9a9", size = 240742, upload-time = "2026-06-27T08:13:35.237Z" }, + { url = "https://files.pythonhosted.org/packages/3f/89/fc682f93e54e486fc338b26a7d6d0d5cb0ab366269273c2608ac62b51afb/xxhash-3.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2fd4b60e8d9fc3923f39079f185b3425e6d76636fcb66d82a33dd7eba7c30f2f", size = 300527, upload-time = "2026-06-27T08:13:36.997Z" }, + { url = "https://files.pythonhosted.org/packages/80/71/a4b4122afb2d17ad69e0922cfeddb5ad5c25b02f37eed3dd3819d42e5f55/xxhash-3.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1da00075f1605794298878cb587f7533329693e2a0c45bbd25d6353644add675", size = 443195, upload-time = "2026-06-27T08:13:38.719Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e5/ed3930f5dc90f4b1bab5ac3be099e8b2e81c1262d85e4adb5f2758e30d23/xxhash-3.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba73801c87d44fa37b2a5feab3004f0a654506027bf032ceb154d94bb74ea772", size = 217252, upload-time = "2026-06-27T08:13:41.179Z" }, + { url = "https://files.pythonhosted.org/packages/44/ae/128ea5794387ca54bb4084566db20dbdfc9c21cb17b67d3fcb403927b5ba/xxhash-3.8.0-cp312-cp312-win32.whl", hash = "sha256:0b0836dee6022e22ba516ebfa8f76c6e4bda08d6c166c553e40867bac89e4a54", size = 31890, upload-time = "2026-06-27T08:13:42.568Z" }, + { url = "https://files.pythonhosted.org/packages/4f/04/a6c182dc566c88e8d1a497d22cc4ffdcfcc0a9fa80325efa6cd4b9002c54/xxhash-3.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3bc2a09b98b8f85c75208cd2b2d2aecf40c77ecb2d72f6bf9757db51a98d3499", size = 32677, upload-time = "2026-06-27T08:13:43.705Z" }, + { url = "https://files.pythonhosted.org/packages/93/b5/aeda4e79f962c8d58ec60cb20a5abfe91c9f7d62e626f69f6659bc0bd0c4/xxhash-3.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:208e6a8b93426896d803224e9fabe26f8b9c651e8381a80b1fa31812faa091e3", size = 29155, upload-time = "2026-06-27T08:13:44.903Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1f/96f43c5c7c7c4d44721f8d2e5d74698c667a30283c4b10a7e50a56804ee3/xxhash-3.8.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:36434c1d1b0a4729df1fa26ab11bffed1ba52666c0beb605c98a995b470cd143", size = 38508, upload-time = "2026-06-27T08:13:46.152Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d9/7d5d6af4876c6481f2e0acb2dda64dd5209574bf7ba1ad4f6af7a1f8d473/xxhash-3.8.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:a5e6497cefcb2d67f1745c66df9718a99112583af6cc2b70da0312a2eb939f1e", size = 36542, upload-time = "2026-06-27T08:13:47.497Z" }, + { url = "https://files.pythonhosted.org/packages/32/ff/66fed439d78c5a09a1491a85af29bf8923b516530116731a9ac6b14dee2b/xxhash-3.8.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5b00b82f1be708da9404fefd658cf5cf3be5ee3be2aae4bfe3b874255badd342", size = 31102, upload-time = "2026-06-27T08:13:48.721Z" }, + { url = "https://files.pythonhosted.org/packages/56/b8/9fae0399281095f8aca1f32b21947b3c3c75ad6021b255c5c6e4b11d3866/xxhash-3.8.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:38b0cb0ab7f283413b7cace2bf710d7cf8f702ea82cbc683908691d52028a89b", size = 32096, upload-time = "2026-06-27T08:13:50.138Z" }, + { url = "https://files.pythonhosted.org/packages/61/a4/e53d162c74a8a2950dc063969914387b0680da4c7c20ad17744ec03a3b0a/xxhash-3.8.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:084312171a9798dea85e924b2674f5e1a44933050a1ea1cb1c6b1364e004c66c", size = 34585, upload-time = "2026-06-27T08:13:51.572Z" }, + { url = "https://files.pythonhosted.org/packages/69/f5/e12397e3f2c4917b6572e103a3277cd27cc56330e304bba61d195d7e5224/xxhash-3.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6a1a9e845bd3bbc57d9356819e0d198fe23282e0576b398a6282a0f8fdc75aef", size = 34622, upload-time = "2026-06-27T08:13:52.818Z" }, + { url = "https://files.pythonhosted.org/packages/70/80/c053dc51af5c942229689a0e9cb66fdc999bbd840f645e761f5ab73cbb17/xxhash-3.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ffbde09743ebaf8957b8426948fbe85eab5e5de0d29eec407fcff5a2812a3cc", size = 32320, upload-time = "2026-06-27T08:13:54.04Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a3/294171b67dfe770e1293edcf2a3f7e41302cdb8aefb258585312191b3ffe/xxhash-3.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a6dee3952c2b6e82e7f1dbc5dbc6167f9c84126851def7926e32827c2816169c", size = 220532, upload-time = "2026-06-27T08:13:55.448Z" }, + { url = "https://files.pythonhosted.org/packages/80/c3/d141bfdeca785c8c680abf867d4b52a5e64a55d90df242c3141a3e58c4b2/xxhash-3.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf8ff8e12416c9fa05b43c7509b9332d6ffc4090413c4e7a1dee8599763b6d59", size = 241215, upload-time = "2026-06-27T08:13:57.047Z" }, + { url = "https://files.pythonhosted.org/packages/09/5a/aeaf35143a6f3d44db73298e861405bdd9c9dacaedfc369cb43d9fd65282/xxhash-3.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cebbb322df4d97d8ef2704f49ed2f6f21f6702fafa0dc0c2a6ae70e904205689", size = 264615, upload-time = "2026-06-27T08:13:58.912Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3e/f8ca782bb34f99693faab70a7989bcc84f62ffe93c9a4cca464a33507a4b/xxhash-3.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9a8d08707b4100ebce598fc59fadf04b42d79b855818d6994f8f0fffd1df8edb", size = 242682, upload-time = "2026-06-27T08:14:00.483Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/ddbee4ff1542c2e88e72269a5a6bd18c3f26a80c2514e0918f5d1f3e9ec5/xxhash-3.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cf5427602dda15d8ce3c6d870d29bf07d43975f59c9d6d3f7f6f93a901b28b12", size = 473551, upload-time = "2026-06-27T08:14:02.17Z" }, + { url = "https://files.pythonhosted.org/packages/25/f5/a680d48dddab37ab2fd9189ca03f775e29e3627122e30790816d7eb365af/xxhash-3.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:97d7bd715ea5050b6c9638b52c62adf3055b648ef6eee6892a4cd9697b530191", size = 220485, upload-time = "2026-06-27T08:14:03.765Z" }, + { url = "https://files.pythonhosted.org/packages/22/b1/7ac129b74981c07f1ff9c649f204465e86f83f9f29b2ebdc70d91514c365/xxhash-3.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0cd25bbbab37d898f6e5a90905ce6ae2c1f8bd6668c07cef406fb3e8c8c570dd", size = 310307, upload-time = "2026-06-27T08:14:05.366Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/43e673411249dd63f6cd974523a1b32fad75cf5453e363bc8f44af215fb9/xxhash-3.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3e30e5c057f483c3c53a11b53eba091a737cb19dfead36c8b23bf5beb4a169cd", size = 238164, upload-time = "2026-06-27T08:14:07.149Z" }, + { url = "https://files.pythonhosted.org/packages/e5/95/87f8baf41f63130f3637104b7a610f82b20106332fc6e289c8dbf7955d0e/xxhash-3.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:07dd44d992ebd456752bc25b1c42cd172d94bd8cb24049300449ad0716081c3a", size = 269062, upload-time = "2026-06-27T08:14:08.834Z" }, + { url = "https://files.pythonhosted.org/packages/38/c9/3369b497cd1f926b930c52fd2400606f177790d887b49f9e86bddcc24562/xxhash-3.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:3118600a3102d4707dc1c485dbc3acbbbf37819069ad3e7854e77b923745d76b", size = 225007, upload-time = "2026-06-27T08:14:10.689Z" }, + { url = "https://files.pythonhosted.org/packages/34/c8/03dceb86a8128858ac105bd6e282d62b3db6fd421a79bd8a9f6b8cdc47a7/xxhash-3.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ed37b0c95d8fb3fbaad5e13cc0a9727eb8739d1d54b2adef28108c250cada3a", size = 240815, upload-time = "2026-06-27T08:14:12.195Z" }, + { url = "https://files.pythonhosted.org/packages/47/a5/ebd43eeb1af1dd8f0201943688b20958e99d3f6eb36481fb8c37b55ef139/xxhash-3.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bb043da412e478e7b1db3407051124b85b133803794d3809ad6d92870b304fc7", size = 300632, upload-time = "2026-06-27T08:14:13.916Z" }, + { url = "https://files.pythonhosted.org/packages/df/24/c873e41a3c00dacc385c8ff08c007723f6a528922c1cea7fd9684e86dae7/xxhash-3.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:196fc132683d9311a0bdce8388ee52bfa07fdc1987cc428a27956e47ccd7b50d", size = 443293, upload-time = "2026-06-27T08:14:15.446Z" }, + { url = "https://files.pythonhosted.org/packages/4f/1b/c671272fe28f70574e3c574d58465f26460154bcc68876121872afa1c14d/xxhash-3.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfb5411af3b77c75e99db100aa15c5ba623c85d72c565e4d7a0ed1a986ff766e", size = 217327, upload-time = "2026-06-27T08:14:17.28Z" }, + { url = "https://files.pythonhosted.org/packages/57/43/b45a52f795812cb769b6ac159e69b605d18b1c067749e63dcac159e90064/xxhash-3.8.0-cp313-cp313-win32.whl", hash = "sha256:6d1d6179e26830c6690fac63f76d372f69714b977e12ca9c42188a60f51c59f5", size = 31898, upload-time = "2026-06-27T08:14:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/a1/42/2bd70e4eec25dc5990652979d708d4d7c999793d7d5af5d0e48ab4374dc1/xxhash-3.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7c92427a56a12f4d5c7bb26dbb9e9a4658c313ecb6c2f1dca349902e3822df07", size = 32680, upload-time = "2026-06-27T08:14:20.277Z" }, + { url = "https://files.pythonhosted.org/packages/d5/c8/2fe61edb6144183cf094035a8c5354c65a073127acf6379655ed1e705b70/xxhash-3.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:9fc8453642c1c6d38b4fbac8901c2452ce1fa88b27f003bfee6703cbfae9bd63", size = 29157, upload-time = "2026-06-27T08:14:21.674Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b8/81d17a993b9a4750ba426ce966421681bb4b8e82a460cd346756491b8cc2/xxhash-3.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:efcacb644a915f010dc477447b045e5dcde1afaa40d16b2f0f8e7cd99c9e1635", size = 34897, upload-time = "2026-06-27T08:14:23.044Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/f5a368e3273440b3ea58fbd3f0b08c19f552b25ca59f43f5732ca96d2126/xxhash-3.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1e0dbc510cff94c5efbcc2b82c28b41519fad09b5b1f9f3d99c63e3940e49a0", size = 32630, upload-time = "2026-06-27T08:14:24.603Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ab/f424359c91c55f564fbbe4e454a126eb522471109f67376f20ad19c5e663/xxhash-3.8.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ff19d016a41c90d1f519005887191896b6da1274e1d5d48b347e17eb798ffc5a", size = 225874, upload-time = "2026-06-27T08:14:25.992Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c2/434579ef9235123b6c9bfa89c5614e0001e988613b91557b24aa326d9faa/xxhash-3.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aafc3eab99c50508852e34307e9565933bf128cad084cac7d2471b7ab1743de0", size = 249705, upload-time = "2026-06-27T08:14:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6c/3c0c917331ca3c71f826cedce2127f230624e2b49b992472dd5e9e72101c/xxhash-3.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5e521368ed79ae6c4d31e1e417726643c49d7d6e286f4fdabf9a8330ed8a8ff7", size = 274716, upload-time = "2026-06-27T08:14:29.495Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f3/a8bb98d3307c67e88be9642dff52854c3de3f488f95989b60ff69c8dcc42/xxhash-3.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6a0127688d116ec0c225e7e1f744e3f206de2b8822ffeb31a9ab5cc6384f92c5", size = 252019, upload-time = "2026-06-27T08:14:31.247Z" }, + { url = "https://files.pythonhosted.org/packages/f7/73/fab69a2e5b6353dde643209fe9b6adf4fbd64c888e531deffc476bfb2635/xxhash-3.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:22c0b17da2f9fea0f8836538512249871b359141616bad44c58d238b5f011f40", size = 482024, upload-time = "2026-06-27T08:14:32.973Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/ba34099b5278097ec9c68c0b740719813553bfd11ca17e7353de6d2a41e3/xxhash-3.8.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d49465646b1a5e3b1729c5f636e05676a2fb52e203e3b22a5411c416c4c5302", size = 226655, upload-time = "2026-06-27T08:14:34.608Z" }, + { url = "https://files.pythonhosted.org/packages/76/0c/90aba4708a37fe752b324a7cbf10058eaa33e892cdd62751ff17a5137b93/xxhash-3.8.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c2853dea1e30ed00ca87dd87d76da5da063d302b823b3fb80ccd18421de0f251", size = 319583, upload-time = "2026-06-27T08:14:36.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/46/42e349e2d3017b2688f4cb301742c37c438e77963e3fef711edce2fc5c65/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:82f0102a2a3760287b7cd7f9e0a30edd4c3b18762ed1a242208d43c8e2bcf30b", size = 246000, upload-time = "2026-06-27T08:14:38.104Z" }, + { url = "https://files.pythonhosted.org/packages/ee/15/741b947ae3c768e82018c46846f8616f6aa9b5042649f318a1a6897defe3/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:b8414a66a7524596d841cad5dc1adab6ce76848db5ab2b83db911fbdab1417af", size = 275455, upload-time = "2026-06-27T08:14:39.841Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b4/a9db84c9458fc8f53eaf0051377d1e9eecd9f330fb1225640027417a309d/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0dbaa73df10414ea1e41b98691a9d8241d4c47ad8d02c726587a3cda05278e53", size = 231209, upload-time = "2026-06-27T08:14:41.543Z" }, + { url = "https://files.pythonhosted.org/packages/20/92/60a868cd34851746d0b0d95dced0f42867c7c00606f6e5dba85b70b232ce/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:43fc9aaba10ab4267c90793601f60d35c3c9caa1544eceb483618a71ad9ce7da", size = 250416, upload-time = "2026-06-27T08:14:43.193Z" }, + { url = "https://files.pythonhosted.org/packages/7a/6a/168ca46a4679c32aae9246caa1fddf35981d6304487e45e992b3d4530324/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ec5eb3d28fbb9802c6d2526f772133a06c91d6f03756fcc67c834b642ffdd51d", size = 309764, upload-time = "2026-06-27T08:14:44.79Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/13646b348c07679c818791ab2d35415db5cb20f3bc77daaa255909a401b4/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:2b77c301b644cd9b4d0749a3291081ec2048a6bef7fe0487c993bbba3efb9ce0", size = 448650, upload-time = "2026-06-27T08:14:46.562Z" }, + { url = "https://files.pythonhosted.org/packages/59/9a/3d244b2acf6bbd86a363817ee09084b4684e8e11840663e19869e9e0d952/xxhash-3.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d7ece11a132325353890a144c30119073617a1299c593ca29b96c315b07e1edd", size = 223572, upload-time = "2026-06-27T08:14:48.294Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c7/143410d026a6e0d86dc69037ec2a3b8db810a54e7f443b340ac17612be2e/xxhash-3.8.0-cp313-cp313t-win32.whl", hash = "sha256:b21db84df7b9d54d9e4195a964243c1b32d745c6fbc0cfcfffee1d4bd297196a", size = 32301, upload-time = "2026-06-27T08:14:49.687Z" }, + { url = "https://files.pythonhosted.org/packages/6c/db/2240b0638161637b2f310231748a7a6a06c79fb43a3adb34c96f359762bf/xxhash-3.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0643b7d9f598f6da6f1f6b899f4358250d0fb853242e2d712cbde27bf5a99d29", size = 33221, upload-time = "2026-06-27T08:14:51.404Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d8/52038e4fa5baf4f00654a225516168d02908edfec7ca104fbefc58af394f/xxhash-3.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4bbacf2e938526969f8ab3334d4ac3da14ea059e1dfd1339a92f9091467e750f", size = 29294, upload-time = "2026-06-27T08:14:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ef/a09907aa28bdcdf6810d5c26656b154c60c0f06bb8db8442a1192d9c227a/xxhash-3.8.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:557e2a7cc0b6a634cf9c8e5c975d96b7da796fdeb1824569d760cf0f25b6f33f", size = 38365, upload-time = "2026-06-27T08:14:54.166Z" }, + { url = "https://files.pythonhosted.org/packages/d2/4d/d991ff77bc489c2231025e64e570502156d573c7bff69c917589cc307089/xxhash-3.8.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:dad744d1613cbfddb844dad93adbffbd51c3e9f53ceea9568f7c3b94bedc19a4", size = 36477, upload-time = "2026-06-27T08:14:55.427Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0e/553eab001f1e274da73da074968cdc8be8cacfb318937ab9871b8e1909cb/xxhash-3.8.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:953f29b22c04b123cf3cd2e08bccde3a73184aeda5a1038e0054cb3355644120", size = 31116, upload-time = "2026-06-27T08:14:56.897Z" }, + { url = "https://files.pythonhosted.org/packages/55/d5/d0f4dbe7b4d9ce0125f16e45ec0be5e04f6a172edb4e2fa551c4f2eb5d7a/xxhash-3.8.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:aa699e0253ceffecf41cae858d0a11f2439d6874a0890b556387bffe11dc1c08", size = 32112, upload-time = "2026-06-27T08:14:58.126Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2f/b332c7bede6a676343f2c9c8dea233c8c82753eaeda6f7a2c321d8c58ca3/xxhash-3.8.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e232c82466babc13e956d53aa84d0149660ed6886bc195248bb4d03bf2eca301", size = 34618, upload-time = "2026-06-27T08:14:59.458Z" }, + { url = "https://files.pythonhosted.org/packages/b3/5b/2bf3c9e61c7cf8f53bce937af45e22b72bb1f224d5afb20352beba0d628d/xxhash-3.8.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7f75fd1c6a5028f345cd4a8c52f4774d2e5b7809fa58111c60a5502b528914a4", size = 34739, upload-time = "2026-06-27T08:15:00.863Z" }, + { url = "https://files.pythonhosted.org/packages/64/b6/e88521f5736c181b89bfb7ab756f0ca658a8a1ecece7277b75e167717614/xxhash-3.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b49d7e09b211a1ad658dbe2dbf6561eb92f2e6926bd1101e2d023178371f2d6f", size = 32332, upload-time = "2026-06-27T08:15:02.383Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a2/fba440739fa5f86d2c28738c202e88d3dd063290c8bbb20e183c5334456a/xxhash-3.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ceb702bc8e56b7f1f1413d42aa294045b9a0e4c9888e07edc5cd153e8c4c948f", size = 220479, upload-time = "2026-06-27T08:15:03.785Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1c/4a1639efec16416695d6c7bc6b224d3f607e0b8cbe2409fa81081a849d1c/xxhash-3.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f3c96e06bdb122e8cc84f5c7088579f3102b828efd62e9dc964a9d17c7b89e", size = 241409, upload-time = "2026-06-27T08:15:05.439Z" }, + { url = "https://files.pythonhosted.org/packages/92/d1/8ce471f8d6752384f972fd5f6363f2e8d8b867a89fbd724c6dbd91d2bb98/xxhash-3.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:415a8d06ac9bea36b1e06b603a347e0f62401042a97d7bfccec8ae2da12ad784", size = 264433, upload-time = "2026-06-27T08:15:07.027Z" }, + { url = "https://files.pythonhosted.org/packages/95/77/400a281683fd39c54e2ac497fa67bdf886baaadb8c0ba58f7e1ea1d7692e/xxhash-3.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7f5ccdd2deb5dce31201cc0eec94388cce97e681429073db50903fab0a0a8a0d", size = 242835, upload-time = "2026-06-27T08:15:08.703Z" }, + { url = "https://files.pythonhosted.org/packages/aa/a6/edda651cfa0ba8e921791e93468fae655b63894d89730fcbfe46704f0d0a/xxhash-3.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1a6cf81bc699d3a5ebfcf2fdb2a7bd2e096708d7de193f6f322944a02ba00953", size = 473800, upload-time = "2026-06-27T08:15:10.503Z" }, + { url = "https://files.pythonhosted.org/packages/dd/da/50f764ec6a93d3961fce294567e41bfca0e66d168deed354a3dc90ebeba6/xxhash-3.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4d12a04d7ffc0359f0eadc4535a53cab113044c8d2f262c7e9a56950a5ed50e", size = 220677, upload-time = "2026-06-27T08:15:12.622Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/9fe4ed5aac6f38629cc83b34f84748b83ad8295a578ec6a49d8bf896cafb/xxhash-3.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d209373fcb66138c652cf843385ee60866e50158a7869bbbf8b322d9a822b765", size = 310385, upload-time = "2026-06-27T08:15:14.384Z" }, + { url = "https://files.pythonhosted.org/packages/83/f5/1147e03c0553ed22bbae9ce47503c37ee0c5f95592aae10f339c25f61de9/xxhash-3.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b88a3fe28277811e599efa6e1c96abce8a77d60dd79c94da7a9b5c377c172b7b", size = 238330, upload-time = "2026-06-27T08:15:16.201Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/92daf66c1966c84da5c97a06ced1480208d3a3bd465cb0630565ec00d1b9/xxhash-3.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5d5a888a5ef997cb35f1aad346eb861cd87ecfe24f5e25d5aa4c9fd1bd3950c2", size = 268667, upload-time = "2026-06-27T08:15:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c0/080c1a92972667e183c04b03f33c877f8ec61cfa3570e61731077286648d/xxhash-3.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:de2836e0329c01555957a603dcd113c337c577081153d691c12a51c5be3282b0", size = 224934, upload-time = "2026-06-27T08:15:19.972Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d5/cbc4e5b2bee10c94cba05b5bb2b8033e7ef44ae742583fdafcd9188e33ed/xxhash-3.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:4bc74eedb0dd5827b3be748bacf9fdb50004037a3e16c7ddb5defae2682cef71", size = 240870, upload-time = "2026-06-27T08:15:22.04Z" }, + { url = "https://files.pythonhosted.org/packages/76/f7/09679b00e192b741b65c230440c4f7e6df3251a9ad427a518ddf262ec71a/xxhash-3.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:c571b03d59e339b010dc84f15a6f1cff80212f3a3116c2a71e2303c95065b1f6", size = 300683, upload-time = "2026-06-27T08:15:23.647Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1b/f43ec36e8c6a20c77be0bcca23f0b133ed8a0312681500d1676eebd71924/xxhash-3.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:87626acdd6e2d762c588a4ffe94258c5ef34fb6049a4a3b25019bdb7f9267a9b", size = 443407, upload-time = "2026-06-27T08:15:25.504Z" }, + { url = "https://files.pythonhosted.org/packages/45/2e/a3e3a779c5e4789daf975e05cc1c7f11bae724a03855120029d4592c8e63/xxhash-3.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:076d8a4fb290af952826922aa42a46bfc64caa31662ce4e2925a445d0e6ce57f", size = 217559, upload-time = "2026-06-27T08:15:27.234Z" }, + { url = "https://files.pythonhosted.org/packages/44/da/1c1e078ac290afff304a541a2a60965beb369ad65b4f30ec93ea1e0b7210/xxhash-3.8.0-cp314-cp314-win32.whl", hash = "sha256:52f8c7c9833d947e60df830671f6eca810d7c667051243985a561c79f1a3d545", size = 32602, upload-time = "2026-06-27T08:15:28.809Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7a/d455cb83d5e3c94046234294fb5dbbe5da600d1bbdf76b9527756920cce9/xxhash-3.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:4fbfcb7dd307e23189a71050f6e27746926590330f37d5fd2ffcb8ea78de1f42", size = 33393, upload-time = "2026-06-27T08:15:30.166Z" }, + { url = "https://files.pythonhosted.org/packages/89/8f/1b14471f617bc96edbb9566099a162d918a981381c398114726cc600b76c/xxhash-3.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:ecef1e65b4715c7326002073763fe94cc44c756a0698508abb915ab3d6be6e3d", size = 30007, upload-time = "2026-06-27T08:15:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/51ad2f9f784121c8057ef1ba36362f58d4595cbcad16322941f5b73eb53d/xxhash-3.8.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:02ed856a765cb6e006168595d9455ac8c3c4d60cc04cd47a158a1ac677d68f0f", size = 34957, upload-time = "2026-06-27T08:15:33.292Z" }, + { url = "https://files.pythonhosted.org/packages/1b/14/175c573ae4fac48bf21a82e5b9ceec75d64c520c51ca08de3105de539438/xxhash-3.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:eec30461a7b457611098ba7ab09363e36c8b2645b4687fb6f3d405bb646e3410", size = 32635, upload-time = "2026-06-27T08:15:34.766Z" }, + { url = "https://files.pythonhosted.org/packages/96/08/f83efabd350a50c31c851b88891e318a6f07bdbf40a43d0f7bb6cedade7f/xxhash-3.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b471744912d1ce5dd6d3975b7525e77518359ebf3aa1bd7d501e199f5ae488ea", size = 225969, upload-time = "2026-06-27T08:15:36.35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/78/2b6d12da9cf572c84d93b88ecbf9bf6539a7c5219bde128b214396b97c8b/xxhash-3.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3748d71202bf3f279e77cb8b273b6d0f29d1bcaefb6ce6cb03b95f358863ba37", size = 249851, upload-time = "2026-06-27T08:15:38.087Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0a/755eeb1882634983b24e6375a95ed233228dc48f0ef12655388bf3c7eeaf/xxhash-3.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b3bf59ea94b2a23b0f992769804ab9401d5cdcd9df0062fe2cd78a491ae8851", size = 274842, upload-time = "2026-06-27T08:15:39.808Z" }, + { url = "https://files.pythonhosted.org/packages/77/f2/09b1231cad17c314e51664c4a004c919108ec59aba10f9a28fa061e7b8be/xxhash-3.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:40f061aa5379eba249e9367b179515571e632be6d1b6f55ac139e6fe3d08463c", size = 252218, upload-time = "2026-06-27T08:15:42.105Z" }, + { url = "https://files.pythonhosted.org/packages/b2/24/de756d55547953494eb6775aea92e258035647b3ecb8547618cd549001e1/xxhash-3.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:680d70896a61fc920cc717a0a8fe8a9fb5858c563184666e31874caa54a16d9e", size = 482135, upload-time = "2026-06-27T08:15:44.476Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/b8147633e32f98ef2b4bb0dfca82f0f63e2b02ff179f20664af64c4216a7/xxhash-3.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14973fbdee136588e57447401b521f466a42faca41eecdf35123c73103512ca8", size = 226776, upload-time = "2026-06-27T08:15:46.597Z" }, + { url = "https://files.pythonhosted.org/packages/29/37/ba051d8f0380d3cf845b23ba058a17d32025846463eb6bf885887fc8effe/xxhash-3.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:96c6bca2486cdc58b125966817a92a6abe6ef1fab86b2f8798a7e93488782540", size = 319738, upload-time = "2026-06-27T08:15:48.394Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/36e0a27dd27ffa3f7b521650cbcd52a00fb86b71343ffadb642374e8263c/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0b1109ae238e932d8482f9cb568b56a405cc73bc7a36b837844087f1298dd218", size = 246136, upload-time = "2026-06-27T08:15:50.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/73/2663dbf4c09386a9dcc8a94d7a14b4609ed4bad8180ced5b848e60a9b660/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1da5db0863400eade7c5a31969754d1392189f26b4105f6631da2c6c7ea3bccc", size = 275568, upload-time = "2026-06-27T08:15:52.735Z" }, + { url = "https://files.pythonhosted.org/packages/d6/58/f3ce1bc3bb3971191f6521273ddae98d3c610bcefbbed5327c3b3627c12f/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c61b5a0f21ace5e886f177cce43826d85a7c84e35a9e17cb6d1b4ac0b7a7d833", size = 231314, upload-time = "2026-06-27T08:15:54.73Z" }, + { url = "https://files.pythonhosted.org/packages/4d/51/835706a36cdc00e5b638fba9b22218b3d40d23a7677c923feca8a3f55b98/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1db4f27835a450c7e729bc9330c6e702113711cea1f873d646e3a31fe96a9732", size = 250521, upload-time = "2026-06-27T08:15:56.853Z" }, + { url = "https://files.pythonhosted.org/packages/c1/47/b0b62caa3caee58ab9de8969f66aef1c3729886f3ff60e173fda3f2762be/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4788a470f946df34383abc6cd345088c13f897a5ee580c4cdd12b1d32ad218ef", size = 309926, upload-time = "2026-06-27T08:15:58.704Z" }, + { url = "https://files.pythonhosted.org/packages/69/c4/60e6d18a0e131c7af622374af9deede15d3c47d8e5e7221933481b57b319/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3b6dfa83096cb1e54d082acebaf67f0c42667c56dc48ba536a76cac08d46391e", size = 448812, upload-time = "2026-06-27T08:16:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/12/9f/c9627daa052be39a932d0e17c6bf6a9041d2cde3afacbded9196acf70261/xxhash-3.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:57ec0ba5299a9a7df376063c139f5826ff0c89b438703939af3d252c31ca96a4", size = 223639, upload-time = "2026-06-27T08:16:02.784Z" }, + { url = "https://files.pythonhosted.org/packages/a9/38/92916e008a84c1f1a9aef82e4363cdc478a722ff69e59c6afbf93d3d1fda/xxhash-3.8.0-cp314-cp314t-win32.whl", hash = "sha256:d9a61f23b999baeb84102aba767b1b3e94958eab94e6c11b08927e7dc4200795", size = 33078, upload-time = "2026-06-27T08:16:04.639Z" }, + { url = "https://files.pythonhosted.org/packages/31/7c/e413bc75121d9628bf023b2ed251411ca3a447cf00cd9aa3438ab17f6c67/xxhash-3.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:61069b260fff84116235bb93845f319284dc6b42527c215af59264f4c2ee3468", size = 33953, upload-time = "2026-06-27T08:16:06.347Z" }, + { url = "https://files.pythonhosted.org/packages/f6/eb/21a96e218375bd8b6ecd6d07cf60c8ff1a046e93cdedc3cf7bc3309edf7b/xxhash-3.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:73cecd431b4f572d38fcf1a7fe85b30eb987778ef9e7a70bc9ffcf2d64810e6f", size = 30164, upload-time = "2026-06-27T08:16:08.009Z" }, + { url = "https://files.pythonhosted.org/packages/96/84/9bb3cc67475ac7678476b30eed2f1140431f06386d637534194037c0624f/xxhash-3.8.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ba14843f20df2dce6ff6684411a56ae53da44336546c55f8947e70aebb8cdd21", size = 32604, upload-time = "2026-06-27T08:17:19.291Z" }, + { url = "https://files.pythonhosted.org/packages/42/6d/e98f9dd62c89e8895e4f3b525b6dbc3efcf27e2b99800e51388c59eb96dd/xxhash-3.8.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ec6666a5311beae3f6cb5f2fd28c2b77e2df32702c8206f45c786a6ef81b3751", size = 29787, upload-time = "2026-06-27T08:17:21.001Z" }, + { url = "https://files.pythonhosted.org/packages/db/51/e7844a65c62d6d78747e4d149508d65a3df6fb65d72322c2526789e9f600/xxhash-3.8.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1ec9afdd53ac5f4fd1d8918807ba6c35ba62269086af794884b9f168a73331ea", size = 43155, upload-time = "2026-06-27T08:17:22.721Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5d/652c47481053fabc33ea229540bd330a45f68d7a5277f45e6cf879c29965/xxhash-3.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68594a54be2eb5992d9b0d0a0ec7c32a7a8e930f06d6cb951d69708055680994", size = 38137, upload-time = "2026-06-27T08:17:24.295Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/7b6e961a03ee713cbdbaa3d2cf3ddd33453a4d4112bbde58f2f607ab64d2/xxhash-3.8.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:591d5eb256abf59438800ace2730ac33f77bc6ab8c3623fab1ea24d9d8b28f3a", size = 34376, upload-time = "2026-06-27T08:17:25.688Z" }, + { url = "https://files.pythonhosted.org/packages/da/aa/95d36393bf732df516a2dcf4fd7e9e851bc033a5970e30774b972137f4da/xxhash-3.8.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7f4eecf800275e62b6bcb41e65f361f2277cc886c2bff4e299959d701e5fcf93", size = 32798, upload-time = "2026-06-27T08:17:27.188Z" }, +] + +[[package]] +name = "yarl" +version = "1.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/12/1e8f37460ea0f7eb59c221fdaf0ed75e7ac43e97f8093b9c6f411df50a78/yarl-1.24.2.tar.gz", hash = "sha256:9ac374123c6fd7abf64d1fec93962b0bd4ee2c19751755a762a72dd96c0378f8", size = 210798, upload-time = "2026-05-19T21:31:05.599Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/df/f1c7a3de0831cd83194f1a85c5bb431b13f81e6b45079314c86d1c4ef3f2/yarl-1.24.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5249a113065c2b7a958bc699759e359cd61cfc81e3069662208f48f191b7ed12", size = 129057, upload-time = "2026-05-19T21:27:47.564Z" }, + { url = "https://files.pythonhosted.org/packages/48/41/7daafb32dd7562bf45b1ce56562e7e1a9146f6479b6456873eb8a3413c40/yarl-1.24.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7f4425fa244fbf530b006d0c5f79ce920114cfff5b4f5f6056e669f8e160fdc0", size = 91545, upload-time = "2026-05-19T21:27:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/a8/8f/7b3ec212f1ea0683f55f978e3246bc313c38818664edfc97a9f349a4901e/yarl-1.24.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15c0b5e49d3c44e2a0b93e6a49476c5edad0a7686b92c395765a7ea775572a75", size = 91380, upload-time = "2026-05-19T21:27:51.953Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1b/8bafab7db23b0567ae9db749099b329d91e3b82bc6028b2050ba583e116c/yarl-1.24.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:246d32a53a947c8f0189f5d699cbd4c7036de45d9359e13ba238d1239678c727", size = 105957, upload-time = "2026-05-19T21:27:53.98Z" }, + { url = "https://files.pythonhosted.org/packages/7f/77/21030c2f8d21d21559719beafc772ada2014be933418ed1eaed9cc800e42/yarl-1.24.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:64480fb3e4d4ed9ed71c48a91a477384fc342a50ca30071d2f8a88d51d9c9413", size = 97242, upload-time = "2026-05-19T21:27:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/d8/f9ea63d1b6aa910a866e089d871fff6cbd49caab29b86b35221a62dfa0d5/yarl-1.24.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:349de4701dc3760b6e876628423a8f147ef4f5599d10aba1e10702075d424ed9", size = 114719, upload-time = "2026-05-19T21:27:58.037Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/04e0ee98ac58a249ea7ed75223f5f901ba81a834f0b4921b58e5cec11757/yarl-1.24.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d162677af8d5d3d6ebab8394b021f4d041ac107a4b705873148a77a49dc9e1b2", size = 112140, upload-time = "2026-05-19T21:27:59.618Z" }, + { url = "https://files.pythonhosted.org/packages/02/ad/0b9cc9f38a7324a7eb1d80f834eaa5283d17e9271bbda3186e598dddaeac/yarl-1.24.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5f5c6ec23a9043f2d139cc072f53dd23168d202a334b9b2fda8de4c3e890d90", size = 106721, upload-time = "2026-05-19T21:28:02.586Z" }, + { url = "https://files.pythonhosted.org/packages/65/e7/a52478ebfc66ec989e085c6ae038b9f1bfa4190baa193b133b669c709e2f/yarl-1.24.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:60de6742447fbbf697f16f070b8a443f1b5fe6ca3826fbef9fe70ecd5328e643", size = 106478, upload-time = "2026-05-19T21:28:04.523Z" }, + { url = "https://files.pythonhosted.org/packages/04/d8/5508530fea8472542de00013ae280765fc938ee196fc4030c43a498afb36/yarl-1.24.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acf93187c3710e422368eb768aee98db551ec7c85adc250207a95c16548ab7ac", size = 105423, upload-time = "2026-05-19T21:28:06.515Z" }, + { url = "https://files.pythonhosted.org/packages/84/f1/ece28505e9628e8b756e11bb4f28864a17cc33b6b44db4d2aaf0622bf630/yarl-1.24.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f4b0352fd41fd34b6651934606268816afd6914d09626f9bcbbf018edb0afb3f", size = 99878, upload-time = "2026-05-19T21:28:08.637Z" }, + { url = "https://files.pythonhosted.org/packages/3f/52/fb5d34529b46dd84013afcfb30b8d2bc2832ed03d412736f577d604fa393/yarl-1.24.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6b208bb939099b4b297438da4e9b25357f0b1c791888669b963e45b203ea9f36", size = 114025, upload-time = "2026-05-19T21:28:10.64Z" }, + { url = "https://files.pythonhosted.org/packages/43/f0/ff9d31aaab024f7a251c0ed308a98ae29bf9f7dc344e78f28b1322431ca2/yarl-1.24.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:4b85b8825e631295ff4bc8943f7471d54c533a9360bbe15ebb38e018b555bb8a", size = 105613, upload-time = "2026-05-19T21:28:12.784Z" }, + { url = "https://files.pythonhosted.org/packages/31/7d/3296fb3f3ecd52bf9ae6c16b0895c1cda7e9170a2083861552b683f70264/yarl-1.24.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e26acf20c26cb4fefc631fdb75aca2a6b8fa8b7b5d7f204fb6a8f1e63c706f53", size = 111665, upload-time = "2026-05-19T21:28:14.393Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/77aa6ddaca4fbf42e45e675a465c43956dd40702281049975a2aa04eae59/yarl-1.24.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:819ca24f8eafcfb683c1bd5f44f2f488cea1274eb8944731ffd2e1f10f619342", size = 106914, upload-time = "2026-05-19T21:28:15.893Z" }, + { url = "https://files.pythonhosted.org/packages/d8/02/7611f22cd1d4ed7373eb7f9ee21fde1046edba2e7c0e514880d760352f48/yarl-1.24.2-cp310-cp310-win_amd64.whl", hash = "sha256:5cb0f995a901c36be096ccbf4c673591c2faabbe96279598ffaec8c030f85bf4", size = 92658, upload-time = "2026-05-19T21:28:17.471Z" }, + { url = "https://files.pythonhosted.org/packages/91/00/671d0add79938127292839ae44506ce2f7fe8909c72d5a931864f128fd0b/yarl-1.24.2-cp310-cp310-win_arm64.whl", hash = "sha256:f408eace7e22a68b467a0562e0d27d322f91fe3eaaa6f466b962c6cfaea9fa39", size = 87887, upload-time = "2026-05-19T21:28:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c5/1ce244152ff2839645e7cae92f90e7bafcb2c52bea7ff586ac714f14f5df/yarl-1.24.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:36348bebb147b83818b9d7e673ea4debc75970afc6ffdc7e3975ad05ce5a58c1", size = 128971, upload-time = "2026-05-19T21:28:20.543Z" }, + { url = "https://files.pythonhosted.org/packages/87/5a/00f36967203ed89cb3acd2c8ed526cc3fed9418eb70ce128160a911c8499/yarl-1.24.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a97e42c8a2233f2f279ecadd9e4a037bcb5d813b78435e8eedd4db5a9e9708c", size = 91507, upload-time = "2026-05-19T21:28:22.556Z" }, + { url = "https://files.pythonhosted.org/packages/31/d0/1fb0c1cd27288f39f6974da4318c32768d72c9890984541fdf1e2e32a51d/yarl-1.24.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d027d56f1035e339d1001ac33eceab5b2ec8e42e449787bb75e289fb9a5cd1d", size = 91343, upload-time = "2026-05-19T21:28:24.092Z" }, + { url = "https://files.pythonhosted.org/packages/03/ce/d4a646508bed2f8dec6435b40166fe9308dd191262033d3f307b2bbcaecd/yarl-1.24.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a6377060e7927187a42b7eb202090cbe2b34933a4eeaf90e3bd9e33432e5cae", size = 105704, upload-time = "2026-05-19T21:28:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/4b/07/b3278e82d8bc41485bcf6d856cd0433262593de615b1d3dc43bd3f5bead4/yarl-1.24.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:17076578bce0049a5ce57d14ad1bded391b68a3b213e9b81b0097b090244999a", size = 97281, upload-time = "2026-05-19T21:28:27.352Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/4cee6e7c92e487bebe7afc797da0aa54a248ab4e776a68fe369ec29665a5/yarl-1.24.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50713f1d4d6be6375bb178bb43d140ee1acb8abe589cd723320b7925a275be1e", size = 114020, upload-time = "2026-05-19T21:28:29.458Z" }, + { url = "https://files.pythonhosted.org/packages/5c/82/111076571545a7d4f9cca3fbd5c6f40615af58642be09f12328f48022468/yarl-1.24.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:34263e2fa8fb5bb63a0d97706cda38edbad62fddb58c7f12d6acbc092812aa50", size = 111450, upload-time = "2026-05-19T21:28:31.262Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ec/08f671f69a444d704aeecebf92af659b67b97a869942411d0a578b08c334/yarl-1.24.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49016d82f032b1bd1e10b01078a7d29ae71bf468eeae0ea22df8bab691e60003", size = 106384, upload-time = "2026-05-19T21:28:32.856Z" }, + { url = "https://files.pythonhosted.org/packages/e5/86/ce41e7a7a199340b2330d52b60f25c4074b6636dd0e60b1a80d31a9db042/yarl-1.24.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3f6d2c216318f8f32038ca3f72501ba08536f0fd18a36e858836b121b2deed9f", size = 106153, upload-time = "2026-05-19T21:28:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5d/31be8a729531ab3e55ac3e7e5c800be8c89ea98947f418b2f6ea259fb6ee/yarl-1.24.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:08d3a33218e0c64393e7610284e770409a9c31c429b078bcb24096ed0a783b8f", size = 105322, upload-time = "2026-05-19T21:28:36.642Z" }, + { url = "https://files.pythonhosted.org/packages/47/9b/b57afb22b386ae87ac9940f09878b98d8c333f89113e6fc96fcf4ca9eb64/yarl-1.24.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5d699376c4ca3cba49bbfae3a05b5b70ded572937171ce1e0b8d87118e2ba294", size = 99057, upload-time = "2026-05-19T21:28:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4f/06348c27c8389256c313e8a57d796808fc0264c915dd5e7cfd3c0e314dc7/yarl-1.24.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a1cab588b4fa14bea2e55ebea27478adfb05372f47573738e1acc4a36c0b05d2", size = 113502, upload-time = "2026-05-19T21:28:40.091Z" }, + { url = "https://files.pythonhosted.org/packages/5f/1c/284f307b298e4a17b7943b07d9d7ecc4151537f8d137ba51f3bb6c31ca20/yarl-1.24.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:ec87ccc31bd21db7ad009d8572c127c1000f268517618a4cc09adba3c2a7f21c", size = 105253, upload-time = "2026-05-19T21:28:41.987Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bf/0de123bec8619e45c80cbded9085f61b5b4a9eddb8abe6d25d28ee1ec866/yarl-1.24.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d1dd47a22843b212baa8d74f37796815d43bd046b42a0f41e9da433386c3136b", size = 111345, upload-time = "2026-05-19T21:28:43.93Z" }, + { url = "https://files.pythonhosted.org/packages/90/af/0248eb065e51129d2a9b2436cd1b5c772c19a6b04e5b6a186955671e3319/yarl-1.24.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7b54b9c67c2b06bd7b9a77253d242124b9c95d2c02def5a1144001ee547dd9d5", size = 106558, upload-time = "2026-05-19T21:28:45.806Z" }, + { url = "https://files.pythonhosted.org/packages/21/3c/f960d7a65ef97d8ba9b424fb5128796a4bc710fc6df2ddbbd7dfdc3bbd20/yarl-1.24.2-cp311-cp311-win_amd64.whl", hash = "sha256:f8fdbcff8b2c7c9284e60c196f693588598ddcee31e11c18e14949ce44519d45", size = 92808, upload-time = "2026-05-19T21:28:48.465Z" }, + { url = "https://files.pythonhosted.org/packages/03/1a/49fb03750e4de4d2284cd5b885a383133c34eef45bd59631b2bb8b7e81e8/yarl-1.24.2-cp311-cp311-win_arm64.whl", hash = "sha256:b32c37a7a337e90822c45797bf3d79d60875cfcccd3ecc80e9f453d87026c122", size = 87610, upload-time = "2026-05-19T21:28:50.07Z" }, + { url = "https://files.pythonhosted.org/packages/f0/da/866bcb01076ba49d2b42b309867bed3826421f1c479655eb7a607b44f20b/yarl-1.24.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b975866c184564c827e0877380f0dae57dcca7e52782128381b72feff6dfceb8", size = 129957, upload-time = "2026-05-19T21:28:51.695Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1d/fcefb70922ea2268a8971d8e5874d9a8218644200fb8465f1dcad55e6851/yarl-1.24.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3b075301a2836a0e297b1b658cb6d6135df535d62efefdd60366bd589c2c82f2", size = 92164, upload-time = "2026-05-19T21:28:53.242Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/170e2b8d4e3bc30e6bfdcca53556537f5bf595e938632dfcb059311f3ff6/yarl-1.24.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ae44649b00947634ab0dab2a374a638f52923a6e67083f2c156cd5cbd1a881d", size = 91688, upload-time = "2026-05-19T21:28:54.865Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a5/c9f655d5553ea0b99fdac9d6a99ad3f9b3e73b8e5758bb46f58c9831f74c/yarl-1.24.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:507cc19f0b45454e2d6dcd62ff7d062b9f77a2812404e62dbdaec05b50faa035", size = 102902, upload-time = "2026-05-19T21:28:56.963Z" }, + { url = "https://files.pythonhosted.org/packages/5d/bc/6b9664d815d79af4ee553337f9d606c56bbf269186ada9172de45f1b5f60/yarl-1.24.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4c17bad5a530912d2111825d3f05e89bab2dd376aaa8cbc77e449e6db63e576", size = 97931, upload-time = "2026-05-19T21:28:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/98/ec/32ba48acae30fecd60928f5791188b80a9d6ee3840507ffda29fecd37b71/yarl-1.24.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5f0cbb112838a4a293985b6ed73948a547dadcc1ba6d2089938e7abdedceef8", size = 111030, upload-time = "2026-05-19T21:29:00.148Z" }, + { url = "https://files.pythonhosted.org/packages/82/5a/6f4cd081e5f4934d2ae3a8ef4abe3afacc010d26f0035ee91b35cd7d7c37/yarl-1.24.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ec8356b8a6afcf81fc7aeeef13b1ff7a49dec00f313394bbb9e83830d32ccd7", size = 110392, upload-time = "2026-05-19T21:29:02.155Z" }, + { url = "https://files.pythonhosted.org/packages/7a/da/323a01c349bd5fb01bb6652e314d9bb218cee630a736bdb810ad50e4013f/yarl-1.24.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e7ebcdef69dec6c6451e616f32b622a6d4a2e92b445c992f7c8e5274a6bbc4c", size = 105612, upload-time = "2026-05-19T21:29:04.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/80/264ab684f181e1a876389374519ff05d10248725535ae2ac4e8ac4e563d6/yarl-1.24.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:47a55d6cf6db2f401017a9e96e5288844e5051911fb4e0c8311a3980f5e59a7d", size = 104487, upload-time = "2026-05-19T21:29:06.491Z" }, + { url = "https://files.pythonhosted.org/packages/41/07/efabe5df87e96d7ad5959760b888344be48cd6884db127b407c6b5503adc/yarl-1.24.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3065657c80a2321225e804048597ad55658a7e76b32d6f5ee4074d04c50401db", size = 102333, upload-time = "2026-05-19T21:29:08.267Z" }, + { url = "https://files.pythonhosted.org/packages/44/0c/bcf7c42603e1009295f586d8890f2ba032c8b53310e815adf0a202c73d9f/yarl-1.24.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:cb84b80d88e19ede158619b80813968713d8d008b0e2497a576e6a0557d50712", size = 99025, upload-time = "2026-05-19T21:29:10.682Z" }, + { url = "https://files.pythonhosted.org/packages/4f/82/84482ab1a57a0f21a08afe6a7004c61d741f8f2ecc3b05c321577c612164/yarl-1.24.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:990de4f680b1c217e77ff0d6aa0029f9eb79889c11fb3e9a3942c7eba29c1996", size = 110507, upload-time = "2026-05-19T21:29:12.954Z" }, + { url = "https://files.pythonhosted.org/packages/c4/8d/a546ba1dfe1b0f290e05fef145cd07614c0f15df1a707195e512d1e39d1d/yarl-1.24.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:abb8ec0323b80161e3802da3150ef660b41d0e9be2048b76a363d93eee992c2b", size = 103719, upload-time = "2026-05-19T21:29:14.893Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b6/267f2a09213138473adfce6b8a6e17791d7fee70bd4d9003218e4dec58b0/yarl-1.24.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e7977781f83638a4c73e0f88425563d70173e0dfd90ac006a45c65036293ee3c", size = 110438, upload-time = "2026-05-19T21:29:16.485Z" }, + { url = "https://files.pythonhosted.org/packages/48/2d/1c8d89c7c5f9cad9fb2902445d94e2ab1d7aa35de029afbb8ae95c42d00f/yarl-1.24.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e30dd55825dc554ec5b66a94953b8eda8745926514c5089dfcacecb9c99b5bd1", size = 105719, upload-time = "2026-05-19T21:29:18.367Z" }, + { url = "https://files.pythonhosted.org/packages/a7/25/722e3b93bd687009afb2d59a35e13d30ddd8f80571445bb0c4e4ce26ec66/yarl-1.24.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dafe10c12ddd4d120d528c4b5599c953bd7b12845347d507b95451195bb6cad", size = 92901, upload-time = "2026-05-19T21:29:20.014Z" }, + { url = "https://files.pythonhosted.org/packages/39/47/4486ccfb674c04854a1ef8aa77868b6a6f765feaf69633409d7ca4f02cb8/yarl-1.24.2-cp312-cp312-win_arm64.whl", hash = "sha256:044a09d8401fcf8681977faef6d286b8ade1e2d2e9dceda175d1cfa5ca496f30", size = 87229, upload-time = "2026-05-19T21:29:22.1Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/fcf0ce677f17e5c471c06311dd25964be38a4c586993632910d2e75278bc/yarl-1.24.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:491ac9141decf49ee8030199e1ee251cdff0e131f25678817ff6aa5f837a3536", size = 128978, upload-time = "2026-05-19T21:29:23.83Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/8e63299bb71ed61a834121d9d3fe6c9fcf2a6a5d09754ff4f20f2d20baf5/yarl-1.24.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e89418f65eda18f99030386305bd44d7d504e328a7945db1ead514fbe03a0607", size = 91733, upload-time = "2026-05-19T21:29:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/c1/24/16748d5dab6daec8b0ed81ccec639a1cded0f18dcc62a4f696b4fe366c37/yarl-1.24.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cdfcce633b4a4bb8281913c57fcafd4b5933fbc19111a5e3930bbd299d6102f1", size = 91113, upload-time = "2026-05-19T21:29:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/1b/66/b63fff7b71211e866624b21432d5943cbb633eb0c2872d9ee3070648f22c/yarl-1.24.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:863297ddede92ee49024e9a9b11ecb59f310ca85b60d8537f56bed9bbb5b1986", size = 103899, upload-time = "2026-05-19T21:29:28.842Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/ba1974b8533909636f7733fe86cf677e3619527c3c2fa913e0ea89c48757/yarl-1.24.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:374423f70754a2c96942ede36a29d37dc6b0cb8f92f8d009ddf3ed78d3da5488", size = 97862, upload-time = "2026-05-19T21:29:31.086Z" }, + { url = "https://files.pythonhosted.org/packages/1b/a5/123ac993b5c2ba6f554a140305620cb8f150fa543711bbc49be3ec0a65a4/yarl-1.24.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33a29b5d00ccbf3219bb3e351d7875739c19481e030779f48cc46a7a71681a9b", size = 111060, upload-time = "2026-05-19T21:29:32.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/37/c472d3af3509688392134a88a825276770a187f1daa4de3f6dc0a327a751/yarl-1.24.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9532c57211730c515341af11fef6e9b61d157487272a096d0c04da445642592", size = 110613, upload-time = "2026-05-19T21:29:34.379Z" }, + { url = "https://files.pythonhosted.org/packages/df/88/09c28dad91e662ccfaa1b78f1c57badde74fc9d0b23e74aef644750ecd73/yarl-1.24.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91e72cf093fd833483a97ee648e0c053c7c629f51ff4a0e7edd84f806b0c5617", size = 107012, upload-time = "2026-05-19T21:29:36.216Z" }, + { url = "https://files.pythonhosted.org/packages/07/ab/9d4f69d571a94f4d112fa7e2e007200f5a54d319f58c82ac7b7baa61f5c6/yarl-1.24.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b3177bc0a768ef3bacceb4f272632990b7bea352f1b2f1eee9d6d6ff16516f92", size = 105887, upload-time = "2026-05-19T21:29:38.746Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9a/000b2b66c0d772a499fc531d21dab92dfeb73b640a12eed6ba89f49bb2d0/yarl-1.24.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e196952aacaf3b232e265ff02980b64d483dc0972bd49bcb061171ff22ac203a", size = 103620, upload-time = "2026-05-19T21:29:40.368Z" }, + { url = "https://files.pythonhosted.org/packages/41/7c/7c1050f73450fbdaa3f0c72017059f00ce5e13366692f3dba25275a1083d/yarl-1.24.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:204e7a61ce99919c0de1bf904ab5d7aa188a129ea8f690a8f76cfb6e2844dc44", size = 100599, upload-time = "2026-05-19T21:29:42.66Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b1/29e5756b3926705f5f6089bd5b9f50a56eaac550da6e260bf713ead44d04/yarl-1.24.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b156914620f0b9d78dc1adb3751141daee561cfec796088abb89ed49d220f1a", size = 110604, upload-time = "2026-05-19T21:29:44.632Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4b/8415bc96e9b150cde942fbac9a8182985e58f40ce5c54c34ed015407d3ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8372a2b976cf70654b2be6619ab6068acabb35f724c0fda7b277fbf53d66a5cf", size = 105161, upload-time = "2026-05-19T21:29:46.755Z" }, + { url = "https://files.pythonhosted.org/packages/8b/d4/cde059abfa229553b7298a2eadde2752e723d50aeedaef86ce59da2718ee/yarl-1.24.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f9a1e9b622ca284143aab5d885848686dcd85453bb1ca9abcdb7503e64dc0056", size = 110619, upload-time = "2026-05-19T21:29:48.972Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2c/d6a6c9a61549f7b6c7e6dc6937d195bcf069582b47b7200dcd0e7b256acf/yarl-1.24.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:810e19b685c8c3c5862f6a38160a1f4e4c0916c9390024ec347b6157a45a0992", size = 107362, upload-time = "2026-05-19T21:29:51Z" }, + { url = "https://files.pythonhosted.org/packages/92/dd/3ae5fe417e9d1c353a548553326eb9935e76b6b727161563b424cc296df3/yarl-1.24.2-cp313-cp313-win_amd64.whl", hash = "sha256:7d37fb7c38f2b6edab0f845c4f85148d4c44204f52bc127021bd2bc9fdbf1656", size = 92667, upload-time = "2026-05-19T21:29:52.743Z" }, + { url = "https://files.pythonhosted.org/packages/10/cc/a7beb239f78f27fca1b053c8e8595e4179c02e62249b4687ec218c370c50/yarl-1.24.2-cp313-cp313-win_arm64.whl", hash = "sha256:1e831894be7c2954240e49791fa4b50c05a0dc881de2552cfe3ffd8631c7f461", size = 87069, upload-time = "2026-05-19T21:29:54.442Z" }, + { url = "https://files.pythonhosted.org/packages/40/0e/e08087695fc12789263821c5dc0f8dc52b5b17efd0887cacf419f8a43ba3/yarl-1.24.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f9312b3c02d9b3d23840f67952913c9c8721d7f1b7db305289faefa878f364c2", size = 129670, upload-time = "2026-05-19T21:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/3a/98/ab4b5ed1b1b5cd973c8a3eb994c3a6aefb6ce6d399e21bb5f0316c33815c/yarl-1.24.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a4f4d6cd615823bfc7fb7e9b5987c3f41666371d870d51058f77e2680fbe9630", size = 91916, upload-time = "2026-05-19T21:29:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/5297bb6a7df4782f7605bffc43b31f5044070935fbbcaa6c705a07e6ac65/yarl-1.24.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0c3063e5c0a8e8e62fae6c2596fa01da1561e4cd1da6fec5789f5cf99a8aefd8", size = 91625, upload-time = "2026-05-19T21:30:00.412Z" }, + { url = "https://files.pythonhosted.org/packages/02/a7/45baabfff76829264e623b185cff0c340d7e11bf3e1cd9ea37e7d17934bd/yarl-1.24.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fecd17873a096036c1c87ab3486f1aef7f269ada7f23f7f856f93b1cc7744f14", size = 104574, upload-time = "2026-05-19T21:30:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/f3/40/3a5ab144d3d650ca37d4f4b57e56169be8af3ca34c448793e064b30baaed/yarl-1.24.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a46d1ab4ba4d32e6dc80daf8a28ce0bd83d08df52fbc32f3e288663427734535", size = 97534, upload-time = "2026-05-19T21:30:04.319Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b5/5658fef3681fb5776b4513b052bec750009f47b3a592251c705d75375798/yarl-1.24.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73e68edf6dfd5f73f9ca127d84e2a6f9213c65bdffb736bda19524c0564fcd14", size = 111481, upload-time = "2026-05-19T21:30:05.988Z" }, + { url = "https://files.pythonhosted.org/packages/4c/06/fdcd7dde037f00866dce123ed4ba23dba94beb56fc4cf561668d27be37f2/yarl-1.24.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a296ca617f2d25fbceafb962b88750d627e5984e75732c712154d058ae8d79a3", size = 111529, upload-time = "2026-05-19T21:30:07.738Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/d81269aaafccea0d33396c03035de997b743f11e648e6e27a0df99c72980/yarl-1.24.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51b2cf5ec89a8b8470177641ed62a3ba22d74e1e898e06ad53aa77972487208", size = 107338, upload-time = "2026-05-19T21:30:09.713Z" }, + { url = "https://files.pythonhosted.org/packages/ae/04/23049463f729bd899df203a7960505a75333edd499cda8aa1d5a82b64df5/yarl-1.24.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:310fc687f7b2044ec54e372c8cbe923bb88f5c37bded0d3079e5791c2fc3cf50", size = 106147, upload-time = "2026-05-19T21:30:11.365Z" }, + { url = "https://files.pythonhosted.org/packages/14/18/04a4b5830b43ed5e4c5015b40e9f6241ad91487d71611061b4e111d6ac80/yarl-1.24.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:297a2fe352ecf858b30a98f87948746ec16f001d279f84aebdbd3bd965e2f1bd", size = 104272, upload-time = "2026-05-19T21:30:12.978Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f7/8cffdf319aee7a7c1dbd07b61d91c3e3fda460c7a93b5f93e445f3806c4c/yarl-1.24.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2a263e76b97bc42bdcd7c5f4953dec1f7cd62a1112fa7f869e57255229390d67", size = 99962, upload-time = "2026-05-19T21:30:15.001Z" }, + { url = "https://files.pythonhosted.org/packages/d7/39/b3cce3b7dbef64ac700ad4cea156a207d01bede0f507587616c364b5468e/yarl-1.24.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:822519b64cf0b474f1a0aaef1dc621438ea46bb77c94df97a5b4d213a7d8a8b1", size = 111063, upload-time = "2026-05-19T21:30:16.683Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ea/100818505e7ebf165c7242ff17fdf7d9fee79e27234aeca871c1082920d7/yarl-1.24.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b6067060d9dc594899ba83e6db6c48c68d1e494a6dab158156ed86977ca7bcb1", size = 105438, upload-time = "2026-05-19T21:30:18.769Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d2/e075a0b32aa6625087de9e653087df0759fed5de4a435fef594181102a77/yarl-1.24.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0063adad533e57171b79db3943b229d40dfafeeee579767f96541f106bac5f1b", size = 111458, upload-time = "2026-05-19T21:30:21.024Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5c/ceea7ba98b65c8eb8d947fdc52f9bedfcd43c6a57c9e3c90c17be8f324a3/yarl-1.24.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ee8e3fb34513e8dc082b586ef4910c98335d43a6fab688cd44d4851bacfce3e8", size = 107589, upload-time = "2026-05-19T21:30:23.412Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d9/5582d57e2b2db9b85eb6663a22efdd78e08805f3f5389566e9fcad254d1b/yarl-1.24.2-cp314-cp314-win_amd64.whl", hash = "sha256:afb00d7fd8e0f285ca29a44cc50df2d622ff2f7a6d933fa641577b5f9d5f3db0", size = 94424, upload-time = "2026-05-19T21:30:25.425Z" }, + { url = "https://files.pythonhosted.org/packages/92/10/7dc07a0e22806a9280f42a57361395506e800c64e22737cd7b0886feab42/yarl-1.24.2-cp314-cp314-win_arm64.whl", hash = "sha256:68cf6eacd6028ef1142bc4b48376b81566385ca6f9e7dde3b0fa91be08ffcb57", size = 88690, upload-time = "2026-05-19T21:30:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/9e/13/d5b8e2c8667db955bcb3de233f18798fefe7edf1d7429c2c9d4f9c401114/yarl-1.24.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:221ce1dd921ac4f603957f17d7c18c5cc0797fbb52f156941f92e04605d1d67b", size = 136248, upload-time = "2026-05-19T21:30:29.297Z" }, + { url = "https://files.pythonhosted.org/packages/de/46/a4a97c05c9c9b8fd266bb2a0df12992c7fbd02391eb9640583411b6dab32/yarl-1.24.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5f3224db28173a00d7afacdee07045cc4673dfab2b15492c7ae10deddbece761", size = 95084, upload-time = "2026-05-19T21:30:31.031Z" }, + { url = "https://files.pythonhosted.org/packages/95/b2/845cf2074a015e6fe0d0808cf1a2d9e868386c4220d657ebd8302b199043/yarl-1.24.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c557165320d6244ebe3a02431b2a201a20080e02f41f0cfa0ccc47a183765da8", size = 95272, upload-time = "2026-05-19T21:30:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/fe/16/e69d4aa244aef45235ddfebc0e04036a6829842bc5a6a795aedc6c998d23/yarl-1.24.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:904065e6e85b1fa54d0d87438bd58c14c0bad97aad654ad1077fd9d87e8478ed", size = 101497, upload-time = "2026-05-19T21:30:34.842Z" }, + { url = "https://files.pythonhosted.org/packages/15/94/c07107715d621076863ee88b3ddf183fa5e9d4aba5769623c9979828410a/yarl-1.24.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8cec2a38d70edc10e0e856ceda886af5327a017ccbde8e1de1bd44d300357543", size = 94002, upload-time = "2026-05-19T21:30:37.724Z" }, + { url = "https://files.pythonhosted.org/packages/a9/35/fc1bbdd895b5e4010b8fdd037f7ed3aa289d3863e08231b30231ca9a0815/yarl-1.24.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e7484b9361ed222ee1ca5b4337aa4cbdcc4618ce5aff57d9ef1582fd95893fc0", size = 106524, upload-time = "2026-05-19T21:30:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/32b66d0a4ba47c296cf86d03e2c67bff58399fe6d6d84d5205c04c66cc6d/yarl-1.24.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:84f9670b89f34db07f81e53aee83e0b938a3412329d51c8f922488be7fcc4024", size = 106165, upload-time = "2026-05-19T21:30:41.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/47/37cb5ff50c5e825d4d38e81bb04d1b7e96bf960f7ab89f9850b162f3f114/yarl-1.24.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:abb2759733d63a28b4956500a5dd57140f26486c92b2caedfb964ab7d9b79dbf", size = 103010, upload-time = "2026-05-19T21:30:43.985Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/4597912315096f7bb359e46e13bf8b60994fcbb2db29b804c0902ef4eff5/yarl-1.24.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:081c2bf54efe03774d0311172bc04fedf9ca01e644d4cd8c805688e527209bdc", size = 101128, upload-time = "2026-05-19T21:30:46.291Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d5/c8e86e120521e646013d02a8e3b8884392e28494be8f392366e50d208efc/yarl-1.24.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:86746bef442aa479107fe28132e1277237f9c24c2f00b0b0cf22b3ee0904f2bb", size = 101382, upload-time = "2026-05-19T21:30:48.085Z" }, + { url = "https://files.pythonhosted.org/packages/fa/98/70b229236118f89dbeb739b76f10225bbf53b5497725502594c9a01d699a/yarl-1.24.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:2d07d21d0bc4b17558e8de0b02fbfdf1e347d3bb3699edd00bb92e7c57925420", size = 95964, upload-time = "2026-05-19T21:30:49.785Z" }, + { url = "https://files.pythonhosted.org/packages/87/f8/56c386981e3c8648d279fdef2397ffec577e8320fd5649745e34d54faeb7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4fb1ac3fc5fecd8ae7453ea237e4d22b49befa70266dfe1629924245c21a0c7f", size = 106204, upload-time = "2026-05-19T21:30:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1e/765afe97811ca35933e2a7de70ac57b1997ea2e4ee895719ee7a231fb7e5/yarl-1.24.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:4da31a5512ed1729ca8d8aacde3f7faeb8843cde3165d6bcf7f88f74f17bb8aa", size = 101510, upload-time = "2026-05-19T21:30:53.62Z" }, + { url = "https://files.pythonhosted.org/packages/ee/78/393913f4b9039e1edd09ae8a9bbb9d539be909a8abf6d8a2084585bed4b7/yarl-1.24.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:533ded4dceb5f1f3da7906244f4e82cf46cfd40d84c69a1faf5ac506aa65ecbe", size = 105584, upload-time = "2026-05-19T21:30:55.962Z" }, + { url = "https://files.pythonhosted.org/packages/78/87/deb17b7049bbe74ea11a713b86f8f27800cc1c8648b0b797243ebb4830ba/yarl-1.24.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7b3a85525f6e7eeabcfdd372862b21ee1915db1b498a04e8bf0e389b607ff0bd", size = 103410, upload-time = "2026-05-19T21:30:57.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/be/f9f7594e23b5b93affff0318e4593c1920331bcaefda326cabcad94296a1/yarl-1.24.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a7624b1ca46ca5d7b864ef0d2f8efe3091454085ee1855b4e992314529972215", size = 102980, upload-time = "2026-05-19T21:30:59.735Z" }, + { url = "https://files.pythonhosted.org/packages/65/a4/ba80dccd3593ff1f01051a818694d07b58cb8232677ee9a22a5a1f93a9fc/yarl-1.24.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e434a45ce2e7a947f951fc5a8944c8cc080b7e59f9c50ae80fd39107cf88126d", size = 91219, upload-time = "2026-05-19T21:31:01.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4d/4b880086bd0d3e034d25647be1d830afc3e3f610e98c4ab3490af6b1b6d5/yarl-1.24.2-py3-none-any.whl", hash = "sha256:2783d9226db8797636cd6896e4de81feed252d1db72265686c9558d97a4d94b9", size = 53576, upload-time = "2026-05-19T21:31:03.909Z" }, +] + +[[package]] +name = "zipp" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d8/eab98a517c14134c0b2eb4e2387bc5f457334293ec5d2dd3857ec2966802/zipp-4.1.0.tar.gz", hash = "sha256:4cb57381f544315db7688e976e922a2b18cdb513d21cc194eb42232ba2a3e602", size = 26214, upload-time = "2026-05-18T20:08:57.967Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/13/547360d81e6d88d58492968ffda9f9542854f11310ee556fef14260cc886/zipp-4.1.0-py3-none-any.whl", hash = "sha256:25ad4e16390cd314347dd8f1de67a2ac538ae658ed4ab9db16029c07c188e97f", size = 10238, upload-time = "2026-05-18T20:08:57.045Z" }, +] From c24fc36aadcc58d9b3c859ef8d2f7fec8798140e Mon Sep 17 00:00:00 2001 From: Cristian Tamblay Date: Fri, 3 Jul 2026 12:21:38 -0400 Subject: [PATCH 073/308] Updated readme and fixed a pre-commit bug that was inactive --- .../dataset_sources/openml_dataset_source.py | 2 +- README.rst | 45 +++++++++++++++++-- docs/docs/build/dev-setup.md | 11 +++++ .../current/build/dev-setup.md | 12 +++++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/DashAI/back/dataset_sources/openml_dataset_source.py b/DashAI/back/dataset_sources/openml_dataset_source.py index 202e398c6..be8dc2a94 100644 --- a/DashAI/back/dataset_sources/openml_dataset_source.py +++ b/DashAI/back/dataset_sources/openml_dataset_source.py @@ -197,7 +197,7 @@ def _meta(did: str) -> tuple[str, tuple[str, ...]] | None: metas = list(pool.map(_meta, ids)) entries = [] - for row, did, meta in zip(rows, ids, metas): + for row, did, meta in zip(rows, ids, metas, strict=False): description = "" dataset_tags: list[str] = [] if meta is not None: diff --git a/README.rst b/README.rst index e743b38be..b12b84577 100644 --- a/README.rst +++ b/README.rst @@ -61,8 +61,21 @@ Installation (PyPI) =================== dashAI needs Python 3.10 or greater. We strongly recommend installing it inside -an isolated environment (``venv`` or ``conda``) to avoid clashes with other -packages. +an isolated environment to avoid clashes with other packages. The quickest way +to do that is with `uv `_ +(recommended, it even installs Python for you); classic ``venv``/``conda`` with +``pip`` works exactly the same if you prefer it. + +**Shortcut:** if you just want dashAI as an app with the default PyTorch build +for your platform, uv can install it in its own isolated environment and put +the ``dashai`` command on your PATH in one line: + +.. code:: bash + + $ uv tool install dashai + +For GPU acceleration, a CPU-slim install, or LLM (GGUF) support, follow the +steps below instead. Installing dashAI also installs PyTorch with the default build for your platform, which works out of the box on CPU. To enable GPU acceleration (NVIDIA @@ -75,6 +88,13 @@ never installed automatically, so install it in step 3 if you need those models. 1. Create an environment ------------------------- +**Any OS (uv, recommended)** + +.. code:: bash + + $ uv venv --python 3.12 + $ source .venv/bin/activate # Windows: .venv\Scripts\activate + **Linux / macOS (venv)** .. code:: bash @@ -104,6 +124,9 @@ With the environment active: .. code:: bash + $ uv pip install dashai + + # or, with plain pip: $ pip install dashai @@ -113,6 +136,9 @@ With the environment active: This step is optional on CPU (step 2 already installed a working PyTorch). Run the section below that matches your hardware to pick a specific build. +Every ``pip install`` command below can also be run as ``uv pip install`` with +the same flags — same result, just faster. + CPU only (Linux / macOS / Windows) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -181,7 +207,7 @@ Replace ```` with your CUDA tag. Prebuilt wheels are published for for the available wheels and other backend options. -1. Run dashAI +4. Run dashAI ------------- Start the server and graphical interface with: @@ -328,6 +354,16 @@ PyTorch wheels instead: $ uv sync --extra cpu +If you prefer plain ``pip``, the same setup works inside any environment +(``venv`` or ``conda``) since all metadata lives in ``pyproject.toml``. Note +that this skips the lockfile, so versions may differ slightly from the ones +the team and CI use: + +.. code:: bash + + $ pip install -e . --group dev # --group needs pip >= 25.1 + $ pre-commit install + Running the Backend ~~~~~~~~~~~~~~~~~~~ @@ -343,6 +379,9 @@ Or, through the installed entry point: $ uv run dashai +(If you installed with pip inside your own environment, drop the ``uv run`` +prefix: ``python -m DashAI`` or ``dashai``.) + Optional Flags ============== diff --git a/docs/docs/build/dev-setup.md b/docs/docs/build/dev-setup.md index cf33ec1f0..7ef011a5a 100644 --- a/docs/docs/build/dev-setup.md +++ b/docs/docs/build/dev-setup.md @@ -37,6 +37,17 @@ which are much lighter: uv sync --extra cpu ``` +Alternatively, plain `pip` works inside any environment (venv or conda), +since all metadata lives in `pyproject.toml` — note this skips the lockfile, +so versions may differ slightly from the ones the team and CI use: + +```bash +pip install -e . --group dev # --group needs pip >= 25.1 +pre-commit install +``` + +If you go the pip route, drop the `uv run` prefix from the commands below. + ## 3. Frontend Setup ```bash diff --git a/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md b/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md index bc91d8728..2284c56b2 100644 --- a/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md +++ b/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md @@ -37,6 +37,18 @@ que son mucho más livianos: uv sync --extra cpu ``` +Como alternativa, `pip` a secas funciona dentro de cualquier entorno (venv o +conda), ya que toda la metadata vive en `pyproject.toml` — ojo que esto no usa +el lockfile, así que las versiones pueden diferir levemente de las que usan el +equipo y el CI: + +```bash +pip install -e . --group dev # --group requiere pip >= 25.1 +pre-commit install +``` + +Si usas la vía pip, omite el prefijo `uv run` en los comandos siguientes. + ## 3. Configuración del Frontend ```bash From 67c2899b3032b5baa180eb864e84081775c069ec Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 14:33:46 -0400 Subject: [PATCH 074/308] refactor: simplify download control and use a plain status icon in the models side bar Extract useComponentDownloadState and deleteComponent from ComponentDownloadControl and drop its compact variant, leaving a single button used by the selectors. The models side bar now downloads on row click and shows a tooltip-free status icon (download, spinner, or delete). --- .../src/components/models/ModelsRightBar.jsx | 34 ++- .../models/model/ComponentDownloadControl.jsx | 193 ++++++++------- .../model/ComponentDownloadControl.test.jsx | 27 +-- .../models/model/ModelDownloadStatusIcon.jsx | 78 +++++++ .../model/ModelDownloadStatusIcon.test.jsx | 50 ++++ .../components/models/model/ModelListItem.jsx | 220 ++++++++---------- 6 files changed, 343 insertions(+), 259 deletions(-) create mode 100644 DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx create mode 100644 DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx diff --git a/DashAI/front/src/components/models/ModelsRightBar.jsx b/DashAI/front/src/components/models/ModelsRightBar.jsx index 85be2c054..bb00620c7 100644 --- a/DashAI/front/src/components/models/ModelsRightBar.jsx +++ b/DashAI/front/src/components/models/ModelsRightBar.jsx @@ -7,7 +7,8 @@ import { useSnackbar } from "notistack"; import SideBar from "../threeSectionLayout/panelContainers/SideBar"; import { getComponents } from "../../api/component"; import ModelListItem from "./model/ModelListItem"; -import ComponentDownloadControl from "./model/ComponentDownloadControl"; +import { startComponentDownload } from "./model/ComponentDownloadControl"; +import ModelDownloadStatusIcon from "./model/ModelDownloadStatusIcon"; import { useTranslation } from "react-i18next"; import { useTourContext } from "../tour/TourProvider"; import { useModels } from "./ModelsContext"; @@ -93,6 +94,12 @@ export default function ModelsRightBar({ onToggle }) { // A download-required model that has not been downloaded cannot be // configured; it is blocked in the list with an inline download control. if (model.metadata?.requires_download && !model.downloaded) { + startComponentDownload({ + component: model, + enqueueSnackbar, + t, + onStatusChange: () => fetchModels(), + }); return; } selectModel(model); @@ -250,29 +257,16 @@ export default function ModelsRightBar({ onToggle }) { return ( handleModelClick(model) - } + onClick={() => handleModelClick(model)} + onDisabledClick={() => handleModelClick(model)} data-tour={index === 0 ? "first-model" : undefined} action={ requiresDownload ? ( - fetchModels()} + fetchModels()} /> ) : null } diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx index 8ad18a8b1..7cf74afde 100644 --- a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx @@ -1,13 +1,5 @@ -import React, { useState, useEffect, useRef } from "react"; -import { - Box, - Button, - CircularProgress, - IconButton, - LinearProgress, - Tooltip, - Typography, -} from "@mui/material"; +import React, { useState, useEffect } from "react"; +import { Box, Button, LinearProgress, Typography } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import DeleteIcon from "@mui/icons-material/Delete"; import { useTranslation } from "react-i18next"; @@ -35,6 +27,7 @@ const formatSize = (bytes) => { const downloadListeners = new Map(); // name -> Set<(state) => void> const downloadStateCache = new Map(); // name -> { downloading, downloaded } const anyChangeListeners = new Set(); // (name, state) => void +const activePollers = new Map(); // name -> poller id const subscribeDownloadState = (name, listener) => { let listeners = downloadListeners.get(name); @@ -65,20 +58,71 @@ const broadcastDownloadState = (name, state) => { anyChangeListeners.forEach((listener) => listener(name, state)); }; -const ComponentDownloadControl = ({ +export const stopComponentDownloadPolling = (componentName) => { + const pollerId = activePollers.get(componentName); + if (pollerId != null) { + stopJobPolling(pollerId); + activePollers.delete(componentName); + } +}; + +export const startComponentDownload = async ({ component, + enqueueSnackbar, + t, onStatusChange, - compact = false, }) => { - const { t } = useTranslation(["common"]); - const { enqueueSnackbar } = useSnackbar(); - const meta = component.metadata || {}; + broadcastDownloadState(component.name, { downloading: true }); + try { + const { id } = await downloadComponent(component.name); + activePollers.set(component.name, id); + startJobPolling( + id, + async () => { + activePollers.delete(component.name); + const status = await getComponentDownloadStatus(component.name); + broadcastDownloadState(component.name, { + downloading: false, + downloaded: status.downloaded, + }); + if (onStatusChange) onStatusChange(status.downloaded); + enqueueSnackbar(t("common:componentDownload.done"), { + variant: "success", + }); + }, + () => { + activePollers.delete(component.name); + broadcastDownloadState(component.name, { + downloading: false, + downloaded: false, + }); + if (onStatusChange) onStatusChange(false); + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + }, + ); + } catch (e) { + broadcastDownloadState(component.name, { + downloading: false, + downloaded: false, + }); + if (onStatusChange) onStatusChange(false); + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + } +}; + +// Subscribe a component to the shared download state. Returns the live +// { downloaded, downloading } flags, kept in sync across every mounted control +// for the same component name. +export const useComponentDownloadState = (component) => { const cached = downloadStateCache.get(component.name); const [downloaded, setDownloaded] = useState( cached?.downloaded ?? Boolean(component.downloaded), ); const [downloading, setDownloading] = useState(cached?.downloading ?? false); - const pollerIdRef = useRef(null); useEffect(() => { const known = downloadStateCache.get(component.name); @@ -86,7 +130,6 @@ const ComponentDownloadControl = ({ setDownloading(known?.downloading ?? false); }, [component.name, component.downloaded]); - // Mirror download/delete triggered by any other control for this component. useEffect(() => { return subscribeDownloadState(component.name, (state) => { if (state.downloading !== undefined) setDownloading(state.downloading); @@ -94,96 +137,46 @@ const ComponentDownloadControl = ({ }); }, [component.name]); - useEffect(() => { - return () => { - if (pollerIdRef.current != null) stopJobPolling(pollerIdRef.current); - }; - }, []); - - if (!meta.requires_download) return null; + return { downloaded, downloading }; +}; - const finish = (isDownloaded) => { +// Delete a component's download and broadcast the new state to every control. +export const deleteComponent = async ({ + component, + enqueueSnackbar, + t, + onStatusChange, +}) => { + try { + await deleteComponentDownload(component.name); broadcastDownloadState(component.name, { downloading: false, - downloaded: isDownloaded, + downloaded: false, }); - if (onStatusChange) onStatusChange(isDownloaded); - }; + if (onStatusChange) onStatusChange(false); + enqueueSnackbar(t("common:componentDownload.deleted"), { + variant: "success", + }); + } catch { + enqueueSnackbar(t("common:componentDownload.failed"), { + variant: "error", + }); + } +}; - const handleDownload = async () => { - broadcastDownloadState(component.name, { downloading: true }); - try { - const { id } = await downloadComponent(component.name); - pollerIdRef.current = id; - startJobPolling( - id, - async () => { - pollerIdRef.current = null; - const status = await getComponentDownloadStatus(component.name); - finish(status.downloaded); - enqueueSnackbar(t("common:componentDownload.done"), { - variant: "success", - }); - }, - () => { - pollerIdRef.current = null; - finish(false); - enqueueSnackbar(t("common:componentDownload.failed"), { - variant: "error", - }); - }, - ); - } catch (e) { - finish(false); - enqueueSnackbar(t("common:componentDownload.failed"), { - variant: "error", - }); - } - }; +const ComponentDownloadControl = ({ component, onStatusChange }) => { + const { t } = useTranslation(["common"]); + const { enqueueSnackbar } = useSnackbar(); + const meta = component.metadata || {}; + const { downloaded, downloading } = useComponentDownloadState(component); - const handleDelete = async () => { - try { - await deleteComponentDownload(component.name); - finish(false); - enqueueSnackbar(t("common:componentDownload.deleted"), { - variant: "success", - }); - } catch { - enqueueSnackbar(t("common:componentDownload.failed"), { - variant: "error", - }); - } - }; + if (!meta.requires_download) return null; - if (compact) { - if (downloading) { - return ( - - - - ); - } - if (downloaded) { - return ( - - - - - - ); - } - return ( - - - - - - ); - } + const handleDownload = () => + startComponentDownload({ component, enqueueSnackbar, t, onStatusChange }); + + const handleDelete = () => + deleteComponent({ component, enqueueSnackbar, t, onStatusChange }); if (downloading) { return ( diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx index 9bd72583a..e8388119f 100644 --- a/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.test.jsx @@ -42,26 +42,17 @@ describe("ComponentDownloadControl", () => { ); }); - it("compact mode triggers download from an icon button", async () => { - renderWithProviders( - {}} - />, - ); - const button = await screen.findByRole("button", { name: /download/i }); - fireEvent.click(button); - await waitFor(() => - expect(downloadComponent).toHaveBeenCalledWith("OpusMtEnRoaTransformer"), - ); - }); - it("shows a delete control for a downloaded component and deletes it", async () => { + // A distinct name avoids the module-level download-state cache carrying + // over from the download test above. + const downloadedComponent = { + ...component, + name: "OpusMtEnRoaTransformerDownloaded", + downloaded: true, + }; renderWithProviders( {}} />, ); @@ -69,7 +60,7 @@ describe("ComponentDownloadControl", () => { fireEvent.click(button); await waitFor(() => expect(deleteComponentDownload).toHaveBeenCalledWith( - "OpusMtEnRoaTransformer", + "OpusMtEnRoaTransformerDownloaded", ), ); }); diff --git a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx new file mode 100644 index 000000000..9fd77a39b --- /dev/null +++ b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx @@ -0,0 +1,78 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Box, CircularProgress } from "@mui/material"; +import DownloadIcon from "@mui/icons-material/Download"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { useSnackbar } from "notistack"; +import { useTranslation } from "react-i18next"; +import { + useComponentDownloadState, + deleteComponent, +} from "./ComponentDownloadControl"; + +/** + * Compact, tooltip-free download status shown at the end of a model row in the + * models side bar. The row click starts the download, so this is only an + * indicator: a spinner while downloading, a delete icon for a downloaded model, + * and a plain download icon otherwise. + * @param {object} model - The model component dict. + * @param {function} onChanged - Called after a delete so the list can refresh. + */ +export default function ModelDownloadStatusIcon({ model, onChanged }) { + const { t } = useTranslation(["common"]); + const { enqueueSnackbar } = useSnackbar(); + const { downloaded, downloading } = useComponentDownloadState(model); + + if (!model.metadata?.requires_download) return null; + + if (downloading) { + return ; + } + + if (downloaded) { + return ( + { + e.stopPropagation(); + deleteComponent({ + component: model, + enqueueSnackbar, + t, + onStatusChange: onChanged, + }); + }} + sx={{ + display: "flex", + alignItems: "center", + color: "error.main", + cursor: "pointer", + }} + > + + + ); + } + + // The row click handles the download; the icon is a non-interactive hint. + return ( + + + + ); +} + +ModelDownloadStatusIcon.propTypes = { + model: PropTypes.object.isRequired, + onChanged: PropTypes.func, +}; diff --git a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx new file mode 100644 index 000000000..05e7827e7 --- /dev/null +++ b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx @@ -0,0 +1,50 @@ +import React from "react"; +import { screen, fireEvent, waitFor } from "@testing-library/react"; +import { renderWithProviders } from "../../../test-utils/renderWithProviders"; + +jest.mock("../../../api/component", () => ({ + downloadComponent: jest.fn(() => Promise.resolve({ id: "job-1" })), + deleteComponentDownload: jest.fn(() => Promise.resolve()), + getComponentDownloadStatus: jest.fn(() => + Promise.resolve({ downloaded: false, requires_download: true }), + ), +})); +jest.mock("../../../utils/jobPoller", () => ({ + startJobPolling: jest.fn(), + stopJobPolling: jest.fn(), + subscribeJobs: jest.fn(() => () => {}), +})); + +import ModelDownloadStatusIcon from "./ModelDownloadStatusIcon"; +import { deleteComponentDownload } from "../../../api/component"; + +const model = { + name: "DummyDownloadableClassifier", + downloaded: false, + metadata: { requires_download: true, download_size_bytes: 268435456 }, +}; + +describe("ModelDownloadStatusIcon", () => { + it("renders no interactive control for an undownloaded model", () => { + renderWithProviders( + {}} />, + ); + expect(screen.queryByRole("button")).toBeNull(); + }); + + it("deletes a downloaded model from a plain delete icon", async () => { + renderWithProviders( + {}} + />, + ); + const del = await screen.findByRole("button", { name: /delete/i }); + fireEvent.click(del); + await waitFor(() => + expect(deleteComponentDownload).toHaveBeenCalledWith( + "DummyDownloadableClassifier", + ), + ); + }); +}); diff --git a/DashAI/front/src/components/models/model/ModelListItem.jsx b/DashAI/front/src/components/models/model/ModelListItem.jsx index dd7a07d29..d04e47212 100644 --- a/DashAI/front/src/components/models/model/ModelListItem.jsx +++ b/DashAI/front/src/components/models/model/ModelListItem.jsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { Box, Typography, Tooltip } from "@mui/material"; +import { Box, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import HoverModelInfo from "./HoverModelInfo"; import { ModelIcon } from "./ModelIcon"; @@ -9,6 +9,7 @@ export default function ModelListItem({ model, disabled = false, onClick, + onDisabledClick, action = null, ...props }) { @@ -17,10 +18,8 @@ export default function ModelListItem({ const [hoveredModel, setHoveredModel] = useState(null); const handleMouseEnter = (event, model) => { - if (!disabled) { - setAnchorEl(event.currentTarget); - setHoveredModel(model); - } + setAnchorEl(event.currentTarget); + setHoveredModel(model); }; const handleMouseLeave = () => { @@ -28,6 +27,16 @@ export default function ModelListItem({ setHoveredModel(null); }; + const handleCardClick = (event) => { + if (disabled) { + if (onDisabledClick) onDisabledClick(event); + return; + } + if (onClick) onClick(event); + }; + + const isClickable = Boolean(onClick || onDisabledClick); + // Get color and icon from metadata or use defaults const color = model.color || model.metadata?.color || theme.palette.text.secondary; @@ -35,146 +44,115 @@ export default function ModelListItem({ return ( <> - { + e.dataTransfer.setData( + "application/x-dashai-model", + JSON.stringify(model), + ); + e.dataTransfer.effectAllowed = "copy"; + setCustomDragImage(e); + } + : undefined + } + onMouseEnter={(e) => handleMouseEnter(e, model)} + onMouseLeave={handleMouseLeave} + onClick={handleCardClick} + {...props} + sx={{ + display: "flex", + alignItems: "center", + gap: 3, + p: 3, + bgcolor: disabled ? theme.palette.ui.disabled : theme.palette.ui.box, + border: `1px solid ${theme.palette.ui.border}`, + borderRadius: 1, + cursor: isClickable ? "pointer" : "default", + transition: "all 0.2s", + position: "relative", + "&:hover": { + bgcolor: disabled + ? theme.palette.ui.disabled + : theme.palette.action.hover, + borderColor: disabled ? theme.palette.ui.border : color, + transform: disabled || !isClickable ? "none" : "translateX(4px)", }, + "&::after": disabled + ? { + content: '""', + position: "absolute", + inset: 0, + borderRadius: 1, + pointerEvents: "none", + background: + "repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0, 0, 0, 0.1) 10px, rgba(0, 0, 0, 0.1) 20px)", + } + : {}, }} > + {/* Icon */} { - e.dataTransfer.setData( - "application/x-dashai-model", - JSON.stringify(model), - ); - e.dataTransfer.effectAllowed = "copy"; - setCustomDragImage(e); - } - : undefined - } - onMouseEnter={(e) => handleMouseEnter(e, model)} - onMouseLeave={handleMouseLeave} - onClick={disabled ? null : onClick} - {...props} sx={{ display: "flex", alignItems: "center", - gap: 3, - p: 3, + justifyContent: "center", + width: 36, + height: 36, + borderRadius: 1, bgcolor: disabled ? theme.palette.ui.disabled - : theme.palette.ui.box, - border: `1px solid ${theme.palette.ui.border}`, - borderRadius: 1, - cursor: disabled ? "not-allowed" : "grab", - transition: "all 0.2s", - opacity: disabled ? 0.5 : 1, - filter: disabled ? "grayscale(0.6)" : "none", - position: "relative", - "&:hover": { - bgcolor: disabled - ? theme.palette.ui.disabled - : theme.palette.action.hover, - borderColor: disabled ? theme.palette.ui.border : color, - transform: disabled ? "none" : "translateX(4px)", - }, - "&::after": disabled - ? { - content: '""', - position: "absolute", - inset: 0, - borderRadius: 1, - pointerEvents: "none", - background: - "repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0, 0, 0, 0.1) 10px, rgba(0, 0, 0, 0.1) 20px)", - } - : {}, + : theme.palette.ui.border, + color: disabled + ? theme.palette.text.disabled + : theme.palette.text.primary, + flexShrink: 0, }} > - {/* Icon */} - + + + {/* Content */} + + - - + {model.display_name || model.name} + + - {/* Content */} - - - {model.display_name || model.name} - + {/* Trailing action (e.g. download/delete control) */} + {action && ( + + {action} - - {/* Trailing action (e.g. download/delete control) */} - {action && ( - e.stopPropagation()} - onMouseDown={(e) => e.stopPropagation()} - onDragStart={(e) => e.stopPropagation()} - sx={{ flexShrink: 0, display: "flex", alignItems: "center" }} - > - {action} - - )} - - - {!disabled && ( + )} + + { - )} + } ); } From 999fda185f9d75c0f599c3c839ce29588474adad Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 14:34:04 -0400 Subject: [PATCH 075/308] feat: show model description and download size on hover in the side bar The hover popover now shows the description for every model, including download-required ones, and appends the download size when present. --- .../models/model/HoverModelInfo.jsx | 26 ++++++++++++++++++- .../src/utils/i18n/locales/de/custom.json | 1 + .../src/utils/i18n/locales/en/custom.json | 1 + .../src/utils/i18n/locales/es/custom.json | 1 + .../src/utils/i18n/locales/pt/custom.json | 1 + .../src/utils/i18n/locales/zh/custom.json | 1 + 6 files changed, 30 insertions(+), 1 deletion(-) diff --git a/DashAI/front/src/components/models/model/HoverModelInfo.jsx b/DashAI/front/src/components/models/model/HoverModelInfo.jsx index 9173ab61f..60a5ba0fb 100644 --- a/DashAI/front/src/components/models/model/HoverModelInfo.jsx +++ b/DashAI/front/src/components/models/model/HoverModelInfo.jsx @@ -3,13 +3,24 @@ import { Box, Typography, Popover } from "@mui/material"; import { useTranslation } from "react-i18next"; import { useTheme } from "@mui/material/styles"; +const formatSize = (bytes) => { + if (bytes == null) return null; + const mb = bytes / 1024 / 1024; + if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`; + return `${Math.round(mb)} MB`; +}; + export default function HoverModelInfo({ anchorEl, hoveredModel, handleMouseLeave, }) { - const { t } = useTranslation(["common"]); + const { t } = useTranslation(["common", "custom"]); const theme = useTheme(); + const size = formatSize( + hoveredModel?.metadata?.download_size_bytes || + hoveredModel?.download_size_bytes, + ); return ( + + {size && ( + + {t("custom:modelSize", { size })} + + )} )} diff --git a/DashAI/front/src/utils/i18n/locales/de/custom.json b/DashAI/front/src/utils/i18n/locales/de/custom.json index cda354d45..3e848d208 100644 --- a/DashAI/front/src/utils/i18n/locales/de/custom.json +++ b/DashAI/front/src/utils/i18n/locales/de/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "Element auswählen, um die Beschreibung zu sehen.", "selectInferenceMethods": "Wählen Sie die anzuwendenden Inferenzmethoden", "search": "Suchen", + "modelSize": "Größe: {{size}}", "noItemsFound": "Keine Komponenten gefunden", "tryAdjustingSearch": "Versuchen Sie, Ihre Suche oder Filter anzupassen", "componentsAvailable_one": "{{count}} Komponente verfügbar", diff --git a/DashAI/front/src/utils/i18n/locales/en/custom.json b/DashAI/front/src/utils/i18n/locales/en/custom.json index 7ca90eba3..3e8644117 100644 --- a/DashAI/front/src/utils/i18n/locales/en/custom.json +++ b/DashAI/front/src/utils/i18n/locales/en/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "Select an item to see the description.", "selectInferenceMethods": "Select the inference methods you want to apply", "search": "Search", + "modelSize": "Size: {{size}}", "noItemsFound": "No components found", "tryAdjustingSearch": "Try adjusting your search or filters", "componentsAvailable_one": "{{count}} component available", diff --git a/DashAI/front/src/utils/i18n/locales/es/custom.json b/DashAI/front/src/utils/i18n/locales/es/custom.json index c2e1699b5..ad33b4e0b 100644 --- a/DashAI/front/src/utils/i18n/locales/es/custom.json +++ b/DashAI/front/src/utils/i18n/locales/es/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "Seleccione un elemento para ver la descripción.", "selectInferenceMethods": "Seleccione los métodos de inferencia que desea aplicar", "search": "Buscar", + "modelSize": "Tamaño: {{size}}", "noItemsFound": "No se encontraron componentes", "tryAdjustingSearch": "Intenta ajustar tu búsqueda o filtros", "componentsAvailable_one": "{{count}} componente disponible", diff --git a/DashAI/front/src/utils/i18n/locales/pt/custom.json b/DashAI/front/src/utils/i18n/locales/pt/custom.json index 38a1242a2..054d5b567 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/custom.json +++ b/DashAI/front/src/utils/i18n/locales/pt/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "Selecione um elemento para ver a descrição.", "selectInferenceMethods": "Selecione os métodos de inferência que deseja aplicar", "search": "Buscar", + "modelSize": "Tamanho: {{size}}", "noItemsFound": "Nenhum componente encontrado", "tryAdjustingSearch": "Tente ajustar sua busca ou filtros", "componentsAvailable_one": "{{count}} componente disponível", diff --git a/DashAI/front/src/utils/i18n/locales/zh/custom.json b/DashAI/front/src/utils/i18n/locales/zh/custom.json index 3f033a19d..2b09eb0ea 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/custom.json +++ b/DashAI/front/src/utils/i18n/locales/zh/custom.json @@ -3,6 +3,7 @@ "selectAnItemToShowInfo": "选择一个项目以查看描述。", "selectInferenceMethods": "选择您想要应用的推理方法", "search": "搜索", + "modelSize": "大小:{{size}}", "noItemsFound": "未找到组件", "tryAdjustingSearch": "尝试调整搜索条件或筛选器", "componentsAvailable_one": "{{count}} 个可用组件", From ed784a3967659bdd691a09f617c6ade08729de43 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 14:39:47 -0400 Subject: [PATCH 076/308] fix: correct model descriptions to reflect explicit downloads Transformer and translation model descriptions said weights download on first use, which no longer holds now that downloads are explicit. Update all five languages to state the weights must be downloaded before use. --- DashAI/back/models/hugging_face/albert_transformer.py | 10 +++++----- .../base_text_classification_transformer.py | 4 ++-- DashAI/back/models/hugging_face/bert_transformer.py | 10 +++++----- DashAI/back/models/hugging_face/bertin_transformer.py | 10 +++++----- DashAI/back/models/hugging_face/beto_transformer.py | 10 +++++----- DashAI/back/models/hugging_face/electra_transformer.py | 10 +++++----- DashAI/back/models/hugging_face/m2m100_transformer.py | 10 +++++----- DashAI/back/models/hugging_face/minilm_transformer.py | 10 +++++----- .../hugging_face/multilingual_bert_transformer.py | 10 +++++----- .../models/hugging_face/opus_mt_en_de_transformer.py | 10 +++++----- .../models/hugging_face/opus_mt_en_es_transformer.py | 10 +++++----- .../models/hugging_face/opus_mt_en_fr_transformer.py | 10 +++++----- .../models/hugging_face/opus_mt_en_pt_transformer.py | 10 +++++----- .../models/hugging_face/opus_mt_es_en_transformer.py | 10 +++++----- .../models/hugging_face/opus_mt_fr_en_transformer.py | 10 +++++----- DashAI/back/models/hugging_face/roberta_transformer.py | 10 +++++----- .../back/models/hugging_face/t5_small_transformer.py | 10 +++++----- .../models/hugging_face/xlm_roberta_transformer.py | 10 +++++----- DashAI/back/models/hugging_face/xlnet_transformer.py | 10 +++++----- 19 files changed, 92 insertions(+), 92 deletions(-) diff --git a/DashAI/back/models/hugging_face/albert_transformer.py b/DashAI/back/models/hugging_face/albert_transformer.py index 104deb58a..8fd152db0 100644 --- a/DashAI/back/models/hugging_face/albert_transformer.py +++ b/DashAI/back/models/hugging_face/albert_transformer.py @@ -34,25 +34,25 @@ class AlbertTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Parameter efficient BERT variant for English text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Variante de BERT eficiente en parámetros para clasificación en inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Variante do BERT eficiente em parâmetros para classificação de " "texto em inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Parametereffiziente BERT-Variante für englische Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "参数高效的 BERT 变体,用于英文文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#00838F" diff --git a/DashAI/back/models/hugging_face/base_text_classification_transformer.py b/DashAI/back/models/hugging_face/base_text_classification_transformer.py index dc839c699..a2f9ffbf5 100644 --- a/DashAI/back/models/hugging_face/base_text_classification_transformer.py +++ b/DashAI/back/models/hugging_face/base_text_classification_transformer.py @@ -38,8 +38,8 @@ class HuggingFaceTextClassificationTransformer( - Save/load utilities that preserve custom training parameters. .. note:: - Requires internet access on first use to download pretrained weights - from the Hugging Face Hub. + The pretrained weights must be downloaded from the Hugging Face Hub + (internet access required) before the model can be used. """ MODEL_NAME: str = "" diff --git a/DashAI/back/models/hugging_face/bert_transformer.py b/DashAI/back/models/hugging_face/bert_transformer.py index d22451107..8b2121fea 100644 --- a/DashAI/back/models/hugging_face/bert_transformer.py +++ b/DashAI/back/models/hugging_face/bert_transformer.py @@ -34,24 +34,24 @@ class BertTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Bidirectional BERT model for English text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo BERT bidireccional para clasificación de texto en inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo BERT bidirecional para classificação de texto em inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Bidirektionales BERT-Modell für englische Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "双向 BERT 模型,用于英文文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#1565C0" diff --git a/DashAI/back/models/hugging_face/bertin_transformer.py b/DashAI/back/models/hugging_face/bertin_transformer.py index 109a7b649..2a4cfb608 100644 --- a/DashAI/back/models/hugging_face/bertin_transformer.py +++ b/DashAI/back/models/hugging_face/bertin_transformer.py @@ -34,24 +34,24 @@ class BertinTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Spanish RoBERTa (BERTIN) pretrained on large Spanish corpora. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "RoBERTa en español (BERTIN) preentrenada en grandes corpus en español. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "RoBERTa em espanhol (BERTIN) pré-treinada em grandes corpus em espanhol. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Spanisches RoBERTa (BERTIN) vortrainiert auf großen spanischen Korpora. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "在大型西班牙语语料库上预训练的 RoBERTa(BERTIN)模型。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#AD1457" diff --git a/DashAI/back/models/hugging_face/beto_transformer.py b/DashAI/back/models/hugging_face/beto_transformer.py index db8440718..4c6212137 100644 --- a/DashAI/back/models/hugging_face/beto_transformer.py +++ b/DashAI/back/models/hugging_face/beto_transformer.py @@ -34,24 +34,24 @@ class BetoTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Spanish BERT (BETO) pretrained on Spanish corpora. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "BERT en español (BETO) preentrenado en corpus en español. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "BERT em espanhol (BETO) pré-treinado em corpus em espanhol. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Spanisches BERT (BETO) vortrainiert auf spanischen Korpora. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "在西班牙语语料库上预训练的 BERT(BETO)模型。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#C62828" diff --git a/DashAI/back/models/hugging_face/electra_transformer.py b/DashAI/back/models/hugging_face/electra_transformer.py index 00e26919e..83867a4d7 100644 --- a/DashAI/back/models/hugging_face/electra_transformer.py +++ b/DashAI/back/models/hugging_face/electra_transformer.py @@ -34,24 +34,24 @@ class ElectraTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Sample efficient ELECTRA discriminator for text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Discriminador ELECTRA eficiente en muestras para clasificación de texto. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Discriminador ELECTRA eficiente em amostras para classificação de texto. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Stichprobeneffizienter ELECTRA-Diskriminator für Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "样本高效的 ELECTRA 判别器,用于文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#558B2F" diff --git a/DashAI/back/models/hugging_face/m2m100_transformer.py b/DashAI/back/models/hugging_face/m2m100_transformer.py index 99c023d00..d5eedddd5 100644 --- a/DashAI/back/models/hugging_face/m2m100_transformer.py +++ b/DashAI/back/models/hugging_face/m2m100_transformer.py @@ -131,28 +131,28 @@ class M2M100Transformer(HFPretrainedDownloadMixin, TranslationModel): en=( "Facebook M2M-100 model for direct translation across 100 languages " "using ISO 639-1 codes. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo M2M-100 de Facebook para traducción directa entre 100 idiomas " "usando códigos ISO 639-1. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo M2M-100 do Facebook para tradução direta entre 100 idiomas " "usando códigos ISO 639-1. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Facebook M2M-100-Modell für direkte Übersetzung zwischen 100 Sprachen " "mit ISO 639-1-Codes. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "Facebook M2M-100 模型,使用 ISO 639-1 代码支持" " 100 种语言之间的直接翻译。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#6A1B9A" diff --git a/DashAI/back/models/hugging_face/minilm_transformer.py b/DashAI/back/models/hugging_face/minilm_transformer.py index 23a323b4d..818e1a6a0 100644 --- a/DashAI/back/models/hugging_face/minilm_transformer.py +++ b/DashAI/back/models/hugging_face/minilm_transformer.py @@ -34,24 +34,24 @@ class MiniLMTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Compact, fast MiniLM model for efficient text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo MiniLM compacto y rápido para clasificación de texto eficiente. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo MiniLM compacto e rápido para classificação de texto eficiente. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Kompaktes, schnelles MiniLM-Modell für effiziente Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "紧凑快速的 MiniLM 模型,用于高效文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#0277BD" diff --git a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py index d012a2e8b..916f6a08f 100644 --- a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py +++ b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py @@ -33,26 +33,26 @@ class MultilingualBertTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "BERT pretrained on 104 languages for multilingual text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "BERT preentrenado en 104 idiomas para clasificación de texto " "multilingüe. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "BERT pré-treinado em 104 idiomas para classificação de texto " "multilingual. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "BERT vortrainiert auf 104 Sprachen für mehrsprachige Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "在 104 种语言上预训练的 BERT,用于多语言文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#283593" diff --git a/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py index acc031fa9..5950dc5ed 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py @@ -39,24 +39,24 @@ class OpusMtEnDeTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for English to German translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción inglés-alemán. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução inglês-alemão. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Englisch-Deutsch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于英语到德语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#455A64" diff --git a/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py index 82f827345..203c11fbb 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py @@ -280,24 +280,24 @@ class OpusMtEnESTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for English to Spanish translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción inglés-español. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução inglês-espanhol. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Englisch-Spanisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于英语到西班牙语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#FFA500" diff --git a/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py index ba45b6b3d..ad026f97f 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py @@ -39,24 +39,24 @@ class OpusMtEnFrTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for English to French translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción inglés-francés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução inglês-francês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Englisch-Französisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于英语到法语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#1976D2" diff --git a/DashAI/back/models/hugging_face/opus_mt_en_pt_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_pt_transformer.py index 17a1f2076..1837c7ec2 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_pt_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_pt_transformer.py @@ -39,24 +39,24 @@ class OpusMtEnPtTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for English to Portuguese translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción inglés-portugués. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução inglês-português. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Englisch-Portugiesisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于英语到葡萄牙语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#2E7D32" diff --git a/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py index c7bcc2e81..4fadc8e1e 100644 --- a/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py @@ -42,24 +42,24 @@ class OpusMtEsENTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for Spanish to English translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción español-inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução espanhol-inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Spanisch-Englisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于西班牙语到英语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#FF8A65" diff --git a/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py index 903a3896c..ec793edbb 100644 --- a/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py @@ -39,24 +39,24 @@ class OpusMtFrEnTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for French to English translation. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción francés-inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução francês-inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für Französisch-Englisch-Übersetzung. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于法语到英语翻译的预训练 Transformer。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#0097A7" diff --git a/DashAI/back/models/hugging_face/roberta_transformer.py b/DashAI/back/models/hugging_face/roberta_transformer.py index 8e8fd40f1..e3383a926 100644 --- a/DashAI/back/models/hugging_face/roberta_transformer.py +++ b/DashAI/back/models/hugging_face/roberta_transformer.py @@ -34,24 +34,24 @@ class RobertaTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Robustly optimised BERT for English text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "BERT optimizado robustamente para clasificación de texto en inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "BERT otimizado robustamente para classificação de texto em inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Robust optimiertes BERT für englische Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "经过鲁棒优化的 BERT,用于英文文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#E65100" diff --git a/DashAI/back/models/hugging_face/t5_small_transformer.py b/DashAI/back/models/hugging_face/t5_small_transformer.py index 366de45b5..997199351 100644 --- a/DashAI/back/models/hugging_face/t5_small_transformer.py +++ b/DashAI/back/models/hugging_face/t5_small_transformer.py @@ -105,27 +105,27 @@ class T5SmallTransformer(HFPretrainedDownloadMixin, TranslationModel): en=( "Google T5-small model for English-to-{German, French, Romanian} " "translation using task prefixes. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo T5-small de Google para traducción inglés-{alemán, francés, " "rumano} usando prefijos de tarea. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo T5-small do Google para tradução inglês-{alemão, francês, " "romeno} usando prefixos de tarefa. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Google T5-small-Modell für Englisch-zu-{Deutsch, Französisch, Rumänisch}-" "Übersetzung mit Aufgabenpräfixen. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "谷歌 T5-small 模型,通过任务前缀实现英语到德语/法语/罗马尼亚语翻译。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#00695C" diff --git a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py index 51722452e..caf06a6a4 100644 --- a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py +++ b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py @@ -35,27 +35,27 @@ class XlmRobertaTransformer(HuggingFaceTextClassificationTransformer): en=( "Multilingual RoBERTa for crosslingual text classification " "(100 languages). " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "RoBERTa multilingüe para clasificación de texto entre idiomas " "(100 idiomas). " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "RoBERTa multilingual para classificação de texto entre idiomas " "(100 idiomas). " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Mehrsprachiges RoBERTa für sprachübergreifende Textklassifikation " "(100 Sprachen). " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "支持跨语言文本分类的多语言 RoBERTa(100 种语言)。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#6A1B9A" diff --git a/DashAI/back/models/hugging_face/xlnet_transformer.py b/DashAI/back/models/hugging_face/xlnet_transformer.py index 0bc110548..9a7f59050 100644 --- a/DashAI/back/models/hugging_face/xlnet_transformer.py +++ b/DashAI/back/models/hugging_face/xlnet_transformer.py @@ -34,24 +34,24 @@ class XlnetTransformer(HuggingFaceTextClassificationTransformer): DESCRIPTION: str = MultilingualString( en=( "Autoregressive XLNet model for English text classification. " - "Downloads weights from Hugging Face on first use (internet required)." + "Download its weights from Hugging Face before use (internet required)." ), es=( "Modelo XLNet autorregresivo para clasificación de texto en inglés. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Modelo XLNet autorregressivo para classificação de texto em inglês. " - "Baixa os pesos do Hugging Face no primeiro uso (requer internet)." + "Baixe seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Autoregressives XLNet-Modell für englische Textklassifikation. " - "Lädt Gewichte von Hugging Face bei der ersten Verwendung herunter " + "Lädt die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "自回归 XLNet 模型,用于英文文本分类。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#37474F" From d6afec323f3da0e77095a168e670968c8ea5e7b6 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 14:47:04 -0400 Subject: [PATCH 077/308] fix: refresh models side bar when any download finishes The row disabled state derives from the fetched model list, so a download that completes after navigating back (started by a previous mount) left the row disabled. Subscribe to the shared download state and refetch on completion or delete. --- .../src/components/models/ModelsRightBar.jsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/DashAI/front/src/components/models/ModelsRightBar.jsx b/DashAI/front/src/components/models/ModelsRightBar.jsx index bb00620c7..513c6daf2 100644 --- a/DashAI/front/src/components/models/ModelsRightBar.jsx +++ b/DashAI/front/src/components/models/ModelsRightBar.jsx @@ -7,7 +7,10 @@ import { useSnackbar } from "notistack"; import SideBar from "../threeSectionLayout/panelContainers/SideBar"; import { getComponents } from "../../api/component"; import ModelListItem from "./model/ModelListItem"; -import { startComponentDownload } from "./model/ComponentDownloadControl"; +import { + startComponentDownload, + subscribeAnyDownloadState, +} from "./model/ComponentDownloadControl"; import ModelDownloadStatusIcon from "./model/ModelDownloadStatusIcon"; import { useTranslation } from "react-i18next"; import { useTourContext } from "../tour/TourProvider"; @@ -66,6 +69,17 @@ export default function ModelsRightBar({ onToggle }) { } }, [session, fetchModels]); + // Refetch when any download finishes (or is deleted) so the row's disabled + // state, which is derived from the fetched model list, stays in sync. The + // download may have been started by a previous mount of this panel, so we + // cannot rely on that download's onStatusChange callback firing here. + useEffect(() => { + if (!session) return undefined; + return subscribeAnyDownloadState((_name, state) => { + if (state.downloaded !== undefined) fetchModels(); + }); + }, [session, fetchModels]); + // Filter models based on search useEffect(() => { if (searchQuery.trim() === "") { From 8813305cce6dcbb299dbaeb8aa214c7968e8d29c Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 14:53:06 -0400 Subject: [PATCH 078/308] fix: keep model row disabled while its download is in progress The backend can report a download as present mid-download (its folder is already non-empty), which un-disabled the row while the icon still showed a spinner. Drive the row's disabled state from the shared live download state, treating an in-progress download as not ready so the row and icon always agree. --- .../src/components/models/ModelsRightBar.jsx | 104 ++++++++++++------ 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/DashAI/front/src/components/models/ModelsRightBar.jsx b/DashAI/front/src/components/models/ModelsRightBar.jsx index 513c6daf2..797da998b 100644 --- a/DashAI/front/src/components/models/ModelsRightBar.jsx +++ b/DashAI/front/src/components/models/ModelsRightBar.jsx @@ -10,8 +10,50 @@ import ModelListItem from "./model/ModelListItem"; import { startComponentDownload, subscribeAnyDownloadState, + useComponentDownloadState, } from "./model/ComponentDownloadControl"; import ModelDownloadStatusIcon from "./model/ModelDownloadStatusIcon"; + +/** + * A single model row whose disabled state and download icon both derive from + * the shared live download state, so they never disagree. While a download is + * in progress the row stays disabled even if the backend already reports the + * (partially written) files as present. + */ +function ModelRow({ model, onUse, onDownload, onChanged, dataTour }) { + const requiresDownload = Boolean(model.metadata?.requires_download); + const { downloaded, downloading } = useComponentDownloadState(model); + const ready = !requiresDownload || (downloaded && !downloading); + + const handleClick = () => { + if (downloading) return; + if (ready) onUse(model); + else onDownload(model); + }; + + return ( + + ) : null + } + /> + ); +} + +ModelRow.propTypes = { + model: PropTypes.object.isRequired, + onUse: PropTypes.func.isRequired, + onDownload: PropTypes.func.isRequired, + onChanged: PropTypes.func, + dataTour: PropTypes.string, +}; import { useTranslation } from "react-i18next"; import { useTourContext } from "../tour/TourProvider"; import { useModels } from "./ModelsContext"; @@ -98,24 +140,13 @@ export default function ModelsRightBar({ onToggle }) { const tourContext = useTourContext(); - const handleModelClick = (model) => { + const handleUseModel = (model) => { if (!session) { enqueueSnackbar(t("models:error.selectSessionFirst"), { variant: "warning", }); return; } - // A download-required model that has not been downloaded cannot be - // configured; it is blocked in the list with an inline download control. - if (model.metadata?.requires_download && !model.downloaded) { - startComponentDownload({ - component: model, - enqueueSnackbar, - t, - onStatusChange: () => fetchModels(), - }); - return; - } selectModel(model); if (tourContext?.run && tourContext?.stepIndex === 2) { const waitForElement = () => { @@ -130,6 +161,21 @@ export default function ModelsRightBar({ onToggle }) { } }; + const handleDownloadModel = (model) => { + if (!session) { + enqueueSnackbar(t("models:error.selectSessionFirst"), { + variant: "warning", + }); + return; + } + startComponentDownload({ + component: model, + enqueueSnackbar, + t, + onStatusChange: () => fetchModels(), + }); + }; + if (sessionRightContent) { return ( @@ -263,30 +309,16 @@ export default function ModelsRightBar({ onToggle }) { ) : ( - {filteredModels.map((model, index) => { - const requiresDownload = Boolean( - model.metadata?.requires_download, - ); - const needsDownload = requiresDownload && !model.downloaded; - return ( - handleModelClick(model)} - onDisabledClick={() => handleModelClick(model)} - data-tour={index === 0 ? "first-model" : undefined} - action={ - requiresDownload ? ( - fetchModels()} - /> - ) : null - } - /> - ); - })} + {filteredModels.map((model, index) => ( + + ))} )} From 640212b1878b0037cf95e7359225997774e80134 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 14:56:24 -0400 Subject: [PATCH 079/308] fix: avoid scroll reset when a model download finishes or is deleted Refetching the model list flipped loading, swapping the list for a spinner and resetting scroll. Update the affected model's downloaded flag in place from the shared download-state subscription instead, keeping the list mounted and the scroll position intact. --- .../src/components/models/ModelsRightBar.jsx | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/DashAI/front/src/components/models/ModelsRightBar.jsx b/DashAI/front/src/components/models/ModelsRightBar.jsx index 797da998b..98a680fca 100644 --- a/DashAI/front/src/components/models/ModelsRightBar.jsx +++ b/DashAI/front/src/components/models/ModelsRightBar.jsx @@ -20,7 +20,7 @@ import ModelDownloadStatusIcon from "./model/ModelDownloadStatusIcon"; * in progress the row stays disabled even if the backend already reports the * (partially written) files as present. */ -function ModelRow({ model, onUse, onDownload, onChanged, dataTour }) { +function ModelRow({ model, onUse, onDownload, dataTour }) { const requiresDownload = Boolean(model.metadata?.requires_download); const { downloaded, downloading } = useComponentDownloadState(model); const ready = !requiresDownload || (downloaded && !downloading); @@ -39,9 +39,7 @@ function ModelRow({ model, onUse, onDownload, onChanged, dataTour }) { onDisabledClick={handleClick} data-tour={dataTour} action={ - requiresDownload ? ( - - ) : null + requiresDownload ? : null } /> ); @@ -51,7 +49,6 @@ ModelRow.propTypes = { model: PropTypes.object.isRequired, onUse: PropTypes.func.isRequired, onDownload: PropTypes.func.isRequired, - onChanged: PropTypes.func, dataTour: PropTypes.string, }; import { useTranslation } from "react-i18next"; @@ -111,16 +108,23 @@ export default function ModelsRightBar({ onToggle }) { } }, [session, fetchModels]); - // Refetch when any download finishes (or is deleted) so the row's disabled - // state, which is derived from the fetched model list, stays in sync. The - // download may have been started by a previous mount of this panel, so we - // cannot rely on that download's onStatusChange callback firing here. + // When any download finishes (or is deleted) update just that model's flag in + // place. A full refetch would flip `loading`, swap the list for a spinner and + // reset the scroll position; an in-place update keeps the list mounted and + // keeps `downloaded` accurate for the model passed on to the config dialog. useEffect(() => { if (!session) return undefined; - return subscribeAnyDownloadState((_name, state) => { - if (state.downloaded !== undefined) fetchModels(); + return subscribeAnyDownloadState((name, state) => { + if (state.downloaded === undefined) return; + setModels((prev) => + prev.map((model) => + model.name === name + ? { ...model, downloaded: state.downloaded } + : model, + ), + ); }); - }, [session, fetchModels]); + }, [session]); // Filter models based on search useEffect(() => { @@ -168,12 +172,10 @@ export default function ModelsRightBar({ onToggle }) { }); return; } - startComponentDownload({ - component: model, - enqueueSnackbar, - t, - onStatusChange: () => fetchModels(), - }); + // Completion is reflected by the shared download-state subscription above, + // which updates the model's flag in place without a scroll-resetting + // refetch. + startComponentDownload({ component: model, enqueueSnackbar, t }); }; if (sessionRightContent) { @@ -315,7 +317,6 @@ export default function ModelsRightBar({ onToggle }) { model={model} onUse={handleUseModel} onDownload={handleDownloadModel} - onChanged={fetchModels} dataTour={index === 0 ? "first-model" : undefined} /> ))} From 0ba2b4b80e4c624087d9034ce36537dd74c4617a Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 15:02:34 -0400 Subject: [PATCH 080/308] fix: render the model delete control as an icon button The delete affordance in the models side bar is now a Material IconButton (hover and ripple feedback) instead of a bare clickable icon; the download hint stays a plain icon. --- .../models/model/ModelDownloadStatusIcon.jsx | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx index 9fd77a39b..3c0920385 100644 --- a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx +++ b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx @@ -1,6 +1,6 @@ import React from "react"; import PropTypes from "prop-types"; -import { Box, CircularProgress } from "@mui/material"; +import { Box, CircularProgress, IconButton } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import DeleteIcon from "@mui/icons-material/Delete"; import { useSnackbar } from "notistack"; @@ -31,9 +31,9 @@ export default function ModelDownloadStatusIcon({ model, onChanged }) { if (downloaded) { return ( - { e.stopPropagation(); @@ -44,15 +44,9 @@ export default function ModelDownloadStatusIcon({ model, onChanged }) { onStatusChange: onChanged, }); }} - sx={{ - display: "flex", - alignItems: "center", - color: "error.main", - cursor: "pointer", - }} > - + ); } From 38552a58d4533ee873c16d214ba746c908206168 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 15:04:07 -0400 Subject: [PATCH 081/308] feat: add a tooltip to the model delete icon button --- .../models/model/ModelDownloadStatusIcon.jsx | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx index 3c0920385..07eea8edb 100644 --- a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx +++ b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.jsx @@ -1,6 +1,6 @@ import React from "react"; import PropTypes from "prop-types"; -import { Box, CircularProgress, IconButton } from "@mui/material"; +import { Box, CircularProgress, IconButton, Tooltip } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import DeleteIcon from "@mui/icons-material/Delete"; import { useSnackbar } from "notistack"; @@ -31,22 +31,24 @@ export default function ModelDownloadStatusIcon({ model, onChanged }) { if (downloaded) { return ( - { - e.stopPropagation(); - deleteComponent({ - component: model, - enqueueSnackbar, - t, - onStatusChange: onChanged, - }); - }} - > - - + + { + e.stopPropagation(); + deleteComponent({ + component: model, + enqueueSnackbar, + t, + onStatusChange: onChanged, + }); + }} + > + + + ); } From 5932951f8707f010670d8cc10e7060c2052bf05b Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 15:08:51 -0400 Subject: [PATCH 082/308] fix: disable train and retrain when the run model is not downloaded A run whose model still needs downloading cannot be trained, so the Train/Retrain button is disabled with a tooltip until the model is downloaded. The state comes from the shared live download state so an inline download re-enables it. --- .../front/src/components/models/RunCard.jsx | 63 ++++++++++++------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/DashAI/front/src/components/models/RunCard.jsx b/DashAI/front/src/components/models/RunCard.jsx index f4f2b18f3..22c7a1ea9 100644 --- a/DashAI/front/src/components/models/RunCard.jsx +++ b/DashAI/front/src/components/models/RunCard.jsx @@ -45,6 +45,7 @@ import FormSchemaContainer from "../shared/FormSchemaContainer"; import OptimizationTableSelectOptimizer from "./modelSession/OptimizationTableSelectOptimizer"; import ModelsTableSelectMetric from "./modelSession/ModelsTableSelectMetric"; import useSchema from "../../hooks/useSchema"; +import { useComponentDownloadState } from "./model/ComponentDownloadControl"; import { updateRunParameters, getRunOperationsCount } from "../../api/run"; import RetrainConfirmDialog from "./RetrainConfirmDialog"; import { renderParamValue } from "./ModelParamBlock"; @@ -252,6 +253,15 @@ function RunCard({ const model = models.find((m) => m.name === run.model_name); const modelDisplayName = model?.display_name || run.model_name; + // A download-required model must be downloaded before it can be trained. + // Track the live download state so the button reflects an inline download. + const { downloaded, downloading } = useComponentDownloadState( + model || { name: run.model_name }, + ); + const modelNotDownloaded = + Boolean(model?.metadata?.requires_download) && + !(downloaded && !downloading); + const getStatusColor = (status) => { switch (status) { case 0: @@ -373,32 +383,37 @@ function RunCard({ {canTrain && ( 0 || - operationsCount.predictions > 0) - ? t("models:message.retrainWillResetOperations", { - explainersCount: operationsCount.explainers, - predictionsCount: operationsCount.predictions, - }) - : "" + modelNotDownloaded + ? t("common:componentDownload.mustDownload") + : run.status === 3 && + operationsCount && + (operationsCount.explainers > 0 || + operationsCount.predictions > 0) + ? t("models:message.retrainWillResetOperations", { + explainersCount: operationsCount.explainers, + predictionsCount: operationsCount.predictions, + }) + : "" } > - + + + )} {isRunning && ( From b2e868667c1e409c613dc4be2c76c7153b906a77 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 15:12:52 -0400 Subject: [PATCH 083/308] fix: skip undownloaded models in Run All Run All trained every not-started run, including ones whose model still needs downloading. It now trains only runs whose model is ready (downloaded or download-free), reading the live download state, and warns when any were skipped. --- .../models/SessionVisualization.jsx | 36 ++++++++++++++++--- .../models/model/ComponentDownloadControl.jsx | 5 +++ .../src/utils/i18n/locales/de/models.json | 1 + .../src/utils/i18n/locales/en/models.json | 1 + .../src/utils/i18n/locales/es/models.json | 1 + .../src/utils/i18n/locales/pt/models.json | 1 + .../src/utils/i18n/locales/zh/models.json | 1 + 7 files changed, 42 insertions(+), 4 deletions(-) diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index 2fd1e0da3..6fbd987c3 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -23,9 +23,11 @@ import { import ModelComparisonTable from "./ModelComparisonTable"; import RunCard from "./RunCard"; import { getComponents } from "../../api/component"; +import { getComponentDownloadState } from "./model/ComponentDownloadControl"; import ResultsGraphs from "../../pages/results/components/ResultsGraphs"; import RetrainConfirmDialog from "./RetrainConfirmDialog"; import { useTranslation } from "react-i18next"; +import { useSnackbar } from "notistack"; import { useModels } from "./ModelsContext"; import { useTourContext } from "../tour/TourProvider"; @@ -42,6 +44,7 @@ export default function SessionVisualization() { const [explainerRefreshTrigger, setExplainerRefreshTrigger] = useState(0); const isResizing = React.useRef(false); const { t } = useTranslation(["models", "common"]); + const { enqueueSnackbar } = useSnackbar(); const sessionTourContext = useTourContext(); const { @@ -199,6 +202,34 @@ export default function SessionVisualization() { } }; + // True when a run's model is ready to train: it either needs no download or + // its download is present and not in progress (live state overrides the + // possibly stale fetched flag). + const isRunModelReady = React.useCallback( + (run) => { + const model = models.find((m) => m.name === run.model_name); + if (!model?.metadata?.requires_download) return true; + const cached = getComponentDownloadState(run.model_name); + const downloaded = cached?.downloaded ?? Boolean(model.downloaded); + const downloading = Boolean(cached?.downloading); + return downloaded && !downloading; + }, + [models], + ); + + // Train every not-started run whose model is downloaded, skipping (and warning + // about) any whose model still needs downloading. + const handleRunAll = () => { + const notStarted = runs.filter((r) => r.status === 0); + const ready = notStarted.filter(isRunModelReady); + ready.forEach((run) => onTrain(run)); + if (ready.length < notStarted.length) { + enqueueSnackbar(t("models:message.skippedUndownloadedRuns"), { + variant: "warning", + }); + } + }; + const handleMouseMove = React.useCallback((e) => { if (isResizing.current) { const details = document.querySelector("[data-accordion-details]"); @@ -444,10 +475,7 @@ export default function SessionVisualization() { variant="contained" size="small" startIcon={} - onClick={() => { - const notStartedRuns = runs.filter((r) => r.status === 0); - notStartedRuns.forEach((run) => onTrain(run)); - }} + onClick={handleRunAll} > {t("models:button.runAll")} diff --git a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx index 7cf74afde..414e1422a 100644 --- a/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx +++ b/DashAI/front/src/components/models/model/ComponentDownloadControl.jsx @@ -41,6 +41,11 @@ const subscribeDownloadState = (name, listener) => { }; }; +// Read the last known download state for a component name, or undefined if it +// has not been tracked this session. Lets non-hook call sites (e.g. a bulk +// "run all" handler) consult the live state without subscribing. +export const getComponentDownloadState = (name) => downloadStateCache.get(name); + // Subscribe to every download/delete regardless of component name. Lets a // container (e.g. a config dialog) re-check which nested components still need // downloading after an inline control finishes. diff --git a/DashAI/front/src/utils/i18n/locales/de/models.json b/DashAI/front/src/utils/i18n/locales/de/models.json index c79b662c4..0ad723411 100644 --- a/DashAI/front/src/utils/i18n/locales/de/models.json +++ b/DashAI/front/src/utils/i18n/locales/de/models.json @@ -177,6 +177,7 @@ "chartType": "Diagrammtyp" }, "message": { + "skippedUndownloadedRuns": "Durchläufe mit nicht heruntergeladenem Modell wurden übersprungen. Lade das Modell zuerst herunter.", "allRunsCompleted": "{{experiment}} hat alle Durchläufe abgeschlossen.", "confirmDeleteRun": "Sind Sie sicher, dass Sie diesen Durchlauf löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", "editingParametersWarning": "Das Bearbeiten von Parametern setzt den Status dieses Durchlaufs auf 'Nicht gestartet' zurück. Alle vorhandenen Metriken und Ergebnisse gehen verloren.", diff --git a/DashAI/front/src/utils/i18n/locales/en/models.json b/DashAI/front/src/utils/i18n/locales/en/models.json index 3770033e7..9a9c1bfc6 100644 --- a/DashAI/front/src/utils/i18n/locales/en/models.json +++ b/DashAI/front/src/utils/i18n/locales/en/models.json @@ -177,6 +177,7 @@ "chartType": "Chart type" }, "message": { + "skippedUndownloadedRuns": "Skipped runs whose model is not downloaded. Download the model first.", "allRunsCompleted": "{{experiment}} has completed all its runs.", "confirmDeleteRun": "Are you sure you want to delete this run? This action cannot be undone.", "editingParametersWarning": "Editing parameters will reset this run's status to 'Not Started'. All existing metrics and results will be lost.", diff --git a/DashAI/front/src/utils/i18n/locales/es/models.json b/DashAI/front/src/utils/i18n/locales/es/models.json index 7ede341d8..03ae785df 100644 --- a/DashAI/front/src/utils/i18n/locales/es/models.json +++ b/DashAI/front/src/utils/i18n/locales/es/models.json @@ -179,6 +179,7 @@ "chartType": "Tipo de gráfico" }, "message": { + "skippedUndownloadedRuns": "Se omitieron las ejecuciones cuyo modelo no está descargado. Descarga el modelo primero.", "allRunsCompleted": "{{experiment}} ha completado todas sus ejecuciones.", "confirmDeleteRun": "¿Está seguro de que desea eliminar esta ejecución? Esta acción no se puede deshacer.", "editingParametersWarning": "Al editar los parámetros se restablecerá el estado de esta ejecución a 'No Iniciado'. Todas las métricas y resultados existentes se perderán.", diff --git a/DashAI/front/src/utils/i18n/locales/pt/models.json b/DashAI/front/src/utils/i18n/locales/pt/models.json index 50f551a17..0de61f6fa 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/models.json +++ b/DashAI/front/src/utils/i18n/locales/pt/models.json @@ -179,6 +179,7 @@ "chartType": "Tipo de gráfico" }, "message": { + "skippedUndownloadedRuns": "Execuções cujo modelo não está baixado foram ignoradas. Baixe o modelo primeiro.", "allRunsCompleted": "{{experiment}} concluiu todas as suas execuções.", "confirmDeleteRun": "Tem certeza de que deseja excluir esta execução? Esta ação não pode ser desfeita.", "editingParametersWarning": "Ao editar os parâmetros, o estado desta execução será redefinido para 'Não Iniciado'. Todas as métricas e resultados existentes serão perdidos.", diff --git a/DashAI/front/src/utils/i18n/locales/zh/models.json b/DashAI/front/src/utils/i18n/locales/zh/models.json index 666fdfe2f..23fc67646 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/models.json +++ b/DashAI/front/src/utils/i18n/locales/zh/models.json @@ -177,6 +177,7 @@ "chartType": "图表类型" }, "message": { + "skippedUndownloadedRuns": "已跳过模型未下载的运行。请先下载模型。", "allRunsCompleted": "{{experiment}} 已完成所有运行。", "confirmDeleteRun": "确定要删除此运行吗?此操作无法撤销。", "editingParametersWarning": "编辑参数将把此运行的状态重置为「未开始」。所有现有指标和结果将丢失。", From 89c16cb3ce3212efbe4f31392cc08e3c4335c4b1 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 15:24:54 -0400 Subject: [PATCH 084/308] fix: block generative session while its model is still downloading The chat input used a one-time reconciled download status that reports true mid-download (partial files), so a still-downloading model was usable. Both the input gate and the model switcher labels now read the shared live download state, blocking while downloading and updating the moment a download finishes. --- .../components/generative/GenerativeChat.jsx | 11 ++++++++-- .../components/generative/ModelSwitcher.jsx | 21 +++++++++++++++++-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/DashAI/front/src/components/generative/GenerativeChat.jsx b/DashAI/front/src/components/generative/GenerativeChat.jsx index 016a9a631..6fa754c9d 100644 --- a/DashAI/front/src/components/generative/GenerativeChat.jsx +++ b/DashAI/front/src/components/generative/GenerativeChat.jsx @@ -21,7 +21,9 @@ import { import { getRelatedComponents } from "../../api/generativeTask"; import InfoSessionModal from "./InfoSessionModal"; import ModelSwitcher from "./ModelSwitcher"; -import ComponentDownloadControl from "../models/model/ComponentDownloadControl"; +import ComponentDownloadControl, { + useComponentDownloadState, +} from "../models/model/ComponentDownloadControl"; import { useSnackbar } from "notistack"; import { MediaInput } from "./MediaInput"; import { Trans, useTranslation } from "react-i18next"; @@ -127,9 +129,14 @@ export default function GenerativeChat() { .catch(() => setModelsByName({})); }, [sessionInfo?.task_name]); + // Use the live download state so an in-progress download keeps the input + // blocked even when the backend already reports the (partial) files as + // present, and unblocks the moment the download actually finishes. + const { downloaded: liveDownloaded, downloading: liveDownloading } = + useComponentDownloadState(modelComponent || { name: modelName || "" }); const modelBlocked = Boolean(modelComponent?.metadata?.requires_download) && - !modelComponent?.downloaded; + !(liveDownloaded && !liveDownloading); const getMessages = () => { getProcessesBySessionId(sessionId).then((response) => { diff --git a/DashAI/front/src/components/generative/ModelSwitcher.jsx b/DashAI/front/src/components/generative/ModelSwitcher.jsx index 54308112c..7a517b2d0 100644 --- a/DashAI/front/src/components/generative/ModelSwitcher.jsx +++ b/DashAI/front/src/components/generative/ModelSwitcher.jsx @@ -5,6 +5,10 @@ import { useSnackbar } from "notistack"; import { useTranslation } from "react-i18next"; import { getRelatedComponents } from "../../api/generativeTask"; import { updateGenerativeSession } from "../../api/session"; +import { + getComponentDownloadState, + subscribeAnyDownloadState, +} from "../models/model/ComponentDownloadControl"; /** * Session-level model switcher: lets the user change the model used by a @@ -21,6 +25,9 @@ export default function ModelSwitcher({ const { enqueueSnackbar } = useSnackbar(); const [models, setModels] = useState([]); const [saving, setSaving] = useState(false); + // Bump to re-render when any download state changes so the labels reflect + // downloads that finished after the model list was fetched. + const [, setDownloadVersion] = useState(0); useEffect(() => { if (!taskName) return; @@ -29,6 +36,11 @@ export default function ModelSwitcher({ .catch(() => setModels([])); }, [taskName]); + useEffect( + () => subscribeAnyDownloadState(() => setDownloadVersion((v) => v + 1)), + [], + ); + const handleChange = async (event) => { const newModel = event.target.value; if (!newModel || newModel === currentModelName) return; @@ -75,10 +87,15 @@ export default function ModelSwitcher({ > {options.map((model) => { // A not-downloaded model is still selectable; the chat blocks input - // and offers the download once it becomes the session's model. + // and offers the download once it becomes the session's model. Read + // the live download state so a finished download drops the label and + // an in-progress one keeps it despite a premature backend flag. + const cached = getComponentDownloadState(model.name); + const downloaded = cached?.downloaded ?? model.downloaded; + const downloading = Boolean(cached?.downloading); const notDownloaded = Boolean(model.metadata?.requires_download) && - !model.downloaded && + !(downloaded && !downloading) && model.name !== currentModelName; return ( From 383ea0a2b426ab4a73486c674034dd939583caad Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 15:39:57 -0400 Subject: [PATCH 085/308] fix: stop reporting freshly queued jobs as failed The job poller's final flush reported every non-finished watched job as an error when the queue briefly looked empty, so a download queued right after a delete showed a false failure. It now only errors a job that vanished or is actually in an error state, and keeps watching pending or running jobs. --- DashAI/front/src/utils/jobPoller.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/DashAI/front/src/utils/jobPoller.js b/DashAI/front/src/utils/jobPoller.js index 2a981e372..5f0fc7dba 100644 --- a/DashAI/front/src/utils/jobPoller.js +++ b/DashAI/front/src/utils/jobPoller.js @@ -125,13 +125,23 @@ async function pollJobs() { const job = allJobs.find((j) => j.id === jobId); if (job?.status === "finished") { if (watcher.onSuccess) watcher.onSuccess(job); - } else { + stopJobPolling(jobId); + } else if (job?.status === "error") { + if (watcher.onError) watcher.onError(job); + stopJobPolling(jobId); + } else if (!job) { + // The job vanished from the queue entirely; treat it as failed. if (watcher.onError) - watcher.onError(job || { id: jobId, status: "deleted" }); + watcher.onError({ id: jobId, status: "deleted" }); + stopJobPolling(jobId); } + // A job still pending or running is left watched and rechecked on the + // next poll, so a freshly queued job is never reported as an error + // just because the queue briefly looked empty. + } + if (state.jobWatchers.size === 0) { + stopJobPoller(); } - state.jobWatchers.clear(); - stopJobPoller(); return; } catch (e) { console.error("[JobPoller] Final flush failed, will retry:", e); From 90ffb3fd2ec63bfd94d7e56d220c8274428d10c0 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 15:50:39 -0400 Subject: [PATCH 086/308] chore: remove dummy downloadable classifier used for nested download testing --- DashAI/back/initial_components.py | 4 - .../dummy_downloadable_classifier.py | 144 ------------------ 2 files changed, 148 deletions(-) delete mode 100644 DashAI/back/models/scikit_learn/dummy_downloadable_classifier.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index eaa6d6ee4..55be38ecb 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -257,9 +257,6 @@ DecisionTreeRegression, ) from DashAI.back.models.scikit_learn.dummy_classifier import DummyClassifier -from DashAI.back.models.scikit_learn.dummy_downloadable_classifier import ( - DummyDownloadableClassifier, -) from DashAI.back.models.scikit_learn.elastic_net_regression import ElasticNetRegression from DashAI.back.models.scikit_learn.extra_trees_classifier import ExtraTreesClassifier from DashAI.back.models.scikit_learn.extra_trees_regression import ExtraTreesRegression @@ -427,7 +424,6 @@ def get_initial_components(): RealVisXLV4, StableDiffusionXLV1ControlNet, SVC, - DummyDownloadableClassifier, SVR, T5SmallTransformer, TfIdfLogRegTextClassificationModel, diff --git a/DashAI/back/models/scikit_learn/dummy_downloadable_classifier.py b/DashAI/back/models/scikit_learn/dummy_downloadable_classifier.py deleted file mode 100644 index c69909ec3..000000000 --- a/DashAI/back/models/scikit_learn/dummy_downloadable_classifier.py +++ /dev/null @@ -1,144 +0,0 @@ -import time -from typing import Optional - -from DashAI.back.core.schema_fields import ( - BaseSchema, - component_field, - schema_field, -) -from DashAI.back.core.utils import MultilingualString -from DashAI.back.dependencies.downloads.downloadable import ( - DownloadableMixin, - ProgressReporter, -) -from DashAI.back.models.scikit_learn.svc import SVC - - -class DummyDownloadableClassifierSchema(BaseSchema): - """Schema for the dummy classifier, exposing one nested classifier field. - - The ``nested_classifier`` parameter is a component field so a second - (possibly download-required) tabular classifier can be selected inside - this one, letting the nested-download flow be exercised at depth. - """ - - nested_classifier: schema_field( - component_field(parent="TabularClassificationModel"), - placeholder={"component": "SVC", "params": {}}, - description=MultilingualString( - en="A nested tabular classifier, used to test nested downloads.", - es="Un clasificador tabular anidado, para probar descargas anidadas.", - pt="Um classificador tabular aninhado, para testar downloads aninhados.", - de=( - "Ein verschachtelter tabellarischer Klassifikator zum Testen " - "verschachtelter Downloads." - ), - zh="嵌套的表格分类器,用于测试嵌套下载。", - ), - alias=MultilingualString( - en="Nested classifier", - es="Clasificador anidado", - pt="Classificador aninhado", - de="Verschachtelter Klassifikator", - zh="嵌套分类器", - ), - ) # type: ignore - - -class DummyDownloadableClassifier(DownloadableMixin, SVC): - """A fake download-required tabular classifier for UI testing. - - Behaves exactly like :class:`SVC` at train time but is flagged as - requiring a download so the inline download control appears when it is - selected as another component's parameter (e.g. the Bag-of-Words tabular - classifier). Its ``download`` writes a marker file instead of fetching any - real artifact, so the download/delete flow can be exercised end to end - without network access. - """ - - SCHEMA = DummyDownloadableClassifierSchema - DOWNLOAD_SIZE_BYTES = 256 * 1024 * 1024 - COLOR = "#B39DDB" - ICON = "Timeline" - DISPLAY_NAME = MultilingualString( - en="Dummy Downloadable Classifier", - es="Clasificador Descargable de Prueba", - pt="Classificador Baixavel de Teste", - de="Dummy Herunterladbarer Klassifikator", - zh="虚拟可下载分类器", - ) - DESCRIPTION = MultilingualString( - en=( - "A test-only classifier that requires a download. It trains like an " - "SVM but is used to preview the inline download control when picking " - "a nested component." - ), - es=( - "Un clasificador solo de prueba que requiere descarga. Entrena como " - "una SVM, pero sirve para previsualizar el control de descarga en " - "linea al elegir un componente anidado." - ), - pt=( - "Um classificador apenas de teste que requer download. Treina como " - "uma SVM, mas serve para pre-visualizar o controle de download em " - "linha ao escolher um componente aninhado." - ), - de=( - "Ein reiner Testklassifikator, der einen Download erfordert. Er " - "trainiert wie eine SVM, dient aber zur Vorschau des Inline-" - "Download-Steuerelements bei der Auswahl einer verschachtelten " - "Komponente." - ), - zh=( - "仅用于测试的分类器,需要下载。它像 SVM 一样训练," - "用于在选择嵌套组件时预览内联下载控件。" - ), - ) - - def __init__(self, **kwargs): - """Store the nested classifier and forward the rest to ``SVC``. - - Parameters - ---------- - **kwargs : dict - May include ``nested_classifier`` (an instantiated tabular - classifier), which is kept as an attribute and not passed to the - underlying sklearn estimator. - """ - self.nested_classifier = kwargs.pop("nested_classifier", None) - super().__init__(**kwargs) - - @classmethod - def is_downloaded(cls) -> bool: - """Return whether the marker file is present. - - Returns - ------- - bool - ``True`` when ``component_dir()`` exists and is non-empty. - """ - directory = cls.component_dir() - return directory.is_dir() and any(directory.iterdir()) - - @classmethod - def download(cls, report: Optional[ProgressReporter] = None) -> None: - """Write a marker file to simulate a download. - - A short delay is inserted so the downloading state is visible in the - UI. No real artifact is fetched. - - Parameters - ---------- - report : ProgressReporter, optional - Callback invoked with progress fractions and phase messages. - """ - directory = cls.component_dir() - directory.mkdir(parents=True, exist_ok=True) - steps = 4 - for step in range(steps): - if report is not None: - report(step / steps, "Downloading dummy weights") - time.sleep(1) - (directory / "weights.marker").write_text("dummy", encoding="utf-8") - if report is not None: - report(1.0, "Done") From e71d100295c5d65013279d4a0d25d5ae22ed78d0 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 3 Jul 2026 15:56:40 -0400 Subject: [PATCH 087/308] test: drop dummy model name from download icon test fixture --- .../components/models/model/ModelDownloadStatusIcon.test.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx index 05e7827e7..c80b99a26 100644 --- a/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx +++ b/DashAI/front/src/components/models/model/ModelDownloadStatusIcon.test.jsx @@ -19,7 +19,7 @@ import ModelDownloadStatusIcon from "./ModelDownloadStatusIcon"; import { deleteComponentDownload } from "../../../api/component"; const model = { - name: "DummyDownloadableClassifier", + name: "DownloadableTestModel", downloaded: false, metadata: { requires_download: true, download_size_bytes: 268435456 }, }; @@ -43,7 +43,7 @@ describe("ModelDownloadStatusIcon", () => { fireEvent.click(del); await waitFor(() => expect(deleteComponentDownload).toHaveBeenCalledWith( - "DummyDownloadableClassifier", + "DownloadableTestModel", ), ); }); From 1af0215ec18c07bef0660ff868107a4d294cbb2d Mon Sep 17 00:00:00 2001 From: CristobalSantana Date: Mon, 6 Jul 2026 21:21:21 -0400 Subject: [PATCH 088/308] feat: add BART (Bayesian Additive Regression Trees) regression model Add a Bayesian Additive Regression Trees regressor for the RegressionTask, backed by pymc-bart. A small scikit-learn-style adapter (PyMCBARTRegressor) samples the posterior over the tree ensemble with the PGBART sampler and keeps only the sampled trees, so the fitted model pickles cleanly through the existing joblib save/load path. Out-of-sample prediction evaluates those trees on new inputs and returns the posterior mean of the regression function. The model exposes the BART hyperparameters (number of trees, depth-prior alpha and beta, leaf response) and the MCMC controls (draws, tune, chains, seed) via a multilingual schema, is registered in initial_components, and adds pymc-bart to requirements. Covered by tests for the schema and for train/predict/save-load on synthetic data. --- DashAI/back/initial_components.py | 2 + DashAI/back/models/pymc/__init__.py | 1 + DashAI/back/models/pymc/bart_regression.py | 452 +++++++++++++++++++++ requirements.txt | 1 + tests/back/models/test_bart_regression.py | 98 +++++ 5 files changed, 554 insertions(+) create mode 100644 DashAI/back/models/pymc/__init__.py create mode 100644 DashAI/back/models/pymc/bart_regression.py create mode 100644 tests/back/models/test_bart_regression.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 49ea258f1..aaf907adf 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -209,6 +209,7 @@ from DashAI.back.models.hugging_face.xlnet_transformer import XlnetTransformer from DashAI.back.models.lenet5_image_classifier import LeNet5ImageClassifier from DashAI.back.models.mlp_image_classifier import MLPImageClassifier +from DashAI.back.models.pymc.bart_regression import BARTRegression from DashAI.back.models.resnet18_image_classifier import ResNet18ImageClassifier from DashAI.back.models.resnet50_image_classifier import ResNet50ImageClassifier from DashAI.back.models.scikit_learn.adaboost_classifier import AdaBoostClassifier @@ -325,6 +326,7 @@ def get_initial_components(): BertinTransformer, BetoTransformer, BayesianRidgeRegression, + BARTRegression, DebertaV3Transformer, DecisionTreeClassifier, DecisionTreeRegression, diff --git a/DashAI/back/models/pymc/__init__.py b/DashAI/back/models/pymc/__init__.py new file mode 100644 index 000000000..9c0fa90a1 --- /dev/null +++ b/DashAI/back/models/pymc/__init__.py @@ -0,0 +1 @@ +# flake8: noqa diff --git a/DashAI/back/models/pymc/bart_regression.py b/DashAI/back/models/pymc/bart_regression.py new file mode 100644 index 000000000..a9026be1a --- /dev/null +++ b/DashAI/back/models/pymc/bart_regression.py @@ -0,0 +1,452 @@ +"""DashAI Bayesian Additive Regression Trees (BART) regression model. + +This module wraps ``pymc-bart`` behind a small scikit-learn-style estimator +(:class:`PyMCBARTRegressor`) so it can be plugged into the same +``RegressionModel`` / ``SklearnLikeRegressor`` machinery used by the other +DashAI tabular regressors. +""" + +from typing import TYPE_CHECKING + +from sklearn.base import BaseEstimator, RegressorMixin + +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + float_field, + int_field, + none_type, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.models.regression_model import RegressionModel +from DashAI.back.models.scikit_learn.sklearn_like_regressor import SklearnLikeRegressor + +if TYPE_CHECKING: + import numpy as np + + +class PyMCBARTRegressor(BaseEstimator, RegressorMixin): + """A minimal scikit-learn-style wrapper around ``pymc_bart``. + + ``pymc-bart`` exposes BART as a PyMC distribution rather than as an + estimator with ``fit`` / ``predict`` methods, and its sampled trees are the + only object needed to predict on new data. This class hides that: ``fit`` + builds a PyMC model, samples the posterior with the PGBART sampler and keeps + the sampled tree ensembles; ``predict`` evaluates those trees on new inputs + and returns the posterior mean of the regression function. + + Only the sampled trees (plain Python/NumPy objects) are stored on the + fitted instance, so the estimator serialises cleanly with ``joblib`` -- the + heavy PyTensor graph is not retained. + + Parameters + ---------- + m : int + Number of trees in the ensemble. + alpha : float + Controls the prior probability over the depth of the trees. Must lie in + the open interval (0, 1). + beta : float + Controls the prior probability over the number of leaves. Must be + positive. + response : str + How leaf-node values are computed: ``constant`` (default), ``linear`` or + ``mix``. The last two are experimental in ``pymc-bart``. + draws : int + Number of posterior samples to draw per chain. + tune : int + Number of tuning (burn-in) iterations per chain, discarded afterwards. + chains : int + Number of independent MCMC chains to run. + random_seed : int, optional + Seed for the sampler and the prediction RNG, for reproducibility. + """ + + def __init__( + self, + m: int = 50, + alpha: float = 0.95, + beta: float = 2.0, + response: str = "constant", + draws: int = 200, + tune: int = 200, + chains: int = 1, + random_seed: int = 0, + ) -> None: + self.m = m + self.alpha = alpha + self.beta = beta + self.response = response + self.draws = draws + self.tune = tune + self.chains = chains + self.random_seed = random_seed + + def fit(self, x, y) -> "PyMCBARTRegressor": + """Sample the BART posterior for the regression of ``y`` on ``x``. + + Parameters + ---------- + x : array-like of shape (n_samples, n_features) + Training covariates. + y : array-like of shape (n_samples,) + Training targets. + + Returns + ------- + PyMCBARTRegressor + The fitted estimator. + """ + import numpy as np + import pymc as pm + import pymc_bart as pmb + + x = np.asarray(x, dtype="float64") + y = np.asarray(y, dtype="float64").ravel() + + sigma_prior = float(np.std(y)) + if not sigma_prior > 0: + sigma_prior = 1.0 + + with pm.Model(): + x_data = pm.Data("X", x) + mu = pmb.BART( + "mu", + x_data, + y, + m=self.m, + alpha=self.alpha, + beta=self.beta, + response=self.response, + ) + sigma = pm.HalfNormal("sigma", sigma_prior) + pm.Normal("y", mu=mu, sigma=sigma, observed=y, shape=mu.shape) + pm.sample( + draws=self.draws, + tune=self.tune, + chains=self.chains, + cores=1, + random_seed=self.random_seed, + progressbar=False, + compute_convergence_checks=False, + ) + + # Keep only the sampled tree ensembles: they are all that is needed to + # predict, and they pickle cleanly (unlike the PyTensor graph). + self.all_trees_ = list(mu.owner.op.all_trees) + self.n_features_in_ = x.shape[1] + self.y_mean_ = float(y.mean()) + return self + + def predict(self, x) -> "np.ndarray": + """Predict the posterior mean of the BART function at ``x``. + + Parameters + ---------- + x : array-like of shape (n_samples, n_features) + Covariates to predict on. + + Returns + ------- + np.ndarray of shape (n_samples,) + Posterior mean regression estimates. + """ + import numpy as np + from pymc_bart.utils import _sample_posterior + + if not hasattr(self, "all_trees_"): + raise RuntimeError( + "This PyMCBARTRegressor instance is not fitted yet. " + "Call 'fit' before using 'predict'." + ) + + x = np.asarray(x, dtype="float64") + rng = np.random.default_rng(self.random_seed) + # _sample_posterior draws `size` tree ensembles (with replacement) from + # the posterior and evaluates them on x; averaging over them yields the + # posterior mean of the regression function. + posterior = _sample_posterior( + self.all_trees_, + X=x, + rng=rng, + size=len(self.all_trees_), + shape=1, + ) + return np.asarray(posterior).mean(axis=0).squeeze(-1) + + +class BARTRegressionSchema(BaseSchema): + """Schema that configures the Bayesian Additive Regression Trees model. + + BART models the regression function as a sum of regression trees, each + constrained by a regularising prior so that individual trees are weak + learners. The posterior over the tree ensemble is sampled with MCMC, + yielding both point predictions and a full predictive distribution. The + underlying implementation is ``pymc-bart``. + """ + + m: schema_field( + int_field(ge=1), + placeholder=50, + description=MultilingualString( + en="The number of trees in the sum-of-trees ensemble.", + es="El número de árboles en el ensamble de suma de árboles.", + pt="O número de árvores no conjunto de soma de árvores.", + de="Die Anzahl der Bäume im Summe-von-Bäumen-Ensemble.", + zh="树集成中树的数量。", + ), + alias=MultilingualString( + en="Number of trees", + es="Número de árboles", + pt="Número de árvores", + de="Anzahl der Bäume", + zh="树的数量", + ), + ) # type: ignore + + alpha: schema_field( + float_field(gt=0.0, lt=1.0), + placeholder=0.95, + description=MultilingualString( + en=( + "Base of the tree-depth prior; the probability that a node at " + "depth d is non-terminal is alpha * (1 + d) ** (-beta). Must be " + "in (0, 1)." + ), + es=( + "Base del prior de profundidad del árbol; la probabilidad de que " + "un nodo a profundidad d no sea terminal es alpha * (1 + d) ** " + "(-beta). Debe estar en (0, 1)." + ), + pt=( + "Base do prior de profundidade da árvore; a probabilidade de um " + "nó na profundidade d não ser terminal é alpha * (1 + d) ** " + "(-beta). Deve estar em (0, 1)." + ), + de=( + "Basis des Baumtiefen-Priors; die Wahrscheinlichkeit, dass ein " + "Knoten in Tiefe d kein Endknoten ist, beträgt alpha * (1 + d) " + "** (-beta). Muss in (0, 1) liegen." + ), + zh="树深度先验的基数;深度为 d 的节点为非终端节点的概率为 " + "alpha * (1 + d) ** (-beta)。必须在 (0, 1) 区间内。", + ), + alias=MultilingualString( + en="Alpha (depth prior)", + es="Alfa (prior de profundidad)", + pt="Alfa (prior de profundidade)", + de="Alpha (Tiefen-Prior)", + zh="Alpha(深度先验)", + ), + ) # type: ignore + + beta: schema_field( + float_field(gt=0.0), + placeholder=2.0, + description=MultilingualString( + en=( + "Exponent of the tree-depth prior; larger values penalise deep " + "trees more strongly. Must be positive." + ), + es=( + "Exponente del prior de profundidad del árbol; valores mayores " + "penalizan más los árboles profundos. Debe ser positivo." + ), + pt=( + "Expoente do prior de profundidade da árvore; valores maiores " + "penalizam mais as árvores profundas. Deve ser positivo." + ), + de=( + "Exponent des Baumtiefen-Priors; größere Werte bestrafen tiefe " + "Bäume stärker. Muss positiv sein." + ), + zh="树深度先验的指数;较大的值对深树的惩罚更强。必须为正。", + ), + alias=MultilingualString( + en="Beta (depth prior)", + es="Beta (prior de profundidad)", + pt="Beta (prior de profundidade)", + de="Beta (Tiefen-Prior)", + zh="Beta(深度先验)", + ), + ) # type: ignore + + response: schema_field( + enum_field(enum=["constant", "linear", "mix"]), + placeholder="constant", + description=MultilingualString( + en=( + "How leaf-node values are computed. 'constant' is recommended; " + "'linear' and 'mix' are experimental." + ), + es=( + "Cómo se calculan los valores de los nodos hoja. Se recomienda " + "'constant'; 'linear' y 'mix' son experimentales." + ), + pt=( + "Como os valores dos nós folha são calculados. 'constant' é " + "recomendado; 'linear' e 'mix' são experimentais." + ), + de=( + "Wie die Werte der Blattknoten berechnet werden. 'constant' wird " + "empfohlen; 'linear' und 'mix' sind experimentell." + ), + zh="叶节点值的计算方式。推荐 'constant';'linear' 和 'mix' 为实验性。", + ), + alias=MultilingualString( + en="Leaf response", + es="Respuesta de hoja", + pt="Resposta de folha", + de="Blatt-Antwort", + zh="叶响应", + ), + ) # type: ignore + + draws: schema_field( + int_field(ge=1), + placeholder=200, + description=MultilingualString( + en="Number of posterior samples drawn per chain.", + es="Número de muestras posteriores extraídas por cadena.", + pt="Número de amostras posteriores extraídas por cadeia.", + de="Anzahl der pro Kette gezogenen Posterior-Stichproben.", + zh="每条链抽取的后验样本数量。", + ), + alias=MultilingualString( + en="Posterior draws", + es="Muestras posteriores", + pt="Amostras posteriores", + de="Posterior-Ziehungen", + zh="后验抽样数", + ), + ) # type: ignore + + tune: schema_field( + int_field(ge=0), + placeholder=200, + description=MultilingualString( + en="Number of tuning (burn-in) iterations per chain, discarded.", + es=("Número de iteraciones de ajuste (burn-in) por cadena, descartadas."), + pt=("Número de iterações de ajuste (burn-in) por cadeia, descartadas."), + de="Anzahl der Tuning-(Burn-in-)Iterationen pro Kette, verworfen.", + zh="每条链的调优(预热)迭代次数,之后被丢弃。", + ), + alias=MultilingualString( + en="Tuning iterations", + es="Iteraciones de ajuste", + pt="Iterações de ajuste", + de="Tuning-Iterationen", + zh="调优迭代次数", + ), + ) # type: ignore + + chains: schema_field( + int_field(ge=1), + placeholder=1, + description=MultilingualString( + en="Number of independent MCMC chains to run.", + es="Número de cadenas MCMC independientes a ejecutar.", + pt="Número de cadeias MCMC independentes a executar.", + de="Anzahl der auszuführenden unabhängigen MCMC-Ketten.", + zh="要运行的独立 MCMC 链的数量。", + ), + alias=MultilingualString( + en="MCMC chains", + es="Cadenas MCMC", + pt="Cadeias MCMC", + de="MCMC-Ketten", + zh="MCMC 链数", + ), + ) # type: ignore + + random_seed: schema_field( + none_type(int_field(ge=0)), + placeholder=0, + description=MultilingualString( + en="Seed for the sampler and prediction RNG, for reproducibility.", + es=( + "Semilla para el muestreador y el RNG de predicción, para " + "reproducibilidad." + ), + pt=( + "Semente para o amostrador e o RNG de predição, para reprodutibilidade." + ), + de=( + "Startwert für den Sampler und den Vorhersage-RNG, zur " + "Reproduzierbarkeit." + ), + zh="采样器和预测随机数生成器的种子,用于可复现性。", + ), + alias=MultilingualString( + en="Random seed", + es="Semilla aleatoria", + pt="Semente aleatória", + de="Zufalls-Seed", + zh="随机种子", + ), + ) # type: ignore + + +class BARTRegression(RegressionModel, SklearnLikeRegressor, PyMCBARTRegressor): + """Bayesian Additive Regression Trees regressor. + + BART represents the regression function as a sum of ``m`` regression trees. + A regularising prior keeps each tree shallow so that it acts as a weak + learner, and the posterior distribution over the whole ensemble is explored + with an MCMC sampler (Particle Gibbs for the trees). Predictions are the + posterior mean of the sum-of-trees function, and the sampled posterior also + provides a natural quantification of predictive uncertainty. + + Key hyperparameters are the number of trees ``m`` and the tree-structure + prior parameters ``alpha`` and ``beta``, together with the MCMC controls + ``draws``, ``tune`` and ``chains``. The implementation wraps ``pymc-bart``. + + References + ---------- + - [1] Chipman, H.A., George, E.I. & McCulloch, R.E. (2010). "BART: Bayesian + Additive Regression Trees." The Annals of Applied Statistics, 4(1), + 266-298. https://doi.org/10.1214/09-AOAS285 + - [2] https://www.pymc.io/projects/bart/ + """ + + SCHEMA = BARTRegressionSchema + DISPLAY_NAME: str = MultilingualString( + en="BART Regression", + es="Regresión BART", + pt="Regressão BART", + de="BART-Regression", + zh="BART 回归", + ) + DESCRIPTION: str = MultilingualString( + en=( + "Bayesian sum-of-trees regression that samples the posterior over a " + "tree ensemble with MCMC." + ), + es=( + "Regresión bayesiana de suma de árboles que muestrea la posterior " + "sobre un ensamble de árboles con MCMC." + ), + pt=( + "Regressão bayesiana de soma de árvores que amostra a posterior " + "sobre um conjunto de árvores com MCMC." + ), + de=( + "Bayessche Summe-von-Bäumen-Regression, die die Posterior über ein " + "Baum-Ensemble mit MCMC abtastet." + ), + zh="贝叶斯树求和回归,使用 MCMC 对树集成的后验进行采样。", + ) + COLOR: str = "#26A69A" + ICON: str = "Park" + + def __init__(self, **kwargs) -> None: + """Initialise the model by forwarding all kwargs to the parent class. + + Parameters + ---------- + **kwargs : dict + Hyperparameter values forwarded to the parent wrapper. See the + associated schema class for available keys and their defaults. + """ + super().__init__(**kwargs) diff --git a/requirements.txt b/requirements.txt index 165d4afe6..de75273de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,6 +21,7 @@ Pillow beartype plotly shap +pymc-bart typer rich torch diff --git a/tests/back/models/test_bart_regression.py b/tests/back/models/test_bart_regression.py new file mode 100644 index 000000000..480f4914b --- /dev/null +++ b/tests/back/models/test_bart_regression.py @@ -0,0 +1,98 @@ +"""Tests for the pymc-bart backed BART regression model.""" + +import numpy as np +import pyarrow as pa +import pytest + +from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset +from DashAI.back.models.pymc.bart_regression import BARTRegression +from DashAI.back.types.value_types import Float + + +@pytest.fixture(scope="module", name="regression_dataset") +def fixture_regression_dataset(): + """A small synthetic regression dataset with a strong linear signal.""" + import pandas as pd + + rng = np.random.default_rng(0) + n = 120 + x0 = rng.uniform(-2, 2, n) + x1 = rng.uniform(-2, 2, n) + x2 = rng.uniform(-2, 2, n) + y = 3.0 * x0 - 2.0 * x1 + 0.5 * x2 + rng.normal(0, 0.2, n) + + feature_df = pd.DataFrame({"x0": x0, "x1": x1, "x2": x2}).astype("float64") + target_df = pd.DataFrame({"target": y}).astype("float64") + + feature_types = {c: Float(arrow_type=pa.float64()) for c in feature_df.columns} + target_types = {"target": Float(arrow_type=pa.float64())} + + x = to_dashai_dataset(feature_df, types=feature_types) + y_ds = to_dashai_dataset(target_df, types=target_types) + + split = 90 + x_train = to_dashai_dataset(feature_df.iloc[:split], types=feature_types) + x_test = to_dashai_dataset(feature_df.iloc[split:], types=feature_types) + y_train = to_dashai_dataset(target_df.iloc[:split], types=target_types) + + return { + "x": x, + "y": y_ds, + "x_train": x_train, + "x_test": x_test, + "y_train": y_train, + "y_true_test": y[split:], + } + + +@pytest.fixture(scope="module", name="bart_params") +def fixture_bart_params() -> dict: + # Deliberately small so the MCMC test stays fast. + return { + "m": 20, + "alpha": 0.95, + "beta": 2.0, + "response": "constant", + "draws": 100, + "tune": 50, + "chains": 1, + "random_seed": 0, + } + + +def test_bart_get_schema(): + schema = BARTRegression.get_schema() + assert isinstance(schema, dict) + assert schema["type"] == "object" + assert isinstance(schema["properties"], dict) + for key in ("m", "alpha", "beta", "response", "draws", "tune", "chains"): + assert key in schema["properties"], f"missing schema field {key}" + + +def test_bart_train_and_predict(regression_dataset, bart_params): + model = BARTRegression(**bart_params) + model.train(regression_dataset["x_train"], regression_dataset["y_train"]) + + y_pred = model.predict(regression_dataset["x_test"]) + + assert isinstance(y_pred, np.ndarray) + assert y_pred.shape == (regression_dataset["x_test"].num_rows,) + assert np.all(np.isfinite(y_pred)) + + # The signal is strong, so predictions must track the true targets. + corr = np.corrcoef(y_pred, regression_dataset["y_true_test"])[0, 1] + assert corr > 0.8, f"BART predictions poorly correlated with target (corr={corr})" + + +def test_bart_save_and_load(tmp_path, regression_dataset, bart_params): + model = BARTRegression(**bart_params) + model.train(regression_dataset["x_train"], regression_dataset["y_train"]) + y_pred = model.predict(regression_dataset["x_test"]) + + model_path = str(tmp_path / "bart_model.joblib") + model.save(model_path) + loaded = BARTRegression.load(model_path) + y_pred_loaded = loaded.predict(regression_dataset["x_test"]) + + assert isinstance(y_pred_loaded, np.ndarray) + np.testing.assert_allclose(y_pred_loaded, y_pred) From 725a566a5c23ab9327d961584c3120082c7a2adc Mon Sep 17 00:00:00 2001 From: CristobalSantana Date: Mon, 6 Jul 2026 20:56:54 -0400 Subject: [PATCH 089/308] feat: add Matthews correlation coefficient classification metric Add the Matthews Correlation Coefficient (MCC) as a new classification metric, wrapping sklearn.metrics.matthews_corrcoef. MCC returns a value in [-1, 1] that remains reliable under class imbalance, complementing the existing accuracy/F1/Cohen-Kappa metrics. Handles binary and multiclass natively. Registered in initial_components and covered by unit tests that check the range and compare against the sklearn reference. --- DashAI/back/initial_components.py | 2 + .../classification/matthews_corrcoef.py | 106 ++++++++++++++++++ .../metrics/test_classification_metrics.py | 35 ++++++ 3 files changed, 143 insertions(+) create mode 100644 DashAI/back/metrics/classification/matthews_corrcoef.py diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 49ea258f1..27365707b 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -119,6 +119,7 @@ from DashAI.back.metrics.classification.f1 import F1 from DashAI.back.metrics.classification.hamming_distance import HammingDistance from DashAI.back.metrics.classification.log_loss import LogLoss +from DashAI.back.metrics.classification.matthews_corrcoef import MatthewsCorrCoef from DashAI.back.metrics.classification.precision import Precision from DashAI.back.metrics.classification.recall import Recall from DashAI.back.metrics.classification.roc_auc import ROCAUC @@ -420,6 +421,7 @@ def get_initial_components(): LogLoss, HammingDistance, CohenKappa, + MatthewsCorrCoef, # Optimizers OptunaOptimizer, HyperOptOptimizer, diff --git a/DashAI/back/metrics/classification/matthews_corrcoef.py b/DashAI/back/metrics/classification/matthews_corrcoef.py new file mode 100644 index 000000000..279c4d41c --- /dev/null +++ b/DashAI/back/metrics/classification/matthews_corrcoef.py @@ -0,0 +1,106 @@ +"""DashAI Matthews Correlation Coefficient classification metric implementation.""" + +from typing import TYPE_CHECKING, Optional + +from DashAI.back.core.utils import MultilingualString +from DashAI.back.metrics.classification_metric import ( + ClassificationMetric, + prepare_to_metric, +) + +if TYPE_CHECKING: + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + + +class MatthewsCorrCoef(ClassificationMetric): + """Correlation between predicted and true labels, robust to class imbalance. + + The Matthews Correlation Coefficient (MCC) is a balanced measure of the + quality of a classification that can be used even when the classes are of + very different sizes. In essence it is the correlation coefficient between + the observed and predicted classifications, computed from the confusion + matrix. + + :: + + MCC = (TP·TN − FP·FN) / + sqrt((TP+FP)(TP+FN)(TN+FP)(TN+FN)) + + Range: [-1, 1]. Interpretation: +1 a perfect prediction; 0 no better than + random guessing; -1 total disagreement between prediction and observation. + Because a high score requires the classifier to do well on every class + (all four confusion-matrix quadrants), MCC is regarded as more informative + than accuracy or F1 on imbalanced problems. It generalises to the + multiclass setting via scikit-learn's implementation. + + References + ---------- + - [1] Matthews, B.W. (1975). "Comparison of the predicted and observed + secondary structure of T4 phage lysozyme." Biochimica et Biophysica + Acta (BBA) - Protein Structure, 405(2), 442-451. + - [2] Chicco, D. & Jurman, G. (2020). "The advantages of the Matthews + correlation coefficient (MCC) over F1 score and accuracy in binary + classification evaluation." BMC Genomics, 21(6). + - [3] https://scikit-learn.org/stable/modules/generated/ + sklearn.metrics.matthews_corrcoef.html + """ + + DESCRIPTION = MultilingualString( + en=( + "The Matthews Correlation Coefficient measures the correlation " + "between predicted and true labels, returning a value in [-1, 1] " + "that stays reliable even when the classes are imbalanced." + ), + es=( + "El Coeficiente de Correlación de Matthews mide la correlación " + "entre las etiquetas predichas y verdaderas, devolviendo un valor " + "en [-1, 1] que sigue siendo confiable incluso con clases " + "desbalanceadas." + ), + pt=( + "O Coeficiente de Correlação de Matthews mede a correlação entre " + "os rótulos previstos e verdadeiros, retornando um valor em " + "[-1, 1] que permanece confiável mesmo com classes desbalanceadas." + ), + de=( + "Der Matthews-Korrelationskoeffizient misst die Korrelation " + "zwischen vorhergesagten und wahren Labels und liefert einen Wert " + "in [-1, 1], der auch bei unausgewogenen Klassen zuverlässig bleibt." + ), + zh="马修斯相关系数衡量预测标签与真实标签之间的相关性,返回 [-1, 1] " + "范围内的值,即使在类别不平衡时也保持可靠。", + ) + + @staticmethod + def score( + true_labels: "DashAIDataset", + probs_pred_labels: "np.ndarray", + multiclass: Optional[bool] = None, + ) -> float: + """Calculate the Matthews Correlation Coefficient. + + Parameters + ---------- + true_labels : DashAIDataset + A DashAI dataset with labels. + probs_pred_labels : np.ndarray + A two-dimensional matrix in which each column represents a class + and the row values represent the probability that an example belongs + to the class associated with the column. + multiclass : bool, optional + Whether the task is a multiclass classification. If None, it will be + determined automatically from the number of unique labels. + + Returns + ------- + float + Matthews Correlation Coefficient between true labels and predicted + labels. + """ + from sklearn.metrics import matthews_corrcoef + + true_labels, pred_labels = prepare_to_metric(true_labels, probs_pred_labels) + + return matthews_corrcoef(true_labels, pred_labels) diff --git a/tests/back/metrics/test_classification_metrics.py b/tests/back/metrics/test_classification_metrics.py index 03f667812..eec97a1e8 100644 --- a/tests/back/metrics/test_classification_metrics.py +++ b/tests/back/metrics/test_classification_metrics.py @@ -6,6 +6,7 @@ from DashAI.back.metrics.classification.accuracy import Accuracy from DashAI.back.metrics.classification.f1 import F1 +from DashAI.back.metrics.classification.matthews_corrcoef import MatthewsCorrCoef from DashAI.back.metrics.classification.precision import Precision from DashAI.back.metrics.classification.recall import Recall @@ -51,6 +52,32 @@ def test_f1_score(metric_input: Dict[str, List[int]]): assert score <= 1.0 +def test_matthews_corrcoef(metric_input: Dict[str, List[int]]): + score = MatthewsCorrCoef.score( + metric_input["true_labels"], metric_input["pred_labels"] + ) + + assert isinstance(score, float) + assert score >= -1.0 + assert score <= 1.0 + + +def test_matthews_corrcoef_matches_sklearn_reference( + metric_input: Dict[str, List[int]], +): + from sklearn.metrics import matthews_corrcoef + + true = np.array(metric_input["true_labels"]["foo"]) + pred = np.argmax(metric_input["pred_labels"], axis=1) + expected = matthews_corrcoef(true, pred) + + score = MatthewsCorrCoef.score( + metric_input["true_labels"], metric_input["pred_labels"] + ) + + assert score == pytest.approx(expected) + + def test_metrics_different_input_sizes(metric_input: Dict[str, List[int]]): error_pattern = ( r"The length of the true labels and the predicted labels must be equal, " @@ -80,3 +107,11 @@ def test_metrics_different_input_sizes(metric_input: Dict[str, List[int]]): match=error_pattern, ): F1.score(metric_input["true_labels"], metric_input["wrong_size_labels"]) + + with pytest.raises( + ValueError, + match=error_pattern, + ): + MatthewsCorrCoef.score( + metric_input["true_labels"], metric_input["wrong_size_labels"] + ) From 6fe815c071b48dca013c071899c5f535ffcf5841 Mon Sep 17 00:00:00 2001 From: Cristian Tamblay Date: Wed, 8 Jul 2026 14:49:13 -0400 Subject: [PATCH 090/308] Fixes to em dashes and capitalization. Added more info to readme for llama-cpp compilation and installation --- CLAUDE.md | 3 ++- README.rst | 25 ++++++++++++++++++- docs/docs/build/dev-setup.md | 25 ++++++++++++++++++- .../current/build/dev-setup.md | 25 ++++++++++++++++++- pyproject.toml | 2 +- 5 files changed, 75 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index dd582196b..ef035fe8e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,8 @@ DashAI is a desktop/web graphical toolbox for training, evaluating, and deployin uv sync # add --extra cpu on machines without NVIDIA GPU uv run pre-commit install -# Run dev server +# Run dev server (if you synced with --extra cpu/cuda, pass the same +# --extra to uv run; a plain uv run re-syncs to the default torch build) uv run python -m DashAI --no-browser --logging-level DEBUG # Lint / format diff --git a/README.rst b/README.rst index b12b84577..9c7d368d0 100644 --- a/README.rst +++ b/README.rst @@ -137,7 +137,7 @@ This step is optional on CPU (step 2 already installed a working PyTorch). Run the section below that matches your hardware to pick a specific build. Every ``pip install`` command below can also be run as ``uv pip install`` with -the same flags — same result, just faster. +the same flags: same result, just faster. CPU only (Linux / macOS / Windows) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -354,6 +354,24 @@ PyTorch wheels instead: $ uv sync --extra cpu +On NVIDIA machines, the ``cuda`` extra pins the CUDA 12.8 PyTorch wheels. To +also get LLM (GGUF) support with CUDA offload, set ``CMAKE_ARGS`` so that +``llama-cpp-python`` compiles against CUDA (this requires CMake, a C compiler +and the CUDA toolkit; see "Build tools for GPU llama-cpp" above): + +.. code:: bash + + $ uv cache clean llama-cpp-python + $ CMAKE_ARGS="-DGGML_CUDA=on" uv sync --extra cuda --reinstall-package llama-cpp-python + +The first command and the ``--reinstall-package`` flag matter: uv skips +packages that are already installed and caches built wheels, and neither +check looks at ``CMAKE_ARGS``, so without them a previous CPU build gets +silently reused. Without ``CMAKE_ARGS``, ``llama-cpp-python`` still installs +but runs on CPU (there is no prebuilt CUDA wheel on PyPI). If nvcc rejects +your default gcc as too new, point it at an older one you have installed, +for example ``CMAKE_ARGS="-DGGML_CUDA=on -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-13"``. + If you prefer plain ``pip``, the same setup works inside any environment (``venv`` or ``conda``) since all metadata lives in ``pyproject.toml``. Note that this skips the lockfile, so versions may differ slightly from the ones @@ -379,6 +397,11 @@ Or, through the installed entry point: $ uv run dashai +**Important:** if you synced with an extra, pass the same extra to ``uv run`` +(for example ``uv run --extra cpu python -m DashAI``). A plain ``uv run`` +re-syncs the environment to the default set and swaps your PyTorch build back +to the PyPI one. + (If you installed with pip inside your own environment, drop the ``uv run`` prefix: ``python -m DashAI`` or ``dashai``.) diff --git a/docs/docs/build/dev-setup.md b/docs/docs/build/dev-setup.md index 7ef011a5a..71036abe0 100644 --- a/docs/docs/build/dev-setup.md +++ b/docs/docs/build/dev-setup.md @@ -37,8 +37,26 @@ which are much lighter: uv sync --extra cpu ``` +On NVIDIA machines, the `cuda` extra pins the CUDA 12.8 PyTorch wheels. To +also get LLM (GGUF) support with CUDA offload, set `CMAKE_ARGS` so that +`llama-cpp-python` compiles against CUDA (requires CMake, a C compiler and +the CUDA toolkit): + +```bash +uv cache clean llama-cpp-python +CMAKE_ARGS="-DGGML_CUDA=on" uv sync --extra cuda --reinstall-package llama-cpp-python +``` + +The first command and the `--reinstall-package` flag matter: uv skips +packages that are already installed and caches built wheels, and neither +check looks at `CMAKE_ARGS`, so without them a previous CPU build gets +silently reused. Without `CMAKE_ARGS`, `llama-cpp-python` still installs but +runs on CPU (there is no prebuilt CUDA wheel on PyPI). If nvcc rejects your +default gcc as too new, point it at an older one you have installed, for +example `CMAKE_ARGS="-DGGML_CUDA=on -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-13"`. + Alternatively, plain `pip` works inside any environment (venv or conda), -since all metadata lives in `pyproject.toml` — note this skips the lockfile, +since all metadata lives in `pyproject.toml`. Note this skips the lockfile, so versions may differ slightly from the ones the team and CI use: ```bash @@ -65,6 +83,11 @@ uv run python -m DashAI uv run dashai --no-browser --logging-level INFO ``` +**Important:** if you synced with an extra, pass the same extra to `uv run` +(for example `uv run --extra cpu python -m DashAI`). A plain `uv run` re-syncs +the environment to the default set and swaps your PyTorch build back to the +PyPI one. + **Frontend** (development server with hot reload): ```bash diff --git a/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md b/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md index 2284c56b2..d74b748d3 100644 --- a/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md +++ b/docs/i18n/es/docusaurus-plugin-content-docs/current/build/dev-setup.md @@ -37,8 +37,26 @@ que son mucho más livianos: uv sync --extra cpu ``` +En máquinas con NVIDIA, el extra `cuda` fija los wheels de PyTorch CUDA 12.8. +Para tener además soporte LLM (GGUF) con offload a CUDA, define `CMAKE_ARGS` +para que `llama-cpp-python` compile contra CUDA (requiere CMake, un compilador +C y el toolkit de CUDA): + +```bash +uv cache clean llama-cpp-python +CMAKE_ARGS="-DGGML_CUDA=on" uv sync --extra cuda --reinstall-package llama-cpp-python +``` + +El primer comando y el flag `--reinstall-package` importan: uv omite paquetes +que ya están instalados y cachea los wheels compilados, y ninguna de esas dos +verificaciones mira `CMAKE_ARGS`, así que sin ellos se reutiliza en silencio +un build CPU anterior. Sin `CMAKE_ARGS`, `llama-cpp-python` se instala igual +pero corre en CPU (no existe wheel CUDA precompilado en PyPI). Si nvcc rechaza +tu gcc por ser muy nuevo, apúntalo a uno más antiguo que tengas instalado, por +ejemplo `CMAKE_ARGS="-DGGML_CUDA=on -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-13"`. + Como alternativa, `pip` a secas funciona dentro de cualquier entorno (venv o -conda), ya que toda la metadata vive en `pyproject.toml` — ojo que esto no usa +conda), ya que toda la metadata vive en `pyproject.toml`. Ojo que esto no usa el lockfile, así que las versiones pueden diferir levemente de las que usan el equipo y el CI: @@ -66,6 +84,11 @@ uv run python -m DashAI uv run dashai --no-browser --logging-level INFO ``` +**Importante:** si sincronizaste con un extra, pásale el mismo extra a +`uv run` (por ejemplo `uv run --extra cpu python -m DashAI`). Un `uv run` sin +flags re-sincroniza el entorno al set por defecto y revierte tu build de +PyTorch al de PyPI. + **Frontend** (servidor de desarrollo con recarga en caliente): ```bash diff --git a/pyproject.toml b/pyproject.toml index 27336ec83..4fbdf5211 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "DashAI" version = "0.9.6" -description = "DashAI: a graphical toolbox for training, evaluating and deploying state-of-the-art AI models." +description = "dashAI: a graphical toolbox for training, evaluating and deploying state-of-the-art AI models." readme = "README.rst" license = "MIT" authors = [{ name = "DashAI Team", email = "fbravo@dcc.uchile.cl" }] From 1ed2f62b16976ad6b41205a577ef0aa1c680c5dd Mon Sep 17 00:00:00 2001 From: Cristian Tamblay Date: Wed, 8 Jul 2026 15:33:26 -0400 Subject: [PATCH 091/308] Some missing DashAI capitalization, and moved the email to @dash-ai.com --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 21e2f59f0..b7b221db6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,12 +3,12 @@ requires = ["setuptools>=77"] build-backend = "setuptools.build_meta" [project] -name = "DashAI" +name = "dashAI" version = "0.9.6" description = "dashAI: a graphical toolbox for training, evaluating and deploying state-of-the-art AI models." readme = "README.rst" license = "MIT" -authors = [{ name = "DashAI Team", email = "fbravo@dcc.uchile.cl" }] +authors = [{ name = "dashAI Team", email = "contacto@dash-ai.com" }] requires-python = ">=3.10" classifiers = [ "Programming Language :: Python :: 3.10", From 962ef3324fe1bdb874cebda4307d7f4c3db46ca6 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 9 Jul 2026 09:38:07 -0400 Subject: [PATCH 092/308] fix: set accurate model download sizes to match the actual download DOWNLOAD_SIZE_BYTES were rough estimates far below what is actually fetched: snapshot_download pulls the whole HF repo (every weight format and, for diffusion, all fp16/fp32 variants). Update each model's declared size to the exact repo byte total, verified against an on-disk download. --- DashAI/back/models/hugging_face/albert_transformer.py | 2 +- DashAI/back/models/hugging_face/bert_transformer.py | 2 +- DashAI/back/models/hugging_face/bertin_transformer.py | 2 +- DashAI/back/models/hugging_face/beto_transformer.py | 2 +- DashAI/back/models/hugging_face/deberta_v3_transformer.py | 2 +- DashAI/back/models/hugging_face/distilbert_transformer.py | 2 +- DashAI/back/models/hugging_face/electra_transformer.py | 2 +- DashAI/back/models/hugging_face/llama_model.py | 6 +++--- DashAI/back/models/hugging_face/m2m100_transformer.py | 2 +- DashAI/back/models/hugging_face/minilm_transformer.py | 2 +- DashAI/back/models/hugging_face/mistral_model.py | 4 ++-- DashAI/back/models/hugging_face/mixtral_model.py | 4 ++-- DashAI/back/models/hugging_face/modernbert_transformer.py | 2 +- .../models/hugging_face/multilingual_bert_transformer.py | 2 +- DashAI/back/models/hugging_face/nllb_transformer.py | 2 +- .../back/models/hugging_face/opus_mt_en_de_transformer.py | 1 + .../back/models/hugging_face/opus_mt_en_es_transformer.py | 1 + .../back/models/hugging_face/opus_mt_en_fr_transformer.py | 1 + .../back/models/hugging_face/opus_mt_es_en_transformer.py | 1 + .../back/models/hugging_face/opus_mt_fr_en_transformer.py | 1 + DashAI/back/models/hugging_face/pixart_sigma_model.py | 4 ++-- DashAI/back/models/hugging_face/qwen_model.py | 4 ++-- DashAI/back/models/hugging_face/roberta_transformer.py | 2 +- .../models/hugging_face/sd15_depth_controlnet_model.py | 2 +- .../back/models/hugging_face/sd15_hed_controlnet_model.py | 2 +- .../models/hugging_face/sd15_openpose_controlnet_model.py | 2 +- .../models/hugging_face/sdxl_canny_controlnet_model.py | 2 +- DashAI/back/models/hugging_face/sdxl_turbo_model.py | 2 +- DashAI/back/models/hugging_face/smol_lm_model.py | 4 ++-- .../hugging_face/stable_diffusion_v1_depth_controlnet.py | 2 +- .../back/models/hugging_face/stable_diffusion_v2_model.py | 8 ++++---- .../back/models/hugging_face/stable_diffusion_v3_model.py | 8 ++++---- .../back/models/hugging_face/stable_diffusion_xl_model.py | 4 ++-- DashAI/back/models/hugging_face/t5_small_transformer.py | 2 +- DashAI/back/models/hugging_face/tongyi_z_image_model.py | 4 ++-- .../back/models/hugging_face/xlm_roberta_transformer.py | 2 +- DashAI/back/models/hugging_face/xlnet_transformer.py | 2 +- 37 files changed, 52 insertions(+), 47 deletions(-) diff --git a/DashAI/back/models/hugging_face/albert_transformer.py b/DashAI/back/models/hugging_face/albert_transformer.py index 8fd152db0..1ace38ad1 100644 --- a/DashAI/back/models/hugging_face/albert_transformer.py +++ b/DashAI/back/models/hugging_face/albert_transformer.py @@ -59,5 +59,5 @@ class AlbertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Speed" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "albert-base-v2" - DOWNLOAD_SIZE_BYTES: int = 47_000_000 + DOWNLOAD_SIZE_BYTES: int = 330556087 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_albert" diff --git a/DashAI/back/models/hugging_face/bert_transformer.py b/DashAI/back/models/hugging_face/bert_transformer.py index 8b2121fea..594fbc0e3 100644 --- a/DashAI/back/models/hugging_face/bert_transformer.py +++ b/DashAI/back/models/hugging_face/bert_transformer.py @@ -58,5 +58,5 @@ class BertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bert-base-uncased" - DOWNLOAD_SIZE_BYTES: int = 440_000_000 + DOWNLOAD_SIZE_BYTES: int = 3454102158 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_bert" diff --git a/DashAI/back/models/hugging_face/bertin_transformer.py b/DashAI/back/models/hugging_face/bertin_transformer.py index 2a4cfb608..984b1e5e2 100644 --- a/DashAI/back/models/hugging_face/bertin_transformer.py +++ b/DashAI/back/models/hugging_face/bertin_transformer.py @@ -58,5 +58,5 @@ class BertinTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "RecordVoiceOver" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bertin-project/bertin-roberta-base-spanish" - DOWNLOAD_SIZE_BYTES: int = 500_000_000 + DOWNLOAD_SIZE_BYTES: int = 1510386247 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_bertin" diff --git a/DashAI/back/models/hugging_face/beto_transformer.py b/DashAI/back/models/hugging_face/beto_transformer.py index 4c6212137..78a549c05 100644 --- a/DashAI/back/models/hugging_face/beto_transformer.py +++ b/DashAI/back/models/hugging_face/beto_transformer.py @@ -58,5 +58,5 @@ class BetoTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "RecordVoiceOver" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "dccuchile/bert-base-spanish-wwm-cased" - DOWNLOAD_SIZE_BYTES: int = 440_000_000 + DOWNLOAD_SIZE_BYTES: int = 1416527075 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_beto" diff --git a/DashAI/back/models/hugging_face/deberta_v3_transformer.py b/DashAI/back/models/hugging_face/deberta_v3_transformer.py index 271fa0f76..71684667d 100644 --- a/DashAI/back/models/hugging_face/deberta_v3_transformer.py +++ b/DashAI/back/models/hugging_face/deberta_v3_transformer.py @@ -61,5 +61,5 @@ class DebertaV3Transformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DebertaV3TransformerSchema MODEL_NAME: str = "microsoft/deberta-v3-base" - DOWNLOAD_SIZE_BYTES: int = 440_000_000 + DOWNLOAD_SIZE_BYTES: int = 1851424112 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_deberta_v3" diff --git a/DashAI/back/models/hugging_face/distilbert_transformer.py b/DashAI/back/models/hugging_face/distilbert_transformer.py index 334ce67ec..84695f532 100644 --- a/DashAI/back/models/hugging_face/distilbert_transformer.py +++ b/DashAI/back/models/hugging_face/distilbert_transformer.py @@ -305,5 +305,5 @@ class DistilBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "distilbert-base-uncased" - DOWNLOAD_SIZE_BYTES: int = 270_000_000 + DOWNLOAD_SIZE_BYTES: int = 1529742866 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_distilbert" diff --git a/DashAI/back/models/hugging_face/electra_transformer.py b/DashAI/back/models/hugging_face/electra_transformer.py index 83867a4d7..088bd2aa7 100644 --- a/DashAI/back/models/hugging_face/electra_transformer.py +++ b/DashAI/back/models/hugging_face/electra_transformer.py @@ -58,5 +58,5 @@ class ElectraTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "ElectricBolt" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "google/electra-small-discriminator" - DOWNLOAD_SIZE_BYTES: int = 54_000_000 + DOWNLOAD_SIZE_BYTES: int = 163615837 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_electra" diff --git a/DashAI/back/models/hugging_face/llama_model.py b/DashAI/back/models/hugging_face/llama_model.py index 170817f39..fd59f4c7c 100644 --- a/DashAI/back/models/hugging_face/llama_model.py +++ b/DashAI/back/models/hugging_face/llama_model.py @@ -21,7 +21,7 @@ class Llama31_8BInstruct(GGUFTextGenerationModel): # noqa: N801 REPO_ID = "bartowski/Meta-Llama-3.1-8B-Instruct-GGUF" GGUF_PATTERN = "*Q4_K_M.gguf" - DOWNLOAD_SIZE_BYTES = 4_900_000_000 + DOWNLOAD_SIZE_BYTES = 4920739232 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#1a237e" DISPLAY_NAME = MultilingualString( @@ -84,7 +84,7 @@ class Llama32_1BInstruct(GGUFTextGenerationModel): # noqa: N801 REPO_ID = "bartowski/Llama-3.2-1B-Instruct-GGUF" GGUF_PATTERN = "*Q4_K_M.gguf" - DOWNLOAD_SIZE_BYTES = 800_000_000 + DOWNLOAD_SIZE_BYTES = 807694464 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#1a237e" DISPLAY_NAME = MultilingualString( @@ -142,7 +142,7 @@ class Llama32_3BInstruct(GGUFTextGenerationModel): # noqa: N801 REPO_ID = "bartowski/Llama-3.2-3B-Instruct-GGUF" GGUF_PATTERN = "*Q4_K_M.gguf" - DOWNLOAD_SIZE_BYTES = 2_000_000_000 + DOWNLOAD_SIZE_BYTES = 2019377696 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#1a237e" DISPLAY_NAME = MultilingualString( diff --git a/DashAI/back/models/hugging_face/m2m100_transformer.py b/DashAI/back/models/hugging_face/m2m100_transformer.py index d5eedddd5..601e5d93b 100644 --- a/DashAI/back/models/hugging_face/m2m100_transformer.py +++ b/DashAI/back/models/hugging_face/m2m100_transformer.py @@ -158,7 +158,7 @@ class M2M100Transformer(HFPretrainedDownloadMixin, TranslationModel): COLOR: str = "#6A1B9A" ICON: str = "Language" MODEL_NAME: str = "facebook/m2m100_418M" - DOWNLOAD_SIZE_BYTES: int = 1_900_000_000 + DOWNLOAD_SIZE_BYTES: int = 3877717593 def __init__(self, model=None, pretrained_dir=None, **kwargs): kwargs = self.validate_and_transform(kwargs) diff --git a/DashAI/back/models/hugging_face/minilm_transformer.py b/DashAI/back/models/hugging_face/minilm_transformer.py index 818e1a6a0..8888c8bc9 100644 --- a/DashAI/back/models/hugging_face/minilm_transformer.py +++ b/DashAI/back/models/hugging_face/minilm_transformer.py @@ -58,5 +58,5 @@ class MiniLMTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Speed" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "microsoft/MiniLM-L12-H384-uncased" - DOWNLOAD_SIZE_BYTES: int = 130_000_000 + DOWNLOAD_SIZE_BYTES: int = 400889386 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_minilm" diff --git a/DashAI/back/models/hugging_face/mistral_model.py b/DashAI/back/models/hugging_face/mistral_model.py index 5b07d1996..52c38adea 100644 --- a/DashAI/back/models/hugging_face/mistral_model.py +++ b/DashAI/back/models/hugging_face/mistral_model.py @@ -21,7 +21,7 @@ class Mistral7BInstructV03(GGUFTextGenerationModel): REPO_ID = "bartowski/Mistral-7B-Instruct-v0.3-GGUF" GGUF_PATTERN = "*Q4_K_M.gguf" - DOWNLOAD_SIZE_BYTES = 4_400_000_000 + DOWNLOAD_SIZE_BYTES = 4372812000 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#ff6f00" DISPLAY_NAME = MultilingualString( @@ -79,7 +79,7 @@ class MistralNemoInstruct2407(GGUFTextGenerationModel): REPO_ID = "bartowski/Mistral-Nemo-Instruct-2407-GGUF" GGUF_PATTERN = "*Q4_K_M.gguf" - DOWNLOAD_SIZE_BYTES = 7_100_000_000 + DOWNLOAD_SIZE_BYTES = 7477208192 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#ff6f00" DISPLAY_NAME = MultilingualString( diff --git a/DashAI/back/models/hugging_face/mixtral_model.py b/DashAI/back/models/hugging_face/mixtral_model.py index 978818d87..78f70b4bc 100644 --- a/DashAI/back/models/hugging_face/mixtral_model.py +++ b/DashAI/back/models/hugging_face/mixtral_model.py @@ -30,7 +30,7 @@ class Mixtral8x7BInstructQ4KM(GGUFTextGenerationModel): REPO_ID = _MIXTRAL_REPO GGUF_PATTERN = "*Q4_K_M.gguf" - DOWNLOAD_SIZE_BYTES = 26_000_000_000 + DOWNLOAD_SIZE_BYTES = 28448468384 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#4a148c" DISPLAY_NAME = MultilingualString( @@ -91,7 +91,7 @@ class Mixtral8x7BInstructQ2K(GGUFTextGenerationModel): REPO_ID = _MIXTRAL_REPO GGUF_PATTERN = "*Q2_K.gguf" - DOWNLOAD_SIZE_BYTES = 16_000_000_000 + DOWNLOAD_SIZE_BYTES = 17311231392 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#4a148c" DISPLAY_NAME = MultilingualString( diff --git a/DashAI/back/models/hugging_face/modernbert_transformer.py b/DashAI/back/models/hugging_face/modernbert_transformer.py index ad7e26033..a087fc310 100644 --- a/DashAI/back/models/hugging_face/modernbert_transformer.py +++ b/DashAI/back/models/hugging_face/modernbert_transformer.py @@ -55,6 +55,6 @@ class ModernBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = ModernBertTransformerSchema MODEL_NAME: str = "answerdotai/ModernBERT-base" - DOWNLOAD_SIZE_BYTES: int = 600_000_000 + DOWNLOAD_SIZE_BYTES: int = 3134313772 MAX_TOKEN_LENGTH: int = 8192 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_modernbert" diff --git a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py index 916f6a08f..5e3574f8f 100644 --- a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py +++ b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py @@ -59,7 +59,7 @@ class MultilingualBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Translate" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bert-base-multilingual-cased" - DOWNLOAD_SIZE_BYTES: int = 680_000_000 + DOWNLOAD_SIZE_BYTES: int = 3226865011 TEMP_CHECKPOINT_DIR: str = ( "DashAI/back/user_models/temp_checkpoints_multilingual_bert" ) diff --git a/DashAI/back/models/hugging_face/nllb_transformer.py b/DashAI/back/models/hugging_face/nllb_transformer.py index 6628ba9d6..9fbe23e81 100644 --- a/DashAI/back/models/hugging_face/nllb_transformer.py +++ b/DashAI/back/models/hugging_face/nllb_transformer.py @@ -205,7 +205,7 @@ def _resolve_language_token_id(self, language_code: str, field_name: str) -> int raise ValueError(f"Unsupported {field_name} '{language_code}'.") MODEL_NAME: str = "facebook/nllb-200-distilled-600M" - DOWNLOAD_SIZE_BYTES: int = 2_400_000_000 + DOWNLOAD_SIZE_BYTES: int = 2482655255 def __init__(self, model=None, pretrained_dir=None, **kwargs): """Initialize the NLLB tokenizer and model. diff --git a/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py index 5950dc5ed..0c5854f6b 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py @@ -29,6 +29,7 @@ class OpusMtEnDeTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-de" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-de" SCHEMA = OpusMtEnDeTransformerSchema + DOWNLOAD_SIZE_BYTES = 1430871510 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-De Transformer", es="Transformer Opus MT En-De", diff --git a/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py index 203c11fbb..2c9cf281a 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py @@ -270,6 +270,7 @@ class OpusMtEnESTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-es" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-es" SCHEMA = OpusMtEnESTransformerSchema + DOWNLOAD_SIZE_BYTES = 937836389 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-Es Transformer", es="Transformer Opus MT En-Es", diff --git a/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py index ad026f97f..9c409d3ad 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py @@ -29,6 +29,7 @@ class OpusMtEnFrTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-fr" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-fr" SCHEMA = OpusMtEnFrTransformerSchema + DOWNLOAD_SIZE_BYTES = 903735972 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-Fr Transformer", es="Transformer Opus MT En-Fr", diff --git a/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py index 4fadc8e1e..96b71168c 100644 --- a/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py @@ -32,6 +32,7 @@ class OpusMtEsENTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-es-en" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-es-en" SCHEMA = OpusMtEsENTransformerSchema + DOWNLOAD_SIZE_BYTES = 627891360 DISPLAY_NAME: str = MultilingualString( en="Opus MT Es-En Transformer", es="Transformer Opus MT Es-En", diff --git a/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py index ec793edbb..fcb7e397c 100644 --- a/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py @@ -29,6 +29,7 @@ class OpusMtFrEnTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-fr-en" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-fr-en" SCHEMA = OpusMtFrEnTransformerSchema + DOWNLOAD_SIZE_BYTES = 1204539675 DISPLAY_NAME: str = MultilingualString( en="Opus MT Fr-En Transformer", es="Transformer Opus MT Fr-En", diff --git a/DashAI/back/models/hugging_face/pixart_sigma_model.py b/DashAI/back/models/hugging_face/pixart_sigma_model.py index 484228ea8..7d4596380 100644 --- a/DashAI/back/models/hugging_face/pixart_sigma_model.py +++ b/DashAI/back/models/hugging_face/pixart_sigma_model.py @@ -512,7 +512,7 @@ class PixArtSigma1024(PixArtSigmaGenerationModel): """ MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - DOWNLOAD_SIZE_BYTES: int = 2500000000 + DOWNLOAD_SIZE_BYTES: int = 21832490389 DISPLAY_NAME = MultilingualString( en="PixArt-Sigma 1024", es="PixArt-Sigma 1024", @@ -570,7 +570,7 @@ class PixArtSigma512(PixArtSigmaGenerationModel): """ MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-512-MS" - DOWNLOAD_SIZE_BYTES: int = 2500000000 + DOWNLOAD_SIZE_BYTES: int = 2447758901 DISPLAY_NAME = MultilingualString( en="PixArt-Sigma 512", es="PixArt-Sigma 512", diff --git a/DashAI/back/models/hugging_face/qwen_model.py b/DashAI/back/models/hugging_face/qwen_model.py index 56afdcb6c..e37706a18 100644 --- a/DashAI/back/models/hugging_face/qwen_model.py +++ b/DashAI/back/models/hugging_face/qwen_model.py @@ -23,7 +23,7 @@ class Qwen25_05BInstruct(GGUFTextGenerationModel): # noqa: N801 REPO_ID = "Qwen/Qwen2.5-0.5B-Instruct-GGUF" GGUF_PATTERN = "*8_0.gguf" - DOWNLOAD_SIZE_BYTES = 700_000_000 + DOWNLOAD_SIZE_BYTES = 675710816 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#2e7d32" DISPLAY_NAME = MultilingualString( @@ -88,7 +88,7 @@ class Qwen25_15BInstruct(GGUFTextGenerationModel): # noqa: N801 REPO_ID = "Qwen/Qwen2.5-1.5B-Instruct-GGUF" GGUF_PATTERN = "*8_0.gguf" - DOWNLOAD_SIZE_BYTES = 1_900_000_000 + DOWNLOAD_SIZE_BYTES = 1894532128 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#2e7d32" DISPLAY_NAME = MultilingualString( diff --git a/DashAI/back/models/hugging_face/roberta_transformer.py b/DashAI/back/models/hugging_face/roberta_transformer.py index e3383a926..2a9414640 100644 --- a/DashAI/back/models/hugging_face/roberta_transformer.py +++ b/DashAI/back/models/hugging_face/roberta_transformer.py @@ -58,5 +58,5 @@ class RobertaTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "SmartToy" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "roberta-base" - DOWNLOAD_SIZE_BYTES: int = 500_000_000 + DOWNLOAD_SIZE_BYTES: int = 2815192007 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_roberta" diff --git a/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py index cead31cfb..206aa3d76 100644 --- a/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_depth_controlnet_model.py @@ -263,7 +263,7 @@ class SD15DepthControlNetModel(HFDownloadableMixin, BaseControlNetModel): ("lllyasviel/sd-controlnet-depth", "model"), ("Intel/dpt-hybrid-midas", "model"), ] - DOWNLOAD_SIZE_BYTES = 5900000000 + DOWNLOAD_SIZE_BYTES = 50640633273 COLOR: str = "#4e342e" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 Depth ControlNet", diff --git a/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py index 461a25a4d..c1647539f 100644 --- a/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_hed_controlnet_model.py @@ -196,7 +196,7 @@ class SD15HEDControlNetModel(HFDownloadableMixin, BaseControlNetModel): ("lllyasviel/sd-controlnet-hed", "model"), ("lllyasviel/Annotators", "model"), ] - DOWNLOAD_SIZE_BYTES = 7400000000 + DOWNLOAD_SIZE_BYTES = 60738022767 COLOR: str = "#006064" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 HED ControlNet", diff --git a/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py b/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py index 1036adf31..6a37aa83e 100644 --- a/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sd15_openpose_controlnet_model.py @@ -191,7 +191,7 @@ class SD15OpenPoseControlNetModel(HFDownloadableMixin, BaseControlNetModel): ("lllyasviel/sd-controlnet-openpose", "model"), ("lllyasviel/Annotators", "model"), ] - DOWNLOAD_SIZE_BYTES = 7400000000 + DOWNLOAD_SIZE_BYTES = 60737694276 COLOR: str = "#880e4f" DISPLAY_NAME: str = MultilingualString( en="SD 1.5 OpenPose ControlNet", diff --git a/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py b/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py index c7b603a84..8f5e7e0a1 100644 --- a/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py @@ -296,7 +296,7 @@ class SDXLCannyControlNetModel(HFDownloadableMixin, BaseControlNetModel): ("diffusers/controlnet-canny-sdxl-1.0", "model"), ("madebyollin/sdxl-vae-fp16-fix", "model"), ] - DOWNLOAD_SIZE_BYTES = 10000000000 + DOWNLOAD_SIZE_BYTES = 93308273070 COLOR: str = "#1a237e" DISPLAY_NAME: str = MultilingualString( en="SDXL Canny ControlNet", diff --git a/DashAI/back/models/hugging_face/sdxl_turbo_model.py b/DashAI/back/models/hugging_face/sdxl_turbo_model.py index 6d7a16f52..0e65a33ee 100644 --- a/DashAI/back/models/hugging_face/sdxl_turbo_model.py +++ b/DashAI/back/models/hugging_face/sdxl_turbo_model.py @@ -328,7 +328,7 @@ class SDXLTurboModel(HFPretrainedDownloadMixin, TextToImageGenerationTaskModel): SCHEMA = SDXLTurboSchema MODEL_NAME: str = "stabilityai/sdxl-turbo" # SDXL-Turbo diffusers pipeline is ~7 GB. - DOWNLOAD_SIZE_BYTES: int = 7_000_000_000 + DOWNLOAD_SIZE_BYTES: int = 55516176914 COLOR: str = "#b71c1c" DISPLAY_NAME: str = MultilingualString( en="SDXL Turbo", diff --git a/DashAI/back/models/hugging_face/smol_lm_model.py b/DashAI/back/models/hugging_face/smol_lm_model.py index b2999369d..e550a19b9 100644 --- a/DashAI/back/models/hugging_face/smol_lm_model.py +++ b/DashAI/back/models/hugging_face/smol_lm_model.py @@ -21,7 +21,7 @@ class SmolLM2_360MInstruct(GGUFTextGenerationModel): # noqa: N801 REPO_ID = "HuggingFaceTB/SmolLM2-360M-Instruct-GGUF" GGUF_PATTERN = "*q8_0.gguf" - DOWNLOAD_SIZE_BYTES = 400_000_000 + DOWNLOAD_SIZE_BYTES = 386404992 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#00695c" DISPLAY_NAME = MultilingualString( @@ -79,7 +79,7 @@ class SmolLM2_17BInstruct(GGUFTextGenerationModel): # noqa: N801 REPO_ID = "HuggingFaceTB/SmolLM2-1.7B-Instruct-GGUF" GGUF_PATTERN = "*q4_k_m.gguf" - DOWNLOAD_SIZE_BYTES = 1_100_000_000 + DOWNLOAD_SIZE_BYTES = 1055609536 SCHEMA = GGUFTextGenerationSchema COLOR: str = "#00695c" DISPLAY_NAME = MultilingualString( diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py index 5965f1c43..b21cfdfbd 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py @@ -217,7 +217,7 @@ class StableDiffusionXLV1ControlNet(HFDownloadableMixin, BaseControlNetModel): ("madebyollin/sdxl-vae-fp16-fix", "model"), ("Intel/dpt-hybrid-midas", "model"), ] - DOWNLOAD_SIZE_BYTES = 10500000000 + DOWNLOAD_SIZE_BYTES = 80696728644 COLOR: str = "#e65100" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion XL V1 ControlNet", diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py b/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py index 55ff085d9..3ed411604 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v2_model.py @@ -546,7 +546,7 @@ class StableDiffusion2(StableDiffusion2GenerationModel): MODEL_NAME: str = "sd2-community/stable-diffusion-2" # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. - DOWNLOAD_SIZE_BYTES: int = 5_200_000_000 + DOWNLOAD_SIZE_BYTES: int = 25911933905 DISPLAY_NAME = MultilingualString( en="Stable Diffusion 2", es="Stable Diffusion 2", @@ -606,7 +606,7 @@ class StableDiffusion2_512(StableDiffusion2GenerationModel): # noqa: N801 MODEL_NAME: str = "sd2-community/stable-diffusion-2-base" # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. - DOWNLOAD_SIZE_BYTES: int = 5_200_000_000 + DOWNLOAD_SIZE_BYTES: int = 25911843836 DISPLAY_NAME = MultilingualString( en="Stable Diffusion 2 (512px)", es="Stable Diffusion 2 (512px)", @@ -661,7 +661,7 @@ class StableDiffusion21(StableDiffusion2GenerationModel): MODEL_NAME: str = "sd2-community/stable-diffusion-2-1" # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. - DOWNLOAD_SIZE_BYTES: int = 5_200_000_000 + DOWNLOAD_SIZE_BYTES: int = 36341303572 DISPLAY_NAME = MultilingualString( en="Stable Diffusion 2.1", es="Stable Diffusion 2.1", @@ -717,7 +717,7 @@ class StableDiffusion21_512(StableDiffusion2GenerationModel): # noqa: N801 MODEL_NAME: str = "sd2-community/stable-diffusion-2-1-base" # Full fp32 diffusers pipeline (text encoder + U-Net + VAE) is ~5 GB. - DOWNLOAD_SIZE_BYTES: int = 5_200_000_000 + DOWNLOAD_SIZE_BYTES: int = 36341275775 DISPLAY_NAME = MultilingualString( en="Stable Diffusion 2.1 (512px)", es="Stable Diffusion 2.1 (512px)", diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py b/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py index 3913d6134..ca8daef20 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v3_model.py @@ -567,7 +567,7 @@ class StableDiffusion3Medium(StableDiffusion3GenerationModel): """ MODEL_NAME: str = "stabilityai/stable-diffusion-3-medium-diffusers" - DOWNLOAD_SIZE_BYTES: int = 5500000000 + DOWNLOAD_SIZE_BYTES: int = 31012147557 DISPLAY_NAME = MultilingualString( en="Stable Diffusion 3 Medium", es="Stable Diffusion 3 Medium", @@ -634,7 +634,7 @@ class StableDiffusion35Medium(StableDiffusion3GenerationModel): """ MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-medium" - DOWNLOAD_SIZE_BYTES: int = 10000000000 + DOWNLOAD_SIZE_BYTES: int = 48861581303 DISPLAY_NAME = MultilingualString( en="Stable Diffusion 3.5 Medium", es="Stable Diffusion 3.5 Medium", @@ -699,7 +699,7 @@ class StableDiffusion35Large(StableDiffusion3GenerationModel): """ MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-large" - DOWNLOAD_SIZE_BYTES: int = 16000000000 + DOWNLOAD_SIZE_BYTES: int = 71585723216 DISPLAY_NAME = MultilingualString( en="Stable Diffusion 3.5 Large", es="Stable Diffusion 3.5 Large", @@ -765,7 +765,7 @@ class StableDiffusion35LargeTurbo(StableDiffusion3GenerationModel): """ MODEL_NAME: str = "stabilityai/stable-diffusion-3.5-large-turbo" - DOWNLOAD_SIZE_BYTES: int = 16000000000 + DOWNLOAD_SIZE_BYTES: int = 71582971259 DISPLAY_NAME = MultilingualString( en="Stable Diffusion 3.5 Large Turbo", es="Stable Diffusion 3.5 Large Turbo", diff --git a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py index da0ec4463..5c690e363 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py @@ -494,7 +494,7 @@ class StableDiffusionXL(StableDiffusionXLGenerationModel): MODEL_NAME: str = "stabilityai/stable-diffusion-xl-base-1.0" # SDXL diffusers pipeline (base + refiner-less) is ~7 GB. - DOWNLOAD_SIZE_BYTES: int = 7_000_000_000 + DOWNLOAD_SIZE_BYTES: int = 76912765291 DISPLAY_NAME = MultilingualString( en="Stable Diffusion XL", es="Stable Diffusion XL", @@ -554,7 +554,7 @@ class RealVisXLV4(StableDiffusionXLGenerationModel): MODEL_NAME: str = "SG161222/RealVisXL_V4.0" # SDXL diffusers pipeline (base + refiner-less) is ~7 GB. - DOWNLOAD_SIZE_BYTES: int = 7_000_000_000 + DOWNLOAD_SIZE_BYTES: int = 27754923233 DISPLAY_NAME = MultilingualString( en="RealVisXL V4.0", es="RealVisXL V4.0", diff --git a/DashAI/back/models/hugging_face/t5_small_transformer.py b/DashAI/back/models/hugging_face/t5_small_transformer.py index 997199351..5a061c63b 100644 --- a/DashAI/back/models/hugging_face/t5_small_transformer.py +++ b/DashAI/back/models/hugging_face/t5_small_transformer.py @@ -131,7 +131,7 @@ class T5SmallTransformer(HFPretrainedDownloadMixin, TranslationModel): COLOR: str = "#00695C" ICON: str = "Language" MODEL_NAME: str = "t5-small" - DOWNLOAD_SIZE_BYTES: int = 240_000_000 + DOWNLOAD_SIZE_BYTES: int = 2246973515 def __init__(self, model=None, pretrained_dir=None, **kwargs): kwargs = self.validate_and_transform(kwargs) diff --git a/DashAI/back/models/hugging_face/tongyi_z_image_model.py b/DashAI/back/models/hugging_face/tongyi_z_image_model.py index 4f3cd9bfe..30ac8f3c8 100644 --- a/DashAI/back/models/hugging_face/tongyi_z_image_model.py +++ b/DashAI/back/models/hugging_face/tongyi_z_image_model.py @@ -445,7 +445,7 @@ class TongyiZImage(TongyiZImageGenerationModel): """ MODEL_NAME: str = "Tongyi-MAI/Z-Image" - DOWNLOAD_SIZE_BYTES: int = 8000000000 + DOWNLOAD_SIZE_BYTES: int = 20547479575 DISPLAY_NAME = MultilingualString( en="Tongyi Z-Image", es="Tongyi Z-Image", @@ -498,7 +498,7 @@ class TongyiZImageTurbo(TongyiZImageGenerationModel): """ MODEL_NAME: str = "Tongyi-MAI/Z-Image-Turbo" - DOWNLOAD_SIZE_BYTES: int = 8000000000 + DOWNLOAD_SIZE_BYTES: int = 32899667397 DISPLAY_NAME = MultilingualString( en="Tongyi Z-Image Turbo", es="Tongyi Z-Image Turbo", diff --git a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py index caf06a6a4..12d1941ab 100644 --- a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py +++ b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py @@ -62,5 +62,5 @@ class XlmRobertaTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Language" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "xlm-roberta-base" - DOWNLOAD_SIZE_BYTES: int = 1_100_000_000 + DOWNLOAD_SIZE_BYTES: int = 6352430498 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_xlm_roberta" diff --git a/DashAI/back/models/hugging_face/xlnet_transformer.py b/DashAI/back/models/hugging_face/xlnet_transformer.py index 9a7f59050..63a914b2b 100644 --- a/DashAI/back/models/hugging_face/xlnet_transformer.py +++ b/DashAI/back/models/hugging_face/xlnet_transformer.py @@ -58,5 +58,5 @@ class XlnetTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "AutoAwesome" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "xlnet-base-cased" - DOWNLOAD_SIZE_BYTES: int = 470_000_000 + DOWNLOAD_SIZE_BYTES: int = 1600067104 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_xlnet" From b50f17fba71534ed562e6cfb109075e5f1e8e560 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 9 Jul 2026 09:38:24 -0400 Subject: [PATCH 093/308] fix: adapt Opus En-Roa and Roa-En models to the download system These two Opus-MT models were merged in still describing weights as downloading on first use and inheriting the generic 300 MB size. Correct their descriptions to state weights must be downloaded before use, and set their real download sizes (1.17 GB / 1.21 GB). --- .../hugging_face/opus_mt_en_roa_transformer.py | 17 +++++++++-------- .../hugging_face/opus_mt_roa_en_transformer.py | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py index 3c68ff48b..9845bf231 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py @@ -81,6 +81,7 @@ class OpusMtEnRoaTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-roa" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-roa" SCHEMA = OpusMtEnRoaTransformerSchema + DOWNLOAD_SIZE_BYTES = 1171638932 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-Roa Transformer", es="Transformer Opus MT En-Roa", @@ -92,32 +93,32 @@ class OpusMtEnRoaTransformer(OpusMtTransformerMixin): en=( "Pretrained transformer for English to Romance translation " "(Portuguese, Spanish, French, Italian, Romanian, Catalan, " - "Galician), selected via the target language parameter. Downloads " - "weights from Hugging Face on first use (internet required)." + "Galician), selected via the target language parameter. Download " + "its weights from Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción del inglés a lenguas " "romances (portugués, español, francés, italiano, rumano, catalán, " "gallego), seleccionadas con el parámetro de idioma de destino. " - "Descarga pesos de Hugging Face en el primer uso (requiere internet)." + "Descarga sus pesos de Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução do inglês para línguas " "românicas (português, espanhol, francês, italiano, romeno, catalão, " - "galego), selecionadas pelo parâmetro de idioma de destino. Baixa os " - "pesos do Hugging Face no primeiro uso (requer internet)." + "galego), selecionadas pelo parâmetro de idioma de destino. Baixe " + "seus pesos do Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für die Übersetzung von Englisch in " "romanische Sprachen (Portugiesisch, Spanisch, Französisch, " "Italienisch, Rumänisch, Katalanisch, Galicisch), ausgewählt über " - "den Zielsprachenparameter. Lädt Gewichte von Hugging Face bei der " - "ersten Verwendung herunter (Internet erforderlich)." + "den Zielsprachenparameter. Lädt die Gewichte vor der Nutzung von " + "Hugging Face herunter (Internet erforderlich)." ), zh=( "用于英语到罗曼语翻译的预训练 Transformer(葡萄牙语、西班牙语、法语、" "意大利语、罗马尼亚语、加泰罗尼亚语、加利西亚语),通过目标语言参数选择。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#5E35B1" diff --git a/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py index 34b9e818c..bfb2f43b0 100644 --- a/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py @@ -31,6 +31,7 @@ class OpusMtRoaEnTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-roa-en" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-roa-en" SCHEMA = OpusMtRoaEnTransformerSchema + DOWNLOAD_SIZE_BYTES = 1206056595 DISPLAY_NAME: str = MultilingualString( en="Opus MT Roa-En Transformer", es="Transformer Opus MT Roa-En", @@ -41,28 +42,28 @@ class OpusMtRoaEnTransformer(OpusMtTransformerMixin): DESCRIPTION: str = MultilingualString( en=( "Pretrained transformer for Romance to English translation " - "(includes Portuguese to English). Downloads weights from Hugging " - "Face on first use (internet required)." + "(includes Portuguese to English). Download its weights from " + "Hugging Face before use (internet required)." ), es=( "Transformer preentrenado para traducción de lenguas romances al " - "inglés (incluye portugués a inglés). Descarga pesos de Hugging " - "Face en el primer uso (requiere internet)." + "inglés (incluye portugués a inglés). Descarga sus pesos de " + "Hugging Face antes de usarlo (requiere internet)." ), pt=( "Transformer pré-treinado para tradução de línguas românicas para o " - "inglês (inclui português para inglês). Baixa os pesos do Hugging " - "Face no primeiro uso (requer internet)." + "inglês (inclui português para inglês). Baixe seus pesos do " + "Hugging Face antes de usar (requer internet)." ), de=( "Vortrainierter Transformer für die Übersetzung romanischer Sprachen " "ins Englische (einschließlich Portugiesisch nach Englisch). Lädt " - "Gewichte von Hugging Face bei der ersten Verwendung herunter " + "die Gewichte vor der Nutzung von Hugging Face herunter " "(Internet erforderlich)." ), zh=( "用于罗曼语到英语翻译的预训练 Transformer(包括葡萄牙语到英语)。" - "首次使用时从 Hugging Face 下载权重(需要网络)。" + "使用前需从 Hugging Face 下载权重(需要网络)。" ), ) COLOR: str = "#00796B" From cdc8e0df13263228a4ff83a4cf0004fa8e04c155 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 9 Jul 2026 09:49:28 -0400 Subject: [PATCH 094/308] feat: skip alternate framework weights when downloading HF models HF repos ship TensorFlow, Flax, Rust, ONNX, OpenVINO and CoreML copies of the weights that from_pretrained never uses here, so downloads were several times larger than needed. Add HF_IGNORE_PATTERNS (applied to every snapshot_download) to skip them, keeping both .bin and .safetensors so fine-tuning and inference still load, and update DOWNLOAD_SIZE_BYTES to the slimmer real sizes (e.g. Bert 3.4GB->882MB, T5 2.2GB->486MB, opus ~1.2GB->~300MB, SDXL 77GB->35GB). --- .../dependencies/downloads/downloadable.py | 23 +++++++++++++++++++ .../models/hugging_face/albert_transformer.py | 2 +- .../models/hugging_face/bert_transformer.py | 2 +- .../models/hugging_face/bertin_transformer.py | 2 +- .../models/hugging_face/beto_transformer.py | 2 +- .../hugging_face/deberta_v3_transformer.py | 2 +- .../hugging_face/distilbert_transformer.py | 2 +- .../hugging_face/electra_transformer.py | 2 +- .../models/hugging_face/m2m100_transformer.py | 2 +- .../models/hugging_face/minilm_transformer.py | 2 +- .../hugging_face/modernbert_transformer.py | 2 +- .../multilingual_bert_transformer.py | 2 +- .../hugging_face/opus_mt_en_de_transformer.py | 2 +- .../hugging_face/opus_mt_en_es_transformer.py | 2 +- .../hugging_face/opus_mt_en_fr_transformer.py | 2 +- .../opus_mt_en_roa_transformer.py | 2 +- .../hugging_face/opus_mt_es_en_transformer.py | 2 +- .../hugging_face/opus_mt_fr_en_transformer.py | 2 +- .../opus_mt_roa_en_transformer.py | 2 +- .../hugging_face/roberta_transformer.py | 2 +- .../sdxl_canny_controlnet_model.py | 2 +- .../models/hugging_face/sdxl_turbo_model.py | 2 +- .../stable_diffusion_v1_depth_controlnet.py | 2 +- .../hugging_face/stable_diffusion_xl_model.py | 2 +- .../hugging_face/t5_small_transformer.py | 2 +- .../hugging_face/xlm_roberta_transformer.py | 2 +- .../models/hugging_face/xlnet_transformer.py | 2 +- 27 files changed, 49 insertions(+), 26 deletions(-) diff --git a/DashAI/back/dependencies/downloads/downloadable.py b/DashAI/back/dependencies/downloads/downloadable.py index 2334b25a0..5b94f43a1 100644 --- a/DashAI/back/dependencies/downloads/downloadable.py +++ b/DashAI/back/dependencies/downloads/downloadable.py @@ -75,9 +75,30 @@ class HFDownloadableMixin(DownloadableMixin): * ``(repo_id, repo_type)`` -- full snapshot download (original behavior). * ``(repo_id, repo_type, allow_patterns)`` -- partial download; only files matching the glob patterns in ``allow_patterns`` are fetched. + + ``HF_IGNORE_PATTERNS`` is applied to every repo download to skip the + non-PyTorch weight formats HuggingFace repos ship alongside the PyTorch / + safetensors weights (TensorFlow, Flax, Rust, ONNX, OpenVINO, CoreML). These + are never used by ``from_pretrained`` here, so dropping them shrinks the + download without affecting fine-tuning or inference. It keeps both ``.bin`` + and ``.safetensors`` so any model still has a loadable weight. """ HF_REPOS: List[Union[Tuple[str, str], Tuple[str, str, List[str]]]] = [] + #: Alternate-framework artifacts to skip on every download (``*`` matches + #: path separators in ``huggingface_hub`` glob semantics, so these match at + #: any depth, e.g. ``unet/diffusion_flax_model.msgpack``). + HF_IGNORE_PATTERNS: Optional[List[str]] = [ + "*.h5", + "*.msgpack", + "*.ot", + "*.onnx", + "*.onnx_data", + "*.tflite", + "*.mlmodel", + "*openvino*", + "*coreml*", + ] @classmethod def hf_repos(cls) -> List[Union[Tuple[str, str], Tuple[str, str, List[str]]]]: @@ -206,6 +227,8 @@ def download(cls, report: Optional[ProgressReporter] = None) -> None: kwargs = {} if allow_patterns is not None: kwargs["allow_patterns"] = allow_patterns + if cls.HF_IGNORE_PATTERNS: + kwargs["ignore_patterns"] = list(cls.HF_IGNORE_PATTERNS) snapshot_download( repo_id=rid, repo_type=rtype, local_dir=str(target), **kwargs ) diff --git a/DashAI/back/models/hugging_face/albert_transformer.py b/DashAI/back/models/hugging_face/albert_transformer.py index 1ace38ad1..25b686d0a 100644 --- a/DashAI/back/models/hugging_face/albert_transformer.py +++ b/DashAI/back/models/hugging_face/albert_transformer.py @@ -59,5 +59,5 @@ class AlbertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Speed" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "albert-base-v2" - DOWNLOAD_SIZE_BYTES: int = 330556087 + DOWNLOAD_SIZE_BYTES: int = 96833451 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_albert" diff --git a/DashAI/back/models/hugging_face/bert_transformer.py b/DashAI/back/models/hugging_face/bert_transformer.py index 594fbc0e3..508448e3d 100644 --- a/DashAI/back/models/hugging_face/bert_transformer.py +++ b/DashAI/back/models/hugging_face/bert_transformer.py @@ -58,5 +58,5 @@ class BertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bert-base-uncased" - DOWNLOAD_SIZE_BYTES: int = 3454102158 + DOWNLOAD_SIZE_BYTES: int = 881643453 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_bert" diff --git a/DashAI/back/models/hugging_face/bertin_transformer.py b/DashAI/back/models/hugging_face/bertin_transformer.py index 984b1e5e2..f3b24ced4 100644 --- a/DashAI/back/models/hugging_face/bertin_transformer.py +++ b/DashAI/back/models/hugging_face/bertin_transformer.py @@ -58,5 +58,5 @@ class BertinTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "RecordVoiceOver" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bertin-project/bertin-roberta-base-spanish" - DOWNLOAD_SIZE_BYTES: int = 1510386247 + DOWNLOAD_SIZE_BYTES: int = 1011598492 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_bertin" diff --git a/DashAI/back/models/hugging_face/beto_transformer.py b/DashAI/back/models/hugging_face/beto_transformer.py index 78a549c05..3e4ecb1bb 100644 --- a/DashAI/back/models/hugging_face/beto_transformer.py +++ b/DashAI/back/models/hugging_face/beto_transformer.py @@ -58,5 +58,5 @@ class BetoTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "RecordVoiceOver" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "dccuchile/bert-base-spanish-wwm-cased" - DOWNLOAD_SIZE_BYTES: int = 1416527075 + DOWNLOAD_SIZE_BYTES: int = 440350800 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_beto" diff --git a/DashAI/back/models/hugging_face/deberta_v3_transformer.py b/DashAI/back/models/hugging_face/deberta_v3_transformer.py index 71684667d..03bd82c5a 100644 --- a/DashAI/back/models/hugging_face/deberta_v3_transformer.py +++ b/DashAI/back/models/hugging_face/deberta_v3_transformer.py @@ -61,5 +61,5 @@ class DebertaV3Transformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DebertaV3TransformerSchema MODEL_NAME: str = "microsoft/deberta-v3-base" - DOWNLOAD_SIZE_BYTES: int = 1851424112 + DOWNLOAD_SIZE_BYTES: int = 373616107 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_deberta_v3" diff --git a/DashAI/back/models/hugging_face/distilbert_transformer.py b/DashAI/back/models/hugging_face/distilbert_transformer.py index 84695f532..ebdff496d 100644 --- a/DashAI/back/models/hugging_face/distilbert_transformer.py +++ b/DashAI/back/models/hugging_face/distilbert_transformer.py @@ -305,5 +305,5 @@ class DistilBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "distilbert-base-uncased" - DOWNLOAD_SIZE_BYTES: int = 1529742866 + DOWNLOAD_SIZE_BYTES: int = 536641210 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_distilbert" diff --git a/DashAI/back/models/hugging_face/electra_transformer.py b/DashAI/back/models/hugging_face/electra_transformer.py index 088bd2aa7..43e8e65dc 100644 --- a/DashAI/back/models/hugging_face/electra_transformer.py +++ b/DashAI/back/models/hugging_face/electra_transformer.py @@ -58,5 +58,5 @@ class ElectraTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "ElectricBolt" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "google/electra-small-discriminator" - DOWNLOAD_SIZE_BYTES: int = 163615837 + DOWNLOAD_SIZE_BYTES: int = 54946248 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_electra" diff --git a/DashAI/back/models/hugging_face/m2m100_transformer.py b/DashAI/back/models/hugging_face/m2m100_transformer.py index 601e5d93b..de5c618cb 100644 --- a/DashAI/back/models/hugging_face/m2m100_transformer.py +++ b/DashAI/back/models/hugging_face/m2m100_transformer.py @@ -158,7 +158,7 @@ class M2M100Transformer(HFPretrainedDownloadMixin, TranslationModel): COLOR: str = "#6A1B9A" ICON: str = "Language" MODEL_NAME: str = "facebook/m2m100_418M" - DOWNLOAD_SIZE_BYTES: int = 3877717593 + DOWNLOAD_SIZE_BYTES: int = 1941936305 def __init__(self, model=None, pretrained_dir=None, **kwargs): kwargs = self.validate_and_transform(kwargs) diff --git a/DashAI/back/models/hugging_face/minilm_transformer.py b/DashAI/back/models/hugging_face/minilm_transformer.py index 8888c8bc9..c9990e4e5 100644 --- a/DashAI/back/models/hugging_face/minilm_transformer.py +++ b/DashAI/back/models/hugging_face/minilm_transformer.py @@ -58,5 +58,5 @@ class MiniLMTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Speed" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "microsoft/MiniLM-L12-H384-uncased" - DOWNLOAD_SIZE_BYTES: int = 400889386 + DOWNLOAD_SIZE_BYTES: int = 133721893 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_minilm" diff --git a/DashAI/back/models/hugging_face/modernbert_transformer.py b/DashAI/back/models/hugging_face/modernbert_transformer.py index a087fc310..af58e5b2a 100644 --- a/DashAI/back/models/hugging_face/modernbert_transformer.py +++ b/DashAI/back/models/hugging_face/modernbert_transformer.py @@ -55,6 +55,6 @@ class ModernBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Psychology" SCHEMA = ModernBertTransformerSchema MODEL_NAME: str = "answerdotai/ModernBERT-base" - DOWNLOAD_SIZE_BYTES: int = 3134313772 + DOWNLOAD_SIZE_BYTES: int = 1199464688 MAX_TOKEN_LENGTH: int = 8192 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_modernbert" diff --git a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py index 5e3574f8f..a3359d142 100644 --- a/DashAI/back/models/hugging_face/multilingual_bert_transformer.py +++ b/DashAI/back/models/hugging_face/multilingual_bert_transformer.py @@ -59,7 +59,7 @@ class MultilingualBertTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Translate" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "bert-base-multilingual-cased" - DOWNLOAD_SIZE_BYTES: int = 3226865011 + DOWNLOAD_SIZE_BYTES: int = 1431570300 TEMP_CHECKPOINT_DIR: str = ( "DashAI/back/user_models/temp_checkpoints_multilingual_bert" ) diff --git a/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py index 0c5854f6b..8007c5e97 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_de_transformer.py @@ -29,7 +29,7 @@ class OpusMtEnDeTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-de" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-de" SCHEMA = OpusMtEnDeTransformerSchema - DOWNLOAD_SIZE_BYTES = 1430871510 + DOWNLOAD_SIZE_BYTES = 300772148 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-De Transformer", es="Transformer Opus MT En-De", diff --git a/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py index 2c9cf281a..3ed97502d 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_es_transformer.py @@ -270,7 +270,7 @@ class OpusMtEnESTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-es" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-es" SCHEMA = OpusMtEnESTransformerSchema - DOWNLOAD_SIZE_BYTES = 937836389 + DOWNLOAD_SIZE_BYTES = 315310815 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-Es Transformer", es="Transformer Opus MT En-Es", diff --git a/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py index 9c409d3ad..18c791f16 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_fr_transformer.py @@ -29,7 +29,7 @@ class OpusMtEnFrTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-fr" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-fr" SCHEMA = OpusMtEnFrTransformerSchema - DOWNLOAD_SIZE_BYTES = 903735972 + DOWNLOAD_SIZE_BYTES = 303750994 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-Fr Transformer", es="Transformer Opus MT En-Fr", diff --git a/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py b/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py index 9845bf231..c6a090c5d 100644 --- a/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_en_roa_transformer.py @@ -81,7 +81,7 @@ class OpusMtEnRoaTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-en-roa" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-en-roa" SCHEMA = OpusMtEnRoaTransformerSchema - DOWNLOAD_SIZE_BYTES = 1171638932 + DOWNLOAD_SIZE_BYTES = 297844640 DISPLAY_NAME: str = MultilingualString( en="Opus MT En-Roa Transformer", es="Transformer Opus MT En-Roa", diff --git a/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py index 96b71168c..43d077460 100644 --- a/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_es_en_transformer.py @@ -32,7 +32,7 @@ class OpusMtEsENTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-es-en" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-es-en" SCHEMA = OpusMtEsENTransformerSchema - DOWNLOAD_SIZE_BYTES = 627891360 + DOWNLOAD_SIZE_BYTES = 315310760 DISPLAY_NAME: str = MultilingualString( en="Opus MT Es-En Transformer", es="Transformer Opus MT Es-En", diff --git a/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py index fcb7e397c..94f77975b 100644 --- a/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_fr_en_transformer.py @@ -29,7 +29,7 @@ class OpusMtFrEnTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-fr-en" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-fr-en" SCHEMA = OpusMtFrEnTransformerSchema - DOWNLOAD_SIZE_BYTES = 1204539675 + DOWNLOAD_SIZE_BYTES = 604554697 DISPLAY_NAME: str = MultilingualString( en="Opus MT Fr-En Transformer", es="Transformer Opus MT Fr-En", diff --git a/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py b/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py index bfb2f43b0..5d1f189f0 100644 --- a/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py +++ b/DashAI/back/models/hugging_face/opus_mt_roa_en_transformer.py @@ -31,7 +31,7 @@ class OpusMtRoaEnTransformer(OpusMtTransformerMixin): MODEL_NAME: str = "Helsinki-NLP/opus-mt-roa-en" TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_opus-mt-roa-en" SCHEMA = OpusMtRoaEnTransformerSchema - DOWNLOAD_SIZE_BYTES = 1206056595 + DOWNLOAD_SIZE_BYTES = 315135823 DISPLAY_NAME: str = MultilingualString( en="Opus MT Roa-En Transformer", es="Transformer Opus MT Roa-En", diff --git a/DashAI/back/models/hugging_face/roberta_transformer.py b/DashAI/back/models/hugging_face/roberta_transformer.py index 2a9414640..434a3bfd4 100644 --- a/DashAI/back/models/hugging_face/roberta_transformer.py +++ b/DashAI/back/models/hugging_face/roberta_transformer.py @@ -58,5 +58,5 @@ class RobertaTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "SmartToy" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "roberta-base" - DOWNLOAD_SIZE_BYTES: int = 2815192007 + DOWNLOAD_SIZE_BYTES: int = 1003342916 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_roberta" diff --git a/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py b/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py index 8f5e7e0a1..83a5bd1d3 100644 --- a/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py +++ b/DashAI/back/models/hugging_face/sdxl_canny_controlnet_model.py @@ -296,7 +296,7 @@ class SDXLCannyControlNetModel(HFDownloadableMixin, BaseControlNetModel): ("diffusers/controlnet-canny-sdxl-1.0", "model"), ("madebyollin/sdxl-vae-fp16-fix", "model"), ] - DOWNLOAD_SIZE_BYTES = 93308273070 + DOWNLOAD_SIZE_BYTES = 51644917846 COLOR: str = "#1a237e" DISPLAY_NAME: str = MultilingualString( en="SDXL Canny ControlNet", diff --git a/DashAI/back/models/hugging_face/sdxl_turbo_model.py b/DashAI/back/models/hugging_face/sdxl_turbo_model.py index 0e65a33ee..0b7c8cca7 100644 --- a/DashAI/back/models/hugging_face/sdxl_turbo_model.py +++ b/DashAI/back/models/hugging_face/sdxl_turbo_model.py @@ -328,7 +328,7 @@ class SDXLTurboModel(HFPretrainedDownloadMixin, TextToImageGenerationTaskModel): SCHEMA = SDXLTurboSchema MODEL_NAME: str = "stabilityai/sdxl-turbo" # SDXL-Turbo diffusers pipeline is ~7 GB. - DOWNLOAD_SIZE_BYTES: int = 55516176914 + DOWNLOAD_SIZE_BYTES: int = 41631892171 COLOR: str = "#b71c1c" DISPLAY_NAME: str = MultilingualString( en="SDXL Turbo", diff --git a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py index b21cfdfbd..03dec56b8 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_v1_depth_controlnet.py @@ -217,7 +217,7 @@ class StableDiffusionXLV1ControlNet(HFDownloadableMixin, BaseControlNetModel): ("madebyollin/sdxl-vae-fp16-fix", "model"), ("Intel/dpt-hybrid-midas", "model"), ] - DOWNLOAD_SIZE_BYTES = 80696728644 + DOWNLOAD_SIZE_BYTES = 39033373420 COLOR: str = "#e65100" DISPLAY_NAME: str = MultilingualString( en="Stable Diffusion XL V1 ControlNet", diff --git a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py index 5c690e363..a5fe8e81c 100644 --- a/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py +++ b/DashAI/back/models/hugging_face/stable_diffusion_xl_model.py @@ -494,7 +494,7 @@ class StableDiffusionXL(StableDiffusionXLGenerationModel): MODEL_NAME: str = "stabilityai/stable-diffusion-xl-base-1.0" # SDXL diffusers pipeline (base + refiner-less) is ~7 GB. - DOWNLOAD_SIZE_BYTES: int = 76912765291 + DOWNLOAD_SIZE_BYTES: int = 35249410067 DISPLAY_NAME = MultilingualString( en="Stable Diffusion XL", es="Stable Diffusion XL", diff --git a/DashAI/back/models/hugging_face/t5_small_transformer.py b/DashAI/back/models/hugging_face/t5_small_transformer.py index 5a061c63b..37a1ba8f6 100644 --- a/DashAI/back/models/hugging_face/t5_small_transformer.py +++ b/DashAI/back/models/hugging_face/t5_small_transformer.py @@ -131,7 +131,7 @@ class T5SmallTransformer(HFPretrainedDownloadMixin, TranslationModel): COLOR: str = "#00695C" ICON: str = "Language" MODEL_NAME: str = "t5-small" - DOWNLOAD_SIZE_BYTES: int = 2246973515 + DOWNLOAD_SIZE_BYTES: int = 486302401 def __init__(self, model=None, pretrained_dir=None, **kwargs): kwargs = self.validate_and_transform(kwargs) diff --git a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py index 12d1941ab..f6cb74142 100644 --- a/DashAI/back/models/hugging_face/xlm_roberta_transformer.py +++ b/DashAI/back/models/hugging_face/xlm_roberta_transformer.py @@ -62,5 +62,5 @@ class XlmRobertaTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "Language" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "xlm-roberta-base" - DOWNLOAD_SIZE_BYTES: int = 6352430498 + DOWNLOAD_SIZE_BYTES: int = 2245330190 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_xlm_roberta" diff --git a/DashAI/back/models/hugging_face/xlnet_transformer.py b/DashAI/back/models/hugging_face/xlnet_transformer.py index 63a914b2b..56b05428b 100644 --- a/DashAI/back/models/hugging_face/xlnet_transformer.py +++ b/DashAI/back/models/hugging_face/xlnet_transformer.py @@ -58,5 +58,5 @@ class XlnetTransformer(HuggingFaceTextClassificationTransformer): ICON: str = "AutoAwesome" SCHEMA = DistilBertTransformerSchema MODEL_NAME: str = "xlnet-base-cased" - DOWNLOAD_SIZE_BYTES: int = 1600067104 + DOWNLOAD_SIZE_BYTES: int = 469226606 TEMP_CHECKPOINT_DIR: str = "DashAI/back/user_models/temp_checkpoints_xlnet" From f51d7556a43a013ea597880606f7016887afc052 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 9 Jul 2026 09:52:49 -0400 Subject: [PATCH 095/308] fix: force classic transfer path for model downloads The Xet transfer backend returns a 404 on its read-token endpoint for some repos (e.g. bert-base-uncased), aborting the whole snapshot download. Disable Xet before downloading so it uses the reliable HTTP/LFS path. --- DashAI/back/dependencies/downloads/downloadable.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/DashAI/back/dependencies/downloads/downloadable.py b/DashAI/back/dependencies/downloads/downloadable.py index 5b94f43a1..555ede709 100644 --- a/DashAI/back/dependencies/downloads/downloadable.py +++ b/DashAI/back/dependencies/downloads/downloadable.py @@ -7,6 +7,7 @@ """ import logging +import os import pathlib import shutil from typing import Callable, List, Optional, Tuple, Union @@ -216,6 +217,18 @@ def download(cls, report: Optional[ProgressReporter] = None) -> None: ``report(None, "Downloading ")``. ``None`` means no progress reporting. """ + # Force the classic HTTP/LFS transfer path. The Xet backend can return + # a 404 on its read-token endpoint for some repos (e.g. bert-base- + # uncased), which aborts the whole download; the classic path is + # slower but reliable. + os.environ["HF_HUB_DISABLE_XET"] = "1" + try: + from huggingface_hub import constants as hf_constants + + hf_constants.HF_HUB_DISABLE_XET = True + except Exception: + pass + for entry in cls.hf_repos(): rid, rtype, allow_patterns = cls._unpack_entry(entry) target = cls._repo_dir(rid) From 12ea6734244509997000ee9322e78177b758817a Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 9 Jul 2026 10:39:42 -0400 Subject: [PATCH 096/308] fix: resolve PixArt-Sigma model source before loading PixArtSigma __init__ discarded the _pretrained_source(None) result and then read self.model_name, which was never set, raising AttributeError on generation. Assign the resolved source to self.model_name like the other diffusion models. --- DashAI/back/models/hugging_face/pixart_sigma_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DashAI/back/models/hugging_face/pixart_sigma_model.py b/DashAI/back/models/hugging_face/pixart_sigma_model.py index 7d4596380..2fd17b261 100644 --- a/DashAI/back/models/hugging_face/pixart_sigma_model.py +++ b/DashAI/back/models/hugging_face/pixart_sigma_model.py @@ -458,7 +458,7 @@ def __init__(self, **kwargs): self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self._pretrained_source(None) + self.model_name = self._pretrained_source(None) self.model = PixArtSigmaPipeline.from_pretrained( self.model_name, From 9263176de94a3a6ee14660fa67272f018623e3da Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 9 Jul 2026 10:50:43 -0400 Subject: [PATCH 097/308] fix: load PixArt-Sigma 512 from its transformer plus the 1024 pipeline The 512 checkpoint has no model_index.json (it ships only the transformer), so PixArtSigmaPipeline.from_pretrained on it failed with 'no file named model_index.json'. Download both the 512 (transformer) and 1024 (full pipeline) repos and build the pipeline from the 1024 scaffold with the 512 transformer injected, the standard PixArt-Sigma 512 recipe. Size updated to the combined download. --- .../models/hugging_face/pixart_sigma_model.py | 84 +++++++++++++++++-- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/DashAI/back/models/hugging_face/pixart_sigma_model.py b/DashAI/back/models/hugging_face/pixart_sigma_model.py index 2fd17b261..dc855743f 100644 --- a/DashAI/back/models/hugging_face/pixart_sigma_model.py +++ b/DashAI/back/models/hugging_face/pixart_sigma_model.py @@ -450,20 +450,13 @@ def __init__(self, **kwargs): num_images_per_prompt : int Number of images to generate per prompt call. """ - import torch - from diffusers import PixArtSigmaPipeline - kwargs = self.validate_and_transform(kwargs) use_gpu = DEVICE_TO_IDX.get(kwargs.get("device")) >= 0 self.device = ( f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) - self.model_name = self._pretrained_source(None) - self.model = PixArtSigmaPipeline.from_pretrained( - self.model_name, - torch_dtype=torch.float16 if use_gpu else torch.float32, - ).to(self.device) + self.model = self._build_pipeline(use_gpu).to(self.device) self.negative_prompt = kwargs.get("negative_prompt") self.num_inference_steps = kwargs.get("num_inference_steps") @@ -473,6 +466,32 @@ def __init__(self, **kwargs): self.height = kwargs.get("height") self.num_images_per_prompt = kwargs.get("num_images_per_prompt") + def _build_pipeline(self, use_gpu: bool): + """Load the PixArt-Sigma pipeline from the downloaded checkpoint. + + The default loads a self-contained checkpoint (one that ships a full + ``model_index.json`` pipeline, e.g. the 1024px variant). Subclasses + whose checkpoint contains only the transformer override this. + + Parameters + ---------- + use_gpu : bool + Whether a GPU is available (selects float16 vs float32). + + Returns + ------- + diffusers.PixArtSigmaPipeline + The loaded pipeline (not yet moved to a device). + """ + import torch + from diffusers import PixArtSigmaPipeline + + self.model_name = self._pretrained_source(None) + return PixArtSigmaPipeline.from_pretrained( + self.model_name, + torch_dtype=torch.float16 if use_gpu else torch.float32, + ) + def generate(self, input: str) -> List[Any]: """Generate images from a text prompt. @@ -570,7 +589,11 @@ class PixArtSigma512(PixArtSigmaGenerationModel): """ MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-512-MS" - DOWNLOAD_SIZE_BYTES: int = 2447758901 + # The 512 checkpoint ships only the transformer; the T5 text encoder, VAE, + # scheduler and tokenizer are loaded from the 1024 checkpoint, so both repos + # are downloaded. Size is the sum of the two. + PIPELINE_REPO: str = "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" + DOWNLOAD_SIZE_BYTES: int = 24280249290 DISPLAY_NAME = MultilingualString( en="PixArt-Sigma 512", es="PixArt-Sigma 512", @@ -615,3 +638,46 @@ class PixArtSigma512(PixArtSigmaGenerationModel): "模型页面: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" ), ) + + @classmethod + def hf_repos(cls): + """Download the 512 transformer repo and the 1024 pipeline repo. + + Returns + ------- + list of tuple of (str, str) + The 512 checkpoint (transformer only) and the 1024 checkpoint + (full pipeline: T5, VAE, scheduler, tokenizer). + """ + return [(cls.MODEL_NAME, "model"), (cls.PIPELINE_REPO, "model")] + + def _build_pipeline(self, use_gpu: bool): + """Load the 512 transformer into the 1024 pipeline scaffold. + + The 512 repo has no ``model_index.json`` (it ships only the + transformer), so the pipeline is loaded from the 1024 repo with the + 512 transformer injected. + + Parameters + ---------- + use_gpu : bool + Whether a GPU is available (selects float16 vs float32). + + Returns + ------- + diffusers.PixArtSigmaPipeline + The loaded pipeline (not yet moved to a device). + """ + import torch + from diffusers import PixArtSigmaPipeline, Transformer2DModel + + dtype = torch.float16 if use_gpu else torch.float32 + transformer_dir = str(self._repo_dir(self.MODEL_NAME)) + pipeline_dir = str(self._repo_dir(self.PIPELINE_REPO)) + self.model_name = pipeline_dir + transformer = Transformer2DModel.from_pretrained( + transformer_dir, subfolder="transformer", torch_dtype=dtype + ) + return PixArtSigmaPipeline.from_pretrained( + pipeline_dir, transformer=transformer, torch_dtype=dtype + ) From 82d00698b269d1723ef35c66fba5c26b78945121 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Thu, 9 Jul 2026 11:00:14 -0400 Subject: [PATCH 098/308] refactor: merge PixArt-Sigma 512 and 1024 into one component The 512 checkpoint has no standalone pipeline and needs the 1024 scaffold, so keeping them as separate components meant downloading the shared T5/VAE twice. Replace PixArtSigma512 and PixArtSigma1024 with a single PixArtSigma that downloads both repos once and picks the checkpoint (1024 or 512) via a schema parameter, injecting the 512 transformer into the 1024 pipeline when selected. --- DashAI/back/initial_components.py | 8 +- .../models/hugging_face/pixart_sigma_model.py | 256 ++++++------------ .../models/test_pixart_tongyi_downloadable.py | 30 +- 3 files changed, 104 insertions(+), 190 deletions(-) diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index c02f29c22..019ecca95 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -187,10 +187,7 @@ from DashAI.back.models.hugging_face.opus_mt_roa_en_transformer import ( OpusMtRoaEnTransformer, ) -from DashAI.back.models.hugging_face.pixart_sigma_model import ( - PixArtSigma512, - PixArtSigma1024, -) +from DashAI.back.models.hugging_face.pixart_sigma_model import PixArtSigma from DashAI.back.models.hugging_face.qwen_model import ( Qwen25_05BInstruct, Qwen25_15BInstruct, @@ -402,8 +399,7 @@ def get_initial_components(): OpusMtEnRoaTransformer, OpusMtEsENTransformer, OpusMtFrEnTransformer, - PixArtSigma1024, - PixArtSigma512, + PixArtSigma, Qwen25_05BInstruct, Qwen25_15BInstruct, OpusMtRoaEnTransformer, diff --git a/DashAI/back/models/hugging_face/pixart_sigma_model.py b/DashAI/back/models/hugging_face/pixart_sigma_model.py index dc855743f..20a5f02fb 100644 --- a/DashAI/back/models/hugging_face/pixart_sigma_model.py +++ b/DashAI/back/models/hugging_face/pixart_sigma_model.py @@ -21,7 +21,7 @@ class PixArtSigmaSchema(BaseSchema): """Configuration schema for PixArt-Sigma text-to-image generation. - Configures the checkpoint variant (``model_name``), prompt conditioning + Configures the checkpoint (``checkpoint``), prompt conditioning (``negative_prompt``), denoising schedule (``num_inference_steps``), classifier free guidance strength (``guidance_scale``), output dimensions (``width``, ``height``), reproducibility (``seed``), hardware target @@ -29,6 +29,46 @@ class PixArtSigmaSchema(BaseSchema): ``PixArtSigmaModel``. """ + checkpoint: schema_field( + enum_field(enum=["1024", "512"]), + placeholder="1024", + description=MultilingualString( + en=( + "Which PixArt-Sigma checkpoint to use: '1024' for best quality " + "at 1024x1024 px, or '512' for a faster, lighter model at " + "512x512 px. Both checkpoints are downloaded together." + ), + es=( + "Qué checkpoint de PixArt-Sigma usar: '1024' para mejor calidad " + "a 1024x1024 px, o '512' para un modelo más rápido y ligero a " + "512x512 px. Ambos checkpoints se descargan juntos." + ), + pt=( + "Qual checkpoint do PixArt-Sigma usar: '1024' para melhor " + "qualidade a 1024x1024 px, ou '512' para um modelo mais rápido " + "e leve a 512x512 px. Ambos os checkpoints são baixados juntos." + ), + de=( + "Welcher PixArt-Sigma-Checkpoint verwendet wird: '1024' für " + "beste Qualität bei 1024x1024 px oder '512' für ein schnelleres, " + "leichteres Modell bei 512x512 px. Beide Checkpoints werden " + "zusammen heruntergeladen." + ), + zh=( + "使用哪个 PixArt-Sigma 检查点:'1024' 表示 1024x1024 " + "像素的最佳质量,'512' 表示 512x512 像素更快更轻量的模型。" + "两个检查点会一起下载。" + ), + ), + alias=MultilingualString( + en="Checkpoint", + es="Checkpoint", + pt="Checkpoint", + de="Checkpoint", + zh="检查点", + ), + ) # type: ignore + negative_prompt: Optional[ schema_field( string_field(), @@ -321,9 +361,7 @@ class PixArtSigmaSchema(BaseSchema): ) # type: ignore -class PixArtSigmaGenerationModel( - HFPretrainedDownloadMixin, TextToImageGenerationTaskModel -): +class PixArtSigma(HFPretrainedDownloadMixin, TextToImageGenerationTaskModel): """Diffusion Transformer model for high efficiency text-to-image generation. Wraps the PixArt-Sigma pipeline, which replaces the U-Net backbone used @@ -345,7 +383,12 @@ class PixArtSigmaGenerationModel( """ SCHEMA = PixArtSigmaSchema - MODEL_NAME: str = "" + # The 1024 checkpoint is a full pipeline (T5, VAE, scheduler, tokenizer). + # The 512 checkpoint ships only a transformer, injected into this pipeline + # when the 512 variant is selected, so both repos are downloaded together. + MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" + TRANSFORMER_512_REPO: str = "PixArt-alpha/PixArt-Sigma-XL-2-512-MS" + DOWNLOAD_SIZE_BYTES: int = 24280249290 COLOR: str = "#6a1b9a" DISPLAY_NAME: str = MultilingualString( en="PixArt-Sigma", @@ -456,6 +499,7 @@ def __init__(self, **kwargs): f"cuda:{DEVICE_TO_IDX.get(kwargs.get('device'))}" if use_gpu else "cpu" ) + self.checkpoint = kwargs.get("checkpoint") self.model = self._build_pipeline(use_gpu).to(self.device) self.negative_prompt = kwargs.get("negative_prompt") @@ -466,12 +510,26 @@ def __init__(self, **kwargs): self.height = kwargs.get("height") self.num_images_per_prompt = kwargs.get("num_images_per_prompt") + @classmethod + def hf_repos(cls): + """Download both the 1024 (full pipeline) and 512 (transformer) repos. + + Returns + ------- + list of tuple of (str, str) + The 1024 checkpoint (T5, VAE, scheduler, tokenizer, transformer) + and the 512 checkpoint (transformer only), so either variant can be + used after a single download. + """ + return [(cls.MODEL_NAME, "model"), (cls.TRANSFORMER_512_REPO, "model")] + def _build_pipeline(self, use_gpu: bool): - """Load the PixArt-Sigma pipeline from the downloaded checkpoint. + """Load the PixArt-Sigma pipeline for the selected checkpoint. - The default loads a self-contained checkpoint (one that ships a full - ``model_index.json`` pipeline, e.g. the 1024px variant). Subclasses - whose checkpoint contains only the transformer override this. + The pipeline scaffold (T5, VAE, scheduler, tokenizer) always comes from + the 1024 repo. For the ``"1024"`` checkpoint its own transformer is + used; for ``"512"`` the transformer from the 512 repo is injected (the + 512 repo has no ``model_index.json`` and cannot be loaded on its own). Parameters ---------- @@ -486,11 +544,22 @@ def _build_pipeline(self, use_gpu: bool): import torch from diffusers import PixArtSigmaPipeline - self.model_name = self._pretrained_source(None) - return PixArtSigmaPipeline.from_pretrained( - self.model_name, - torch_dtype=torch.float16 if use_gpu else torch.float32, - ) + dtype = torch.float16 if use_gpu else torch.float32 + pipeline_dir = str(self._repo_dir(self.MODEL_NAME)) + self.model_name = pipeline_dir + + if self.checkpoint == "512": + from diffusers import Transformer2DModel + + transformer_dir = str(self._repo_dir(self.TRANSFORMER_512_REPO)) + transformer = Transformer2DModel.from_pretrained( + transformer_dir, subfolder="transformer", torch_dtype=dtype + ) + return PixArtSigmaPipeline.from_pretrained( + pipeline_dir, transformer=transformer, torch_dtype=dtype + ) + + return PixArtSigmaPipeline.from_pretrained(pipeline_dir, torch_dtype=dtype) def generate(self, input: str) -> List[Any]: """Generate images from a text prompt. @@ -522,162 +591,3 @@ def generate(self, input: str) -> List[Any]: num_images_per_prompt=self.num_images_per_prompt, ) return output.images - - -class PixArtSigma1024(PixArtSigmaGenerationModel): - """PixArt-Sigma XL 1024px checkpoint. - - Downloads its checkpoint into the component's own download folder. - """ - - MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - DOWNLOAD_SIZE_BYTES: int = 21832490389 - DISPLAY_NAME = MultilingualString( - en="PixArt-Sigma 1024", - es="PixArt-Sigma 1024", - pt="PixArt-Sigma 1024", - de="PixArt-Sigma 1024", - zh="PixArt-Sigma 1024", - ) - DESCRIPTION = MultilingualString( - en=( - "PixArt-Sigma XL by PixArt-alpha, a diffusion transformer (DiT) " - "text-to-image model that reaches quality comparable to larger diffusion " - "models with far fewer parameters. This checkpoint generates at " - "1024x1024 px. Weights are downloaded into the component's own folder. " - "Model page: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-M" - "S" - ), - es=( - "PixArt-Sigma XL de PixArt-alpha, un modelo de texto a imagen basado en " - "transformer de difusión (DiT) que alcanza una calidad comparable a " - "modelos de difusión más grandes con muchos menos parámetros. Este " - "checkpoint genera a 1024x1024 px. Los pesos se descargan en la carpeta " - "propia del componente. Página del modelo: " - "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - ), - pt=( - "PixArt-Sigma XL de PixArt-alpha, um modelo de texto para imagem baseado " - "em transformer de difusão (DiT) que atinge qualidade comparável a " - "modelos de difusão maiores com muito menos parâmetros. Este " - "checkpoint gera a 1024x1024 px. Os pesos são baixados na pasta " - "própria do componente. Página do modelo: " - "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - ), - de=( - "PixArt-Sigma XL von PixArt-alpha, ein Text-zu-Bild-Modell auf Basis " - "eines Diffusion-Transformers (DiT), das mit weit weniger Parametern " - "eine Qualität vergleichbar mit größeren Diffusionsmodellen erreicht. " - "Dieser Checkpoint erzeugt Bilder mit 1024x1024 px. Die Gewichte werden " - "in den eigenen Ordner der Komponente heruntergeladen. Modellseite: " - "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - ), - zh=( - "PixArt-alpha 推出的 PixArt-Sigma XL,是一种基于扩散 " - "Transformer(DiT)的文本到图像模型,以远更少的参数量达到可媲美更大扩散模" - "型的质量。该检查点以 1024x1024 " - "像素生成。权重会下载到该组件自己的文件夹中。 模型页面: " - "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - ), - ) - - -class PixArtSigma512(PixArtSigmaGenerationModel): - """PixArt-Sigma XL 512px checkpoint (faster). - - Downloads its checkpoint into the component's own download folder. - """ - - MODEL_NAME: str = "PixArt-alpha/PixArt-Sigma-XL-2-512-MS" - # The 512 checkpoint ships only the transformer; the T5 text encoder, VAE, - # scheduler and tokenizer are loaded from the 1024 checkpoint, so both repos - # are downloaded. Size is the sum of the two. - PIPELINE_REPO: str = "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" - DOWNLOAD_SIZE_BYTES: int = 24280249290 - DISPLAY_NAME = MultilingualString( - en="PixArt-Sigma 512", - es="PixArt-Sigma 512", - pt="PixArt-Sigma 512", - de="PixArt-Sigma 512", - zh="PixArt-Sigma 512", - ) - DESCRIPTION = MultilingualString( - en=( - "PixArt-Sigma XL by PixArt-alpha at 512x512 px, a diffusion transformer " - "(DiT) text-to-image model. The lower resolution makes it faster and " - "lighter than the 1024 px variant. Weights are downloaded into the " - "component's own folder. Model page: https://huggingface.co/PixArt-alpha/" - "PixArt-Sigma-XL-2-512-MS" - ), - es=( - "PixArt-Sigma XL de PixArt-alpha a 512x512 px, un modelo de texto a " - "imagen basado en transformer de difusión (DiT). La menor resolución " - "lo hace más rápido y ligero que la variante de 1024 px. Los pesos se " - "descargan en la carpeta propia del componente. Página del modelo: " - "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" - ), - pt=( - "PixArt-Sigma XL de PixArt-alpha a 512x512 px, um modelo de texto para " - "imagem baseado em transformer de difusão (DiT). A menor resolução o " - "torna mais rápido e leve que a variante de 1024 px. Os pesos são " - "baixados na pasta própria do componente. Página do modelo: " - "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" - ), - de=( - "PixArt-Sigma XL von PixArt-alpha bei 512x512 px, ein " - "Text-zu-Bild-Modell auf Basis eines Diffusion-Transformers (DiT). Die " - "geringere Auflösung macht es schneller und leichter als die " - "1024-px-Variante. Die Gewichte werden in den eigenen Ordner der " - "Komponente heruntergeladen. Modellseite: " - "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" - ), - zh=( - "PixArt-alpha 推出的 PixArt-Sigma XL,分辨率为 512x512 " - "像素,是一种基于扩散 Transformer(DiT)的文本到图像模型。较低的分辨率使" - "其比 1024 像素变体更快、更轻量。权重会下载到该组件自己的文件夹中。 " - "模型页面: https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS" - ), - ) - - @classmethod - def hf_repos(cls): - """Download the 512 transformer repo and the 1024 pipeline repo. - - Returns - ------- - list of tuple of (str, str) - The 512 checkpoint (transformer only) and the 1024 checkpoint - (full pipeline: T5, VAE, scheduler, tokenizer). - """ - return [(cls.MODEL_NAME, "model"), (cls.PIPELINE_REPO, "model")] - - def _build_pipeline(self, use_gpu: bool): - """Load the 512 transformer into the 1024 pipeline scaffold. - - The 512 repo has no ``model_index.json`` (it ships only the - transformer), so the pipeline is loaded from the 1024 repo with the - 512 transformer injected. - - Parameters - ---------- - use_gpu : bool - Whether a GPU is available (selects float16 vs float32). - - Returns - ------- - diffusers.PixArtSigmaPipeline - The loaded pipeline (not yet moved to a device). - """ - import torch - from diffusers import PixArtSigmaPipeline, Transformer2DModel - - dtype = torch.float16 if use_gpu else torch.float32 - transformer_dir = str(self._repo_dir(self.MODEL_NAME)) - pipeline_dir = str(self._repo_dir(self.PIPELINE_REPO)) - self.model_name = pipeline_dir - transformer = Transformer2DModel.from_pretrained( - transformer_dir, subfolder="transformer", torch_dtype=dtype - ) - return PixArtSigmaPipeline.from_pretrained( - pipeline_dir, transformer=transformer, torch_dtype=dtype - ) diff --git a/tests/back/models/test_pixart_tongyi_downloadable.py b/tests/back/models/test_pixart_tongyi_downloadable.py index 1a646268a..982b9d7a7 100644 --- a/tests/back/models/test_pixart_tongyi_downloadable.py +++ b/tests/back/models/test_pixart_tongyi_downloadable.py @@ -3,33 +3,41 @@ import pytest from DashAI.back.dependencies.downloads.downloadable import HFPretrainedDownloadMixin -from DashAI.back.models.hugging_face.pixart_sigma_model import ( - PixArtSigma512, - PixArtSigma1024, -) +from DashAI.back.models.hugging_face.pixart_sigma_model import PixArtSigma from DashAI.back.models.hugging_face.tongyi_z_image_model import ( TongyiZImage, TongyiZImageTurbo, ) -_CASES = [ - (PixArtSigma1024, "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS"), - (PixArtSigma512, "PixArt-alpha/PixArt-Sigma-XL-2-512-MS"), +# Single-repo checkpoints: hf_repos() is exactly one (repo_id, "model"). +_SINGLE_REPO_CASES = [ (TongyiZImage, "Tongyi-MAI/Z-Image"), (TongyiZImageTurbo, "Tongyi-MAI/Z-Image-Turbo"), ] +_ALL = [PixArtSigma, TongyiZImage, TongyiZImageTurbo] + -@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) -def test_is_downloadable(model_cls, repo_id): +@pytest.mark.parametrize(("model_cls", "repo_id"), _SINGLE_REPO_CASES) +def test_single_repo_is_downloadable(model_cls, repo_id): assert issubclass(model_cls, HFPretrainedDownloadMixin) assert model_cls.REQUIRES_DOWNLOAD is True assert model_cls.DOWNLOAD_SIZE_BYTES is not None assert model_cls.hf_repos() == [(repo_id, "model")] -@pytest.mark.parametrize(("model_cls", "repo_id"), _CASES) -def test_metadata_flags_download(model_cls, repo_id): +def test_pixart_sigma_downloads_both_checkpoints(): + """PixArt-Sigma downloads the 1024 pipeline and the 512 transformer.""" + assert issubclass(PixArtSigma, HFPretrainedDownloadMixin) + assert PixArtSigma.REQUIRES_DOWNLOAD is True + assert PixArtSigma.DOWNLOAD_SIZE_BYTES is not None + repos = [repo_id for repo_id, *_ in PixArtSigma.hf_repos()] + assert "PixArt-alpha/PixArt-Sigma-XL-2-1024-MS" in repos + assert "PixArt-alpha/PixArt-Sigma-XL-2-512-MS" in repos + + +@pytest.mark.parametrize("model_cls", _ALL) +def test_metadata_flags_download(model_cls): meta = model_cls.get_metadata() assert meta["requires_download"] is True assert meta["download_size_bytes"] == model_cls.DOWNLOAD_SIZE_BYTES From e6da49a4be232f8bf022487962d96d453befcc77 Mon Sep 17 00:00:00 2001 From: Creylay Date: Thu, 9 Jul 2026 11:23:49 -0400 Subject: [PATCH 099/308] Add routing and breadcrumbs for model runs in session visualization --- DashAI/front/src/App.jsx | 4 ++++ .../components/models/ModelsBreadcrumbs.jsx | 18 +++++++++++++++++- .../src/components/models/ModelsContext.jsx | 3 +++ .../components/models/SessionVisualization.jsx | 11 +++++++++++ .../front/src/pages/models/ModelsContent.jsx | 6 ++++++ 5 files changed, 41 insertions(+), 1 deletion(-) diff --git a/DashAI/front/src/App.jsx b/DashAI/front/src/App.jsx index 903e514dc..46abcdeb6 100644 --- a/DashAI/front/src/App.jsx +++ b/DashAI/front/src/App.jsx @@ -61,6 +61,10 @@ function App() { path="/app/models/sessions/new/:taskName" element={} /> + } + /> } /> } /> s.id === Number(params.id)); + const sessionName = session?.name ?? `#${params.id}`; + const run = runs.find((r) => r.id === Number(params.runId)); + const runName = run?.name ?? `#${params.runId}`; + return [ + rootCrumb, + { + label: sessionName, + path: `/app/models/sessions/${params.id}`, + current: false, + }, + { label: runName, path: null, current: true }, + ]; + } + if (path.startsWith("/app/models/sessions/") && params.id) { const session = sessions.find((s) => s.id === Number(params.id)); const name = session?.name ?? `#${params.id}`; diff --git a/DashAI/front/src/components/models/ModelsContext.jsx b/DashAI/front/src/components/models/ModelsContext.jsx index 2c092e26e..45e02e65c 100644 --- a/DashAI/front/src/components/models/ModelsContext.jsx +++ b/DashAI/front/src/components/models/ModelsContext.jsx @@ -84,6 +84,7 @@ export function ModelsProvider({ children }) { const [selectedModel, setSelectedModel] = useState(null); const [configOpen, setConfigOpen] = useState(false); const [step, setStep] = useState(0); + const [activeRunId, setActiveRunId] = useState(null); const [selectedOption, setSelectedOption] = useState(OptionsEnum.NEW); const [datasetInfo, setDatasetInfo] = useState(null); const [datasetTab, setDatasetTab] = useState(0); @@ -149,6 +150,8 @@ export function ModelsProvider({ children }) { setSelectedSession, step, setStep, + activeRunId, + setActiveRunId, runs, setRuns, retrainDialogOpen, diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index 9c5cd7857..f864778e2 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -14,6 +14,7 @@ import { Tooltip, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; +import { useParams } from "react-router-dom"; import { PlayArrow, TableChart, @@ -25,6 +26,7 @@ import RunCard from "./RunCard"; import { getComponents } from "../../api/component"; import ResultsGraphs from "../../pages/results/components/ResultsGraphs"; import RetrainConfirmDialog from "./RetrainConfirmDialog"; +import ModelsBreadcrumbs from "./ModelsBreadcrumbs"; import { useTranslation } from "react-i18next"; import { useModels } from "./ModelsContext"; @@ -43,6 +45,7 @@ export default function SessionVisualization() { const isResizing = React.useRef(false); const { t } = useTranslation(["models", "common"]); const sessionTourContext = useTourContext(); + const params = useParams(); const { selectedSession: session, @@ -337,6 +340,14 @@ export default function SessionVisualization() { )} + {/* TEMP scaffold for phase 1 (routing/breadcrumbs) — replaced by the + full-screen model detail view in phase 3 */} + {params.runId && ( + + + + )} + {/* Sticky Comparison Table */} { @@ -43,6 +44,7 @@ export default function ModelsContent() { selectDataset(id); setSelectedSessionId(null); setSelectedTask(null); + setActiveRunId(null); setStep(2); return; } @@ -56,6 +58,7 @@ export default function ModelsContent() { if (task) { setSelectedTask(task); setSelectedSessionId(null); + setActiveRunId(null); setStep(1); } return; @@ -64,6 +67,7 @@ export default function ModelsContent() { if (path.startsWith("/app/models/sessions/") && params.id) { const id = Number(params.id); setSelectedSessionId(id); + setActiveRunId(params.runId ? Number(params.runId) : null); selectDataset(null); return; } @@ -71,6 +75,7 @@ export default function ModelsContent() { if (path === "/app/models" || path === "/app/models/") { setSelectedSessionId(null); setSelectedTask(null); + setActiveRunId(null); const preserved = location.state?.preselectedDatasetId; if (preserved != null) { selectDataset(preserved); @@ -84,6 +89,7 @@ export default function ModelsContent() { location.state?.preselectedDatasetId, params.id, params.taskName, + params.runId, tasks, ]); From e93199a1e3864c994f1a56fe78a897b163e5bbc1 Mon Sep 17 00:00:00 2001 From: Creylay Date: Thu, 9 Jul 2026 11:59:20 -0400 Subject: [PATCH 100/308] Implement ModelCardCompact component and enhance RunCard with status color handling; update translations for run not found messages --- .../components/models/ModelCardCompact.jsx | 193 +++++++ .../front/src/components/models/RunCard.jsx | 66 +-- .../models/SessionVisualization.jsx | 494 ++++++++++-------- .../src/utils/i18n/locales/de/models.json | 1 + .../src/utils/i18n/locales/en/models.json | 1 + .../src/utils/i18n/locales/es/models.json | 1 + .../src/utils/i18n/locales/pt/models.json | 1 + .../src/utils/i18n/locales/zh/models.json | 1 + DashAI/front/src/utils/runStatus.js | 16 + 9 files changed, 516 insertions(+), 258 deletions(-) create mode 100644 DashAI/front/src/components/models/ModelCardCompact.jsx diff --git a/DashAI/front/src/components/models/ModelCardCompact.jsx b/DashAI/front/src/components/models/ModelCardCompact.jsx new file mode 100644 index 000000000..a28976094 --- /dev/null +++ b/DashAI/front/src/components/models/ModelCardCompact.jsx @@ -0,0 +1,193 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { + Paper, + Box, + Typography, + Chip, + IconButton, + Button, + Tooltip, +} from "@mui/material"; +import { useTheme, alpha } from "@mui/material/styles"; +import { PlayArrow, Delete } from "@mui/icons-material"; +import { useTranslation } from "react-i18next"; +import { getRunStatus, getRunStatusColor } from "../../utils/runStatus"; +import { ModelIcon } from "./model/ModelIcon"; +import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; + +/** + * Compact launcher card for a single run — shows just enough to identify + * the model and its state, and opens the full-screen model detail view. + */ +function ModelCardCompact({ + run, + models = [], + onTrain, + onDelete, + onOpen, + isHighlighted = false, +}) { + const theme = useTheme(); + const { t } = useTranslation(["models", "common"]); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + + const model = models.find((m) => m.name === run.model_name); + const modelDisplayName = model?.display_name || run.model_name; + const statusText = getRunStatus(run.status, t); + const canTrain = run.status === 0 || run.status === 3 || run.status === 4; + const isRunning = run.status === 1 || run.status === 2; + + const primaryMetric = (() => { + if (!run.trained_models || run.trained_models.length === 0) return null; + const values = {}; + run.trained_models.forEach((tm) => { + if (!tm.metrics) return; + Object.entries(tm.metrics).forEach(([key, value]) => { + if (!values[key]) values[key] = []; + values[key].push(value); + }); + }); + const keys = Object.keys(values); + if (keys.length === 0) return null; + const key = + run.goal_metric && values[run.goal_metric] ? run.goal_metric : keys[0]; + const avg = values[key].reduce((sum, v) => sum + v, 0) / values[key].length; + return { key, avg }; + })(); + + return ( + + + + + + + + {modelDisplayName} + + + {run.name} + + + + + + + {primaryMetric && ( + + {primaryMetric.key.toUpperCase()}:{" "} + + {primaryMetric.avg.toFixed(4)} + + + )} + + + e.stopPropagation()} + > + {canTrain && ( + + + + )} + + setDeleteConfirmOpen(true)} + > + + + + + + setDeleteConfirmOpen(false)} + onConfirm={() => { + setDeleteConfirmOpen(false); + localStorage.removeItem(`run-${run.id}-results-visible`); + localStorage.removeItem(`run-${run.id}-active-tab`); + onDelete(run); + }} + content={t("models:message.confirmDeleteRun")} + /> + + ); +} + +ModelCardCompact.propTypes = { + run: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + model_name: PropTypes.string, + status: PropTypes.number, + goal_metric: PropTypes.string, + trained_models: PropTypes.array, + }).isRequired, + models: PropTypes.array, + onTrain: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onOpen: PropTypes.func.isRequired, + isHighlighted: PropTypes.bool, +}; + +export default ModelCardCompact; diff --git a/DashAI/front/src/components/models/RunCard.jsx b/DashAI/front/src/components/models/RunCard.jsx index f4f2b18f3..50faa52ee 100644 --- a/DashAI/front/src/components/models/RunCard.jsx +++ b/DashAI/front/src/components/models/RunCard.jsx @@ -38,7 +38,7 @@ import { Close as CloseIcon, } from "@mui/icons-material"; import { useSnackbar } from "notistack"; -import { getRunStatus } from "../../utils/runStatus"; +import { getRunStatus, getRunStatusColor } from "../../utils/runStatus"; import RunResults from "./RunResults"; import FormSchemaWithSelectedModel from "../shared/FormSchemaWithSelectedModel"; import FormSchemaContainer from "../shared/FormSchemaContainer"; @@ -67,6 +67,7 @@ function RunCard({ existingRuns = [], onRefresh, isHighlighted = false, + forceExpanded = false, }) { const theme = useTheme(); const { t } = useTranslation(["models", "common"]); @@ -76,13 +77,15 @@ function RunCard({ const saved = localStorage.getItem(`run-${run.id}-results-visible`); return saved ? JSON.parse(saved) : false; }); + const isResultsVisible = forceExpanded || resultsVisible; useEffect(() => { + if (forceExpanded) return; localStorage.setItem( `run-${run.id}-results-visible`, JSON.stringify(resultsVisible), ); - }, [resultsVisible, run.id]); + }, [resultsVisible, run.id, forceExpanded]); const [isEditing, setIsEditing] = useState(false); const [editedName, setEditedName] = useState(run.name || ""); const [editedParameters, setEditedParameters] = useState( @@ -252,22 +255,6 @@ function RunCard({ const model = models.find((m) => m.name === run.model_name); const modelDisplayName = model?.display_name || run.model_name; - const getStatusColor = (status) => { - switch (status) { - case 0: - return "default"; - case 1: - case 2: - return "info"; - case 3: - return "success"; - case 4: - return "error"; - default: - return "default"; - } - }; - useEffect(() => { if (run.status !== 1 && run.status !== 2) { setAutoExpand(false); @@ -415,7 +402,7 @@ function RunCard({ @@ -429,25 +416,27 @@ function RunCard({ - - setResultsVisible(!resultsVisible)} - color="default" + {!forceExpanded && ( + - {resultsVisible ? ( - - ) : ( - - )} - - + setResultsVisible(!resultsVisible)} + color="default" + > + {resultsVisible ? ( + + ) : ( + + )} + + + )} @@ -494,7 +483,7 @@ function RunCard({ session={session} onRefresh={onOperationsRefresh} explainerRefreshTrigger={explainerRefreshTrigger} - resultsVisible={resultsVisible} + resultsVisible={isResultsVisible} setResultsVisible={setResultsVisible} autoExpand={autoExpand} /> @@ -684,6 +673,7 @@ RunCard.propTypes = { isLastRun: PropTypes.bool, existingRuns: PropTypes.array, onRefresh: PropTypes.func, + forceExpanded: PropTypes.bool, }; export default RunCard; diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index f864778e2..d2375d1e4 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -2,7 +2,6 @@ import React, { useState, useEffect } from "react"; import { Box, Typography, - Stack, Accordion, AccordionSummary, AccordionDetails, @@ -14,7 +13,7 @@ import { Tooltip, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; -import { useParams } from "react-router-dom"; +import { useParams, useNavigate } from "react-router-dom"; import { PlayArrow, TableChart, @@ -23,6 +22,7 @@ import { } from "@mui/icons-material"; import ModelComparisonTable from "./ModelComparisonTable"; import RunCard from "./RunCard"; +import ModelCardCompact from "./ModelCardCompact"; import { getComponents } from "../../api/component"; import ResultsGraphs from "../../pages/results/components/ResultsGraphs"; import RetrainConfirmDialog from "./RetrainConfirmDialog"; @@ -46,6 +46,7 @@ export default function SessionVisualization() { const { t } = useTranslation(["models", "common"]); const sessionTourContext = useTourContext(); const params = useParams(); + const navigate = useNavigate(); const { selectedSession: session, @@ -182,6 +183,10 @@ export default function SessionVisualization() { [runs], ); + const activeRun = params.runId + ? runs.find((r) => String(r.id) === params.runId) + : null; + // Check which metrics are available const hasTrainMetrics = runs.some( (run) => run.train_metrics && Object.keys(run.train_metrics).length > 0, @@ -348,134 +353,244 @@ export default function SessionVisualization() { )} - {/* Sticky Comparison Table */} - setTableCollapsed((v) => !v)} - disableGutters - elevation={1} - sx={{ - flexShrink: 0, - borderBottom: "1px solid", - borderColor: "divider", - borderRadius: "4px", - "&:before": { display: "none" }, - }} - > - - - - } + {/* Sticky Comparison Table — hidden while viewing a single model's + detail; comparing across models doesn't apply there */} + {!params.runId && ( + setTableCollapsed((v) => !v)} + disableGutters + elevation={1} sx={{ - alignItems: "flex-start", - "& .MuiAccordionSummary-content": { my: "8px", mr: 1 }, - "& .MuiAccordionSummary-expandIconWrapper": { mt: "10px" }, + flexShrink: 0, + borderBottom: "1px solid", + borderColor: "divider", + borderRadius: "4px", + "&:before": { display: "none" }, }} > - + + + } sx={{ - display: "flex", - justifyContent: "space-between", - alignItems: "center", - width: "100%", - flexWrap: "wrap", - gap: 1, + alignItems: "flex-start", + "& .MuiAccordionSummary-content": { my: "8px", mr: 1 }, + "& .MuiAccordionSummary-expandIconWrapper": { mt: "10px" }, }} > - - {t("models:label.modelComparison")} - e.stopPropagation()} > - {/* Metric Split Selector — controls both table and graph views */} - {(hasTrainMetrics || - hasValidationMetrics || - hasTestMetrics) && ( - { - if (newValue !== null) setMetricSplit(newValue); - }} - size="small" - > - {hasTrainMetrics && ( - - {t("common:train")} - - )} - {hasValidationMetrics && ( - - {t("common:validation")} - - )} - {hasTestMetrics && ( - - {t("common:test")} - - )} - - )} + + {t("models:label.modelComparison")} + + e.stopPropagation()} + > + {/* Metric Split Selector — controls both table and graph views */} + {(hasTrainMetrics || + hasValidationMetrics || + hasTestMetrics) && ( + { + if (newValue !== null) setMetricSplit(newValue); + }} + size="small" + > + {hasTrainMetrics && ( + + {t("common:train")} + + )} + {hasValidationMetrics && ( + + {t("common:validation")} + + )} + {hasTestMetrics && ( + + {t("common:test")} + + )} + + )} - {/* Toggle between Table and Graphs */} - - - - + {/* Toggle between Table and Graphs */} + + + + - {/* Run All Button */} - {runs.length > 0 && runs.some((r) => r.status === 0) && ( - - )} + {/* Run All Button */} + {runs.length > 0 && runs.some((r) => r.status === 0) && ( + + )} + - - + - + {runs.length === 0 ? ( + + + {t("models:label.noRunsYet")} + + + ) : ( + + {showTable ? ( + + ) : ( + + )} + + )} + + {/* Resize Handle */} + { + isResizing.current = true; + document.body.style.cursor = "row-resize"; + document.body.style.userSelect = "none"; + }} + sx={{ + position: "absolute", + bottom: 0, + left: 0, + right: 0, + height: "5px", + cursor: "row-resize", + bgcolor: "transparent", + transition: "background-color 0.2s ease", + "&:hover": { bgcolor: "primary.main" }, + zIndex: 10, + }} + /> + + + )} + + {!params.runId && } + + {/* Model detail (interim placeholder — full-screen layout lands in + phase 3; for now this just keeps edit/train/results reachable + while a specific run is selected via the URL) */} + {params.runId ? ( + + {activeRun ? ( + + setExplainerRefreshTrigger((prev) => prev + 1) + } + existingRuns={runs} + onRefresh={fetchRuns} + forceExpanded + /> + ) : ( + + + {t("models:label.runNotFound")} + + + )} + + ) : ( + /* Compact model cards */ + {runs.length === 0 ? ( @@ -487,120 +602,59 @@ export default function SessionVisualization() { height: "100%", }} > - + {t("models:label.noRunsYet")} ) : ( - - {showTable ? ( - - ) : ( - - )} + + {sortedRuns.map((run, index) => ( + + + navigate( + `/app/models/sessions/${session.id}/model/${run.id}`, + ) + } + isHighlighted={highlightedRunId === run.id} + /> + + ))} )} - - {/* Resize Handle */} - { - isResizing.current = true; - document.body.style.cursor = "row-resize"; - document.body.style.userSelect = "none"; - }} - sx={{ - position: "absolute", - bottom: 0, - left: 0, - right: 0, - height: "5px", - cursor: "row-resize", - bgcolor: "transparent", - transition: "background-color 0.2s ease", - "&:hover": { bgcolor: "primary.main" }, - zIndex: 10, - }} - /> - - - - - - {/* Scrollable Run Cards */} - - {runs.length === 0 ? ( - - - {t("models:label.noRunsYet")} - - - ) : ( - - {sortedRuns.map((run, index) => ( - - - setExplainerRefreshTrigger((prev) => prev + 1) - } - isLastRun={index === sortedRuns.length - 1} - existingRuns={runs} - onRefresh={fetchRuns} - isHighlighted={highlightedRunId === run.id} - /> - - ))} - - )} - + + )} {{runName}}\" setzt den Durchlauf zurück. Folgendes wird beim erneuten Training gelöscht:", "runDetails": "Durchlaufdetails", + "runNotFound": "Durchlauf nicht gefunden", "runExperimentToSeeMetrics": "Gehen Sie zum<1>Experiments-Tab, um Ihr Experiment auszuführen.", "runFailedNoHyperparameterPlots": "Durchlauf fehlgeschlagen. Keine Hyperparameter-Plots verfügbar.", "runInProgressCannotEdit": "Der Durchlauf wird gerade ausgeführt und kann nicht bearbeitet werden.", diff --git a/DashAI/front/src/utils/i18n/locales/en/models.json b/DashAI/front/src/utils/i18n/locales/en/models.json index 3770033e7..d3a830806 100644 --- a/DashAI/front/src/utils/i18n/locales/en/models.json +++ b/DashAI/front/src/utils/i18n/locales/en/models.json @@ -128,6 +128,7 @@ "showResults": "Show Results", "saveWillDeleteOperationsDetails": "Saving \"<1>{{runName}}\" will reset the run. The following will be deleted when you train again:", "runDetails": "Run Details", + "runNotFound": "Run not found", "runExperimentToSeeMetrics": "Go to<1>experiments tabto run your experiment.", "runFailedNoHyperparameterPlots": "Run Failed. No hyperparameter plots available.", "runInProgressCannotEdit": "Run is currently in progress and cannot be edited.", diff --git a/DashAI/front/src/utils/i18n/locales/es/models.json b/DashAI/front/src/utils/i18n/locales/es/models.json index 7ede341d8..ff62160f5 100644 --- a/DashAI/front/src/utils/i18n/locales/es/models.json +++ b/DashAI/front/src/utils/i18n/locales/es/models.json @@ -130,6 +130,7 @@ "showResults": "Mostrar Resultados", "saveWillDeleteOperationsDetails": "Guardar \"<1>{{runName}}\" restablecerá la ejecución. Lo siguiente se eliminará cuando vuelva a entrenar:", "runDetails": "Detalles de la Ejecución", + "runNotFound": "Ejecución no encontrada", "runExperimentToSeeMetrics": "Ve a la <1>pestaña de experimentos para ejecutar tu experimento.", "runFailedNoHyperparameterPlots": "Ejecución Fallida. No hay gráficos de hiperparámetros disponibles.", "runInProgressCannotEdit": "La ejecución está actualmente en progreso y no puede ser editada.", diff --git a/DashAI/front/src/utils/i18n/locales/pt/models.json b/DashAI/front/src/utils/i18n/locales/pt/models.json index 50f551a17..329a0f44a 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/models.json +++ b/DashAI/front/src/utils/i18n/locales/pt/models.json @@ -130,6 +130,7 @@ "showResults": "Mostrar Resultados", "saveWillDeleteOperationsDetails": "Salvar \"<1>{{runName}}\" redefinirá a execução. O seguinte será excluído ao retreinar:", "runDetails": "Detalhes da Execução", + "runNotFound": "Execução não encontrada", "runExperimentToSeeMetrics": "Vá para a <1>aba de experimentos para executar seu experimento.", "runFailedNoHyperparameterPlots": "Execução Falhou. Não há gráficos de hiperparâmetros disponíveis.", "runInProgressCannotEdit": "A execução está em andamento e não pode ser editada.", diff --git a/DashAI/front/src/utils/i18n/locales/zh/models.json b/DashAI/front/src/utils/i18n/locales/zh/models.json index 666fdfe2f..4bed578b8 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/models.json +++ b/DashAI/front/src/utils/i18n/locales/zh/models.json @@ -128,6 +128,7 @@ "showResults": "显示结果", "saveWillDeleteOperationsDetails": "保存 \"<1>{{runName}}\" 将重置该运行。再次训练时以下内容将被删除:", "runDetails": "运行详情", + "runNotFound": "未找到该运行", "runExperimentToSeeMetrics": "前往<1>实验标签页运行您的实验。", "runFailedNoHyperparameterPlots": "运行失败。无超参数图表可用。", "runInProgressCannotEdit": "运行正在进行中,无法编辑。", diff --git a/DashAI/front/src/utils/runStatus.js b/DashAI/front/src/utils/runStatus.js index 9bb93c0cf..d92b7bfb1 100644 --- a/DashAI/front/src/utils/runStatus.js +++ b/DashAI/front/src/utils/runStatus.js @@ -14,3 +14,19 @@ export function getRunStatus(statusNumber, t) { throw new Error(`Error ${statusNumber} is not a valid status`); } } + +export function getRunStatusColor(statusNumber) { + switch (statusNumber) { + case 0: + return "default"; + case 1: + case 2: + return "info"; + case 3: + return "success"; + case 4: + return "error"; + default: + return "default"; + } +} From 89d28dfec014b7ac5735e831ddee3aeb7f048260 Mon Sep 17 00:00:00 2001 From: Creylay Date: Thu, 9 Jul 2026 12:07:25 -0400 Subject: [PATCH 101/308] Remove root crumb from session breadcrumbs in ModelsBreadcrumbs component --- DashAI/front/src/components/models/ModelsBreadcrumbs.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/DashAI/front/src/components/models/ModelsBreadcrumbs.jsx b/DashAI/front/src/components/models/ModelsBreadcrumbs.jsx index 477ebc988..22ab1498d 100644 --- a/DashAI/front/src/components/models/ModelsBreadcrumbs.jsx +++ b/DashAI/front/src/components/models/ModelsBreadcrumbs.jsx @@ -53,7 +53,6 @@ export default function ModelsBreadcrumbs() { const run = runs.find((r) => r.id === Number(params.runId)); const runName = run?.name ?? `#${params.runId}`; return [ - rootCrumb, { label: sessionName, path: `/app/models/sessions/${params.id}`, From 7730911670aaae8218a5a13e876f414b53a12d00 Mon Sep 17 00:00:00 2001 From: Creylay Date: Thu, 9 Jul 2026 13:00:10 -0400 Subject: [PATCH 102/308] Refactor SessionVisualization component: simplify view toggling, remove unused state and handlers, and enhance layout for model cards and comparison table --- .../models/SessionVisualization.jsx | 392 ++++++------------ 1 file changed, 136 insertions(+), 256 deletions(-) diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index d2375d1e4..2dafeab6b 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -2,24 +2,15 @@ import React, { useState, useEffect } from "react"; import { Box, Typography, - Accordion, - AccordionSummary, - AccordionDetails, Divider, Button, ButtonGroup, ToggleButtonGroup, ToggleButton, - Tooltip, } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { useParams, useNavigate } from "react-router-dom"; -import { - PlayArrow, - TableChart, - BarChart, - ExpandMore, -} from "@mui/icons-material"; +import { PlayArrow, TableChart, BarChart } from "@mui/icons-material"; import ModelComparisonTable from "./ModelComparisonTable"; import RunCard from "./RunCard"; import ModelCardCompact from "./ModelCardCompact"; @@ -36,13 +27,9 @@ export default function SessionVisualization() { const [models, setModels] = useState([]); const [selectedRunId, setSelectedRunId] = useState(null); const [highlightedRunId, setHighlightedRunId] = useState(null); - const [tableHeight, setTableHeight] = useState(280); const [showTable, setShowTable] = useState(true); - const [previousTableHeight, setPreviousTableHeight] = useState(280); const [metricSplit, setMetricSplit] = useState("test"); - const [tableCollapsed, setTableCollapsed] = useState(false); const [explainerRefreshTrigger, setExplainerRefreshTrigger] = useState(0); - const isResizing = React.useRef(false); const { t } = useTranslation(["models", "common"]); const sessionTourContext = useTourContext(); const params = useParams(); @@ -86,21 +73,9 @@ export default function SessionVisualization() { }; }, []); - // Auto-expand when switching to graphs - const handleToggleView = React.useCallback( - (isTable) => { - if (!isTable && showTable) { - // Switching from Table to Graphs - setPreviousTableHeight(tableHeight); - setTableHeight(Math.max(tableHeight, 600)); - } else if (isTable && !showTable) { - // Switching from Graphs to Table - setTableHeight(previousTableHeight); - } - setShowTable(isTable); - }, - [showTable, tableHeight, previousTableHeight], - ); + const handleToggleView = React.useCallback((isTable) => { + setShowTable(isTable); + }, []); const fetchModels = React.useCallback(async () => { try { @@ -208,38 +183,6 @@ export default function SessionVisualization() { } }; - const handleMouseMove = React.useCallback((e) => { - if (isResizing.current) { - const details = document.querySelector("[data-accordion-details]"); - if (details) { - const detailsRect = details.getBoundingClientRect(); - const newHeight = e.clientY - detailsRect.top; - const minHeight = 150; - const maxHeight = window.innerHeight * 0.7; - const clampedHeight = Math.max( - minHeight, - Math.min(maxHeight, newHeight), - ); - setTableHeight(clampedHeight); - } - } - }, []); - - const handleMouseUp = React.useCallback(() => { - isResizing.current = false; - document.body.style.cursor = "default"; - document.body.style.userSelect = "auto"; - }, []); - - React.useEffect(() => { - window.addEventListener("mousemove", handleMouseMove); - window.addEventListener("mouseup", handleMouseUp); - return () => { - window.removeEventListener("mousemove", handleMouseMove); - window.removeEventListener("mouseup", handleMouseUp); - }; - }, [handleMouseMove, handleMouseUp]); - if (!session) { return ( <> @@ -304,7 +247,7 @@ export default function SessionVisualization() { display: "flex", flexDirection: "column", height: "100%", - overflow: "hidden", + overflow: "auto", position: "relative", outline: isDragOver ? `2px dashed ${theme.palette.primary.main}` @@ -353,37 +296,120 @@ export default function SessionVisualization() { )} - {/* Sticky Comparison Table — hidden while viewing a single model's - detail; comparing across models doesn't apply there */} - {!params.runId && ( - setTableCollapsed((v) => !v)} - disableGutters - elevation={1} - sx={{ - flexShrink: 0, - borderBottom: "1px solid", - borderColor: "divider", - borderRadius: "4px", - "&:before": { display: "none" }, - }} - > - + {activeRun ? ( + + setExplainerRefreshTrigger((prev) => prev + 1) + } + existingRuns={runs} + onRefresh={fetchRuns} + forceExpanded + /> + ) : ( + + + {t("models:label.runNotFound")} + + + )} + + ) : ( + <> + {/* Compact model cards — quick access to each model */} + + {runs.length === 0 ? ( + + + {t("models:label.noRunsYet")} + + + ) : ( + - - - } + {sortedRuns.map((run, index) => ( + + + navigate( + `/app/models/sessions/${session.id}/model/${run.id}`, + ) + } + isHighlighted={highlightedRunId === run.id} + /> + + ))} + + )} + + + + + {/* Comparison analysis area — table/graphs across all models */} + @@ -406,7 +433,6 @@ export default function SessionVisualization() { alignItems: "center", flexWrap: "wrap", }} - onClick={(e) => e.stopPropagation()} > {/* Metric Split Selector — controls both table and graph views */} {(hasTrainMetrics || @@ -475,185 +501,39 @@ export default function SessionVisualization() { )} - - {runs.length === 0 ? ( {t("models:label.noRunsYet")} + ) : showTable ? ( + ) : ( - - {showTable ? ( - - ) : ( - - )} - + )} - - {/* Resize Handle */} - { - isResizing.current = true; - document.body.style.cursor = "row-resize"; - document.body.style.userSelect = "none"; - }} - sx={{ - position: "absolute", - bottom: 0, - left: 0, - right: 0, - height: "5px", - cursor: "row-resize", - bgcolor: "transparent", - transition: "background-color 0.2s ease", - "&:hover": { bgcolor: "primary.main" }, - zIndex: 10, - }} - /> - - - )} - - {!params.runId && } - - {/* Model detail (interim placeholder — full-screen layout lands in - phase 3; for now this just keeps edit/train/results reachable - while a specific run is selected via the URL) */} - {params.runId ? ( - - {activeRun ? ( - - setExplainerRefreshTrigger((prev) => prev + 1) - } - existingRuns={runs} - onRefresh={fetchRuns} - forceExpanded - /> - ) : ( - - - {t("models:label.runNotFound")} - - - )} - - ) : ( - /* Compact model cards */ - - {runs.length === 0 ? ( - - - {t("models:label.noRunsYet")} - - - ) : ( - - {sortedRuns.map((run, index) => ( - - - navigate( - `/app/models/sessions/${session.id}/model/${run.id}`, - ) - } - isHighlighted={highlightedRunId === run.id} - /> - - ))} - - )} - + + )} From f3d1ec8a17f25a4aae45556df69b15b3dff08114 Mon Sep 17 00:00:00 2001 From: Creylay Date: Thu, 9 Jul 2026 14:17:19 -0400 Subject: [PATCH 103/308] Refactor ModelCardCompact and ModelComparisonTable components: integrate ScoreRing for score visualization, streamline profile handling, and enhance layout for improved user experience --- .../components/models/ModelCardCompact.jsx | 264 +++++++++++++----- .../models/ModelComparisonTable.jsx | 42 +-- .../models/SessionVisualization.jsx | 15 +- DashAI/front/src/hooks/models/useRunScores.js | 90 ++++++ 4 files changed, 311 insertions(+), 100 deletions(-) create mode 100644 DashAI/front/src/hooks/models/useRunScores.js diff --git a/DashAI/front/src/components/models/ModelCardCompact.jsx b/DashAI/front/src/components/models/ModelCardCompact.jsx index a28976094..a0780be7f 100644 --- a/DashAI/front/src/components/models/ModelCardCompact.jsx +++ b/DashAI/front/src/components/models/ModelCardCompact.jsx @@ -4,18 +4,169 @@ import { Paper, Box, Typography, - Chip, IconButton, - Button, Tooltip, + CircularProgress, } from "@mui/material"; import { useTheme, alpha } from "@mui/material/styles"; -import { PlayArrow, Delete } from "@mui/icons-material"; +import { PlayArrow, Delete, WarningAmber } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; -import { getRunStatus, getRunStatusColor } from "../../utils/runStatus"; +import { getRunStatusColor } from "../../utils/runStatus"; import { ModelIcon } from "./model/ModelIcon"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; +const RING_SIZE = 24; + +function ScoreRing({ run, score, statusMain }) { + const theme = useTheme(); + const { t } = useTranslation(["models", "common"]); + const isRunning = run.status === 1 || run.status === 2; + const isError = run.status === 4; + const isFinished = run.status === 3; + + if (isRunning) { + return ( + + ); + } + + if (isError) { + return ( + + + + + + + ); + } + + if (isFinished && score) { + const rounded = Math.round(score.score); + const tooltipContent = ( + + + {t("models:label.score")}: {score.score.toFixed(1)}/100 + + {score.breakdown?.map( + ({ metric_name, value, normalized_weight }, i) => ( + + {i === 0 ? "=" : "+"} {metric_name} ({value.toFixed(4)}) ×{" "} + {(normalized_weight * 100).toFixed(0)}% + + ), + )} + + ); + + return ( + + + + + + + {rounded} + + + + + ); + } + + // Not started (or finished with no score yet available) + return ( + + + + + – + + + + ); +} + /** * Compact launcher card for a single run — shows just enough to identify * the model and its state, and opens the full-screen model detail view. @@ -23,6 +174,7 @@ import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationMod function ModelCardCompact({ run, models = [], + score, onTrain, onDelete, onOpen, @@ -34,51 +186,29 @@ function ModelCardCompact({ const model = models.find((m) => m.name === run.model_name); const modelDisplayName = model?.display_name || run.model_name; - const statusText = getRunStatus(run.status, t); const canTrain = run.status === 0 || run.status === 3 || run.status === 4; const isRunning = run.status === 1 || run.status === 2; - const primaryMetric = (() => { - if (!run.trained_models || run.trained_models.length === 0) return null; - const values = {}; - run.trained_models.forEach((tm) => { - if (!tm.metrics) return; - Object.entries(tm.metrics).forEach(([key, value]) => { - if (!values[key]) values[key] = []; - values[key].push(value); - }); - }); - const keys = Object.keys(values); - if (keys.length === 0) return null; - const key = - run.goal_metric && values[run.goal_metric] ? run.goal_metric : keys[0]; - const avg = values[key].reduce((sum, v) => sum + v, 0) / values[key].length; - return { key, avg }; - })(); + const statusColorKey = getRunStatusColor(run.status); + const statusMain = + statusColorKey === "default" + ? theme.palette.text.disabled + : theme.palette[statusColorKey].main; return ( + {modelDisplayName} - - {run.name} - + + + + {run.name} + + - - - - {primaryMetric && ( - - {primaryMetric.key.toUpperCase()}:{" "} - - {primaryMetric.avg.toFixed(4)} - - - )} + e.stopPropagation()} > {canTrain && ( @@ -137,14 +265,9 @@ function ModelCardCompact({ run.status === 3 ? t("common:retrain") : t("common:trainVerb") } > - + onTrain(run)}> + + )} @@ -174,6 +297,17 @@ function ModelCardCompact({ ); } +ScoreRing.propTypes = { + run: PropTypes.shape({ + status: PropTypes.number, + }).isRequired, + score: PropTypes.shape({ + score: PropTypes.number, + breakdown: PropTypes.array, + }), + statusMain: PropTypes.string.isRequired, +}; + ModelCardCompact.propTypes = { run: PropTypes.shape({ id: PropTypes.number, @@ -184,6 +318,10 @@ ModelCardCompact.propTypes = { trained_models: PropTypes.array, }).isRequired, models: PropTypes.array, + score: PropTypes.shape({ + score: PropTypes.number, + breakdown: PropTypes.array, + }), onTrain: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, onOpen: PropTypes.func.isRequired, diff --git a/DashAI/front/src/components/models/ModelComparisonTable.jsx b/DashAI/front/src/components/models/ModelComparisonTable.jsx index 7d8eed469..be608c6ce 100644 --- a/DashAI/front/src/components/models/ModelComparisonTable.jsx +++ b/DashAI/front/src/components/models/ModelComparisonTable.jsx @@ -34,11 +34,12 @@ function ModelComparisonTable({ onDelete, onRowClick, metricSplit = "test", + profiles = [], + selectedProfile = null, + onProfileChange, }) { const [models, setModels] = useState([]); const [metrics, setMetrics] = useState([]); - const [profiles, setProfiles] = useState([]); - const [selectedProfile, setSelectedProfile] = useState(null); const [scores, setScores] = useState({}); const [loadingScores, setLoadingScores] = useState(false); const [runs, setRuns] = useState(initialRuns); @@ -84,35 +85,9 @@ function ModelComparisonTable({ fetchMetrics(); }, [i18n.language]); - // ──────────────────────────────────────────────────────────────────────── - // Fetch scoring profiles for this session's task - // ──────────────────────────────────────────────────────────────────────── - - useEffect(() => { - const fetchProfiles = async () => { - try { - const params = {}; - if (session?.task_name) { - params.task_name = session.task_name; - } - const response = await api.get("/v1/scoring/profiles", { params }); - const profilesList = response.data; - setProfiles(profilesList); - - // Keep current profile only if still valid; otherwise select first - setSelectedProfile((prevProfile) => { - if (profilesList.length === 0) { - return null; - } - const profileExists = profilesList.some((p) => p.id === prevProfile); - return profileExists ? prevProfile : profilesList[0].id; - }); - } catch (error) { - console.error("Error fetching scoring profiles:", error); - } - }; - fetchProfiles(); - }, [session?.task_name]); + // Scoring profiles and the selected profile are owned by the parent + // (shared with the compact model cards via useRunScores) and passed in + // as props, so both views always score against the same profile. // Stable string that changes only when a run's status changes. // Used as a dep so the score fetch re-triggers after training completes @@ -565,7 +540,7 @@ function ModelComparisonTable({ setCurrentPlot(event.target.value)} + value={currentArtifact} + onChange={(event) => setCurrentArtifact(event.target.value)} label="class" autoWidth > - {explainersPlots.map((_, i) => ( + {artifacts.map((artifact, i) => ( - {t("explainers:label.instanceNumber", { number: i + 1 })} + {artifact.title ?? + t("explainers:label.instanceNumber", { number: i + 1 })} ))} )} {!loading && explainer.status === 3 ? ( - explainersPlots.length > 0 && explainersPlots[currentPlot] ? ( - + artifacts.length > 0 && artifacts[currentArtifact] ? ( + ) : ( {t("explainers:error.noData")} ) diff --git a/DashAI/front/src/components/explorations/explorers/DetailTabs/Results.jsx b/DashAI/front/src/components/explorations/explorers/DetailTabs/Results.jsx index dd71cbd7d..46b20fef2 100644 --- a/DashAI/front/src/components/explorations/explorers/DetailTabs/Results.jsx +++ b/DashAI/front/src/components/explorations/explorers/DetailTabs/Results.jsx @@ -1,147 +1,19 @@ import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; -import { Box, CircularProgress, Tooltip, Typography } from "@mui/material"; +import { Box, CircularProgress } from "@mui/material"; import { getExplorerResults } from "../../../../api/explorer"; +import { + artifactToVisualizerData, + visualizersKeys, +} from "../../../../utils/artifactVisualizerData"; import { TabularVisualizer, PlotlyJsonVisualizer, ImageVisualizer, } from "../../Visualizations"; -/** - * NullCell component to render null values in the tabular visualizer - * @param {Object} props - */ -function NullCell({}) { - const [hover, setHover] = useState(false); - return ( - setHover(true)} - onMouseLeave={() => setHover(false)} - > - - {hover ? "None" : "-"} - - - ); -} - -const visualizers = { - tabular: TabularVisualizer, - plotly_json: PlotlyJsonVisualizer, - image_base64: ImageVisualizer, - image_url: ImageVisualizer, -}; -const visualizersKeys = { - tabular: "tabular", - plotly_json: "plotly_json", - image_base64: "image_base64", - image_url: "image_url", -}; - -const ORIENTATIONS = { - dict: "dict", - records: "records", -}; - -/** - * Get the data from the orientation given. This function is used to transform the data - * from the explorer results to the format required by the tabular visualizer. - * @param {Object} data The data from the explorer results - * @param {String} orientation The orientation of the data - */ -const getDataFromOrientation = (data, orientation) => { - let res = { - columns: [], - rows: [], - }; - - if (orientation === ORIENTATIONS.records) { - throw new Error(`orientation ${orientation} not supported`); - } - - if (orientation === ORIENTATIONS.dict) { - // ‘dict’ (default) : dict like {column -> {index -> value}} - // Get the columns - const columns = Object.keys(data); - res.columns = [ - { - field: "id", - headerName: "Index", - renderCell: (params) => { - return ( - - {params.value} - - ); - }, - }, - ...columns.map((column) => { - return { - field: column, - headerName: column, - renderCell: (params) => { - if (params.value === null) { - return ; - } else if (typeof params.value === "object") { - const tooltip = JSON.stringify(params.value); - return ( - - - {JSON.stringify(params.value)} - - - ); - } else if ( - params.value !== "" && - !isNaN(params.value) && - !Number.isInteger(params.value) - ) { - const tooltip = params.value; - const display = parseFloat(params.value).toFixed(2); - return ( - - {display} - - ); - } - const tooltip = params.value; - return ( - - {params.value} - - ); - }, - }; - }), - ]; - - // Get the rows - const rows = []; - const indexes = Object.keys(data[columns[0]]); - indexes.forEach((index) => { - const row = { - id: index, - }; - columns.forEach((column) => { - row[column] = data[column][index]; - }); - rows.push(row); - }); - res.rows = rows; - } - - return res; -}; - /** * Results component to render the results of the exploration * @param {Object} props @@ -157,45 +29,15 @@ function Results({ id, updateFlag = false, setUpdateFlag = () => {} }) { const fetchExplorerResults = async () => { setLoading(true); getExplorerResults(id) - .then((results) => { - if (!results?.type) { - throw new Error("No result type specified in the response"); - } - - // Check if there is an appropriate visualizer - if (!Object.keys(visualizers).includes(results.type)) { - throw new Error(`No visualizer found for type: ${results.type}`); - } - setDataType(results.type); - - if (results.type === visualizersKeys.tabular) { - // Get the data from the orientation - const data = getDataFromOrientation( - results.data, - results.config.orient, - ); - setData({ - columns: data.columns.map((column) => { - return { - ...column, - // flex: 1, - }; - }), - rows: data.rows, - }); + .then((artifacts) => { + const [artifact] = artifacts ?? []; + if (!artifact?.type) { + throw new Error("No artifacts in the response"); } - if (results.type === visualizersKeys.plotly_json) { - setData(JSON.parse(results.data)); - } - - if (results.type === visualizersKeys.image_base64) { - setData(results.data); - } - - if (results.type === visualizersKeys.image_url) { - setData(results.data); - } + const visualizerData = artifactToVisualizerData(artifact); + setDataType(visualizerData.dataType); + setData(visualizerData.data); }) .catch((error) => { console.error(error); @@ -246,13 +88,11 @@ function Results({ id, updateFlag = false, setUpdateFlag = () => {} }) { )} - {!loading && dataType === visualizersKeys.image_base64 && ( - - )} - - {!loading && dataType === visualizersKeys.image_url && ( - - )} + {!loading && + (dataType === visualizersKeys.image_base64 || + dataType === visualizersKeys.image_url) && ( + + )} ); } diff --git a/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx b/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx index 6a7d9902d..39d047781 100644 --- a/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx +++ b/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx @@ -1,55 +1,9 @@ -import { Box, Typography, Tooltip } from "@mui/material"; import { useState, useEffect } from "react"; import { getExplorerResults } from "../../../api/explorer"; -import ImageVisualizer from ".//visualizations/ImageVisualizer"; -import PlotlyJsonVisualizer from "./visualizations/PlotlyJsonVisualizer"; -import TabularVisualizer from "./visualizations/TabularVisualizer"; -import { getExplorerStatus } from "../../../utils/explorerStatus"; -import { useTranslation } from "react-i18next"; - -/** - * NullCell component to render null values in the tabular visualizer - * @param {Object} props - */ -function NullCell({}) { - const [hover, setHover] = useState(false); - const { t } = useTranslation(["common"]); - return ( - setHover(true)} - onMouseLeave={() => setHover(false)} - > - - {hover ? t("common:none") : "-"} - - - ); -} - -const visualizers = { - tabular: TabularVisualizer, - plotly_json: PlotlyJsonVisualizer, - image_base64: ImageVisualizer, - image_url: ImageVisualizer, -}; - -const visualizersKeys = { - tabular: "tabular", - plotly_json: "plotly_json", - image_base64: "image_base64", - image_url: "image_url", -}; - -const ORIENTATIONS = { - dict: "dict", - records: "records", -}; +import { + artifactToVisualizerData, + visualizersKeys, +} from "../../../utils/artifactVisualizerData"; /** * Hook to manage explorer results data @@ -69,36 +23,15 @@ export function useExplorerResults(explorer) { setLoading(true); try { - const results = await getExplorerResults(explorer.id); - if (!results?.type) { - throw new Error("No result type specified in the response"); + const artifacts = await getExplorerResults(explorer.id); + const [artifact] = artifacts ?? []; + if (!artifact?.type) { + throw new Error("No artifacts in the response"); } - // Check if there is an appropriate visualizer - if (!Object.keys(visualizers).includes(results.type)) { - throw new Error(`No visualizer found for type: ${results.type}`); - } - - setDataType(results.type); - - // Process data based on type - if (results.type === visualizersKeys.tabular) { - const processedData = getDataFromOrientation( - results.data, - results.config.orient, - ); - setData({ - columns: processedData.columns, - rows: processedData.rows, - }); - } else if (results.type === visualizersKeys.plotly_json) { - setData(JSON.parse(results.data)); - } else if ( - results.type === visualizersKeys.image_base64 || - results.type === visualizersKeys.image_url - ) { - setData(results.data); - } + const visualizerData = artifactToVisualizerData(artifact); + setDataType(visualizerData.dataType); + setData(visualizerData.data); } catch (error) { console.error("Error fetching explorer results:", error); throw error; @@ -121,94 +54,4 @@ export function useExplorerResults(explorer) { }; } -/** - * Get the data from the orientation given. This function is used to transform the data - * from the explorer results to the format required by the tabular visualizer. - * @param {Object} data The data from the explorer results - * @param {String} orientation The orientation of the data - */ -const getDataFromOrientation = (data, orientation) => { - let res = { - columns: [], - rows: [], - }; - - if (orientation === ORIENTATIONS.records) { - throw new Error(`orientation ${orientation} not supported`); - } - - if (orientation === ORIENTATIONS.dict) { - // ‘dict’ (default) : dict like {column -> {index -> value}} - // Get the columns - const columns = Object.keys(data); - res.columns = [ - { - field: "id", - headerName: "Index", - renderCell: (params) => { - return ( - - {params.value} - - ); - }, - }, - ...columns.map((column) => { - return { - field: column, - headerName: column, - renderCell: (params) => { - if (params.value === null) { - return ; - } else if (typeof params.value === "object") { - const tooltip = JSON.stringify(params.value); - return ( - - - {JSON.stringify(params.value)} - - - ); - } else if ( - params.value !== "" && - !isNaN(params.value) && - !Number.isInteger(params.value) - ) { - const tooltip = params.value; - const display = parseFloat(params.value).toFixed(2); - return ( - - {display} - - ); - } - const tooltip = params.value; - return ( - - {params.value} - - ); - }, - }; - }), - ]; - - // Get the rows - const rows = []; - const indexes = Object.keys(data[columns[0]]); - indexes.forEach((index) => { - const row = { - id: index, - }; - columns.forEach((column) => { - row[column] = data[column][index]; - }); - rows.push(row); - }); - res.rows = rows; - } - - return res; -}; - export { visualizersKeys }; diff --git a/DashAI/front/src/components/pipelines/results/ResultsExploration.jsx b/DashAI/front/src/components/pipelines/results/ResultsExploration.jsx index 4a8b59fd6..381d10c17 100644 --- a/DashAI/front/src/components/pipelines/results/ResultsExploration.jsx +++ b/DashAI/front/src/components/pipelines/results/ResultsExploration.jsx @@ -4,120 +4,12 @@ import { PlotlyJsonVisualizer, ImageVisualizer, } from "../../explorations/Visualizations"; -import { Box, Typography, Tooltip } from "@mui/material"; +import { Box, Typography } from "@mui/material"; import { getExplorationResults } from "../../../api/pipeline"; - -function NullCell() { - const [hover, setHover] = useState(false); - return ( - setHover(true)} - onMouseLeave={() => setHover(false)} - > - - {hover ? "None" : "-"} - - - ); -} - -const visualizers = { - tabular: TabularVisualizer, - plotly_json: PlotlyJsonVisualizer, - image_base64: ImageVisualizer, - image_url: ImageVisualizer, -}; - -const visualizersKeys = { - tabular: "tabular", - plotly_json: "plotly_json", - image_base64: "image_base64", - image_url: "image_url", -}; - -const ORIENTATIONS = { - dict: "dict", - records: "records", -}; - -const getDataFromOrientation = (data, orientation) => { - let res = { - columns: [], - rows: [], - }; - - if (orientation === ORIENTATIONS.records) { - throw new Error(`orientation ${orientation} not supported`); - } - - if (orientation === ORIENTATIONS.dict) { - const columns = Object.keys(data); - res.columns = [ - { - field: "id", - headerName: "Index", - renderCell: (params) => ( - - {params.value} - - ), - }, - ...columns.map((column) => ({ - field: column, - headerName: column, - renderCell: (params) => { - if (params.value === null) return ; - if (typeof params.value === "object") { - const tooltip = JSON.stringify(params.value); - return ( - - - {JSON.stringify(params.value)} - - - ); - } - if ( - params.value !== "" && - !isNaN(params.value) && - !Number.isInteger(params.value) - ) { - const display = parseFloat(params.value).toFixed(2); - return ( - - {display} - - ); - } - return ( - - {params.value} - - ); - }, - })), - ]; - - const rows = []; - const indexes = Object.keys(data[columns[0]]); - indexes.forEach((index) => { - const row = { id: index }; - columns.forEach((column) => { - row[column] = data[column][index]; - }); - rows.push(row); - }); - res.rows = rows; - } - - return res; -}; +import { + artifactToVisualizerData, + visualizersKeys, +} from "../../../utils/artifactVisualizerData"; function Results({ pipelineId }) { const [explorationResults, setExplorationResults] = useState(null); @@ -137,36 +29,31 @@ function Results({ pipelineId }) { } }, [pipelineId]); - const renderVisualizer = (type, dataObj) => { - if (!Object.keys(visualizers).includes(type)) { - console.error(`No visualizer found for type: ${type}`); + const renderArtifact = (artifact, key) => { + let visualizerData; + try { + visualizerData = artifactToVisualizerData(artifact); + } catch (error) { + console.error(error); return null; } + const { dataType, data } = visualizerData; - if (type === visualizersKeys.tabular) { - const data = getDataFromOrientation(dataObj.data, dataObj.config.orient); + if (dataType === visualizersKeys.tabular) { return ( - + ); } - if (type === visualizersKeys.plotly_json) { - return ( - - ); - } - - if (type === visualizersKeys.image_base64) { - return ( - - ); + if (dataType === visualizersKeys.plotly_json) { + return ; } - if (type === visualizersKeys.image_url) { - return ; + if ( + dataType === visualizersKeys.image_base64 || + dataType === visualizersKeys.image_url + ) { + return ; } return null; @@ -193,7 +80,9 @@ function Results({ pipelineId }) { {i}: {result.exploration_type} {result.name ? ` | ${result.name}` : ""} - {renderVisualizer(result.results.type, result.results)} + {(result.results ?? []).map((artifact, artifactIndex) => + renderArtifact(artifact, `${explorationName}-${artifactIndex}`), + )} ), )} diff --git a/DashAI/front/src/components/shared/ArtifactRenderer.jsx b/DashAI/front/src/components/shared/ArtifactRenderer.jsx new file mode 100644 index 000000000..3448d91dd --- /dev/null +++ b/DashAI/front/src/components/shared/ArtifactRenderer.jsx @@ -0,0 +1,148 @@ +import React, { useMemo } from "react"; +import { + Box, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import { useTheme, alpha } from "@mui/material/styles"; +import Plot from "react-plotly.js"; +import PropTypes from "prop-types"; +import { useTranslation } from "react-i18next"; + +import { applyThemeToLayout } from "../../utils/plotlyTheme"; + +/** + * Renders a single typed artifact ({type, payload, title}) returned by the + * backend (explainer plots, explorer results). Supported types: "plotly" + * (payload: plotly JSON string), "table" (payload: {columns, rows, + * highlight}), "image" (payload: {data, mime}) and "text" (payload: string). + * Unknown types fall back to preformatted text so nothing is silently lost. + */ +export default function ArtifactRenderer({ artifact }) { + const theme = useTheme(); + const { t } = useTranslation(["common"]); + + const parsedFigure = useMemo(() => { + if (artifact.type !== "plotly") return null; + try { + return typeof artifact.payload === "string" + ? JSON.parse(artifact.payload) + : artifact.payload; + } catch (error) { + console.error("Invalid plotly artifact payload", error); + return null; + } + }, [artifact]); + + const themedLayout = useMemo(() => { + if (!parsedFigure) return {}; + return applyThemeToLayout(parsedFigure.layout, theme); + }, [parsedFigure, theme]); + + const highlightedCells = useMemo(() => { + if (artifact.type !== "table") return new Set(); + const cells = artifact.payload?.highlight ?? []; + return new Set(cells.map((cell) => `${cell.row}-${cell.column}`)); + }, [artifact]); + + const renderContent = () => { + switch (artifact.type) { + case "plotly": + if (!parsedFigure) return null; + return ( + + ); + case "table": { + const { columns = [], rows = [] } = artifact.payload ?? {}; + return ( + + + + + {columns.map((column) => ( + {column} + ))} + + + + {rows.map((row, rowIndex) => ( + + {row.map((value, columnIndex) => ( + + {value === null ? "-" : String(value)} + + ))} + + ))} + +
+
+ ); + } + case "image": { + const { data = "", mime = "image/png" } = artifact.payload ?? {}; + return ( + + ); + } + case "text": + default: + return ( + + {typeof artifact.payload === "string" + ? artifact.payload + : JSON.stringify(artifact.payload, null, 2)} + + ); + } + }; + + return ( + + {artifact.title && ( + + {artifact.title} + + )} + {renderContent()} + + ); +} + +ArtifactRenderer.propTypes = { + artifact: PropTypes.shape({ + type: PropTypes.string.isRequired, + payload: PropTypes.any, + title: PropTypes.string, + }).isRequired, +}; diff --git a/DashAI/front/src/types/artifact.ts b/DashAI/front/src/types/artifact.ts new file mode 100644 index 000000000..aa1a1e86b --- /dev/null +++ b/DashAI/front/src/types/artifact.ts @@ -0,0 +1,13 @@ +/** + * Typed render artifact returned by explainer plot and explorer results + * endpoints. Payload shape depends on `type`: + * - "plotly": JSON string of a plotly figure. + * - "table": { columns: string[], rows: unknown[][], highlight: {row, column}[] }. + * - "text": plain string. + * - "image": { data: base64 string, mime: string }. + */ +export interface IArtifact { + type: string; + payload: unknown; + title: string | null; +} diff --git a/DashAI/front/src/types/explorer.ts b/DashAI/front/src/types/explorer.ts index 21ec81b3e..184c3eb6b 100644 --- a/DashAI/front/src/types/explorer.ts +++ b/DashAI/front/src/types/explorer.ts @@ -21,9 +21,3 @@ export enum ExplorerStatus { FINISHED, ERROR, } - -export interface IExplorerResults { - type: string; - data: object; - config: object; -} diff --git a/DashAI/front/src/utils/artifactVisualizerData.jsx b/DashAI/front/src/utils/artifactVisualizerData.jsx new file mode 100644 index 000000000..11c2d7918 --- /dev/null +++ b/DashAI/front/src/utils/artifactVisualizerData.jsx @@ -0,0 +1,124 @@ +import React, { useState } from "react"; +import PropTypes from "prop-types"; +import { Box, Tooltip, Typography } from "@mui/material"; +import { useTranslation } from "react-i18next"; + +/** + * Keys of the visualizers used to render explorer results. + */ +export const visualizersKeys = { + tabular: "tabular", + plotly_json: "plotly_json", + image_base64: "image_base64", + image_url: "image_url", +}; + +/** + * NullCell component to render null values in the tabular visualizer + */ +function NullCell() { + const [hover, setHover] = useState(false); + const { t } = useTranslation(["common"]); + return ( + setHover(true)} + onMouseLeave={() => setHover(false)} + > + + {hover ? t("common:none") : "-"} + + + ); +} + +NullCell.propTypes = {}; + +const buildTableColumn = (field, headerName) => ({ + field, + headerName, + renderCell: (params) => { + if (params.value === null) { + return ; + } else if (typeof params.value === "object") { + const tooltip = JSON.stringify(params.value); + return ( + + + {JSON.stringify(params.value)} + + + ); + } else if ( + params.value !== "" && + !isNaN(params.value) && + !Number.isInteger(params.value) + ) { + const tooltip = params.value; + const display = parseFloat(params.value).toFixed(2); + return ( + + {display} + + ); + } + const tooltip = params.value; + return ( + + {params.value} + + ); + }, +}); + +/** + * Convert a typed artifact ({type, payload, title}) returned by the backend + * into the {dataType, data} pair consumed by the explorer visualizers + * (TabularVisualizer, PlotlyJsonVisualizer, ImageVisualizer). + * @param {Object} artifact The artifact to convert + * @returns {{dataType: string, data: any}} + */ +export function artifactToVisualizerData(artifact) { + switch (artifact?.type) { + case "plotly": { + const figure = + typeof artifact.payload === "string" + ? JSON.parse(artifact.payload) + : artifact.payload; + return { dataType: visualizersKeys.plotly_json, data: figure }; + } + case "table": { + const { columns = [], rows = [] } = artifact.payload ?? {}; + const gridColumns = columns.map((column) => + buildTableColumn(column, column === "index" ? "Index" : column), + ); + const gridRows = rows.map((row, rowIndex) => { + const gridRow = { id: rowIndex }; + columns.forEach((column, columnIndex) => { + gridRow[column] = row[columnIndex]; + }); + return gridRow; + }); + return { + dataType: visualizersKeys.tabular, + data: { columns: gridColumns, rows: gridRows }, + }; + } + case "image": { + const { data = "", mime = "image/png" } = artifact.payload ?? {}; + return { + dataType: visualizersKeys.image_url, + data: `data:${mime};base64,${data}`, + }; + } + default: + throw new Error( + `No visualizer found for artifact type: ${artifact?.type}`, + ); + } +} diff --git a/DashAI/front/src/utils/i18n/locales/de/explainers.json b/DashAI/front/src/utils/i18n/locales/de/explainers.json index cd792d738..36b1857e5 100644 --- a/DashAI/front/src/utils/i18n/locales/de/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/de/explainers.json @@ -54,6 +54,7 @@ "selectDatasetToExplain": "Datensatz mit zu erklärenden Instanzen auswählen", "selectExplainer": "Name und Erklärungsmodell festlegen", "selectExplainerAndName": "Erklärungsmodell auswählen und Namen eingeben", + "selectInstance": "Instanz auswählen", "explainerInProgress": "Erklärungsmodell wird verarbeitet...", "splitSelectionSummary": "Prozentsatz: {{percentage}}% | Ausgewählte Zeilen: {{rowsSelected}} / {{totalRows}}", "searchExplainers": "Erklärungsmodelle suchen..." diff --git a/DashAI/front/src/utils/i18n/locales/en/explainers.json b/DashAI/front/src/utils/i18n/locales/en/explainers.json index d937ff713..cfc67f07d 100644 --- a/DashAI/front/src/utils/i18n/locales/en/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/en/explainers.json @@ -54,6 +54,7 @@ "selectDatasetToExplain": "Select a dataset with instances to explain", "selectExplainer": "Set name and explainer", "selectExplainerAndName": "Select a explainer and enter a name", + "selectInstance": "Select an instance", "explainerInProgress": "Explainer in progress...", "splitSelectionSummary": "Percentage: {{percentage}}% | Rows selected: {{rowsSelected}} / {{totalRows}}", "searchExplainers": "Search explainers..." diff --git a/DashAI/front/src/utils/i18n/locales/es/explainers.json b/DashAI/front/src/utils/i18n/locales/es/explainers.json index 686a146a8..f093d5371 100644 --- a/DashAI/front/src/utils/i18n/locales/es/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/es/explainers.json @@ -54,6 +54,7 @@ "selectDatasetToExplain": "Seleccione un dataset con instancias para explicar", "selectExplainer": "Establecer nombre y explicador", "selectExplainerAndName": "Seleccione un explicador e ingrese un nombre", + "selectInstance": "Selecciona una instancia", "explainerInProgress": "Explicador en progreso...", "splitSelectionSummary": "Porcentaje: {{percentage}}% | Filas seleccionadas: {{rowsSelected}} / {{totalRows}}", "searchExplainers": "Buscar explicadores..." diff --git a/DashAI/front/src/utils/i18n/locales/pt/explainers.json b/DashAI/front/src/utils/i18n/locales/pt/explainers.json index e2e0f82fa..d647b4602 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/pt/explainers.json @@ -54,6 +54,7 @@ "selectDatasetToExplain": "Selecione um conjunto de dados com instâncias para explicar", "selectExplainer": "Definir nome e explicador", "selectExplainerAndName": "Selecione um explicador e insira um nome", + "selectInstance": "Selecione uma instância", "explainerInProgress": "Explicador em andamento...", "splitSelectionSummary": "Percentual: {{percentage}}% | Linhas selecionadas: {{rowsSelected}} / {{totalRows}}", "searchExplainers": "Pesquisar explicadores..." diff --git a/DashAI/front/src/utils/i18n/locales/zh/explainers.json b/DashAI/front/src/utils/i18n/locales/zh/explainers.json index b5977b715..7ddd5dd0d 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/zh/explainers.json @@ -54,6 +54,7 @@ "selectDatasetToExplain": "选择包含待解释实例的数据集", "selectExplainer": "设置名称和解释器", "selectExplainerAndName": "选择解释器并输入名称", + "selectInstance": "选择一个实例", "explainerInProgress": "解释器运行中...", "splitSelectionSummary": "百分比:{{percentage}}% | 已选行数:{{rowsSelected}} / {{totalRows}}", "searchExplainers": "搜索解释器..." From c069af7174e2d61b6003b670db60ac2b151062b6 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Mon, 13 Jul 2026 12:05:25 -0400 Subject: [PATCH 135/308] feat: offer model specific explainers in creation dialog --- .../explainers/InlineExplainerCreator.jsx | 4 ++- .../explainers/NewGlobalExplainerModal.jsx | 3 ++- .../explainers/NewLocalExplainerModal.jsx | 3 ++- .../explainers/SetNameAndExplainerStep.jsx | 25 ++++++++++++++++--- .../src/components/models/RunResults.jsx | 2 ++ 5 files changed, 31 insertions(+), 6 deletions(-) diff --git a/DashAI/front/src/components/explainers/InlineExplainerCreator.jsx b/DashAI/front/src/components/explainers/InlineExplainerCreator.jsx index 753d80897..4b338a261 100644 --- a/DashAI/front/src/components/explainers/InlineExplainerCreator.jsx +++ b/DashAI/front/src/components/explainers/InlineExplainerCreator.jsx @@ -55,7 +55,7 @@ export default function InlineExplainerCreator({ const { t } = useTranslation(["explainers", "common"]); const formSubmitRef = useRef(null); - const { runId, taskName } = explainerConfig; + const { runId, taskName, modelName } = explainerConfig; const isLocal = scope === "local"; const defaultNewExplainer = useMemo( @@ -309,6 +309,7 @@ export default function InlineExplainerCreator({ setNextEnabled={setNextEnabled} scope={isLocal ? "Local" : "Global"} taskName={taskName} + modelName={modelName} existingExplainers={existingExplainers} /> )} @@ -368,6 +369,7 @@ InlineExplainerCreator.propTypes = { explainerConfig: PropTypes.shape({ runId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), taskName: PropTypes.string, + modelName: PropTypes.string, }).isRequired, onCreated: PropTypes.func, onCancel: PropTypes.func.isRequired, diff --git a/DashAI/front/src/components/explainers/NewGlobalExplainerModal.jsx b/DashAI/front/src/components/explainers/NewGlobalExplainerModal.jsx index 47f703038..36791161b 100644 --- a/DashAI/front/src/components/explainers/NewGlobalExplainerModal.jsx +++ b/DashAI/front/src/components/explainers/NewGlobalExplainerModal.jsx @@ -78,7 +78,7 @@ export default function NewGlobalExplainerModal({ const { enqueueSnackbar } = useSnackbar(); - const { runId, taskName } = explainerConfig; + const { runId, taskName, modelName } = explainerConfig; const defaultNewGlobalExpl = { name: "", @@ -331,6 +331,7 @@ export default function NewGlobalExplainerModal({ setNextEnabled={setNextEnabled} scope={"Global"} taskName={taskName} + modelName={modelName} existingExplainers={existingGlobalExplainers} /> )} diff --git a/DashAI/front/src/components/explainers/NewLocalExplainerModal.jsx b/DashAI/front/src/components/explainers/NewLocalExplainerModal.jsx index cf13aefe1..58c337208 100644 --- a/DashAI/front/src/components/explainers/NewLocalExplainerModal.jsx +++ b/DashAI/front/src/components/explainers/NewLocalExplainerModal.jsx @@ -80,7 +80,7 @@ export default function NewLocalExplainerModal({ const { enqueueSnackbar } = useSnackbar(); - const { runId, taskName } = explainerConfig; + const { runId, taskName, modelName } = explainerConfig; const defaultNewLocalExpl = { name: "", @@ -337,6 +337,7 @@ export default function NewLocalExplainerModal({ setNextEnabled={setNextEnabled} scope={"Local"} taskName={taskName} + modelName={modelName} existingExplainers={existingLocalExplainers} /> )} diff --git a/DashAI/front/src/components/explainers/SetNameAndExplainerStep.jsx b/DashAI/front/src/components/explainers/SetNameAndExplainerStep.jsx index fedf5942d..697a40530 100644 --- a/DashAI/front/src/components/explainers/SetNameAndExplainerStep.jsx +++ b/DashAI/front/src/components/explainers/SetNameAndExplainerStep.jsx @@ -13,6 +13,7 @@ function SetNameAndExplainerStep({ setNextEnabled, scope, taskName, + modelName, existingExplainers = [], }) { const { enqueueSnackbar } = useSnackbar(); @@ -31,9 +32,26 @@ function SetNameAndExplainerStep({ const getExplainers = async () => { setLoading(true); try { - const result = await getComponentsRequest({ - selectTypes: [`${scope}Explainer`], - relatedComponent: taskName, + // Explainers related to the task are model agnostic (usable by any + // model of the task); explainers related to the run's model are + // model specific ones the model declares in COMPATIBLE_COMPONENTS. + const [taskRelated, modelRelated] = await Promise.all([ + getComponentsRequest({ + selectTypes: [`${scope}Explainer`], + relatedComponent: taskName, + }), + modelName + ? getComponentsRequest({ + selectTypes: [`${scope}Explainer`], + relatedComponent: modelName, + }) + : Promise.resolve([]), + ]); + const seen = new Set(); + const result = [...taskRelated, ...modelRelated].filter((obj) => { + if (seen.has(obj.name)) return false; + seen.add(obj.name); + return true; }); setExplainers(result.filter((obj) => !obj.name.startsWith("Fit"))); } catch (error) { @@ -165,6 +183,7 @@ SetNameAndExplainerStep.propTypes = { setNextEnabled: PropTypes.func.isRequired, scope: PropTypes.string.isRequired, taskName: PropTypes.string, + modelName: PropTypes.string, existingExplainers: PropTypes.array, }; diff --git a/DashAI/front/src/components/models/RunResults.jsx b/DashAI/front/src/components/models/RunResults.jsx index 7b34cf45d..b9ade14f2 100644 --- a/DashAI/front/src/components/models/RunResults.jsx +++ b/DashAI/front/src/components/models/RunResults.jsx @@ -426,6 +426,7 @@ export default function RunResults({ explainerConfig={{ runId: run.id, taskName: session?.task_name, + modelName: run.model_name, }} onCreated={handleExplainerCreated} onCancel={() => setGlobalCreatorOpen(false)} @@ -436,6 +437,7 @@ export default function RunResults({ explainerConfig={{ runId: run.id, taskName: session?.task_name, + modelName: run.model_name, }} onCreated={handleExplainerCreated} onCancel={() => setLocalCreatorOpen(false)} From 359fcef4f60df2d45ab90a33905fc2c778ff7b3d Mon Sep 17 00:00:00 2001 From: Irozuku Date: Mon, 13 Jul 2026 12:21:09 -0400 Subject: [PATCH 136/308] feat: merge compatible components across class hierarchy --- .../registry/component_registry.py | 49 ++++++++++++++----- tests/back/registries/test_registry.py | 45 +++++++++++++++++ 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/DashAI/back/dependencies/registry/component_registry.py b/DashAI/back/dependencies/registry/component_registry.py index d99145aaf..98da941f7 100644 --- a/DashAI/back/dependencies/registry/component_registry.py +++ b/DashAI/back/dependencies/registry/component_registry.py @@ -167,6 +167,33 @@ def _get_base_type(self, new_component: type) -> str: return base_classes_cantidates[0].TYPE + @staticmethod + @beartype + def _collect_compatible_components(component: type) -> List[str]: + """Collect the union of ``COMPATIBLE_COMPONENTS`` declared along the MRO. + + Each class in the component's MRO contributes only the entries it + declares itself, so mixins and base classes compose instead of the + first declaration shadowing the rest (e.g. a task mixin declaring the + task plus a model base class declaring its supported explainers). + + Parameters + ---------- + component : type + The component class to inspect. + + Returns + ------- + List[str] + Deduplicated compatible component names in MRO order. + """ + compatible_components: List[str] = [] + for klass in component.__mro__: + for entry in vars(klass).get("COMPATIBLE_COMPONENTS", []): + if entry not in compatible_components: + compatible_components.append(entry) + return compatible_components + @beartype def register_component(self, new_component: Type) -> None: """Register a component within the registry. @@ -225,12 +252,11 @@ def register_component(self, new_component: Type) -> None: else: self._registry[base_type][new_component.__name__] = new_register_component - if hasattr(new_component, "COMPATIBLE_COMPONENTS"): - for compatible_component in new_component.COMPATIBLE_COMPONENTS: - self._relationship_manager.add_relationship( - new_component.__name__, - compatible_component, - ) + for compatible_component in self._collect_compatible_components(new_component): + self._relationship_manager.add_relationship( + new_component.__name__, + compatible_component, + ) @beartype def unregister_component(self, component: Type) -> None: @@ -254,12 +280,11 @@ def unregister_component(self, component: Type) -> None: f"in the registry. Exception: {e}" ) from e - if hasattr(component, "COMPATIBLE_COMPONENTS"): - for compatible_component in component.COMPATIBLE_COMPONENTS: - self._relationship_manager.remove_relationship( - component.__name__, - compatible_component, - ) + for compatible_component in self._collect_compatible_components(component): + self._relationship_manager.remove_relationship( + component.__name__, + compatible_component, + ) @beartype def get_components_by_types( diff --git a/tests/back/registries/test_registry.py b/tests/back/registries/test_registry.py index c15b17238..51e7bae3e 100644 --- a/tests/back/registries/test_registry.py +++ b/tests/back/registries/test_registry.py @@ -70,6 +70,19 @@ class ComponentWithTwoBaseClasses(BaseConfigComponent1, BaseConfigComponent2): . class NoComponent: ... +class RelatedMixin: + COMPATIBLE_COMPONENTS = ["Component1"] + + +class RelatedParentComponent(BaseStaticComponent): + COMPATIBLE_COMPONENTS = ["Component2"] + + +class CombinedRelatedComponent(RelatedMixin, RelatedParentComponent): + # Redeclares an inherited entry on purpose: the union must deduplicate. + COMPATIBLE_COMPONENTS = ["Component1"] + + COMPONENT1_DICT = { "name": "Component1", "type": "ConfigComponent1", @@ -482,3 +495,35 @@ def test_relationships_module(): COMPONENT1_DICT, COMPONENT2_DICT, ] + + +def test_compatible_components_merge_across_bases(): + test_registry = ComponentRegistry( + initial_components=[ + Component1, + Component2, + ] + ) + + test_registry.register_component(CombinedRelatedComponent) + + # The mixin contributes "Component1", the static base "Component2"; + # the subclass redeclaration of "Component1" does not duplicate it. + assert test_registry._relationship_manager["CombinedRelatedComponent"] == [ + "Component1", + "Component2", + ] + assert test_registry.get_related_components("CombinedRelatedComponent") == [ + COMPONENT1_DICT, + COMPONENT2_DICT, + ] + assert [ + component["name"] + for component in test_registry.get_related_components("Component2") + ] == ["CombinedRelatedComponent"] + + # Unregistering removes both inherited edges symmetrically. + test_registry.unregister_component(CombinedRelatedComponent) + assert test_registry._relationship_manager["CombinedRelatedComponent"] == [] + assert test_registry.get_related_components("Component1") == [] + assert test_registry.get_related_components("Component2") == [] From 7bc173db7d560fee95805f2f80acd7a22e5f8cde Mon Sep 17 00:00:00 2001 From: Creylay Date: Mon, 13 Jul 2026 12:35:33 -0400 Subject: [PATCH 137/308] Refactor session visualization components: redesign ModelComparisonTable for natural height rendering, enhance SessionVisualization by removing unnecessary state and buttons, and update ResultsGraphs to support small multiples for better metric visualization. Remove unused ResultsGraphsSelection component and streamline graph rendering logic. --- .../models/ModelComparisonTable.jsx | 7 +- .../models/SessionVisualization.jsx | 88 +++----- .../src/constants/tours/modelsSessionTour.js | 21 -- .../results/components/ResultsGraphs.jsx | 38 ++-- .../components/ResultsGraphsLayout.jsx | 32 +-- .../components/ResultsGraphsParameters.jsx | 114 +++++----- .../results/components/ResultsGraphsPlot.jsx | 205 ++++++++++++++---- .../components/ResultsGraphsSelection.jsx | 53 ----- .../pages/results/constants/graphsMaking.jsx | 76 ++++++- .../i18n/locales/de/modelsSessionTour.json | 3 +- .../i18n/locales/en/modelsSessionTour.json | 3 +- .../i18n/locales/es/modelsSessionTour.json | 3 +- .../i18n/locales/pt/modelsSessionTour.json | 3 +- .../i18n/locales/zh/modelsSessionTour.json | 3 +- 14 files changed, 351 insertions(+), 298 deletions(-) delete mode 100644 DashAI/front/src/pages/results/components/ResultsGraphsSelection.jsx diff --git a/DashAI/front/src/components/models/ModelComparisonTable.jsx b/DashAI/front/src/components/models/ModelComparisonTable.jsx index be608c6ce..32492a791 100644 --- a/DashAI/front/src/components/models/ModelComparisonTable.jsx +++ b/DashAI/front/src/components/models/ModelComparisonTable.jsx @@ -22,7 +22,7 @@ import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationMod /** * Compact comparison table showing all runs in a session. - * Designed for sticky header display with fixed height. + * Renders at its natural content height — the page scrolls, not the table. * * Scores are computed server-side and fetched from the backend. */ @@ -473,17 +473,15 @@ function ModelComparisonTable({ muiTablePaperProps: { elevation: 0, sx: { - height: "100%", display: "flex", flexDirection: "column", border: "1px solid", borderColor: "divider", }, }, - muiTableContainerProps: { sx: { flex: 1, overflow: "auto" } }, localization, initialState: { density: "compact" }, - enableStickyHeader: true, + enableStickyHeader: false, enableRowSelection: false, enablePagination: false, enableTopToolbar: false, @@ -511,7 +509,6 @@ function ModelComparisonTable({ return ( { - setShowTable(isTable); - }, []); - const fetchModels = React.useCallback(async () => { try { const response = await getComponents({ selectTypes: ["Model"] }); @@ -95,22 +89,6 @@ export default function SessionVisualization() { fetchModels(); }, [fetchModels]); - useEffect(() => { - const handleGraphsButtonClick = (e) => { - const graphsButton = e.target.closest('[data-tour="graphs-button"]'); - if (graphsButton && sessionTourContext?.stepIndex === 7) { - setTimeout(() => { - sessionTourContext.nextStep(); - }, 500); - } - }; - - document.addEventListener("click", handleGraphsButtonClick, true); - return () => { - document.removeEventListener("click", handleGraphsButtonClick, true); - }; - }, [sessionTourContext]); - // Check if tour should start from previous tutorial useEffect(() => { const shouldStartTour = sessionStorage.getItem("startModelsSessionTour"); @@ -515,25 +493,6 @@ export default function SessionVisualization() { )} )} - - {/* Toggle between Table and Graphs */} - - - - @@ -550,25 +509,34 @@ export default function SessionVisualization() { {t("models:label.noRunsYet")} - ) : showTable ? ( - ) : ( - + <> + + + + {t("common:graphs")} + + + )} diff --git a/DashAI/front/src/constants/tours/modelsSessionTour.js b/DashAI/front/src/constants/tours/modelsSessionTour.js index b6c3596f6..cb1ed7ca6 100644 --- a/DashAI/front/src/constants/tours/modelsSessionTour.js +++ b/DashAI/front/src/constants/tours/modelsSessionTour.js @@ -156,27 +156,6 @@ export const modelsSessionTourSteps = [ disableBeacon: true, maxWidth: "420px", }, - { - target: '[data-tour="graphs-button"]', - content: ( - -

-

-

- -

-

- -

-
- - ), - placement: "bottom", - disableBeacon: true, - spotlightClicks: true, - isInteractive: true, - disableBackButton: true, - }, { target: '[data-tour="model-comparison-panel"]', content: ( diff --git a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx index 70a7adfb1..bc7635185 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx @@ -6,7 +6,7 @@ import { useTheme } from "@mui/material/styles"; import { useTranslation } from "react-i18next"; import { getComponents } from "../../../api/component"; -import graphsMaking, { heatmapMaking } from "../constants/graphsMaking"; +import { heatmapMaking, smallMultiplesMaking } from "../constants/graphsMaking"; import layoutMaking from "../constants/layoutMaking"; import ResultsGraphsLayout from "./ResultsGraphsLayout"; @@ -19,7 +19,6 @@ function ResultsGraphs({ const theme = useTheme(); const { t } = useTranslation(["models"]); - const [selectedChart, setSelectedChart] = useState("bar"); // Internal split state — used only when no controlled prop is provided const [internalSplit, setInternalSplit] = useState("test"); const [selectedMetrics, setSelectedMetrics] = useState([]); @@ -97,21 +96,11 @@ function ResultsGraphs({ try { const metricsKey = `${selectedSplit}_metrics`; - const graphsToView = {}; - - finishedRuns.forEach((run, idx) => { - const metricsObj = run[metricsKey] ?? {}; - const values = selectedMetrics.map((m) => { - const v = metricsObj[m]; - if (v === undefined || v === null) return null; - if (Array.isArray(v)) return v[v.length - 1]?.value ?? null; - return typeof v === "number" ? v : null; - }); - graphsMaking(graphsToView, run, selectedMetrics, values, idx, theme); - }); - // Heatmap is a single all-runs trace — built after the loop. - graphsToView.heatmap = heatmapMaking( + // Bar view: one small chart per metric (small multiples) instead of + // one combined chart, so metrics with different scales/ranges never + // share an axis. Every run keeps the same color across all panels. + const { panels, legend, xaxis } = smallMultiplesMaking( finishedRuns, selectedMetrics, metricsKey, @@ -119,12 +108,17 @@ function ResultsGraphs({ metricsMetadata, ); - const { generalLayout } = layoutMaking( - selectedChart, - graphsToView, + // Heatmap is a single all-runs trace, unchanged. + const heatmap = heatmapMaking( + finishedRuns, + selectedMetrics, + metricsKey, theme, + metricsMetadata, ); - setChartData({ generalLayout, ...graphsToView }); + + const { generalLayout } = layoutMaking("heatmap", {}, theme); + setChartData({ generalLayout, bar: panels, legend, xaxis, heatmap }); } catch (error) { enqueueSnackbar(t("models:error.errorProcesingExperimentResults"), { variant: "error", @@ -135,14 +129,12 @@ function ResultsGraphs({ finishedRuns, selectedSplit, selectedMetrics, - selectedChart, theme, metricsMetadata, enqueueSnackbar, t, ]); - const handleChangeChart = (chartType) => setSelectedChart(chartType); const handleToggleMetric = (metric) => { const canonicalOrder = availableMetrics[selectedSplit] ?? []; setSelectedMetrics((prev) => { @@ -170,8 +162,6 @@ function ResultsGraphs({ return ( - {/* Chart type selector */} - + {/* Plotly chart area — bar panels + heatmap in one grid */} - {/* Metric filter sidebar */} - - - {/* Plotly chart area */} - + ); } ResultsGraphsLayout.propTypes = { - selectedChart: PropTypes.string.isRequired, - handleChangeChart: PropTypes.func.isRequired, currentMetrics: PropTypes.array.isRequired, selectedMetrics: PropTypes.array.isRequired, handleToggleMetric: PropTypes.func.isRequired, diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx index e7f59622c..d63a1fac1 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx @@ -23,69 +23,63 @@ function ResultsGraphsParameters({ return ( - {/* ── Metric checkboxes ── */} - - + - - {t("common:metrics", "Metrics")} - + {t("common:metrics", "Metrics")} + - - - - - + + + - {currentMetrics.length === 0 ? ( - - {t("models:label.noMetricsAvailableForThisView")} - - ) : ( - currentMetrics.map((metric) => ( + {currentMetrics.length === 0 ? ( + + {t("models:label.noMetricsAvailableForThisView")} + + ) : ( + + {currentMetrics.map((metric) => ( } label={{metric}} - sx={{ display: "flex", m: 0, py: 1 }} + sx={{ display: "flex", m: 0, mr: 3 }} /> - )) - )} - + ))} + + )} ); } diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx index cc5740eee..e0395351d 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx @@ -1,64 +1,187 @@ import React from "react"; import PropTypes from "prop-types"; import { Box, Typography } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; import Plot from "react-plotly.js"; import { useTranslation } from "react-i18next"; -function ResultsGraphsPlot({ selectedChart, chartData }) { - const { t } = useTranslation(["models"]); +function EmptyState({ message }) { + return ( + + {message} + + ); +} - const traceData = - selectedChart === "heatmap" - ? (chartData.heatmap ?? []) - : (chartData.bar ?? []); +function ResultsGraphsPlot({ chartData }) { + const { t } = useTranslation(["models"]); + const theme = useTheme(); + const bgColor = theme.palette.background.paper; + const textColor = theme.palette.text.primary; + const gridColor = theme.palette.divider; - const hasData = traceData.length > 0; + const panels = chartData.bar ?? []; + const legend = chartData.legend ?? []; + const xaxis = chartData.xaxis; + const heatmapData = chartData.heatmap ?? []; - if (!hasData) { + if (panels.length === 0 && heatmapData.length === 0) { return ( - - - {t("models:label.noMetricsAvailableForThisView")} - - + ); } + const panelLayout = { + autosize: true, + height: 240, + margin: { l: 44, r: 12, t: 8, b: 64 }, + showlegend: false, + paper_bgcolor: bgColor, + plot_bgcolor: bgColor, + bargap: 0.25, + font: { + color: textColor, + family: theme.typography.fontFamily, + size: 11, + }, + xaxis: { + gridcolor: gridColor, + tickfont: { color: textColor, size: 10 }, + tickangle: -30, + automargin: true, + tickvals: xaxis?.tickvals, + ticktext: xaxis?.ticktext, + }, + yaxis: { + gridcolor: gridColor, + zerolinecolor: gridColor, + tickfont: { color: textColor, size: 10 }, + }, + }; + return ( - - + {/* Shared legend — one entry per run, same color in every panel */} + {legend.length > 1 && ( + + {legend.map(({ label, color }) => ( + + + + {label} + + + ))} + + )} + + + > + {panels.map((panel) => ( + + + {panel.title} + + + + ))} + + {/* Heatmap — spans the full grid width since it needs room for + every run × metric cell, but still reflows as one grid item */} + {heatmapData.length > 0 && ( + + + {t("models:label.heatmap")} + + + + + + )} + ); } ResultsGraphsPlot.propTypes = { - selectedChart: PropTypes.string.isRequired, chartData: PropTypes.object.isRequired, }; diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsSelection.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsSelection.jsx deleted file mode 100644 index a3899d410..000000000 --- a/DashAI/front/src/pages/results/components/ResultsGraphsSelection.jsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { - Box, - ToggleButton, - ToggleButtonGroup, - Typography, -} from "@mui/material"; -import { useTheme } from "@mui/material/styles"; -import { useTranslation } from "react-i18next"; - -function ResultsGraphsSelection({ selectedChart, handleChangeChart }) { - const { t } = useTranslation(["models"]); - const theme = useTheme(); - - return ( - - - {t("models:label.chartType", "Chart type")} - - - { - if (v) handleChangeChart(v); - }} - size="small" - > - {t("models:label.bar")} - {t("models:label.heatmap")} - - - ); -} - -ResultsGraphsSelection.propTypes = { - selectedChart: PropTypes.string.isRequired, - handleChangeChart: PropTypes.func.isRequired, -}; - -export default ResultsGraphsSelection; diff --git a/DashAI/front/src/pages/results/constants/graphsMaking.jsx b/DashAI/front/src/pages/results/constants/graphsMaking.jsx index c069f1896..16a909fbe 100644 --- a/DashAI/front/src/pages/results/constants/graphsMaking.jsx +++ b/DashAI/front/src/pages/results/constants/graphsMaking.jsx @@ -41,6 +41,80 @@ function graphsMaking(graphsToView, run, metrics, values, runIndex, theme) { return graphsToView; } +/** + * Build one small bar chart PER METRIC (small multiples), instead of a single + * chart with all metrics grouped on one x-axis. Each metric gets its own + * scale, so metrics with very different ranges (e.g. Accuracy vs LogLoss) + * are never forced onto a shared axis. Every run keeps the same color across + * every panel (identity, not rank) so it stays recognizable throughout. + * + * @param {object[]} finishedRuns Array of completed run objects + * @param {string[]} metrics Metric names — one panel each + * @param {string} metricsKey e.g. "test_metrics" + * @param {object} theme MUI theme object + * @param {object} [metricsMetadata={}] { MetricName: { maximize: bool } } + * @returns {{ panels: object[], legend: {label: string, color: string}[] }} + */ +function smallMultiplesMaking( + finishedRuns, + metrics, + metricsKey, + theme, + metricsMetadata = {}, +) { + const MAX_LABEL = 16; + const truncate = (s) => + s.length > MAX_LABEL ? `${s.slice(0, MAX_LABEL)}…` : s; + + const colors = getTraceColors(theme); + const fullRunLabels = finishedRuns.map( + (run, idx) => run.run_name || run.name || `Run ${idx + 1}`, + ); + const runLabels = fullRunLabels.map(truncate); + const runColors = finishedRuns.map((_, idx) => colors[idx % colors.length]); + + // Use numeric slots (not the run name) as the x category. Two different + // runs of the same model (e.g. "BaggingClassifier_1"/"_2") often share the + // same truncated prefix — if the label itself were the x value, Plotly + // would treat them as the same category and merge their bars into one. + const xValues = finishedRuns.map((_, idx) => idx); + + const panels = metrics.map((metric) => { + const isInverse = metricsMetadata[metric]?.maximize === false; + const values = finishedRuns.map((run) => { + const metricsObj = run[metricsKey] ?? {}; + const v = metricsObj[metric]; + if (v === undefined || v === null) return null; + if (Array.isArray(v)) return v[v.length - 1]?.value ?? null; + return typeof v === "number" ? v : null; + }); + + return { + metric, + title: isInverse ? `${metric} ↓` : metric, + data: [ + { + type: "bar", + x: xValues, + y: values, + customdata: fullRunLabels, + marker: { color: runColors, opacity: 0.85 }, + hovertemplate: "%{customdata}
%{y:.4f}", + }, + ], + }; + }); + + const legend = fullRunLabels.map((label, idx) => ({ + label, + color: runColors[idx], + })); + + const xaxis = { tickvals: xValues, ticktext: runLabels }; + + return { panels, legend, xaxis }; +} + /** * Build a single Plotly heatmap trace from all runs at once. * @@ -151,5 +225,5 @@ function heatmapMaking( ]; } -export { heatmapMaking }; +export { heatmapMaking, smallMultiplesMaking }; export default graphsMaking; diff --git a/DashAI/front/src/utils/i18n/locales/de/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/de/modelsSessionTour.json index 700d26c2c..7ba89c580 100644 --- a/DashAI/front/src/utils/i18n/locales/de/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/de/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>Ihre Modell-Durchlauf-Karte<1>Diese Karte enthält alle Informationen zu Ihrem Modelldurchlauf. Hier können Sie:<2><0><0>Trainieren: Den Trainingsprozess mit Ihren konfigurierten Parametern starten<1><0>Metriken anzeigen: Leistungswerte nach dem Training einsehen<2><0>Vorhersagen erstellen: Ihr trainiertes Modell auf neue Daten anwenden<3><0>Erklärungsmodelle erstellen: Verstehen, wie Ihr Modell Entscheidungen trifft<3>Trainieren Sie dieses Modell, um seine Leistung zu sehen!", "performanceVisualizations": "<0><0>Leistungsvisualisierungen<1>Die Graphenansicht zeigt Leistungsmetriken und andere Visualisierungen, um die Leistung Ihrer Modelle besser zu verstehen.<2>🎉 <0>Gut gemacht! Sie können jetzt weitere Modelle hinzufügen und mit verschiedenen Parametern experimentieren!", "sessionVisualization": "<0><0>Sitzungsvisualisierung<1>Willkommen in der Sitzungsvisualisierung! Hier können Sie verschiedene Modelle vergleichen, trainieren und ihre Leistung analysieren.<2>Beginnen wir damit, einige Modelle zum Vergleich in dieser Sitzung hinzuzufügen.", - "trainModel": "<0><0>Modell trainieren<1>Klicken Sie auf die Schaltfläche <0>Trainieren, um Ihr Modell mit den konfigurierten Parametern zu trainieren.<2>Der Trainingsprozess läuft im Hintergrund, und Sie können hier den Fortschritt und die Ergebnisse sehen.<3><0>Klicken Sie auf \"Trainieren\", um fortzufahren!", - "visualizeResults": "<0><0>Ergebnisse visualisieren<1>Möchten Sie Ihre Ergebnisse anschaulicher sehen? Klicken Sie auf die Schaltfläche <0>Graphen, um von der Tabellenansicht zu interaktiven Diagrammen zu wechseln.<2><0>Klicken Sie auf \"Graphen\", um fortzufahren!" + "trainModel": "<0><0>Modell trainieren<1>Klicken Sie auf die Schaltfläche <0>Trainieren, um Ihr Modell mit den konfigurierten Parametern zu trainieren.<2>Der Trainingsprozess läuft im Hintergrund, und Sie können hier den Fortschritt und die Ergebnisse sehen.<3><0>Klicken Sie auf \"Trainieren\", um fortzufahren!" } diff --git a/DashAI/front/src/utils/i18n/locales/en/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/en/modelsSessionTour.json index 4d176c2d0..95bb8e5e6 100644 --- a/DashAI/front/src/utils/i18n/locales/en/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/en/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>Your Model Run Card<1>Perfect! This card contains everything about your model run. Here you can:<2><0><0>Train: Start the training process with your configured parameters<1><0>View Metrics: See performance scores once training is complete<2><0>Make Predictions: Use your trained model on new data<3><0>Create Explainers: Understand how your model makes decisions<3>Let's train this model to see how it performs!", "performanceVisualizations": "<0><0>Performance Visualizations<1>The graphs view shows performance metrics and other visualizations to help you better understand your models' performance.<2>🎉 <0>Great job! You can now add more models and experiment with different parameters!", "sessionVisualization": "<0><0>Session Visualization<1>Welcome to the Session Visualization! This is where you can compare different models, train them, and analyze their performance.<2>Let's start by adding some models to compare in this session.", - "trainModel": "<0><0>Train Your Model<1>Click the <0>Train button to start training your model with the configured parameters.<2>The training process will run in the background, and you'll be able to see the progress and results here.<3><0>Click \"Train\" to continue!", - "visualizeResults": "<0><0>Visualize Results<1>Want to see your results in a more visual way? Click the <0>Graphs button to switch from the table view to interactive charts.<2><0>Click \"Graphs\" to continue!" + "trainModel": "<0><0>Train Your Model<1>Click the <0>Train button to start training your model with the configured parameters.<2>The training process will run in the background, and you'll be able to see the progress and results here.<3><0>Click \"Train\" to continue!" } diff --git a/DashAI/front/src/utils/i18n/locales/es/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/es/modelsSessionTour.json index 291d8e047..6c3953460 100644 --- a/DashAI/front/src/utils/i18n/locales/es/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/es/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>Tarjeta de Ejecución del Modelo<1>¡Perfecto! Esta tarjeta contiene todo sobre la ejecución de tu modelo. Aquí puedes:<2><0><0>Entrenar: Iniciar el proceso de entrenamiento con tus parámetros configurados<1><0>Ver Métricas: Ver las puntuaciones de rendimiento una vez completado el entrenamiento<2><0>Hacer Predicciones: Usar tu modelo entrenado con datos nuevos<3><0>Crear Explicadores: Entender cómo tu modelo toma decisiones<3>¡Entrenemos este modelo para ver cómo funciona!", "performanceVisualizations": "<0><0>Visualizaciones de Rendimiento<1>La vista de gráficos muestra métricas de rendimiento y otras visualizaciones para ayudarte a entender mejor el rendimiento de tus modelos.<2>🎉 <0>¡Excelente trabajo! ¡Ahora puedes agregar más modelos y experimentar con diferentes parámetros!", "sessionVisualization": "<0><0>Visualización de Sesión<1>¡Bienvenido(a) a la Visualización de Sesión! Aquí es donde puedes comparar diferentes modelos, entrenarlos y analizar su rendimiento.<2>Comencemos agregando algunos modelos para comparar en esta sesión.", - "trainModel": "<0><0>Entrena tu Modelo<1>Haz clic en el botón <0>Entrenar para comenzar a entrenar tu modelo con los parámetros configurados.<2>El proceso de entrenamiento se ejecutará en segundo plano, y podrás ver el progreso y los resultados aquí.<3><0>¡Haz clic en \"Entrenar\" para continuar!", - "visualizeResults": "<0><0>Visualizar Resultados<1>¿Quieres ver tus resultados de una manera más visual? Haz clic en el botón <0>Gráficos para cambiar de la vista de tabla a gráficos interactivos.<2><0>¡Haz clic en \"Gráficos\" para continuar!" + "trainModel": "<0><0>Entrena tu Modelo<1>Haz clic en el botón <0>Entrenar para comenzar a entrenar tu modelo con los parámetros configurados.<2>El proceso de entrenamiento se ejecutará en segundo plano, y podrás ver el progreso y los resultados aquí.<3><0>¡Haz clic en \"Entrenar\" para continuar!" } diff --git a/DashAI/front/src/utils/i18n/locales/pt/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/pt/modelsSessionTour.json index 9f31219fc..e4721c9cf 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/pt/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>Cartão de Execução do Modelo<1>Perfeito! Este cartão contém tudo sobre a execução do seu modelo. Aqui você pode:<2><0><0>Treinar: Iniciar o processo de treinamento com seus parâmetros configurados<1><0>Ver Métricas: Ver as pontuações de desempenho após concluir o treinamento<2><0>Fazer Previsões: Usar seu modelo treinado com novos dados<3><0>Criar Explicadores: Entender como seu modelo toma decisões<3>Vamos treinar este modelo para ver como funciona!", "performanceVisualizations": "<0><0>Visualizações de Desempenho<1>A visualização de gráficos mostra métricas de desempenho e outras visualizações para ajudá-lo(a) a entender melhor o desempenho dos seus modelos.<2>🎉 <0>Excelente trabalho! Agora você pode adicionar mais modelos e experimentar com diferentes parâmetros!", "sessionVisualization": "<0><0>Visualização de Sessão<1>Bem-vindo(a) à Visualização de Sessão! Aqui é onde você pode comparar diferentes modelos, treiná-los e analisar seu desempenho.<2>Vamos começar adicionando alguns modelos para comparar nesta sessão.", - "trainModel": "<0><0>Treine seu Modelo<1>Clique no botão <0>Treinar para começar a treinar seu modelo com os parâmetros configurados.<2>O processo de treinamento será executado em segundo plano e você poderá ver o progresso e os resultados aqui.<3><0>Clique em \"Treinar\" para continuar!", - "visualizeResults": "<0><0>Visualizar Resultados<1>Quer ver seus resultados de forma mais visual? Clique no botão <0>Gráficos para alternar da visualização de tabela para gráficos interativos.<2><0>Clique em \"Gráficos\" para continuar!" + "trainModel": "<0><0>Treine seu Modelo<1>Clique no botão <0>Treinar para começar a treinar seu modelo com os parâmetros configurados.<2>O processo de treinamento será executado em segundo plano e você poderá ver o progresso e os resultados aqui.<3><0>Clique em \"Treinar\" para continuar!" } diff --git a/DashAI/front/src/utils/i18n/locales/zh/modelsSessionTour.json b/DashAI/front/src/utils/i18n/locales/zh/modelsSessionTour.json index 471b7d17c..77d88ede8 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/modelsSessionTour.json +++ b/DashAI/front/src/utils/i18n/locales/zh/modelsSessionTour.json @@ -6,6 +6,5 @@ "modelRunCard": "<0><0>您的模型运行卡片<1>完美!此卡片包含关于您模型运行的所有信息。您可以:<2><0><0>训练:使用已配置的参数开始训练过程<1><0>查看指标:训练完成后查看性能分数<2><0>进行预测:使用训练好的模型对新数据进行预测<3><0>创建解释器:了解您的模型如何做出决策<3>让我们训练此模型,看看它的表现!", "performanceVisualizations": "<0><0>性能可视化<1>图表视图显示性能指标和其他可视化内容,帮助您更好地理解模型性能。<2>🎉 <0>干得好!您现在可以添加更多模型并尝试不同参数!", "sessionVisualization": "<0><0>会话可视化<1>欢迎来到会话可视化!这是您可以比较不同模型、训练并分析其性能的地方。<2>让我们从向此会话添加一些模型开始比较。", - "trainModel": "<0><0>训练您的模型<1>点击<0>训练按钮使用已配置的参数开始训练模型。<2>训练过程将在后台运行,您将在此处看到进度和结果。<3><0>点击「训练」继续!", - "visualizeResults": "<0><0>可视化结果<1>想以更直观的方式查看结果?点击<0>图表按钮从表格视图切换到交互式图表。<2><0>点击「图表」继续!" + "trainModel": "<0><0>训练您的模型<1>点击<0>训练按钮使用已配置的参数开始训练模型。<2>训练过程将在后台运行,您将在此处看到进度和结果。<3><0>点击「训练」继续!" } From 32cf5c4edffaa1cdd8b5f2eec51e5de2b2e7df01 Mon Sep 17 00:00:00 2001 From: Creylay Date: Mon, 13 Jul 2026 13:05:45 -0400 Subject: [PATCH 138/308] Refactor ModelCardCompact component: enhance layout and interactions, add ChevronRight icon for better navigation indication, and improve hover effects. Update translations for 'operations' in multiple languages. --- .../components/models/ModelCardCompact.jsx | 194 +++++++++++------- .../src/utils/i18n/locales/de/models.json | 1 + .../src/utils/i18n/locales/en/models.json | 1 + .../src/utils/i18n/locales/es/models.json | 1 + .../src/utils/i18n/locales/pt/models.json | 1 + .../src/utils/i18n/locales/zh/models.json | 1 + 6 files changed, 130 insertions(+), 69 deletions(-) diff --git a/DashAI/front/src/components/models/ModelCardCompact.jsx b/DashAI/front/src/components/models/ModelCardCompact.jsx index 9c50e02d9..6536120a3 100644 --- a/DashAI/front/src/components/models/ModelCardCompact.jsx +++ b/DashAI/front/src/components/models/ModelCardCompact.jsx @@ -9,7 +9,12 @@ import { CircularProgress, } from "@mui/material"; import { useTheme, alpha } from "@mui/material/styles"; -import { PlayArrow, Delete, WarningAmber } from "@mui/icons-material"; +import { + PlayArrow, + Delete, + WarningAmber, + ChevronRight, +} from "@mui/icons-material"; import { useTranslation } from "react-i18next"; import { getRunStatusColor } from "../../utils/runStatus"; import { ModelIcon } from "./model/ModelIcon"; @@ -200,16 +205,22 @@ function ModelCardCompact({ elevation={0} onClick={onOpen} sx={{ - p: 2.5, display: "flex", - alignItems: "center", - gap: 2, + flexDirection: "column", cursor: "pointer", border: 1, borderColor: alpha(statusMain, 0.35), - transition: "border-color 0.15s, box-shadow 1.2s ease-out", - "&:hover": { borderColor: alpha(statusMain, 0.7) }, + transition: + "border-color 0.15s, box-shadow 0.2s ease-out, transform 0.15s ease-out", + "&:hover": { + borderColor: alpha(statusMain, 0.7), + transform: "translateY(-3px)", + boxShadow: `0 6px 16px ${alpha(theme.palette.common.black, 0.35)}`, + "& .card-chevron-icon": { color: theme.palette.primary.main }, + }, ...(isHighlighted && { + transition: + "border-color 0.15s, box-shadow 1.2s ease-out, transform 0.15s ease-out", boxShadow: `0 0 0 3px ${alpha( theme.palette.primary.main, 0.4, @@ -219,83 +230,128 @@ function ModelCardCompact({ > - - - - - - {run.name} - - - + - - {modelDisplayName} + + + + + {run.name} + + + + {modelDisplayName} + + - - + - e.stopPropagation()} - > - {canTrain && ( - - onTrain(run)}> - + e.stopPropagation()} + > + {canTrain && ( + + onTrain(run)}> + + + + )} + + setDeleteConfirmOpen(true)} + > + - )} - - setDeleteConfirmOpen(true)} - > - - - + + + {/* Wrapped so clicks inside the modal (a portal) don't bubble through + the React tree into the card's onClick={onOpen} above */} + e.stopPropagation()}> + setDeleteConfirmOpen(false)} + onConfirm={() => { + setDeleteConfirmOpen(false); + localStorage.removeItem(`run-${run.id}-results-visible`); + localStorage.removeItem(`run-${run.id}-active-tab`); + onDelete(run); + }} + content={t("models:message.confirmDeleteRun")} + /> + - {/* Wrapped so clicks inside the modal (a portal) don't bubble through - the React tree into the card's onClick={onOpen} above */} - e.stopPropagation()}> - setDeleteConfirmOpen(false)} - onConfirm={() => { - setDeleteConfirmOpen(false); - localStorage.removeItem(`run-${run.id}-results-visible`); - localStorage.removeItem(`run-${run.id}-active-tab`); - onDelete(run); + {/* Footer hint — signals the whole card is clickable to open the + full model detail view (configuration, metrics, predictions, etc.) */} + + + {t("models:label.configuration")} | {t("models:label.operations")} + + diff --git a/DashAI/front/src/utils/i18n/locales/de/models.json b/DashAI/front/src/utils/i18n/locales/de/models.json index 6a6c4f35a..c743f13ff 100644 --- a/DashAI/front/src/utils/i18n/locales/de/models.json +++ b/DashAI/front/src/utils/i18n/locales/de/models.json @@ -113,6 +113,7 @@ "noPredictionsYet": "Noch keine Vorhersagen", "noRunsYet": "Noch keine Durchläufe. Fügen Sie Modelle aus dem rechten Panel hinzu.", "noSessionSelected": "Keine Sitzung ausgewählt", + "operations": "Vorgänge", "operationsWillBeDeletedWarning": "Diese Operationen werden dauerhaft gelöscht und können nicht wiederhergestellt werden. Sind Sie sicher?", "optimizerConfiguration": "Einstellungen des Hyperparameter-Optimierers konfigurieren", "optimizerParameters": "Optimiererparameter", diff --git a/DashAI/front/src/utils/i18n/locales/en/models.json b/DashAI/front/src/utils/i18n/locales/en/models.json index bceecaa3c..7ba31aede 100644 --- a/DashAI/front/src/utils/i18n/locales/en/models.json +++ b/DashAI/front/src/utils/i18n/locales/en/models.json @@ -113,6 +113,7 @@ "noPredictionsYet": "No predictions yet", "noRunsYet": "No runs yet. Add models from the right panel.", "noSessionSelected": "No Session Selected", + "operations": "Operations", "operationsWillBeDeletedWarning": "These operations will be permanently removed and cannot be recovered. Are you sure you want to continue?", "optimizerConfiguration": "Configure hyperparameter optimizer settings", "optimizerParameters": "Optimizer Parameters", diff --git a/DashAI/front/src/utils/i18n/locales/es/models.json b/DashAI/front/src/utils/i18n/locales/es/models.json index ad8d8df3c..ef670b010 100644 --- a/DashAI/front/src/utils/i18n/locales/es/models.json +++ b/DashAI/front/src/utils/i18n/locales/es/models.json @@ -114,6 +114,7 @@ "noPredictionsYet": "Aún no hay predicciones", "noRunsYet": "Aún no hay ejecuciones. Agregue modelos desde el panel derecho.", "noSessionSelected": "No se Seleccionó Ninguna Sesión", + "operations": "Operaciones", "operationsWillBeDeletedWarning": "Estas operaciones se eliminarán permanentemente y no se pueden recuperar. ¿Está seguro de que desea continuar?", "optimizerConfiguration": "Configure los ajustes del optimizador de hiperparámetros", "optimizerParameters": "Parámetros del Optimizador", diff --git a/DashAI/front/src/utils/i18n/locales/pt/models.json b/DashAI/front/src/utils/i18n/locales/pt/models.json index c0a6eff25..5d6648013 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/models.json +++ b/DashAI/front/src/utils/i18n/locales/pt/models.json @@ -114,6 +114,7 @@ "noPredictionsYet": "Ainda não há previsões", "noRunsYet": "Ainda não há execuções. Adicione modelos pelo painel direito.", "noSessionSelected": "Nenhuma Sessão Selecionada", + "operations": "Operações", "operationsWillBeDeletedWarning": "Estas operações serão excluídas permanentemente e não podem ser recuperadas. Tem certeza de que deseja continuar?", "optimizerConfiguration": "Configure as definições do otimizador de hiperparâmetros", "optimizerParameters": "Parâmetros do Otimizador", diff --git a/DashAI/front/src/utils/i18n/locales/zh/models.json b/DashAI/front/src/utils/i18n/locales/zh/models.json index 9e57eab4a..5f6b13a9b 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/models.json +++ b/DashAI/front/src/utils/i18n/locales/zh/models.json @@ -113,6 +113,7 @@ "noPredictionsYet": "暂无预测记录", "noRunsYet": "暂无运行记录。请从右侧面板添加模型。", "noSessionSelected": "未选择会话", + "operations": "操作", "operationsWillBeDeletedWarning": "这些操作将被永久删除且无法恢复。确定要继续吗?", "optimizerConfiguration": "配置超参数优化器设置", "optimizerParameters": "优化器参数", From f668e8fcffdf05628f4bdbb564b8b838b4125209 Mon Sep 17 00:00:00 2001 From: Creylay Date: Mon, 13 Jul 2026 14:07:40 -0400 Subject: [PATCH 139/308] Refactor session visualization components: implement RunEditDialog for editing run parameters, enhance ModelConfigurationContent for better parameter display, and update SessionVisualization to support new features. Improve translations for configuration availability in multiple languages. --- .../components/models/ModelCardCompact.jsx | 29 +- .../models/ModelConfigurationContent.jsx | 117 ++++++ .../front/src/components/models/RunCard.jsx | 326 +--------------- .../src/components/models/RunEditDialog.jsx | 347 ++++++++++++++++++ .../src/components/models/RunResults.jsx | 125 +------ .../models/SessionVisualization.jsx | 3 + .../src/utils/i18n/locales/de/models.json | 1 + .../src/utils/i18n/locales/en/models.json | 1 + .../src/utils/i18n/locales/es/models.json | 1 + .../src/utils/i18n/locales/pt/models.json | 1 + .../src/utils/i18n/locales/zh/models.json | 1 + 11 files changed, 513 insertions(+), 439 deletions(-) create mode 100644 DashAI/front/src/components/models/ModelConfigurationContent.jsx create mode 100644 DashAI/front/src/components/models/RunEditDialog.jsx diff --git a/DashAI/front/src/components/models/ModelCardCompact.jsx b/DashAI/front/src/components/models/ModelCardCompact.jsx index 6536120a3..d949fdffb 100644 --- a/DashAI/front/src/components/models/ModelCardCompact.jsx +++ b/DashAI/front/src/components/models/ModelCardCompact.jsx @@ -14,11 +14,13 @@ import { Delete, WarningAmber, ChevronRight, + Edit, } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; import { getRunStatusColor } from "../../utils/runStatus"; import { ModelIcon } from "./model/ModelIcon"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; +import RunEditDialog from "./RunEditDialog"; const RING_SIZE = 36; @@ -180,14 +182,18 @@ function ModelCardCompact({ run, models = [], score, + session, + existingRuns = [], onTrain, onDelete, + onRefresh, onOpen, isHighlighted = false, }) { const theme = useTheme(); const { t } = useTranslation(["models", "common"]); const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + const [configOpen, setConfigOpen] = useState(false); const model = models.find((m) => m.name === run.model_name); const modelDisplayName = model?.display_name || run.model_name; @@ -213,7 +219,7 @@ function ModelCardCompact({ transition: "border-color 0.15s, box-shadow 0.2s ease-out, transform 0.15s ease-out", "&:hover": { - borderColor: alpha(statusMain, 0.7), + borderColor: theme.palette.primary.main, transform: "translateY(-3px)", boxShadow: `0 6px 16px ${alpha(theme.palette.common.black, 0.35)}`, "& .card-chevron-icon": { color: theme.palette.primary.main }, @@ -292,6 +298,11 @@ function ModelCardCompact({ )} + + setConfigOpen(true)}> + + + + + setConfigOpen(false)} + /> @@ -383,8 +403,15 @@ ModelCardCompact.propTypes = { score: PropTypes.number, breakdown: PropTypes.array, }), + session: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + task_name: PropTypes.string, + }), + existingRuns: PropTypes.array, onTrain: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, + onRefresh: PropTypes.func, onOpen: PropTypes.func.isRequired, isHighlighted: PropTypes.bool, }; diff --git a/DashAI/front/src/components/models/ModelConfigurationContent.jsx b/DashAI/front/src/components/models/ModelConfigurationContent.jsx new file mode 100644 index 000000000..493ca97ba --- /dev/null +++ b/DashAI/front/src/components/models/ModelConfigurationContent.jsx @@ -0,0 +1,117 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { + Box, + Typography, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; +import { renderParamValue } from "./ModelParamBlock"; + +function ParamsTable({ rows }) { + const { t } = useTranslation(["common"]); + return ( + + + + + {t("common:parameter")} + {t("common:value")} + + + + {rows.map(([key, value]) => ( + + {key} + {value} + + ))} + +
+
+ ); +} + +ParamsTable.propTypes = { + rows: PropTypes.arrayOf(PropTypes.array).isRequired, +}; + +/** + * Shared body for a run's "Configuración" view — the parameters it was + * trained with, plus its optimizer setup if it was tuned via HPO. Used both + * as a tab inside RunResults and as a standalone dialog opened from the + * compact model card. + */ +function ModelConfigurationContent({ run, model }) { + const { t } = useTranslation(["models", "common"]); + const paramProperties = model?.schema?.properties ?? {}; + const getParamLabel = (key) => paramProperties[key]?.title ?? key; + + const hasParams = run.parameters && Object.keys(run.parameters).length > 0; + const hasOptimizer = run.optimizer_name && run.goal_metric; + + if (!hasParams && !hasOptimizer) { + return ( + + {t("models:label.noConfigurationAvailable")} + + ); + } + + return ( + + {hasParams && ( + + + {t("common:modelParameters")} + + [ + getParamLabel(key), + renderParamValue(value), + ])} + /> + + )} + + {hasOptimizer && ( + + + {t("common:optimizer")}: {run.optimizer_name} + + {run.optimizer_parameters && + Object.keys(run.optimizer_parameters).length > 0 && ( + [key, renderParamValue(value)], + )} + /> + )} + + {t("models:label.goalMetric")}: {run.goal_metric} + + + )} + + ); +} + +ModelConfigurationContent.propTypes = { + run: PropTypes.shape({ + parameters: PropTypes.object, + optimizer_name: PropTypes.string, + optimizer_parameters: PropTypes.object, + goal_metric: PropTypes.string, + }).isRequired, + model: PropTypes.shape({ + schema: PropTypes.object, + }), +}; + +export default ModelConfigurationContent; diff --git a/DashAI/front/src/components/models/RunCard.jsx b/DashAI/front/src/components/models/RunCard.jsx index 0f8dc92fa..767013f90 100644 --- a/DashAI/front/src/components/models/RunCard.jsx +++ b/DashAI/front/src/components/models/RunCard.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useMemo, useCallback } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import PropTypes from "prop-types"; import { Card, @@ -9,21 +9,7 @@ import { IconButton, Button, Collapse, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Paper, - Divider, Tooltip, - TextField, - Alert, - Dialog, - DialogTitle, - DialogContent, - DialogActions, } from "@mui/material"; import { useTheme, alpha } from "@mui/material/styles"; import { @@ -31,26 +17,15 @@ import { Stop, Edit, Delete, - Save, - Cancel, ExpandMore, ExpandLess, - Close as CloseIcon, } from "@mui/icons-material"; -import { useSnackbar } from "notistack"; import { getRunStatus, getRunStatusColor } from "../../utils/runStatus"; import RunResults from "./RunResults"; -import FormSchemaWithSelectedModel from "../shared/FormSchemaWithSelectedModel"; -import FormSchemaContainer from "../shared/FormSchemaContainer"; -import OptimizationTableSelectOptimizer from "./modelSession/OptimizationTableSelectOptimizer"; -import ModelsTableSelectMetric from "./modelSession/ModelsTableSelectMetric"; -import useSchema from "../../hooks/useSchema"; -import { updateRunParameters, getRunOperationsCount } from "../../api/run"; -import RetrainConfirmDialog from "./RetrainConfirmDialog"; -import { renderParamValue } from "./ModelParamBlock"; +import RunEditDialog from "./RunEditDialog"; +import { getRunOperationsCount } from "../../api/run"; import { useTranslation } from "react-i18next"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; -import { checkIfHaveOptimazers } from "../../utils/schema"; /** * Card component displaying a model run with actions and details @@ -76,7 +51,6 @@ function RunCard({ }) { const theme = useTheme(); const { t } = useTranslation(["models", "common"]); - const { enqueueSnackbar } = useSnackbar(); const [resultsVisible, setResultsVisible] = useState(() => { if (run.status === 0) return false; const saved = localStorage.getItem(`run-${run.id}-results-visible`); @@ -110,41 +84,9 @@ function RunCard({ const setDeleteConfirmOpen = isDeleteConfirmControlled ? setControlledDeleteConfirmOpen : setInternalDeleteConfirmOpen; - const [editedName, setEditedName] = useState(run.name || ""); - const [editedParameters, setEditedParameters] = useState( - run.parameters || {}, - ); - const [editedOptimizer, setEditedOptimizer] = useState( - run.optimizer_name || "", - ); - const [editedOptimizerParams, setEditedOptimizerParams] = useState( - run.optimizer_parameters || {}, - ); - const [editedGoalMetric, setEditedGoalMetric] = useState( - run.goal_metric || "", - ); const [operationsCount, setOperationsCount] = useState(null); - const [isSaving, setIsSaving] = useState(false); - const [saveConfirmOpen, setSaveConfirmOpen] = useState(false); const [autoExpand, setAutoExpand] = useState(false); - const { - defaultValues: defaultOptimizerParams, - loading: optimizerSchemaLoading, - } = useSchema({ - modelName: isEditing ? editedOptimizer : null, - }); - - useEffect(() => { - if (!isEditing) { - setEditedName(run.name || ""); - setEditedParameters(run.parameters || {}); - setEditedOptimizer(run.optimizer_name || ""); - setEditedOptimizerParams(run.optimizer_parameters || {}); - setEditedGoalMetric(run.goal_metric || ""); - } - }, [run, isEditing]); - const fetchOperationsCount = useCallback(async () => { if (!run?.id) return; try { @@ -159,120 +101,6 @@ function RunCard({ fetchOperationsCount(); }, [fetchOperationsCount, explainerRefreshTrigger]); - const hasOptimizableParams = useMemo(() => { - return checkIfHaveOptimazers(editedParameters); - }, [editedParameters]); - - const handleStartEdit = () => { - setIsEditing(true); - }; - - const handleCancelEdit = () => { - setIsEditing(false); - setEditedName(run.name || ""); - setEditedParameters(run.parameters || {}); - setEditedOptimizer(run.optimizer_name || ""); - setEditedOptimizerParams(run.optimizer_parameters || {}); - setEditedGoalMetric(run.goal_metric || ""); - }; - - const doSave = async () => { - setSaveConfirmOpen(false); - setIsSaving(true); - try { - await updateRunParameters( - run.id.toString(), - editedName.trim(), - editedParameters, - editedOptimizer || "", - { ...defaultOptimizerParams, ...editedOptimizerParams }, - editedGoalMetric || "", - ); - - enqueueSnackbar( - t("models:message.runUpdatedSuccess", { runName: editedName }), - { variant: "success" }, - ); - - setIsEditing(false); - - if (onRefresh) { - await onRefresh(); - } - await fetchOperationsCount(); - } catch (error) { - console.error("Error updating run:", error); - enqueueSnackbar( - t("models:error.failedToUpdateRun", { - error: error.message || t("common:unknownError"), - }), - { variant: "error" }, - ); - } finally { - setIsSaving(false); - } - }; - - const handleSaveEdit = async () => { - if (!editedName.trim()) { - enqueueSnackbar(t("models:error.runNameEmpty"), { variant: "warning" }); - return; - } - - const nameExists = existingRuns.some( - (r) => - r.id !== run.id && - r.name && - r.name.toLowerCase() === editedName.trim().toLowerCase(), - ); - if (nameExists) { - enqueueSnackbar( - t("models:error.runNameExists", { name: editedName.trim() }), - { variant: "error" }, - ); - return; - } - - if (hasOptimizableParams) { - if (!editedOptimizer) { - enqueueSnackbar(t("models:error.selectOptimizerRequired"), { - variant: "warning", - }); - return; - } - if (!editedGoalMetric) { - enqueueSnackbar(t("models:error.selectGoalMetricRequired"), { - variant: "warning", - }); - return; - } - } - - // If operations exist, warn before saving (they will be deleted on next train) - if ( - operationsCount && - (operationsCount.explainers > 0 || operationsCount.predictions > 0) - ) { - setSaveConfirmOpen(true); - return; - } - - await doSave(); - }; - - const handleParametersChange = useCallback((values) => { - setEditedParameters(values); - }, []); - - const handleOptimizerParamsChange = useCallback((values) => { - setEditedOptimizerParams(values); - }, []); - - const handleOptimizerSelected = (optimizerName) => { - setEditedOptimizer(optimizerName); - setEditedOptimizerParams({}); - }; - const statusText = getRunStatus(run.status, t); const model = models.find((m) => m.name === run.model_name); const modelDisplayName = model?.display_name || run.model_name; @@ -387,7 +215,7 @@ function RunCard({ size="small" color="primary" startIcon={} - onClick={handleStartEdit} + onClick={() => setIsEditing(true)} > {t("common:edit")} @@ -525,14 +353,6 @@ function RunCard({ autoExpand={autoExpand} />
- setSaveConfirmOpen(false)} - onConfirm={doSave} - run={run} - operationsCount={operationsCount} - /> setDeleteConfirmOpen(false)} @@ -546,138 +366,14 @@ function RunCard({ /> - - - - - {t("models:label.editRun")} - - - - - - - - - - - {t("models:message.editingParametersWarning")} - - - setEditedName(e.target.value)} - fullWidth - required - size="small" - /> - - {run.model_name && ( - - - {t("common:modelParameters")} - - - {}} - hideButtons - /> - - - )} - - {hasOptimizableParams && ( - - - - {t("models:label.hyperparameterOptimizerConfiguration")} - - - {t("models:message.parametersMarkedForOptimization")} - - - - - {t("models:label.goalMetric")} * - - - - - - - {editedOptimizer && ( - - - {t("common:optimizerParameters")} - - - - setEditedOptimizerParams(values) - } - onValuesChange={handleOptimizerParamsChange} - onCancel={() => {}} - hideButtons - /> - - - )} - - )} - - - - - - - - + onClose={() => setIsEditing(false)} + /> ); } diff --git a/DashAI/front/src/components/models/RunEditDialog.jsx b/DashAI/front/src/components/models/RunEditDialog.jsx new file mode 100644 index 000000000..b8bfd13c3 --- /dev/null +++ b/DashAI/front/src/components/models/RunEditDialog.jsx @@ -0,0 +1,347 @@ +import React, { useState, useEffect, useMemo, useCallback } from "react"; +import PropTypes from "prop-types"; +import { + Box, + Typography, + Button, + Divider, + TextField, + Alert, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + IconButton, +} from "@mui/material"; +import { Save, Cancel, Close as CloseIcon } from "@mui/icons-material"; +import { useSnackbar } from "notistack"; +import { useTranslation } from "react-i18next"; +import FormSchemaWithSelectedModel from "../shared/FormSchemaWithSelectedModel"; +import FormSchemaContainer from "../shared/FormSchemaContainer"; +import OptimizationTableSelectOptimizer from "./modelSession/OptimizationTableSelectOptimizer"; +import ModelsTableSelectMetric from "./modelSession/ModelsTableSelectMetric"; +import useSchema from "../../hooks/useSchema"; +import { updateRunParameters, getRunOperationsCount } from "../../api/run"; +import RetrainConfirmDialog from "./RetrainConfirmDialog"; +import { checkIfHaveOptimazers } from "../../utils/schema"; + +/** + * Editable-parameters dialog for a run — the same form used to configure it + * before training, pre-filled with its current values. Shared by RunCard's + * "Editar" button and the compact model card's quick-edit action so both + * entry points open the exact same modal. + */ +export default function RunEditDialog({ + run, + session, + existingRuns = [], + onRefresh, + open, + onClose, +}) { + const { t } = useTranslation(["models", "common"]); + const { enqueueSnackbar } = useSnackbar(); + + const [editedName, setEditedName] = useState(run.name || ""); + const [editedParameters, setEditedParameters] = useState( + run.parameters || {}, + ); + const [editedOptimizer, setEditedOptimizer] = useState( + run.optimizer_name || "", + ); + const [editedOptimizerParams, setEditedOptimizerParams] = useState( + run.optimizer_parameters || {}, + ); + const [editedGoalMetric, setEditedGoalMetric] = useState( + run.goal_metric || "", + ); + const [operationsCount, setOperationsCount] = useState(null); + const [isSaving, setIsSaving] = useState(false); + const [saveConfirmOpen, setSaveConfirmOpen] = useState(false); + + const { defaultValues: defaultOptimizerParams } = useSchema({ + modelName: open ? editedOptimizer : null, + }); + + useEffect(() => { + if (!open) return; + setEditedName(run.name || ""); + setEditedParameters(run.parameters || {}); + setEditedOptimizer(run.optimizer_name || ""); + setEditedOptimizerParams(run.optimizer_parameters || {}); + setEditedGoalMetric(run.goal_metric || ""); + }, [run, open]); + + const runId = run.id; + useEffect(() => { + if (!open || !runId) return; + getRunOperationsCount(runId.toString()) + .then(setOperationsCount) + .catch((error) => + console.error("Error fetching operations count:", error), + ); + }, [open, runId]); + + const hasOptimizableParams = useMemo( + () => checkIfHaveOptimazers(editedParameters), + [editedParameters], + ); + + const doSave = async () => { + setSaveConfirmOpen(false); + setIsSaving(true); + try { + await updateRunParameters( + run.id.toString(), + editedName.trim(), + editedParameters, + editedOptimizer || "", + { ...defaultOptimizerParams, ...editedOptimizerParams }, + editedGoalMetric || "", + ); + + enqueueSnackbar( + t("models:message.runUpdatedSuccess", { runName: editedName }), + { variant: "success" }, + ); + + onClose(); + if (onRefresh) await onRefresh(); + } catch (error) { + console.error("Error updating run:", error); + enqueueSnackbar( + t("models:error.failedToUpdateRun", { + error: error.message || t("common:unknownError"), + }), + { variant: "error" }, + ); + } finally { + setIsSaving(false); + } + }; + + const handleSaveEdit = async () => { + if (!editedName.trim()) { + enqueueSnackbar(t("models:error.runNameEmpty"), { variant: "warning" }); + return; + } + + const nameExists = existingRuns.some( + (r) => + r.id !== run.id && + r.name && + r.name.toLowerCase() === editedName.trim().toLowerCase(), + ); + if (nameExists) { + enqueueSnackbar( + t("models:error.runNameExists", { name: editedName.trim() }), + { variant: "error" }, + ); + return; + } + + if (hasOptimizableParams) { + if (!editedOptimizer) { + enqueueSnackbar(t("models:error.selectOptimizerRequired"), { + variant: "warning", + }); + return; + } + if (!editedGoalMetric) { + enqueueSnackbar(t("models:error.selectGoalMetricRequired"), { + variant: "warning", + }); + return; + } + } + + // If operations exist, warn before saving (they will be deleted on next train) + if ( + operationsCount && + (operationsCount.explainers > 0 || operationsCount.predictions > 0) + ) { + setSaveConfirmOpen(true); + return; + } + + await doSave(); + }; + + const handleParametersChange = useCallback((values) => { + setEditedParameters(values); + }, []); + + const handleOptimizerParamsChange = useCallback((values) => { + setEditedOptimizerParams(values); + }, []); + + const handleOptimizerSelected = (optimizerName) => { + setEditedOptimizer(optimizerName); + setEditedOptimizerParams({}); + }; + + return ( + <> + + + + + {t("models:label.editRun")} + + + + + + + + + + + {t("models:message.editingParametersWarning")} + + + setEditedName(e.target.value)} + fullWidth + required + size="small" + /> + + {run.model_name && ( + + + {t("common:modelParameters")} + + + {}} + hideButtons + /> + + + )} + + {hasOptimizableParams && ( + + + + {t("models:label.hyperparameterOptimizerConfiguration")} + + + {t("models:message.parametersMarkedForOptimization")} + + + + + {t("models:label.goalMetric")} * + + + + + + + {editedOptimizer && ( + + + {t("common:optimizerParameters")} + + + + setEditedOptimizerParams(values) + } + onValuesChange={handleOptimizerParamsChange} + onCancel={() => {}} + hideButtons + /> + + + )} + + )} + + + + + + + + + + setSaveConfirmOpen(false)} + onConfirm={doSave} + run={run} + operationsCount={operationsCount} + /> + + ); +} + +RunEditDialog.propTypes = { + run: PropTypes.shape({ + id: PropTypes.number, + name: PropTypes.string, + model_name: PropTypes.string, + parameters: PropTypes.object, + optimizer_name: PropTypes.string, + optimizer_parameters: PropTypes.object, + goal_metric: PropTypes.string, + }).isRequired, + session: PropTypes.shape({ + task_name: PropTypes.string, + }), + existingRuns: PropTypes.array, + onRefresh: PropTypes.func, + open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/models/RunResults.jsx b/DashAI/front/src/components/models/RunResults.jsx index 351b88ad6..7956fe6f1 100644 --- a/DashAI/front/src/components/models/RunResults.jsx +++ b/DashAI/front/src/components/models/RunResults.jsx @@ -16,7 +16,7 @@ import { DialogContent, DialogActions, } from "@mui/material"; -import { renderParamValue } from "./ModelParamBlock"; +import ModelConfigurationContent from "./ModelConfigurationContent"; import { ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon, @@ -99,11 +99,6 @@ export default function RunResults({ isSaving: false, }); - // Map a parameter key to its display name using the matching model's schema - // (the model comes from the right side bar list, so no extra backend fetch). - const paramProperties = model?.schema?.properties ?? {}; - const getParamLabel = (key) => paramProperties[key]?.title ?? key; - const optimizables = checkHowManyOptimazers({ params: run.parameters }); const isFinished = run.status === 3; const isRunning = run.status === 1 || run.status === 2; @@ -304,123 +299,7 @@ export default function RunResults({ {t("common:modelParameters")} - - {run.parameters && - Object.entries(run.parameters).map(([key, value]) => ( - - - {getParamLabel(key)} - - - - {renderParamValue(value)} - - - - ))} - - {run.optimizer_name && run.goal_metric && ( - <> - - - {t("common:optimizer")} - - - - {run.optimizer_name} - - - - - - - {t("models:label.goalMetric")} - - - {run.goal_metric} - - - - {run.optimizer_parameters && - Object.entries(run.optimizer_parameters).map( - ([key, value]) => ( - - - {key} - - - - {renderParamValue(value)} - - - - ), - )} - - )} - +
)} diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index cd062dab0..6b116bd98 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -417,8 +417,11 @@ export default function SessionVisualization() { run={run} models={models} score={scores[run.id]} + session={session} + existingRuns={runs} onTrain={handleTrainWithTour} onDelete={handleDeleteRun} + onRefresh={fetchRuns} onOpen={() => navigate( `/app/models/sessions/${session.id}/model/${run.id}`, diff --git a/DashAI/front/src/utils/i18n/locales/de/models.json b/DashAI/front/src/utils/i18n/locales/de/models.json index c743f13ff..30ef441a9 100644 --- a/DashAI/front/src/utils/i18n/locales/de/models.json +++ b/DashAI/front/src/utils/i18n/locales/de/models.json @@ -102,6 +102,7 @@ "nameYourSession": "Sitzung benennen", "selectDatasetAndPrepare": "Benennen Sie Ihre Sitzung, wählen Sie einen Datensatz aus und konfigurieren Sie Spalten und Aufteilungen.", "noCompatibleModelsFound": "Keine kompatiblen Modelle gefunden", + "noConfigurationAvailable": "Keine Konfiguration für diesen Durchlauf verfügbar", "dropModelHere": "Hier ablegen zum Hinzufügen", "noDatasetPredictionsYet": "Noch keine Datensatz-Vorhersagen", "noGlobalExplainersYet": "Noch keine globalen Erklärungsmodelle", diff --git a/DashAI/front/src/utils/i18n/locales/en/models.json b/DashAI/front/src/utils/i18n/locales/en/models.json index 7ba31aede..19cd40e92 100644 --- a/DashAI/front/src/utils/i18n/locales/en/models.json +++ b/DashAI/front/src/utils/i18n/locales/en/models.json @@ -102,6 +102,7 @@ "nameYourSession": "Name Your Session", "selectDatasetAndPrepare": "Name your session, select a dataset and configure its columns and splits.", "noCompatibleModelsFound": "No compatible models found", + "noConfigurationAvailable": "No configuration available for this run", "dropModelHere": "Drop here to add", "noDatasetPredictionsYet": "No dataset predictions yet", "noGlobalExplainersYet": "No global explainers yet", diff --git a/DashAI/front/src/utils/i18n/locales/es/models.json b/DashAI/front/src/utils/i18n/locales/es/models.json index ef670b010..a467da193 100644 --- a/DashAI/front/src/utils/i18n/locales/es/models.json +++ b/DashAI/front/src/utils/i18n/locales/es/models.json @@ -103,6 +103,7 @@ "nameYourSession": "Nombre su Sesión", "selectDatasetAndPrepare": "Nombra tu sesión, selecciona un dataset y configura sus columnas y particiones.", "noCompatibleModelsFound": "No se encontraron modelos compatibles", + "noConfigurationAvailable": "No hay configuración disponible para esta ejecución", "dropModelHere": "Suelta aquí para agregar", "noDatasetPredictionsYet": "Aún no hay predicciones de dataset", "noGlobalExplainersYet": "Aún no hay explicadores globales", diff --git a/DashAI/front/src/utils/i18n/locales/pt/models.json b/DashAI/front/src/utils/i18n/locales/pt/models.json index 5d6648013..8c4b11804 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/models.json +++ b/DashAI/front/src/utils/i18n/locales/pt/models.json @@ -103,6 +103,7 @@ "nameYourSession": "Nomeie sua Sessão", "selectDatasetAndPrepare": "Nomeie sua sessão, selecione um conjunto de dados e configure suas colunas e partições.", "noCompatibleModelsFound": "Nenhum modelo compatível encontrado", + "noConfigurationAvailable": "Nenhuma configuração disponível para esta execução", "dropModelHere": "Solte aqui para adicionar", "noDatasetPredictionsYet": "Ainda não há previsões de conjunto de dados", "noGlobalExplainersYet": "Ainda não há explicadores globais", diff --git a/DashAI/front/src/utils/i18n/locales/zh/models.json b/DashAI/front/src/utils/i18n/locales/zh/models.json index 5f6b13a9b..5b9f5062a 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/models.json +++ b/DashAI/front/src/utils/i18n/locales/zh/models.json @@ -102,6 +102,7 @@ "nameYourSession": "为会话命名", "selectDatasetAndPrepare": "为会话命名,选择数据集并配置列和划分。", "noCompatibleModelsFound": "未找到兼容模型", + "noConfigurationAvailable": "此运行没有可用的配置", "dropModelHere": "拖放此处以添加", "noDatasetPredictionsYet": "暂无数据集预测", "noGlobalExplainersYet": "暂无全局解释器", From 1321da122012aae087e08a180090a552b9174af7 Mon Sep 17 00:00:00 2001 From: Creylay Date: Mon, 13 Jul 2026 14:21:01 -0400 Subject: [PATCH 140/308] Refactor ResultsGraphs and ResultsGraphsPlot components: update axis handling for small multiples, improve layout margins, and enhance chart data structure for better visualization. --- .../results/components/ResultsGraphs.jsx | 4 ++-- .../results/components/ResultsGraphsPlot.jsx | 15 +++++++-------- .../pages/results/constants/graphsMaking.jsx | 19 ++++++++++--------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx index bc7635185..06a7cbef4 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx @@ -100,7 +100,7 @@ function ResultsGraphs({ // Bar view: one small chart per metric (small multiples) instead of // one combined chart, so metrics with different scales/ranges never // share an axis. Every run keeps the same color across all panels. - const { panels, legend, xaxis } = smallMultiplesMaking( + const { panels, legend, yaxis } = smallMultiplesMaking( finishedRuns, selectedMetrics, metricsKey, @@ -118,7 +118,7 @@ function ResultsGraphs({ ); const { generalLayout } = layoutMaking("heatmap", {}, theme); - setChartData({ generalLayout, bar: panels, legend, xaxis, heatmap }); + setChartData({ generalLayout, bar: panels, legend, yaxis, heatmap }); } catch (error) { enqueueSnackbar(t("models:error.errorProcesingExperimentResults"), { variant: "error", diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx index e0395351d..3be7543df 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx @@ -34,7 +34,7 @@ function ResultsGraphsPlot({ chartData }) { const panels = chartData.bar ?? []; const legend = chartData.legend ?? []; - const xaxis = chartData.xaxis; + const yaxis = chartData.yaxis; const heatmapData = chartData.heatmap ?? []; if (panels.length === 0 && heatmapData.length === 0) { @@ -46,7 +46,7 @@ function ResultsGraphsPlot({ chartData }) { const panelLayout = { autosize: true, height: 240, - margin: { l: 44, r: 12, t: 8, b: 64 }, + margin: { l: 110, r: 12, t: 8, b: 32 }, showlegend: false, paper_bgcolor: bgColor, plot_bgcolor: bgColor, @@ -58,16 +58,15 @@ function ResultsGraphsPlot({ chartData }) { }, xaxis: { gridcolor: gridColor, + zerolinecolor: gridColor, tickfont: { color: textColor, size: 10 }, - tickangle: -30, - automargin: true, - tickvals: xaxis?.tickvals, - ticktext: xaxis?.ticktext, }, yaxis: { gridcolor: gridColor, - zerolinecolor: gridColor, tickfont: { color: textColor, size: 10 }, + automargin: true, + tickvals: yaxis?.tickvals, + ticktext: yaxis?.ticktext, }, }; @@ -110,7 +109,7 @@ function ResultsGraphsPlot({ chartData }) { sx={{ display: "grid", gap: 3, - gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", + gridTemplateColumns: "repeat(auto-fill, minmax(420px, 1fr))", }} > {panels.map((panel) => ( diff --git a/DashAI/front/src/pages/results/constants/graphsMaking.jsx b/DashAI/front/src/pages/results/constants/graphsMaking.jsx index 16a909fbe..1f06ab381 100644 --- a/DashAI/front/src/pages/results/constants/graphsMaking.jsx +++ b/DashAI/front/src/pages/results/constants/graphsMaking.jsx @@ -73,11 +73,11 @@ function smallMultiplesMaking( const runLabels = fullRunLabels.map(truncate); const runColors = finishedRuns.map((_, idx) => colors[idx % colors.length]); - // Use numeric slots (not the run name) as the x category. Two different + // Use numeric slots (not the run name) as the category axis. Two different // runs of the same model (e.g. "BaggingClassifier_1"/"_2") often share the - // same truncated prefix — if the label itself were the x value, Plotly - // would treat them as the same category and merge their bars into one. - const xValues = finishedRuns.map((_, idx) => idx); + // same truncated prefix — if the label itself were the category value, + // Plotly would treat them as the same category and merge their bars. + const yValues = finishedRuns.map((_, idx) => idx); const panels = metrics.map((metric) => { const isInverse = metricsMetadata[metric]?.maximize === false; @@ -95,11 +95,12 @@ function smallMultiplesMaking( data: [ { type: "bar", - x: xValues, - y: values, + orientation: "h", + y: yValues, + x: values, customdata: fullRunLabels, marker: { color: runColors, opacity: 0.85 }, - hovertemplate: "%{customdata}
%{y:.4f}", + hovertemplate: "%{customdata}
%{x:.4f}", }, ], }; @@ -110,9 +111,9 @@ function smallMultiplesMaking( color: runColors[idx], })); - const xaxis = { tickvals: xValues, ticktext: runLabels }; + const yaxis = { tickvals: yValues, ticktext: runLabels }; - return { panels, legend, xaxis }; + return { panels, legend, yaxis }; } /** From 32c9a8a1713dd0a9e57685f0615f60be5d8bac7b Mon Sep 17 00:00:00 2001 From: Irozuku Date: Mon, 13 Jul 2026 14:39:28 -0400 Subject: [PATCH 141/308] fix: import visualizers keys from shared module --- .../src/components/notebooks/explorer/tabs/Results.jsx | 2 +- .../components/notebooks/explorer/useExplorerResults.jsx | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/DashAI/front/src/components/notebooks/explorer/tabs/Results.jsx b/DashAI/front/src/components/notebooks/explorer/tabs/Results.jsx index 18ffcf527..39c221244 100644 --- a/DashAI/front/src/components/notebooks/explorer/tabs/Results.jsx +++ b/DashAI/front/src/components/notebooks/explorer/tabs/Results.jsx @@ -1,6 +1,6 @@ import React from "react"; import { Box, CircularProgress } from "@mui/material"; -import { visualizersKeys } from "../useExplorerResults"; +import { visualizersKeys } from "../../../../utils/artifactVisualizerData"; import ImageVisualizer from "../visualizations/ImageVisualizer"; import PlotlyJsonVisualizer from "../visualizations/PlotlyJsonVisualizer"; import TabularVisualizer from "../visualizations/TabularVisualizer"; diff --git a/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx b/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx index 39d047781..8f6732cd6 100644 --- a/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx +++ b/DashAI/front/src/components/notebooks/explorer/useExplorerResults.jsx @@ -1,9 +1,6 @@ import { useState, useEffect } from "react"; import { getExplorerResults } from "../../../api/explorer"; -import { - artifactToVisualizerData, - visualizersKeys, -} from "../../../utils/artifactVisualizerData"; +import { artifactToVisualizerData } from "../../../utils/artifactVisualizerData"; /** * Hook to manage explorer results data @@ -53,5 +50,3 @@ export function useExplorerResults(explorer) { fetchExplorerResults, }; } - -export { visualizersKeys }; From babdd7afb96f0377506e6d023b9e81437793c789 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Mon, 13 Jul 2026 15:30:14 -0400 Subject: [PATCH 142/308] feat: add grad-cam dice-ml and lime dependencies --- pyproject.toml | 3 + uv.lock | 531 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 534 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b7b221db6..612ae1339 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,9 @@ dependencies = [ "pywebview", "openml", "oslo.concurrency", + "grad-cam>=1.5.5", + "dice-ml>=0.12", + "lime>=0.2.0.1", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 75de2dd8d..b9bea0266 100644 --- a/uv.lock +++ b/uv.lock @@ -1432,10 +1432,12 @@ dependencies = [ { name = "cmaes" }, { name = "controlnet-aux" }, { name = "datasets" }, + { name = "dice-ml" }, { name = "diffusers" }, { name = "evaluate" }, { name = "fastapi", extra = ["all"] }, { name = "filetype" }, + { name = "grad-cam" }, { name = "greenery" }, { name = "httpx" }, { name = "huey" }, @@ -1444,6 +1446,7 @@ dependencies = [ { name = "imblearn" }, { name = "joblib" }, { name = "kink" }, + { name = "lime" }, { name = "llvmlite" }, { name = "numba", version = "0.47.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, { name = "numba", version = "0.66.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or platform_machine != 'x86_64' or sys_platform != 'darwin' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, @@ -1527,10 +1530,12 @@ requires-dist = [ { name = "cmaes" }, { name = "controlnet-aux" }, { name = "datasets" }, + { name = "dice-ml", specifier = ">=0.12" }, { name = "diffusers" }, { name = "evaluate" }, { name = "fastapi", extras = ["all"] }, { name = "filetype" }, + { name = "grad-cam", specifier = ">=1.5.5" }, { name = "greenery", specifier = "==3.2" }, { name = "httpx" }, { name = "huey" }, @@ -1539,6 +1544,7 @@ requires-dist = [ { name = "imblearn" }, { name = "joblib" }, { name = "kink" }, + { name = "lime", specifier = ">=0.2.0.1" }, { name = "llama-cpp-python", marker = "extra == 'cpu'", index = "https://abetlen.github.io/llama-cpp-python/whl/cpu", conflict = { package = "dashai", extra = "cpu" } }, { name = "llama-cpp-python", marker = "extra == 'cuda'" }, { name = "llvmlite" }, @@ -1641,6 +1647,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/34/8cc73273414405086c58852916e4031812a6a30fe04c057e37ad99397b7f/detect_installer-0.1.0-py3-none-any.whl", hash = "sha256:034fb20fd665c36e6ba52b8821525ea07fb4f7f938cac459df889fb33801528a", size = 4539, upload-time = "2026-02-23T10:40:23.807Z" }, ] +[[package]] +name = "dice-ml" +version = "0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema" }, + { name = "lightgbm" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas" }, + { name = "raiutils" }, + { name = "scikit-learn" }, + { name = "tqdm" }, + { name = "xgboost", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "xgboost", version = "3.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/84/05049e71e51caf266c89f6eec4c93c90e0c086d3b75c30c7ffa4d4dd40dc/dice_ml-0.12.tar.gz", hash = "sha256:3e40771ef82ad1084ffe1dd098b801f9cd9d7cdf40efba1b85e38a615ae5a75b", size = 15024998, upload-time = "2025-07-13T17:35:33.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/a2/63c11da0358ac2e931b0ab2e2cf203d9a234beeb201d62708733f7f7eea7/dice_ml-0.12-py3-none-any.whl", hash = "sha256:77d8195a40e36ff82ffa4c7fc4d19f364f099497b1be46b2123c7396a2e4bbae", size = 2528224, upload-time = "2025-07-13T17:35:31.115Z" }, +] + [[package]] name = "diffusers" version = "0.39.0" @@ -2252,6 +2280,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl", hash = "sha256:929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216", size = 491326, upload-time = "2024-02-21T11:52:35.956Z" }, ] +[[package]] +name = "grad-cam" +version = "1.5.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "scikit-learn" }, + { name = "torch", version = "2.11.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torch", version = "2.12.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torch", version = "2.12.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "torchvision", version = "0.26.0+cu128", source = { registry = "https://download.pytorch.org/whl/cu128" }, marker = "extra == 'extra-6-dashai-cuda'" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version < '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.15' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1", source = { registry = "https://pypi.org/simple" }, marker = "(extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')" }, + { name = "torchvision", version = "0.27.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version >= '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu') or (python_full_version < '3.15' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version < '3.12' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu')" }, + { name = "tqdm" }, + { name = "ttach" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/b3/e8b060e69d4de4b4d8a86868762dbc1ecaa58affa538a8af201a38a408ef/grad-cam-1.5.5.tar.gz", hash = "sha256:690c433d226d35c89c9eb170462db204909cb06b39c7381e6880a49b6fc37015", size = 7783293, upload-time = "2025-04-07T05:13:54.984Z" } + [[package]] name = "greenery" version = "3.2" @@ -2820,6 +2874,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, ] +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "kink" version = "0.9.0" @@ -3061,6 +3143,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/06/e6/42a475bfca683b0cd5366f6dd06580062b7e567bb8534d225c877c2f14f3/librt-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bca1472acbd473eff61059b4409f802c5a1bcb4cd0344d06f939df9c4c125d40", size = 104282, upload-time = "2026-06-30T16:14:09.29Z" }, ] +[[package]] +name = "lightgbm" +version = "4.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/0b/a2e9f5c5da7ef047cc60cef37f86185088845e8433e54d2e7ed439cce8a3/lightgbm-4.6.0.tar.gz", hash = "sha256:cb1c59720eb569389c0ba74d14f52351b573af489f230032a1c9f314f8bab7fe", size = 1703705, upload-time = "2025-02-15T04:03:03.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/75/cffc9962cca296bc5536896b7e65b4a7cdeb8db208e71b9c0133c08f8f7e/lightgbm-4.6.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:b7a393de8a334d5c8e490df91270f0763f83f959574d504c7ccb9eee4aef70ed", size = 2010151, upload-time = "2025-02-15T04:02:50.961Z" }, + { url = "https://files.pythonhosted.org/packages/21/1b/550ee378512b78847930f5d74228ca1fdba2a7fbdeaac9aeccc085b0e257/lightgbm-4.6.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:2dafd98d4e02b844ceb0b61450a660681076b1ea6c7adb8c566dfd66832aafad", size = 1592172, upload-time = "2025-02-15T04:02:53.937Z" }, + { url = "https://files.pythonhosted.org/packages/64/41/4fbde2c3d29e25ee7c41d87df2f2e5eda65b431ee154d4d462c31041846c/lightgbm-4.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4d68712bbd2b57a0b14390cbf9376c1d5ed773fa2e71e099cac588703b590336", size = 3454567, upload-time = "2025-02-15T04:02:56.443Z" }, + { url = "https://files.pythonhosted.org/packages/42/86/dabda8fbcb1b00bcfb0003c3776e8ade1aa7b413dff0a2c08f457dace22f/lightgbm-4.6.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:cb19b5afea55b5b61cbb2131095f50538bd608a00655f23ad5d25ae3e3bf1c8d", size = 3569831, upload-time = "2025-02-15T04:02:58.925Z" }, + { url = "https://files.pythonhosted.org/packages/5e/23/f8b28ca248bb629b9e08f877dd2965d1994e1674a03d67cd10c5246da248/lightgbm-4.6.0-py3-none-win_amd64.whl", hash = "sha256:37089ee95664b6550a7189d887dbf098e3eadab03537e411f52c63c121e3ba4b", size = 1451509, upload-time = "2025-02-15T04:03:01.515Z" }, +] + [[package]] name = "lightning-utilities" version = "0.15.3" @@ -3074,6 +3177,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/f4/ead6e0e37209b07c9baa3e984ccdb0348ca370b77cea3aaea8ddbb097e00/lightning_utilities-0.15.3-py3-none-any.whl", hash = "sha256:6c55f1bee70084a1cbeaa41ada96e4b3a0fea5909e844dd335bd80f5a73c5f91", size = 31906, upload-time = "2026-02-22T14:48:52.488Z" }, ] +[[package]] +name = "lime" +version = "0.2.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib", version = "3.10.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "matplotlib", version = "3.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-image", version = "0.25.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-image", version = "0.26.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/86/91a13127d83d793ecb50eb75e716f76e6eda809b6803c5a4ff462339789e/lime-0.2.0.1.tar.gz", hash = "sha256:76960e4f055feb53e89b5022383bafc87b63f25bac6265984b0a333d1a57f781", size = 275719, upload-time = "2020-06-26T21:38:15.46Z" } + [[package]] name = "llama-cpp-python" version = "0.3.32" @@ -6161,6 +6284,41 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045, upload-time = "2025-02-11T15:09:24.162Z" }, ] +[[package]] +name = "raiutils" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'x86_64') or (python_full_version == '3.11.*' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.11' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version != '3.11.*' and platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.11' and sys_platform != 'darwin') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "pandas" }, + { name = "requests" }, + { name = "scikit-learn" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/4b/ec9518b3f59b38e14be6db4863bfe021e05fab8434bd883f416fbea93351/raiutils-0.4.2.tar.gz", hash = "sha256:d210a4d5a059e48388d341ee02cb87f3c92bbf1f0bcbcecf04fd93a599d2dca4", size = 19817, upload-time = "2024-04-15T21:13:58.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/81/dde454fb014545f8e3b35b49947e9093f255e95a6ebc2883c75e6d9f8598/raiutils-0.4.2-py3-none-any.whl", hash = "sha256:69b8966c1f5f9ba8e5c4b8ff802b3cd3a379f3a1234f9e412369315d87998192", size = 17554, upload-time = "2024-04-15T21:13:57.117Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "rpds-py", version = "2026.6.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "typing-extensions", marker = "python_full_version < '3.13' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "regex" version = "2026.6.28" @@ -6463,6 +6621,291 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/0c/0c411a0ec64ccb6d104dcabe0e713e05e153a9a2c3c2bd2b32ce412166fe/rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288", size = 370490, upload-time = "2025-11-30T20:21:33.256Z" }, + { url = "https://files.pythonhosted.org/packages/19/6a/4ba3d0fb7297ebae71171822554abe48d7cab29c28b8f9f2c04b79988c05/rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00", size = 359751, upload-time = "2025-11-30T20:21:34.591Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7c/e4933565ef7f7a0818985d87c15d9d273f1a649afa6a52ea35ad011195ea/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6", size = 389696, upload-time = "2025-11-30T20:21:36.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/01/6271a2511ad0815f00f7ed4390cf2567bec1d4b1da39e2c27a41e6e3b4de/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7", size = 403136, upload-time = "2025-11-30T20:21:37.728Z" }, + { url = "https://files.pythonhosted.org/packages/55/64/c857eb7cd7541e9b4eee9d49c196e833128a55b89a9850a9c9ac33ccf897/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324", size = 524699, upload-time = "2025-11-30T20:21:38.92Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ed/94816543404078af9ab26159c44f9e98e20fe47e2126d5d32c9d9948d10a/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df", size = 412022, upload-time = "2025-11-30T20:21:40.407Z" }, + { url = "https://files.pythonhosted.org/packages/61/b5/707f6cf0066a6412aacc11d17920ea2e19e5b2f04081c64526eb35b5c6e7/rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3", size = 390522, upload-time = "2025-11-30T20:21:42.17Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/57a85fda37a229ff4226f8cbcf09f2a455d1ed20e802ce5b2b4a7f5ed053/rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221", size = 404579, upload-time = "2025-11-30T20:21:43.769Z" }, + { url = "https://files.pythonhosted.org/packages/f9/da/c9339293513ec680a721e0e16bf2bac3db6e5d7e922488de471308349bba/rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7", size = 421305, upload-time = "2025-11-30T20:21:44.994Z" }, + { url = "https://files.pythonhosted.org/packages/f9/be/522cb84751114f4ad9d822ff5a1aa3c98006341895d5f084779b99596e5c/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff", size = 572503, upload-time = "2025-11-30T20:21:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9b/de879f7e7ceddc973ea6e4629e9b380213a6938a249e94b0cdbcc325bb66/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7", size = 598322, upload-time = "2025-11-30T20:21:48.709Z" }, + { url = "https://files.pythonhosted.org/packages/48/ac/f01fc22efec3f37d8a914fc1b2fb9bcafd56a299edbe96406f3053edea5a/rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139", size = 560792, upload-time = "2025-11-30T20:21:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/da/4e2b19d0f131f35b6146425f846563d0ce036763e38913d917187307a671/rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464", size = 221901, upload-time = "2025-11-30T20:21:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/96/cb/156d7a5cf4f78a7cc571465d8aec7a3c447c94f6749c5123f08438bcf7bc/rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169", size = 235823, upload-time = "2025-11-30T20:21:52.505Z" }, + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/1f/a2dca5ffdbf1d475ffc4e80e4d5d720ff3a00f691795910116960ee12511/rpds_py-2026.6.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", size = 342174, upload-time = "2026-06-30T07:14:54.821Z" }, + { url = "https://files.pythonhosted.org/packages/4d/dc/323d08583c0832911768663d1944f0107fcd4088704858d84b5e06d105a0/rpds_py-2026.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", size = 345513, upload-time = "2026-06-30T07:14:56.515Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2a/e31989834d18d2f26ec1d2774c5b1eb3331df4ea8ada525175294c94b48a/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", size = 373783, upload-time = "2026-06-30T07:14:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/87/fe/e80107ee3639585c9941c17d6a42cd65325022f656c023191fce78c324c8/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", size = 378316, upload-time = "2026-06-30T07:14:59.077Z" }, + { url = "https://files.pythonhosted.org/packages/22/6f/81e3adf81acfb6fa694de2a6e4e7d8863121e3e0799e0a7725e6cf5679c4/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", size = 499423, upload-time = "2026-06-30T07:15:00.488Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9a/41263969df0ce3d9af2a96d5005a288200af1989aed3354bfceb5fc0b21f/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", size = 386077, upload-time = "2026-06-30T07:15:01.911Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/7e98f468bd50346faff5b10e5297374b443bfdddacc8e9fbc65984539597/rpds_py-2026.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", size = 371315, upload-time = "2026-06-30T07:15:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/99/3c/2b973b4d371906a134b03decfea7f5d9835a2c6d263454392e15b64b5b18/rpds_py-2026.6.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", size = 383502, upload-time = "2026-06-30T07:15:04.627Z" }, + { url = "https://files.pythonhosted.org/packages/98/2a/12e2799500af0a307bca76b63361c51f9fe479223561489c29eea1f2ee41/rpds_py-2026.6.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", size = 402673, upload-time = "2026-06-30T07:15:05.856Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/21e5872d165fe08be4f229e3d5ee9d90019c0bf0e5538de60dbd54009450/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", size = 549964, upload-time = "2026-06-30T07:15:07.159Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d0/5ee0fe36844297de8123bee27bc12078c1a7416ad9f1b8a8ca18d6b0c0ac/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", size = 615446, upload-time = "2026-06-30T07:15:08.531Z" }, + { url = "https://files.pythonhosted.org/packages/b1/80/1ea5873cb683f2fbe5f21b23ea1f6d179ead19f3c5b249b7eb5dca568ef2/rpds_py-2026.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", size = 576975, upload-time = "2026-06-30T07:15:09.97Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e1/90ef639217a5ddb15b7f4f61b1c33911fd044ad03c311bafdd2bcab85582/rpds_py-2026.6.3-cp311-cp311-win32.whl", hash = "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", size = 204453, upload-time = "2026-06-30T07:15:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b7/b7a1695d7af36f521fb11e80d6d3adbd744f73b921859bd3c2a2c0dc706f/rpds_py-2026.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", size = 223219, upload-time = "2026-06-30T07:15:12.476Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/145afacf796e4506062825941176ad9445c2dcf2b3b6a1f13d3030a15e19/rpds_py-2026.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", size = 219137, upload-time = "2026-06-30T07:15:13.631Z" }, + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, + { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, + { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, + { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" }, + { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" }, + { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" }, + { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" }, + { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504, upload-time = "2026-06-30T07:16:16.893Z" }, + { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380, upload-time = "2026-06-30T07:16:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976, upload-time = "2026-06-30T07:16:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" }, + { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" }, + { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" }, + { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068, upload-time = "2026-06-30T07:16:41.316Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600, upload-time = "2026-06-30T07:16:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" }, + { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" }, + { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" }, + { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" }, + { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" }, + { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864, upload-time = "2026-06-30T07:17:05.746Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430, upload-time = "2026-06-30T07:17:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877, upload-time = "2026-06-30T07:17:09.008Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" }, + { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" }, + { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" }, + { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" }, + { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" }, + { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, + { url = "https://files.pythonhosted.org/packages/b4/9c/f0d19ac587fd0e4ab6b72cda355e9c5a6166b01ef7e064e437aef8eb9fef/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", size = 349791, upload-time = "2026-06-30T07:17:33.315Z" }, + { url = "https://files.pythonhosted.org/packages/38/c7/1d49d204c9fd2ee6c537601dc4c1ba921e03363ca576bfab94a00254ac9a/rpds_py-2026.6.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", size = 352842, upload-time = "2026-06-30T07:17:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e5/c0b5dc93cd0d4c06ce1f438907649514e2ea077bcd911e3154a51e96c38e/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", size = 382094, upload-time = "2026-06-30T07:17:36.514Z" }, + { url = "https://files.pythonhosted.org/packages/0d/54/ec0e907b4ca8d541112db352409bd15f871c9b243e0c92c9b5a46ae96f01/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", size = 388662, upload-time = "2026-06-30T07:17:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f4/921c22a4fd0f1c1ac13a3996ffbf0aa67951e2c8ad0d1d9574938a2932e8/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", size = 504896, upload-time = "2026-06-30T07:17:39.689Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1b/a114b972cefa1ab1cdb3c7bb177cd3844a12826c507c722d3a73516dbbaf/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", size = 391545, upload-time = "2026-06-30T07:17:41.336Z" }, + { url = "https://files.pythonhosted.org/packages/4e/98/af9b3db77d47fcbe6c8c1f36e2c2147ec70292819e99c325f871584a1c11/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", size = 380059, upload-time = "2026-06-30T07:17:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ba/0efd8668b97c1d26a61566386c636a7a7a09829e474fdf807caa15a2c844/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", size = 393235, upload-time = "2026-06-30T07:17:44.637Z" }, + { url = "https://files.pythonhosted.org/packages/62/90/8c139ee9690f73b0829f32647de6f40d826f8f443af6fa72644f96351aac/rpds_py-2026.6.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", size = 413008, upload-time = "2026-06-30T07:17:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/0043896fdd7828ce09a1d9a8b06433714d0960fc4ff3fc4aa72b666b764e/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", size = 558118, upload-time = "2026-06-30T07:17:47.759Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/02355f0e134f783a8f9814c4680a1bd311d37671577a5964ea838573ff37/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", size = 623138, upload-time = "2026-06-30T07:17:49.355Z" }, + { url = "https://files.pythonhosted.org/packages/10/85/48f0abdcef5cce4e034c7a5b0ceeceba0b01bf0d942824f4bb720afe2dec/rpds_py-2026.6.3-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", size = 586486, upload-time = "2026-06-30T07:17:51.141Z" }, +] + [[package]] name = "ruff" version = "0.15.20" @@ -8294,6 +8737,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, ] +[[package]] +name = "ttach" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/5d/4c49e0eca4206bc25eff4ba89cee51b781466e2e3aad2f1057fd5d2634be/ttach-0.0.3.tar.gz", hash = "sha256:120c4dd881feb0e9c8dd63b154f2655891c3e20689b68a94d162bfd5557bcb48", size = 9600, upload-time = "2020-07-09T14:44:09.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/a3/ee48a184a185c1897c582c72240c2c8a0d0aeb5f8051a71d4e4cd930c52d/ttach-0.0.3-py3-none-any.whl", hash = "sha256:7000bb4334f856b0c79a341df386c92f1c76faf091043cc3cd7f541d2149faf8", size = 9839, upload-time = "2020-07-09T14:44:08.006Z" }, +] + [[package]] name = "typer" version = "0.26.8" @@ -8922,6 +9374,85 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/d4/225027a913621a879b429a043674aa35220e6ce67785acad4f7bd0c4ff33/xarray_einstats-0.10.0-py3-none-any.whl", hash = "sha256:fa3169b46cee29092db820d8bbc203148bada4fc970ee75e62cbf3dd7c5a8945", size = 39099, upload-time = "2026-02-19T18:13:53.174Z" }, ] +[[package]] +name = "xgboost" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.11.*' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version < '3.11' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", +] +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nccl-cu12", marker = "(python_full_version < '3.12' and sys_platform == 'linux') or (python_full_version >= '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.11.*' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/bb/1eb0242409d22db725d7a88088e6cfd6556829fb0736f9ff69aa9f1e9455/xgboost-3.2.0.tar.gz", hash = "sha256:99b0e9a2a64896cdaf509c5e46372d336c692406646d20f2af505003c0c5d70d", size = 1263936, upload-time = "2026-02-10T11:03:05.542Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/49/6e4cdd877c24adf56cb3586bc96d93d4dcd780b5ea1efb32e1ee0de08bae/xgboost-3.2.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:2f661966d3e322536d9c448090a870fcba1e32ee5760c10b7c46bac7a342079a", size = 2507014, upload-time = "2026-02-10T10:50:57.44Z" }, + { url = "https://files.pythonhosted.org/packages/93/f1/c09ef1add609453aa3ba5bafcd0d1c1a805c1263c0b60138ec968f8ec296/xgboost-3.2.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:eabbd40d474b8dbf6cb3536325f9150b9e6f0db32d18de9914fb3227d0bef5b7", size = 2328527, upload-time = "2026-02-10T10:51:17.502Z" }, + { url = "https://files.pythonhosted.org/packages/96/9f/d9914a7b8df842832850b1a18e5f47aaa071c217cdd1da2ae9deb291018b/xgboost-3.2.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:852eabc6d3b3702a59bf78dbfdcd1cb9c4d3a3b6e5ed1f8781d8b9512354fdd2", size = 131100954, upload-time = "2026-02-10T11:02:42.704Z" }, + { url = "https://files.pythonhosted.org/packages/79/98/679de17c2caa4fd3b0b4386ecf7377301702cb0afb22930a07c142fcb1d8/xgboost-3.2.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:99b4a6bbcb47212fec5cf5fbe12347215f073c08967431b0122cfbd1ee70312c", size = 131748579, upload-time = "2026-02-10T10:54:40.424Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/1661dd114a914a67e3f7ab66fa1382e7599c2a8c340f314ad30a3e2b4d08/xgboost-3.2.0-py3-none-win_amd64.whl", hash = "sha256:0d169736fd836fc13646c7ab787167b3a8110351c2c6bc770c755ee1618f0442", size = 101681668, upload-time = "2026-02-10T10:59:31.202Z" }, +] + +[[package]] +name = "xgboost" +version = "3.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine != 'x86_64' and sys_platform == 'darwin' and extra == 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version >= '3.15' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.14.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.13.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "python_full_version == '3.12.*' and platform_machine == 'x86_64' and sys_platform == 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda'", + "(python_full_version >= '3.15' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version >= '3.15' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.14.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.14.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.13.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.13.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", + "(python_full_version == '3.12.*' and platform_machine != 'x86_64' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda') or (python_full_version == '3.12.*' and sys_platform != 'darwin' and extra != 'extra-6-dashai-cpu' and extra != 'extra-6-dashai-cuda')", +] +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine != 'x86_64') or (python_full_version < '3.12' and platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (python_full_version >= '3.12' and sys_platform != 'darwin') or (platform_machine == 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "numpy", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.12' and platform_machine == 'x86_64' and sys_platform == 'darwin') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (platform_machine != 'x86_64' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'darwin' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "nvidia-nccl-cu12", marker = "(python_full_version >= '3.12' and sys_platform == 'linux') or (python_full_version < '3.12' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda') or (sys_platform != 'linux' and extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, + { name = "scipy", version = "1.18.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12' or (extra == 'extra-6-dashai-cpu' and extra == 'extra-6-dashai-cuda')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/41/846d4de2b8fc694073fd3ac5052caf68caa1ea11cb7fa32d7ad9c049b232/xgboost-3.3.0.tar.gz", hash = "sha256:58bcb8a4cace648cdab7b94fa4f16d2c9ff26d90dd4d26907168106fa06d8746", size = 1224702, upload-time = "2026-06-17T21:26:50.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/72/3b68983c0215ef65d48e9eeb1f168c3c6e3d62a61ece605de3209c79cae1/xgboost-3.3.0-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:07688a377046b8640897b62421150bf73c6cc7101823474ec6ad08b93290f587", size = 2553505, upload-time = "2026-06-17T21:21:32.146Z" }, + { url = "https://files.pythonhosted.org/packages/c9/62/b49e756822b29909d0c95ed334662dc6c7c81a99ec6bc10dc18e69f3d6e7/xgboost-3.3.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:af7cea10f418b7c251ddc8da440f57bdab2990b5fc9f74a35a92b0f150ea287d", size = 2376040, upload-time = "2026-06-17T21:22:01.981Z" }, + { url = "https://files.pythonhosted.org/packages/47/3a/a0adcd1ee28f525bd5c9dc3ebe78a7599bf97c22866d6449f967b829e338/xgboost-3.3.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:624a83aeb1e7ba081719795db179f4ce6fff12e79de05cd9baf15ee48fd22f0e", size = 98180629, upload-time = "2026-06-17T21:24:00.804Z" }, + { url = "https://files.pythonhosted.org/packages/47/1f/8b3e578cfd8e3bcdb4374e2bbe0b40b4e5320accb5cbdcf535ecc512eb5c/xgboost-3.3.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:f59edaf28eccd1c519788607c72ed907ee6cedfa933d706620bc1612d24b354e", size = 98716607, upload-time = "2026-06-17T21:26:21.058Z" }, + { url = "https://files.pythonhosted.org/packages/07/6b/087fd5d28fdbb90d385c50ee9308a820241b82feebdf42e72e19a48e4b32/xgboost-3.3.0-py3-none-win_amd64.whl", hash = "sha256:b06057f6a018fc04e6b3e0c15568ca636b8151a5b5f333478e500fcaf4fc7594", size = 69522696, upload-time = "2026-06-17T21:20:53.707Z" }, +] + [[package]] name = "xlrd" version = "2.0.2" From 9a3e11be22563f45ef529426172339ee6b250ff8 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Mon, 13 Jul 2026 15:43:25 -0400 Subject: [PATCH 143/308] feat: add ten explainers with typed artifact plots --- .../registry/component_registry.py | 4 +- .../explainers/contrastive_shap.py | 458 ++++++++++++++++++ .../explainers/dice_counterfactual.py | 452 +++++++++++++++++ .../explainability/explainers/grad_cam.py | 292 +++++++++++ .../explainers/image_explainer_utils.py | 170 +++++++ .../explainability/explainers/lime_text.py | 319 ++++++++++++ .../explainers/nearest_counterfactual.py | 414 ++++++++++++++++ .../explainers/occlusion_saliency.py | 347 +++++++++++++ .../explainers/regression_kernel_shap.py | 337 +++++++++++++ .../regression_partial_dependence.py | 244 ++++++++++ ...gression_permutation_feature_importance.py | 312 ++++++++++++ .../explainers/token_ablation.py | 358 ++++++++++++++ DashAI/back/initial_components.py | 30 ++ .../back/explainers/test_image_explainers.py | 151 ++++++ tests/back/explainers/test_lib_explainers.py | 175 +++++++ tests/back/explainers/test_new_explainers.py | 225 +++++++++ tests/back/explainers/test_task_explainers.py | 306 ++++++++++++ tests/back/registries/test_registry.py | 16 + 18 files changed, 4609 insertions(+), 1 deletion(-) create mode 100644 DashAI/back/explainability/explainers/contrastive_shap.py create mode 100644 DashAI/back/explainability/explainers/dice_counterfactual.py create mode 100644 DashAI/back/explainability/explainers/grad_cam.py create mode 100644 DashAI/back/explainability/explainers/image_explainer_utils.py create mode 100644 DashAI/back/explainability/explainers/lime_text.py create mode 100644 DashAI/back/explainability/explainers/nearest_counterfactual.py create mode 100644 DashAI/back/explainability/explainers/occlusion_saliency.py create mode 100644 DashAI/back/explainability/explainers/regression_kernel_shap.py create mode 100644 DashAI/back/explainability/explainers/regression_partial_dependence.py create mode 100644 DashAI/back/explainability/explainers/regression_permutation_feature_importance.py create mode 100644 DashAI/back/explainability/explainers/token_ablation.py create mode 100644 tests/back/explainers/test_image_explainers.py create mode 100644 tests/back/explainers/test_lib_explainers.py create mode 100644 tests/back/explainers/test_new_explainers.py create mode 100644 tests/back/explainers/test_task_explainers.py diff --git a/DashAI/back/dependencies/registry/component_registry.py b/DashAI/back/dependencies/registry/component_registry.py index 98da941f7..89b336b1b 100644 --- a/DashAI/back/dependencies/registry/component_registry.py +++ b/DashAI/back/dependencies/registry/component_registry.py @@ -454,7 +454,8 @@ def get_related_components(self, component_id: str) -> List[Dict[str, Any]]: """Obtain any related component of the given component name. If the component has no related components, then the method returns an empty - list. + list. Related names that are not registered components (e.g. an explainer + declared by a model but provided by an uninstalled plugin) are skipped. Parameters ---------- @@ -479,4 +480,5 @@ def get_related_components(self, component_id: str) -> List[Dict[str, Any]]: return [ self.__getitem__(related_component_id) for related_component_id in self._relationship_manager[component_id] + if self.__contains__(related_component_id) ] diff --git a/DashAI/back/explainability/explainers/contrastive_shap.py b/DashAI/back/explainability/explainers/contrastive_shap.py new file mode 100644 index 000000000..4b2ef9f7f --- /dev/null +++ b/DashAI/back/explainability/explainers/contrastive_shap.py @@ -0,0 +1,458 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact, TextArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + bool_field, + float_field, + schema_field, + string_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class ContrastiveShapSchema(BaseSchema): + """Schema for ContrastiveShap explainer hyperparameters. + + Configures the foil class the explanation contrasts against and the + background sampling used to fit the underlying SHAP explainer. + """ + + foil_class: schema_field( + string_field(), + placeholder="second_most_probable", + description=MultilingualString( + en=( + "Class to contrast against (the foil in 'why P rather than " + "Q?'). Enter an exact class name, or leave " + "'second_most_probable' to contrast against the runner-up " + "class of each instance." + ), + es=( + "Clase contra la que se contrasta (el foil en '¿por qué P y " + "no Q?'). Ingrese un nombre de clase exacto, o deje " + "'second_most_probable' para contrastar con la segunda clase " + "más probable de cada instancia." + ), + pt=( + "Classe contra a qual contrastar (o foil em 'por que P e não " + "Q?'). Insira um nome de classe exato, ou deixe " + "'second_most_probable' para contrastar com a segunda classe " + "mais provável de cada instância." + ), + zh=( + "对比的目标类别('为什么是P而不是Q'中的Q)。" + "输入准确的类别名称,或保留'second_most_probable'以对比每个实例的第二可能类别。" + ), + de=( + "Klasse, gegen die kontrastiert wird (das Foil in 'warum P " + "statt Q?'). Geben Sie einen exakten Klassennamen ein oder " + "belassen Sie 'second_most_probable', um gegen die " + "zweitwahrscheinlichste Klasse zu kontrastieren." + ), + ), + alias=MultilingualString( + en="Foil class", + es="Clase foil", + pt="Classe foil", + zh="对比类别", + de="Foil-Klasse", + ), + ) # type: ignore + + fit_parameter_sample_background_data: schema_field( + bool_field(), + placeholder=True, + description=MultilingualString( + en=( + "'true' if background data must be sampled; otherwise the " + "entire training set is used. Smaller datasets speed up the " + "algorithm runtime." + ), + es=( + "'true' si se deben muestrear los datos de fondo; de lo " + "contrario se usa el conjunto de entrenamiento completo. " + "Conjuntos más pequeños reducen el tiempo de ejecución." + ), + pt=( + "'true' se os dados de fundo devem ser amostrados; caso " + "contrário, usa-se o conjunto de treinamento completo. " + "Conjuntos menores reduzem o tempo de execução." + ), + zh=( + "如果需要对背景数据进行采样则为'true';否则使用整个训练集。较小的数据集可加速算法运行。" + ), + de=( + "'true', wenn Hintergrunddaten gesamplet werden müssen; sonst " + "wird der gesamte Trainingssatz verwendet. Kleinere " + "Datensätze beschleunigen die Laufzeit." + ), + ), + alias=MultilingualString( + en="Sample background data", + es="Muestrear datos de fondo", + pt="Amostrar dados de fundo", + zh="采样背景数据", + de="Hintergrunddaten samplen", + ), + ) # type: ignore + + fit_parameter_background_fraction: schema_field( + float_field(ge=0, le=1), + placeholder=0.2, + description=MultilingualString( + en=( + "If 'Sample background data' is selected, fraction of " + "background samples to draw from the training set." + ), + es=( + "Si se selecciona 'Muestrear datos de fondo', proporción de " + "muestras de fondo a extraer del conjunto de entrenamiento." + ), + pt=( + "Se 'Amostrar dados de fundo' estiver selecionado, fração de " + "amostras de fundo a extrair do conjunto de treinamento." + ), + zh=("如果选择了'采样背景数据',则为从训练集中抽取的背景样本比例。"), + de=( + "Wenn 'Hintergrunddaten samplen' ausgewählt ist, Anteil der " + "Hintergrundproben aus dem Trainingssatz." + ), + ), + alias=MultilingualString( + en="Background fraction", + es="Fracción de fondo", + pt="Fração de fundo", + zh="背景比例", + de="Hintergrundfraktion", + ), + ) # type: ignore + + +class ContrastiveShap(BaseLocalExplainer): + """Contrastive local explainer: why class P rather than class Q? + + Standard attribution methods answer "why did the model predict P?". + Contrastive explanations answer the question people actually ask: "why P + rather than Q?". This explainer computes Kernel SHAP attributions for both + the predicted class (the fact) and a contrast class (the foil), and + reports the per-feature difference. Features with a large positive delta + are the ones that pushed the model towards the fact and away from the + foil. + + The foil can be a fixed class name, or the second most probable class of + each instance (default). + + References + ---------- + - [1] Miller, T. (2019). "Explanation in Artificial Intelligence: + Insights from the Social Sciences." Artificial Intelligence 267. + https://arxiv.org/abs/1706.07269 + - [2] Lundberg, S.M. & Lee, S.I. (2017). "A Unified Approach to + Interpreting Model Predictions." NeurIPS 30. + https://arxiv.org/abs/1705.07874 + """ + + COMPATIBLE_COMPONENTS = ["TabularClassificationTask"] + DISPLAY_NAME = MultilingualString( + en="Contrastive SHAP (why P rather than Q)", + es="SHAP contrastivo (por qué P y no Q)", + pt="SHAP contrastivo (por que P e não Q)", + zh="对比SHAP(为什么是P而不是Q)", + de="Kontrastives SHAP (warum P statt Q)", + ) + DESCRIPTION = MultilingualString( + en=( + "Explains why the model predicted one class rather than another " + "by contrasting SHAP attributions between the two classes." + ), + es=( + "Explica por qué el modelo predijo una clase y no otra, " + "contrastando las atribuciones SHAP entre ambas clases." + ), + pt=( + "Explica por que o modelo previu uma classe e não outra, " + "contrastando as atribuições SHAP entre as duas classes." + ), + zh=("通过对比两个类别之间的SHAP归因,解释模型为什么预测一个类别而不是另一个。"), + de=( + "Erklärt, warum das Modell eine Klasse statt einer anderen " + "vorhergesagt hat, durch Kontrastierung der SHAP-Attributionen " + "beider Klassen." + ), + ) + COLOR = "#00695C" + SCHEMA = ContrastiveShapSchema + + def __init__( + self, + model: BaseModel, + foil_class: str = "second_most_probable", + ) -> None: + """Initialize a new instance of a ContrastiveShap explainer. + + Parameters + ---------- + model : BaseModel + Model to be explained. + foil_class : str + Name of the class to contrast against, or + 'second_most_probable' to use the runner-up class per instance. + """ + super().__init__(model) + self.foil_class = foil_class + + def fit( + self, + background_dataset, + sample_background_data=False, + background_fraction=None, + **kwargs, + ): + """Fit the underlying Kernel SHAP explainer on background data. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits; the train split is used + as SHAP background data. + sample_background_data : bool + True if the background data must be sampled. + background_fraction : float + Fraction of the training samples used as background data when + ``sample_background_data`` is True. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + ContrastiveShap + The fitted explainer instance (``self``). + """ + import shap + + x, y = background_dataset + x_train = x["train"] + y_train = y["train"] + + background_data = x_train.to_pandas() + feature_names = list(x_train.column_names) + + if bool(sample_background_data) and background_fraction: + n_samples = max(1, int(background_fraction * len(background_data))) + background_data = shap.sample(background_data, n_samples) + + self.explainer = shap.KernelExplainer( + model=self.model.predict, + data=background_data, + feature_names=feature_names, + ) + + output_column = y_train.column_names[0] + target_names = y_train.types[output_column].categories + self.metadata = { + "feature_names": feature_names, + "target_names": list(target_names), + } + + return self + + def _resolve_foil(self, prediction, fact_class: int) -> int: + """Resolve the foil class index for one instance. + + Parameters + ---------- + prediction : np.ndarray + Per-class probabilities for the instance. + fact_class : int + Index of the predicted (fact) class. + + Returns + ------- + int + Index of the foil class. Falls back to the second most probable + class when the configured name is unknown or equals the fact. + """ + import numpy as np + + target_names = self.metadata["target_names"] + if self.foil_class in target_names: + foil = target_names.index(self.foil_class) + if foil != fact_class: + return foil + + order = np.argsort(prediction)[::-1] + return int(order[1]) if len(order) > 1 else fact_class + + def explain_instance(self, instances): + """Compute contrastive SHAP attributions for the given instances. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained. + + Returns + ------- + dict + Dictionary with, for each instance, the fact and foil classes and + the per-feature attribution difference (fact minus foil). + """ + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + + dataset = to_dashai_dataset(instances) + X = dataset.to_pandas() + + predictions = np.asarray(self.model.predict(dataset)) + + shap_values = self.explainer.shap_values(X=X) + # (n_instances, n_features, n_classes) -> (n_instances, n_classes, + # n_features), same normalization used by the KernelShap explainer. + shap_values = np.array(shap_values).transpose(0, 2, 1) + + explanation = {"metadata": self.metadata} + for i, (instance, prediction, contributions) in enumerate( + zip(X.to_numpy(), predictions, shap_values) # noqa: B905 + ): + fact_class = int(np.argmax(prediction)) + foil_class = self._resolve_foil(prediction, fact_class) + delta = contributions[fact_class] - contributions[foil_class] + + explanation[i] = { + "instance_values": instance.tolist(), + "model_prediction": prediction.tolist(), + "fact_class": fact_class, + "foil_class": foil_class, + "fact_shap_values": np.round(contributions[fact_class], 3).tolist(), + "foil_shap_values": np.round(contributions[foil_class], 3).tolist(), + "delta_values": np.round(delta, 3).tolist(), + } + + return explanation + + def _create_plot(self, data, fact_name, foil_name, fact_prob, foil_prob): + """Create the contrastive bar plot for one instance. + + Parameters + ---------- + data : pd.DataFrame + Dataframe with 'label' and 'delta' columns, sorted for plotting. + fact_name : str + Name of the predicted class. + foil_name : str + Name of the foil class. + fact_prob : float + Predicted probability of the fact class. + foil_prob : float + Predicted probability of the foil class. + + Returns + ------- + plotly.graph_objs.Figure + The Plotly figure. + """ + import plotly.graph_objs as go + + colors = [ + "rgb(231,63,116)" if value >= 0 else "rgb(47,138,196)" + for value in data["delta"] + ] + + fig = go.Figure( + go.Bar( + x=data["delta"], + y=data["label"], + orientation="h", + marker={"color": colors}, + text=data["delta"], + textposition="auto", + ) + ) + + fig.update_layout( + title={ + "text": ( + f"Why {fact_name} (p={fact_prob}) rather than " + f"{foil_name} (p={foil_prob})?" + ), + "font": {"size": 14}, + }, + margin={"pad": 20, "l": 100, "r": 60, "t": 60, "b": 40}, + xaxis={"title_text": "Attribution difference (fact - foil)"}, + yaxis={"showgrid": True}, + ) + + return fig + + def plot(self, explanation: dict) -> List[Artifact]: + """Render each instance as a contrastive bar plot plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[Artifact] + A list of typed artifacts: one plotly and one text artifact per + explained instance. + """ + import numpy as np + import pandas as pd + + exp = explanation.copy() + metadata = exp.pop("metadata") + feature_names = metadata["feature_names"] + target_names = metadata["target_names"] + max_features = 8 + + artifacts = [] + for i in exp: + instance = exp[i] + fact_class = instance["fact_class"] + foil_class = instance["foil_class"] + fact_name = target_names[fact_class] + foil_name = target_names[foil_class] + prediction = instance["model_prediction"] + fact_prob = float(np.round(prediction[fact_class], 3)) + foil_prob = float(np.round(prediction[foil_class], 3)) + + data = pd.DataFrame( + { + "features": feature_names, + "values": instance["instance_values"], + "delta": instance["delta_values"], + } + ) + data["delta_abs"] = data["delta"].abs() + data = data.sort_values(by="delta_abs", ascending=True) + if len(data) > max_features: + data = data.iloc[-max_features:, :] + data["label"] = data["features"] + "=" + data["values"].map(str) + + title = f"Instance {int(i) + 1}" + fig = self._create_plot(data, fact_name, foil_name, fact_prob, foil_prob) + artifacts.append(PlotlyArtifact(payload=fig, title=title)) + + top = data.iloc[::-1].head(3) + top_features = ", ".join( + f"{feature}={value}" + for feature, value in zip( + top["features"].tolist(), + top["values"].tolist(), + strict=True, + ) + ) + summary = ( + f"The model predicted {fact_name} (p={fact_prob}) rather than " + f"{foil_name} (p={foil_prob}) mainly because of: " + f"{top_features}." + ) + artifacts.append(TextArtifact(payload=summary, title=title)) + + return artifacts diff --git a/DashAI/back/explainability/explainers/dice_counterfactual.py b/DashAI/back/explainability/explainers/dice_counterfactual.py new file mode 100644 index 000000000..d7fc87712 --- /dev/null +++ b/DashAI/back/explainability/explainers/dice_counterfactual.py @@ -0,0 +1,452 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + Artifact, + TableArtifact, + TablePayload, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + int_field, + schema_field, + string_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class DiceCounterfactualSchema(BaseSchema): + """Schema for the DiCE counterfactual explainer hyperparameters. + + Configures how many counterfactuals are generated, the generation + method and the class the counterfactuals should reach. + """ + + total_cfs: schema_field( + int_field(ge=1, le=10), + placeholder=3, + description=MultilingualString( + en="Number of counterfactual examples to generate per instance.", + es="Número de ejemplos contrafactuales a generar por instancia.", + pt="Número de exemplos contrafactuais a gerar por instância.", + zh="为每个实例生成的反事实示例数量。", + de="Anzahl der pro Instanz erzeugten kontrafaktischen Beispiele.", + ), + alias=MultilingualString( + en="Number of counterfactuals", + es="Número de contrafactuales", + pt="Número de contrafactuais", + zh="反事实数量", + de="Anzahl kontrafaktischer Beispiele", + ), + ) # type: ignore + + method: schema_field( + enum_field(enum=["random", "genetic", "kdtree"]), + placeholder="random", + description=MultilingualString( + en=( + "Counterfactual search strategy: 'random' (random sampling of " + "feature perturbations), 'genetic' (genetic algorithm " + "optimizing proximity and diversity) or 'kdtree' (closest " + "real training examples)." + ), + es=( + "Estrategia de búsqueda: 'random' (muestreo aleatorio de " + "perturbaciones), 'genetic' (algoritmo genético que optimiza " + "proximidad y diversidad) o 'kdtree' (ejemplos reales más " + "cercanos del entrenamiento)." + ), + pt=( + "Estratégia de busca: 'random' (amostragem aleatória de " + "perturbações), 'genetic' (algoritmo genético que otimiza " + "proximidade e diversidade) ou 'kdtree' (exemplos reais mais " + "próximos do treinamento)." + ), + zh=( + "反事实搜索策略:'random'(随机采样特征扰动)、" + "'genetic'(优化接近度和多样性的遗传算法)或'kdtree'(最近的真实训练样本)。" + ), + de=( + "Suchstrategie: 'random' (zufällige Merkmalsstörungen), " + "'genetic' (genetischer Algorithmus für Nähe und Diversität) " + "oder 'kdtree' (nächstgelegene echte Trainingsbeispiele)." + ), + ), + alias=MultilingualString( + en="Search method", + es="Método de búsqueda", + pt="Método de busca", + zh="搜索方法", + de="Suchmethode", + ), + ) # type: ignore + + desired_class: schema_field( + string_field(), + placeholder="opposite", + description=MultilingualString( + en=( + "Class the counterfactuals should reach. Enter an exact class " + "name, or leave 'opposite' to target the runner-up class of " + "each instance." + ), + es=( + "Clase que los contrafactuales deben alcanzar. Ingrese un " + "nombre de clase exacto, o deje 'opposite' para apuntar a la " + "segunda clase más probable de cada instancia." + ), + pt=( + "Classe que os contrafactuais devem alcançar. Insira um nome " + "de classe exato, ou deixe 'opposite' para apontar à segunda " + "classe mais provável de cada instância." + ), + zh="反事实应达到的类别。输入准确的类别名称,或保留'opposite'以针对每个实例的第二可能类别。", + de=( + "Klasse, die die kontrafaktischen Beispiele erreichen sollen. " + "Geben Sie einen exakten Klassennamen ein oder belassen Sie " + "'opposite' für die zweitwahrscheinlichste Klasse." + ), + ), + alias=MultilingualString( + en="Desired class", + es="Clase deseada", + pt="Classe desejada", + zh="目标类别", + de="Zielklasse", + ), + ) # type: ignore + + +class _SklearnProbaShim: + """Adapter exposing the sklearn-native interface DiCE expects. + + DashAI classifiers override ``predict`` to return probabilities; DiCE + expects ``predict`` to return class labels and ``predict_proba`` to + return probabilities. + """ + + def __init__(self, model): + self._model = model + + def predict_proba(self, x): + """Return the class-probability matrix for ``x``.""" + return self._model.predict_proba(x) + + def predict(self, x): + """Return hard class labels derived from the probabilities.""" + import numpy as np + + return np.argmax(self._model.predict_proba(x), axis=1) + + +class DiceCounterfactual(BaseLocalExplainer): + """Diverse counterfactual explanations via the DiCE library. + + For each instance, generates a set of synthetic examples that the model + classifies as a different (desired) class while staying close to the + original instance, answering "what minimal changes would flip this + prediction?". Unlike the Nearest Counterfactual explainer (which returns + real training rows), DiCE synthesizes new feature combinations and + optimizes for both proximity and diversity. + + Note: DiCE queries the underlying estimator directly with raw feature + values, so it is intended for datasets with numeric features. + + References + ---------- + - [1] Mothilal, R.K., Sharma, A. & Tan, C. (2020). "Explaining Machine + Learning Classifiers through Diverse Counterfactual Explanations." + FAT* 2020. https://arxiv.org/abs/1905.07697 + - [2] https://github.com/interpretml/DiCE + """ + + DISPLAY_NAME = MultilingualString( + en="DiCE Counterfactuals", + es="Contrafactuales DiCE", + pt="Contrafactuais DiCE", + zh="DiCE反事实", + de="DiCE-Kontrafaktuale", + ) + DESCRIPTION = MultilingualString( + en=( + "Generates diverse synthetic examples with minimal changes that " + "flip the model's prediction to a desired class." + ), + es=( + "Genera ejemplos sintéticos diversos con cambios mínimos que " + "invierten la predicción del modelo hacia una clase deseada." + ), + pt=( + "Gera exemplos sintéticos diversos com mudanças mínimas que " + "invertem a previsão do modelo para uma classe desejada." + ), + zh="生成具有最小变化的多样化合成示例,将模型预测翻转到目标类别。", + de=( + "Erzeugt diverse synthetische Beispiele mit minimalen Änderungen, " + "die die Modellvorhersage zu einer gewünschten Klasse kippen." + ), + ) + COLOR = "#6A1B9A" + SCHEMA = DiceCounterfactualSchema + + def __init__( + self, + model: BaseModel, + total_cfs: int = 3, + method: str = "random", + desired_class: str = "opposite", + ) -> None: + """Initialize a new instance of a DiceCounterfactual explainer. + + Parameters + ---------- + model : BaseModel + Classification model to be explained. + total_cfs : int + Number of counterfactuals generated per instance. + method : str + DiCE search method: 'random', 'genetic' or 'kdtree'. + desired_class : str + Class name the counterfactuals should reach, or 'opposite' for + the runner-up class of each instance. + """ + super().__init__(model) + self.total_cfs = total_cfs + self.method = method + self.desired_class = desired_class + + def fit(self, background_dataset, **kwargs): + """Build the DiCE data and model interfaces from the train split. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + DiceCounterfactual + The fitted explainer instance (``self``). + """ + import dice_ml + import numpy as np + + x, y = background_dataset + x_train = x["train"] + y_train = y["train"] + + train_frame = x_train.to_pandas() + self.feature_names = list(train_frame.columns) + + output_column = y_train.column_names[0] + target_names = [str(c) for c in y_train.types[output_column].categories] + self.metadata = { + "feature_names": self.feature_names, + "target_names": target_names, + } + self.output_column = output_column + + labels = y_train.to_pandas()[output_column].astype(str) + encoded = labels.map({name: k for k, name in enumerate(target_names)}) + train_frame = train_frame.copy() + train_frame[output_column] = encoded.to_numpy() + + continuous = [ + column + for column in self.feature_names + if np.issubdtype(train_frame[column].dtype, np.number) + ] + + data_interface = dice_ml.Data( + dataframe=train_frame, + continuous_features=continuous, + outcome_name=output_column, + ) + model_interface = dice_ml.Model( + model=_SklearnProbaShim(self.model), + backend="sklearn", + model_type="classifier", + ) + self._dice = dice_ml.Dice(data_interface, model_interface, method=self.method) + + return self + + def _resolve_desired_class(self, prediction, fact_class: int): + """Resolve DiCE's desired_class argument for one instance. + + Parameters + ---------- + prediction : np.ndarray + Per-class probabilities for the instance. + fact_class : int + Index of the predicted class. + + Returns + ------- + int or str + A class index, or the literal 'opposite' for binary problems. + """ + import numpy as np + + target_names = self.metadata["target_names"] + if self.desired_class in target_names: + desired = target_names.index(self.desired_class) + if desired != fact_class: + return desired + + if len(target_names) == 2: + return "opposite" + order = np.argsort(prediction)[::-1] + return int(order[1]) + + def explain_instance(self, instances): + """Generate counterfactual examples for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained. + + Returns + ------- + dict + Dictionary with, for each instance, the model prediction and the + generated counterfactual examples. + """ + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + + dataset = to_dashai_dataset(instances) + X = dataset.to_pandas()[self.feature_names] + + predictions = np.asarray(self.model.predict(dataset)) + + explanation = {"metadata": self.metadata} + for i in range(len(X)): + row = X.iloc[[i]] + fact_class = int(np.argmax(predictions[i])) + desired = self._resolve_desired_class(predictions[i], fact_class) + + counterfactuals = [] + try: + result = self._dice.generate_counterfactuals( + row, + total_CFs=self.total_cfs, + desired_class=desired, + ) + cfs_frame = result.cf_examples_list[0].final_cfs_df + if cfs_frame is not None: + for _, cf_row in cfs_frame.iterrows(): + values = [cf_row[f] for f in self.feature_names] + changed = [ + feature + for j, feature in enumerate(self.feature_names) + if not np.isclose(float(values[j]), float(row.iloc[0, j])) + ] + counterfactuals.append( + { + "values": [float(v) for v in values], + "predicted_class": int(cf_row[self.output_column]), + "changed_features": changed, + } + ) + except Exception: # noqa: BLE001 - DiCE may fail to find CFs + counterfactuals = [] + + explanation[i] = { + "instance_values": row.iloc[0].tolist(), + "model_prediction": predictions[i].tolist(), + "predicted_class": fact_class, + "counterfactuals": counterfactuals, + } + + return explanation + + def plot(self, explanation: dict) -> List[Artifact]: + """Render each instance as a comparison table plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[Artifact] + A list of typed artifacts: one table and one text artifact per + explained instance. + """ + import numpy as np + + exp = explanation.copy() + metadata = exp.pop("metadata") + feature_names = metadata["feature_names"] + target_names = metadata["target_names"] + + artifacts = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + counterfactuals = instance["counterfactuals"] + + columns = ["Feature", "Instance"] + [ + f"Counterfactual {k + 1}" for k in range(len(counterfactuals)) + ] + rows = [] + highlight = [] + for row_idx, feature in enumerate(feature_names): + row = [feature, instance["instance_values"][row_idx]] + for cf_idx, counterfactual in enumerate(counterfactuals): + row.append(counterfactual["values"][row_idx]) + if feature in counterfactual["changed_features"]: + highlight.append({"row": row_idx, "column": 2 + cf_idx}) + rows.append(row) + + prediction_row = ["Predicted class", predicted_name] + [ + target_names[counterfactual["predicted_class"]] + for counterfactual in counterfactuals + ] + rows.append(prediction_row) + for cf_idx in range(len(counterfactuals)): + highlight.append({"row": len(feature_names), "column": 2 + cf_idx}) + + title = f"Instance {int(i) + 1}" + artifacts.append( + TableArtifact( + payload=TablePayload( + columns=columns, rows=rows, highlight=highlight + ), + title=title, + ) + ) + + if counterfactuals: + lines = [f"The model predicted {predicted_name} (p={predicted_prob})."] + for cf_idx, counterfactual in enumerate(counterfactuals): + cf_name = target_names[counterfactual["predicted_class"]] + changed = ", ".join(counterfactual["changed_features"]) or "nothing" + lines.append( + f"Counterfactual {cf_idx + 1}: changing {changed} " + f"yields {cf_name}." + ) + summary = "\n".join(lines) + else: + summary = ( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). DiCE could not generate " + "counterfactuals for this instance." + ) + artifacts.append(TextArtifact(payload=summary, title=title)) + + return artifacts diff --git a/DashAI/back/explainability/explainers/grad_cam.py b/DashAI/back/explainability/explainers/grad_cam.py new file mode 100644 index 000000000..616c6a513 --- /dev/null +++ b/DashAI/back/explainability/explainers/grad_cam.py @@ -0,0 +1,292 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, TextArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.explainers.image_explainer_utils import ( + get_target_names, + get_torch_module, + get_transform, + heatmap_overlay_artifact, + iter_pil_images, +) +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class GradCamSchema(BaseSchema): + """Schema for the Grad-CAM explainer hyperparameters. + + Configures the CAM variant used to compute the class activation map. + """ + + method: schema_field( + enum_field(enum=["gradcam", "gradcam++", "eigencam"]), + placeholder="gradcam", + description=MultilingualString( + en=( + "CAM variant: 'gradcam' (original), 'gradcam++' (better for " + "multiple occurrences of a class) or 'eigencam' " + "(gradient-free, first principal component of activations)." + ), + es=( + "Variante de CAM: 'gradcam' (original), 'gradcam++' (mejor " + "para múltiples ocurrencias de una clase) o 'eigencam' (sin " + "gradientes, primera componente principal de activaciones)." + ), + pt=( + "Variante de CAM: 'gradcam' (original), 'gradcam++' (melhor " + "para múltiplas ocorrências de uma classe) ou 'eigencam' (sem " + "gradientes, primeira componente principal das ativações)." + ), + zh=( + "CAM变体:'gradcam'(原始)、'gradcam++'(更适合类别多次出现)" + "或'eigencam'(无梯度,激活的第一主成分)。" + ), + de=( + "CAM-Variante: 'gradcam' (Original), 'gradcam++' (besser bei " + "mehrfachem Auftreten einer Klasse) oder 'eigencam' " + "(gradientenfrei, erste Hauptkomponente der Aktivierungen)." + ), + ), + alias=MultilingualString( + en="CAM method", + es="Método CAM", + pt="Método CAM", + zh="CAM方法", + de="CAM-Methode", + ), + ) # type: ignore + + +class GradCam(BaseLocalExplainer): + """Gradient-based class activation maps for image classifiers. + + Grad-CAM backpropagates the score of the predicted class to the last + convolutional layer and weights its activation maps by the averaged + gradients, producing a heatmap of the image regions that most influenced + the prediction. This is a white-box method: it requires a torch module + with a convolutional backbone, so it works with all DashAI image + classifiers except the MLP (use Occlusion Saliency there instead). + + Implemented on top of the ``pytorch-grad-cam`` library. + + References + ---------- + - [1] Selvaraju, R.R. et al. (2017). "Grad-CAM: Visual Explanations from + Deep Networks via Gradient-based Localization." ICCV 2017. + https://arxiv.org/abs/1610.02391 + - [2] https://github.com/jacobgil/pytorch-grad-cam + """ + + DISPLAY_NAME = MultilingualString( + en="Grad-CAM", + es="Grad-CAM", + pt="Grad-CAM", + zh="Grad-CAM", + de="Grad-CAM", + ) + DESCRIPTION = MultilingualString( + en=( + "Highlights the image regions that most influenced the model's " + "prediction using gradient-weighted class activation maps." + ), + es=( + "Resalta las regiones de la imagen que más influyeron en la " + "predicción del modelo usando mapas de activación ponderados por " + "gradientes." + ), + pt=( + "Destaca as regiões da imagem que mais influenciaram a previsão " + "do modelo usando mapas de ativação ponderados por gradientes." + ), + zh="使用梯度加权类激活图突出显示对模型预测影响最大的图像区域。", + de=( + "Hebt die Bildregionen hervor, die die Vorhersage des Modells am " + "stärksten beeinflusst haben, mittels gradientengewichteter " + "Klassenaktivierungskarten." + ), + ) + COLOR = "#C62828" + SCHEMA = GradCamSchema + + def __init__(self, model: BaseModel, method: str = "gradcam") -> None: + """Initialize a new instance of a GradCam explainer. + + Parameters + ---------- + model : BaseModel + Image classification model to be explained. + method : str + CAM variant: 'gradcam', 'gradcam++' or 'eigencam'. + """ + super().__init__(model) + self.method = method + + def fit(self, background_dataset, **kwargs): + """Store class names in the model's class-index order. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + GradCam + The fitted explainer instance (``self``). + """ + _, y = background_dataset + self.metadata = {"target_names": get_target_names(self.model, y)} + return self + + @staticmethod + def _find_target_layer(module): + """Return the last Conv2d layer of the module. + + Parameters + ---------- + module : torch.nn.Module + The model's torch module. + + Returns + ------- + torch.nn.Conv2d + The last convolutional layer. + + Raises + ------ + ValueError + If the module has no convolutional layer. + """ + import torch + + target = None + for layer in module.modules(): + if isinstance(layer, torch.nn.Conv2d): + target = layer + if target is None: + raise ValueError( + "Grad-CAM requires a convolutional backbone, but the model " + "has no Conv2d layer. Use Occlusion Saliency for " + "non-convolutional image models." + ) + return target + + def explain_instance(self, instances): + """Compute a class activation map for each image. + + Parameters + ---------- + instances : DashAIDataset + Images to be explained; the first column must contain images. + + Returns + ------- + dict + Dictionary with, for each image, the resized image, the CAM + heatmap and the model prediction. + """ + import numpy as np + import torch + from pytorch_grad_cam import EigenCAM, GradCAM, GradCAMPlusPlus + from pytorch_grad_cam.utils.model_targets import ClassifierOutputTarget + + cam_classes = { + "gradcam": GradCAM, + "gradcam++": GradCAMPlusPlus, + "eigencam": EigenCAM, + } + cam_class = cam_classes[self.method] + + module = get_torch_module(self.model) + target_layer = self._find_target_layer(module) + transform = get_transform(self.model) + image_size = int(getattr(self.model, "image_size", 224)) + device = getattr(self.model, "device", torch.device("cpu")) + + module = module.to(device).eval() + + explanation = {"metadata": self.metadata} + with cam_class(model=module, target_layers=[target_layer]) as cam: + for i, pil_image in enumerate(iter_pil_images(instances)): + tensor = transform(pil_image).unsqueeze(0).to(device) + + with torch.no_grad(): + probs = torch.softmax(module(tensor), dim=1)[0] + predicted_class = int(torch.argmax(probs)) + + grayscale = cam( + input_tensor=tensor, + targets=[ClassifierOutputTarget(predicted_class)], + )[0] + + resized = pil_image.resize((image_size, image_size)) + explanation[i] = { + "image": np.asarray(resized, dtype=np.uint8).tolist(), + "heatmap": np.round(grayscale, 4).tolist(), + "model_prediction": np.round( + probs.detach().cpu().numpy(), 4 + ).tolist(), + "predicted_class": predicted_class, + } + + return explanation + + def plot(self, explanation: dict) -> List[Artifact]: + """Render each image as a heatmap overlay plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[Artifact] + A list of typed artifacts: one plotly overlay and one text + artifact per explained image. + """ + import numpy as np + + exp = explanation.copy() + metadata = exp.pop("metadata") + target_names = metadata["target_names"] + + artifacts = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + + title = f"Image {int(i) + 1}" + subtitle = ( + f"{self.method}: regions supporting {predicted_name} " + f"(p={predicted_prob})" + ) + artifacts.append( + heatmap_overlay_artifact( + instance["image"], instance["heatmap"], title, subtitle + ) + ) + artifacts.append( + TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Highlighted regions are the " + "areas whose activations most supported this class." + ), + title=title, + ) + ) + + return artifacts diff --git a/DashAI/back/explainability/explainers/image_explainer_utils.py b/DashAI/back/explainability/explainers/image_explainer_utils.py new file mode 100644 index 000000000..31e645d7b --- /dev/null +++ b/DashAI/back/explainability/explainers/image_explainer_utils.py @@ -0,0 +1,170 @@ +"""Shared helpers for image-classification explainers. + +These helpers define the (minimal) white-box capability contract image +explainers rely on: + +- ``model.model`` is the underlying ``torch.nn.Module``. +- ``model.get_inference_transform()`` returns the exact transform the model + applies to input images (all DashAI image classifiers expose it). +- ``model.image_size`` (int) is the model's input resolution. +- ``model.idx_to_label`` maps class indices to label names. +""" + +from typing import Any, List + +from DashAI.back.core.artifacts import PlotlyArtifact + + +def get_torch_module(model: Any): + """Return the underlying ``torch.nn.Module`` of a DashAI image model. + + Parameters + ---------- + model : Any + The DashAI model wrapper. + + Returns + ------- + torch.nn.Module + The trained torch module. + + Raises + ------ + ValueError + If the model does not expose a torch module. + """ + import torch + + module = getattr(model, "model", None) + if module is None or not isinstance(module, torch.nn.Module): + raise ValueError( + "This explainer requires a model exposing its torch module via " + f"the 'model' attribute; got {type(model).__name__}." + ) + return module + + +def get_transform(model: Any): + """Return the model's inference transform, with a plain fallback. + + Parameters + ---------- + model : Any + The DashAI model wrapper. + + Returns + ------- + Callable + A transform mapping a PIL image to a normalized tensor. + """ + if hasattr(model, "get_inference_transform"): + return model.get_inference_transform() + + from torchvision import transforms + + image_size = int(getattr(model, "image_size", 224)) + return transforms.Compose( + [ + transforms.Lambda(lambda img: img.convert("RGB")), + transforms.Resize((image_size, image_size)), + transforms.ToTensor(), + ] + ) + + +def get_target_names(model: Any, y_dataset) -> List[str]: + """Resolve class names in the model's class-index order. + + Prefers the model's ``idx_to_label`` mapping (which reflects the label + order used at training time) and falls back to the sorted categories of + the target column. + + Parameters + ---------- + model : Any + The DashAI model wrapper. + y_dataset : Any + Target splits; ``y_dataset["train"]`` must expose ``column_names`` + and ``types``. + + Returns + ------- + List[str] + Class names indexed by model output position. + """ + idx_to_label = getattr(model, "idx_to_label", None) + if idx_to_label: + return [str(idx_to_label[key]) for key in sorted(idx_to_label)] + + y_train = y_dataset["train"] + output_column = y_train.column_names[0] + return sorted(str(c) for c in y_train.types[output_column].categories) + + +def iter_pil_images(instances): + """Yield the PIL image of each row in an image dataset. + + Parameters + ---------- + instances : Any + A DashAIDataset (or compatible) whose first column holds images + exposing ``to_pil()``. + + Yields + ------ + PIL.Image.Image + Each image converted to RGB. + """ + image_column = list(instances.features.keys())[0] + for index in range(len(instances)): + yield instances[index][image_column].to_pil().convert("RGB") + + +def heatmap_overlay_artifact( + image_uint8, + heatmap, + title: str, + subtitle: str, +) -> PlotlyArtifact: + """Build a plotly artifact with a jet heatmap blended over an image. + + Parameters + ---------- + image_uint8 : array-like + RGB image of shape (H, W, 3), uint8 values. + heatmap : array-like + Saliency map of shape (H, W) with values in [0, 1]. + title : str + Artifact title (shown in the instance selector). + subtitle : str + Figure title (e.g. predicted class and probability). + + Returns + ------- + PlotlyArtifact + The plotly artifact with the blended overlay figure. + """ + import numpy as np + import plotly.graph_objs as go + + image = np.asarray(image_uint8, dtype=np.float32) / 255.0 + cam = np.clip(np.asarray(heatmap, dtype=np.float32), 0.0, 1.0) + + # Jet-like colormap, avoids a matplotlib/cv2 dependency at plot time. + red = np.clip(1.5 - np.abs(4 * cam - 3), 0, 1) + green = np.clip(1.5 - np.abs(4 * cam - 2), 0, 1) + blue = np.clip(1.5 - np.abs(4 * cam - 1), 0, 1) + colored = np.stack([red, green, blue], axis=-1) + + blended = (0.5 * image + 0.5 * colored) * 255.0 + blended = blended.astype(np.uint8) + + fig = go.Figure(go.Image(z=blended)) + fig.update_layout( + title={"text": subtitle, "font": {"size": 14}}, + margin={"l": 10, "r": 10, "t": 50, "b": 10}, + xaxis={"visible": False}, + yaxis={"visible": False}, + ) + + return PlotlyArtifact(payload=fig, title=title) diff --git a/DashAI/back/explainability/explainers/lime_text.py b/DashAI/back/explainability/explainers/lime_text.py new file mode 100644 index 000000000..89c8f5626 --- /dev/null +++ b/DashAI/back/explainability/explainers/lime_text.py @@ -0,0 +1,319 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact, TextArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class LimeTextSchema(BaseSchema): + """Schema for the LIME text explainer hyperparameters. + + Configures how many words are reported and how many perturbed samples + LIME draws to fit its local surrogate model. + """ + + num_features: schema_field( + int_field(ge=1, le=50), + placeholder=10, + description=MultilingualString( + en="Maximum number of words reported in the explanation.", + es="Número máximo de palabras reportadas en la explicación.", + pt="Número máximo de palavras reportadas na explicação.", + zh="解释中报告的最大词数。", + de="Maximale Anzahl der in der Erklärung gemeldeten Wörter.", + ), + alias=MultilingualString( + en="Number of words", + es="Número de palabras", + pt="Número de palavras", + zh="词数", + de="Anzahl der Wörter", + ), + ) # type: ignore + + num_samples: schema_field( + int_field(ge=100, le=5000), + placeholder=1000, + description=MultilingualString( + en=( + "Number of perturbed texts sampled to fit the local surrogate " + "model. More samples give more stable explanations but take " + "longer." + ), + es=( + "Número de textos perturbados muestreados para ajustar el " + "modelo sustituto local. Más muestras dan explicaciones más " + "estables pero tardan más." + ), + pt=( + "Número de textos perturbados amostrados para ajustar o " + "modelo substituto local. Mais amostras dão explicações mais " + "estáveis, mas demoram mais." + ), + zh="为拟合局部代理模型而采样的扰动文本数量。样本越多解释越稳定,但耗时越长。", + de=( + "Anzahl der gestörten Texte zum Anpassen des lokalen " + "Ersatzmodells. Mehr Stichproben ergeben stabilere " + "Erklärungen, dauern aber länger." + ), + ), + alias=MultilingualString( + en="Number of samples", + es="Número de muestras", + pt="Número de amostras", + zh="样本数量", + de="Anzahl der Stichproben", + ), + ) # type: ignore + + +class LimeText(BaseLocalExplainer): + """LIME explanations for text classification models. + + Fits a sparse linear surrogate model on random word-masked variants of + the input text, weighting variants by similarity to the original. The + surrogate's coefficients estimate each word's contribution to the + predicted class. Model agnostic: only ``predict`` is queried. Compared to + Token Ablation (one word at a time), LIME captures joint effects of + removing several words but is stochastic and needs more model calls. + + References + ---------- + - [1] Ribeiro, M.T., Singh, S. & Guestrin, C. (2016). "'Why Should I + Trust You?' Explaining the Predictions of Any Classifier." + KDD 2016. https://arxiv.org/abs/1602.04938 + - [2] https://github.com/marcotcr/lime + """ + + COMPATIBLE_COMPONENTS = ["TextClassificationTask"] + DISPLAY_NAME = MultilingualString( + en="LIME (text)", + es="LIME (texto)", + pt="LIME (texto)", + zh="LIME(文本)", + de="LIME (Text)", + ) + DESCRIPTION = MultilingualString( + en=( + "Fits a local linear surrogate on word-masked text variants to " + "estimate each word's contribution to the prediction." + ), + es=( + "Ajusta un sustituto lineal local sobre variantes del texto con " + "palabras enmascaradas para estimar la contribución de cada " + "palabra a la predicción." + ), + pt=( + "Ajusta um substituto linear local em variantes do texto com " + "palavras mascaradas para estimar a contribuição de cada palavra " + "à previsão." + ), + zh="在词遮蔽的文本变体上拟合局部线性代理模型,以估计每个词对预测的贡献。", + de=( + "Passt ein lokales lineares Ersatzmodell auf wortmaskierten " + "Textvarianten an, um den Beitrag jedes Wortes zur Vorhersage zu " + "schätzen." + ), + ) + COLOR = "#2E7D32" + SCHEMA = LimeTextSchema + + def __init__( + self, + model: BaseModel, + num_features: int = 10, + num_samples: int = 1000, + ) -> None: + """Initialize a new instance of a LimeText explainer. + + Parameters + ---------- + model : BaseModel + Text classification model to be explained. + num_features : int + Maximum number of words reported per explanation. + num_samples : int + Number of perturbed texts sampled by LIME. + """ + super().__init__(model) + self.num_features = num_features + self.num_samples = num_samples + + def fit(self, background_dataset, **kwargs): + """Store class names from the training targets. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + LimeText + The fitted explainer instance (``self``). + """ + _, y = background_dataset + y_train = y["train"] + + output_column = y_train.column_names[0] + target_names = y_train.types[output_column].categories + self.metadata = {"target_names": [str(c) for c in target_names]} + + return self + + def explain_instance(self, instances): + """Compute LIME word attributions for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained; must contain a single text column + (tokenizer artifact columns are ignored). + + Returns + ------- + dict + Dictionary with, for each instance, the word weights and the + model prediction. + """ + import numpy as np + import pandas as pd + from lime.lime_text import LimeTextExplainer + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + + dataset = to_dashai_dataset(instances) + X = dataset.to_pandas() + + # Same guard as TokenAblation: the job may hand over a dataset the + # model already prepared (tokenized), so rebuild from raw text only. + tokenizer_columns = {"input_ids", "attention_mask", "token_type_ids", "label"} + text_columns = [c for c in X.columns if c not in tokenizer_columns] + if not text_columns: + raise ValueError(f"No text column found among columns: {list(X.columns)}") + text_column = text_columns[0] + texts = X[text_column].astype(str).tolist() + + def classifier_fn(variant_texts): + variants_dataset = to_dashai_dataset( + pd.DataFrame({text_column: list(variant_texts)}) + ) + return np.asarray(self.model.predict(variants_dataset)) + + base_dataset = to_dashai_dataset(pd.DataFrame({text_column: texts})) + base_predictions = np.asarray(self.model.predict(base_dataset)) + + target_names = self.metadata["target_names"] + lime_explainer = LimeTextExplainer(class_names=target_names, random_state=0) + + explanation = {"metadata": {**self.metadata, "text_column": text_column}} + for i, text in enumerate(texts): + predicted_class = int(np.argmax(base_predictions[i])) + + lime_result = lime_explainer.explain_instance( + text, + classifier_fn, + labels=(predicted_class,), + num_features=self.num_features, + num_samples=self.num_samples, + ) + word_weights = [ + [word, float(np.round(weight, 4))] + for word, weight in lime_result.as_list(label=predicted_class) + ] + + explanation[i] = { + "text": text, + "word_weights": word_weights, + "model_prediction": base_predictions[i].tolist(), + "predicted_class": predicted_class, + } + + return explanation + + def plot(self, explanation: dict) -> List[Artifact]: + """Render each instance as a word-weight bar plot plus a summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[Artifact] + A list of typed artifacts: one plotly and one text artifact per + explained instance. + """ + import numpy as np + import plotly.graph_objs as go + + exp = explanation.copy() + metadata = exp.pop("metadata") + target_names = metadata["target_names"] + + artifacts = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + word_weights = sorted( + instance["word_weights"], key=lambda pair: abs(pair[1]) + ) + + words = [pair[0] for pair in word_weights] + weights = [pair[1] for pair in word_weights] + colors = [ + "rgb(231,63,116)" if value >= 0 else "rgb(47,138,196)" + for value in weights + ] + fig = go.Figure( + go.Bar( + x=weights, + y=words, + orientation="h", + marker={"color": colors}, + text=weights, + textposition="auto", + ) + ) + fig.update_layout( + title={ + "text": ( + f"LIME word weights for {predicted_name} (p={predicted_prob})" + ), + "font": {"size": 14}, + }, + margin={"pad": 20, "l": 100, "r": 60, "t": 60, "b": 40}, + xaxis={"title_text": "Weight (towards predicted class)"}, + yaxis={"showgrid": True}, + ) + + title = f"Instance {int(i) + 1}" + artifacts.append(PlotlyArtifact(payload=fig, title=title)) + + top = list(reversed(word_weights))[:3] + top_words = ", ".join(f"'{word}' ({weight:+})" for word, weight in top) + artifacts.append( + TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Most influential words: " + f"{top_words}." + ), + title=title, + ) + ) + + return artifacts diff --git a/DashAI/back/explainability/explainers/nearest_counterfactual.py b/DashAI/back/explainability/explainers/nearest_counterfactual.py new file mode 100644 index 000000000..513448e25 --- /dev/null +++ b/DashAI/back/explainability/explainers/nearest_counterfactual.py @@ -0,0 +1,414 @@ +from typing import List + +from DashAI.back.core.artifacts import ( + Artifact, + TableArtifact, + TablePayload, + TextArtifact, +) +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class NearestCounterfactualSchema(BaseSchema): + """Schema for NearestCounterfactual explainer hyperparameters. + + Configures how many counterfactual examples are retrieved per instance and + the distance metric used to rank candidate examples. + """ + + n_counterfactuals: schema_field( + int_field(ge=1, le=10), + placeholder=3, + description=MultilingualString( + en=( + "Number of counterfactual examples to retrieve for each " + "instance. Each counterfactual is a real training example " + "that the model classifies differently." + ), + es=( + "Número de ejemplos contrafactuales a recuperar por cada " + "instancia. Cada contrafactual es un ejemplo real de " + "entrenamiento que el modelo clasifica de forma distinta." + ), + pt=( + "Número de exemplos contrafactuais a recuperar para cada " + "instância. Cada contrafactual é um exemplo real de " + "treinamento que o modelo classifica de forma diferente." + ), + zh=( + "为每个实例检索的反事实示例数量。每个反事实都是模型分类不同的真实训练样本。" + ), + de=( + "Anzahl der kontrafaktischen Beispiele pro Instanz. Jedes " + "kontrafaktische Beispiel ist ein echtes Trainingsbeispiel, " + "das das Modell anders klassifiziert." + ), + ), + alias=MultilingualString( + en="Number of counterfactuals", + es="Número de contrafactuales", + pt="Número de contrafactuais", + zh="反事实数量", + de="Anzahl kontrafaktischer Beispiele", + ), + ) # type: ignore + + distance: schema_field( + enum_field(enum=["l1", "l2"]), + placeholder="l1", + description=MultilingualString( + en=( + "Distance used to rank candidate counterfactuals. Numeric " + "features are normalized by their range; non-numeric features " + "add a constant penalty when they differ." + ), + es=( + "Distancia usada para ordenar los contrafactuales candidatos. " + "Las características numéricas se normalizan por su rango; " + "las no numéricas agregan una penalización constante cuando " + "difieren." + ), + pt=( + "Distância usada para ordenar os contrafactuais candidatos. " + "As características numéricas são normalizadas pelo seu " + "intervalo; as não numéricas adicionam uma penalização " + "constante quando diferem." + ), + zh=( + "用于对候选反事实排序的距离。数值特征按范围归一化;非数值特征在不同时增加固定惩罚。" + ), + de=( + "Distanz zur Rangordnung der kontrafaktischen Kandidaten. " + "Numerische Merkmale werden über ihren Wertebereich " + "normalisiert; nicht numerische Merkmale erhalten bei " + "Abweichung eine konstante Strafe." + ), + ), + alias=MultilingualString( + en="Distance metric", + es="Métrica de distancia", + pt="Métrica de distância", + zh="距离度量", + de="Distanzmetrik", + ), + ) # type: ignore + + +class NearestCounterfactual(BaseLocalExplainer): + """Case-based counterfactual explainer for tabular classification. + + For each instance to explain, this explainer answers "what would have to + be different for the model to predict another class?" by retrieving the + nearest real training examples that the model classifies differently + (nearest unlike neighbors). Because counterfactuals are actual dataset + rows, they are always plausible and never out of distribution, unlike + synthetic perturbation-based counterfactuals. + + The explainer is fully model agnostic: it only queries ``predict``. + + References + ---------- + - [1] Wachter, S., Mittelstadt, B. & Russell, C. (2017). "Counterfactual + Explanations without Opening the Black Box." Harvard JOLT 31(2). + https://arxiv.org/abs/1711.00399 + - [2] Keane, M.T. & Smyth, B. (2020). "Good Counterfactuals and Where to + Find Them." ICCBR 2020. https://arxiv.org/abs/2005.13997 + """ + + COMPATIBLE_COMPONENTS = ["TabularClassificationTask"] + DISPLAY_NAME = MultilingualString( + en="Nearest Counterfactual", + es="Contrafactual más cercano", + pt="Contrafactual mais próximo", + zh="最近反事实", + de="Nächstes kontrafaktisches Beispiel", + ) + DESCRIPTION = MultilingualString( + en=( + "Finds the closest real examples classified differently by the " + "model, showing which feature changes would flip the prediction." + ), + es=( + "Encuentra los ejemplos reales más cercanos clasificados de forma " + "distinta por el modelo, mostrando qué cambios de características " + "invertirían la predicción." + ), + pt=( + "Encontra os exemplos reais mais próximos classificados de forma " + "diferente pelo modelo, mostrando quais mudanças de " + "características inverteriam a previsão." + ), + zh=("查找模型分类不同的最近真实示例,展示哪些特征变化会翻转预测。"), + de=( + "Findet die nächstgelegenen realen Beispiele, die das Modell " + "anders klassifiziert, und zeigt, welche Merkmalsänderungen die " + "Vorhersage kippen würden." + ), + ) + COLOR = "#7B1FA2" + SCHEMA = NearestCounterfactualSchema + + def __init__( + self, + model: BaseModel, + n_counterfactuals: int = 3, + distance: str = "l1", + ) -> None: + """Initialize a new instance of a NearestCounterfactual explainer. + + Parameters + ---------- + model : BaseModel + Model to be explained. + n_counterfactuals : int + Number of counterfactual examples retrieved per instance. + distance : str + Distance used to rank candidates: 'l1' or 'l2'. + """ + super().__init__(model) + self.n_counterfactuals = n_counterfactuals + self.distance = distance + + def fit(self, background_dataset, **kwargs): + """Store the background data and its model predictions. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. The train split is used + as the pool of counterfactual candidates. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + NearestCounterfactual + The fitted explainer instance (``self``). + """ + import numpy as np + + x, y = background_dataset + x_train = x["train"] + y_train = y["train"] + + self.background_data = x_train.to_pandas() + self.feature_names = list(x_train.column_names) + + background_probs = self.model.predict(x_train) + self.background_classes = np.argmax(np.asarray(background_probs), axis=1) + + # Per-feature range for numeric columns, used to normalize distances. + self._numeric_columns = [ + column + for column in self.background_data.columns + if np.issubdtype(self.background_data[column].dtype, np.number) + ] + ranges = {} + for column in self._numeric_columns: + column_range = float( + self.background_data[column].max() - self.background_data[column].min() + ) + ranges[column] = column_range if column_range > 0 else 1.0 + self._ranges = ranges + + output_column = y_train.column_names[0] + target_names = y_train.types[output_column].categories + self.metadata = { + "feature_names": self.feature_names, + "target_names": list(target_names), + } + + return self + + def _distances(self, instance_row, candidates): + """Compute normalized distances between one instance and candidates. + + Parameters + ---------- + instance_row : pd.Series + The instance to explain. + candidates : pd.DataFrame + Candidate counterfactual rows. + + Returns + ------- + np.ndarray + One distance per candidate row. + """ + import numpy as np + + total = np.zeros(len(candidates), dtype=float) + for column in candidates.columns: + if column in self._ranges: + diff = ( + np.abs( + candidates[column].to_numpy(dtype=float) + - float(instance_row[column]) + ) + / self._ranges[column] + ) + total += diff if self.distance == "l1" else diff**2 + else: + mismatch = ( + candidates[column].to_numpy() != instance_row[column] + ).astype(float) + total += mismatch + + return np.sqrt(total) if self.distance == "l2" else total + + def explain_instance(self, instances): + """Retrieve the nearest counterfactual examples for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained. + + Returns + ------- + dict + Dictionary with, for each instance, the model prediction and the + retrieved counterfactual examples. + """ + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + + dataset = to_dashai_dataset(instances) + X = dataset.to_pandas() + + predictions = np.asarray(self.model.predict(dataset)) + + explanation = {"metadata": self.metadata} + for i, (_, instance_row) in enumerate(X.iterrows()): + predicted_class = int(np.argmax(predictions[i])) + + candidate_mask = self.background_classes != predicted_class + candidates = self.background_data[candidate_mask] + + counterfactuals = [] + if len(candidates) > 0: + distances = self._distances(instance_row, candidates) + order = np.argsort(distances)[: self.n_counterfactuals] + for rank in order: + row = candidates.iloc[int(rank)] + changed_features = [ + feature + for feature in self.feature_names + if row[feature] != instance_row[feature] + ] + candidate_index = int(candidates.index[int(rank)]) + counterfactuals.append( + { + "values": row.tolist(), + "predicted_class": int( + self.background_classes[ + self.background_data.index.get_loc(candidate_index) + ] + ), + "distance": float(np.round(distances[int(rank)], 4)), + "changed_features": changed_features, + } + ) + + explanation[i] = { + "instance_values": instance_row.tolist(), + "model_prediction": predictions[i].tolist(), + "predicted_class": predicted_class, + "counterfactuals": counterfactuals, + } + + return explanation + + def plot(self, explanation: dict) -> List[Artifact]: + """Render each instance as a comparison table plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[Artifact] + A list of typed artifacts: one table and one text artifact per + explained instance. + """ + import numpy as np + + exp = explanation.copy() + metadata = exp.pop("metadata") + feature_names = metadata["feature_names"] + target_names = metadata["target_names"] + + artifacts = [] + for i in exp: + instance = exp[i] + instance_values = instance["instance_values"] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + counterfactuals = instance["counterfactuals"] + + columns = ["Feature", "Instance"] + [ + f"Counterfactual {k + 1}" for k in range(len(counterfactuals)) + ] + rows = [] + highlight = [] + for row_idx, feature in enumerate(feature_names): + row = [feature, instance_values[row_idx]] + for cf_idx, counterfactual in enumerate(counterfactuals): + row.append(counterfactual["values"][row_idx]) + if feature in counterfactual["changed_features"]: + highlight.append({"row": row_idx, "column": 2 + cf_idx}) + rows.append(row) + + prediction_row = ["Predicted class", predicted_name] + [ + target_names[counterfactual["predicted_class"]] + for counterfactual in counterfactuals + ] + rows.append(prediction_row) + for cf_idx in range(len(counterfactuals)): + highlight.append({"row": len(feature_names), "column": 2 + cf_idx}) + + title = f"Instance {int(i) + 1}" + artifacts.append( + TableArtifact( + payload=TablePayload( + columns=columns, rows=rows, highlight=highlight + ), + title=title, + ) + ) + + if counterfactuals: + lines = [ + (f"The model predicted {predicted_name} (p={predicted_prob}).") + ] + for cf_idx, counterfactual in enumerate(counterfactuals): + cf_name = target_names[counterfactual["predicted_class"]] + changed = ", ".join(counterfactual["changed_features"]) or "nothing" + lines.append( + f"Counterfactual {cf_idx + 1}: changing {changed} " + f"yields {cf_name} " + f"(distance {counterfactual['distance']})." + ) + summary = "\n".join(lines) + else: + summary = ( + f"The model predicted {predicted_name} (p={predicted_prob}). " + "No counterfactual examples were found in the training data." + ) + artifacts.append(TextArtifact(payload=summary, title=title)) + + return artifacts diff --git a/DashAI/back/explainability/explainers/occlusion_saliency.py b/DashAI/back/explainability/explainers/occlusion_saliency.py new file mode 100644 index 000000000..44fe05bff --- /dev/null +++ b/DashAI/back/explainability/explainers/occlusion_saliency.py @@ -0,0 +1,347 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, TextArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.explainers.image_explainer_utils import ( + get_target_names, + get_torch_module, + get_transform, + heatmap_overlay_artifact, + iter_pil_images, +) +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class OcclusionSaliencySchema(BaseSchema): + """Schema for the Occlusion Saliency explainer hyperparameters. + + Configures the size and stride of the occlusion patch, in pixels of the + model's input resolution. + """ + + patch_size: schema_field( + int_field(ge=4, le=128), + placeholder=16, + description=MultilingualString( + en=( + "Side (in pixels) of the square patch that is occluded at " + "each position. Smaller patches give finer maps but require " + "more model evaluations." + ), + es=( + "Lado (en píxeles) del parche cuadrado que se ocluye en cada " + "posición. Parches más pequeños dan mapas más finos pero " + "requieren más evaluaciones del modelo." + ), + pt=( + "Lado (em pixels) do patch quadrado ocluído em cada posição. " + "Patches menores dão mapas mais finos, mas requerem mais " + "avaliações do modelo." + ), + zh="每个位置遮挡的正方形补丁的边长(像素)。较小的补丁产生更精细的图,但需要更多模型评估。", + de=( + "Seitenlänge (in Pixeln) des quadratischen Patches, der an " + "jeder Position verdeckt wird. Kleinere Patches ergeben " + "feinere Karten, erfordern aber mehr Modellauswertungen." + ), + ), + alias=MultilingualString( + en="Patch size", + es="Tamaño del parche", + pt="Tamanho do patch", + zh="补丁大小", + de="Patchgröße", + ), + ) # type: ignore + + stride: schema_field( + int_field(ge=2, le=64), + placeholder=8, + description=MultilingualString( + en=( + "Step (in pixels) between consecutive patch positions. " + "Smaller strides give smoother maps but require more model " + "evaluations." + ), + es=( + "Paso (en píxeles) entre posiciones consecutivas del parche. " + "Pasos más pequeños dan mapas más suaves pero requieren más " + "evaluaciones del modelo." + ), + pt=( + "Passo (em pixels) entre posições consecutivas do patch. " + "Passos menores dão mapas mais suaves, mas requerem mais " + "avaliações do modelo." + ), + zh="连续补丁位置之间的步长(像素)。较小的步长产生更平滑的图,但需要更多模型评估。", + de=( + "Schrittweite (in Pixeln) zwischen aufeinanderfolgenden " + "Patchpositionen. Kleinere Schritte ergeben glattere Karten, " + "erfordern aber mehr Modellauswertungen." + ), + ), + alias=MultilingualString( + en="Stride", + es="Paso", + pt="Passo", + zh="步长", + de="Schrittweite", + ), + ) # type: ignore + + +class OcclusionSaliency(BaseLocalExplainer): + """Perturbation-based saliency maps for image classifiers. + + Slides a gray patch over the image and records how much the predicted + class probability drops at each position. Regions whose occlusion causes + a large drop are the ones the model relied on. Unlike Grad-CAM, this + method needs no gradients or convolutional layers, so it works with every + DashAI image classifier including the MLP; the trade-off is one model + evaluation per patch position. + + References + ---------- + - [1] Zeiler, M.D. & Fergus, R. (2014). "Visualizing and Understanding + Convolutional Networks." ECCV 2014. https://arxiv.org/abs/1311.2901 + """ + + DISPLAY_NAME = MultilingualString( + en="Occlusion Saliency", + es="Saliencia por oclusión", + pt="Saliência por oclusão", + zh="遮挡显著性", + de="Okklusions-Salienz", + ) + DESCRIPTION = MultilingualString( + en=( + "Slides a gray patch over the image and maps how much each " + "region's occlusion lowers the predicted class probability." + ), + es=( + "Desliza un parche gris sobre la imagen y mapea cuánto baja la " + "probabilidad de la clase predicha al ocluir cada región." + ), + pt=( + "Desliza um patch cinza sobre a imagem e mapeia o quanto a " + "oclusão de cada região reduz a probabilidade da classe prevista." + ), + zh="在图像上滑动灰色补丁,映射遮挡每个区域对预测类别概率的降低程度。", + de=( + "Schiebt einen grauen Patch über das Bild und kartiert, wie stark " + "die Verdeckung jeder Region die vorhergesagte " + "Klassenwahrscheinlichkeit senkt." + ), + ) + COLOR = "#AD1457" + SCHEMA = OcclusionSaliencySchema + + def __init__( + self, + model: BaseModel, + patch_size: int = 16, + stride: int = 8, + ) -> None: + """Initialize a new instance of an OcclusionSaliency explainer. + + Parameters + ---------- + model : BaseModel + Image classification model to be explained. + patch_size : int + Side of the occluded square patch, in pixels. + stride : int + Step between consecutive patch positions, in pixels. + """ + super().__init__(model) + self.patch_size = patch_size + self.stride = stride + + def fit(self, background_dataset, **kwargs): + """Store class names in the model's class-index order. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + OcclusionSaliency + The fitted explainer instance (``self``). + """ + _, y = background_dataset + self.metadata = {"target_names": get_target_names(self.model, y)} + return self + + def _occlusion_map(self, module, tensor, predicted_class, device): + """Compute the probability-drop map for one image tensor. + + Parameters + ---------- + module : torch.nn.Module + The model's torch module in eval mode. + tensor : torch.Tensor + Input tensor of shape (1, C, H, W). + predicted_class : int + Class whose probability drop is measured. + device : torch.device + Device to run the evaluations on. + + Returns + ------- + np.ndarray + Saliency map of shape (H, W), normalized to [0, 1]. + """ + import numpy as np + import torch + + _, _, height, width = tensor.shape + baseline = tensor.mean(dim=(2, 3), keepdim=True) + + with torch.no_grad(): + base_prob = torch.softmax(module(tensor), dim=1)[0, predicted_class] + base_prob = float(base_prob) + + positions = [ + (top, left) + for top in range(0, max(height - self.patch_size, 0) + 1, self.stride) + for left in range(0, max(width - self.patch_size, 0) + 1, self.stride) + ] + + drops = np.zeros((height, width), dtype=np.float32) + counts = np.zeros((height, width), dtype=np.float32) + + batch_size = 32 + with torch.no_grad(): + for start in range(0, len(positions), batch_size): + batch_positions = positions[start : start + batch_size] + occluded = tensor.repeat(len(batch_positions), 1, 1, 1) + for j, (top, left) in enumerate(batch_positions): + occluded[ + j, + :, + top : top + self.patch_size, + left : left + self.patch_size, + ] = baseline[0] + probs = torch.softmax(module(occluded.to(device)), dim=1)[ + :, predicted_class + ] + for j, (top, left) in enumerate(batch_positions): + drop = base_prob - float(probs[j]) + drops[ + top : top + self.patch_size, + left : left + self.patch_size, + ] += drop + counts[ + top : top + self.patch_size, + left : left + self.patch_size, + ] += 1.0 + + saliency = drops / np.maximum(counts, 1.0) + saliency = np.clip(saliency, 0.0, None) + max_value = saliency.max() + if max_value > 0: + saliency = saliency / max_value + return saliency + + def explain_instance(self, instances): + """Compute an occlusion saliency map for each image. + + Parameters + ---------- + instances : DashAIDataset + Images to be explained; the first column must contain images. + + Returns + ------- + dict + Dictionary with, for each image, the resized image, the saliency + map and the model prediction. + """ + import numpy as np + import torch + + module = get_torch_module(self.model) + transform = get_transform(self.model) + image_size = int(getattr(self.model, "image_size", 224)) + device = getattr(self.model, "device", torch.device("cpu")) + + module = module.to(device).eval() + + explanation = {"metadata": self.metadata} + for i, pil_image in enumerate(iter_pil_images(instances)): + tensor = transform(pil_image).unsqueeze(0).to(device) + + with torch.no_grad(): + probs = torch.softmax(module(tensor), dim=1)[0] + predicted_class = int(torch.argmax(probs)) + + saliency = self._occlusion_map(module, tensor, predicted_class, device) + + resized = pil_image.resize((image_size, image_size)) + explanation[i] = { + "image": np.asarray(resized, dtype=np.uint8).tolist(), + "heatmap": np.round(saliency, 4).tolist(), + "model_prediction": np.round(probs.detach().cpu().numpy(), 4).tolist(), + "predicted_class": predicted_class, + } + + return explanation + + def plot(self, explanation: dict) -> List[Artifact]: + """Render each image as a saliency overlay plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[Artifact] + A list of typed artifacts: one plotly overlay and one text + artifact per explained image. + """ + import numpy as np + + exp = explanation.copy() + metadata = exp.pop("metadata") + target_names = metadata["target_names"] + + artifacts = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + + title = f"Image {int(i) + 1}" + subtitle = f"Occlusion saliency for {predicted_name} (p={predicted_prob})" + artifacts.append( + heatmap_overlay_artifact( + instance["image"], instance["heatmap"], title, subtitle + ) + ) + artifacts.append( + TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Highlighted regions are those " + "whose occlusion most lowered that probability." + ), + title=title, + ) + ) + + return artifacts diff --git a/DashAI/back/explainability/explainers/regression_kernel_shap.py b/DashAI/back/explainability/explainers/regression_kernel_shap.py new file mode 100644 index 000000000..2d0a05838 --- /dev/null +++ b/DashAI/back/explainability/explainers/regression_kernel_shap.py @@ -0,0 +1,337 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact, TextArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + bool_field, + float_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class RegressionKernelShapSchema(BaseSchema): + """Schema for the regression Kernel SHAP explainer hyperparameters. + + Configures the background sampling used to fit the SHAP explainer. + """ + + fit_parameter_sample_background_data: schema_field( + bool_field(), + placeholder=True, + description=MultilingualString( + en=( + "'true' if background data must be sampled; otherwise the " + "entire training set is used. Smaller datasets speed up the " + "algorithm runtime." + ), + es=( + "'true' si se deben muestrear los datos de fondo; de lo " + "contrario se usa el conjunto de entrenamiento completo. " + "Conjuntos más pequeños reducen el tiempo de ejecución." + ), + pt=( + "'true' se os dados de fundo devem ser amostrados; caso " + "contrário, usa-se o conjunto de treinamento completo. " + "Conjuntos menores reduzem o tempo de execução." + ), + zh=( + "如果需要对背景数据进行采样则为'true';" + "否则使用整个训练集。较小的数据集可加速算法运行。" + ), + de=( + "'true', wenn Hintergrunddaten gesamplet werden müssen; sonst " + "wird der gesamte Trainingssatz verwendet. Kleinere " + "Datensätze beschleunigen die Laufzeit." + ), + ), + alias=MultilingualString( + en="Sample background data", + es="Muestrear datos de fondo", + pt="Amostrar dados de fundo", + zh="采样背景数据", + de="Hintergrunddaten samplen", + ), + ) # type: ignore + + fit_parameter_background_fraction: schema_field( + float_field(ge=0, le=1), + placeholder=0.2, + description=MultilingualString( + en=( + "If 'Sample background data' is selected, fraction of " + "background samples to draw from the training set." + ), + es=( + "Si se selecciona 'Muestrear datos de fondo', proporción de " + "muestras de fondo a extraer del conjunto de entrenamiento." + ), + pt=( + "Se 'Amostrar dados de fundo' estiver selecionado, fração de " + "amostras de fundo a extrair do conjunto de treinamento." + ), + zh="如果选择了'采样背景数据',则为从训练集中抽取的背景样本比例。", + de=( + "Wenn 'Hintergrunddaten samplen' ausgewählt ist, Anteil der " + "Hintergrundproben aus dem Trainingssatz." + ), + ), + alias=MultilingualString( + en="Background fraction", + es="Fracción de fondo", + pt="Fração de fundo", + zh="背景比例", + de="Hintergrundfraktion", + ), + ) # type: ignore + + +class RegressionKernelShap(BaseLocalExplainer): + """Model agnostic local explainer for regression via Kernel SHAP. + + For each instance, estimates how much each feature value pushed the + model's numeric prediction above or below the expected (baseline) output, + using the Kernel SHAP weighted linear model over sampled feature + coalitions. The model is treated as a black box: only ``predict`` is + queried. + + References + ---------- + - [1] Lundberg, S.M. & Lee, S.I. (2017). "A Unified Approach to + Interpreting Model Predictions." NeurIPS 30. + https://arxiv.org/abs/1705.07874 + - [2] https://shap.readthedocs.io/en/latest/generated/shap.KernelExplainer.html + """ + + COMPATIBLE_COMPONENTS = ["RegressionTask"] + DISPLAY_NAME = MultilingualString( + en="Kernel SHAP (regression)", + es="Kernel SHAP (regresión)", + pt="Kernel SHAP (regressão)", + zh="Kernel SHAP(回归)", + de="Kernel SHAP (Regression)", + ) + DESCRIPTION = MultilingualString( + en=( + "Attributes a regression model's numeric prediction to each " + "feature value using SHAP values." + ), + es=( + "Atribuye la predicción numérica de un modelo de regresión a cada " + "valor de característica usando valores SHAP." + ), + pt=( + "Atribui a previsão numérica de um modelo de regressão a cada " + "valor de característica usando valores SHAP." + ), + zh="使用SHAP值将回归模型的数值预测归因于每个特征值。", + de=( + "Ordnet die numerische Vorhersage eines Regressionsmodells jedem " + "Merkmalswert mittels SHAP-Werten zu." + ), + ) + COLOR = "#00838F" + SCHEMA = RegressionKernelShapSchema + + def __init__(self, model: BaseModel) -> None: + """Initialize a new instance of a RegressionKernelShap explainer. + + Parameters + ---------- + model : BaseModel + Regression model to be explained. + """ + super().__init__(model) + + def fit( + self, + background_dataset, + sample_background_data=False, + background_fraction=None, + **kwargs, + ): + """Fit the Kernel SHAP explainer on background data. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits; the train split is used + as SHAP background data. + sample_background_data : bool + True if the background data must be sampled. + background_fraction : float + Fraction of the training samples used as background data when + ``sample_background_data`` is True. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + RegressionKernelShap + The fitted explainer instance (``self``). + """ + import shap + + x, y = background_dataset + x_train = x["train"] + y_train = y["train"] + + background_data = x_train.to_pandas() + feature_names = list(x_train.column_names) + + if bool(sample_background_data) and background_fraction: + n_samples = max(1, int(background_fraction * len(background_data))) + background_data = shap.sample(background_data, n_samples) + + self.explainer = shap.KernelExplainer( + model=self.model.predict, + data=background_data, + feature_names=feature_names, + ) + + self.metadata = { + "feature_names": feature_names, + "output_column": y_train.column_names[0], + } + + return self + + def explain_instance(self, instances): + """Compute SHAP values for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained. + + Returns + ------- + dict + Dictionary with, for each instance, the model prediction, the + baseline value and the per-feature SHAP values. + """ + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + + dataset = to_dashai_dataset(instances) + X = dataset.to_pandas() + + predictions = np.asarray(self.model.predict(dataset)).ravel() + + shap_values = np.asarray(self.explainer.shap_values(X=X)) + # Single-output models may yield (n, n_features) or (n, n_features, 1). + if shap_values.ndim == 3: + shap_values = shap_values[..., 0] + + base_value = np.asarray(self.explainer.expected_value).ravel()[0] + + explanation = { + "metadata": self.metadata, + "base_value": float(np.round(base_value, 3)), + } + for i, (instance, prediction, contributions) in enumerate( + zip(X.to_numpy(), predictions, shap_values, strict=True) + ): + explanation[i] = { + "instance_values": instance.tolist(), + "model_prediction": float(np.round(prediction, 3)), + "shap_values": np.round(contributions, 3).tolist(), + } + + return explanation + + def plot(self, explanation: dict) -> List[Artifact]: + """Render each instance as a SHAP bar plot plus a text summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[Artifact] + A list of typed artifacts: one plotly and one text artifact per + explained instance. + """ + import numpy as np + import pandas as pd + import plotly.graph_objs as go + + exp = explanation.copy() + metadata = exp.pop("metadata") + base_value = exp.pop("base_value") + feature_names = metadata["feature_names"] + output_column = metadata["output_column"] + max_features = 8 + + artifacts = [] + for i in exp: + instance = exp[i] + prediction = instance["model_prediction"] + + data = pd.DataFrame( + { + "features": feature_names, + "values": instance["instance_values"], + "shap_values": instance["shap_values"], + } + ) + data["shap_abs"] = data["shap_values"].abs() + data = data.sort_values(by="shap_abs", ascending=True) + if len(data) > max_features: + data = data.iloc[-max_features:, :] + data["label"] = data["features"] + "=" + data["values"].map(str) + + colors = [ + "rgb(231,63,116)" if value >= 0 else "rgb(47,138,196)" + for value in data["shap_values"] + ] + fig = go.Figure( + go.Bar( + x=data["shap_values"], + y=data["label"], + orientation="h", + marker={"color": colors}, + text=data["shap_values"], + textposition="auto", + ) + ) + fig.update_layout( + title={ + "text": ( + f"{output_column}: prediction f(x)={prediction}, " + f"baseline E[f(x)]={base_value}" + ), + "font": {"size": 14}, + }, + margin={"pad": 20, "l": 100, "r": 60, "t": 60, "b": 40}, + xaxis={"title_text": "SHAP value (impact on prediction)"}, + yaxis={"showgrid": True}, + ) + + title = f"Instance {int(i) + 1}" + artifacts.append(PlotlyArtifact(payload=fig, title=title)) + + top = data.iloc[::-1].head(3) + top_features = ", ".join( + f"{feature}={value} ({shap:+})" + for feature, value, shap in zip( + top["features"].tolist(), + top["values"].tolist(), + top["shap_values"].tolist(), + strict=True, + ) + ) + delta = float(np.round(prediction - base_value, 3)) + summary = ( + f"The model predicted {output_column}={prediction}, " + f"{delta:+} from the baseline {base_value}. " + f"Main contributions: {top_features}." + ) + artifacts.append(TextArtifact(payload=summary, title=title)) + + return artifacts diff --git a/DashAI/back/explainability/explainers/regression_partial_dependence.py b/DashAI/back/explainability/explainers/regression_partial_dependence.py new file mode 100644 index 000000000..19142b1fc --- /dev/null +++ b/DashAI/back/explainability/explainers/regression_partial_dependence.py @@ -0,0 +1,244 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + float_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.global_explainer import BaseGlobalExplainer +from DashAI.back.models.base_model import BaseModel + + +class RegressionPartialDependenceSchema(BaseSchema): + """Schema for the regression Partial Dependence explainer. + + Configures the grid resolution and the percentile range of each + feature's grid. + """ + + grid_resolution: schema_field( + int_field(ge=5, le=200), + placeholder=50, + description=MultilingualString( + en="Number of equally spaced grid points per feature.", + es="Número de puntos de la grilla equiespaciados por característica.", + pt="Número de pontos de grade igualmente espaçados por característica.", + zh="每个特征等距网格点的数量。", + de="Anzahl gleichmäßig verteilter Gitterpunkte pro Merkmal.", + ), + alias=MultilingualString( + en="Grid resolution", + es="Resolución de la grilla", + pt="Resolução da grade", + zh="网格分辨率", + de="Gitterauflösung", + ), + ) # type: ignore + + lower_percentile: schema_field( + float_field(ge=0.0, le=1.0), + placeholder=0.05, + description=MultilingualString( + en="Lower percentile of the feature values used as grid start.", + es="Percentil inferior de los valores usados como inicio de la grilla.", + pt="Percentil inferior dos valores usados como início da grade.", + zh="用作网格起点的特征值下分位数。", + de="Unteres Perzentil der Merkmalswerte als Gitterstart.", + ), + alias=MultilingualString( + en="Lower percentile", + es="Percentil inferior", + pt="Percentil inferior", + zh="下分位数", + de="Unteres Perzentil", + ), + ) # type: ignore + + upper_percentile: schema_field( + float_field(ge=0.0, le=1.0), + placeholder=0.95, + description=MultilingualString( + en="Upper percentile of the feature values used as grid end.", + es="Percentil superior de los valores usados como fin de la grilla.", + pt="Percentil superior dos valores usados como fim da grade.", + zh="用作网格终点的特征值上分位数。", + de="Oberes Perzentil der Merkmalswerte als Gitterende.", + ), + alias=MultilingualString( + en="Upper percentile", + es="Percentil superior", + pt="Percentil superior", + zh="上分位数", + de="Oberes Perzentil", + ), + ) # type: ignore + + +class RegressionPartialDependence(BaseGlobalExplainer): + """Partial dependence curves for regression models. + + For each numeric feature, sweeps a grid of values, replaces the feature + with each grid value across the test set and averages the model's + predictions, showing the marginal effect of the feature on the predicted + value. Model agnostic (only ``predict`` is queried); assumes features are + not strongly correlated. + + References + ---------- + - [1] Friedman, J.H. (2001). "Greedy Function Approximation: A Gradient + Boosting Machine." Annals of Statistics 29(5). + - [2] https://scikit-learn.org/stable/modules/partial_dependence.html + """ + + COMPATIBLE_COMPONENTS = ["RegressionTask"] + DISPLAY_NAME = MultilingualString( + en="Partial Dependence (regression)", + es="Dependencia Parcial (regresión)", + pt="Dependência Parcial (regressão)", + zh="部分依赖(回归)", + de="Partielle Abhängigkeit (Regression)", + ) + DESCRIPTION = MultilingualString( + en=( + "Shows how the model's predicted value changes on average as each " + "feature sweeps through its range." + ), + es=( + "Muestra cómo cambia en promedio el valor predicho por el modelo " + "a medida que cada característica recorre su rango." + ), + pt=( + "Mostra como o valor previsto pelo modelo muda em média à medida " + "que cada característica percorre seu intervalo." + ), + zh="展示随着每个特征遍历其取值范围,模型预测值的平均变化。", + de=( + "Zeigt, wie sich der vorhergesagte Wert des Modells im Mittel " + "ändert, wenn jedes Merkmal seinen Wertebereich durchläuft." + ), + ) + COLOR = "#5D4037" + SCHEMA = RegressionPartialDependenceSchema + + def __init__( + self, + model: BaseModel, + grid_resolution: int = 50, + lower_percentile: float = 0.05, + upper_percentile: float = 0.95, + ): + """Initialise the regression Partial Dependence explainer. + + Parameters + ---------- + model : BaseModel + The trained DashAI regression model to be explained. + grid_resolution : int + Number of grid points per feature. + lower_percentile : float + Lower percentile of the feature values used as grid start. + upper_percentile : float + Upper percentile of the feature values used as grid end. + """ + super().__init__(model) + assert lower_percentile < upper_percentile, ( + "lower_percentile must be smaller than upper_percentile" + ) + self.grid_resolution = grid_resolution + self.lower_percentile = lower_percentile + self.upper_percentile = upper_percentile + + def explain(self, dataset): + """Compute partial dependence curves on the test split. + + Parameters + ---------- + dataset : Tuple[DatasetDict, DatasetDict] + A ``(x, y)`` pair where each element has at least a ``"test"`` + split. + + Returns + ------- + dict + Mapping from feature name to ``{"grid_values", "average"}``, + plus a ``"metadata"`` entry with the output column name. + """ + import numpy as np + + x, y = dataset + x_test = x["test"].to_pandas() + + # Cap rows to bound the number of model evaluations. + max_rows = 200 + if len(x_test) > max_rows: + x_test = x_test.iloc[:max_rows] + + output_column = y["test"].column_names[0] + explanation = {"metadata": {"output_column": output_column}} + + for column in x_test.columns: + if not np.issubdtype(x_test[column].dtype, np.number): + continue + + values = x_test[column].to_numpy(dtype=float) + start = np.quantile(values, self.lower_percentile) + stop = np.quantile(values, self.upper_percentile) + grid = np.linspace(start, stop, self.grid_resolution) + + averages = [] + frame = x_test.copy() + for grid_value in grid: + frame[column] = grid_value + predictions = np.asarray(self.model.predict(frame)).ravel() + averages.append(float(np.round(np.mean(predictions), 4))) + + explanation[column] = { + "grid_values": np.round(grid, 4).tolist(), + "average": averages, + } + + return explanation + + def plot(self, explanation: dict) -> List[Artifact]: + """Create one line-plot artifact per feature. + + Parameters + ---------- + explanation : dict + Output of :meth:`explain`. + + Returns + ------- + List[Artifact] + A list of artifacts: one plotly artifact per numeric feature. + """ + import plotly.graph_objs as go + + exp = explanation.copy() + metadata = exp.pop("metadata") + output_column = metadata["output_column"] + + artifacts = [] + for feature, curve in exp.items(): + fig = go.Figure( + go.Scatter( + x=curve["grid_values"], + y=curve["average"], + mode="lines", + ) + ) + fig.update_layout( + title={ + "text": f"Partial dependence of {output_column} on {feature}", + "font": {"size": 14}, + }, + xaxis={"title_text": feature}, + yaxis={"title_text": f"Average predicted {output_column}"}, + margin={"l": 60, "r": 30, "t": 50, "b": 50}, + ) + artifacts.append(PlotlyArtifact(payload=fig, title=feature)) + + return artifacts diff --git a/DashAI/back/explainability/explainers/regression_permutation_feature_importance.py b/DashAI/back/explainability/explainers/regression_permutation_feature_importance.py new file mode 100644 index 000000000..3749ac8a5 --- /dev/null +++ b/DashAI/back/explainability/explainers/regression_permutation_feature_importance.py @@ -0,0 +1,312 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + float_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.global_explainer import BaseGlobalExplainer +from DashAI.back.models.base_model import BaseModel + + +class RegressionPermutationFeatureImportanceSchema(BaseSchema): + """Schema for the regression Permutation Feature Importance explainer. + + Configures the regression scoring metric, the number of permutation + repeats per feature and the random seed. + """ + + scoring: schema_field( + enum_field(enum=["r2", "neg_mean_squared_error", "neg_mean_absolute_error"]), + placeholder="r2", + description=MultilingualString( + en=( + "Regression metric used to evaluate how the model's " + "performance changes when a particular feature is shuffled." + ), + es=( + "Métrica de regresión utilizada para evaluar cómo cambia el " + "rendimiento del modelo cuando se baraja una característica." + ), + pt=( + "Métrica de regressão usada para avaliar como o desempenho do " + "modelo muda quando uma característica é embaralhada." + ), + zh="用于评估特定特征被打乱时模型性能变化的回归指标。", + de=( + "Regressionsmetrik zur Bewertung, wie sich die Modellleistung " + "ändert, wenn ein bestimmtes Merkmal permutiert wird." + ), + ), + alias=MultilingualString( + en="Scoring metric", + es="Métrica de evaluación", + pt="Métrica de avaliação", + zh="评分指标", + de="Bewertungsmetrik", + ), + ) # type: ignore + + n_repeats: schema_field( + int_field(ge=1), + placeholder=10, + description=MultilingualString( + en="Number of times to permute a feature.", + es="Número de veces que se permuta una característica.", + pt="Número de vezes que uma característica é permutada.", + zh="对特征进行排列的次数。", + de="Anzahl der Permutationen eines Merkmals.", + ), + alias=MultilingualString( + en="Number of repeats", + es="Número de repeticiones", + pt="Número de repetições", + zh="重复次数", + de="Anzahl der Wiederholungen", + ), + ) # type: ignore + + random_state: schema_field( + int_field(), + placeholder=0, + description=MultilingualString( + en=( + "Seed for the random number generator to control permutations " + "of each feature." + ), + es=( + "Semilla del generador aleatorio para controlar las " + "permutaciones de cada característica." + ), + pt=( + "Semente do gerador de números aleatórios para controlar as " + "permutações de cada característica." + ), + zh="用于控制每个特征排列的随机数生成器种子。", + de=( + "Startwert für den Zufallszahlengenerator zur Steuerung der " + "Permutationen jedes Merkmals." + ), + ), + alias=MultilingualString( + en="Random state", + es="Semilla aleatoria", + pt="Estado aleatório", + zh="随机状态", + de="Zufallszustand", + ), + ) # type: ignore + + max_samples_fraction: schema_field( + float_field(ge=0.0, le=1.0), + placeholder=1.0, + description=MultilingualString( + en=( + "Fraction of samples to draw from the test set to calculate " + "feature importance at each repetition." + ), + es=( + "Fracción de muestras a extraer del conjunto de prueba para " + "calcular la importancia en cada repetición." + ), + pt=( + "Fração de amostras a extrair do conjunto de teste para " + "calcular a importância a cada repetição." + ), + zh="每次重复时从测试集中抽取的样本比例。", + de=( + "Anteil der aus dem Testdatensatz gezogenen Stichproben zur " + "Berechnung der Merkmalswichtigkeit." + ), + ), + alias=MultilingualString( + en="Max samples fraction", + es="Fracción máxima de muestras", + pt="Fração máxima de amostras", + zh="最大样本比例", + de="Maximaler Stichprobenanteil", + ), + ) # type: ignore + + +class RegressionPermutationFeatureImportance(BaseGlobalExplainer): + """Global permutation feature importance for regression models. + + Measures the importance of each feature by randomly shuffling its values + across the test set and recording the resulting decrease in a regression + scoring metric (R2, negative MSE or negative MAE). Repeating the + permutation ``n_repeats`` times yields a mean importance and standard + deviation per feature. The method is model agnostic and computed on held + out data. + + References + ---------- + - [1] Breiman, L. (2001). "Random Forests." Machine Learning, 45(1), 5-32. + - [2] Fisher, A. et al. (2019). "All Models are Wrong, but Many are + Useful." JMLR, 20(177), 1-81. https://arxiv.org/abs/1801.01489 + """ + + COMPATIBLE_COMPONENTS = ["RegressionTask"] + DISPLAY_NAME = MultilingualString( + en="Permutation Feature Importance (regression)", + es="Importancia por Permutación (regresión)", + pt="Importância por Permutação (regressão)", + zh="排列特征重要性(回归)", + de="Permutations-Merkmalswichtigkeit (Regression)", + ) + DESCRIPTION = MultilingualString( + en=( + "Assesses feature importance for regression models by measuring " + "the drop in a regression metric when a feature's values are " + "randomly shuffled." + ), + es=( + "Evalúa la importancia de las características en modelos de " + "regresión midiendo la caída de una métrica de regresión cuando " + "los valores de una característica se barajan aleatoriamente." + ), + pt=( + "Avalia a importância das características em modelos de regressão " + "medindo a queda de uma métrica de regressão quando os valores de " + "uma característica são embaralhados aleatoriamente." + ), + zh="通过测量特征值被随机打乱时回归指标的下降来评估回归模型的特征重要性。", + de=( + "Bewertet die Merkmalswichtigkeit von Regressionsmodellen durch " + "Messung des Abfalls einer Regressionsmetrik, wenn die Werte " + "eines Merkmals zufällig permutiert werden." + ), + ) + COLOR = "#3F51B5" + SCHEMA = RegressionPermutationFeatureImportanceSchema + + def __init__( + self, + model: BaseModel, + scoring: str = "r2", + n_repeats: int = 10, + random_state: int = None, + max_samples_fraction: float = 1.0, + ): + """Initialise the regression permutation feature importance explainer. + + Parameters + ---------- + model : BaseModel + The trained DashAI regression model to be explained. + scoring : str + Regression metric: 'r2', 'neg_mean_squared_error' or + 'neg_mean_absolute_error'. + n_repeats : int + Number of times each feature is permuted. + random_state : int or None + Seed for the random number generator controlling permutations. + max_samples_fraction : float + Fraction of the test set sampled for the calculation. + """ + super().__init__(model) + + from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score + + metrics = { + "r2": r2_score, + "neg_mean_squared_error": lambda y_true, y_pred: ( + -mean_squared_error(y_true, y_pred) + ), + "neg_mean_absolute_error": lambda y_true, y_pred: ( + -mean_absolute_error(y_true, y_pred) + ), + } + + self.scoring_name = scoring + self.scoring = metrics[scoring] + self.n_repeats = n_repeats + self.random_state = random_state + self.max_samples_fraction = max_samples_fraction + + def explain(self, dataset): + """Compute permutation feature importance on the test split. + + Parameters + ---------- + dataset : Tuple[DatasetDict, DatasetDict] + A ``(x, y)`` pair where each element has at least a ``"test"`` + split. + + Returns + ------- + dict + Dictionary with keys ``"features"``, ``"importances_mean"`` and + ``"importances_std"``. + """ + import numpy as np + + x, y = dataset + x_test = x["test"].to_pandas() + y_test = y["test"].to_pandas().to_numpy().ravel() + + rng = np.random.RandomState(self.random_state) + n_samples = max(1, int(len(x_test) * self.max_samples_fraction)) + sample_indexes = rng.choice(len(x_test), size=n_samples, replace=False) + x_sample = x_test.iloc[sample_indexes].reset_index(drop=True) + y_sample = y_test[sample_indexes] + + baseline_score = self.scoring( + y_sample, np.asarray(self.model.predict(x_sample)).ravel() + ) + + results = {"features": [], "importances_mean": [], "importances_std": []} + for column in x_sample.columns: + importances = [] + for _ in range(self.n_repeats): + x_permuted = x_sample.copy() + x_permuted[column] = x_sample[column].to_numpy()[ + rng.permutation(n_samples) + ] + permuted_score = self.scoring( + y_sample, np.asarray(self.model.predict(x_permuted)).ravel() + ) + importances.append(baseline_score - permuted_score) + + results["features"].append(column) + results["importances_mean"].append(float(np.round(np.mean(importances), 3))) + results["importances_std"].append(float(np.round(np.std(importances), 3))) + + return results + + def plot(self, explanation: dict) -> List[Artifact]: + """Create a bar chart of feature importances. + + Parameters + ---------- + explanation : dict + Output of :meth:`explain`. + + Returns + ------- + List[Artifact] + A list with a single plotly artifact holding the importance bar + chart. + """ + import pandas as pd + import plotly.express as px + + data = pd.DataFrame.from_dict(explanation) + data = data.sort_values(by=["importances_mean"], ascending=True) + + fig = px.bar( + data, + x=data["importances_mean"], + y=data["features"], + error_x=data["importances_std"], + ) + fig.update_layout( + xaxis_title=f"Importance ({self.scoring_name})", + yaxis_title=None, + ) + + return [PlotlyArtifact(payload=fig, title="Permutation Feature Importance")] diff --git a/DashAI/back/explainability/explainers/token_ablation.py b/DashAI/back/explainability/explainers/token_ablation.py new file mode 100644 index 000000000..b0f20b488 --- /dev/null +++ b/DashAI/back/explainability/explainers/token_ablation.py @@ -0,0 +1,358 @@ +from typing import List + +from DashAI.back.core.artifacts import Artifact, PlotlyArtifact, TextArtifact +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + int_field, + schema_field, +) +from DashAI.back.core.utils import MultilingualString +from DashAI.back.explainability.local_explainer import BaseLocalExplainer +from DashAI.back.models.base_model import BaseModel + + +class TokenAblationSchema(BaseSchema): + """Schema for the Token Ablation explainer hyperparameters. + + Configures how many tokens are evaluated per instance and how ablated + tokens are replaced. + """ + + max_tokens: schema_field( + int_field(ge=1, le=256), + placeholder=50, + description=MultilingualString( + en=( + "Maximum number of tokens (whitespace-separated words) " + "evaluated per instance. Texts longer than this are truncated " + "for the analysis to bound the number of model calls." + ), + es=( + "Número máximo de tokens (palabras separadas por espacios) " + "evaluados por instancia. Los textos más largos se truncan " + "para el análisis para limitar las llamadas al modelo." + ), + pt=( + "Número máximo de tokens (palavras separadas por espaços) " + "avaliados por instância. Textos mais longos são truncados " + "para a análise para limitar as chamadas ao modelo." + ), + zh="每个实例评估的最大token数(按空格分词)。超长文本将被截断以限制模型调用次数。", + de=( + "Maximale Anzahl der pro Instanz ausgewerteten Tokens (durch " + "Leerzeichen getrennte Wörter). Längere Texte werden für die " + "Analyse gekürzt, um die Modellaufrufe zu begrenzen." + ), + ), + alias=MultilingualString( + en="Max tokens", + es="Máximo de tokens", + pt="Máximo de tokens", + zh="最大token数", + de="Maximale Tokenanzahl", + ), + ) # type: ignore + + replacement: schema_field( + enum_field(enum=["remove", "unk"]), + placeholder="remove", + description=MultilingualString( + en=( + "How an ablated token is handled: 'remove' deletes it from " + "the text, 'unk' replaces it with the [UNK] placeholder." + ), + es=( + "Cómo se trata un token eliminado: 'remove' lo borra del " + "texto, 'unk' lo reemplaza por el marcador [UNK]." + ), + pt=( + "Como um token removido é tratado: 'remove' o exclui do " + "texto, 'unk' o substitui pelo marcador [UNK]." + ), + zh="被消融token的处理方式:'remove'从文本中删除,'unk'替换为[UNK]占位符。", + de=( + "Behandlung eines entfernten Tokens: 'remove' löscht es aus " + "dem Text, 'unk' ersetzt es durch den Platzhalter [UNK]." + ), + ), + alias=MultilingualString( + en="Replacement strategy", + es="Estrategia de reemplazo", + pt="Estratégia de substituição", + zh="替换策略", + de="Ersetzungsstrategie", + ), + ) # type: ignore + + +class TokenAblation(BaseLocalExplainer): + """Occlusion-based local explainer for text classification. + + For each instance, ablates one token at a time (removing it or replacing + it with an [UNK] placeholder) and measures how much the predicted class + probability drops. Tokens whose removal causes a large drop are the ones + the model relied on for its prediction. The method is model agnostic: it + only queries ``predict``, so it works with any text classifier. + + References + ---------- + - [1] Zeiler, M.D. & Fergus, R. (2014). "Visualizing and Understanding + Convolutional Networks." ECCV 2014. https://arxiv.org/abs/1311.2901 + - [2] Li, J. et al. (2016). "Understanding Neural Networks through + Representation Erasure." https://arxiv.org/abs/1612.08220 + """ + + COMPATIBLE_COMPONENTS = ["TextClassificationTask"] + DISPLAY_NAME = MultilingualString( + en="Token Ablation", + es="Ablación de tokens", + pt="Ablação de tokens", + zh="Token消融", + de="Token-Ablation", + ) + DESCRIPTION = MultilingualString( + en=( + "Measures each word's importance by removing it from the text " + "and recording the drop in the predicted class probability." + ), + es=( + "Mide la importancia de cada palabra eliminándola del texto y " + "registrando la caída en la probabilidad de la clase predicha." + ), + pt=( + "Mede a importância de cada palavra removendo-a do texto e " + "registrando a queda na probabilidade da classe prevista." + ), + zh="通过从文本中删除每个词并记录预测类别概率的下降来衡量词的重要性。", + de=( + "Misst die Wichtigkeit jedes Wortes, indem es aus dem Text " + "entfernt und der Rückgang der vorhergesagten " + "Klassenwahrscheinlichkeit erfasst wird." + ), + ) + COLOR = "#E65100" + SCHEMA = TokenAblationSchema + + def __init__( + self, + model: BaseModel, + max_tokens: int = 50, + replacement: str = "remove", + ) -> None: + """Initialize a new instance of a TokenAblation explainer. + + Parameters + ---------- + model : BaseModel + Text classification model to be explained. + max_tokens : int + Maximum number of tokens evaluated per instance. + replacement : str + 'remove' to delete the token, 'unk' to replace it with [UNK]. + """ + super().__init__(model) + self.max_tokens = max_tokens + self.replacement = replacement + + def fit(self, background_dataset, **kwargs): + """Store class names from the training targets. + + Parameters + ---------- + background_dataset : Tuple[DatasetDict, DatasetDict] + Tuple ``(x, y)`` with the dataset splits. + **kwargs : Any + Ignored; present for interface compatibility. + + Returns + ------- + TokenAblation + The fitted explainer instance (``self``). + """ + _, y = background_dataset + y_train = y["train"] + + output_column = y_train.column_names[0] + target_names = y_train.types[output_column].categories + self.metadata = {"target_names": list(target_names)} + + return self + + def _ablate(self, tokens, index): + """Build the text variant with the token at ``index`` ablated. + + Parameters + ---------- + tokens : List[str] + Whitespace tokens of the original text. + index : int + Position of the token to ablate. + + Returns + ------- + str + The perturbed text. + """ + if self.replacement == "unk": + variant = tokens.copy() + variant[index] = "[UNK]" + return " ".join(variant) + return " ".join(tokens[:index] + tokens[index + 1 :]) + + def explain_instance(self, instances): + """Compute token importances for each instance. + + Parameters + ---------- + instances : DatasetDict + Instances to be explained; must contain a single text column. + + Returns + ------- + dict + Dictionary with, for each instance, the tokens, their importance + (probability drop when ablated) and the model prediction. + """ + import numpy as np + import pandas as pd + + from DashAI.back.dataloaders.classes.dashai_dataset import to_dashai_dataset + + dataset = to_dashai_dataset(instances) + X = dataset.to_pandas() + + # The job may hand over an already-prepared dataset (e.g. tokenized by + # a transformer model, adding input_ids/attention_mask columns). + # Rebuild a clean single-text-column dataset so that model.predict can + # run its own preparation from raw text. + tokenizer_columns = {"input_ids", "attention_mask", "token_type_ids", "label"} + text_columns = [c for c in X.columns if c not in tokenizer_columns] + if not text_columns: + raise ValueError(f"No text column found among columns: {list(X.columns)}") + text_column = text_columns[0] + texts = X[text_column].astype(str).tolist() + + base_dataset = to_dashai_dataset(pd.DataFrame({text_column: texts})) + base_predictions = np.asarray(self.model.predict(base_dataset)) + + explanation = {"metadata": {**self.metadata, "text_column": text_column}} + for i, text in enumerate(texts): + tokens = str(text).split()[: self.max_tokens] + predicted_class = int(np.argmax(base_predictions[i])) + base_prob = float(base_predictions[i][predicted_class]) + + importances = [] + if tokens: + variants = [self._ablate(tokens, index) for index in range(len(tokens))] + variants_dataset = to_dashai_dataset( + pd.DataFrame({text_column: variants}) + ) + variant_predictions = np.asarray(self.model.predict(variants_dataset)) + importances = [ + float( + np.round(base_prob - variant_predictions[j][predicted_class], 4) + ) + for j in range(len(tokens)) + ] + + explanation[i] = { + "text": str(text), + "tokens": tokens, + "token_importances": importances, + "model_prediction": base_predictions[i].tolist(), + "predicted_class": predicted_class, + } + + return explanation + + def plot(self, explanation: dict) -> List[Artifact]: + """Render each instance as a token importance bar plot plus a summary. + + Parameters + ---------- + explanation : dict + Dictionary with the explanation generated by the explainer. + + Returns + ------- + List[Artifact] + A list of typed artifacts: one plotly and one text artifact per + explained instance. + """ + import numpy as np + import pandas as pd + import plotly.graph_objs as go + + exp = explanation.copy() + metadata = exp.pop("metadata") + target_names = metadata["target_names"] + max_tokens_plotted = 15 + + artifacts = [] + for i in exp: + instance = exp[i] + predicted_class = instance["predicted_class"] + predicted_name = target_names[predicted_class] + predicted_prob = float( + np.round(instance["model_prediction"][predicted_class], 3) + ) + + data = pd.DataFrame( + { + "tokens": [ + f"{token} ({position})" + for position, token in enumerate(instance["tokens"]) + ], + "importances": instance["token_importances"], + } + ) + data["importance_abs"] = data["importances"].abs() + data = data.sort_values(by="importance_abs", ascending=True) + if len(data) > max_tokens_plotted: + data = data.iloc[-max_tokens_plotted:, :] + + colors = [ + "rgb(231,63,116)" if value >= 0 else "rgb(47,138,196)" + for value in data["importances"] + ] + fig = go.Figure( + go.Bar( + x=data["importances"], + y=data["tokens"], + orientation="h", + marker={"color": colors}, + text=data["importances"], + textposition="auto", + ) + ) + fig.update_layout( + title={ + "text": ( + f"Token importance for prediction {predicted_name} " + f"(p={predicted_prob})" + ), + "font": {"size": 14}, + }, + margin={"pad": 20, "l": 100, "r": 60, "t": 60, "b": 40}, + xaxis={"title_text": "Probability drop when token is ablated"}, + yaxis={"showgrid": True}, + ) + + title = f"Instance {int(i) + 1}" + artifacts.append(PlotlyArtifact(payload=fig, title=title)) + + top = data.iloc[::-1].head(3) + top_tokens = ", ".join( + f"'{token}' ({importance:+})" + for token, importance in zip( + top["tokens"].tolist(), top["importances"].tolist(), strict=True + ) + ) + summary = ( + f"The model predicted {predicted_name} (p={predicted_prob}). " + f"Most influential tokens: {top_tokens}." + ) + artifacts.append(TextArtifact(payload=summary, title=title)) + + return artifacts diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 3c26524e7..18ac12d7b 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -76,11 +76,31 @@ from DashAI.back.dataset_sources.zenodo_dataset_source import ZenodoDatasetSource # Explainers +from DashAI.back.explainability.explainers.contrastive_shap import ContrastiveShap +from DashAI.back.explainability.explainers.dice_counterfactual import ( + DiceCounterfactual, +) +from DashAI.back.explainability.explainers.grad_cam import GradCam from DashAI.back.explainability.explainers.kernel_shap import KernelShap +from DashAI.back.explainability.explainers.lime_text import LimeText +from DashAI.back.explainability.explainers.nearest_counterfactual import ( + NearestCounterfactual, +) +from DashAI.back.explainability.explainers.occlusion_saliency import OcclusionSaliency from DashAI.back.explainability.explainers.partial_dependence import PartialDependence from DashAI.back.explainability.explainers.permutation_feature_importance import ( PermutationFeatureImportance, ) +from DashAI.back.explainability.explainers.regression_kernel_shap import ( + RegressionKernelShap, +) +from DashAI.back.explainability.explainers.regression_partial_dependence import ( + RegressionPartialDependence, +) +from DashAI.back.explainability.explainers.regression_permutation_feature_importance import ( # noqa: E501 + RegressionPermutationFeatureImportance, +) +from DashAI.back.explainability.explainers.token_ablation import TokenAblation # Explorers from DashAI.back.exploration.explorers.box_plot import BoxPlotExplorer @@ -442,9 +462,19 @@ def get_initial_components(): GenerativeJob, PipelineJob, # Explainers + ContrastiveShap, + DiceCounterfactual, + GradCam, KernelShap, + LimeText, + NearestCounterfactual, + OcclusionSaliency, PartialDependence, PermutationFeatureImportance, + RegressionKernelShap, + RegressionPartialDependence, + RegressionPermutationFeatureImportance, + TokenAblation, # Explorers DescribeExplorer, ScatterPlotExplorer, diff --git a/tests/back/explainers/test_image_explainers.py b/tests/back/explainers/test_image_explainers.py new file mode 100644 index 000000000..bed104c2e --- /dev/null +++ b/tests/back/explainers/test_image_explainers.py @@ -0,0 +1,151 @@ +import numpy as np +import pytest +from PIL import Image + +from DashAI.back.explainability.explainers.grad_cam import GradCam +from DashAI.back.explainability.explainers.occlusion_saliency import ( + OcclusionSaliency, +) + +IMAGE_SIZE = 32 + + +class _FakeImageValue: + """Wraps a PIL image behind the DashAI image type interface.""" + + def __init__(self, pil_image): + self._pil_image = pil_image + + def to_pil(self): + return self._pil_image + + +class _FakeImageDataset: + """Minimal stand-in for a DashAIDataset holding one image column.""" + + def __init__(self, images): + self._rows = [{"image": _FakeImageValue(image)} for image in images] + self.features = {"image": None} + + def __len__(self): + return len(self._rows) + + def __getitem__(self, index): + return self._rows[index] + + +class _ConvImageModel: + """Tiny convolutional image classifier exposing the capability contract.""" + + def __init__(self): + import torch + import torch.nn as nn + + torch.manual_seed(0) + self.image_size = IMAGE_SIZE + self.device = torch.device("cpu") + self.idx_to_label = {0: "cat", 1: "dog"} + self.model = nn.Sequential( + nn.Conv2d(3, 4, 3, padding=1), + nn.ReLU(), + nn.AdaptiveAvgPool2d(4), + nn.Flatten(), + nn.Linear(4 * 4 * 4, 2), + ) + + def get_inference_transform(self): + from torchvision import transforms + + return transforms.Compose( + [ + transforms.Lambda(lambda img: img.convert("RGB")), + transforms.Resize((self.image_size, self.image_size)), + transforms.ToTensor(), + ] + ) + + +class _MlpImageModel(_ConvImageModel): + """Image model with no convolutional layers (like MLPImageClassifier).""" + + def __init__(self): + import torch + import torch.nn as nn + + super().__init__() + torch.manual_seed(0) + self.model = nn.Sequential( + nn.Flatten(), + nn.Linear(3 * IMAGE_SIZE * IMAGE_SIZE, 2), + ) + + +@pytest.fixture(name="images") +def images_fixture(): + rng = np.random.RandomState(0) + return [ + Image.fromarray( + rng.randint(0, 255, size=(IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.uint8) + ) + for _ in range(2) + ] + + +def _assert_image_explanation(explanation, n_instances): + assert explanation["metadata"]["target_names"] == ["cat", "dog"] + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == n_instances + + for key in instance_keys: + instance = explanation[key] + heatmap = np.asarray(instance["heatmap"]) + assert heatmap.shape == (IMAGE_SIZE, IMAGE_SIZE) + assert heatmap.min() >= 0.0 + assert heatmap.max() <= 1.0 + assert np.asarray(instance["image"]).shape == (IMAGE_SIZE, IMAGE_SIZE, 3) + assert instance["predicted_class"] in (0, 1) + assert len(instance["model_prediction"]) == 2 + + +@pytest.mark.parametrize("method", ["gradcam", "gradcam++"]) +def test_grad_cam(images, method): + model = _ConvImageModel() + explainer = GradCam(model, method=method) + explainer.fit((None, None)) + + explanation = explainer.explain_instance(_FakeImageDataset(images)) + _assert_image_explanation(explanation, len(images)) + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 2 * len(images) + assert [a.type for a in artifacts[:2]] == ["plotly", "text"] + + +def test_grad_cam_rejects_non_convolutional_models(images): + explainer = GradCam(_MlpImageModel()) + explainer.fit((None, None)) + + with pytest.raises(ValueError, match="convolutional"): + explainer.explain_instance(_FakeImageDataset(images)) + + +def test_occlusion_saliency(images): + model = _ConvImageModel() + explainer = OcclusionSaliency(model, patch_size=8, stride=8) + explainer.fit((None, None)) + + explanation = explainer.explain_instance(_FakeImageDataset(images)) + _assert_image_explanation(explanation, len(images)) + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 2 * len(images) + assert [a.type for a in artifacts[:2]] == ["plotly", "text"] + + +def test_occlusion_saliency_works_without_conv_layers(images): + # Unlike Grad-CAM, occlusion only needs forward passes. + explainer = OcclusionSaliency(_MlpImageModel(), patch_size=8, stride=8) + explainer.fit((None, None)) + + explanation = explainer.explain_instance(_FakeImageDataset(images)) + _assert_image_explanation(explanation, len(images)) diff --git a/tests/back/explainers/test_lib_explainers.py b/tests/back/explainers/test_lib_explainers.py new file mode 100644 index 000000000..e1f7d684d --- /dev/null +++ b/tests/back/explainers/test_lib_explainers.py @@ -0,0 +1,175 @@ +import copy + +import numpy as np +import pandas as pd +import pyarrow as pa +import pytest + +from DashAI.back.dataloaders.classes.csv_dataloader import CSVDataLoader +from DashAI.back.dataloaders.classes.dashai_dataset import ( + DashAIDataset, + select_columns, + split_dataset, + split_indexes, +) +from DashAI.back.explainability.explainers.dice_counterfactual import ( + DiceCounterfactual, +) +from DashAI.back.explainability.explainers.lime_text import LimeText +from DashAI.back.models.scikit_learn.decision_tree_classifier import ( + DecisionTreeClassifier, +) +from DashAI.back.types.categorical import Categorical +from DashAI.back.types.utils import save_types_in_arrow_metadata +from DashAI.back.types.value_types import Float + +INPUT_COLUMNS = [ + "SepalLengthCm", + "SepalWidthCm", + "PetalLengthCm", + "PetalWidthCm", +] +OUTPUT_COLUMNS = ["Species"] +TARGETS = [ + "Iris-setosa", + "Iris-versicolor", + "Iris-virginica", +] + + +@pytest.fixture(scope="module", name="dataset") +def tabular_dataset_fixture(): + dataset_path = "tests/back/explainers/iris.csv" + dataloader = CSVDataLoader() + + datasetdict = dataloader.load_data( + filepath_or_buffer=dataset_path, + temp_path="tests/back/explainers", + params={ + "separator": ",", + "schema": { + "SepalLengthCm": {"type": "Float", "dtype": "float64"}, + "SepalWidthCm": {"type": "Float", "dtype": "float64"}, + "PetalLengthCm": {"type": "Float", "dtype": "float64"}, + "PetalWidthCm": {"type": "Float", "dtype": "float64"}, + "Species": {"type": "Categorical", "dtype": "string"}, + }, + }, + ) + datasetdict.types = { + "SepalLengthCm": Float(arrow_type=pa.float64()), + "SepalWidthCm": Float(arrow_type=pa.float64()), + "PetalLengthCm": Float(arrow_type=pa.float64()), + "PetalWidthCm": Float(arrow_type=pa.float64()), + "Species": Categorical(values=TARGETS), + } + + new_table = save_types_in_arrow_metadata( + datasetdict.arrow_table, + {col: dtype.to_string() for col, dtype in datasetdict.types.items()}, + ) + + datasetdict = DashAIDataset( + new_table, splits=datasetdict.splits, types=datasetdict.types + ) + + total_rows = datasetdict.num_rows + train_indexes, test_indexes, val_indexes = split_indexes( + total_rows=total_rows, train_size=0.7, test_size=0.1, val_size=0.2 + ) + split_dataset_dict = split_dataset( + datasetdict, + train_indexes=train_indexes, + test_indexes=test_indexes, + val_indexes=val_indexes, + ) + + x, y = select_columns(split_dataset_dict, INPUT_COLUMNS, OUTPUT_COLUMNS) + + y = split_dataset(y) + x = split_dataset(x) + + return x, y + + +@pytest.fixture(scope="module", name="trained_model") +def trained_model(dataset): + x, y = dataset + model = DecisionTreeClassifier( + criterion="gini", + max_depth=3, + min_samples_split=2, + min_samples_leaf=1, + max_features=None, + ) + model.train(x["train"], y["train"]) + + return model + + +def test_dice_counterfactual(trained_model, dataset): + x, _ = dataset + + explainer = DiceCounterfactual(trained_model, total_cfs=2, method="random") + explainer.fit(copy.deepcopy(dataset)) + + instances = x["test"].select(range(2)) + explanation = explainer.explain_instance(instances) + + metadata = explanation["metadata"] + assert metadata["feature_names"] == INPUT_COLUMNS + assert set(metadata["target_names"]) == set(TARGETS) + + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == 2 + + found_any = False + for key in instance_keys: + instance = explanation[key] + assert len(instance["instance_values"]) == len(INPUT_COLUMNS) + assert 0 <= instance["predicted_class"] < len(TARGETS) + for counterfactual in instance["counterfactuals"]: + found_any = True + assert len(counterfactual["values"]) == len(INPUT_COLUMNS) + # A counterfactual must reach a different class. + assert counterfactual["predicted_class"] != instance["predicted_class"] + # DiCE's random search on iris should find counterfactuals. + assert found_any + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 2 * len(instance_keys) + types = {a.type for a in artifacts} + assert types == {"table", "text"} + + +class DummyTextModel: + """Predicts positive when the text contains the word 'good'.""" + + def predict(self, dataset): + frame = dataset.to_pandas() + texts = frame.iloc[:, 0].tolist() + return np.array( + [[0.1, 0.9] if "good" in str(t).split() else [0.9, 0.1] for t in texts] + ) + + +def test_lime_text(): + explainer = LimeText(DummyTextModel(), num_features=5, num_samples=200) + explainer.metadata = {"target_names": ["negative", "positive"]} + + instances = pd.DataFrame({"text": ["this movie was good indeed"]}) + explanation = explainer.explain_instance(instances) + + instance = explanation[0] + assert instance["predicted_class"] == 1 + + word_weights = dict(instance["word_weights"]) + assert "good" in word_weights + # 'good' drives the dummy model towards the positive class. + assert word_weights["good"] > 0 + assert word_weights["good"] == max(word_weights.values()) + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 2 + assert [a.type for a in artifacts] == ["plotly", "text"] + assert "good" in artifacts[1].payload diff --git a/tests/back/explainers/test_new_explainers.py b/tests/back/explainers/test_new_explainers.py new file mode 100644 index 000000000..279a9be2b --- /dev/null +++ b/tests/back/explainers/test_new_explainers.py @@ -0,0 +1,225 @@ +import copy + +import numpy as np +import pyarrow as pa +import pytest + +from DashAI.back.dataloaders.classes.csv_dataloader import CSVDataLoader +from DashAI.back.dataloaders.classes.dashai_dataset import ( + DashAIDataset, + select_columns, + split_dataset, + split_indexes, +) +from DashAI.back.explainability.explainers.contrastive_shap import ContrastiveShap +from DashAI.back.explainability.explainers.nearest_counterfactual import ( + NearestCounterfactual, +) +from DashAI.back.models.scikit_learn.decision_tree_classifier import ( + DecisionTreeClassifier, +) +from DashAI.back.types.categorical import Categorical +from DashAI.back.types.utils import save_types_in_arrow_metadata +from DashAI.back.types.value_types import Float + +INPUT_COLUMNS = [ + "SepalLengthCm", + "SepalWidthCm", + "PetalLengthCm", + "PetalWidthCm", +] +OUTPUT_COLUMNS = ["Species"] +TARGETS = [ + "Iris-setosa", + "Iris-versicolor", + "Iris-virginica", +] + + +@pytest.fixture(scope="module", name="dataset") +def tabular_model_fixture(): + dataset_path = "tests/back/explainers/iris.csv" + dataloader = CSVDataLoader() + + datasetdict = dataloader.load_data( + filepath_or_buffer=dataset_path, + temp_path="tests/back/explainers", + params={ + "separator": ",", + "schema": { + "SepalLengthCm": {"type": "Float", "dtype": "float64"}, + "SepalWidthCm": {"type": "Float", "dtype": "float64"}, + "PetalLengthCm": {"type": "Float", "dtype": "float64"}, + "PetalWidthCm": {"type": "Float", "dtype": "float64"}, + "Species": {"type": "Categorical", "dtype": "string"}, + }, + }, + ) + datasetdict.types = { + "SepalLengthCm": Float(arrow_type=pa.float64()), + "SepalWidthCm": Float(arrow_type=pa.float64()), + "PetalLengthCm": Float(arrow_type=pa.float64()), + "PetalWidthCm": Float(arrow_type=pa.float64()), + "Species": Categorical(values=TARGETS), + } + + new_table = save_types_in_arrow_metadata( + datasetdict.arrow_table, + {col: dtype.to_string() for col, dtype in datasetdict.types.items()}, + ) + + datasetdict = DashAIDataset( + new_table, splits=datasetdict.splits, types=datasetdict.types + ) + + total_rows = datasetdict.num_rows + train_indexes, test_indexes, val_indexes = split_indexes( + total_rows=total_rows, train_size=0.7, test_size=0.1, val_size=0.2 + ) + split_dataset_dict = split_dataset( + datasetdict, + train_indexes=train_indexes, + test_indexes=test_indexes, + val_indexes=val_indexes, + ) + + x, y = select_columns(split_dataset_dict, INPUT_COLUMNS, OUTPUT_COLUMNS) + + y = split_dataset(y) + x = split_dataset(x) + + return x, y + + +@pytest.fixture(scope="module", name="trained_model") +def trained_model(dataset): + x, y = dataset + model = DecisionTreeClassifier( + criterion="gini", + max_depth=3, + min_samples_split=2, + min_samples_leaf=1, + max_features=None, + ) + model.train(x["train"], y["train"]) + + return model + + +def test_nearest_counterfactual(trained_model, dataset): + x, _ = dataset + n_counterfactuals = 2 + + explainer = NearestCounterfactual( + trained_model, n_counterfactuals=n_counterfactuals, distance="l1" + ) + explainer.fit(copy.deepcopy(dataset)) + + instances = x["test"] + explanation = explainer.explain_instance(instances) + + metadata = explanation["metadata"] + assert set(metadata["target_names"]) == set(TARGETS) + assert metadata["feature_names"] == INPUT_COLUMNS + + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == instances.num_rows + + for key in instance_keys: + instance = explanation[key] + assert len(instance["instance_values"]) == len(INPUT_COLUMNS) + assert len(instance["model_prediction"]) == len(TARGETS) + assert len(instance["counterfactuals"]) <= n_counterfactuals + + for counterfactual in instance["counterfactuals"]: + # A counterfactual must be classified differently. + assert counterfactual["predicted_class"] != instance["predicted_class"] + assert counterfactual["distance"] >= 0 + assert len(counterfactual["values"]) == len(INPUT_COLUMNS) + + artifacts = explainer.plot(explanation) + # One table and one text artifact per instance. + assert len(artifacts) == 2 * len(instance_keys) + tables = [a for a in artifacts if a.type == "table"] + texts = [a for a in artifacts if a.type == "text"] + assert len(tables) == len(instance_keys) + assert len(texts) == len(instance_keys) + + first_table = tables[0].payload + # Feature rows plus the predicted class row. + assert len(first_table.rows) == len(INPUT_COLUMNS) + 1 + for cell in first_table.highlight: + assert 0 <= cell.row < len(first_table.rows) + assert 0 <= cell.column < len(first_table.columns) + + +def test_nearest_counterfactual_distance_l2(trained_model, dataset): + x, _ = dataset + + explainer = NearestCounterfactual(trained_model, n_counterfactuals=1, distance="l2") + explainer.fit(copy.deepcopy(dataset)) + + instances = x["test"].select(range(2)) + explanation = explainer.explain_instance(instances) + + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == 2 + for key in instance_keys: + assert len(explanation[key]["counterfactuals"]) == 1 + + +def test_contrastive_shap(trained_model, dataset): + x, _ = dataset + + explainer = ContrastiveShap(trained_model) + explainer.fit( + copy.deepcopy(dataset), + sample_background_data=True, + background_fraction=0.3, + ) + + instances = x["test"].select(range(3)) + explanation = explainer.explain_instance(instances) + + metadata = explanation["metadata"] + assert set(metadata["target_names"]) == set(TARGETS) + + instance_keys = [key for key in explanation if key != "metadata"] + assert len(instance_keys) == 3 + + for key in instance_keys: + instance = explanation[key] + assert instance["fact_class"] != instance["foil_class"] + assert len(instance["delta_values"]) == len(INPUT_COLUMNS) + + delta = np.asarray(instance["delta_values"]) + fact = np.asarray(instance["fact_shap_values"]) + foil = np.asarray(instance["foil_shap_values"]) + assert np.allclose(delta, fact - foil, atol=1e-2) + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 2 * len(instance_keys) + assert [a.type for a in artifacts[:2]] == ["plotly", "text"] + assert "rather than" in artifacts[1].payload + + +def test_contrastive_shap_fixed_foil(trained_model, dataset): + x, _ = dataset + + explainer = ContrastiveShap(trained_model, foil_class="Iris-virginica") + explainer.fit(copy.deepcopy(dataset)) + + instances = x["test"].select(range(2)) + explanation = explainer.explain_instance(instances) + + target_names = explanation["metadata"]["target_names"] + virginica = target_names.index("Iris-virginica") + + instance_keys = [key for key in explanation if key != "metadata"] + for key in instance_keys: + instance = explanation[key] + if instance["fact_class"] != virginica: + assert instance["foil_class"] == virginica + else: + # Fixed foil equals the fact: falls back to the runner-up class. + assert instance["foil_class"] != virginica diff --git a/tests/back/explainers/test_task_explainers.py b/tests/back/explainers/test_task_explainers.py new file mode 100644 index 000000000..5722beb9a --- /dev/null +++ b/tests/back/explainers/test_task_explainers.py @@ -0,0 +1,306 @@ +import copy + +import numpy as np +import pandas as pd +import pyarrow as pa +import pytest + +from DashAI.back.dataloaders.classes.csv_dataloader import CSVDataLoader +from DashAI.back.dataloaders.classes.dashai_dataset import ( + DashAIDataset, + select_columns, + split_dataset, + split_indexes, +) +from DashAI.back.explainability.explainers.regression_kernel_shap import ( + RegressionKernelShap, +) +from DashAI.back.explainability.explainers.regression_partial_dependence import ( + RegressionPartialDependence, +) +from DashAI.back.explainability.explainers.regression_permutation_feature_importance import ( # noqa: E501 + RegressionPermutationFeatureImportance, +) +from DashAI.back.explainability.explainers.token_ablation import TokenAblation +from DashAI.back.models.scikit_learn.linear_regression import LinearRegression +from DashAI.back.types.categorical import Categorical +from DashAI.back.types.utils import save_types_in_arrow_metadata +from DashAI.back.types.value_types import Float + +REGRESSION_INPUT_COLUMNS = [ + "SepalLengthCm", + "SepalWidthCm", + "PetalLengthCm", +] +REGRESSION_OUTPUT_COLUMNS = ["PetalWidthCm"] + + +@pytest.fixture(scope="module", name="regression_dataset") +def regression_dataset_fixture(): + dataset_path = "tests/back/explainers/iris.csv" + dataloader = CSVDataLoader() + + datasetdict = dataloader.load_data( + filepath_or_buffer=dataset_path, + temp_path="tests/back/explainers", + params={ + "separator": ",", + "schema": { + "SepalLengthCm": {"type": "Float", "dtype": "float64"}, + "SepalWidthCm": {"type": "Float", "dtype": "float64"}, + "PetalLengthCm": {"type": "Float", "dtype": "float64"}, + "PetalWidthCm": {"type": "Float", "dtype": "float64"}, + "Species": {"type": "Categorical", "dtype": "string"}, + }, + }, + ) + datasetdict.types = { + "SepalLengthCm": Float(arrow_type=pa.float64()), + "SepalWidthCm": Float(arrow_type=pa.float64()), + "PetalLengthCm": Float(arrow_type=pa.float64()), + "PetalWidthCm": Float(arrow_type=pa.float64()), + "Species": Categorical( + values=["Iris-setosa", "Iris-versicolor", "Iris-virginica"] + ), + } + + new_table = save_types_in_arrow_metadata( + datasetdict.arrow_table, + {col: dtype.to_string() for col, dtype in datasetdict.types.items()}, + ) + + datasetdict = DashAIDataset( + new_table, splits=datasetdict.splits, types=datasetdict.types + ) + + total_rows = datasetdict.num_rows + train_indexes, test_indexes, val_indexes = split_indexes( + total_rows=total_rows, train_size=0.7, test_size=0.1, val_size=0.2 + ) + split_dataset_dict = split_dataset( + datasetdict, + train_indexes=train_indexes, + test_indexes=test_indexes, + val_indexes=val_indexes, + ) + + x, y = select_columns( + split_dataset_dict, REGRESSION_INPUT_COLUMNS, REGRESSION_OUTPUT_COLUMNS + ) + + y = split_dataset(y) + x = split_dataset(x) + + return x, y + + +@pytest.fixture(scope="module", name="trained_regressor") +def trained_regressor(regression_dataset): + x, y = regression_dataset + model = LinearRegression(fit_intercept=True) + model.train(x["train"], y["train"]) + + return model + + +def test_regression_permutation_feature_importance( + trained_regressor, regression_dataset +): + explainer = RegressionPermutationFeatureImportance( + trained_regressor, + scoring="r2", + n_repeats=5, + random_state=0, + max_samples_fraction=1.0, + ) + explanation = explainer.explain(copy.deepcopy(regression_dataset)) + + assert explanation["features"] == REGRESSION_INPUT_COLUMNS + assert len(explanation["importances_mean"]) == len(REGRESSION_INPUT_COLUMNS) + assert len(explanation["importances_std"]) == len(REGRESSION_INPUT_COLUMNS) + # PetalLengthCm is highly correlated with PetalWidthCm: its importance + # must be positive. + petal_length = explanation["features"].index("PetalLengthCm") + assert explanation["importances_mean"][petal_length] > 0 + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 1 + assert artifacts[0].type == "plotly" + assert artifacts[0].title == "Permutation Feature Importance" + + +@pytest.mark.parametrize( + "scoring", ["neg_mean_squared_error", "neg_mean_absolute_error"] +) +def test_regression_pfi_other_scorings(trained_regressor, regression_dataset, scoring): + explainer = RegressionPermutationFeatureImportance( + trained_regressor, scoring=scoring, n_repeats=3, random_state=0 + ) + explanation = explainer.explain(copy.deepcopy(regression_dataset)) + assert len(explanation["importances_mean"]) == len(REGRESSION_INPUT_COLUMNS) + + +def test_regression_kernel_shap(trained_regressor, regression_dataset): + x, _ = regression_dataset + + explainer = RegressionKernelShap(trained_regressor) + explainer.fit( + copy.deepcopy(regression_dataset), + sample_background_data=True, + background_fraction=0.3, + ) + + instances = x["test"].select(range(3)) + explanation = explainer.explain_instance(instances) + + assert explanation["metadata"]["feature_names"] == REGRESSION_INPUT_COLUMNS + assert explanation["metadata"]["output_column"] == "PetalWidthCm" + + base_value = explanation["base_value"] + instance_keys = [ + key for key in explanation if key not in ("metadata", "base_value") + ] + assert len(instance_keys) == 3 + + for key in instance_keys: + instance = explanation[key] + assert len(instance["shap_values"]) == len(REGRESSION_INPUT_COLUMNS) + # SHAP values are additive: base + contributions ~= prediction. + reconstructed = base_value + sum(instance["shap_values"]) + assert reconstructed == pytest.approx(instance["model_prediction"], abs=0.05) + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 2 * len(instance_keys) + assert [a.type for a in artifacts[:2]] == ["plotly", "text"] + assert "baseline" in artifacts[1].payload + + +def test_regression_partial_dependence(trained_regressor, regression_dataset): + explainer = RegressionPartialDependence( + trained_regressor, + grid_resolution=10, + lower_percentile=0.05, + upper_percentile=0.95, + ) + explanation = explainer.explain(copy.deepcopy(regression_dataset)) + + assert explanation["metadata"]["output_column"] == "PetalWidthCm" + for feature in REGRESSION_INPUT_COLUMNS: + assert len(explanation[feature]["grid_values"]) == 10 + assert len(explanation[feature]["average"]) == 10 + grid = explanation[feature]["grid_values"] + assert grid == sorted(grid) + + # PetalLengthCm drives PetalWidthCm: its PDP curve must not be flat. + petal_curve = explanation["PetalLengthCm"]["average"] + assert max(petal_curve) - min(petal_curve) > 0.1 + + artifacts = explainer.plot(explanation) + assert len(artifacts) == len(REGRESSION_INPUT_COLUMNS) + assert all(a.type == "plotly" for a in artifacts) + assert artifacts[0].title in REGRESSION_INPUT_COLUMNS + + +def test_regression_pdp_invalid_percentiles(trained_regressor): + with pytest.raises(AssertionError): + RegressionPartialDependence( + trained_regressor, lower_percentile=0.9, upper_percentile=0.1 + ) + + +class DummyTextModel: + """Predicts positive when the text contains the word 'good'. + + Mimics the transformer models' strictness: predict raises if the dataset + has more than one text column (see ``tokenize_data`` in + ``base_text_classification_transformer``). + """ + + def predict(self, dataset): + frame = dataset.to_pandas() + text_columns = [col for col in frame.columns if col != "label"] + if len(text_columns) != 1: + raise ValueError(f"Expected exactly one text column, found: {text_columns}") + texts = frame[text_columns[0]].tolist() + return np.array( + [[0.1, 0.9] if "good" in str(t).split() else [0.9, 0.1] for t in texts] + ) + + +class _FakeTargetSplit: + """Minimal stand-in for a DashAIDataset target split.""" + + column_names = ["label"] + types = {"label": Categorical(values=["negative", "positive"])} + + +def test_token_ablation_fit_reads_target_names(): + explainer = TokenAblation(DummyTextModel()) + explainer.fit((None, {"train": _FakeTargetSplit()})) + assert explainer.metadata["target_names"] == ["negative", "positive"] + + +def test_token_ablation_explains_influential_tokens(): + explainer = TokenAblation(DummyTextModel(), max_tokens=20, replacement="remove") + explainer.metadata = {"target_names": ["negative", "positive"]} + + instances = pd.DataFrame( + {"text": ["this movie was good indeed", "terrible boring plot"]} + ) + explanation = explainer.explain_instance(instances) + + first = explanation[0] + assert first["predicted_class"] == 1 + tokens = first["tokens"] + importances = first["token_importances"] + assert len(tokens) == len(importances) + + # Removing 'good' flips the dummy model: it must be the top token. + good_importance = importances[tokens.index("good")] + assert good_importance == pytest.approx(0.8, abs=1e-6) + assert all(importance <= good_importance for importance in importances) + + second = explanation[1] + assert second["predicted_class"] == 0 + # No single token changes the dummy model's negative prediction. + assert all( + importance == pytest.approx(0.0, abs=1e-6) + for importance in second["token_importances"] + ) + + artifacts = explainer.plot(explanation) + assert len(artifacts) == 4 + assert [a.type for a in artifacts[:2]] == ["plotly", "text"] + assert "good" in artifacts[1].payload + + +def test_token_ablation_ignores_tokenizer_columns(): + # The explainer job hands over datasets already prepared by the model; + # transformer models add input_ids/attention_mask columns. The explainer + # must rebuild a clean single-text-column dataset before predicting. + explainer = TokenAblation(DummyTextModel(), max_tokens=10) + explainer.metadata = {"target_names": ["negative", "positive"]} + + instances = pd.DataFrame( + { + "text": ["good stuff", "bad stuff"], + "input_ids": [[101, 102], [101, 103]], + "attention_mask": [[1, 1], [1, 1]], + } + ) + explanation = explainer.explain_instance(instances) + + assert explanation["metadata"]["text_column"] == "text" + assert explanation[0]["predicted_class"] == 1 + assert explanation[1]["predicted_class"] == 0 + + +def test_token_ablation_unk_replacement(): + explainer = TokenAblation(DummyTextModel(), max_tokens=10, replacement="unk") + explainer.metadata = {"target_names": ["negative", "positive"]} + + instances = pd.DataFrame({"text": ["good"]}) + explanation = explainer.explain_instance(instances) + + # Single token replaced by [UNK]: prediction flips, importance 0.8. + assert explanation[0]["token_importances"] == [pytest.approx(0.8, abs=1e-6)] diff --git a/tests/back/registries/test_registry.py b/tests/back/registries/test_registry.py index 51e7bae3e..d66214eb7 100644 --- a/tests/back/registries/test_registry.py +++ b/tests/back/registries/test_registry.py @@ -497,6 +497,22 @@ def test_relationships_module(): ] +def test_get_related_components_skips_unregistered_names(): + test_registry = ComponentRegistry(initial_components=[Component1]) + + # RelatedComponent2 declares Component1 (registered) and Component2 + # (NOT registered): lookups must skip the unregistered name. + test_registry.register_component(RelatedComponent2) + + assert test_registry.get_related_components("RelatedComponent2") == [ + COMPONENT1_DICT + ] + assert [ + component["name"] + for component in test_registry.get_related_components("Component1") + ] == ["RelatedComponent2"] + + def test_compatible_components_merge_across_bases(): test_registry = ComponentRegistry( initial_components=[ From eda5755f9ca31f88201f2883ea2ed3bae48aea27 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Mon, 13 Jul 2026 15:55:49 -0400 Subject: [PATCH 144/308] feat: declare image explainers on torch image models --- .../explainers/image_explainer_utils.py | 43 +++++++++++++------ .../base_torchvision_image_classifier.py | 2 +- DashAI/back/models/cnn_image_classifier.py | 2 +- DashAI/back/models/lenet5_image_classifier.py | 2 +- DashAI/back/models/mlp_image_classifier.py | 2 +- .../back/explainers/test_image_explainers.py | 11 ----- 6 files changed, 34 insertions(+), 28 deletions(-) diff --git a/DashAI/back/explainability/explainers/image_explainer_utils.py b/DashAI/back/explainability/explainers/image_explainer_utils.py index 31e645d7b..3f48466b4 100644 --- a/DashAI/back/explainability/explainers/image_explainer_utils.py +++ b/DashAI/back/explainability/explainers/image_explainer_utils.py @@ -1,13 +1,15 @@ """Shared helpers for image-classification explainers. These helpers define the (minimal) white-box capability contract image -explainers rely on: +explainers rely on; models expose no explainability-specific methods, +only their existing public state: - ``model.model`` is the underlying ``torch.nn.Module``. -- ``model.get_inference_transform()`` returns the exact transform the model - applies to input images (all DashAI image classifiers expose it). - ``model.image_size`` (int) is the model's input resolution. - ``model.idx_to_label`` maps class indices to label names. + +The inference transform is reconstructed on the explainer side by +:func:`get_transform` from that public state. """ from typing import Any, List @@ -45,7 +47,13 @@ def get_torch_module(model: Any): def get_transform(model: Any): - """Return the model's inference transform, with a plain fallback. + """Build the model's inference transform from its public state. + + Preprocessing knowledge lives on the explainer side so models carry no + explainability responsibilities. The transform replicates what each + model family applies internally when predicting: torchvision-backbone + classifiers add the ImageNet normalization; every other image model + gets a plain resize plus tensor conversion based on ``image_size``. Parameters ---------- @@ -57,20 +65,29 @@ def get_transform(model: Any): Callable A transform mapping a PIL image to a normalized tensor. """ - if hasattr(model, "get_inference_transform"): - return model.get_inference_transform() - from torchvision import transforms image_size = int(getattr(model, "image_size", 224)) - return transforms.Compose( - [ - transforms.Lambda(lambda img: img.convert("RGB")), - transforms.Resize((image_size, image_size)), - transforms.ToTensor(), - ] + steps = [ + transforms.Lambda(lambda img: img.convert("RGB")), + transforms.Resize((image_size, image_size)), + transforms.ToTensor(), + ] + + from DashAI.back.models.base_torchvision_image_classifier import ( + TorchvisionImageClassifier, ) + if isinstance(model, TorchvisionImageClassifier): + steps.append( + transforms.Normalize( + mean=[0.485, 0.456, 0.406], + std=[0.229, 0.224, 0.225], + ) + ) + + return transforms.Compose(steps) + def get_target_names(model: Any, y_dataset) -> List[str]: """Resolve class names in the model's class-index order. diff --git a/DashAI/back/models/base_torchvision_image_classifier.py b/DashAI/back/models/base_torchvision_image_classifier.py index be18391d7..e4fbf56be 100644 --- a/DashAI/back/models/base_torchvision_image_classifier.py +++ b/DashAI/back/models/base_torchvision_image_classifier.py @@ -347,7 +347,7 @@ class TorchvisionImageClassifier(BaseModel, abc.ABC): """ SCHEMA = TorchvisionImageClassifierSchema - COMPATIBLE_COMPONENTS = ["ImageClassificationTask"] + COMPATIBLE_COMPONENTS = ["ImageClassificationTask", "GradCam", "OcclusionSaliency"] @abc.abstractmethod def _build_backbone(self, num_classes: int, pretrained: bool): diff --git a/DashAI/back/models/cnn_image_classifier.py b/DashAI/back/models/cnn_image_classifier.py index 606a5ec71..a13db88d8 100644 --- a/DashAI/back/models/cnn_image_classifier.py +++ b/DashAI/back/models/cnn_image_classifier.py @@ -411,7 +411,7 @@ class CNNImageClassifier(BaseModel): """ SCHEMA = CNNImageClassifierSchema - COMPATIBLE_COMPONENTS = ["ImageClassificationTask"] + COMPATIBLE_COMPONENTS = ["ImageClassificationTask", "GradCam", "OcclusionSaliency"] DISPLAY_NAME: str = MultilingualString( en="CNN Image Classifier", es="Clasificador de Imágenes CNN", diff --git a/DashAI/back/models/lenet5_image_classifier.py b/DashAI/back/models/lenet5_image_classifier.py index abd50387a..2b8fba34a 100644 --- a/DashAI/back/models/lenet5_image_classifier.py +++ b/DashAI/back/models/lenet5_image_classifier.py @@ -327,7 +327,7 @@ class LeNet5ImageClassifier(BaseModel): """ SCHEMA = LeNet5ImageClassifierSchema - COMPATIBLE_COMPONENTS = ["ImageClassificationTask"] + COMPATIBLE_COMPONENTS = ["ImageClassificationTask", "GradCam", "OcclusionSaliency"] DISPLAY_NAME: str = MultilingualString( en="LeNet-5", es="LeNet-5", diff --git a/DashAI/back/models/mlp_image_classifier.py b/DashAI/back/models/mlp_image_classifier.py index 12293361d..4b5418eaa 100644 --- a/DashAI/back/models/mlp_image_classifier.py +++ b/DashAI/back/models/mlp_image_classifier.py @@ -358,7 +358,7 @@ class MLPImageClassifier(BaseModel): """ SCHEMA = MLPImageClassifierSchema - COMPATIBLE_COMPONENTS = ["ImageClassificationTask"] + COMPATIBLE_COMPONENTS = ["ImageClassificationTask", "OcclusionSaliency"] DISPLAY_NAME: str = MultilingualString( en="MLP Image Classifier", es="Clasificador de Imágenes MLP", diff --git a/tests/back/explainers/test_image_explainers.py b/tests/back/explainers/test_image_explainers.py index bed104c2e..d41080c13 100644 --- a/tests/back/explainers/test_image_explainers.py +++ b/tests/back/explainers/test_image_explainers.py @@ -53,17 +53,6 @@ def __init__(self): nn.Linear(4 * 4 * 4, 2), ) - def get_inference_transform(self): - from torchvision import transforms - - return transforms.Compose( - [ - transforms.Lambda(lambda img: img.convert("RGB")), - transforms.Resize((self.image_size, self.image_size)), - transforms.ToTensor(), - ] - ) - class _MlpImageModel(_ConvImageModel): """Image model with no convolutional layers (like MLPImageClassifier).""" From d06f9509476082f878ee54c9202d7d3559a21a9a Mon Sep 17 00:00:00 2001 From: Irozuku Date: Mon, 13 Jul 2026 16:28:44 -0400 Subject: [PATCH 145/308] fix: correct formatting in docstrings and comments --- .../explainability/explainers/image_explainer_utils.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DashAI/back/explainability/explainers/image_explainer_utils.py b/DashAI/back/explainability/explainers/image_explainer_utils.py index 3f48466b4..bf8581d07 100644 --- a/DashAI/back/explainability/explainers/image_explainer_utils.py +++ b/DashAI/back/explainability/explainers/image_explainer_utils.py @@ -1,7 +1,7 @@ """Shared helpers for image-classification explainers. -These helpers define the (minimal) white-box capability contract image -explainers rely on; models expose no explainability-specific methods, +These helpers define the (minimal) white box capability contract image +explainers rely on; models expose no explainability specific methods, only their existing public state: - ``model.model`` is the underlying ``torch.nn.Module``. @@ -147,9 +147,9 @@ def heatmap_overlay_artifact( Parameters ---------- - image_uint8 : array-like + image_uint8 : array like RGB image of shape (H, W, 3), uint8 values. - heatmap : array-like + heatmap : array like Saliency map of shape (H, W) with values in [0, 1]. title : str Artifact title (shown in the instance selector). @@ -167,7 +167,7 @@ def heatmap_overlay_artifact( image = np.asarray(image_uint8, dtype=np.float32) / 255.0 cam = np.clip(np.asarray(heatmap, dtype=np.float32), 0.0, 1.0) - # Jet-like colormap, avoids a matplotlib/cv2 dependency at plot time. + # Jet like colormap, avoids a matplotlib/cv2 dependency at plot time. red = np.clip(1.5 - np.abs(4 * cam - 3), 0, 1) green = np.clip(1.5 - np.abs(4 * cam - 2), 0, 1) blue = np.clip(1.5 - np.abs(4 * cam - 1), 0, 1) From c2b5888267471663a1346c0ab30a67d165635245 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Mon, 13 Jul 2026 16:59:04 -0400 Subject: [PATCH 146/308] fix: group explanation artifacts per instance in selector --- .../components/explainers/ExplainersPlot.jsx | 69 ++++++++++++++----- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 9a87e6048..0d1958bd7 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -6,6 +6,7 @@ import { Select, CircularProgress, Box, + Typography, } from "@mui/material"; import PropTypes from "prop-types"; import { useSnackbar } from "notistack"; @@ -27,10 +28,34 @@ function parseExplanationArtifacts(items) { ); } +/** + * Group consecutive artifacts sharing the same non null title. Explainers + * emit several artifacts per explained instance (e.g. a plot followed by a + * text summary) under one title; each group becomes a single entry in the + * instance selector and its artifacts render stacked together. Untitled + * artifacts stay in their own group. + */ +function groupArtifacts(artifacts) { + const groups = []; + artifacts.forEach((artifact) => { + const lastGroup = groups[groups.length - 1]; + if ( + artifact.title != null && + lastGroup && + lastGroup.title === artifact.title + ) { + lastGroup.artifacts.push(artifact); + } else { + groups.push({ title: artifact.title ?? null, artifacts: [artifact] }); + } + }); + return groups; +} + export default function ExplainersPlot({ explainer, scope }) { const { enqueueSnackbar } = useSnackbar(); - const [artifacts, setArtifacts] = useState([]); - const [currentArtifact, setCurrentArtifact] = useState(0); + const [groups, setGroups] = useState([]); + const [currentGroup, setCurrentGroup] = useState(0); const [loading, setLoading] = useState(true); const { t } = useTranslation(["explainers"]); @@ -39,19 +64,19 @@ export default function ExplainersPlot({ explainer, scope }) { try { const response = await getExplainerPlotRequest(explainer.id, scope); if (!response || response.length === 0) { - setArtifacts([]); - setCurrentArtifact(0); + setGroups([]); + setCurrentGroup(0); enqueueSnackbar(t("explainers:error.noData"), { variant: "warning", }); } else { - setArtifacts(parseExplanationArtifacts(response)); - // Reset currentArtifact when data updates to avoid stale index - setCurrentArtifact(0); + setGroups(groupArtifacts(parseExplanationArtifacts(response))); + // Reset currentGroup when data updates to avoid stale index + setCurrentGroup(0); } } catch (error) { - setArtifacts([]); - setCurrentArtifact(0); + setGroups([]); + setCurrentGroup(0); enqueueSnackbar(t("explainers:error.fetchExplainers"), { variant: "error", }); @@ -88,21 +113,21 @@ export default function ExplainersPlot({ explainer, scope }) { p: 1, }} > - {!loading && artifacts.length > 1 && ( + {!loading && groups.length > 1 && ( {t("explainers:label.selectInstance")} setCurrentGroup(event.target.value)} - label="class" + label={t("explainers:label.selectInstance")} autoWidth > - {groups.map((group, i) => ( + {groups.map((g, i) => ( - {group.title ?? + {g.title ?? t("explainers:label.instanceNumber", { number: i + 1 })} ))} )} - {!loading && explainer.status === 3 ? ( - groups.length > 0 && groups[currentGroup] ? ( - - {groups[currentGroup].title && ( - - {groups[currentGroup].title} - - )} - {groups[currentGroup].artifacts.map((artifact, i) => ( - - ))} - - ) : ( - {t("explainers:error.noData")} - ) - ) : explainer.status === 4 ? ( - {t("explainers:error.explainerFailed")} - ) : ( - - + + {inputArtifacts.length > 0 && ( + + + {t("explainers:label.modelInput")} + + {inputArtifacts.map((artifact) => ( + + ))} )} + + {explanationArtifacts.map((artifact) => ( + onSaveOverride(artifact.index, figure) + : null + } + onResetEdit={ + onResetOverride ? () => onResetOverride(artifact.index) : null + } + /> + ))} ); } ExplainersPlot.propTypes = { explainer: PropTypes.shape({ - explainer_name: PropTypes.string, id: PropTypes.number, - parameters: PropTypes.objectOf( - PropTypes.oneOfType([ - PropTypes.number, - PropTypes.string, - PropTypes.arrayOf(PropTypes.string), - ]), - ), status: PropTypes.number, - runId: PropTypes.number, - explanationPath: PropTypes.string, - plot_path: PropTypes.string, - name: PropTypes.string, - created: PropTypes.string, }).isRequired, scope: PropTypes.string.isRequired, + onSaveOverride: PropTypes.func, + onResetOverride: PropTypes.func, + overriddenIndexes: PropTypes.arrayOf(PropTypes.number), }; diff --git a/DashAI/front/src/types/artifact.ts b/DashAI/front/src/types/artifact.ts index aa1a1e86b..25c39f187 100644 --- a/DashAI/front/src/types/artifact.ts +++ b/DashAI/front/src/types/artifact.ts @@ -10,4 +10,5 @@ export interface IArtifact { type: string; payload: unknown; title: string | null; + role?: "input" | "explanation"; } From 1fc59a077ce6ac077525d44386caf5c4ead4746f Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 16:33:09 -0400 Subject: [PATCH 162/308] feat: add i18n keys for artifact viewer actions --- .../front/src/utils/i18n/locales/de/explainers.json | 11 +++++++++-- .../front/src/utils/i18n/locales/en/explainers.json | 11 +++++++++-- .../front/src/utils/i18n/locales/es/explainers.json | 11 +++++++++-- .../front/src/utils/i18n/locales/pt/explainers.json | 11 +++++++++-- .../front/src/utils/i18n/locales/zh/explainers.json | 11 +++++++++-- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/DashAI/front/src/utils/i18n/locales/de/explainers.json b/DashAI/front/src/utils/i18n/locales/de/explainers.json index 36b1857e5..feb07a994 100644 --- a/DashAI/front/src/utils/i18n/locales/de/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/de/explainers.json @@ -3,7 +3,13 @@ "addExplainer": "Erklärungsmodell hinzufügen", "backToExplainers": "Zurück zu Erklärungsmodellen", "hidePlot": "Plot ausblenden", - "showPlot": "Plot anzeigen" + "showPlot": "Plot anzeigen", + "download": "Herunterladen", + "downloadPng": "Als PNG herunterladen", + "downloadSvg": "Als SVG herunterladen", + "editPlot": "Diagramm bearbeiten", + "resetPlot": "Bearbeitungen zuruecksetzen", + "fullscreen": "Vollbild" }, "error": { "explainerFailed": "Erklärungsmodell konnte nicht generiert werden.", @@ -57,7 +63,8 @@ "selectInstance": "Instanz auswählen", "explainerInProgress": "Erklärungsmodell wird verarbeitet...", "splitSelectionSummary": "Prozentsatz: {{percentage}}% | Ausgewählte Zeilen: {{rowsSelected}} / {{totalRows}}", - "searchExplainers": "Erklärungsmodelle suchen..." + "searchExplainers": "Erklärungsmodelle suchen...", + "modelInput": "Modelleingabe" }, "message": { "explainerJobCompleted": "Erklärungsmodell {{name}} erfolgreich abgeschlossen", diff --git a/DashAI/front/src/utils/i18n/locales/en/explainers.json b/DashAI/front/src/utils/i18n/locales/en/explainers.json index cfc67f07d..0f802e35b 100644 --- a/DashAI/front/src/utils/i18n/locales/en/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/en/explainers.json @@ -3,7 +3,13 @@ "addExplainer": "Add Explainer", "backToExplainers": "Back to explainers", "hidePlot": "Hide Plot", - "showPlot": "Show Plot" + "showPlot": "Show Plot", + "download": "Download", + "downloadPng": "Download as PNG", + "downloadSvg": "Download as SVG", + "editPlot": "Edit plot", + "resetPlot": "Reset edits", + "fullscreen": "Fullscreen" }, "error": { "explainerFailed": "Explainer failed to generate.", @@ -57,7 +63,8 @@ "selectInstance": "Select an instance", "explainerInProgress": "Explainer in progress...", "splitSelectionSummary": "Percentage: {{percentage}}% | Rows selected: {{rowsSelected}} / {{totalRows}}", - "searchExplainers": "Search explainers..." + "searchExplainers": "Search explainers...", + "modelInput": "Model input" }, "message": { "explainerJobCompleted": "Explainer {{name}} completed successfully", diff --git a/DashAI/front/src/utils/i18n/locales/es/explainers.json b/DashAI/front/src/utils/i18n/locales/es/explainers.json index f093d5371..19a186c21 100644 --- a/DashAI/front/src/utils/i18n/locales/es/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/es/explainers.json @@ -3,7 +3,13 @@ "addExplainer": "Agregar Explicador", "backToExplainers": "Volver a explicadores", "hidePlot": "Ocultar Gráfico", - "showPlot": "Mostrar Gráfico" + "showPlot": "Mostrar Gráfico", + "download": "Descargar", + "downloadPng": "Descargar como PNG", + "downloadSvg": "Descargar como SVG", + "editPlot": "Editar gráfico", + "resetPlot": "Restablecer ediciones", + "fullscreen": "Pantalla completa" }, "error": { "explainerFailed": "Error al generar el explicador.", @@ -57,7 +63,8 @@ "selectInstance": "Selecciona una instancia", "explainerInProgress": "Explicador en progreso...", "splitSelectionSummary": "Porcentaje: {{percentage}}% | Filas seleccionadas: {{rowsSelected}} / {{totalRows}}", - "searchExplainers": "Buscar explicadores..." + "searchExplainers": "Buscar explicadores...", + "modelInput": "Entrada del modelo" }, "message": { "explainerJobCompleted": "Explicador {{name}} completado exitosamente", diff --git a/DashAI/front/src/utils/i18n/locales/pt/explainers.json b/DashAI/front/src/utils/i18n/locales/pt/explainers.json index d647b4602..36f6e788b 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/pt/explainers.json @@ -3,7 +3,13 @@ "addExplainer": "Adicionar Explicador", "backToExplainers": "Voltar para explicadores", "hidePlot": "Ocultar Gráfico", - "showPlot": "Mostrar Gráfico" + "showPlot": "Mostrar Gráfico", + "download": "Baixar", + "downloadPng": "Baixar como PNG", + "downloadSvg": "Baixar como SVG", + "editPlot": "Editar gráfico", + "resetPlot": "Redefinir edições", + "fullscreen": "Tela cheia" }, "error": { "explainerFailed": "Erro ao gerar o explicador.", @@ -57,7 +63,8 @@ "selectInstance": "Selecione uma instância", "explainerInProgress": "Explicador em andamento...", "splitSelectionSummary": "Percentual: {{percentage}}% | Linhas selecionadas: {{rowsSelected}} / {{totalRows}}", - "searchExplainers": "Pesquisar explicadores..." + "searchExplainers": "Pesquisar explicadores...", + "modelInput": "Entrada do modelo" }, "message": { "explainerJobCompleted": "Explicador {{name}} concluído com sucesso", diff --git a/DashAI/front/src/utils/i18n/locales/zh/explainers.json b/DashAI/front/src/utils/i18n/locales/zh/explainers.json index 7ddd5dd0d..f6dd1030b 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/explainers.json +++ b/DashAI/front/src/utils/i18n/locales/zh/explainers.json @@ -3,7 +3,13 @@ "addExplainer": "添加解释器", "backToExplainers": "返回解释器列表", "hidePlot": "隐藏图表", - "showPlot": "显示图表" + "showPlot": "显示图表", + "download": "下载", + "downloadPng": "下载为 PNG", + "downloadSvg": "下载为 SVG", + "editPlot": "编辑图表", + "resetPlot": "重置编辑", + "fullscreen": "全屏" }, "error": { "explainerFailed": "解释器生成失败。", @@ -57,7 +63,8 @@ "selectInstance": "选择一个实例", "explainerInProgress": "解释器运行中...", "splitSelectionSummary": "百分比:{{percentage}}% | 已选行数:{{rowsSelected}} / {{totalRows}}", - "searchExplainers": "搜索解释器..." + "searchExplainers": "搜索解释器...", + "modelInput": "模型输入" }, "message": { "explainerJobCompleted": "解释器 {{name}} 成功完成", From 0e44ff52e46e36fbb038c6fbfe8534c440d293a8 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 16:37:21 -0400 Subject: [PATCH 163/308] feat: make explainer card an always-open instance viewer --- .../components/explainers/ExplainersCard.jsx | 34 ++++--------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersCard.jsx b/DashAI/front/src/components/explainers/ExplainersCard.jsx index 8d02386d3..50c796f7c 100644 --- a/DashAI/front/src/components/explainers/ExplainersCard.jsx +++ b/DashAI/front/src/components/explainers/ExplainersCard.jsx @@ -4,16 +4,12 @@ import { Typography, IconButton, Paper, - Button, - Collapse, Box, CircularProgress, } from "@mui/material"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; import DeleteIcon from "@mui/icons-material/Delete"; import ZoomInIcon from "@mui/icons-material/ZoomIn"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import PropTypes from "prop-types"; import ExplainersPlot from "./ExplainersPlot"; import { useNavigate } from "react-router-dom"; @@ -35,15 +31,6 @@ export default function ExplainersCard({ compact = false, }) { const [open, setOpen] = useState(false); - const expandedStorageKey = `explainer-${scope}-${explainer.id}-expanded`; - const [expanded, setExpanded] = useState(() => { - const saved = localStorage.getItem(expandedStorageKey); - return saved !== null ? JSON.parse(saved) : true; - }); - - useEffect(() => { - localStorage.setItem(expandedStorageKey, JSON.stringify(expanded)); - }, [expanded, expandedStorageKey]); const [componentData, setComponentData] = useState(null); const { t } = useTranslation(["explainers"]); const isRunning = RUNNING_STATUSES.includes(explainer.status); @@ -140,22 +127,13 @@ export default function ExplainersCard({ ) : ( - - - - - - - + {/* Reserved slot for the future "generate story" action button. + Kept hidden until that feature lands. */} + + )} From b85eea5a179dd309d0c4153b7a86bbaee400cbcf Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 16:47:55 -0400 Subject: [PATCH 164/308] feat: redesign explainer artifact card into clean bordered blocks --- .../components/explainers/ExplainersCard.jsx | 14 ++- .../components/explainers/ExplainersPlot.jsx | 30 ++++--- .../src/components/shared/ArtifactViewer.jsx | 85 ++++++++++++++----- 3 files changed, 94 insertions(+), 35 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersCard.jsx b/DashAI/front/src/components/explainers/ExplainersCard.jsx index 50c796f7c..aedf95fca 100644 --- a/DashAI/front/src/components/explainers/ExplainersCard.jsx +++ b/DashAI/front/src/components/explainers/ExplainersCard.jsx @@ -7,6 +7,7 @@ import { Box, CircularProgress, } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; import DeleteIcon from "@mui/icons-material/Delete"; import ZoomInIcon from "@mui/icons-material/ZoomIn"; @@ -30,6 +31,7 @@ export default function ExplainersCard({ onDelete, compact = false, }) { + const theme = useTheme(); const [open, setOpen] = useState(false); const [componentData, setComponentData] = useState(null); const { t } = useTranslation(["explainers"]); @@ -72,8 +74,16 @@ export default function ExplainersCard({ if (compact) { return ( <> - - + + a.role !== "input", ); + const hasSelector = groups.length > 1; return ( - {groups.length > 1 && ( - + {hasSelector ? ( + {t("explainers:label.selectInstance")} @@ -124,7 +125,6 @@ export default function ExplainersPlot({ value={currentGroup} onChange={(event) => setCurrentGroup(event.target.value)} label={t("explainers:label.selectInstance")} - autoWidth > {groups.map((g, i) => ( @@ -134,19 +134,21 @@ export default function ExplainersPlot({ ))} + ) : ( + group.title && ( + + {group.title} + + ) )} {inputArtifacts.length > 0 && ( - - + + {t("explainers:label.modelInput")} {inputArtifacts.map((artifact) => ( diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx index ff71ace60..f76e3faca 100644 --- a/DashAI/front/src/components/shared/ArtifactViewer.jsx +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -1,4 +1,3 @@ -// DashAI/front/src/components/shared/ArtifactViewer.jsx import React, { useMemo, useRef, useState } from "react"; import PropTypes from "prop-types"; import { @@ -15,7 +14,7 @@ import FullscreenIcon from "@mui/icons-material/Fullscreen"; import SaveIcon from "@mui/icons-material/Save"; import RestartAltIcon from "@mui/icons-material/RestartAlt"; import CloseIcon from "@mui/icons-material/Close"; -import { useTheme } from "@mui/material/styles"; +import { useTheme, alpha } from "@mui/material/styles"; import Plot from "react-plotly.js"; import { useTranslation } from "react-i18next"; @@ -24,9 +23,12 @@ import { applyThemeToLayout } from "../../utils/plotlyTheme"; import { downloadArtifact } from "../../utils/downloadArtifact"; /** - * Renders a single typed artifact with a toolbar: type-aware download, plot - * editing (plotly only), and fullscreen. When onSaveEdit is provided, edited - * plotly figures can be persisted; otherwise edits are client-side only. + * Renders one typed artifact as a self-contained bordered block. The actions + * that apply to that artifact (download, plot editing, fullscreen) live in a + * compact cluster docked to the block's top-right corner, revealed on hover + * or keyboard focus so the resting card stays uncluttered. Plot editing is + * offered only for plotly artifacts; edits persist when onSaveEdit is given, + * otherwise they are client-side only. */ export default function ArtifactViewer({ artifact, @@ -39,6 +41,7 @@ export default function ArtifactViewer({ const [downloadAnchor, setDownloadAnchor] = useState(null); const [editing, setEditing] = useState(false); const [fullscreen, setFullscreen] = useState(false); + const [editFigure, setEditFigure] = useState(null); const plotWrapRef = useRef(null); const isPlotly = artifact.type === "plotly"; @@ -54,9 +57,8 @@ export default function ArtifactViewer({ } }, [artifact, isPlotly]); - const [editFigure, setEditFigure] = useState(null); - const startEdit = () => { + if (!figure?.data) return; setEditFigure({ data: JSON.parse(JSON.stringify(figure.data)), layout: applyThemeToLayout(figure.layout, theme), @@ -65,8 +67,12 @@ export default function ArtifactViewer({ }; const saveEdit = async () => { - if (onSaveEdit && editFigure) await onSaveEdit(editFigure); - setEditing(false); + try { + if (onSaveEdit && editFigure) await onSaveEdit(editFigure); + setEditing(false); + } catch (error) { + console.error("Failed to save plot edits", error); + } }; const findPlotEl = () => @@ -74,20 +80,55 @@ export default function ArtifactViewer({ ? plotWrapRef.current.querySelector(".js-plotly-plot") : null; + const actionButtonSx = { + color: "text.secondary", + "&:hover": { color: "text.primary" }, + }; + return ( - - {/* Toolbar */} + + {/* Action cluster, docked to the block corner and attached to this + artifact's content. */} isPlotly ? setDownloadAnchor(e.currentTarget) @@ -99,20 +140,24 @@ export default function ArtifactViewer({ {isPlotly && figure && ( - + )} {canReset && onResetEdit && ( - + )} - setFullscreen(true)}> + setFullscreen(true)} + > @@ -141,7 +186,9 @@ export default function ArtifactViewer({
- + {/* The instance label is shown once by the parent; suppress the + per-artifact title so it is not repeated on every block. */} + {/* Edit dialog: editable plotly figure */} - + From 70a15a700db2e6cb28bf1a62d306f15858c98c52 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 16:50:21 -0400 Subject: [PATCH 165/308] feat: offer model-specific explainers in the explainers sidebar --- .../explainers/ExplainersSidebar.jsx | 47 ++++++++++++------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersSidebar.jsx b/DashAI/front/src/components/explainers/ExplainersSidebar.jsx index e2f1c5572..3a377eb06 100644 --- a/DashAI/front/src/components/explainers/ExplainersSidebar.jsx +++ b/DashAI/front/src/components/explainers/ExplainersSidebar.jsx @@ -32,27 +32,42 @@ export default function ExplainersSidebar({ run, session, onCreated }) { const [creator, setCreator] = useState(null); const taskName = session?.task_name; + const modelName = run?.model_name; const fetchExplainers = useCallback(async () => { if (!taskName) return; try { setLoading(true); + + const fetchScope = async (explainerType) => { + const [taskRelated, modelRelated] = await Promise.all([ + getComponents({ + selectTypes: [explainerType], + relatedComponent: taskName, + }), + modelName + ? getComponents({ + selectTypes: [explainerType], + relatedComponent: modelName, + }) + : Promise.resolve([]), + ]); + const seen = new Set(); + return [...taskRelated, ...modelRelated] + .filter((obj) => { + if (seen.has(obj.name)) return false; + seen.add(obj.name); + return true; + }) + .filter((obj) => !obj.name.startsWith("Fit")); + }; + const [globalResponse, localResponse] = await Promise.all([ - getComponents({ - selectTypes: ["GlobalExplainer"], - relatedComponent: taskName, - }), - getComponents({ - selectTypes: ["LocalExplainer"], - relatedComponent: taskName, - }), + fetchScope("GlobalExplainer"), + fetchScope("LocalExplainer"), ]); - setGlobalExplainers( - globalResponse.filter((obj) => !obj.name.startsWith("Fit")), - ); - setLocalExplainers( - localResponse.filter((obj) => !obj.name.startsWith("Fit")), - ); + setGlobalExplainers(globalResponse); + setLocalExplainers(localResponse); } catch (error) { console.error("Error fetching explainers:", error); enqueueSnackbar(t("explainers:error.fetchExplainers"), { @@ -61,7 +76,7 @@ export default function ExplainersSidebar({ run, session, onCreated }) { } finally { setLoading(false); } - }, [taskName, enqueueSnackbar, t]); + }, [taskName, modelName, enqueueSnackbar, t]); useEffect(() => { fetchExplainers(); @@ -180,7 +195,7 @@ export default function ExplainersSidebar({ run, session, onCreated }) { setCreator(null)} From 4d050282bee8a36466e0c493d1e490fcbd9d335c Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 16:55:09 -0400 Subject: [PATCH 166/308] fix: stop plot edit modal freezing from onUpdate re-render loop --- .../src/components/shared/ArtifactViewer.jsx | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx index f76e3faca..e7b9f1b14 100644 --- a/DashAI/front/src/components/shared/ArtifactViewer.jsx +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -41,7 +41,13 @@ export default function ArtifactViewer({ const [downloadAnchor, setDownloadAnchor] = useState(null); const [editing, setEditing] = useState(false); const [fullscreen, setFullscreen] = useState(false); - const [editFigure, setEditFigure] = useState(null); + // editInitial is the figure the editable Plot mounts with (set once per edit + // session). editFigureRef holds the latest edited figure, captured in + // onUpdate WITHOUT setState so Plotly's own edit events do not trigger a + // React re-render that would re-run Plotly.react and loop the page into a + // freeze. + const [editInitial, setEditInitial] = useState(null); + const editFigureRef = useRef(null); const plotWrapRef = useRef(null); const isPlotly = artifact.type === "plotly"; @@ -59,17 +65,27 @@ export default function ArtifactViewer({ const startEdit = () => { if (!figure?.data) return; - setEditFigure({ + const initial = { data: JSON.parse(JSON.stringify(figure.data)), layout: applyThemeToLayout(figure.layout, theme), - }); + }; + setEditInitial(initial); + editFigureRef.current = initial; setEditing(true); }; + const closeEdit = () => { + setEditing(false); + setEditInitial(null); + editFigureRef.current = null; + }; + const saveEdit = async () => { try { - if (onSaveEdit && editFigure) await onSaveEdit(editFigure); - setEditing(false); + if (onSaveEdit && editFigureRef.current) { + await onSaveEdit(editFigureRef.current); + } + closeEdit(); } catch (error) { console.error("Failed to save plot edits", error); } @@ -190,13 +206,11 @@ export default function ArtifactViewer({ per-artifact title so it is not repeated on every block. */} - {/* Edit dialog: editable plotly figure */} - setEditing(false)} - fullWidth - maxWidth="lg" - > + {/* Edit dialog: editable plotly figure. The Plot mounts once with + editInitial and reports edits through onUpdate into a ref; we never + feed those edits back as props, so Plotly does not re-render in a + loop. */} + {onSaveEdit && ( @@ -205,18 +219,18 @@ export default function ArtifactViewer({ )} - setEditing(false)}> + - {editFigure && ( + {editInitial && ( - setEditFigure({ data: fig.data, layout: fig.layout }) - } + onUpdate={(fig) => { + editFigureRef.current = { data: fig.data, layout: fig.layout }; + }} useResizeHandler style={{ width: "100%" }} /> From 5ce288a39963577df2144c3536c025ef4e1501b5 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 16:58:43 -0400 Subject: [PATCH 167/308] feat: beautify artifact fullscreen view with themed header and large content --- .../components/shared/ArtifactRenderer.jsx | 11 +++-- .../src/components/shared/ArtifactViewer.jsx | 49 +++++++++++++++++-- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/DashAI/front/src/components/shared/ArtifactRenderer.jsx b/DashAI/front/src/components/shared/ArtifactRenderer.jsx index 3448d91dd..525411456 100644 --- a/DashAI/front/src/components/shared/ArtifactRenderer.jsx +++ b/DashAI/front/src/components/shared/ArtifactRenderer.jsx @@ -23,8 +23,10 @@ import { applyThemeToLayout } from "../../utils/plotlyTheme"; * (payload: plotly JSON string), "table" (payload: {columns, rows, * highlight}), "image" (payload: {data, mime}) and "text" (payload: string). * Unknown types fall back to preformatted text so nothing is silently lost. + * The optional height sets the plot height and caps image/table height; it + * lets callers render larger (for example a fullscreen view). */ -export default function ArtifactRenderer({ artifact }) { +export default function ArtifactRenderer({ artifact, height = 380 }) { const theme = useTheme(); const { t } = useTranslation(["common"]); @@ -58,7 +60,7 @@ export default function ArtifactRenderer({ artifact }) { return ( + @@ -111,7 +113,7 @@ export default function ArtifactRenderer({ artifact }) { component="img" src={`data:${mime};base64,${data}`} alt={artifact.title || t("common:image")} - sx={{ maxWidth: "100%", maxHeight: 380, objectFit: "contain" }} + sx={{ maxWidth: "100%", maxHeight: height, objectFit: "contain" }} /> ); } @@ -145,4 +147,5 @@ ArtifactRenderer.propTypes = { payload: PropTypes.any, title: PropTypes.string, }).isRequired, + height: PropTypes.number, }; diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx index e7b9f1b14..4953ed6a2 100644 --- a/DashAI/front/src/components/shared/ArtifactViewer.jsx +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -101,6 +101,13 @@ export default function ArtifactViewer({ "&:hover": { color: "text.primary" }, }; + // Fill most of the viewport in the fullscreen view, leaving room for the + // header bar and padding. + const fullscreenHeight = + typeof window !== "undefined" + ? Math.max(360, Math.round(window.innerHeight * 0.8)) + : 720; + return ( {/* Fullscreen view */} - setFullscreen(false)}> - - setFullscreen(false)}> + setFullscreen(false)} + PaperProps={{ sx: { bgcolor: "background.default" } }} + > + + + {artifact.title || ""} + + setFullscreen(false)} sx={actionButtonSx}> - - + + + + From 91bd7da85a9be9c2dff32fef5f860c633ba0b103 Mon Sep 17 00:00:00 2001 From: Creylay Date: Tue, 14 Jul 2026 17:02:43 -0400 Subject: [PATCH 168/308] Refactor RunResults: enhance tab styling with theme integration, improve layout for better visual consistency, and streamline component structure for clarity. --- .../src/components/models/RunResults.jsx | 82 +++++++++++++++---- 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/DashAI/front/src/components/models/RunResults.jsx b/DashAI/front/src/components/models/RunResults.jsx index fe23378ff..2c8bc01c2 100644 --- a/DashAI/front/src/components/models/RunResults.jsx +++ b/DashAI/front/src/components/models/RunResults.jsx @@ -18,6 +18,7 @@ import { DialogActions, Tooltip, } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; import { ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon, @@ -103,6 +104,7 @@ export default function RunResults({ const isFinished = run.status === 3; const isRunning = run.status === 1 || run.status === 2; const { t } = useTranslation(["models", "common"]); + const theme = useTheme(); // Explains *why* a tab is disabled, so it reads as a real (if currently // unavailable) tab rather than being confused with the static group labels. @@ -115,6 +117,42 @@ export default function RunResults({ ? t("models:message.noOptimizableParamsForHpo") : ""; + // Same "pill bar" tab styling used in DatasetVisualization, so both + // sections of the app read as one consistent tab component. + const pillTabsSx = { + minHeight: 40, + bgcolor: theme.palette.ui.box, + borderRadius: 1, + "& .MuiTabs-indicator": { height: "2px" }, + "& .MuiTab-root": { + minHeight: 40, + fontSize: "0.85rem", + borderRadius: "4px", + transition: "all 0.2s", + border: "1px solid transparent", + textTransform: "none", + "&:hover": { bgcolor: theme.palette.action.hover }, + "&.Mui-disabled": { + color: theme.palette.text.disabled, + bgcolor: theme.palette.ui.disabled, + borderColor: theme.palette.ui.border, + opacity: 0.6, + cursor: "not-allowed", + filter: "grayscale(0.6)", + position: "relative", + "&::after": { + content: '""', + position: "absolute", + inset: 0, + borderRadius: "4px", + pointerEvents: "none", + background: + "repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0,0,0,0.1) 10px, rgba(0,0,0,0.1) 20px)", + }, + }, + }, + }; + const runId = run.id; const fetchOperations = useCallback(async () => { if (!runId) return; @@ -251,11 +289,8 @@ export default function RunResults({ @@ -276,13 +311,9 @@ export default function RunResults({ value={[0, 3].includes(activeTab) ? activeTab : false} onChange={(e, newValue) => setActiveTab(newValue)} aria-label="Result characteristics tabs" - sx={{ minHeight: 40 }} + sx={pillTabsSx} > - + } disabled={!isFinished || optimizables === 0} - sx={{ minHeight: 40 }} /> - + {/* Empty spacer just for the horizontal gap between groups — kept + out of the flex height/alignment calculation so the actual + rule (positioned absolutely inside it) can be sized freely + without pushing the tabs around. */} + + + setActiveTab(newValue)} aria-label="Result operations tabs" - sx={{ minHeight: 40 }} + sx={pillTabsSx} > } disabled={!isFinished} - sx={{ minHeight: 40 }} /> } disabled={!isFinished} - sx={{ minHeight: 40 }} /> + + {activeTab === 0 && ( From 7fc92021dde71c1bb41024f167952f4dc16f7c07 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 17:11:44 -0400 Subject: [PATCH 169/308] fix: add Typography component import to ArtifactViewer --- DashAI/front/src/components/shared/ArtifactViewer.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx index 4953ed6a2..3adae65d0 100644 --- a/DashAI/front/src/components/shared/ArtifactViewer.jsx +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -5,6 +5,7 @@ import { IconButton, Menu, MenuItem, + Typography, Tooltip, Dialog, } from "@mui/material"; From 919492762515432e74c83366bd407a8f515d0c33 Mon Sep 17 00:00:00 2001 From: Creylay Date: Tue, 14 Jul 2026 17:23:07 -0400 Subject: [PATCH 170/308] Refactor ModelConfigSidebar: adjust padding for improved layout and visual consistency in the run edit form. --- DashAI/front/src/components/models/ModelConfigSidebar.jsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DashAI/front/src/components/models/ModelConfigSidebar.jsx b/DashAI/front/src/components/models/ModelConfigSidebar.jsx index e2b1a7d0d..7fc075adf 100644 --- a/DashAI/front/src/components/models/ModelConfigSidebar.jsx +++ b/DashAI/front/src/components/models/ModelConfigSidebar.jsx @@ -51,14 +51,15 @@ export default function ModelConfigSidebar({ - + Date: Tue, 14 Jul 2026 17:45:30 -0400 Subject: [PATCH 171/308] Refactor ResultsGraphs: implement run toggling functionality, manage hidden runs state, and update chart rendering logic for improved user interaction and clarity. --- .../results/components/ResultsGraphs.jsx | 29 +++++++++++++ .../components/ResultsGraphsLayout.jsx | 4 +- .../results/components/ResultsGraphsPlot.jsx | 19 ++++++-- .../pages/results/constants/graphsMaking.jsx | 43 +++++++++++++------ 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx index 06a7cbef4..c949f8059 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx @@ -25,6 +25,9 @@ function ResultsGraphs({ const [chartData, setChartData] = useState({}); // { MetricName: { maximize: bool } } — fetched once on mount const [metricsMetadata, setMetricsMetadata] = useState({}); + // Run ids the user deselected from the legend — excluded from the charts + // but still listed (dimmed) so they can be toggled back on. + const [hiddenRunIds, setHiddenRunIds] = useState(() => new Set()); // Controlled or uncontrolled split const selectedSplit = splitProp ?? internalSplit; @@ -102,6 +105,7 @@ function ResultsGraphs({ // share an axis. Every run keeps the same color across all panels. const { panels, legend, yaxis } = smallMultiplesMaking( finishedRuns, + hiddenRunIds, selectedMetrics, metricsKey, theme, @@ -111,6 +115,7 @@ function ResultsGraphs({ // Heatmap is a single all-runs trace, unchanged. const heatmap = heatmapMaking( finishedRuns, + hiddenRunIds, selectedMetrics, metricsKey, theme, @@ -127,6 +132,7 @@ function ResultsGraphs({ } }, [ finishedRuns, + hiddenRunIds, selectedSplit, selectedMetrics, theme, @@ -135,6 +141,16 @@ function ResultsGraphs({ t, ]); + // Reset deselected runs whenever the underlying run set changes (e.g. a + // run is deleted or a new one finishes), so a stale id can't stay hidden. + useEffect(() => { + const validIds = new Set(finishedRuns.map((r) => r.id)); + setHiddenRunIds((prev) => { + const next = new Set([...prev].filter((id) => validIds.has(id))); + return next.size === prev.size ? prev : next; + }); + }, [finishedRuns]); + const handleToggleMetric = (metric) => { const canonicalOrder = availableMetrics[selectedSplit] ?? []; setSelectedMetrics((prev) => { @@ -149,6 +165,18 @@ function ResultsGraphs({ setSelectedMetrics(availableMetrics[selectedSplit] ?? []); const handleClearAll = () => setSelectedMetrics([]); + const handleToggleRun = (runId) => { + setHiddenRunIds((prev) => { + const next = new Set(prev); + if (next.has(runId)) { + next.delete(runId); + } else { + next.add(runId); + } + return next; + }); + }; + if (finishedRuns.length === 0) { return ( @@ -168,6 +196,7 @@ function ResultsGraphs({ handleSelectAll={handleSelectAll} handleClearAll={handleClearAll} chartData={chartData} + onToggleRun={handleToggleRun} /> ); } diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx index dcca2bcbc..f5814a7f2 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx @@ -12,6 +12,7 @@ function ResultsGraphsLayout({ handleSelectAll, handleClearAll, chartData, + onToggleRun, }) { return ( - + ); @@ -45,6 +46,7 @@ ResultsGraphsLayout.propTypes = { handleSelectAll: PropTypes.func.isRequired, handleClearAll: PropTypes.func.isRequired, chartData: PropTypes.object.isRequired, + onToggleRun: PropTypes.func.isRequired, }; export default ResultsGraphsLayout; diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx index 3be7543df..0bc2d2325 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx @@ -25,7 +25,7 @@ function EmptyState({ message }) { ); } -function ResultsGraphsPlot({ chartData }) { +function ResultsGraphsPlot({ chartData, onToggleRun }) { const { t } = useTranslation(["models"]); const theme = useTheme(); const bgColor = theme.palette.background.paper; @@ -83,10 +83,19 @@ function ResultsGraphsPlot({ chartData }) { px: 1, }} > - {legend.map(({ label, color }) => ( + {legend.map(({ id, label, color, hidden }) => ( onToggleRun(id)} + sx={{ + display: "flex", + alignItems: "center", + gap: 1.5, + cursor: "pointer", + opacity: hidden ? 0.4 : 1, + userSelect: "none", + "&:hover": { opacity: hidden ? 0.65 : 0.8 }, + }} > - {/* Empty spacer just for the horizontal gap between groups — kept + {/* Empty spacer just for the horizontal gap between groups. Kept out of the flex height/alignment calculation so the actual rule (positioned absolutely inside it) can be sized freely without pushing the tabs around. */} diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index 37159c9f3..0a52fe476 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -289,7 +289,7 @@ export default function SessionVisualization() { )} - {/* Model detail — full-screen view for a single run */} + {/* Model detail: full-screen view for a single run */} {params.runId ? ( activeRun ? ( - {/* Session header — breadcrumb, title, quick stats */} + {/* Session header: breadcrumb, title, quick stats */} - {/* Compact model cards — quick access to each model */} + {/* Compact model cards: quick access to each model */} - {/* Comparison analysis area — table/graphs across all models */} + {/* Comparison analysis area: table/graphs across all models */} - {/* Metric Split Selector — controls both table and graph views */} + {/* Metric Split Selector: controls both table and graph views */} {(hasTrainMetrics || hasValidationMetrics || hasTestMetrics) && ( From 85970ec75067e182a1e7de515d38fd818c9c5ed1 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 21:43:41 -0400 Subject: [PATCH 173/308] feat: add role field to render artifacts --- DashAI/back/core/artifacts.py | 5 ++- tests/back/core/test_artifacts.py | 60 ++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/DashAI/back/core/artifacts.py b/DashAI/back/core/artifacts.py index 00dc49fab..7e63dad81 100644 --- a/DashAI/back/core/artifacts.py +++ b/DashAI/back/core/artifacts.py @@ -62,10 +62,13 @@ class Artifact(BaseModel): Discriminator naming the artifact kind; fixed per subclass. title : Optional[str] Human readable title shown above the rendered artifact. + role : Literal["input", "explanation"] + Role indicating artifact type, defaulting to explanation. """ type: str title: Optional[str] = None + role: Literal["input", "explanation"] = "explanation" def to_dict(self) -> Dict[str, Any]: """Serialize the artifact to its wire format. @@ -344,7 +347,7 @@ def normalize_artifacts(items: Any) -> List[Dict[str, Any]]: elif isinstance(item, str): artifacts.append(PlotlyArtifact(payload=item).to_dict()) elif isinstance(item, dict) and "type" in item and "payload" in item: - artifacts.append({"title": None, **item}) + artifacts.append({"title": None, "role": "explanation", **item}) elif isinstance(item, dict) and "type" in item and "data" in item: artifacts.append(_legacy_explorer_artifact(item)) else: diff --git a/tests/back/core/test_artifacts.py b/tests/back/core/test_artifacts.py index a65c34d75..372604a44 100644 --- a/tests/back/core/test_artifacts.py +++ b/tests/back/core/test_artifacts.py @@ -24,6 +24,7 @@ def test_plotly_artifact_to_dict(): "type": "plotly", "payload": '{"data": []}', "title": "A plot", + "role": "explanation", } @@ -53,6 +54,7 @@ def test_table_artifact_to_dict(): "highlight": [{"row": 1, "column": 0}], }, "title": "A table", + "role": "explanation", } @@ -76,6 +78,7 @@ def test_text_artifact_to_dict(): "type": "text", "payload": "line 1\nline 2", "title": None, + "role": "explanation", } @@ -138,7 +141,14 @@ def test_normalize_none_is_empty(): def test_normalize_legacy_plotly_strings(): artifacts = normalize_artifacts(['{"data": []}']) - assert artifacts == [{"type": "plotly", "payload": '{"data": []}', "title": None}] + assert artifacts == [ + { + "type": "plotly", + "payload": '{"data": []}', + "title": None, + "role": "explanation", + } + ] def test_normalize_wraps_single_values(): @@ -148,20 +158,29 @@ def test_normalize_wraps_single_values(): def test_normalize_artifact_instances(): artifacts = normalize_artifacts([TextArtifact(payload="x", title="t")]) - assert artifacts == [{"type": "text", "payload": "x", "title": "t"}] + assert artifacts == [ + {"type": "text", "payload": "x", "title": "t", "role": "explanation"} + ] def test_normalize_passes_artifact_dicts_through(): item = {"type": "text", "payload": "x"} assert normalize_artifacts([item]) == [ - {"type": "text", "payload": "x", "title": None} + {"type": "text", "payload": "x", "title": None, "role": "explanation"} ] def test_normalize_legacy_explorer_plotly(): legacy = {"type": "plotly_json", "data": '{"data": []}', "config": {}} artifacts = normalize_artifacts([legacy]) - assert artifacts == [{"type": "plotly", "payload": '{"data": []}', "title": None}] + assert artifacts == [ + { + "type": "plotly", + "payload": '{"data": []}', + "title": None, + "role": "explanation", + } + ] def test_normalize_legacy_explorer_tabular(): @@ -186,4 +205,35 @@ def test_normalize_legacy_explorer_image(): def test_normalize_unrenderable_falls_back_to_text(): [artifact] = normalize_artifacts([42]) - assert artifact == {"type": "text", "payload": "42", "title": None} + assert artifact == { + "type": "text", + "payload": "42", + "title": None, + "role": "explanation", + } + + +def test_artifact_role_defaults_to_explanation(): + from DashAI.back.core.artifacts import TextArtifact + + artifact = TextArtifact(payload="hi") + assert artifact.to_dict()["role"] == "explanation" + + +def test_artifact_role_roundtrips_input(): + from DashAI.back.core.artifacts import TableArtifact, TablePayload + + artifact = TableArtifact( + payload=TablePayload(columns=["a"], rows=[[1]]), + role="input", + ) + assert artifact.to_dict()["role"] == "input" + + +def test_normalize_artifacts_preserves_role(): + from DashAI.back.core.artifacts import normalize_artifacts + + result = normalize_artifacts( + [{"type": "text", "payload": "x", "title": "Instance 1", "role": "input"}] + ) + assert result[0]["role"] == "input" From 1ecf6814c0544eabd1556f94d1f6b535b8d01b06 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 21:47:47 -0400 Subject: [PATCH 174/308] feat: add input artifact builders --- DashAI/back/core/artifacts.py | 71 +++++++++++++++++++++++++++++++ tests/back/core/test_artifacts.py | 33 ++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/DashAI/back/core/artifacts.py b/DashAI/back/core/artifacts.py index 7e63dad81..c1dd9d231 100644 --- a/DashAI/back/core/artifacts.py +++ b/DashAI/back/core/artifacts.py @@ -353,3 +353,74 @@ def normalize_artifacts(items: Any) -> List[Dict[str, Any]]: else: artifacts.append(TextArtifact(payload=str(item)).to_dict()) return artifacts + + +def build_tabular_input_artifact( + feature_names: List[str], + instance_values: List[Any], + title: Optional[str] = None, +) -> "TableArtifact": + """Build an input artifact holding one instance's feature values. + + Parameters + ---------- + feature_names : List[str] + Column headers, one per feature. + instance_values : List[Any] + The feature values fed to the model for this instance. + title : Optional[str] + Group title shared with the instance's explanation artifacts. + + Returns + ------- + TableArtifact + A single-row table artifact with role "input". + """ + return TableArtifact( + payload=TablePayload( + columns=[str(name) for name in feature_names], + rows=[list(instance_values)], + ), + title=title, + role="input", + ) + + +def build_text_input_artifact(text: str, title: Optional[str] = None) -> "TextArtifact": + """Build an input artifact holding the text fed to the model. + + Parameters + ---------- + text : str + The input text for this instance. + title : Optional[str] + Group title shared with the instance's explanation artifacts. + + Returns + ------- + TextArtifact + A text artifact with role "input". + """ + return TextArtifact(payload=text, title=title, role="input") + + +def build_image_input_artifact( + image: Any, title: Optional[str] = None +) -> "ImageArtifact": + """Build an input artifact from the image fed to the model. + + Parameters + ---------- + image : DashAIImage + The input image instance for this explained sample. + title : Optional[str] + Group title shared with the instance's explanation artifacts. + + Returns + ------- + ImageArtifact + An image artifact with role "input". + """ + artifact = ImageArtifact.from_dashai_image(image, title=title) + artifact.role = "input" + return artifact diff --git a/tests/back/core/test_artifacts.py b/tests/back/core/test_artifacts.py index 372604a44..34a572aab 100644 --- a/tests/back/core/test_artifacts.py +++ b/tests/back/core/test_artifacts.py @@ -237,3 +237,36 @@ def test_normalize_artifacts_preserves_role(): [{"type": "text", "payload": "x", "title": "Instance 1", "role": "input"}] ) assert result[0]["role"] == "input" + + +def test_build_tabular_input_artifact(): + from DashAI.back.core.artifacts import build_tabular_input_artifact + + artifact = build_tabular_input_artifact(["age", "city"], [42, "NY"], "Instance 1") + payload = artifact.to_dict() + assert payload["role"] == "input" + assert payload["title"] == "Instance 1" + assert payload["payload"]["columns"] == ["age", "city"] + assert payload["payload"]["rows"] == [[42, "NY"]] + + +def test_build_text_input_artifact(): + from DashAI.back.core.artifacts import build_text_input_artifact + + artifact = build_text_input_artifact("hello world", "Instance 2") + payload = artifact.to_dict() + assert payload["role"] == "input" + assert payload["type"] == "text" + assert payload["payload"] == "hello world" + + +def test_build_image_input_artifact(): + from DashAI.back.core.artifacts import build_image_input_artifact + from DashAI.back.types.dashai_image import DashAIImage + + image = DashAIImage(bytes=PNG_BYTES, path="img.png") + artifact = build_image_input_artifact(image, "Instance 3") + payload = artifact.to_dict() + assert payload["role"] == "input" + assert payload["type"] == "image" + assert payload["title"] == "Instance 3" From e245e915284f09b40b478226c3fc4c3785379c36 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 21:51:00 -0400 Subject: [PATCH 175/308] feat: add plot_overrides column to explainer tables --- ...3d4e5f_add_plot_overrides_to_explainers.py | 31 +++++++++++++++++++ DashAI/back/dependencies/database/models.py | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 DashAI/alembic/versions/9a1b2c3d4e5f_add_plot_overrides_to_explainers.py diff --git a/DashAI/alembic/versions/9a1b2c3d4e5f_add_plot_overrides_to_explainers.py b/DashAI/alembic/versions/9a1b2c3d4e5f_add_plot_overrides_to_explainers.py new file mode 100644 index 000000000..8d7dc68f0 --- /dev/null +++ b/DashAI/alembic/versions/9a1b2c3d4e5f_add_plot_overrides_to_explainers.py @@ -0,0 +1,31 @@ +"""Add plot_overrides to explainers + +Revision ID: 9a1b2c3d4e5f +Revises: f1a2b3c4d5e6 +Create Date: 2026-07-14 00:00:00.000000 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = "9a1b2c3d4e5f" +down_revision: Union[str, None] = "f1a2b3c4d5e6" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "global_explainer", sa.Column("plot_overrides", sa.JSON(), nullable=True) + ) + op.add_column( + "local_explainer", sa.Column("plot_overrides", sa.JSON(), nullable=True) + ) + + +def downgrade() -> None: + op.drop_column("local_explainer", "plot_overrides") + op.drop_column("global_explainer", "plot_overrides") diff --git a/DashAI/back/dependencies/database/models.py b/DashAI/back/dependencies/database/models.py index cdf57d968..cb82329a1 100644 --- a/DashAI/back/dependencies/database/models.py +++ b/DashAI/back/dependencies/database/models.py @@ -348,6 +348,7 @@ class GlobalExplainer(Base): explainer_name: Mapped[str] = mapped_column(String, nullable=False) explanation_path: Mapped[str] = mapped_column(String, nullable=True) plot_path: Mapped[str] = mapped_column(String, nullable=True) + plot_overrides: Mapped[JSON] = mapped_column(JSON, nullable=True) parameters: Mapped[JSON] = mapped_column(JSON) created: Mapped[DateTime] = mapped_column(DateTime, default=datetime.now) status: Mapped[Enum] = mapped_column( @@ -390,6 +391,7 @@ class LocalExplainer(Base): dataset_id: Mapped[int] = mapped_column(nullable=False) explanation_path: Mapped[str] = mapped_column(String, nullable=True) plots_path: Mapped[str] = mapped_column(String, nullable=True) + plot_overrides: Mapped[JSON] = mapped_column(JSON, nullable=True) parameters: Mapped[JSON] = mapped_column(JSON) fit_parameters: Mapped[JSON] = mapped_column(JSON) scope: Mapped[JSON] = mapped_column(JSON) From 423e12ac5093c116a5c922a2a7b765180106872b Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 21:55:42 -0400 Subject: [PATCH 176/308] feat: persist and apply explainer plot edits as overrides --- .../back/api/api_v1/endpoints/explainers.py | 156 +++++++++++++++++- DashAI/back/job/explainer_job.py | 2 + tests/back/api/test_explainers_overrides.py | 61 +++++++ 3 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 tests/back/api/test_explainers_overrides.py diff --git a/DashAI/back/api/api_v1/endpoints/explainers.py b/DashAI/back/api/api_v1/endpoints/explainers.py index bcbaaab6d..5930435b5 100755 --- a/DashAI/back/api/api_v1/endpoints/explainers.py +++ b/DashAI/back/api/api_v1/endpoints/explainers.py @@ -4,6 +4,7 @@ from fastapi import APIRouter, Depends, status from fastapi.exceptions import HTTPException from kink import di, inject +from pydantic import BaseModel from sqlalchemy import exc, select from DashAI.back.api.api_v1.schemas.explainers_params import ( @@ -29,6 +30,52 @@ router = APIRouter() +def _apply_overrides(artifacts: list, overrides: dict | None) -> list: + """Replace plotly artifact payloads with stored edited figures. + + Parameters + ---------- + artifacts : list + Normalized artifact dicts from ``normalize_artifacts``. + overrides : dict or None + Mapping of ``str(index)`` to an edited plotly figure (JSON string). + + Returns + ------- + list + The artifacts with overridden plotly payloads applied. + """ + if not overrides: + return artifacts + import json + + for key, figure in overrides.items(): + try: + idx = int(key) + except (TypeError, ValueError): + continue + if 0 <= idx < len(artifacts) and artifacts[idx].get("type") == "plotly": + artifacts[idx]["payload"] = ( + figure if isinstance(figure, str) else json.dumps(figure) + ) + return artifacts + + +class PlotOverrideBody(BaseModel): + """Request body for saving one plot override. + + Parameters + ---------- + index : int + Artifact index whose payload is being overridden. + figure : object + The edited plotly figure, either a JSON string or a dict. + """ + + index: int + figure: object + + @router.get("/global") @inject async def get_global_explainers( @@ -190,6 +237,7 @@ async def get_global_explanation_plot( ) plot_path = global_explainer[0].plot_path + plot_overrides = global_explainer[0].plot_overrides with open(plot_path, "rb") as file: plot = pickle.load(file) @@ -201,7 +249,7 @@ async def get_global_explanation_plot( detail="Internal database error", ) from e - return normalize_artifacts(plot) + return _apply_overrides(normalize_artifacts(plot), plot_overrides) @router.post("/global", status_code=status.HTTP_201_CREATED) @@ -476,6 +524,7 @@ async def get_local_explanation_plot( ) plots_path = local_explainer[0].plots_path + plot_overrides = local_explainer[0].plot_overrides with open(plots_path, "rb") as file: plots = pickle.load(file) @@ -487,7 +536,110 @@ async def get_local_explanation_plot( detail="Internal database error", ) from e - return normalize_artifacts(plots) + return _apply_overrides(normalize_artifacts(plots), plot_overrides) + + +@router.put("/{scope}/plot/{explainer_id}/override") +@inject +async def save_plot_override( + scope: str, + explainer_id: int, + body: PlotOverrideBody, + session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), +): + """Persist an edited plotly figure for one artifact of an explanation. + + Parameters + ---------- + scope : str + Either "global" or "local". + explainer_id : int + Id of the explainer whose plot is being edited. + body : PlotOverrideBody + The artifact index and the edited plotly figure. + session_factory : Callable[..., ContextManager[Session]] + Factory yielding a SQLAlchemy session. + + Returns + ------- + dict + ``{"status": "ok"}`` on success. + + Raises + ------ + HTTPException + If the scope is invalid or the explainer does not exist. + """ + import json + + if scope not in ("global", "local"): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid scope" + ) + model = GlobalExplainer if scope == "global" else LocalExplainer + with session_factory() as db: + explainer = db.get(model, explainer_id) + if explainer is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Explainer not found" + ) + overrides = dict(explainer.plot_overrides or {}) + figure = body.figure + overrides[str(body.index)] = ( + figure if isinstance(figure, str) else json.dumps(figure) + ) + explainer.plot_overrides = overrides + db.commit() + return {"status": "ok"} + + +@router.delete("/{scope}/plot/{explainer_id}/override/{index}") +@inject +async def delete_plot_override( + scope: str, + explainer_id: int, + index: int, + session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), +): + """Remove a stored plot override, reverting to the computed figure. + + Parameters + ---------- + scope : str + Either "global" or "local". + explainer_id : int + Id of the explainer. + index : int + Artifact index whose override is removed. + session_factory : Callable[..., ContextManager[Session]] + Factory yielding a SQLAlchemy session. + + Returns + ------- + dict + ``{"status": "ok"}``. + + Raises + ------ + HTTPException + If the scope is invalid or the explainer does not exist. + """ + if scope not in ("global", "local"): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid scope" + ) + model = GlobalExplainer if scope == "global" else LocalExplainer + with session_factory() as db: + explainer = db.get(model, explainer_id) + if explainer is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="Explainer not found" + ) + overrides = dict(explainer.plot_overrides or {}) + overrides.pop(str(index), None) + explainer.plot_overrides = overrides or None + db.commit() + return {"status": "ok"} @router.post("/local", status_code=status.HTTP_201_CREATED) diff --git a/DashAI/back/job/explainer_job.py b/DashAI/back/job/explainer_job.py index 0e72adf74..a40d9b6f7 100644 --- a/DashAI/back/job/explainer_job.py +++ b/DashAI/back/job/explainer_job.py @@ -162,6 +162,7 @@ def _generate_global_explanation( try: self.explainer_db.explanation_path = explanation_path self.explainer_db.plot_path = plot_path + self.explainer_db.plot_overrides = None db.commit() except Exception as e: log.exception(e) @@ -301,6 +302,7 @@ def _generate_local_explanation( try: self.explainer_db.explanation_path = explanation_path self.explainer_db.plots_path = plots_path + self.explainer_db.plot_overrides = None db.commit() except Exception as e: log.exception(e) diff --git a/tests/back/api/test_explainers_overrides.py b/tests/back/api/test_explainers_overrides.py new file mode 100644 index 000000000..003b88c22 --- /dev/null +++ b/tests/back/api/test_explainers_overrides.py @@ -0,0 +1,61 @@ +"""Unit tests for the ``_apply_overrides`` helper in explainers endpoints. + +These tests import only the pure helper function, not the FastAPI app, so +they can run without the heavy explainer dependencies (grad_cam, dice_ml, +lime) that are currently missing from the project venv and would otherwise +be pulled in by component registration when booting a TestClient. + +NOTE: The HTTP round-trip tests for the override endpoints (PUT/DELETE +``/{scope}/plot/{explainer_id}/override``) described in the task brief are +deferred until the environment has the explainer dependencies installed, so +a TestClient can be instantiated without import errors. +""" + +import json + +from DashAI.back.api.api_v1.endpoints.explainers import _apply_overrides + + +def test_apply_overrides_replaces_plotly_payload(): + """An override at a plotly artifact's index replaces its payload.""" + artifacts = [ + {"type": "plotly", "payload": "original", "title": "Plot 0"}, + ] + figure = {"data": [], "layout": {"title": "edited"}} + + result = _apply_overrides(artifacts, {"0": figure}) + + assert result[0]["payload"] != "original" + assert json.loads(result[0]["payload"]) == figure + + +def test_apply_overrides_leaves_non_plotly_artifact_unchanged(): + """An override targeting a non-plotly artifact index is ignored.""" + artifacts = [ + {"type": "image", "payload": "original-image", "title": "Image 0"}, + ] + + result = _apply_overrides(artifacts, {"0": {"data": [], "layout": {}}}) + + assert result[0]["payload"] == "original-image" + + +def test_apply_overrides_ignores_out_of_range_index(): + """An override with an out-of-range index does not raise or mutate.""" + artifacts = [ + {"type": "plotly", "payload": "original", "title": "Plot 0"}, + ] + + result = _apply_overrides(artifacts, {"5": {"data": []}}) + + assert result[0]["payload"] == "original" + + +def test_apply_overrides_returns_unchanged_for_none_or_empty(): + """None or empty overrides leave the artifacts list unchanged.""" + artifacts = [ + {"type": "plotly", "payload": "original", "title": "Plot 0"}, + ] + + assert _apply_overrides(artifacts, None) == artifacts + assert _apply_overrides(artifacts, {}) == artifacts From 9549c4c2622848a424b471697db1ad1aed800f6e Mon Sep 17 00:00:00 2001 From: Irozuku Date: Tue, 14 Jul 2026 22:01:12 -0400 Subject: [PATCH 177/308] feat: wire explainer plot edit save and reset to the API --- DashAI/front/src/api/explainer.ts | 24 +++++++++++++++++ .../components/explainers/ExplainersCard.jsx | 27 +++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/DashAI/front/src/api/explainer.ts b/DashAI/front/src/api/explainer.ts index a69583059..7e5f3880a 100644 --- a/DashAI/front/src/api/explainer.ts +++ b/DashAI/front/src/api/explainer.ts @@ -85,3 +85,27 @@ export const deleteExplainer = async ( const response = await api.delete(`/v1/explainer/${scope}/${id}`); return response.data; }; + +export const saveExplainerPlotOverride = async ( + scope: string, + explainerId: number, + index: number, + figure: unknown, +): Promise => { + const response = await api.put( + `/v1/explainer/${scope}/plot/${explainerId}/override`, + { index, figure }, + ); + return response.data; +}; + +export const resetExplainerPlotOverride = async ( + scope: string, + explainerId: number, + index: number, +): Promise => { + const response = await api.delete( + `/v1/explainer/${scope}/plot/${explainerId}/override/${index}`, + ); + return response.data; +}; diff --git a/DashAI/front/src/components/explainers/ExplainersCard.jsx b/DashAI/front/src/components/explainers/ExplainersCard.jsx index aedf95fca..b3c1d8e43 100644 --- a/DashAI/front/src/components/explainers/ExplainersCard.jsx +++ b/DashAI/front/src/components/explainers/ExplainersCard.jsx @@ -14,7 +14,11 @@ import ZoomInIcon from "@mui/icons-material/ZoomIn"; import PropTypes from "prop-types"; import ExplainersPlot from "./ExplainersPlot"; import { useNavigate } from "react-router-dom"; -import { deleteExplainer } from "../../api/explainer"; +import { + deleteExplainer, + saveExplainerPlotOverride, + resetExplainerPlotOverride, +} from "../../api/explainer"; import { useTranslation } from "react-i18next"; import { getComponentById } from "../../api/component"; @@ -34,6 +38,7 @@ export default function ExplainersCard({ const theme = useTheme(); const [open, setOpen] = useState(false); const [componentData, setComponentData] = useState(null); + const [overriddenIndexes, setOverriddenIndexes] = useState([]); const { t } = useTranslation(["explainers"]); const isRunning = RUNNING_STATUSES.includes(explainer.status); @@ -61,6 +66,18 @@ export default function ExplainersCard({ } }; + const handleSaveOverride = async (index, figure) => { + await saveExplainerPlotOverride(scope, explainer.id, index, figure); + setOverriddenIndexes((prev) => + prev.includes(index) ? prev : [...prev, index], + ); + }; + + const handleResetOverride = async (index) => { + await resetExplainerPlotOverride(scope, explainer.id, index); + setOverriddenIndexes((prev) => prev.filter((i) => i !== index)); + }; + useEffect(() => { getComponentById(explainer.explainer_name) .then((data) => { @@ -143,7 +160,13 @@ export default function ExplainersCard({ {/* Reserved slot for the future "generate story" action button. Kept hidden until that feature lands. */} - + )} From 5209e3ff3b80893c5507d24b7b7288db723a06ff Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 00:34:26 -0400 Subject: [PATCH 178/308] feat: serve original explained rows for local explainer input panel --- .../back/api/api_v1/endpoints/explainers.py | 62 +++++++++ DashAI/back/explainability/input_rows.py | 120 ++++++++++++++++++ DashAI/back/job/explainer_job.py | 12 ++ tests/back/explainers/test_input_rows.py | 35 +++++ 4 files changed, 229 insertions(+) create mode 100644 DashAI/back/explainability/input_rows.py create mode 100644 tests/back/explainers/test_input_rows.py diff --git a/DashAI/back/api/api_v1/endpoints/explainers.py b/DashAI/back/api/api_v1/endpoints/explainers.py index 5930435b5..2d0e35fb3 100755 --- a/DashAI/back/api/api_v1/endpoints/explainers.py +++ b/DashAI/back/api/api_v1/endpoints/explainers.py @@ -539,6 +539,68 @@ async def get_local_explanation_plot( return _apply_overrides(normalize_artifacts(plots), plot_overrides) +@router.get("/local/{explainer_id}/inputs") +@inject +async def get_local_explanation_inputs( + explainer_id: int, + session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), +): + """Return the original dataset rows explained by a local explainer. + + These are the model inputs for each explained instance, as they were + before the model's own preprocessing: feature values for tabular tasks, + the input text for text tasks, and the original image for image tasks. + + Parameters + ---------- + explainer_id: int + Id of the local explainer whose input rows to retrieve. + session_factory : Callable[..., ContextManager[Session]] + A factory that creates a context manager that handles a SQLAlchemy + session. + + Returns + ------- + dict + The serialized input rows (see + ``DashAI.back.explainability.input_rows``). ``{"kind": "none", + "instances": []}`` when no input rows were saved (for example an + explainer computed before this feature existed). + + Raises + ------ + HTTPException + If the explainer does not exist or is not finished. + """ + import os + import pickle + + config = di["config"] + + with session_factory() as db: + local_explainer = db.get(LocalExplainer, explainer_id) + if local_explainer is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Explainer not found", + ) + if local_explainer.status != ExplainerStatus.FINISHED: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Explanation not found", + ) + + inputs_path = os.path.join( + config["EXPLANATIONS_PATH"], + f"local_explanation_inputs_{explainer_id}.pickle", + ) + if not os.path.exists(inputs_path): + return {"kind": "none", "instances": []} + + with open(inputs_path, "rb") as file: + return pickle.load(file) + + @router.put("/{scope}/plot/{explainer_id}/override") @inject async def save_plot_override( diff --git a/DashAI/back/explainability/input_rows.py b/DashAI/back/explainability/input_rows.py new file mode 100644 index 000000000..89ac4f43d --- /dev/null +++ b/DashAI/back/explainability/input_rows.py @@ -0,0 +1,120 @@ +"""Serialize the original dataset rows explained by a local explainer. + +A local explainer runs over a selection of rows taken from a dataset (a split +plus the first percentage of it). The frontend shows those original rows as +the "model input" for each explained instance: the feature values for tabular +tasks, the input text for text tasks, and the original image for image tasks. + +This module turns the selected rows (as they were before the model's own +preprocessing) into a JSON-serializable structure the input endpoint returns +verbatim:: + + { + "kind": "tabular" | "image" | "none", + "columns": [, ...], # tabular only + "instances": [ # one entry per explained instance + {"kind": "tabular", "values": [, ...]}, + {"kind": "image", "data": , "mime": }, + ... + ], + } +""" + +import base64 +import io +from typing import Any, Dict, List + + +def _is_image_feature(feature: Any) -> bool: + """Return whether a datasets feature holds images. + + Parameters + ---------- + feature : Any + A value from a datasets ``Dataset.features`` mapping. + + Returns + ------- + bool + True when the feature is an image feature (including DashAIImage). + """ + return "image" in type(feature).__name__.lower() + + +def _image_cell_to_base64(cell: Any) -> str: + """Encode one image cell to base64, tolerating the shapes it can take. + + A datasets image cell can arrive as a dict with a ``bytes`` key, as a + DashAIImage exposing ``bytes``, or as a decoded PIL image. + + Parameters + ---------- + cell : Any + The value stored in an image column for one row. + + Returns + ------- + str + Base64-encoded image bytes, or an empty string when no bytes are + available. + """ + raw = None + if isinstance(cell, dict): + raw = cell.get("bytes") + elif getattr(cell, "bytes", None) is not None: + raw = cell.bytes + if raw is None and hasattr(cell, "save"): + buffer = io.BytesIO() + cell.save(buffer, format="PNG") + raw = buffer.getvalue() + if raw is None: + return "" + return base64.b64encode(bytes(raw)).decode("ascii") + + +def serialize_local_input_rows( + dataset: Any, input_columns: List[str] +) -> Dict[str, Any]: + """Serialize the explained rows of a local explainer for the frontend. + + Parameters + ---------- + dataset : datasets.Dataset + The selected rows as they were before the model's preprocessing, in + explanation order (row ``i`` is explained instance ``i``). + input_columns : List[str] + The model input columns to include. + + Returns + ------- + Dict[str, Any] + A JSON-serializable structure as documented in the module docstring. + Image tasks use the first image input column; all other tasks are + rendered as a table of feature values (text columns included). + """ + features = getattr(dataset, "features", {}) or {} + image_columns = [ + column for column in input_columns if _is_image_feature(features.get(column)) + ] + + if image_columns: + column = image_columns[0] + instances = [ + { + "kind": "image", + "data": _image_cell_to_base64(row[column]), + "mime": "image/png", + } + for row in dataset + ] + return {"kind": "image", "columns": [column], "instances": instances} + + frame = dataset.to_pandas()[list(input_columns)] + instances = [ + {"kind": "tabular", "values": list(row)} for row in frame.values.tolist() + ] + return { + "kind": "tabular", + "columns": list(input_columns), + "instances": instances, + } diff --git a/DashAI/back/job/explainer_job.py b/DashAI/back/job/explainer_job.py index a40d9b6f7..6f5899def 100644 --- a/DashAI/back/job/explainer_job.py +++ b/DashAI/back/job/explainer_job.py @@ -265,6 +265,13 @@ def _generate_local_explanation( self.input_columns, self.output_columns, ) + # Capture the original selected rows (the model input for each + # explained instance) before the model's own preprocessing runs. + from DashAI.back.explainability.input_rows import ( + serialize_local_input_rows, + ) + + input_rows = serialize_local_input_rows(X["train"], self.input_columns) X = trained_model.prepare_dataset(X, is_fit=False) except Exception as e: @@ -294,6 +301,11 @@ def _generate_local_explanation( with open(plots_path, "wb") as file: pickle.dump(plots, file) + inputs_filename = f"local_explanation_inputs_{explainer_id}.pickle" + inputs_path = os.path.join(config["EXPLANATIONS_PATH"], inputs_filename) + with open(inputs_path, "wb") as file: + pickle.dump(input_rows, file) + except Exception as e: log.exception(e) raise JobError( diff --git a/tests/back/explainers/test_input_rows.py b/tests/back/explainers/test_input_rows.py new file mode 100644 index 000000000..cecbb7e90 --- /dev/null +++ b/tests/back/explainers/test_input_rows.py @@ -0,0 +1,35 @@ +"""Tests for serializing a local explainer's original input rows.""" + +from datasets import Dataset + +from DashAI.back.explainability.input_rows import serialize_local_input_rows + + +def test_serialize_tabular_rows_preserves_order_and_columns(): + dataset = Dataset.from_dict( + { + "age": [30, 41, 25], + "city": ["NY", "LA", "SF"], + "label": [0, 1, 0], + } + ) + result = serialize_local_input_rows(dataset, ["age", "city"]) + + assert result["kind"] == "tabular" + assert result["columns"] == ["age", "city"] + assert [i["values"] for i in result["instances"]] == [ + [30, "NY"], + [41, "LA"], + [25, "SF"], + ] + # One entry per row, and the output column is excluded. + assert len(result["instances"]) == 3 + assert all(i["kind"] == "tabular" for i in result["instances"]) + + +def test_serialize_tabular_rows_only_includes_requested_columns(): + dataset = Dataset.from_dict({"a": [1, 2], "b": [3, 4], "c": [5, 6]}) + result = serialize_local_input_rows(dataset, ["a", "c"]) + + assert result["columns"] == ["a", "c"] + assert [i["values"] for i in result["instances"]] == [[1, 5], [2, 6]] From 28cc2faea17f86bd5887703189e7f6094786127e Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 00:37:26 -0400 Subject: [PATCH 179/308] feat: show original dataset row in explainer model input panel --- DashAI/front/src/api/explainer.ts | 11 +++ .../components/explainers/ExplainersPlot.jsx | 51 ++++++++++--- .../components/explainers/ModelInputView.jsx | 73 +++++++++++++++++++ 3 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 DashAI/front/src/components/explainers/ModelInputView.jsx diff --git a/DashAI/front/src/api/explainer.ts b/DashAI/front/src/api/explainer.ts index 7e5f3880a..93ce9019e 100644 --- a/DashAI/front/src/api/explainer.ts +++ b/DashAI/front/src/api/explainer.ts @@ -24,6 +24,17 @@ export const getExplainerPlot = async ( return response.data; }; +export const getExplainerInputs = async ( + explainerId: number, +): Promise<{ + kind: string; + columns?: string[]; + instances: Array>; +}> => { + const response = await api.get(`/v1/explainer/local/${explainerId}/inputs`); + return response.data; +}; + export const createGlobalExplainer = async ( name: string, runId: number, diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index aa4655883..149b9d392 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -11,9 +11,13 @@ import { import PropTypes from "prop-types"; import { useSnackbar } from "notistack"; -import { getExplainerPlot as getExplainerPlotRequest } from "../../api/explainer"; +import { + getExplainerPlot as getExplainerPlotRequest, + getExplainerInputs as getExplainerInputsRequest, +} from "../../api/explainer"; import { useTranslation } from "react-i18next"; import ArtifactViewer from "../shared/ArtifactViewer"; +import ModelInputView from "./ModelInputView"; /** Wrap legacy plotly JSON strings as plotly artifacts; pass typed dicts through. */ function parseExplanationArtifacts(items) { @@ -58,7 +62,9 @@ export default function ExplainersPlot({ const [groups, setGroups] = useState([]); const [currentGroup, setCurrentGroup] = useState(0); const [loading, setLoading] = useState(true); + const [inputs, setInputs] = useState(null); const { t } = useTranslation(["explainers"]); + const isLocal = scope === "local"; const getExplainerPlot = async () => { setLoading(true); @@ -84,8 +90,21 @@ export default function ExplainersPlot({ } }; + const getInputs = async () => { + try { + const response = await getExplainerInputsRequest(explainer.id); + setInputs(response); + } catch (error) { + setInputs(null); + console.error(error); + } + }; + useEffect(() => { - if (explainer.status === 3) getExplainerPlot(); + if (explainer.status === 3) { + getExplainerPlot(); + if (isLocal) getInputs(); + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [explainer.id, explainer.status]); @@ -105,11 +124,12 @@ export default function ExplainersPlot({ } const group = groups[currentGroup]; - const inputArtifacts = group.artifacts.filter((a) => a.role === "input"); - const explanationArtifacts = group.artifacts.filter( - (a) => a.role !== "input", - ); + const explanationArtifacts = group.artifacts; const hasSelector = groups.length > 1; + // The original dataset row given to the model for the selected instance, + // fetched from the explainer's dataset (local explainers only). + const currentInput = + isLocal && inputs?.instances ? inputs.instances[currentGroup] : null; return ( 0 && ( - + {currentInput && ( + {t("explainers:label.modelInput")} - {inputArtifacts.map((artifact) => ( - - ))} + )} diff --git a/DashAI/front/src/components/explainers/ModelInputView.jsx b/DashAI/front/src/components/explainers/ModelInputView.jsx new file mode 100644 index 000000000..c7a70cb87 --- /dev/null +++ b/DashAI/front/src/components/explainers/ModelInputView.jsx @@ -0,0 +1,73 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { + Box, + Table, + TableBody, + TableCell, + TableRow, + Typography, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; + +/** + * Renders the original dataset row given to the model for one explained + * instance: the input image for image tasks, or a feature name/value table + * for tabular and text tasks. Returns null when there is nothing to show. + */ +export default function ModelInputView({ input, columns = [] }) { + const { t } = useTranslation(["common"]); + + if (!input) return null; + + if (input.kind === "image") { + if (!input.data) return null; + return ( + + ); + } + + if (input.kind === "tabular") { + const values = input.values || []; + return ( +
+ + {columns.map((column, i) => ( + + + {column} + + + {values[i] === null || values[i] === undefined + ? "-" + : String(values[i])} + + + ))} + +
+ ); + } + + return ( + + {typeof input.text === "string" ? input.text : ""} + + ); +} + +ModelInputView.propTypes = { + input: PropTypes.shape({ + kind: PropTypes.string, + data: PropTypes.string, + mime: PropTypes.string, + values: PropTypes.array, + text: PropTypes.string, + }), + columns: PropTypes.arrayOf(PropTypes.string), +}; From 29878061fc549aca1b81d2c638b182f11ae5ab8a Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 07:22:39 -0400 Subject: [PATCH 180/308] fix: pass selected dataset to input-row serializer instead of a split key --- DashAI/back/job/explainer_job.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/DashAI/back/job/explainer_job.py b/DashAI/back/job/explainer_job.py index 6f5899def..aa12223e0 100644 --- a/DashAI/back/job/explainer_job.py +++ b/DashAI/back/job/explainer_job.py @@ -271,7 +271,10 @@ def _generate_local_explanation( serialize_local_input_rows, ) - input_rows = serialize_local_input_rows(X["train"], self.input_columns) + input_source = X["train"] if isinstance(X, DatasetDict) else X + input_rows = serialize_local_input_rows( + input_source, self.input_columns + ) X = trained_model.prepare_dataset(X, is_fit=False) except Exception as e: From 94668847c3f576acf03bcb34d49a36a317bd5f99 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 07:26:22 -0400 Subject: [PATCH 181/308] fix: encode explainer input images via to_pil for image tasks --- DashAI/back/explainability/input_rows.py | 81 ++++++++++++------------ 1 file changed, 40 insertions(+), 41 deletions(-) diff --git a/DashAI/back/explainability/input_rows.py b/DashAI/back/explainability/input_rows.py index 89ac4f43d..14d333d39 100644 --- a/DashAI/back/explainability/input_rows.py +++ b/DashAI/back/explainability/input_rows.py @@ -25,51 +25,50 @@ from typing import Any, Dict, List -def _is_image_feature(feature: Any) -> bool: - """Return whether a datasets feature holds images. +def _encode_pil_image(image: Any) -> str: + """Encode a PIL image as a base64 PNG string. Parameters ---------- - feature : Any - A value from a datasets ``Dataset.features`` mapping. + image : PIL.Image.Image + The image to encode. Returns ------- - bool - True when the feature is an image feature (including DashAIImage). + str + Base64-encoded PNG bytes. """ - return "image" in type(feature).__name__.lower() + buffer = io.BytesIO() + image.convert("RGB").save(buffer, format="PNG") + return base64.b64encode(buffer.getvalue()).decode("ascii") -def _image_cell_to_base64(cell: Any) -> str: - """Encode one image cell to base64, tolerating the shapes it can take. +def _detect_image_columns(dataset: Any, input_columns: List[str]) -> List[str]: + """Return the input columns that hold images. - A datasets image cell can arrive as a dict with a ``bytes`` key, as a - DashAIImage exposing ``bytes``, or as a decoded PIL image. + Image cells in a DashAIDataset expose ``to_pil``; probing the first row is + more reliable than inspecting feature type names. Parameters ---------- - cell : Any - The value stored in an image column for one row. + dataset : datasets.Dataset + The selected rows. + input_columns : List[str] + The model input columns to consider. Returns ------- - str - Base64-encoded image bytes, or an empty string when no bytes are - available. + List[str] + The input columns whose cells are images. """ - raw = None - if isinstance(cell, dict): - raw = cell.get("bytes") - elif getattr(cell, "bytes", None) is not None: - raw = cell.bytes - if raw is None and hasattr(cell, "save"): - buffer = io.BytesIO() - cell.save(buffer, format="PNG") - raw = buffer.getvalue() - if raw is None: - return "" - return base64.b64encode(bytes(raw)).decode("ascii") + if len(dataset) == 0: + return [] + first_row = dataset[0] + return [ + column + for column in input_columns + if hasattr(first_row.get(column), "to_pil") + ] def serialize_local_input_rows( @@ -92,26 +91,26 @@ def serialize_local_input_rows( Image tasks use the first image input column; all other tasks are rendered as a table of feature values (text columns included). """ - features = getattr(dataset, "features", {}) or {} - image_columns = [ - column for column in input_columns if _is_image_feature(features.get(column)) - ] + image_columns = _detect_image_columns(dataset, input_columns) if image_columns: column = image_columns[0] - instances = [ - { - "kind": "image", - "data": _image_cell_to_base64(row[column]), - "mime": "image/png", - } - for row in dataset - ] + instances = [] + for index in range(len(dataset)): + cell = dataset[index][column] + try: + data = _encode_pil_image(cell.to_pil()) + except Exception: + data = "" + instances.append( + {"kind": "image", "data": data, "mime": "image/png"} + ) return {"kind": "image", "columns": [column], "instances": instances} frame = dataset.to_pandas()[list(input_columns)] instances = [ - {"kind": "tabular", "values": list(row)} for row in frame.values.tolist() + {"kind": "tabular", "values": list(row)} + for row in frame.values.tolist() ] return { "kind": "tabular", From 2cf88a265993b805016cc2b487716beb90001362 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 07:39:25 -0400 Subject: [PATCH 182/308] feat: store explained rows as a dataset for the explainer input view --- ...d_input_dataset_path_to_local_explainer.py | 29 +++++ .../back/api/api_v1/endpoints/explainers.py | 62 --------- DashAI/back/dependencies/database/models.py | 1 + DashAI/back/explainability/input_rows.py | 119 ------------------ DashAI/back/job/explainer_job.py | 23 ++-- tests/back/explainers/test_input_rows.py | 35 ------ 6 files changed, 40 insertions(+), 229 deletions(-) create mode 100644 DashAI/alembic/versions/b2c3d4e5f6a7_add_input_dataset_path_to_local_explainer.py delete mode 100644 DashAI/back/explainability/input_rows.py delete mode 100644 tests/back/explainers/test_input_rows.py diff --git a/DashAI/alembic/versions/b2c3d4e5f6a7_add_input_dataset_path_to_local_explainer.py b/DashAI/alembic/versions/b2c3d4e5f6a7_add_input_dataset_path_to_local_explainer.py new file mode 100644 index 000000000..12fefbac4 --- /dev/null +++ b/DashAI/alembic/versions/b2c3d4e5f6a7_add_input_dataset_path_to_local_explainer.py @@ -0,0 +1,29 @@ +"""Add input_dataset_path to local_explainer + +Revision ID: b2c3d4e5f6a7 +Revises: 9a1b2c3d4e5f +Create Date: 2026-07-15 00:00:00.000000 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa + +from alembic import op + +revision: str = "b2c3d4e5f6a7" +down_revision: Union[str, None] = "9a1b2c3d4e5f" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column( + "local_explainer", + sa.Column("input_dataset_path", sa.String(), nullable=True), + ) + + +def downgrade() -> None: + op.drop_column("local_explainer", "input_dataset_path") diff --git a/DashAI/back/api/api_v1/endpoints/explainers.py b/DashAI/back/api/api_v1/endpoints/explainers.py index 2d0e35fb3..5930435b5 100755 --- a/DashAI/back/api/api_v1/endpoints/explainers.py +++ b/DashAI/back/api/api_v1/endpoints/explainers.py @@ -539,68 +539,6 @@ async def get_local_explanation_plot( return _apply_overrides(normalize_artifacts(plots), plot_overrides) -@router.get("/local/{explainer_id}/inputs") -@inject -async def get_local_explanation_inputs( - explainer_id: int, - session_factory: "sessionmaker" = Depends(lambda: di["session_factory"]), -): - """Return the original dataset rows explained by a local explainer. - - These are the model inputs for each explained instance, as they were - before the model's own preprocessing: feature values for tabular tasks, - the input text for text tasks, and the original image for image tasks. - - Parameters - ---------- - explainer_id: int - Id of the local explainer whose input rows to retrieve. - session_factory : Callable[..., ContextManager[Session]] - A factory that creates a context manager that handles a SQLAlchemy - session. - - Returns - ------- - dict - The serialized input rows (see - ``DashAI.back.explainability.input_rows``). ``{"kind": "none", - "instances": []}`` when no input rows were saved (for example an - explainer computed before this feature existed). - - Raises - ------ - HTTPException - If the explainer does not exist or is not finished. - """ - import os - import pickle - - config = di["config"] - - with session_factory() as db: - local_explainer = db.get(LocalExplainer, explainer_id) - if local_explainer is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Explainer not found", - ) - if local_explainer.status != ExplainerStatus.FINISHED: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Explanation not found", - ) - - inputs_path = os.path.join( - config["EXPLANATIONS_PATH"], - f"local_explanation_inputs_{explainer_id}.pickle", - ) - if not os.path.exists(inputs_path): - return {"kind": "none", "instances": []} - - with open(inputs_path, "rb") as file: - return pickle.load(file) - - @router.put("/{scope}/plot/{explainer_id}/override") @inject async def save_plot_override( diff --git a/DashAI/back/dependencies/database/models.py b/DashAI/back/dependencies/database/models.py index cb82329a1..574b02bbb 100644 --- a/DashAI/back/dependencies/database/models.py +++ b/DashAI/back/dependencies/database/models.py @@ -392,6 +392,7 @@ class LocalExplainer(Base): explanation_path: Mapped[str] = mapped_column(String, nullable=True) plots_path: Mapped[str] = mapped_column(String, nullable=True) plot_overrides: Mapped[JSON] = mapped_column(JSON, nullable=True) + input_dataset_path: Mapped[str] = mapped_column(String, nullable=True) parameters: Mapped[JSON] = mapped_column(JSON) fit_parameters: Mapped[JSON] = mapped_column(JSON) scope: Mapped[JSON] = mapped_column(JSON) diff --git a/DashAI/back/explainability/input_rows.py b/DashAI/back/explainability/input_rows.py deleted file mode 100644 index 14d333d39..000000000 --- a/DashAI/back/explainability/input_rows.py +++ /dev/null @@ -1,119 +0,0 @@ -"""Serialize the original dataset rows explained by a local explainer. - -A local explainer runs over a selection of rows taken from a dataset (a split -plus the first percentage of it). The frontend shows those original rows as -the "model input" for each explained instance: the feature values for tabular -tasks, the input text for text tasks, and the original image for image tasks. - -This module turns the selected rows (as they were before the model's own -preprocessing) into a JSON-serializable structure the input endpoint returns -verbatim:: - - { - "kind": "tabular" | "image" | "none", - "columns": [, ...], # tabular only - "instances": [ # one entry per explained instance - {"kind": "tabular", "values": [, ...]}, - {"kind": "image", "data": , "mime": }, - ... - ], - } -""" - -import base64 -import io -from typing import Any, Dict, List - - -def _encode_pil_image(image: Any) -> str: - """Encode a PIL image as a base64 PNG string. - - Parameters - ---------- - image : PIL.Image.Image - The image to encode. - - Returns - ------- - str - Base64-encoded PNG bytes. - """ - buffer = io.BytesIO() - image.convert("RGB").save(buffer, format="PNG") - return base64.b64encode(buffer.getvalue()).decode("ascii") - - -def _detect_image_columns(dataset: Any, input_columns: List[str]) -> List[str]: - """Return the input columns that hold images. - - Image cells in a DashAIDataset expose ``to_pil``; probing the first row is - more reliable than inspecting feature type names. - - Parameters - ---------- - dataset : datasets.Dataset - The selected rows. - input_columns : List[str] - The model input columns to consider. - - Returns - ------- - List[str] - The input columns whose cells are images. - """ - if len(dataset) == 0: - return [] - first_row = dataset[0] - return [ - column - for column in input_columns - if hasattr(first_row.get(column), "to_pil") - ] - - -def serialize_local_input_rows( - dataset: Any, input_columns: List[str] -) -> Dict[str, Any]: - """Serialize the explained rows of a local explainer for the frontend. - - Parameters - ---------- - dataset : datasets.Dataset - The selected rows as they were before the model's preprocessing, in - explanation order (row ``i`` is explained instance ``i``). - input_columns : List[str] - The model input columns to include. - - Returns - ------- - Dict[str, Any] - A JSON-serializable structure as documented in the module docstring. - Image tasks use the first image input column; all other tasks are - rendered as a table of feature values (text columns included). - """ - image_columns = _detect_image_columns(dataset, input_columns) - - if image_columns: - column = image_columns[0] - instances = [] - for index in range(len(dataset)): - cell = dataset[index][column] - try: - data = _encode_pil_image(cell.to_pil()) - except Exception: - data = "" - instances.append( - {"kind": "image", "data": data, "mime": "image/png"} - ) - return {"kind": "image", "columns": [column], "instances": instances} - - frame = dataset.to_pandas()[list(input_columns)] - instances = [ - {"kind": "tabular", "values": list(row)} - for row in frame.values.tolist() - ] - return { - "kind": "tabular", - "columns": list(input_columns), - "instances": instances, - } diff --git a/DashAI/back/job/explainer_job.py b/DashAI/back/job/explainer_job.py index aa12223e0..392b5161c 100644 --- a/DashAI/back/job/explainer_job.py +++ b/DashAI/back/job/explainer_job.py @@ -191,6 +191,7 @@ def _generate_local_explanation( from DashAI.back.dataloaders.classes.dashai_dataset import ( load_dataset, prepare_for_model_session, + save_dataset, select_columns, split_dataset, ) @@ -265,16 +266,16 @@ def _generate_local_explanation( self.input_columns, self.output_columns, ) - # Capture the original selected rows (the model input for each - # explained instance) before the model's own preprocessing runs. - from DashAI.back.explainability.input_rows import ( - serialize_local_input_rows, - ) - + # Persist the original selected rows (the model input for each + # explained instance) as a DashAIDataset before the model's own + # preprocessing runs, so the frontend can read them back with + # the existing dataset endpoints. input_source = X["train"] if isinstance(X, DatasetDict) else X - input_rows = serialize_local_input_rows( - input_source, self.input_columns + input_dataset_path = os.path.join( + config["EXPLANATIONS_PATH"], + f"local_explanation_input_{explainer_id}", ) + save_dataset(input_source, os.path.join(input_dataset_path, "dataset")) X = trained_model.prepare_dataset(X, is_fit=False) except Exception as e: @@ -304,11 +305,6 @@ def _generate_local_explanation( with open(plots_path, "wb") as file: pickle.dump(plots, file) - inputs_filename = f"local_explanation_inputs_{explainer_id}.pickle" - inputs_path = os.path.join(config["EXPLANATIONS_PATH"], inputs_filename) - with open(inputs_path, "wb") as file: - pickle.dump(input_rows, file) - except Exception as e: log.exception(e) raise JobError( @@ -317,6 +313,7 @@ def _generate_local_explanation( try: self.explainer_db.explanation_path = explanation_path self.explainer_db.plots_path = plots_path + self.explainer_db.input_dataset_path = input_dataset_path self.explainer_db.plot_overrides = None db.commit() except Exception as e: diff --git a/tests/back/explainers/test_input_rows.py b/tests/back/explainers/test_input_rows.py deleted file mode 100644 index cecbb7e90..000000000 --- a/tests/back/explainers/test_input_rows.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Tests for serializing a local explainer's original input rows.""" - -from datasets import Dataset - -from DashAI.back.explainability.input_rows import serialize_local_input_rows - - -def test_serialize_tabular_rows_preserves_order_and_columns(): - dataset = Dataset.from_dict( - { - "age": [30, 41, 25], - "city": ["NY", "LA", "SF"], - "label": [0, 1, 0], - } - ) - result = serialize_local_input_rows(dataset, ["age", "city"]) - - assert result["kind"] == "tabular" - assert result["columns"] == ["age", "city"] - assert [i["values"] for i in result["instances"]] == [ - [30, "NY"], - [41, "LA"], - [25, "SF"], - ] - # One entry per row, and the output column is excluded. - assert len(result["instances"]) == 3 - assert all(i["kind"] == "tabular" for i in result["instances"]) - - -def test_serialize_tabular_rows_only_includes_requested_columns(): - dataset = Dataset.from_dict({"a": [1, 2], "b": [3, 4], "c": [5, 6]}) - result = serialize_local_input_rows(dataset, ["a", "c"]) - - assert result["columns"] == ["a", "c"] - assert [i["values"] for i in result["instances"]] == [[1, 5], [2, 6]] From dac224c46c03742ebd01cb62b8ec8a229238476c Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 07:55:06 -0400 Subject: [PATCH 183/308] feat: master detail explainer view with paginated instance table --- DashAI/front/src/api/explainer.ts | 11 -- .../explainers/ExplainerInstanceTable.jsx | 153 ++++++++++++++++++ .../components/explainers/ExplainersPlot.jsx | 148 +++++++---------- .../components/explainers/ModelInputView.jsx | 73 --------- .../src/components/models/RunResults.jsx | 10 +- 5 files changed, 217 insertions(+), 178 deletions(-) create mode 100644 DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx delete mode 100644 DashAI/front/src/components/explainers/ModelInputView.jsx diff --git a/DashAI/front/src/api/explainer.ts b/DashAI/front/src/api/explainer.ts index 93ce9019e..7e5f3880a 100644 --- a/DashAI/front/src/api/explainer.ts +++ b/DashAI/front/src/api/explainer.ts @@ -24,17 +24,6 @@ export const getExplainerPlot = async ( return response.data; }; -export const getExplainerInputs = async ( - explainerId: number, -): Promise<{ - kind: string; - columns?: string[]; - instances: Array>; -}> => { - const response = await api.get(`/v1/explainer/local/${explainerId}/inputs`); - return response.data; -}; - export const createGlobalExplainer = async ( name: string, runId: number, diff --git a/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx new file mode 100644 index 000000000..5470ca064 --- /dev/null +++ b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx @@ -0,0 +1,153 @@ +import React, { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import { + Box, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TablePagination, + TableRow, +} from "@mui/material"; + +import { getDatasetFile } from "../../api/datasets"; + +const ROWS_PER_PAGE = 8; + +const isImageValue = (value) => + typeof value === "string" && value.startsWith("data:image"); + +/** + * Paginated table of the rows a local explainer explained, used to pick an + * instance. Rows come from the stored input DashAIDataset via the existing + * dataset file endpoint (images arrive as data URIs). When no dataset path is + * available it falls back to a list of instance titles. Clicking a row calls + * onSelect with the row's global index (its instance index). + */ +export default function ExplainerInstanceTable({ + datasetPath = null, + titles = [], + selectedIndex, + onSelect, +}) { + const [rows, setRows] = useState([]); + const [columns, setColumns] = useState([]); + const [total, setTotal] = useState(titles.length); + const [page, setPage] = useState(0); + + useEffect(() => { + if (!datasetPath) { + setTotal(titles.length); + return undefined; + } + let cancelled = false; + getDatasetFile(datasetPath, page, ROWS_PER_PAGE) + .then((response) => { + if (cancelled) return; + const fetchedRows = response?.rows ?? []; + setRows(fetchedRows); + setTotal(response?.total ?? fetchedRows.length); + setColumns(fetchedRows.length ? Object.keys(fetchedRows[0]) : []); + }) + .catch((error) => { + if (!cancelled) console.error(error); + }); + return () => { + cancelled = true; + }; + }, [datasetPath, page, titles.length]); + + const pageStart = page * ROWS_PER_PAGE; + const fallbackTitles = titles.slice(pageStart, pageStart + ROWS_PER_PAGE); + + return ( + + + + {datasetPath && columns.length > 0 && ( + + + {columns.map((column) => ( + + {column} + + ))} + + + )} + + {datasetPath + ? rows.map((row, i) => { + const globalIndex = pageStart + i; + return ( + onSelect(globalIndex)} + sx={{ cursor: "pointer" }} + > + {columns.map((column) => ( + + {isImageValue(row[column]) ? ( + + ) : ( + String(row[column] ?? "-") + )} + + ))} + + ); + }) + : fallbackTitles.map((title, i) => { + const globalIndex = pageStart + i; + return ( + onSelect(globalIndex)} + sx={{ cursor: "pointer" }} + > + {title} + + ); + })} + +
+
+ setPage(newPage)} + rowsPerPage={ROWS_PER_PAGE} + rowsPerPageOptions={[ROWS_PER_PAGE]} + /> +
+ ); +} + +ExplainerInstanceTable.propTypes = { + datasetPath: PropTypes.string, + titles: PropTypes.arrayOf(PropTypes.string), + selectedIndex: PropTypes.number, + onSelect: PropTypes.func.isRequired, +}; diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 149b9d392..2f0ad1630 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -1,23 +1,12 @@ import { React, useEffect, useState } from "react"; -import { - FormControl, - InputLabel, - MenuItem, - Select, - CircularProgress, - Box, - Typography, -} from "@mui/material"; +import { CircularProgress, Box, Typography } from "@mui/material"; import PropTypes from "prop-types"; import { useSnackbar } from "notistack"; -import { - getExplainerPlot as getExplainerPlotRequest, - getExplainerInputs as getExplainerInputsRequest, -} from "../../api/explainer"; +import { getExplainerPlot as getExplainerPlotRequest } from "../../api/explainer"; import { useTranslation } from "react-i18next"; import ArtifactViewer from "../shared/ArtifactViewer"; -import ModelInputView from "./ModelInputView"; +import ExplainerInstanceTable from "./ExplainerInstanceTable"; /** Wrap legacy plotly JSON strings as plotly artifacts; pass typed dicts through. */ function parseExplanationArtifacts(items) { @@ -62,9 +51,9 @@ export default function ExplainersPlot({ const [groups, setGroups] = useState([]); const [currentGroup, setCurrentGroup] = useState(0); const [loading, setLoading] = useState(true); - const [inputs, setInputs] = useState(null); const { t } = useTranslation(["explainers"]); const isLocal = scope === "local"; + const datasetPath = isLocal ? explainer.input_dataset_path : null; const getExplainerPlot = async () => { setLoading(true); @@ -90,21 +79,8 @@ export default function ExplainersPlot({ } }; - const getInputs = async () => { - try { - const response = await getExplainerInputsRequest(explainer.id); - setInputs(response); - } catch (error) { - setInputs(null); - console.error(error); - } - }; - useEffect(() => { - if (explainer.status === 3) { - getExplainerPlot(); - if (isLocal) getInputs(); - } + if (explainer.status === 3) getExplainerPlot(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [explainer.id, explainer.status]); @@ -124,69 +100,22 @@ export default function ExplainersPlot({ } const group = groups[currentGroup]; - const explanationArtifacts = group.artifacts; const hasSelector = groups.length > 1; - // The original dataset row given to the model for the selected instance, - // fetched from the explainer's dataset (local explainers only). - const currentInput = - isLocal && inputs?.instances ? inputs.instances[currentGroup] : null; + const instanceLabel = (g, index) => + g.title ?? t("explainers:label.instanceNumber", { number: index + 1 }); - return ( + // The explanation artifacts for the selected instance. + const detail = ( - {hasSelector ? ( - - - {t("explainers:label.selectInstance")} - - - - ) : ( - group.title && ( - - {group.title} - - ) - )} - - {currentInput && ( - - - {t("explainers:label.modelInput")} - - - - )} - - {explanationArtifacts.map((artifact) => ( + {group.artifacts.map((artifact) => ( ); + + // Single instance: no selector needed, show the explanation full width. + if (!hasSelector) { + return ( + + {group.title && ( + + {group.title} + + )} + {detail} + + ); + } + + // Many instances: the explained dataset rows on the left (paginated, the + // model input for each instance), the selected instance's explanation on + // the right. + return ( + + + + {t("explainers:label.modelInput")} + + instanceLabel(g, i))} + selectedIndex={currentGroup} + onSelect={setCurrentGroup} + /> + + {detail} + + ); } ExplainersPlot.propTypes = { explainer: PropTypes.shape({ id: PropTypes.number, status: PropTypes.number, + input_dataset_path: PropTypes.string, }).isRequired, scope: PropTypes.string.isRequired, onSaveOverride: PropTypes.func, diff --git a/DashAI/front/src/components/explainers/ModelInputView.jsx b/DashAI/front/src/components/explainers/ModelInputView.jsx deleted file mode 100644 index c7a70cb87..000000000 --- a/DashAI/front/src/components/explainers/ModelInputView.jsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { - Box, - Table, - TableBody, - TableCell, - TableRow, - Typography, -} from "@mui/material"; -import { useTranslation } from "react-i18next"; - -/** - * Renders the original dataset row given to the model for one explained - * instance: the input image for image tasks, or a feature name/value table - * for tabular and text tasks. Returns null when there is nothing to show. - */ -export default function ModelInputView({ input, columns = [] }) { - const { t } = useTranslation(["common"]); - - if (!input) return null; - - if (input.kind === "image") { - if (!input.data) return null; - return ( - - ); - } - - if (input.kind === "tabular") { - const values = input.values || []; - return ( - - - {columns.map((column, i) => ( - - - {column} - - - {values[i] === null || values[i] === undefined - ? "-" - : String(values[i])} - - - ))} - -
- ); - } - - return ( - - {typeof input.text === "string" ? input.text : ""} - - ); -} - -ModelInputView.propTypes = { - input: PropTypes.shape({ - kind: PropTypes.string, - data: PropTypes.string, - mime: PropTypes.string, - values: PropTypes.array, - text: PropTypes.string, - }), - columns: PropTypes.arrayOf(PropTypes.string), -}; diff --git a/DashAI/front/src/components/models/RunResults.jsx b/DashAI/front/src/components/models/RunResults.jsx index f9b4ed3ce..5b01732e4 100644 --- a/DashAI/front/src/components/models/RunResults.jsx +++ b/DashAI/front/src/components/models/RunResults.jsx @@ -493,9 +493,8 @@ export default function RunResults({ ) : ( @@ -564,9 +563,8 @@ export default function RunResults({ ) : ( From 4fc28f151137c79b1cb591764164285175425633 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 07:55:11 -0400 Subject: [PATCH 184/308] fix: remove duplicate explainer name in single instance card --- .../src/components/explainers/ExplainersPlot.jsx | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 2f0ad1630..cbf6ef99d 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -133,20 +133,10 @@ export default function ExplainersPlot({ ); - // Single instance: no selector needed, show the explanation full width. + // Single instance (typically a global explainer): no selector and no title, + // since the card header already names the explainer. Show it full width. if (!hasSelector) { - return ( - - {group.title && ( - - {group.title} - - )} - {detail} - - ); + return detail; } // Many instances: the explained dataset rows on the left (paginated, the From 7f85edc96d1901d14af17bcabec39f0d84ddf2ae Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 08:07:11 -0400 Subject: [PATCH 185/308] refactor: reuse LeanDatasetTable for explainer instances with list fallback --- .../explainers/ExplainerInstanceTable.jsx | 148 ++++++------------ .../components/explainers/ExplainersPlot.jsx | 17 +- .../leanDatasetTable/LeanDatasetTable.jsx | 40 ++++- .../leanDatasetTable/leanDatasetTable.css | 8 + 4 files changed, 97 insertions(+), 116 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx index 5470ca064..ef956ace1 100644 --- a/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx +++ b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx @@ -1,153 +1,99 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import PropTypes from "prop-types"; import { - Box, Paper, Table, TableBody, TableCell, TableContainer, - TableHead, TablePagination, TableRow, } from "@mui/material"; import { getDatasetFile } from "../../api/datasets"; +import LeanDatasetTable from "../shared/leanDatasetTable/LeanDatasetTable"; -const ROWS_PER_PAGE = 8; - -const isImageValue = (value) => - typeof value === "string" && value.startsWith("data:image"); +const ROWS_PER_PAGE = 10; /** - * Paginated table of the rows a local explainer explained, used to pick an - * instance. Rows come from the stored input DashAIDataset via the existing - * dataset file endpoint (images arrive as data URIs). When no dataset path is - * available it falls back to a list of instance titles. Clicking a row calls - * onSelect with the row's global index (its instance index). + * Instance picker for a local explainer's explained rows. When the explainer + * stored its input rows as a dataset (datasetPath), it renders the shared + * dataset table (feature values, image thumbnails, pagination). Otherwise, for + * explainers computed before input rows were persisted, it falls back to a + * simple paginated list of instance labels. Selecting a row calls onSelect + * with the instance index. */ export default function ExplainerInstanceTable({ datasetPath = null, - titles = [], + titles, selectedIndex, onSelect, }) { - const [rows, setRows] = useState([]); - const [columns, setColumns] = useState([]); - const [total, setTotal] = useState(titles.length); const [page, setPage] = useState(0); - useEffect(() => { - if (!datasetPath) { - setTotal(titles.length); - return undefined; - } - let cancelled = false; - getDatasetFile(datasetPath, page, ROWS_PER_PAGE) - .then((response) => { - if (cancelled) return; - const fetchedRows = response?.rows ?? []; - setRows(fetchedRows); - setTotal(response?.total ?? fetchedRows.length); - setColumns(fetchedRows.length ? Object.keys(fetchedRows[0]) : []); - }) - .catch((error) => { - if (!cancelled) console.error(error); - }); - return () => { - cancelled = true; - }; - }, [datasetPath, page, titles.length]); + if (datasetPath) { + return ( + + getDatasetFile(datasetPath, fetchPageIndex, pageSize) + } + datasetPath={datasetPath} + initialPageSize={10} + enableFilters={false} + enableSearch={false} + enableColumnVisibility={false} + enableRowsPerPage={false} + showExportButton={false} + selectedRowIndex={selectedIndex} + onRowClick={(row, globalIndex) => onSelect(globalIndex)} + /> + ); + } const pageStart = page * ROWS_PER_PAGE; - const fallbackTitles = titles.slice(pageStart, pageStart + ROWS_PER_PAGE); + const pageTitles = titles.slice(pageStart, pageStart + ROWS_PER_PAGE); return ( - +
- {datasetPath && columns.length > 0 && ( - - - {columns.map((column) => ( - - {column} - - ))} - - - )} - {datasetPath - ? rows.map((row, i) => { - const globalIndex = pageStart + i; - return ( - onSelect(globalIndex)} - sx={{ cursor: "pointer" }} - > - {columns.map((column) => ( - - {isImageValue(row[column]) ? ( - - ) : ( - String(row[column] ?? "-") - )} - - ))} - - ); - }) - : fallbackTitles.map((title, i) => { - const globalIndex = pageStart + i; - return ( - onSelect(globalIndex)} - sx={{ cursor: "pointer" }} - > - {title} - - ); - })} + {pageTitles.map((title, i) => { + const globalIndex = pageStart + i; + return ( + onSelect(globalIndex)} + sx={{ cursor: "pointer" }} + > + {title} + + ); + })}
setPage(newPage)} rowsPerPage={ROWS_PER_PAGE} rowsPerPageOptions={[ROWS_PER_PAGE]} /> - +
); } ExplainerInstanceTable.propTypes = { datasetPath: PropTypes.string, - titles: PropTypes.arrayOf(PropTypes.string), + titles: PropTypes.arrayOf(PropTypes.string).isRequired, selectedIndex: PropTypes.number, onSelect: PropTypes.func.isRequired, }; diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index cbf6ef99d..d2dbd532a 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -81,7 +81,6 @@ export default function ExplainersPlot({ useEffect(() => { if (explainer.status === 3) getExplainerPlot(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [explainer.id, explainer.status]); if (loading || explainer.status !== 3) { @@ -101,8 +100,6 @@ export default function ExplainersPlot({ const group = groups[currentGroup]; const hasSelector = groups.length > 1; - const instanceLabel = (g, index) => - g.title ?? t("explainers:label.instanceNumber", { number: index + 1 }); // The explanation artifacts for the selected instance. const detail = ( @@ -139,14 +136,14 @@ export default function ExplainersPlot({ return detail; } - // Many instances: the explained dataset rows on the left (paginated, the - // model input for each instance), the selected instance's explanation on - // the right. + // Many instances: the instance picker on the left (the explained dataset + // rows when stored, else a list of instance labels), the selected + // instance's explanation on the right. return ( - + instanceLabel(g, i))} + titles={groups.map( + (g, i) => + g.title ?? + t("explainers:label.instanceNumber", { number: i + 1 }), + )} selectedIndex={currentGroup} onSelect={setCurrentGroup} /> diff --git a/DashAI/front/src/components/shared/leanDatasetTable/LeanDatasetTable.jsx b/DashAI/front/src/components/shared/leanDatasetTable/LeanDatasetTable.jsx index 292d41ad4..bced3db6f 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/LeanDatasetTable.jsx +++ b/DashAI/front/src/components/shared/leanDatasetTable/LeanDatasetTable.jsx @@ -35,6 +35,8 @@ function LeanDatasetTable({ enableRowsPerPage = true, enableColumnVisibility = true, showExportButton = true, + onRowClick = null, + selectedRowIndex = null, }) { const theme = useTheme(); const { enqueueSnackbar } = useSnackbar(); @@ -290,6 +292,7 @@ function LeanDatasetTable({ "--lean-header-bg": theme.palette.ui.panelDark, "--lean-header-fg": theme.palette.text.primary, "--lean-body-bg": theme.palette.ui.panelDark, + "--lean-row-hover": theme.palette.action.hover, }} > - {rows.map((row, i) => ( - - {visibleColumnKeys.map((key) => ( - - ))} - - ))} + {rows.map((row, i) => { + const globalIndex = page * pageSize + i; + const isSelected = selectedRowIndex === globalIndex; + return ( + onRowClick(row, globalIndex) : undefined + } + style={{ + backgroundColor: isSelected + ? theme.palette.action.selected + : undefined, + }} + > + {visibleColumnKeys.map((key) => ( + + ))} + + ); + })} {!loading && rows.length === 0 && ( @@ -444,6 +468,8 @@ LeanDatasetTable.propTypes = { enableRowsPerPage: PropTypes.bool, enableColumnVisibility: PropTypes.bool, showExportButton: PropTypes.bool, + onRowClick: PropTypes.func, + selectedRowIndex: PropTypes.number, }; export default LeanDatasetTable; diff --git a/DashAI/front/src/components/shared/leanDatasetTable/leanDatasetTable.css b/DashAI/front/src/components/shared/leanDatasetTable/leanDatasetTable.css index 20bd751ad..382b15f4e 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/leanDatasetTable.css +++ b/DashAI/front/src/components/shared/leanDatasetTable/leanDatasetTable.css @@ -291,3 +291,11 @@ .lean-sort--desc .lean-sort-arrow--down { opacity: 1; } + +.lean-row--clickable { + cursor: pointer; +} + +.lean-row--clickable:hover { + background: var(--lean-row-hover, rgba(255, 255, 255, 0.06)); +} From f6831ae5bf9d11fd1a9a5ebf41d93f51b136c759 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 08:22:04 -0400 Subject: [PATCH 186/308] refactor: remove model input label from ExplainersPlot component --- DashAI/front/src/components/explainers/ExplainersPlot.jsx | 7 ------- DashAI/front/src/utils/i18n/locales/de/explainers.json | 3 +-- DashAI/front/src/utils/i18n/locales/en/explainers.json | 3 +-- DashAI/front/src/utils/i18n/locales/es/explainers.json | 3 +-- DashAI/front/src/utils/i18n/locales/pt/explainers.json | 3 +-- DashAI/front/src/utils/i18n/locales/zh/explainers.json | 3 +-- 6 files changed, 5 insertions(+), 17 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index d2dbd532a..792ba0be1 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -144,13 +144,6 @@ export default function ExplainersPlot({ sx={{ display: "flex", gap: 3, width: "100%", alignItems: "flex-start" }} > - - {t("explainers:label.modelInput")} - Date: Wed, 15 Jul 2026 08:22:21 -0400 Subject: [PATCH 187/308] feat: add conditional rendering to LeanToolbar --- .../components/shared/leanDatasetTable/LeanToolbar.jsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/DashAI/front/src/components/shared/leanDatasetTable/LeanToolbar.jsx b/DashAI/front/src/components/shared/leanDatasetTable/LeanToolbar.jsx index 9049f3be3..df167838f 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/LeanToolbar.jsx +++ b/DashAI/front/src/components/shared/leanDatasetTable/LeanToolbar.jsx @@ -34,6 +34,16 @@ const LeanToolbar = memo(function LeanToolbar({ onExport, }) { const { t } = useTranslation(["datasets"]); + + if ( + !enableFilters && + !enableSearch && + !enableColumnVisibility && + !showExportButton + ) { + return null; + } + return ( {/* Left: export */} From 12bf063480622712bafa2c73745c709a9c76d172 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 08:33:43 -0400 Subject: [PATCH 188/308] feat: full height instance table styled like the dataset table --- .../explainers/ExplainerInstanceTable.jsx | 76 +++++++++++-------- .../components/explainers/ExplainersPlot.jsx | 14 +++- .../leanDatasetTable/leanDatasetTable.css | 6 ++ 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx index ef956ace1..ea2a726ff 100644 --- a/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx +++ b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx @@ -1,17 +1,11 @@ import React, { useState } from "react"; import PropTypes from "prop-types"; -import { - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TablePagination, - TableRow, -} from "@mui/material"; +import { Box, TablePagination } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; import { getDatasetFile } from "../../api/datasets"; import LeanDatasetTable from "../shared/leanDatasetTable/LeanDatasetTable"; +import "../shared/leanDatasetTable/leanDatasetTable.css"; const ROWS_PER_PAGE = 10; @@ -20,8 +14,8 @@ const ROWS_PER_PAGE = 10; * stored its input rows as a dataset (datasetPath), it renders the shared * dataset table (feature values, image thumbnails, pagination). Otherwise, for * explainers computed before input rows were persisted, it falls back to a - * simple paginated list of instance labels. Selecting a row calls onSelect - * with the instance index. + * list of instance labels styled like the dataset table. Selecting a row calls + * onSelect with the instance index. */ export default function ExplainerInstanceTable({ datasetPath = null, @@ -29,6 +23,7 @@ export default function ExplainerInstanceTable({ selectedIndex, onSelect, }) { + const theme = useTheme(); const [page, setPage] = useState(0); if (datasetPath) { @@ -54,40 +49,61 @@ export default function ExplainerInstanceTable({ const pageTitles = titles.slice(pageStart, pageStart + ROWS_PER_PAGE); return ( -
- - - + +
+
+ {pageTitles.map((title, i) => { const globalIndex = pageStart + i; + const isSelected = globalIndex === selectedIndex; return ( - onSelect(globalIndex)} - sx={{ cursor: "pointer" }} + style={{ + backgroundColor: isSelected + ? theme.palette.action.selected + : undefined, + }} > - {title} - + + ); })} - -
+ {title} +
-
+ + +
setPage(newPage)} rowsPerPage={ROWS_PER_PAGE} + showFirstButton + showLastButton + onPageChange={(_event, newPage) => setPage(newPage)} rowsPerPageOptions={[ROWS_PER_PAGE]} + labelRowsPerPage="" + slotProps={{ select: { sx: { display: "none" } } }} /> - +
); } diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 792ba0be1..0c7330333 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -140,10 +140,16 @@ export default function ExplainersPlot({ // rows when stored, else a list of instance labels), the selected // instance's explanation on the right. return ( - - + + Date: Wed, 15 Jul 2026 10:53:42 -0400 Subject: [PATCH 189/308] feat: replace "-" with null when no types given in LeanHeaderCell --- .../src/components/shared/leanDatasetTable/LeanHeaderCell.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DashAI/front/src/components/shared/leanDatasetTable/LeanHeaderCell.jsx b/DashAI/front/src/components/shared/leanDatasetTable/LeanHeaderCell.jsx index 14e4d77e2..318dd9eb7 100644 --- a/DashAI/front/src/components/shared/leanDatasetTable/LeanHeaderCell.jsx +++ b/DashAI/front/src/components/shared/leanDatasetTable/LeanHeaderCell.jsx @@ -65,7 +65,7 @@ export default function LeanHeaderCell({
- {type || "-"} + {type || null} {type === "Categorical" && datasetId && ( Date: Wed, 15 Jul 2026 12:00:38 -0400 Subject: [PATCH 190/308] feat: add NumericExpansion converter for unary numeric transformations - Implemented NumericExpansion converter to apply unary operations (log1p, square, sqrt) on numeric columns. - Added NumericExpansionSchema for hyperparameter validation. - Updated initial_components.py to include NumericExpansion. - Introduced BalancedAccuracy metric for improved classification performance evaluation. - Enhanced various classifiers (DecisionTree, ExtraTrees, HistGradientBoosting, etc.) with class_weight parameter to handle class imbalance. - Updated classification_task to support BalancedAccuracy. - Added tests for BalancedAccuracy to ensure correctness against sklearn reference. --- .../category/feature_engineering.py | 28 ++ .../simple_converters/column_arithmetic.py | 474 ++++++++++++++++++ .../simple_converters/numeric_expansion.py | 261 ++++++++++ DashAI/back/initial_components.py | 6 + .../classification/balanced_accuracy.py | 77 +++ .../scikit_learn/decision_tree_classifier.py | 40 ++ .../scikit_learn/extra_trees_classifier.py | 49 ++ .../hist_gradient_boosting_classifier.py | 42 ++ .../scikit_learn/lightgbm_classifier.py | 51 ++ .../scikit_learn/linear_svc_classifier.py | 51 +- .../scikit_learn/logistic_regression.py | 41 ++ .../scikit_learn/random_forest_classifier.py | 56 ++- .../models/scikit_learn/sgd_classifier.py | 42 ++ DashAI/back/models/scikit_learn/svc.py | 41 ++ DashAI/back/tasks/classification_task.py | 1 + .../metrics/test_classification_metrics.py | 35 ++ 16 files changed, 1293 insertions(+), 2 deletions(-) create mode 100644 DashAI/back/converters/category/feature_engineering.py create mode 100644 DashAI/back/converters/simple_converters/column_arithmetic.py create mode 100644 DashAI/back/converters/simple_converters/numeric_expansion.py create mode 100644 DashAI/back/metrics/classification/balanced_accuracy.py diff --git a/DashAI/back/converters/category/feature_engineering.py b/DashAI/back/converters/category/feature_engineering.py new file mode 100644 index 000000000..10f54a75b --- /dev/null +++ b/DashAI/back/converters/category/feature_engineering.py @@ -0,0 +1,28 @@ +from typing import Final + +from DashAI.back.converters.base_converter import BaseConverter +from DashAI.back.core.utils import MultilingualString +from DashAI.back.static.icons import Icon + + +class FeatureEngineeringConverter(BaseConverter): + """Base class for converters that derive new numeric features from existing columns. + + Feature engineering converters compute new columns out of one or more + existing columns instead of modifying them in place. Examples include + ColumnArithmetic (arithmetic combinations of two columns) and + NumericExpansion (log1p, square, and square-root expansions of a column). + + Use these converters to craft new signals for models when the raw + columns alone are not expressive enough. + """ + + CATEGORY = MultilingualString( + en="Feature Engineering", + es="Ingeniería de Características", + pt="Engenharia de Características", + de="Feature-Engineering", + zh="特征工程", + ) + ICON: Final[str] = Icon.Functions.value + COLOR: Final[str] = "rgb(0, 188, 212)" diff --git a/DashAI/back/converters/simple_converters/column_arithmetic.py b/DashAI/back/converters/simple_converters/column_arithmetic.py new file mode 100644 index 000000000..3163ce462 --- /dev/null +++ b/DashAI/back/converters/simple_converters/column_arithmetic.py @@ -0,0 +1,474 @@ +from typing import TYPE_CHECKING, Union + +from DashAI.back.converters.base_converter import BaseConverter +from DashAI.back.converters.category.feature_engineering import ( + FeatureEngineeringConverter, +) +from DashAI.back.core.schema_fields import ( + enum_field, + float_field, + none_type, + schema_field, + string_field, +) +from DashAI.back.core.schema_fields.base_schema import BaseSchema +from DashAI.back.core.utils import MultilingualString +from DashAI.back.types.dashai_data_type import DashAIDataType +from DashAI.back.types.value_types import Float, Integer + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + +OPERATIONS = ["add", "subtract", "multiply", "divide"] +OPERAND_B_MODES = ["column", "constant"] + + +class ColumnArithmeticSchema(BaseSchema): + """Schema for ColumnArithmetic hyperparameters.""" + + column_a: schema_field( + string_field(), + "", + description=MultilingualString( + en="Name of the first operand column.", + es="Nombre de la columna del primer operando.", + pt="Nome da coluna do primeiro operando.", + de="Name der ersten Operanden-Spalte.", + zh="第一个操作数列的名称。", + ), + ) # type: ignore + operand_b_mode: schema_field( + enum_field(OPERAND_B_MODES), + "column", + description=MultilingualString( + en=( + "Whether the second operand is another column ('column') or " + "a fixed number ('constant')." + ), + es=( + "Si el segundo operando es otra columna ('column') o un " + "número fijo ('constant')." + ), + pt=( + "Se o segundo operando é outra coluna ('column') ou um " + "número fixo ('constant')." + ), + de=( + "Ob der zweite Operand eine weitere Spalte ('column') oder " + "eine feste Zahl ('constant') ist." + ), + zh="第二个操作数是另一列('column')还是固定数值('constant')。", + ), + ) # type: ignore + column_b: schema_field( + none_type(string_field()), + None, + description=MultilingualString( + en=( + "Name of the second operand column. Can be the same as " + "'column_a'. Required when 'operand_b_mode' is 'column'." + ), + es=( + "Nombre de la columna del segundo operando. Puede ser la " + "misma que 'column_a'. Requerido cuando 'operand_b_mode' es " + "'column'." + ), + pt=( + "Nome da coluna do segundo operando. Pode ser a mesma que " + "'column_a'. Necessário quando 'operand_b_mode' é 'column'." + ), + de=( + "Name der zweiten Operanden-Spalte. Kann mit 'column_a' " + "übereinstimmen. Erforderlich, wenn 'operand_b_mode' " + "'column' ist." + ), + zh="第二个操作数列的名称。可以与 'column_a' 相同。" + "当 'operand_b_mode' 为 'column' 时必填。", + ), + ) # type: ignore + constant: schema_field( + none_type(float_field()), + None, + description=MultilingualString( + en=( + "Fixed number used as the second operand. Required when " + "'operand_b_mode' is 'constant'." + ), + es=( + "Número fijo usado como segundo operando. Requerido cuando " + "'operand_b_mode' es 'constant'." + ), + pt=( + "Número fixo usado como segundo operando. Necessário quando " + "'operand_b_mode' é 'constant'." + ), + de=( + "Feste Zahl, die als zweiter Operand verwendet wird. " + "Erforderlich, wenn 'operand_b_mode' 'constant' ist." + ), + zh="用作第二个操作数的固定数值。当 'operand_b_mode' 为 'constant' 时必填。", + ), + ) # type: ignore + operation: schema_field( + enum_field(OPERATIONS), + "add", + description=MultilingualString( + en=( + "Arithmetic operation to apply between 'column_a' and the " + "second operand ('column_b' or 'constant')." + ), + es=( + "Operación aritmética a aplicar entre 'column_a' y el " + "segundo operando ('column_b' o 'constant')." + ), + pt=( + "Operação aritmética a aplicar entre 'column_a' e o " + "segundo operando ('column_b' ou 'constant')." + ), + de=( + "Arithmetische Operation zwischen 'column_a' und dem " + "zweiten Operanden ('column_b' oder 'constant')." + ), + zh="在 'column_a' 与第二个操作数('column_b' 或 'constant')" + "之间应用的算术运算。", + ), + ) # type: ignore + output_column_name: schema_field( + none_type(string_field()), + None, + description=MultilingualString( + en=( + "Name of the resulting column. If null, a name is generated " + "from the operands and the operation." + ), + es=( + "Nombre de la columna resultante. Si es nulo, se genera un " + "nombre a partir de los operandos y la operación." + ), + pt=( + "Nome da coluna resultante. Se nulo, um nome é gerado a " + "partir dos operandos e da operação." + ), + de=( + "Name der resultierenden Spalte. Wenn null, wird ein Name " + "aus den Operanden und der Operation generiert." + ), + zh="结果列的名称。如果为空,将根据操作数和运算生成名称。", + ), + ) # type: ignore + + +class ColumnArithmetic(FeatureEngineeringConverter, BaseConverter): + """Combine a column with a second operand into a new numeric column. + + Applies addition, subtraction, multiplication, or division element-wise + between ``column_a`` and a second operand, which is either another + column (``operand_b_mode="column"``, via ``column_b``) or a fixed number + (``operand_b_mode="constant"``, via ``constant``, e.g. ``column_a * 2``). + When using two columns, both may refer to the same one (e.g. dividing a + column by itself). Division by zero yields ``NaN`` instead of raising an + error. + + The original columns are left untouched; the result is appended as a new + column named ``output_column_name``, or, if not provided, + ``__`` or ``__``. + + The output column is ``Integer`` when both operands are ``Integer`` (a + whole-number ``constant`` counts as ``Integer``) and the operation is + ``add``, ``subtract``, or ``multiply`` (all of which stay exact on + integers). ``divide`` always produces a ``Float`` column, since integer + division is not exact in general, and any operation involving a + ``Float`` operand also produces a ``Float`` column. + """ + + SCHEMA = ColumnArithmeticSchema + DESCRIPTION = MultilingualString( + en=( + "Applies an arithmetic operation (add, subtract, multiply, divide) " + "between a column and a second operand — another column or a " + "fixed constant (e.g. column * 2) — and appends the result as a " + "new column." + ), + es=( + "Aplica una operación aritmética (sumar, restar, multiplicar, " + "dividir) entre una columna y un segundo operando — otra " + "columna o una constante fija (por ejemplo, columna * 2) — y " + "agrega el resultado como una nueva columna." + ), + pt=( + "Aplica uma operação aritmética (somar, subtrair, multiplicar, " + "dividir) entre uma coluna e um segundo operando — outra " + "coluna ou uma constante fixa (por exemplo, coluna * 2) — e " + "adiciona o resultado como uma nova coluna." + ), + de=( + "Wendet eine arithmetische Operation (Addieren, Subtrahieren, " + "Multiplizieren, Dividieren) zwischen einer Spalte und einem " + "zweiten Operanden an — einer weiteren Spalte oder einer festen " + "Konstante (z. B. Spalte * 2) — und fügt das Ergebnis als neue " + "Spalte hinzu." + ), + zh=( + "对一列与第二个操作数(另一列或固定常量,例如 列 * 2)应用算术运算" + "(加、减、乘、除),并将结果作为新列追加。" + ), + ) + SHORT_DESCRIPTION = MultilingualString( + en="Arithmetic combination of a column with another column or a constant.", + es="Combinación aritmética de una columna con otra columna o una constante.", + pt="Combinação aritmética de uma coluna com outra coluna ou uma constante.", + de="Arithmetische Kombination einer Spalte mit einer weiteren Spalte " + "oder einer Konstante.", + zh="将一列与另一列或常量进行算术组合。", + ) + DISPLAY_NAME = MultilingualString( + en="Column Arithmetic", + es="Aritmética de Columnas", + pt="Aritmética de Colunas", + de="Spalten-Arithmetik", + zh="列算术运算", + ) + IMAGE_PREVIEW = "column_arithmetic.png" + + metadata = { + "allowed_types": [Float, Integer], + "allowed_dtypes": [], + } + + def __init__( + self, + column_a: str, + operation: str, + operand_b_mode: str = "column", + column_b: Union[str, None] = None, + constant: Union[float, None] = None, + output_column_name: Union[str, None] = None, + ): + """Initialise the converter with the operands and the operation to apply. + + Parameters + ---------- + column_a : str + Name of the first operand column. Must be non-empty. + operation : str + One of ``"add"``, ``"subtract"``, ``"multiply"``, ``"divide"``. + operand_b_mode : str, optional + Either ``"column"`` (default), to use ``column_b`` as the second + operand, or ``"constant"``, to use ``constant`` instead. + column_b : str, optional + Name of the second operand column. Required (non-empty) when + ``operand_b_mode`` is ``"column"``. May be equal to ``column_a``. + constant : float, optional + Fixed number used as the second operand. Required when + ``operand_b_mode`` is ``"constant"``. + output_column_name : str, optional + Name of the resulting column. If ``None`` or not a string, a name + is generated from the operands and the operation. + + Raises + ------ + ValueError + If ``column_a`` is empty, if ``operation`` or ``operand_b_mode`` + is not one of the supported values, or if the operand required by + ``operand_b_mode`` (``column_b`` or ``constant``) is missing. + """ + super().__init__() + if not isinstance(column_a, str) or not column_a: + raise ValueError("'column_a' must be a non-empty string.") + if operation not in OPERATIONS: + raise ValueError( + f"'operation' must be one of {OPERATIONS}, got '{operation}'." + ) + if operand_b_mode not in OPERAND_B_MODES: + raise ValueError( + f"'operand_b_mode' must be one of {OPERAND_B_MODES}, " + f"got '{operand_b_mode}'." + ) + if operand_b_mode == "column" and ( + not isinstance(column_b, str) or not column_b + ): + raise ValueError( + "'column_b' must be a non-empty string when 'operand_b_mode' " + "is 'column'." + ) + if operand_b_mode == "constant" and ( + not isinstance(constant, (int, float)) or isinstance(constant, bool) + ): + raise ValueError( + "'constant' must be a number when 'operand_b_mode' is 'constant'." + ) + + self.column_a = column_a + self.operation = operation + self.operand_b_mode = operand_b_mode + self.column_b = column_b if operand_b_mode == "column" else None + self.constant = float(constant) if operand_b_mode == "constant" else None + self.output_column_name = ( + output_column_name if isinstance(output_column_name, str) else None + ) + self._result_column_name: Union[str, None] = None + self._output_is_integer: bool = False + + @staticmethod + def _format_constant(value: float) -> str: + """Render a constant for use in an auto-generated column name.""" + return str(int(value)) if value == int(value) else str(value) + + def fit( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "ColumnArithmetic": + """Validate that the operand column(s) are present and numeric. + + Parameters + ---------- + x : DashAIDataset + The scoped dataset expected to contain ``column_a`` (and + ``column_b`` when ``operand_b_mode`` is ``"column"``). + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + ColumnArithmetic + The fitted converter instance (self). + + Raises + ------ + ValueError + If an operand column is missing from ``x`` or is not of a + numeric (Float or Integer) type. + """ + operand_columns = ( + (self.column_a, self.column_b) + if self.operand_b_mode == "column" + else (self.column_a,) + ) + for col in operand_columns: + if col not in x.column_names: + raise ValueError( + f"Column '{col}' was not found in the converter's scope. " + "Make sure it is included in the selected columns." + ) + if col in x.types and not isinstance(x.types[col], (Float, Integer)): + raise ValueError( + f"Column '{col}' must be numeric (Float or Integer) to be " + "used in ColumnArithmetic." + ) + + if self.operand_b_mode == "column": + operand_b_label = self.column_b + operand_b_is_integer = isinstance(x.types.get(self.column_b), Integer) + else: + operand_b_label = self._format_constant(self.constant) + operand_b_is_integer = self.constant == int(self.constant) + + self._result_column_name = self.output_column_name or ( + f"{self.column_a}_{self.operation}_{operand_b_label}" + ) + self._output_is_integer = ( + self.operation != "divide" + and isinstance(x.types.get(self.column_a), Integer) + and operand_b_is_integer + ) + return self + + def _get_operand_b(self, x_pandas, dtype: str): + """Return the second operand as an array aligned with ``x_pandas``. + + Either the values of ``column_b``, or ``constant`` broadcast to the + same length, depending on ``operand_b_mode``. + """ + import numpy as np + + if self.operand_b_mode == "column": + return x_pandas[self.column_b].to_numpy(dtype=dtype) + return np.full(len(x_pandas), self.constant, dtype=dtype) + + def transform( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "DashAIDataset": + """Compute the arithmetic result and append it as a new column. + + Parameters + ---------- + x : DashAIDataset + The dataset containing ``column_a`` (and ``column_b`` when + ``operand_b_mode`` is ``"column"``). + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + DashAIDataset + The original dataset with the arithmetic result appended as a + new column, typed ``Integer`` or ``Float`` depending on the + operands and the operation (see class docstring). + """ + import numpy as np + import pyarrow as pa + + from DashAI.back.dataloaders.classes.dashai_dataset import modify_table + + x_pandas = x.to_pandas() + + if self._output_is_integer: + a = x_pandas[self.column_a].to_numpy(dtype="int64") + b = self._get_operand_b(x_pandas, "int64") + + if self.operation == "add": + result = a + b + elif self.operation == "subtract": + result = a - b + else: # multiply + result = a * b + + arrow_type = pa.int64() + else: + a = x_pandas[self.column_a].to_numpy(dtype="float64") + b = self._get_operand_b(x_pandas, "float64") + + with np.errstate(divide="ignore", invalid="ignore"): + if self.operation == "add": + result = a + b + elif self.operation == "subtract": + result = a - b + elif self.operation == "multiply": + result = a * b + else: # divide + result = np.where(b != 0, a / b, np.nan) + + arrow_type = pa.float64() + + new_types = dict(x.types) + new_types[self._result_column_name] = self.get_output_type() + + return modify_table( + x, + {self._result_column_name: pa.array(result, type=arrow_type)}, + types=new_types, + ) + + def get_output_type(self, column_name: str = None) -> DashAIDataType: + """Return the output type for the arithmetic result. + + Determined during ``fit``: ``Integer`` when both operands are + ``Integer`` (a whole-number ``constant`` counts as ``Integer``) and + the operation isn't ``divide``, ``Float`` otherwise. + + Parameters + ---------- + column_name : str, optional + Not used; the result column always has the same type. + Defaults to None. + + Returns + ------- + DashAIDataType + An ``Integer`` type backed by ``pyarrow.int64()``, or a + ``Float`` type backed by ``pyarrow.float64()``. + """ + import pyarrow as pa + + if self._output_is_integer: + return Integer(arrow_type=pa.int64()) + return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/converters/simple_converters/numeric_expansion.py b/DashAI/back/converters/simple_converters/numeric_expansion.py new file mode 100644 index 000000000..b2062c31c --- /dev/null +++ b/DashAI/back/converters/simple_converters/numeric_expansion.py @@ -0,0 +1,261 @@ +from typing import TYPE_CHECKING, Dict, List, Union + +from DashAI.back.converters.base_converter import BaseConverter +from DashAI.back.converters.category.feature_engineering import ( + FeatureEngineeringConverter, +) +from DashAI.back.core.schema_fields import enum_field, schema_field +from DashAI.back.core.schema_fields.base_schema import BaseSchema +from DashAI.back.core.utils import MultilingualString +from DashAI.back.types.dashai_data_type import DashAIDataType +from DashAI.back.types.value_types import Float, Integer + +if TYPE_CHECKING: + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + +OPERATIONS = ["log1p", "square", "sqrt"] + + +class NumericExpansionSchema(BaseSchema): + """Schema for NumericExpansion hyperparameters.""" + + operation: schema_field( + enum_field(OPERATIONS), + "log1p", + description=MultilingualString( + en=( + "Unary numeric expansion to apply to each selected column: " + "'log1p' (ln(1+x)), 'square' (x^2), or 'sqrt' (sqrt(x))." + ), + es=( + "Expansión numérica unaria a aplicar a cada columna " + "seleccionada: 'log1p' (ln(1+x)), 'square' (x^2) o " + "'sqrt' (raíz cuadrada de x)." + ), + pt=( + "Expansão numérica unária a aplicar a cada coluna " + "selecionada: 'log1p' (ln(1+x)), 'square' (x^2) ou " + "'sqrt' (raiz quadrada de x)." + ), + de=( + "Unäre numerische Erweiterung, die auf jede ausgewählte " + "Spalte angewendet wird: 'log1p' (ln(1+x)), 'square' (x^2) " + "oder 'sqrt' (Quadratwurzel von x)." + ), + zh="应用于每个所选列的一元数值扩展:" + "'log1p'(ln(1+x))、'square'(x^2)或 'sqrt'(x 的平方根)。", + ), + ) # type: ignore + + +class NumericExpansion(FeatureEngineeringConverter, BaseConverter): + """Derive a new numeric feature from each selected column via a unary function. + + Applies one of ``log1p`` (``ln(1+x)``), ``square`` (``x^2``), or ``sqrt`` + (``sqrt(x)``) to every numeric column in scope, appending one new column + per input column named ``_``. Values outside the + domain of the chosen function (``x <= -1`` for ``log1p``, ``x < 0`` for + ``sqrt``) become ``NaN`` in the corresponding output. + + The original columns are left untouched. ``square`` preserves the input + column's type (``Integer`` stays ``Integer``, ``Float`` stays ``Float``), + since squaring is exact for both. ``log1p`` and ``sqrt`` always produce a + ``Float`` column, since they can yield non-integer or ``NaN`` results + even from integer input. + """ + + SCHEMA = NumericExpansionSchema + DESCRIPTION = MultilingualString( + en=( + "Applies a unary numeric expansion (log1p, square, or sqrt) to " + "each selected column and appends the result as a new column." + ), + es=( + "Aplica una expansión numérica unaria (log1p, square o sqrt) a " + "cada columna seleccionada y agrega el resultado como una nueva " + "columna." + ), + pt=( + "Aplica uma expansão numérica unária (log1p, square ou sqrt) a " + "cada coluna selecionada e adiciona o resultado como uma nova " + "coluna." + ), + de=( + "Wendet eine unäre numerische Erweiterung (log1p, square oder " + "sqrt) auf jede ausgewählte Spalte an und fügt das Ergebnis als " + "neue Spalte hinzu." + ), + zh=( + "对每个所选列应用一元数值扩展(log1p、square 或 sqrt)," + "并将结果作为新列追加。" + ), + ) + SHORT_DESCRIPTION = MultilingualString( + en="Unary numeric expansion (log1p, square, sqrt) of a column.", + es="Expansión numérica unaria (log1p, square, sqrt) de una columna.", + pt="Expansão numérica unária (log1p, square, sqrt) de uma coluna.", + de="Unäre numerische Erweiterung (log1p, square, sqrt) einer Spalte.", + zh="列的一元数值扩展(log1p、square、sqrt)。", + ) + DISPLAY_NAME = MultilingualString( + en="Numeric Expansion", + es="Expansión Numérica", + pt="Expansão Numérica", + de="Numerische Erweiterung", + zh="数值扩展", + ) + IMAGE_PREVIEW = "numeric_expansion.png" + + metadata = { + "allowed_types": [Float, Integer], + "allowed_dtypes": [], + } + + def __init__(self, operation: str): + """Initialise the converter with the unary operation to apply. + + Parameters + ---------- + operation : str + One of ``"log1p"``, ``"square"``, ``"sqrt"``. + + Raises + ------ + ValueError + If ``operation`` is not one of the supported operations. + """ + super().__init__() + if operation not in OPERATIONS: + raise ValueError( + f"'operation' must be one of {OPERATIONS}, got '{operation}'." + ) + self.operation = operation + self._target_columns: List[str] = [] + self._output_types: Dict[str, DashAIDataType] = {} + + def fit( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "NumericExpansion": + """Identify which columns in ``x`` are numeric (Float or Integer). + + Also precomputes the output type of each resulting column: ``square`` + keeps the input column's type, while ``log1p`` and ``sqrt`` always + produce ``Float``. + + Parameters + ---------- + x : DashAIDataset + The dataset whose columns will be inspected. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + NumericExpansion + The fitted converter instance (self). + """ + import pyarrow as pa + + self._target_columns = [] + self._output_types = {} + for col_name in x.column_names: + col_type = x.types.get(col_name) + if isinstance(col_type, (Float, Integer)): + self._target_columns.append(col_name) + new_col_name = f"{self.operation}_{col_name}" + if self.operation == "square" and isinstance(col_type, Integer): + self._output_types[new_col_name] = Integer(arrow_type=pa.int64()) + else: + self._output_types[new_col_name] = Float(arrow_type=pa.float64()) + else: + print( + f"Warning: Column '{col_name}' in scope is not numeric " + "(Float or Integer) and will be ignored by NumericExpansion." + ) + if not self._target_columns: + print( + "Warning: NumericExpansion did not find any valid numeric " + "columns in the provided scope." + ) + return self + + def transform( + self, x: "DashAIDataset", y: Union["DashAIDataset", None] = None + ) -> "DashAIDataset": + """Apply the configured unary expansion to the fitted numeric columns. + + Parameters + ---------- + x : DashAIDataset + The dataset to transform. + y : DashAIDataset, optional + Ignored. Defaults to None. + + Returns + ------- + DashAIDataset + The dataset with one new ``_`` column appended + per fitted numeric column, typed ``Integer`` or ``Float`` + depending on the source column and the operation (see class + docstring). + """ + import numpy as np + import pyarrow as pa + + from DashAI.back.dataloaders.classes.dashai_dataset import modify_table + + if not self._target_columns: + return x + + x_pandas = x.to_pandas() + new_columns = {} + new_types = dict(x.types) + + with np.errstate(divide="ignore", invalid="ignore"): + for col in self._target_columns: + new_col_name = f"{self.operation}_{col}" + output_type = self._output_types[new_col_name] + + if isinstance(output_type, Integer): + values = x_pandas[col].to_numpy(dtype="int64") + result = values**2 + arrow_type = pa.int64() + else: + values = x_pandas[col].to_numpy(dtype="float64") + if self.operation == "log1p": + result = np.where(values > -1, np.log1p(values), np.nan) + elif self.operation == "square": + result = values**2 + else: # sqrt + result = np.where(values >= 0, np.sqrt(values), np.nan) + arrow_type = pa.float64() + + new_columns[new_col_name] = pa.array(result, type=arrow_type) + new_types[new_col_name] = output_type + + return modify_table(x, new_columns, types=new_types) + + def get_output_type(self, column_name: str = None) -> DashAIDataType: + """Return the output type for a given expanded column. + + Determined during ``fit``: ``Integer`` when the operation is + ``square`` and the source column is ``Integer``, ``Float`` + otherwise. + + Parameters + ---------- + column_name : str, optional + Name of the output column (e.g. ``"square_age"``). Defaults to + None. + + Returns + ------- + DashAIDataType + An ``Integer`` type backed by ``pyarrow.int64()``, or a + ``Float`` type backed by ``pyarrow.float64()``. + """ + import pyarrow as pa + + if column_name in self._output_types: + return self._output_types[column_name] + return Float(arrow_type=pa.float64()) diff --git a/DashAI/back/initial_components.py b/DashAI/back/initial_components.py index 71f8899f6..126d94c58 100644 --- a/DashAI/back/initial_components.py +++ b/DashAI/back/initial_components.py @@ -58,8 +58,10 @@ from DashAI.back.converters.simple_converters.character_replacer import ( CharacterReplacer, ) +from DashAI.back.converters.simple_converters.column_arithmetic import ColumnArithmetic from DashAI.back.converters.simple_converters.column_remover import ColumnRemover from DashAI.back.converters.simple_converters.nan_remover import NanRemover +from DashAI.back.converters.simple_converters.numeric_expansion import NumericExpansion # DataLoaders from DashAI.back.dataloaders.classes.arff_dataloader import ARFFDataLoader @@ -115,6 +117,7 @@ # Metrics from DashAI.back.metrics.classification.accuracy import Accuracy +from DashAI.back.metrics.classification.balanced_accuracy import BalancedAccuracy from DashAI.back.metrics.classification.cohen_kappa import CohenKappa from DashAI.back.metrics.classification.f1 import F1 from DashAI.back.metrics.classification.hamming_distance import HammingDistance @@ -416,6 +419,7 @@ def get_initial_components(): # Metrics F1, Accuracy, + BalancedAccuracy, Precision, Recall, Bleu, @@ -468,6 +472,8 @@ def get_initial_components(): ColumnRemover, NanRemover, CharacterReplacer, + ColumnArithmetic, + NumericExpansion, FastICA, IncrementalPCA, PCA, diff --git a/DashAI/back/metrics/classification/balanced_accuracy.py b/DashAI/back/metrics/classification/balanced_accuracy.py new file mode 100644 index 000000000..2a1fe0873 --- /dev/null +++ b/DashAI/back/metrics/classification/balanced_accuracy.py @@ -0,0 +1,77 @@ +"""DashAI balanced accuracy classification metric implementation.""" + +from typing import TYPE_CHECKING + +from DashAI.back.core.utils import MultilingualString +from DashAI.back.metrics.classification_metric import ( + ClassificationMetric, + prepare_to_metric, +) + +if TYPE_CHECKING: + import numpy as np + + from DashAI.back.dataloaders.classes.dashai_dataset import DashAIDataset + + +class BalancedAccuracy(ClassificationMetric): + """Average of recall obtained on each class. + + Balanced Accuracy is the macro-average of recall scores per class. It + avoids the inflated performance estimates that plain accuracy gives on + imbalanced datasets, since each class contributes equally regardless of + how many samples it has. + + :: + + Balanced Accuracy = (1 / C) * sum(recall_c for c in classes) + + Range: [0, 1], higher is better (``MAXIMIZE = True``). + + References + ---------- + - [1] https://scikit-learn.org/stable/modules/generated/sklearn.metrics.balanced_accuracy_score.html + """ + + DESCRIPTION = MultilingualString( + en=("Macro-average of recall per class, best suited for imbalanced datasets."), + es=( + "Promedio macro del recall por clase, más adecuado para " + "datasets desbalanceados." + ), + pt=( + "Média macro do recall por classe, mais adequada para " + "conjuntos de dados desbalanceados." + ), + de=( + "Makro-Durchschnitt des Recalls je Klasse, am besten geeignet für " + "unausgewogene Datensätze." + ), + zh=("按类别宏平均的召回率,最适用于类别不均衡的数据集。"), + ) + + @staticmethod + def score( + true_labels: "DashAIDataset", + probs_pred_labels: "np.ndarray", + ) -> float: + """Calculate the balanced accuracy between true and predicted labels. + + Parameters + ---------- + true_labels : DashAIDataset + A DashAI dataset with labels. + probs_pred_labels : np.ndarray + A two-dimensional matrix in which each column represents a class + and the row values represent the probability that an example belongs + to the class associated with the column. + + Returns + ------- + float + Balanced accuracy score between true labels and predicted labels + """ + from sklearn.metrics import balanced_accuracy_score + + true_labels, pred_labels = prepare_to_metric(true_labels, probs_pred_labels) + return balanced_accuracy_score(true_labels, pred_labels) diff --git a/DashAI/back/models/scikit_learn/decision_tree_classifier.py b/DashAI/back/models/scikit_learn/decision_tree_classifier.py index cfeab27e0..fcabdebdb 100644 --- a/DashAI/back/models/scikit_learn/decision_tree_classifier.py +++ b/DashAI/back/models/scikit_learn/decision_tree_classifier.py @@ -184,6 +184,46 @@ class DecisionTreeClassifierSchema(BaseSchema): zh="最大特征数", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class DecisionTreeClassifier( diff --git a/DashAI/back/models/scikit_learn/extra_trees_classifier.py b/DashAI/back/models/scikit_learn/extra_trees_classifier.py index cb2f5ba4c..4f0efbe8b 100644 --- a/DashAI/back/models/scikit_learn/extra_trees_classifier.py +++ b/DashAI/back/models/scikit_learn/extra_trees_classifier.py @@ -3,6 +3,7 @@ from DashAI.back.core.schema_fields import ( BaseSchema, bool_field, + enum_field, none_type, optimizer_int_field, schema_field, @@ -198,6 +199,54 @@ class ExtraTreesClassifierSchema(BaseSchema): zh="随机状态", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced", "balanced_subsample"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' adjusts weights inversely proportional to " + "class frequencies in the whole dataset; 'balanced_subsample' does " + "the same but per bootstrap sample of each tree. Use None for no " + "weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta los pesos de forma inversamente " + "proporcional a la frecuencia de cada clase en todo el conjunto de " + "datos; 'balanced_subsample' hace lo mismo pero por cada muestra " + "bootstrap de cada árbol. Use None para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta os pesos de forma " + "inversamente proporcional à frequência de cada classe em todo o " + "conjunto de dados; 'balanced_subsample' faz o mesmo, mas por " + "amostra bootstrap de cada árvore. Use None para não aplicar " + "ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte umgekehrt proportional zur Klassenhäufigkeit im " + "gesamten Datensatz an; 'balanced_subsample' tut dasselbe, jedoch " + "pro Bootstrap-Stichprobe jedes Baums. Verwenden Sie None für " + "keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'根据整个数据集中" + "各类别频率的反比调整权重;'balanced_subsample'则对每棵树的自举" + "采样分别执行相同操作。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class ExtraTreesClassifier( diff --git a/DashAI/back/models/scikit_learn/hist_gradient_boosting_classifier.py b/DashAI/back/models/scikit_learn/hist_gradient_boosting_classifier.py index 515914e7c..21af0271d 100644 --- a/DashAI/back/models/scikit_learn/hist_gradient_boosting_classifier.py +++ b/DashAI/back/models/scikit_learn/hist_gradient_boosting_classifier.py @@ -4,6 +4,8 @@ from DashAI.back.core.schema_fields import ( BaseSchema, + enum_field, + none_type, optimizer_float_field, optimizer_int_field, schema_field, @@ -228,6 +230,46 @@ class HistGradientBoostingClassifierSchema(BaseSchema): zh="L2正则化", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class HistGradientBoostingClassifier( diff --git a/DashAI/back/models/scikit_learn/lightgbm_classifier.py b/DashAI/back/models/scikit_learn/lightgbm_classifier.py index 45933d511..90584582f 100644 --- a/DashAI/back/models/scikit_learn/lightgbm_classifier.py +++ b/DashAI/back/models/scikit_learn/lightgbm_classifier.py @@ -2,6 +2,8 @@ from DashAI.back.core.schema_fields import ( BaseSchema, + enum_field, + none_type, optimizer_float_field, optimizer_int_field, schema_field, @@ -319,6 +321,55 @@ class LGBMClassifierSchema(BaseSchema): zh="最小叶节点样本数", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting. " + "Only applies to multiclass problems, or binary problems where " + "``is_unbalance``/``scale_pos_weight`` is not set." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación. Solo aplica a problemas " + "multiclase, o binarios en los que no se haya configurado " + "``is_unbalance``/``scale_pos_weight``." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação. Aplica-se apenas a " + "problemas multiclasse, ou binários em que ``is_unbalance``/" + "``scale_pos_weight`` não estejam definidos." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung. " + "Gilt nur für Mehrklassenprobleme oder binäre Probleme, bei " + "denen ``is_unbalance``/``scale_pos_weight`` nicht gesetzt ist." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。仅适用于多分类问题," + "或未设置``is_unbalance``/``scale_pos_weight``的二分类问题。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class _LightGBMDashAIMixin(TabularClassificationModel, SklearnLikeClassifier): diff --git a/DashAI/back/models/scikit_learn/linear_svc_classifier.py b/DashAI/back/models/scikit_learn/linear_svc_classifier.py index dcb314d91..dcc7cbff6 100644 --- a/DashAI/back/models/scikit_learn/linear_svc_classifier.py +++ b/DashAI/back/models/scikit_learn/linear_svc_classifier.py @@ -192,6 +192,47 @@ class LinearSVCClassifierSchema(BaseSchema): ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore + class LinearSVCClassifier( TabularClassificationModel, SklearnLikeClassifier, _LinearSVC @@ -281,7 +322,15 @@ def train(self, x_train, y_train, x_validation=None, y_validation=None): params = { k: getattr(self, k) - for k in ["C", "loss", "max_iter", "tol", "fit_intercept", "random_state"] + for k in [ + "C", + "loss", + "max_iter", + "tol", + "fit_intercept", + "random_state", + "class_weight", + ] if hasattr(self, k) } base = _LinearSVCRaw(**params) diff --git a/DashAI/back/models/scikit_learn/logistic_regression.py b/DashAI/back/models/scikit_learn/logistic_regression.py index 9aa5f4dc8..2efc4581e 100644 --- a/DashAI/back/models/scikit_learn/logistic_regression.py +++ b/DashAI/back/models/scikit_learn/logistic_regression.py @@ -3,6 +3,7 @@ from DashAI.back.core.schema_fields import ( BaseSchema, enum_field, + none_type, optimizer_float_field, optimizer_int_field, schema_field, @@ -114,6 +115,46 @@ class LogisticRegressionSchema(BaseSchema): zh="最大迭代次数", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class LogisticRegression( diff --git a/DashAI/back/models/scikit_learn/random_forest_classifier.py b/DashAI/back/models/scikit_learn/random_forest_classifier.py index 7c6104b1a..1b808b886 100644 --- a/DashAI/back/models/scikit_learn/random_forest_classifier.py +++ b/DashAI/back/models/scikit_learn/random_forest_classifier.py @@ -1,6 +1,12 @@ from sklearn.ensemble import RandomForestClassifier as _RandomForestClassifier -from DashAI.back.core.schema_fields import BaseSchema, optimizer_int_field, schema_field +from DashAI.back.core.schema_fields import ( + BaseSchema, + enum_field, + none_type, + optimizer_int_field, + schema_field, +) from DashAI.back.core.utils import MultilingualString from DashAI.back.models.scikit_learn.sklearn_like_classifier import ( SklearnLikeClassifier, @@ -224,6 +230,54 @@ class RandomForestClassifierSchema(BaseSchema): zh="随机状态", ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced", "balanced_subsample"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' adjusts weights inversely proportional to " + "class frequencies in the whole dataset; 'balanced_subsample' does " + "the same but per bootstrap sample of each tree. Use None for no " + "weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta los pesos de forma inversamente " + "proporcional a la frecuencia de cada clase en todo el conjunto de " + "datos; 'balanced_subsample' hace lo mismo pero por cada muestra " + "bootstrap de cada árbol. Use None para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta os pesos de forma " + "inversamente proporcional à frequência de cada classe em todo o " + "conjunto de dados; 'balanced_subsample' faz o mesmo, mas por " + "amostra bootstrap de cada árvore. Use None para não aplicar " + "ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte umgekehrt proportional zur Klassenhäufigkeit im " + "gesamten Datensatz an; 'balanced_subsample' tut dasselbe, jedoch " + "pro Bootstrap-Stichprobe jedes Baums. Verwenden Sie None für " + "keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'根据整个数据集中" + "各类别频率的反比调整权重;'balanced_subsample'则对每棵树的自举" + "采样分别执行相同操作。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class RandomForestClassifier( diff --git a/DashAI/back/models/scikit_learn/sgd_classifier.py b/DashAI/back/models/scikit_learn/sgd_classifier.py index 335471f54..f1a445d46 100644 --- a/DashAI/back/models/scikit_learn/sgd_classifier.py +++ b/DashAI/back/models/scikit_learn/sgd_classifier.py @@ -234,6 +234,47 @@ class SGDClassifierSchema(BaseSchema): ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore + class SGDClassifier(TabularClassificationModel, SklearnLikeClassifier, _SGDClassifier): """SGD classifier with probability calibration for consistent predict_proba output. @@ -318,6 +359,7 @@ def train(self, x_train, y_train, x_validation=None, y_validation=None): "tol", "learning_rate", "random_state", + "class_weight", ] if hasattr(self, k) } diff --git a/DashAI/back/models/scikit_learn/svc.py b/DashAI/back/models/scikit_learn/svc.py index 34b08dc15..bba3ecf0b 100644 --- a/DashAI/back/models/scikit_learn/svc.py +++ b/DashAI/back/models/scikit_learn/svc.py @@ -4,6 +4,7 @@ BaseSchema, bool_field, enum_field, + none_type, optimizer_float_field, optimizer_int_field, schema_field, @@ -222,6 +223,46 @@ class SVCSchema(BaseSchema): en="tolerance", es="tolerancia", pt="tolerância", de="Toleranz", zh="容差" ), ) # type: ignore + class_weight: schema_field( + none_type(enum_field(enum=["balanced"])), + placeholder=None, + description=MultilingualString( + en=( + "Weights associated with classes, used to correct for class " + "imbalance. 'balanced' automatically adjusts weights inversely " + "proportional to class frequencies. Use None for no weighting." + ), + es=( + "Pesos asociados a las clases, usados para corregir el desbalance " + "de clases. 'balanced' ajusta automáticamente los pesos de forma " + "inversamente proporcional a la frecuencia de cada clase. Use None " + "para no aplicar ponderación." + ), + pt=( + "Pesos associados às classes, usados para corrigir o " + "desbalanceamento de classes. 'balanced' ajusta automaticamente os " + "pesos de forma inversamente proporcional à frequência de cada " + "classe. Use None para não aplicar ponderação." + ), + de=( + "Gewichte, die den Klassen zugeordnet sind, um " + "Klassenungleichgewichte auszugleichen. 'balanced' passt die " + "Gewichte automatisch umgekehrt proportional zur " + "Klassenhäufigkeit an. Verwenden Sie None für keine Gewichtung." + ), + zh=( + "与类别关联的权重,用于纠正类别不平衡。'balanced'会根据类别频率的" + "反比自动调整权重。使用None表示不加权。" + ), + ), + alias=MultilingualString( + en="Class weight", + es="Peso de clase", + pt="Peso da classe", + de="Klassengewicht", + zh="类别权重", + ), + ) # type: ignore class SVC(TabularClassificationModel, SklearnLikeClassifier, _SVC): diff --git a/DashAI/back/tasks/classification_task.py b/DashAI/back/tasks/classification_task.py index 5dc347d5a..b12c3a17f 100644 --- a/DashAI/back/tasks/classification_task.py +++ b/DashAI/back/tasks/classification_task.py @@ -23,6 +23,7 @@ class ClassificationTask(BaseTask): COMPATIBLE_COMPONENTS = [ "Accuracy", + "BalancedAccuracy", "Precision", "Recall", "F1", diff --git a/tests/back/metrics/test_classification_metrics.py b/tests/back/metrics/test_classification_metrics.py index eec97a1e8..b95f8d964 100644 --- a/tests/back/metrics/test_classification_metrics.py +++ b/tests/back/metrics/test_classification_metrics.py @@ -5,6 +5,7 @@ from datasets import Dataset from DashAI.back.metrics.classification.accuracy import Accuracy +from DashAI.back.metrics.classification.balanced_accuracy import BalancedAccuracy from DashAI.back.metrics.classification.f1 import F1 from DashAI.back.metrics.classification.matthews_corrcoef import MatthewsCorrCoef from DashAI.back.metrics.classification.precision import Precision @@ -28,6 +29,32 @@ def test_accuracy(metric_input: Dict[str, List[int]]): assert score <= 1.0 +def test_balanced_accuracy(metric_input: Dict[str, List[int]]): + score = BalancedAccuracy.score( + metric_input["true_labels"], metric_input["pred_labels"] + ) + + assert isinstance(score, float) + assert score >= 0.0 + assert score <= 1.0 + + +def test_balanced_accuracy_matches_sklearn_reference( + metric_input: Dict[str, List[int]], +): + from sklearn.metrics import balanced_accuracy_score + + true = np.array(metric_input["true_labels"]["foo"]) + pred = np.argmax(metric_input["pred_labels"], axis=1) + expected = balanced_accuracy_score(true, pred) + + score = BalancedAccuracy.score( + metric_input["true_labels"], metric_input["pred_labels"] + ) + + assert score == pytest.approx(expected) + + def test_precision(metric_input: Dict[str, List[int]]): score = Precision.score(metric_input["true_labels"], metric_input["pred_labels"]) @@ -90,6 +117,14 @@ def test_metrics_different_input_sizes(metric_input: Dict[str, List[int]]): ): Accuracy.score(metric_input["true_labels"], metric_input["wrong_size_labels"]) + with pytest.raises( + ValueError, + match=error_pattern, + ): + BalancedAccuracy.score( + metric_input["true_labels"], metric_input["wrong_size_labels"] + ) + with pytest.raises( ValueError, match=error_pattern, From c0608e8f98448e2c55a6f76fb06273ba09509687 Mon Sep 17 00:00:00 2001 From: Creylay Date: Wed, 15 Jul 2026 12:17:00 -0400 Subject: [PATCH 191/308] Refactor ResultsGraphsPlot: implement draggable card functionality for metric panels and heatmap, enhancing user interaction and layout management. --- DashAI/front/package.json | 2 + .../results/components/ResultsGraphsPlot.jsx | 262 +++++++++++++----- DashAI/front/yarn.lock | 15 + 3 files changed, 214 insertions(+), 65 deletions(-) diff --git a/DashAI/front/package.json b/DashAI/front/package.json index 915f700fa..826abe627 100644 --- a/DashAI/front/package.json +++ b/DashAI/front/package.json @@ -4,6 +4,8 @@ "private": true, "dependencies": { "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@mui/icons-material": "^7", diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx index 0bc2d2325..e77358e2c 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx @@ -1,9 +1,27 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import PropTypes from "prop-types"; import { Box, Typography } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import Plot from "react-plotly.js"; import { useTranslation } from "react-i18next"; +import { DragIndicator } from "@mui/icons-material"; +import { + DndContext, + closestCenter, + PointerSensor, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { + SortableContext, + arrayMove, + rectSortingStrategy, + useSortable, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; + +const PANEL_ORDER_STORAGE_KEY = "dashai-results-panel-order"; +const HEATMAP_ID = "__heatmap__"; function EmptyState({ message }) { return ( @@ -25,6 +43,64 @@ function EmptyState({ message }) { ); } +/** + * One draggable card (a metric panel or the heatmap) — the drag handle is a + * small grip icon next to the title, so dragging never conflicts with + * hovering/clicking the plot itself (Plotly needs mouse events for its own + * hover tooltips). + */ +function SortableCard({ id, title, gridColumn, children }) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + opacity: isDragging ? 0.4 : 1, + gridColumn, + }; + + return ( + + + + + + + {title} + + + {children} + + ); +} + function ResultsGraphsPlot({ chartData, onToggleRun }) { const { t } = useTranslation(["models"]); const theme = useTheme(); @@ -37,12 +113,68 @@ function ResultsGraphsPlot({ chartData, onToggleRun }) { const yaxis = chartData.yaxis; const heatmapData = chartData.heatmap ?? []; + const [order, setOrder] = useState(() => { + try { + const saved = localStorage.getItem(PANEL_ORDER_STORAGE_KEY); + return saved ? JSON.parse(saved) : []; + } catch { + return []; + } + }); + + // Every draggable card — one per metric panel, plus the heatmap. + const cardIds = panels + .map((p) => p.metric) + .concat(heatmapData.length > 0 ? [HEATMAP_ID] : []); + + // Keep the stored order in sync with whatever cards are actually being + // shown right now — known cards keep their saved position, newly + // selected ones (or ones seen for the first time) are appended at the end. + const cardIdsKey = cardIds.join("|"); + useEffect(() => { + // Skip while chart data is still loading (cards momentarily empty) — + // reconciling against an empty list would wipe the saved order before + // the real cards ever arrive. + if (cardIds.length === 0) return; + setOrder((prev) => { + const known = prev.filter((id) => cardIds.includes(id)); + const missing = cardIds.filter((id) => !known.includes(id)); + const next = [...known, ...missing]; + const unchanged = + next.length === prev.length && next.every((id, i) => id === prev[i]); + return unchanged ? prev : next; + }); + }, [cardIdsKey]); + + useEffect(() => { + if (order.length > 0) { + localStorage.setItem(PANEL_ORDER_STORAGE_KEY, JSON.stringify(order)); + } + }, [order]); + + const sensors = useSensors( + useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), + ); + + const handleDragEnd = (event) => { + const { active, over } = event; + if (!over || active.id === over.id) return; + setOrder((prev) => { + const oldIndex = prev.indexOf(active.id); + const newIndex = prev.indexOf(over.id); + if (oldIndex === -1 || newIndex === -1) return prev; + return arrayMove(prev, oldIndex, newIndex); + }); + }; + if (panels.length === 0 && heatmapData.length === 0) { return ( ); } + const orderedIds = order.filter((id) => cardIds.includes(id)); + const panelLayout = { autosize: true, height: 240, @@ -115,81 +247,81 @@ function ResultsGraphsPlot({ chartData, onToggleRun }) { )} - - {panels.map((panel) => ( + - - {panel.title} - - - - ))} + {orderedIds.map((id) => { + if (id === HEATMAP_ID) { + return ( + + + + + + ); + } - {/* Heatmap — spans the full grid width since it needs room for - every run × metric cell, but still reflows as one grid item */} - {heatmapData.length > 0 && ( - - - {t("models:label.heatmap")} - - - - + const panel = panels.find((p) => p.metric === id); + if (!panel) return null; + return ( + + + + ); + })} - )} - + + ); } +SortableCard.propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + gridColumn: PropTypes.string, + children: PropTypes.node.isRequired, +}; + +SortableCard.defaultProps = { + gridColumn: undefined, +}; + ResultsGraphsPlot.propTypes = { chartData: PropTypes.object.isRequired, onToggleRun: PropTypes.func.isRequired, diff --git a/DashAI/front/yarn.lock b/DashAI/front/yarn.lock index 132aafbbe..2f851508f 100644 --- a/DashAI/front/yarn.lock +++ b/DashAI/front/yarn.lock @@ -1817,6 +1817,19 @@ __metadata: languageName: node linkType: hard +"@dnd-kit/sortable@npm:^10.0.0": + version: 10.0.0 + resolution: "@dnd-kit/sortable@npm:10.0.0" + dependencies: + "@dnd-kit/utilities": ^3.2.2 + tslib: ^2.0.0 + peerDependencies: + "@dnd-kit/core": ^6.3.0 + react: ">=16.8.0" + checksum: c853cb65d2ffb3d58d400d9f1c993b00413932acf5cf5b780c76acf3b1057aa88e7866021c6b178c4b33fc17db7fe7640584dba4449772e02edcb72cc797eeb0 + languageName: node + linkType: hard + "@dnd-kit/utilities@npm:^3.2.2": version: 3.2.2 resolution: "@dnd-kit/utilities@npm:3.2.2" @@ -7992,6 +8005,8 @@ __metadata: resolution: "dashai-frontend@workspace:." dependencies: "@dnd-kit/core": ^6.3.1 + "@dnd-kit/sortable": ^10.0.0 + "@dnd-kit/utilities": ^3.2.2 "@emotion/react": ^11.10.6 "@emotion/styled": ^11.10.6 "@mui/icons-material": ^7 From 4ce5f5a3ba06c1a0710c3ccd517b3796b080bcb1 Mon Sep 17 00:00:00 2001 From: Creylay Date: Wed, 15 Jul 2026 12:19:55 -0400 Subject: [PATCH 192/308] Refactor SessionVisualization: update handleViewDetails to navigate directly to model details, improving user experience and code clarity. --- .../components/models/SessionVisualization.jsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index 6b116bd98..991a7aba9 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -132,14 +132,13 @@ export default function SessionVisualization() { setTimeout(() => setSelectedRunId(null), 2000); }, []); - const handleViewDetails = React.useCallback((run) => { - if (!run?.id) return; - setSelectedRunId(run.id); - const element = document.getElementById(`run-card-${run.id}`); - if (element) { - element.scrollIntoView({ behavior: "smooth", block: "center" }); - } - }, []); + const handleViewDetails = React.useCallback( + (run) => { + if (!run?.id) return; + navigate(`/app/models/sessions/${session.id}/model/${run.id}`); + }, + [navigate, session?.id], + ); const sortedRuns = React.useMemo( () => [...runs].sort((a, b) => new Date(a.created) - new Date(b.created)), From 9aec352648e993cb2345102c76fc81ff0d5cb3e5 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 12:39:25 -0400 Subject: [PATCH 193/308] feat: split partial dependence into per curve plots with a left selector --- .../explainers/partial_dependence.py | 79 +++++-------------- .../components/explainers/ExplainersPlot.jsx | 6 +- tests/back/explainers/test_explainers.py | 14 ++-- 3 files changed, 34 insertions(+), 65 deletions(-) diff --git a/DashAI/back/explainability/explainers/partial_dependence.py b/DashAI/back/explainability/explainers/partial_dependence.py index bfaf50d91..e7a52d158 100644 --- a/DashAI/back/explainability/explainers/partial_dependence.py +++ b/DashAI/back/explainability/explainers/partial_dependence.py @@ -245,77 +245,40 @@ def explain(self, dataset): return explanation def _create_plot(self, data: List[object]) -> List[Artifact]: - """Helper method to create the explanation plot using plotly. + """Helper method to create the explanation plots using plotly. + + Emits one plotly artifact per feature and class curve, titled by that + curve, so the frontend lists them in its instance selector instead of + a dropdown embedded in a single figure. Parameters ---------- data : List - Per-feature DataFrames with the explanation generated by the - explainer. + Per-feature-and-class DataFrames with the explanation generated by + the explainer. Each DataFrame has the curve values in its first + column and the grid positions in ``"grid_values"``. Returns ------- List[Artifact] - A single-element list with the plotly artifact of the - explanation plot. + One plotly artifact per feature and class curve. """ # Lazy imports import plotly.express as px - fig = px.line( - data[0], - x=data[0]["grid_values"], - y=data[0].iloc[:, 0], - labels={"grid_values": "Feature value"}, - ) - - fig.update_layout( - yaxis_title="Partial Dependence", - updatemenus=[ - { - "x": 0, - "xanchor": "left", - "y": 1.2, - "yanchor": "top", - "buttons": [ - { - "label": data[i].columns[0], - "method": "restyle", - "args": [ - { - "x": [data[i]["grid_values"]], - "y": [data[i].iloc[:, 0]], - }, - ], - } - for i in range(len(data)) - ], - } - ], - ) - - plot_note = ( - "This graph shows the marginal effect of the selected feature " - "on the
probability predicted by the model for the selected " - "class" - ) - - fig.add_annotation( - align="center", - arrowsize=0.3, - arrowwidth=0.1, - borderwidth=2, - font={"size": 12}, - showarrow=False, - text=plot_note, - xanchor="center", - yanchor="bottom", - xref="paper", - yref="paper", - y=-0.35, - ) + artifacts = [] + for df in data: + column_name = df.columns[0] + fig = px.line( + df, + x=df["grid_values"], + y=df[column_name], + labels={"grid_values": "Feature value"}, + ) + fig.update_layout(yaxis_title="Partial Dependence") + artifacts.append(PlotlyArtifact(payload=fig, title=column_name)) - return [PlotlyArtifact(payload=fig, title="Partial Dependence")] + return artifacts def plot(self, explanation: dict) -> List[Artifact]: """Method to create the explanation plot. diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 0c7330333..78e0523d6 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -143,8 +143,10 @@ export default function ExplainersPlot({ Date: Wed, 15 Jul 2026 12:39:31 -0400 Subject: [PATCH 194/308] feat: split permutation importance into per count plots with a left selector --- .../permutation_feature_importance.py | 82 +++++-------------- tests/back/explainers/test_explainers.py | 16 ++-- 2 files changed, 32 insertions(+), 66 deletions(-) diff --git a/DashAI/back/explainability/explainers/permutation_feature_importance.py b/DashAI/back/explainability/explainers/permutation_feature_importance.py index ab5d655c2..11c37c65e 100644 --- a/DashAI/back/explainability/explainers/permutation_feature_importance.py +++ b/DashAI/back/explainability/explainers/permutation_feature_importance.py @@ -517,74 +517,40 @@ def patched_metric(y_true, y_pred_probas): "importances_std": np.round(pfi["importances_std"], 3).tolist(), } - def _create_plot(self, data, n_features: int): - """Build a Plotly horizontal bar chart of feature importances. + def _create_plot(self, data) -> List[Artifact]: + """Build one horizontal bar chart per feature count. + + Each artifact shows the top ``count`` most important features, from all + features down to one, so the frontend lists the counts in its selector + instead of a dropdown embedded in a single figure. Parameters ---------- data : pandas.DataFrame DataFrame with columns ``"features"``, ``"importances_mean"``, and ``"importances_std"``, sorted ascending by importance. - n_features : int - Number of top features (last rows of ``data``) to display in the - default view. A dropdown menu lets users cycle through all counts. Returns ------- List[Artifact] - A single-element list with the plotly artifact of the - explanation plot. + One plotly artifact per feature count, most features first. """ # Lazy imports import plotly.express as px - fig = px.bar( - data.iloc[-n_features:], - x=data.iloc[-n_features:]["importances_mean"], - y=data.iloc[-n_features:]["features"], - error_x=data.iloc[-n_features:]["importances_std"], - ) - - fig.update_layout( - xaxis_title="Importance", - yaxis_title=None, - annotations=[ - { - "text": "", - "showarrow": False, - "x": 0, - "y": 1.15, - "xanchor": "left", - "xref": "paper", - "yref": "paper", - "yanchor": "top", - } - ], - updatemenus=[ - { - "x": 0, - "xanchor": "left", - "y": 1.2, - "yanchor": "top", - "buttons": [ - { - "label": f"N° features: {len(data.iloc[-c:,])}", - "method": "restyle", - "args": [ - { - "x": [data.iloc[-c:]["importances_mean"]], - "y": [data.iloc[-c:]["features"]], - "error_x": [data.iloc[-c:]["importances_std"]], - }, - ], - } - for c in range(len(data)) - ], - } - ], - ) + artifacts = [] + for count in range(len(data), 0, -1): + subset = data.iloc[-count:] + fig = px.bar( + subset, + x=subset["importances_mean"], + y=subset["features"], + error_x=subset["importances_std"], + ) + fig.update_layout(xaxis_title="Importance", yaxis_title=None) + artifacts.append(PlotlyArtifact(payload=fig, title=f"Top {count} features")) - return [PlotlyArtifact(payload=fig, title="Permutation Feature Importance")] + return artifacts def plot(self, explanation: dict) -> List[Artifact]: """Create a Plotly bar chart from a feature importance explanation dict. @@ -598,17 +564,13 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- List[Artifact] - A single-element list with the plotly artifact of the - explanation plot (built by :meth:`_create_plot`). + One plotly artifact per feature count (built by + :meth:`_create_plot`). """ - n_features = 10 # Lazy import import pandas as pd data = pd.DataFrame.from_dict(explanation) data = data.sort_values(by=["importances_mean"], ascending=True) - if n_features > len(data): - n_features = len(data) - - return self._create_plot(data, n_features) + return self._create_plot(data) diff --git a/tests/back/explainers/test_explainers.py b/tests/back/explainers/test_explainers.py index f6ffc977a..9d2a93203 100644 --- a/tests/back/explainers/test_explainers.py +++ b/tests/back/explainers/test_explainers.py @@ -173,11 +173,15 @@ def test_permutation_feature_importance(trained_model: BaseModel, dataset: Datas key in explanation for key in ["features", "importances_mean", "importances_std"] ) - assert len(plot) == 1 - assert isinstance(plot[0], PlotlyArtifact) - artifact_dict = plot[0].to_dict() - assert artifact_dict["type"] == "plotly" - json.loads(artifact_dict["payload"]) + # One plotly artifact per feature count (all features down to one), so the + # frontend lists the counts in its selector instead of an in-figure dropdown. + assert len(plot) == len(INPUT_COLUMNS) + for artifact in plot: + assert isinstance(artifact, PlotlyArtifact) + artifact_dict = artifact.to_dict() + assert artifact_dict["type"] == "plotly" + assert artifact_dict["title"].startswith("Top ") + json.loads(artifact_dict["payload"]) for values in explanation.values(): assert len(values) == len(INPUT_COLUMNS) @@ -196,7 +200,7 @@ def test_permutation_feature_importance(trained_model: BaseModel, dataset: Datas key in explanation for key in ["features", "importances_mean", "importances_std"] ) - assert len(plot) == 1 + assert len(plot) == len(INPUT_COLUMNS) for values in explanation.values(): assert len(values) == len(INPUT_COLUMNS) From 9038c8871141737832b1345d63676fb83ffd21aa Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 12:44:36 -0400 Subject: [PATCH 195/308] feat: adjust flex property for global explainers in ExplainersPlot --- DashAI/front/src/components/explainers/ExplainersPlot.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 78e0523d6..e3a08e62b 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -145,7 +145,7 @@ export default function ExplainersPlot({ sx={{ // Local explainers select a dataset instance (wider table); global // explainers select from a short list of curves (narrower). - flex: isLocal ? "0 0 46%" : "0 0 33%", + flex: isLocal ? "0 0 46%" : "0 0 25%", minWidth: isLocal ? 320 : 220, display: "flex", flexDirection: "column", From 9c72723c50ac462c3626a2af763a68e0f33af75b Mon Sep 17 00:00:00 2001 From: Creylay Date: Wed, 15 Jul 2026 13:12:19 -0400 Subject: [PATCH 196/308] Refactor LiveMetricsChart: replace chart implementation with Plotly for enhanced visualization, update metric selection to use toggle buttons, and improve layout for better user experience. --- .../components/models/LiveMetricsChart.jsx | 242 +++++++++++------- 1 file changed, 146 insertions(+), 96 deletions(-) diff --git a/DashAI/front/src/components/models/LiveMetricsChart.jsx b/DashAI/front/src/components/models/LiveMetricsChart.jsx index 6e91b53de..a07acded2 100644 --- a/DashAI/front/src/components/models/LiveMetricsChart.jsx +++ b/DashAI/front/src/components/models/LiveMetricsChart.jsx @@ -1,30 +1,39 @@ import { Box, - FormControl, - InputLabel, - MenuItem, - Select, - Tabs, - Tab, + ToggleButtonGroup, + ToggleButton, Typography, Button, ButtonGroup, } from "@mui/material"; -import { - LineChart, - Line, - XAxis, - YAxis, - Tooltip, - Legend, - ResponsiveContainer, -} from "recharts"; +import { useTheme } from "@mui/material/styles"; +import Plot from "react-plotly.js"; import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { getModelSessionById } from "../../api/modelSession"; +import ResultsGraphsParameters from "../../pages/results/components/ResultsGraphsParameters"; + +// Same color source as the session results charts (ResultsGraphsPlot / +// graphsMaking) so a metric's line color stays visually consistent with the +// rest of the app. +const getTraceColors = (theme) => [ + theme.palette.primary.main, + theme.palette.secondary.main, + ...(theme.palette.chart?.palette || [ + "#66bb6a", + "#42a5f5", + "#ff9800", + "#ab47bc", + "#ef5350", + "#26a69a", + "#8d6e63", + "#78909c", + ]), +]; export function LiveMetricsChart({ run }) { const { t } = useTranslation("models"); + const theme = useTheme(); const [level, setLevel] = useState(null); const [split, setSplit] = useState("TRAIN"); const [data, setData] = useState({}); @@ -208,35 +217,23 @@ export function LiveMetricsChart({ run }) { ); }, [data, split, level, availableMetrics]); - const chartData = useMemo(() => { - if (Object.keys(filteredMetrics).length === 0) return []; - - const allSteps = new Set(); - for (const metricName in filteredMetrics) { - const metricData = filteredMetrics[metricName]; - if (Array.isArray(metricData)) { - metricData.forEach((point) => { - allSteps.add(point.step); - }); - } - } - - const sortedSteps = Array.from(allSteps).sort((a, b) => a - b); - - return sortedSteps.map((step) => { - const point = { x: step }; - for (const metricName in filteredMetrics) { - const metricData = filteredMetrics[metricName]; - if (Array.isArray(metricData)) { - const dataPoint = metricData.find((p) => p.step === step); - point[metricName] = dataPoint?.value ?? null; - } else { - point[metricName] = null; - } - } - return point; + // One small panel per metric — each keeps its own x/y scale instead of + // sharing a single overlaid axis, same "small multiples" approach used for + // the session results charts. + const panels = useMemo(() => { + const colors = getTraceColors(theme); + return selectedMetrics.map((metricName, idx) => { + const points = (filteredMetrics[metricName] ?? []) + .slice() + .sort((a, b) => a.step - b.step); + return { + metric: metricName, + x: points.map((p) => p.step), + y: points.map((p) => p.value), + color: colors[idx % colors.length], + }; }); - }, [filteredMetrics]); + }, [selectedMetrics, filteredMetrics, theme]); const hasTrialData = data[split]?.TRIAL && Object.keys(data[split].TRIAL).length > 0; @@ -291,48 +288,62 @@ export function LiveMetricsChart({ run }) { } }, [split, level, filteredMetricKeys]); - const handleMetricChange = (e) => { - const newSelection = e.target.value; + const handleToggleMetric = (metric) => { + const canonicalOrder = Object.keys(filteredMetrics); + const newSelection = selectedMetrics.includes(metric) + ? selectedMetrics.filter((m) => m !== metric) + : canonicalOrder.filter( + (m) => m === metric || selectedMetrics.includes(m), + ); + setSelectedMetrics(newSelection); + selectedMetricsPerSplit.current[split] = newSelection; + }; + + const handleSelectAll = () => { + const newSelection = Object.keys(filteredMetrics); setSelectedMetrics(newSelection); selectedMetricsPerSplit.current[split] = newSelection; }; + const handleClearAll = () => { + setSelectedMetrics([]); + selectedMetricsPerSplit.current[split] = []; + }; + const handleLevelChange = (newLevel) => { setLevel(newLevel); }; return ( - - + { + if (newValue !== null) setSplit(newValue); + }} size="small" - sx={{ minWidth: 250 }} - disabled={Object.keys(filteredMetrics).length === 0} > - {t("models:label.metrics")} - - + {t("models:label.train")} + + {t("models:label.validation")} + + {t("models:label.test")} + - setSplit(v)} sx={{ mb: 4 }}> - - - - + + + - {chartData.length === 0 || selectedMetrics.length === 0 ? ( + {panels.length === 0 ? ( ) : ( - - - - - - - - {selectedMetrics.map((metric, idx) => ( - + {panels.map((panel) => ( + + + {panel.metric} + + 1 ? "lines" : "markers", + x: panel.x, + y: panel.y, + line: { + color: panel.color, + width: 2, + shape: "spline", + smoothing: 0.7, + }, + marker: { color: panel.color }, + hovertemplate: "%{x}: %{y:.4f}", + }, + ]} + layout={{ + autosize: true, + height: 240, + margin: { l: 50, r: 12, t: 8, b: 40 }, + showlegend: false, + paper_bgcolor: theme.palette.background.paper, + plot_bgcolor: theme.palette.background.paper, + font: { + color: theme.palette.text.primary, + family: theme.typography.fontFamily, + size: 11, + }, + xaxis: { + title: levelLabel, + gridcolor: theme.palette.divider, + zerolinecolor: theme.palette.divider, + tickfont: { color: theme.palette.text.primary, size: 10 }, + }, + yaxis: { + gridcolor: theme.palette.divider, + tickfont: { color: theme.palette.text.primary, size: 10 }, + automargin: true, + }, + }} + useResizeHandler + style={{ width: "100%", height: "240px" }} + config={{ responsive: true, displayModeBar: false }} /> - ))} - - + + ))} + )} From d391934da99f1a179f31cdbb61b305fda1959cc8 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 14:39:55 -0400 Subject: [PATCH 197/308] feat: update artifact grouping logic and enhance ArtifactViewer with sibling data --- DashAI/front/src/components/explainers/ExplainersPlot.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index e3a08e62b..7c5af9ddf 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -18,7 +18,7 @@ function parseExplanationArtifacts(items) { } /** - * Group consecutive artifacts sharing the same non-null title into one + * Group consecutive artifacts sharing the same non null title into one * instance group, tracking each artifact's flat index in the endpoint * response so edits can target it. */ @@ -112,10 +112,12 @@ export default function ExplainersPlot({ gap: 3, }} > - {group.artifacts.map((artifact) => ( + {group.artifacts.map((artifact, i) => ( Date: Wed, 15 Jul 2026 14:40:09 -0400 Subject: [PATCH 198/308] feat: add sibling navigation and fullscreen enhancements to ArtifactViewer --- .../src/components/shared/ArtifactViewer.jsx | 151 ++++++++++++++---- 1 file changed, 116 insertions(+), 35 deletions(-) diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx index 3adae65d0..bc66bd91d 100644 --- a/DashAI/front/src/components/shared/ArtifactViewer.jsx +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -15,6 +15,8 @@ import FullscreenIcon from "@mui/icons-material/Fullscreen"; import SaveIcon from "@mui/icons-material/Save"; import RestartAltIcon from "@mui/icons-material/RestartAlt"; import CloseIcon from "@mui/icons-material/Close"; +import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; import { useTheme, alpha } from "@mui/material/styles"; import Plot from "react-plotly.js"; import { useTranslation } from "react-i18next"; @@ -24,28 +26,48 @@ import { applyThemeToLayout } from "../../utils/plotlyTheme"; import { downloadArtifact } from "../../utils/downloadArtifact"; /** - * Renders one typed artifact as a self-contained bordered block. The actions + * Renders one typed artifact as a self contained bordered block. The actions * that apply to that artifact (download, plot editing, fullscreen) live in a - * compact cluster docked to the block's top-right corner, revealed on hover + * compact cluster docked to the block's top right corner, revealed on hover * or keyboard focus so the resting card stays uncluttered. Plot editing is * offered only for plotly artifacts; edits persist when onSaveEdit is given, - * otherwise they are client-side only. + * otherwise they are client side only. */ export default function ArtifactViewer({ artifact, onSaveEdit = null, onResetEdit = null, canReset = false, + siblingArtifacts = null, + siblingIndex = 0, }) { const theme = useTheme(); const { t } = useTranslation(["explainers", "common"]); const [downloadAnchor, setDownloadAnchor] = useState(null); const [editing, setEditing] = useState(false); const [fullscreen, setFullscreen] = useState(false); + const [fullscreenIndex, setFullscreenIndex] = useState(siblingIndex); + const hasSiblings = siblingArtifacts && siblingArtifacts.length > 1; + const fullscreenArtifact = hasSiblings + ? siblingArtifacts[fullscreenIndex] + : artifact; + + const openFullscreen = () => { + setFullscreenIndex(siblingIndex); + setFullscreen(true); + }; + + const stepFullscreen = (delta) => { + if (!hasSiblings) return; + setFullscreenIndex( + (fullscreenIndex + delta + siblingArtifacts.length) % + siblingArtifacts.length, + ); + }; // editInitial is the figure the editable Plot mounts with (set once per edit // session). editFigureRef holds the latest edited figure, captured in // onUpdate WITHOUT setState so Plotly's own edit events do not trigger a - // React re-render that would re-run Plotly.react and loop the page into a + // React re render that would re run Plotly.react and loop the page into a // freeze. const [editInitial, setEditInitial] = useState(null); const editFigureRef = useRef(null); @@ -102,6 +124,30 @@ export default function ArtifactViewer({ "&:hover": { color: "text.primary" }, }; + // Circular "glass" buttons for the fullscreen lightbox: translucent white + // against the dark blurred overlay, regardless of the app's light/dark + // theme (the overlay itself is always near black). + const lightboxButtonSx = { + position: "absolute", + top: 16, + right: 16, + zIndex: 1, + width: 36, + height: 36, + color: "#fff", + bgcolor: "rgba(255, 255, 255, 0.12)", + border: "1px solid rgba(255, 255, 255, 0.2)", + "&:hover": { bgcolor: "rgba(255, 255, 255, 0.25)" }, + }; + + const lightboxArrowSx = { + top: "50%", + right: "auto", + transform: "translateY(-50%)", + width: 44, + height: 44, + }; + // Fill most of the viewport in the fullscreen view, leaving room for the // header bar and padding. const fullscreenHeight = @@ -177,11 +223,7 @@ export default function ArtifactViewer({ )} - setFullscreen(true)} - > + @@ -211,12 +253,12 @@ export default function ArtifactViewer({ {/* The instance label is shown once by the parent; suppress the - per-artifact title so it is not repeated on every block. */} + per artifact title so it is not repeated on every block. */} {/* Edit dialog: editable plotly figure. The Plot mounts once with editInitial and reports edits through onUpdate into a ref; we never - feed those edits back as props, so Plotly does not re-render in a + feed those edits back as props, so Plotly does not re render in a loop. */} @@ -245,47 +287,84 @@ export default function ArtifactViewer({ )} - {/* Fullscreen view */} + {/* Fullscreen view: dark blurred lightbox overlay, rounded/shadowed + content card, and circular glass buttons for close + prev/next. */} setFullscreen(false)} - PaperProps={{ sx: { bgcolor: "background.default" } }} + transitionDuration={0} + keepMounted + PaperProps={{ + elevation: 0, + sx: { + bgcolor: "rgba(0, 0, 0, 0.15)", + backgroundImage: "none", + backdropFilter: "blur(6px)", + willChange: "backdrop-filter", + boxShadow: "none", + }, + }} > { + if (e.target === e.currentTarget) setFullscreen(false); }} - > - - {artifact.title || ""} - - setFullscreen(false)} sx={actionButtonSx}> - - - - - + setFullscreen(false)} + aria-label={t("explainers:button.close", { + defaultValue: "Close", + })} + sx={lightboxButtonSx} + > + + + + {hasSiblings && ( + stepFullscreen(-1)} + aria-label="previous" + sx={{ ...lightboxButtonSx, ...lightboxArrowSx, left: 20 }} + > + + + )} + + + + {hasSiblings && ( + stepFullscreen(1)} + aria-label="next" + sx={{ ...lightboxButtonSx, ...lightboxArrowSx, right: 20 }} + > + + + )} @@ -302,4 +381,6 @@ ArtifactViewer.propTypes = { onSaveEdit: PropTypes.func, onResetEdit: PropTypes.func, canReset: PropTypes.bool, + siblingArtifacts: PropTypes.array, + siblingIndex: PropTypes.number, }; From ac089e06ae1a54c8b45f01f353d4d636361254d1 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 14:44:17 -0400 Subject: [PATCH 199/308] fix: increase z-index for action buttons in ArtifactViewer --- DashAI/front/src/components/shared/ArtifactViewer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx index bc66bd91d..31a8d03b9 100644 --- a/DashAI/front/src/components/shared/ArtifactViewer.jsx +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -185,7 +185,7 @@ export default function ArtifactViewer({ position: "absolute", top: 6, right: 6, - zIndex: 2, + zIndex: 3, display: "flex", gap: 0.5, p: 0.5, From 6ab917c7ccc98e7be75804a35878ef1fce41ae1d Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 14:47:00 -0400 Subject: [PATCH 200/308] feat: add Divider component to ExplainerInstanceTable --- .../front/src/components/explainers/ExplainerInstanceTable.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx index ea2a726ff..8ab89c2a2 100644 --- a/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx +++ b/DashAI/front/src/components/explainers/ExplainerInstanceTable.jsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import PropTypes from "prop-types"; -import { Box, TablePagination } from "@mui/material"; +import { Box, Divider, TablePagination } from "@mui/material"; import { useTheme } from "@mui/material/styles"; import { getDatasetFile } from "../../api/datasets"; @@ -84,6 +84,7 @@ export default function ExplainerInstanceTable({
+ Date: Wed, 15 Jul 2026 15:15:36 -0400 Subject: [PATCH 201/308] Refactor components: enhance LiveMetricsChart, ModelDetailView, RunCard, RunResults, and SessionVisualization to support profile selection, improving user interaction and data visualization. --- .../components/models/LiveMetricsChart.jsx | 185 +++++++++++++++++- .../src/components/models/ModelDetailView.jsx | 9 + .../front/src/components/models/RunCard.jsx | 9 + .../src/components/models/RunResults.jsx | 14 +- .../models/SessionVisualization.jsx | 3 + 5 files changed, 218 insertions(+), 2 deletions(-) diff --git a/DashAI/front/src/components/models/LiveMetricsChart.jsx b/DashAI/front/src/components/models/LiveMetricsChart.jsx index a07acded2..160b9a71a 100644 --- a/DashAI/front/src/components/models/LiveMetricsChart.jsx +++ b/DashAI/front/src/components/models/LiveMetricsChart.jsx @@ -1,7 +1,11 @@ import { Box, + Divider, + MenuItem, + Select, ToggleButtonGroup, ToggleButton, + Tooltip, Typography, Button, ButtonGroup, @@ -12,6 +16,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { getModelSessionById } from "../../api/modelSession"; import ResultsGraphsParameters from "../../pages/results/components/ResultsGraphsParameters"; +import api from "../../api/api"; // Same color source as the session results charts (ResultsGraphsPlot / // graphsMaking) so a metric's line color stays visually consistent with the @@ -31,7 +36,21 @@ const getTraceColors = (theme) => [ ]), ]; -export function LiveMetricsChart({ run }) { +function toFinalValue(value) { + const resolved = Array.isArray(value) + ? (value[value.length - 1]?.value ?? null) + : value; + const num = Number(resolved); + return Number.isNaN(num) ? null : num; +} + +export function LiveMetricsChart({ + run, + session, + profiles, + selectedProfile, + onProfileChange, +}) { const { t } = useTranslation("models"); const theme = useTheme(); const [level, setLevel] = useState(null); @@ -217,6 +236,48 @@ export function LiveMetricsChart({ run }) { ); }, [data, split, level, availableMetrics]); + // Compact final-value summary for whichever split is selected — same + // numbers/score shown in the session's comparison table, scoped to the + // split currently picked here instead of a fixed one. + const summaryMetrics = useMemo(() => { + const rawMetrics = run[`${split.toLowerCase()}_metrics`] ?? {}; + return Object.entries(rawMetrics) + .map(([name, value]) => [name, toFinalValue(value)]) + .filter(([, value]) => value !== null); + }, [run, split]); + + const [runScore, setRunScore] = useState(null); + + useEffect(() => { + if (!selectedProfile || !session?.id) { + setRunScore(null); + return; + } + + let cancelled = false; + api + .get("/v1/run/", { + params: { + model_session_id: session.id, + include_scores: true, + profile_id: selectedProfile, + metric_split: split.toLowerCase(), + }, + }) + .then((response) => { + if (cancelled) return; + const match = response.data.find((r) => r.id === run.id); + setRunScore(match?.score ?? null); + }) + .catch((error) => { + console.error("Error fetching run score:", error); + }); + + return () => { + cancelled = true; + }; + }, [selectedProfile, session?.id, split, run.id]); + // One small panel per metric — each keeps its own x/y scale instead of // sharing a single overlaid axis, same "small multiples" approach used for // the session results charts. @@ -333,6 +394,128 @@ export function LiveMetricsChart({ run }) {
+ {summaryMetrics.length > 0 && ( + + {profiles && profiles.length > 0 && ( + <> + + + {t("models:label.scoreProfile")}: + + + + + + )} + + {runScore && ( + <> + + + {t("models:label.score")} + + + {runScore.breakdown.map( + ({ metric_name, value, normalized_weight }, i) => ( + + {i === 0 ? "=" : "+"} {metric_name} ( + {value.toFixed(4)}) ×{" "} + {(normalized_weight * 100).toFixed(0)}% + + ), + )} +
+ } + placement="top" + arrow + > + + + ★ + + + {runScore.score.toFixed(1)} + + + +
+ + + )} + + {summaryMetrics.map(([name, value]) => ( + + + {name} + + + {value.toFixed(4)} + + + ))} +
+ )} +
); @@ -232,4 +238,7 @@ ModelDetailView.propTypes = { onOperationsRefresh: PropTypes.func, existingRuns: PropTypes.array, onRefresh: PropTypes.func, + profiles: PropTypes.array, + selectedProfile: PropTypes.string, + onProfileChange: PropTypes.func, }; diff --git a/DashAI/front/src/components/models/RunCard.jsx b/DashAI/front/src/components/models/RunCard.jsx index 9d143a7ec..b45257794 100644 --- a/DashAI/front/src/components/models/RunCard.jsx +++ b/DashAI/front/src/components/models/RunCard.jsx @@ -48,6 +48,9 @@ function RunCard({ setIsEditing: setControlledIsEditing = undefined, deleteConfirmOpen: controlledDeleteConfirmOpen = undefined, setDeleteConfirmOpen: setControlledDeleteConfirmOpen = undefined, + profiles, + selectedProfile, + onProfileChange, }) { const theme = useTheme(); const { t } = useTranslation(["models", "common"]); @@ -350,6 +353,9 @@ function RunCard({ resultsVisible={isResultsVisible} setResultsVisible={setResultsVisible} autoExpand={autoExpand} + profiles={profiles} + selectedProfile={selectedProfile} + onProfileChange={onProfileChange} />
- +
)} @@ -974,4 +983,7 @@ RunResults.propTypes = { resultsVisible: PropTypes.bool, setResultsVisible: PropTypes.func, autoExpand: PropTypes.bool, + profiles: PropTypes.array, + selectedProfile: PropTypes.string, + onProfileChange: PropTypes.func, }; diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index 991a7aba9..d598ee165 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -303,6 +303,9 @@ export default function SessionVisualization() { } existingRuns={runs} onRefresh={fetchRuns} + profiles={profiles} + selectedProfile={selectedProfile} + onProfileChange={setSelectedProfile} /> ) : ( From 5ddd33ba4ee74e178feca1be9d42b8951216e68f Mon Sep 17 00:00:00 2001 From: Irozuku Date: Wed, 15 Jul 2026 15:22:23 -0400 Subject: [PATCH 202/308] refactor: move table artifact into a separated component --- .../components/shared/ArtifactRenderer.jsx | 56 ++------- .../src/components/shared/TableArtifact.jsx | 109 ++++++++++++++++++ 2 files changed, 118 insertions(+), 47 deletions(-) create mode 100644 DashAI/front/src/components/shared/TableArtifact.jsx diff --git a/DashAI/front/src/components/shared/ArtifactRenderer.jsx b/DashAI/front/src/components/shared/ArtifactRenderer.jsx index 525411456..9580c7c5e 100644 --- a/DashAI/front/src/components/shared/ArtifactRenderer.jsx +++ b/DashAI/front/src/components/shared/ArtifactRenderer.jsx @@ -1,21 +1,12 @@ import React, { useMemo } from "react"; -import { - Box, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Typography, -} from "@mui/material"; -import { useTheme, alpha } from "@mui/material/styles"; +import { Box, Typography } from "@mui/material"; +import { useTheme } from "@mui/material/styles"; import Plot from "react-plotly.js"; import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import { applyThemeToLayout } from "../../utils/plotlyTheme"; +import TableArtifact from "./TableArtifact"; /** * Renders a single typed artifact ({type, payload, title}) returned by the @@ -69,41 +60,12 @@ export default function ArtifactRenderer({ artifact, height = 380 }) { case "table": { const { columns = [], rows = [] } = artifact.payload ?? {}; return ( - - - - - {columns.map((column) => ( - {column} - ))} - - - - {rows.map((row, rowIndex) => ( - - {row.map((value, columnIndex) => ( - - {value === null ? "-" : String(value)} - - ))} - - ))} - -
-
+ ); } case "image": { diff --git a/DashAI/front/src/components/shared/TableArtifact.jsx b/DashAI/front/src/components/shared/TableArtifact.jsx new file mode 100644 index 000000000..5331e2d99 --- /dev/null +++ b/DashAI/front/src/components/shared/TableArtifact.jsx @@ -0,0 +1,109 @@ +import { useEffect, useState } from "react"; +import PropTypes from "prop-types"; +import { Box, TablePagination } from "@mui/material"; +import { useTheme, alpha } from "@mui/material/styles"; + +import "./leanDatasetTable/leanDatasetTable.css"; + +const TABLE_ROWS_PER_PAGE = 5; + +/** + * Table artifact content: a sticky-header table over a fixed-height, + * independently scrolling body with client-side pagination pinned below it. + * Reuses LeanDatasetTable's markup/CSS classes so it reads as the same + * table style, without pulling in that component's dataset-specific + * features (filtering, sorting, column visibility). + */ +export default function TableArtifact({ + columns, + rows, + highlightedCells, + height, +}) { + const theme = useTheme(); + const [page, setPage] = useState(0); + + // Reset to the first page whenever the underlying data changes (e.g. + // navigating to a sibling artifact in the fullscreen lightbox). + useEffect(() => setPage(0), [rows]); + + const pageStart = page * TABLE_ROWS_PER_PAGE; + const pageRows = rows.slice(pageStart, pageStart + TABLE_ROWS_PER_PAGE); + + return ( + +
+ + + + {columns.map((column) => ( + + ))} + + + + {pageRows.map((row, i) => { + const rowIndex = pageStart + i; + return ( + + {row.map((value, columnIndex) => ( + + ))} + + ); + })} + +
+ {column} +
+ {value === null ? "-" : String(value)} +
+
+ setPage(p)} + /> +
+ ); +} + +TableArtifact.propTypes = { + columns: PropTypes.arrayOf(PropTypes.string).isRequired, + rows: PropTypes.array.isRequired, + highlightedCells: PropTypes.instanceOf(Set).isRequired, + height: PropTypes.number.isRequired, +}; From 1ed291d3596d27bb5e3ccdbe2bda7cc9921e26de Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 09:05:06 -0400 Subject: [PATCH 203/308] feat: add support for grouped artifacts Introduce `ArtifactGroup` and `GroupedArtifacts` models to allow components to return multiple sets of artifacts organized into selectable groups. This enables the frontend to render interactive selectors for switching between different batches of artifacts (e.g., different views or curves) within a single component output. Updated `normalize_artifacts` to handle these new types and maintain compatibility with legacy artifact shapes. --- DashAI/back/core/artifacts.py | 139 ++++++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 16 deletions(-) diff --git a/DashAI/back/core/artifacts.py b/DashAI/back/core/artifacts.py index c1dd9d231..4d8785f3e 100644 --- a/DashAI/back/core/artifacts.py +++ b/DashAI/back/core/artifacts.py @@ -14,6 +14,14 @@ - ``"text"``: payload is a plain string rendered as preformatted text. - ``"image"``: payload is ``{"data": , "mime": }``. +A component may also return a :class:`GroupedArtifacts` alongside plain +artifacts. It bundles several :class:`ArtifactGroup` entries (each a titled +batch of leaf artifacts, e.g. a summary table next to its plot) into one +interactive selector: the frontend lists every group's title and shows one +group's artifacts at a time. It serializes to ``{"type": "grouped", "title": +, "groups": [{"title": , "artifacts": [, ...]}, ...]}``; a group cannot itself contain another group. + Components created before this module returned other shapes: explainers returned lists of plotly JSON strings, explorers returned a single ``{"data", "type", "config"}`` dict. :func:`normalize_artifacts` upgrades @@ -280,6 +288,67 @@ def from_dashai_image( _ANY_ARTIFACT_ADAPTER: TypeAdapter = TypeAdapter(AnyArtifact) +class ArtifactGroup(BaseModel): + """One selectable entry inside a :class:`GroupedArtifacts`. + + A group is a titled batch of leaf artifacts (e.g. a summary table next to + its plot) shown together when its entry is selected. It cannot itself + contain another group: ``artifacts`` is typed as leaf :data:`AnyArtifact` + only. + + Attributes + ---------- + title : Optional[str] + Human readable label for this entry, shown as one row in the parent + group's selector. + artifacts : List[AnyArtifact] + The leaf artifacts shown when this entry is selected, in display + order. + """ + + title: Optional[str] = None + artifacts: List[AnyArtifact] + + +class GroupedArtifacts(BaseModel): + """A selector over several :class:`ArtifactGroup` entries. + + Lets a component (typically a global explainer producing one set of + artifacts per curve/count) return a single interactive unit: the frontend + renders a selector listing every group's title and shows one group's + artifacts at a time. A component may return several ``GroupedArtifacts`` + in its ``plot`` output; each becomes its own independent selector. + + Attributes + ---------- + title : Optional[str] + Optional overall title for the selector. + groups : List[ArtifactGroup] + The selectable groups, in listing order. + """ + + type: Literal["grouped"] = "grouped" + title: Optional[str] = None + groups: List[ArtifactGroup] + + def to_dict(self) -> Dict[str, Any]: + """Serialize the grouped artifacts to their wire format. + + Returns + ------- + Dict[str, Any] + ``{"type": "grouped", "title", "groups"}`` with each group's + artifacts serialized to their own wire format dicts. + """ + return self.model_dump() + + +AnyArtifactOrGroup = Annotated[ + Union[PlotlyArtifact, TableArtifact, TextArtifact, ImageArtifact, GroupedArtifacts], + Field(discriminator="type"), +] + + def _legacy_explorer_artifact(item: Dict[str, Any]) -> Dict[str, Any]: """Convert a legacy explorer result dict into an artifact dict. @@ -316,13 +385,18 @@ def _legacy_explorer_artifact(item: Dict[str, Any]) -> Dict[str, Any]: def normalize_artifacts(items: Any) -> List[Dict[str, Any]]: - """Coerce any component output into a list of artifact wire dicts. + """Coerce any component output into a list of artifact/group wire dicts. + + Handles current values (``Artifact`` or :class:`GroupedArtifacts` + instances, or their wire dicts) and legacy shapes: plain plotly JSON + strings from old explainers, and ``{"data", "type", "config"}`` dicts + from old explorers. Anything else is stringified into a text artifact so + the frontend never receives an unrenderable value. - Handles current values (``Artifact`` instances or artifact dicts) and - legacy shapes: plain plotly JSON strings from old explainers, and - ``{"data", "type", "config"}`` dicts from old explorers. Anything else - is stringified into a text artifact so the frontend never receives an - unrenderable value. + Every leaf artifact, whether at the top level or nested inside a + group's ``artifacts``, is stamped with a flat, sequential ``index`` so + the frontend (and the plot override endpoints) can address any leaf by a + single integer regardless of nesting depth. Parameters ---------- @@ -333,26 +407,59 @@ def normalize_artifacts(items: Any) -> List[Dict[str, Any]]: Returns ------- List[Dict[str, Any]] - A list of artifact dicts in wire format. + A list of artifact/grouped wire dicts; leaf artifacts carry an + ``"index"`` key. A grouped dict is + ``{"type": "grouped", "title", "groups": [{"title", "artifacts"}]}``. """ if items is None: return [] - if isinstance(items, (str, dict, Artifact)): + if isinstance(items, (str, dict, Artifact, GroupedArtifacts)): items = [items] - artifacts: List[Dict[str, Any]] = [] - for item in items: + next_index = 0 + + def normalize_leaf(item: Any) -> Dict[str, Any]: + nonlocal next_index if isinstance(item, Artifact): - artifacts.append(item.to_dict()) + data = item.to_dict() elif isinstance(item, str): - artifacts.append(PlotlyArtifact(payload=item).to_dict()) + data = PlotlyArtifact(payload=item).to_dict() elif isinstance(item, dict) and "type" in item and "payload" in item: - artifacts.append({"title": None, "role": "explanation", **item}) + data = {"title": None, "role": "explanation", **item} elif isinstance(item, dict) and "type" in item and "data" in item: - artifacts.append(_legacy_explorer_artifact(item)) + data = _legacy_explorer_artifact(item) + else: + data = TextArtifact(payload=str(item)).to_dict() + data["index"] = next_index + next_index += 1 + return data + + def normalize_group(group: Any) -> Dict[str, Any]: + if isinstance(group, ArtifactGroup): + title, artifacts = group.title, group.artifacts else: - artifacts.append(TextArtifact(payload=str(item)).to_dict()) - return artifacts + title, artifacts = group.get("title"), group.get("artifacts", []) + return { + "title": title, + "artifacts": [normalize_leaf(a) for a in artifacts], + } + + def normalize_item(item: Any) -> Dict[str, Any]: + if isinstance(item, GroupedArtifacts): + return { + "type": "grouped", + "title": item.title, + "groups": [normalize_group(g) for g in item.groups], + } + if isinstance(item, dict) and item.get("type") == "grouped": + return { + "type": "grouped", + "title": item.get("title"), + "groups": [normalize_group(g) for g in item.get("groups", [])], + } + return normalize_leaf(item) + + return [normalize_item(item) for item in items] def build_tabular_input_artifact( From f379b5184272fdf463cbd833e9d620c1aebddc65 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 09:06:08 -0400 Subject: [PATCH 204/308] feat: update explainers to use GroupedArtifacts for better organization - Updated all explainer classes (ContrastiveShap, DiceCounterfactual, GradCam, etc.) to return GroupedArtifacts instead of individual Artifact lists in their plot methods. - Modified the frontend component (ExplainersPlot) to handle the new GroupedArtifacts structure, allowing for improved rendering of explanations with selectors for grouped instances. - Adjusted type hints in the base explainer classes to accommodate the new return types. --- .../back/api/api_v1/endpoints/explainers.py | 27 +- .../explainers/contrastive_shap.py | 24 +- .../explainers/dice_counterfactual.py | 27 +- .../explainability/explainers/grad_cam.py | 40 +-- .../explainability/explainers/kernel_shap.py | 23 +- .../explainability/explainers/lime_text.py | 39 +-- .../explainers/nearest_counterfactual.py | 27 +- .../explainers/occlusion_saliency.py | 40 +-- .../explainers/partial_dependence.py | 37 ++- .../permutation_feature_importance.py | 39 ++- .../explainers/regression_kernel_shap.py | 24 +- .../regression_partial_dependence.py | 23 +- .../explainers/token_ablation.py | 24 +- .../back/explainability/global_explainer.py | 12 +- DashAI/back/explainability/local_explainer.py | 21 +- .../components/explainers/ExplainersPlot.jsx | 259 +++++++++++------- 16 files changed, 413 insertions(+), 273 deletions(-) diff --git a/DashAI/back/api/api_v1/endpoints/explainers.py b/DashAI/back/api/api_v1/endpoints/explainers.py index 5930435b5..b9e7d7f17 100755 --- a/DashAI/back/api/api_v1/endpoints/explainers.py +++ b/DashAI/back/api/api_v1/endpoints/explainers.py @@ -33,10 +33,16 @@ def _apply_overrides(artifacts: list, overrides: dict | None) -> list: """Replace plotly artifact payloads with stored edited figures. + Leaves nested inside a ``"grouped"`` selector (see + :class:`DashAI.back.core.artifacts.GroupedArtifacts`), i.e. under each + group's ``artifacts``, are matched by their stamped ``"index"`` just + like top level ones, so a group's plotly artifact can be edited/reset the + same way as a top level one. + Parameters ---------- artifacts : list - Normalized artifact dicts from ``normalize_artifacts``. + Normalized artifact/grouped dicts from ``normalize_artifacts``. overrides : dict or None Mapping of ``str(index)`` to an edited plotly figure (JSON string). @@ -49,15 +55,26 @@ def _apply_overrides(artifacts: list, overrides: dict | None) -> list: return artifacts import json + leaves_by_index = {} + + def collect_leaves(items): + for item in items: + if item.get("type") == "grouped": + for group in item.get("groups", []): + collect_leaves(group.get("artifacts", [])) + else: + leaves_by_index[item.get("index")] = item + + collect_leaves(artifacts) + for key, figure in overrides.items(): try: idx = int(key) except (TypeError, ValueError): continue - if 0 <= idx < len(artifacts) and artifacts[idx].get("type") == "plotly": - artifacts[idx]["payload"] = ( - figure if isinstance(figure, str) else json.dumps(figure) - ) + leaf = leaves_by_index.get(idx) + if leaf is not None and leaf.get("type") == "plotly": + leaf["payload"] = figure if isinstance(figure, str) else json.dumps(figure) return artifacts diff --git a/DashAI/back/explainability/explainers/contrastive_shap.py b/DashAI/back/explainability/explainers/contrastive_shap.py index 4b2ef9f7f..33154dd7f 100644 --- a/DashAI/back/explainability/explainers/contrastive_shap.py +++ b/DashAI/back/explainability/explainers/contrastive_shap.py @@ -1,6 +1,11 @@ from typing import List -from DashAI.back.core.artifacts import Artifact, PlotlyArtifact, TextArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, + TextArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, bool_field, @@ -388,7 +393,7 @@ def _create_plot(self, data, fact_name, foil_name, fact_prob, foil_prob): return fig - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Render each instance as a contrastive bar plot plus a text summary. Parameters @@ -398,9 +403,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of typed artifacts: one plotly and one text artifact per - explained instance. + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's contrastive plot and text summary. """ import numpy as np import pandas as pd @@ -411,7 +416,7 @@ def plot(self, explanation: dict) -> List[Artifact]: target_names = metadata["target_names"] max_features = 8 - artifacts = [] + groups = [] for i in exp: instance = exp[i] fact_class = instance["fact_class"] @@ -437,7 +442,7 @@ def plot(self, explanation: dict) -> List[Artifact]: title = f"Instance {int(i) + 1}" fig = self._create_plot(data, fact_name, foil_name, fact_prob, foil_prob) - artifacts.append(PlotlyArtifact(payload=fig, title=title)) + plot = PlotlyArtifact(payload=fig) top = data.iloc[::-1].head(3) top_features = ", ".join( @@ -453,6 +458,7 @@ def plot(self, explanation: dict) -> List[Artifact]: f"{foil_name} (p={foil_prob}) mainly because of: " f"{top_features}." ) - artifacts.append(TextArtifact(payload=summary, title=title)) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/dice_counterfactual.py b/DashAI/back/explainability/explainers/dice_counterfactual.py index d7fc87712..eea0e866a 100644 --- a/DashAI/back/explainability/explainers/dice_counterfactual.py +++ b/DashAI/back/explainability/explainers/dice_counterfactual.py @@ -1,7 +1,8 @@ from typing import List from DashAI.back.core.artifacts import ( - Artifact, + ArtifactGroup, + GroupedArtifacts, TableArtifact, TablePayload, TextArtifact, @@ -369,7 +370,7 @@ def explain_instance(self, instances): return explanation - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Render each instance as a comparison table plus a text summary. Parameters @@ -379,9 +380,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of typed artifacts: one table and one text artifact per - explained instance. + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's comparison table and text summary. """ import numpy as np @@ -390,7 +391,7 @@ def plot(self, explanation: dict) -> List[Artifact]: feature_names = metadata["feature_names"] target_names = metadata["target_names"] - artifacts = [] + groups = [] for i in exp: instance = exp[i] predicted_class = instance["predicted_class"] @@ -422,13 +423,8 @@ def plot(self, explanation: dict) -> List[Artifact]: highlight.append({"row": len(feature_names), "column": 2 + cf_idx}) title = f"Instance {int(i) + 1}" - artifacts.append( - TableArtifact( - payload=TablePayload( - columns=columns, rows=rows, highlight=highlight - ), - title=title, - ) + table = TableArtifact( + payload=TablePayload(columns=columns, rows=rows, highlight=highlight), ) if counterfactuals: @@ -447,6 +443,7 @@ def plot(self, explanation: dict) -> List[Artifact]: f"(p={predicted_prob}). DiCE could not generate " "counterfactuals for this instance." ) - artifacts.append(TextArtifact(payload=summary, title=title)) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[table, text])) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/grad_cam.py b/DashAI/back/explainability/explainers/grad_cam.py index 616c6a513..d22b95a30 100644 --- a/DashAI/back/explainability/explainers/grad_cam.py +++ b/DashAI/back/explainability/explainers/grad_cam.py @@ -1,6 +1,10 @@ from typing import List -from DashAI.back.core.artifacts import Artifact, TextArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + TextArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, enum_field, @@ -239,7 +243,7 @@ def explain_instance(self, instances): return explanation - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Render each image as a heatmap overlay plus a text summary. Parameters @@ -249,9 +253,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of typed artifacts: one plotly overlay and one text - artifact per explained image. + List[GroupedArtifacts] + A single grouped artifact with one group per explained image, each + holding that image's heatmap overlay and text summary. """ import numpy as np @@ -259,7 +263,7 @@ def plot(self, explanation: dict) -> List[Artifact]: metadata = exp.pop("metadata") target_names = metadata["target_names"] - artifacts = [] + groups = [] for i in exp: instance = exp[i] predicted_class = instance["predicted_class"] @@ -273,20 +277,16 @@ def plot(self, explanation: dict) -> List[Artifact]: f"{self.method}: regions supporting {predicted_name} " f"(p={predicted_prob})" ) - artifacts.append( - heatmap_overlay_artifact( - instance["image"], instance["heatmap"], title, subtitle - ) + overlay = heatmap_overlay_artifact( + instance["image"], instance["heatmap"], title, subtitle ) - artifacts.append( - TextArtifact( - payload=( - f"The model predicted {predicted_name} " - f"(p={predicted_prob}). Highlighted regions are the " - "areas whose activations most supported this class." - ), - title=title, - ) + text = TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Highlighted regions are the " + "areas whose activations most supported this class." + ), ) + groups.append(ArtifactGroup(title=title, artifacts=[overlay, text])) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/kernel_shap.py b/DashAI/back/explainability/explainers/kernel_shap.py index b3b7587b8..733728c3b 100644 --- a/DashAI/back/explainability/explainers/kernel_shap.py +++ b/DashAI/back/explainability/explainers/kernel_shap.py @@ -1,6 +1,10 @@ from typing import List, Optional -from DashAI.back.core.artifacts import Artifact, PlotlyArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, bool_field, @@ -520,7 +524,7 @@ def _create_plot( return PlotlyArtifact(payload=fig, title=title) - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Method to create the explanation plots using plotly. Parameters @@ -530,9 +534,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list with one plotly artifact per explained instance; the - artifact titles ("Instance 1", ...) identify each instance. + List[GroupedArtifacts] + A single grouped artifact with one group ("Instance 1", ...) per + explained instance, each holding that instance's plotly plot. """ exp = explanation.copy() @@ -550,7 +554,7 @@ def plot(self, explanation: dict) -> List[Artifact]: feats = np.asarray(feature_names, dtype=str).reshape(-1) - plots = [] + groups = [] for instance_number, i in enumerate(exp, start=1): instance_values = exp[i]["instance_values"] model_prediction = exp[i]["model_prediction"] @@ -656,8 +660,9 @@ def plot(self, explanation: dict) -> List[Artifact]: base_value, y_pred_pbb, y_pred_name, - title=f"Instance {instance_number}", ) - plots.append(plot) + groups.append( + ArtifactGroup(title=f"Instance {instance_number}", artifacts=[plot]) + ) - return plots + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/lime_text.py b/DashAI/back/explainability/explainers/lime_text.py index 89c8f5626..41fdd8e74 100644 --- a/DashAI/back/explainability/explainers/lime_text.py +++ b/DashAI/back/explainability/explainers/lime_text.py @@ -1,6 +1,11 @@ from typing import List -from DashAI.back.core.artifacts import Artifact, PlotlyArtifact, TextArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, + TextArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, int_field, @@ -239,8 +244,8 @@ def classifier_fn(variant_texts): return explanation - def plot(self, explanation: dict) -> List[Artifact]: - """Render each instance as a word-weight bar plot plus a summary. + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Render each instance as a word weight bar plot plus a summary. Parameters ---------- @@ -249,9 +254,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of typed artifacts: one plotly and one text artifact per - explained instance. + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's word weight plot and text summary. """ import numpy as np import plotly.graph_objs as go @@ -260,7 +265,7 @@ def plot(self, explanation: dict) -> List[Artifact]: metadata = exp.pop("metadata") target_names = metadata["target_names"] - artifacts = [] + groups = [] for i in exp: instance = exp[i] predicted_class = instance["predicted_class"] @@ -301,19 +306,17 @@ def plot(self, explanation: dict) -> List[Artifact]: ) title = f"Instance {int(i) + 1}" - artifacts.append(PlotlyArtifact(payload=fig, title=title)) + plot = PlotlyArtifact(payload=fig) top = list(reversed(word_weights))[:3] top_words = ", ".join(f"'{word}' ({weight:+})" for word, weight in top) - artifacts.append( - TextArtifact( - payload=( - f"The model predicted {predicted_name} " - f"(p={predicted_prob}). Most influential words: " - f"{top_words}." - ), - title=title, - ) + text = TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Most influential words: " + f"{top_words}." + ), ) + groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/nearest_counterfactual.py b/DashAI/back/explainability/explainers/nearest_counterfactual.py index 513448e25..0bc82c13b 100644 --- a/DashAI/back/explainability/explainers/nearest_counterfactual.py +++ b/DashAI/back/explainability/explainers/nearest_counterfactual.py @@ -1,7 +1,8 @@ from typing import List from DashAI.back.core.artifacts import ( - Artifact, + ArtifactGroup, + GroupedArtifacts, TableArtifact, TablePayload, TextArtifact, @@ -328,7 +329,7 @@ def explain_instance(self, instances): return explanation - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Render each instance as a comparison table plus a text summary. Parameters @@ -338,9 +339,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of typed artifacts: one table and one text artifact per - explained instance. + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's comparison table and text summary. """ import numpy as np @@ -349,7 +350,7 @@ def plot(self, explanation: dict) -> List[Artifact]: feature_names = metadata["feature_names"] target_names = metadata["target_names"] - artifacts = [] + groups = [] for i in exp: instance = exp[i] instance_values = instance["instance_values"] @@ -382,13 +383,8 @@ def plot(self, explanation: dict) -> List[Artifact]: highlight.append({"row": len(feature_names), "column": 2 + cf_idx}) title = f"Instance {int(i) + 1}" - artifacts.append( - TableArtifact( - payload=TablePayload( - columns=columns, rows=rows, highlight=highlight - ), - title=title, - ) + table = TableArtifact( + payload=TablePayload(columns=columns, rows=rows, highlight=highlight), ) if counterfactuals: @@ -409,6 +405,7 @@ def plot(self, explanation: dict) -> List[Artifact]: f"The model predicted {predicted_name} (p={predicted_prob}). " "No counterfactual examples were found in the training data." ) - artifacts.append(TextArtifact(payload=summary, title=title)) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[table, text])) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/occlusion_saliency.py b/DashAI/back/explainability/explainers/occlusion_saliency.py index 44fe05bff..5baa6a18d 100644 --- a/DashAI/back/explainability/explainers/occlusion_saliency.py +++ b/DashAI/back/explainability/explainers/occlusion_saliency.py @@ -1,6 +1,10 @@ from typing import List -from DashAI.back.core.artifacts import Artifact, TextArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + TextArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, int_field, @@ -297,7 +301,7 @@ def explain_instance(self, instances): return explanation - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Render each image as a saliency overlay plus a text summary. Parameters @@ -307,9 +311,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of typed artifacts: one plotly overlay and one text - artifact per explained image. + List[GroupedArtifacts] + A single grouped artifact with one group per explained image, each + holding that image's saliency overlay and text summary. """ import numpy as np @@ -317,7 +321,7 @@ def plot(self, explanation: dict) -> List[Artifact]: metadata = exp.pop("metadata") target_names = metadata["target_names"] - artifacts = [] + groups = [] for i in exp: instance = exp[i] predicted_class = instance["predicted_class"] @@ -328,20 +332,16 @@ def plot(self, explanation: dict) -> List[Artifact]: title = f"Image {int(i) + 1}" subtitle = f"Occlusion saliency for {predicted_name} (p={predicted_prob})" - artifacts.append( - heatmap_overlay_artifact( - instance["image"], instance["heatmap"], title, subtitle - ) + overlay = heatmap_overlay_artifact( + instance["image"], instance["heatmap"], title, subtitle ) - artifacts.append( - TextArtifact( - payload=( - f"The model predicted {predicted_name} " - f"(p={predicted_prob}). Highlighted regions are those " - "whose occlusion most lowered that probability." - ), - title=title, - ) + text = TextArtifact( + payload=( + f"The model predicted {predicted_name} " + f"(p={predicted_prob}). Highlighted regions are those " + "whose occlusion most lowered that probability." + ), ) + groups.append(ArtifactGroup(title=title, artifacts=[overlay, text])) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/partial_dependence.py b/DashAI/back/explainability/explainers/partial_dependence.py index e7a52d158..c9613bd56 100644 --- a/DashAI/back/explainability/explainers/partial_dependence.py +++ b/DashAI/back/explainability/explainers/partial_dependence.py @@ -1,6 +1,10 @@ from typing import List -from DashAI.back.core.artifacts import Artifact, PlotlyArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, float_field, @@ -244,29 +248,30 @@ def explain(self, dataset): return explanation - def _create_plot(self, data: List[object]) -> List[Artifact]: + def _create_plot(self, data: List[object]) -> List[GroupedArtifacts]: """Helper method to create the explanation plots using plotly. - Emits one plotly artifact per feature and class curve, titled by that - curve, so the frontend lists them in its instance selector instead of + Bundles one group per feature and class curve into a single grouped + artifact, so the frontend renders a selector over the curves instead of a dropdown embedded in a single figure. Parameters ---------- data : List - Per-feature-and-class DataFrames with the explanation generated by + Per feature and class DataFrames with the explanation generated by the explainer. Each DataFrame has the curve values in its first column and the grid positions in ``"grid_values"``. Returns ------- - List[Artifact] - One plotly artifact per feature and class curve. + List[GroupedArtifacts] + A single grouped artifact with one group (a plotly curve) per + feature and class. """ # Lazy imports import plotly.express as px - artifacts = [] + groups = [] for df in data: column_name = df.columns[0] fig = px.line( @@ -276,11 +281,15 @@ def _create_plot(self, data: List[object]) -> List[Artifact]: labels={"grid_values": "Feature value"}, ) fig.update_layout(yaxis_title="Partial Dependence") - artifacts.append(PlotlyArtifact(payload=fig, title=column_name)) + groups.append( + ArtifactGroup( + title=column_name, artifacts=[PlotlyArtifact(payload=fig)] + ) + ) - return artifacts + return [GroupedArtifacts(groups=groups)] - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Method to create the explanation plot. Parameters @@ -290,9 +299,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A single-element list with the plotly artifact of the - explanation plot. + List[GroupedArtifacts] + A single grouped artifact with one group per feature and class + curve. """ # Lazy import import pandas as pd diff --git a/DashAI/back/explainability/explainers/permutation_feature_importance.py b/DashAI/back/explainability/explainers/permutation_feature_importance.py index 11c37c65e..8603a3f82 100644 --- a/DashAI/back/explainability/explainers/permutation_feature_importance.py +++ b/DashAI/back/explainability/explainers/permutation_feature_importance.py @@ -1,6 +1,10 @@ from typing import Dict, List, Union -from DashAI.back.core.artifacts import Artifact, PlotlyArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, enum_field, @@ -517,12 +521,13 @@ def patched_metric(y_true, y_pred_probas): "importances_std": np.round(pfi["importances_std"], 3).tolist(), } - def _create_plot(self, data) -> List[Artifact]: - """Build one horizontal bar chart per feature count. + def _create_plot(self, data) -> List[GroupedArtifacts]: + """Build one selector over feature counts. - Each artifact shows the top ``count`` most important features, from all - features down to one, so the frontend lists the counts in its selector - instead of a dropdown embedded in a single figure. + Each count (from all features down to one) is a selectable group + holding the horizontal bar chart of the top ``count`` most important + features, so the frontend lists the counts in a selector instead of a + dropdown embedded in a single figure. Parameters ---------- @@ -532,13 +537,14 @@ def _create_plot(self, data) -> List[Artifact]: Returns ------- - List[Artifact] - One plotly artifact per feature count, most features first. + List[GroupedArtifacts] + A single grouped artifact with one group (a bar chart) per feature + count, most features first. """ # Lazy imports import plotly.express as px - artifacts = [] + groups = [] for count in range(len(data), 0, -1): subset = data.iloc[-count:] fig = px.bar( @@ -548,11 +554,16 @@ def _create_plot(self, data) -> List[Artifact]: error_x=subset["importances_std"], ) fig.update_layout(xaxis_title="Importance", yaxis_title=None) - artifacts.append(PlotlyArtifact(payload=fig, title=f"Top {count} features")) + groups.append( + ArtifactGroup( + title=f"Top {count} features", + artifacts=[PlotlyArtifact(payload=fig)], + ) + ) - return artifacts + return [GroupedArtifacts(groups=groups)] - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Create a Plotly bar chart from a feature importance explanation dict. Parameters @@ -563,8 +574,8 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - One plotly artifact per feature count (built by + List[GroupedArtifacts] + A single selector over the feature counts (built by :meth:`_create_plot`). """ # Lazy import diff --git a/DashAI/back/explainability/explainers/regression_kernel_shap.py b/DashAI/back/explainability/explainers/regression_kernel_shap.py index 2d0a05838..d6c990f61 100644 --- a/DashAI/back/explainability/explainers/regression_kernel_shap.py +++ b/DashAI/back/explainability/explainers/regression_kernel_shap.py @@ -1,6 +1,11 @@ from typing import List -from DashAI.back.core.artifacts import Artifact, PlotlyArtifact, TextArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, + TextArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, bool_field, @@ -243,7 +248,7 @@ def explain_instance(self, instances): return explanation - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Render each instance as a SHAP bar plot plus a text summary. Parameters @@ -253,9 +258,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of typed artifacts: one plotly and one text artifact per - explained instance. + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's plotly plot and text summary. """ import numpy as np import pandas as pd @@ -268,7 +273,7 @@ def plot(self, explanation: dict) -> List[Artifact]: output_column = metadata["output_column"] max_features = 8 - artifacts = [] + groups = [] for i in exp: instance = exp[i] prediction = instance["model_prediction"] @@ -314,7 +319,7 @@ def plot(self, explanation: dict) -> List[Artifact]: ) title = f"Instance {int(i) + 1}" - artifacts.append(PlotlyArtifact(payload=fig, title=title)) + plot = PlotlyArtifact(payload=fig) top = data.iloc[::-1].head(3) top_features = ", ".join( @@ -332,6 +337,7 @@ def plot(self, explanation: dict) -> List[Artifact]: f"{delta:+} from the baseline {base_value}. " f"Main contributions: {top_features}." ) - artifacts.append(TextArtifact(payload=summary, title=title)) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/regression_partial_dependence.py b/DashAI/back/explainability/explainers/regression_partial_dependence.py index 19142b1fc..f0bd92f31 100644 --- a/DashAI/back/explainability/explainers/regression_partial_dependence.py +++ b/DashAI/back/explainability/explainers/regression_partial_dependence.py @@ -1,6 +1,10 @@ from typing import List -from DashAI.back.core.artifacts import Artifact, PlotlyArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, float_field, @@ -202,8 +206,8 @@ def explain(self, dataset): return explanation - def plot(self, explanation: dict) -> List[Artifact]: - """Create one line-plot artifact per feature. + def plot(self, explanation: dict) -> List[GroupedArtifacts]: + """Create a grouped artifact with one line plot group per feature. Parameters ---------- @@ -212,8 +216,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of artifacts: one plotly artifact per numeric feature. + List[GroupedArtifacts] + A single grouped artifact whose groups are one plotly curve per + numeric feature. """ import plotly.graph_objs as go @@ -221,7 +226,7 @@ def plot(self, explanation: dict) -> List[Artifact]: metadata = exp.pop("metadata") output_column = metadata["output_column"] - artifacts = [] + groups = [] for feature, curve in exp.items(): fig = go.Figure( go.Scatter( @@ -239,6 +244,8 @@ def plot(self, explanation: dict) -> List[Artifact]: yaxis={"title_text": f"Average predicted {output_column}"}, margin={"l": 60, "r": 30, "t": 50, "b": 50}, ) - artifacts.append(PlotlyArtifact(payload=fig, title=feature)) + groups.append( + ArtifactGroup(title=feature, artifacts=[PlotlyArtifact(payload=fig)]) + ) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/token_ablation.py b/DashAI/back/explainability/explainers/token_ablation.py index b0f20b488..401daf1c8 100644 --- a/DashAI/back/explainability/explainers/token_ablation.py +++ b/DashAI/back/explainability/explainers/token_ablation.py @@ -1,6 +1,11 @@ from typing import List -from DashAI.back.core.artifacts import Artifact, PlotlyArtifact, TextArtifact +from DashAI.back.core.artifacts import ( + ArtifactGroup, + GroupedArtifacts, + PlotlyArtifact, + TextArtifact, +) from DashAI.back.core.schema_fields import ( BaseSchema, enum_field, @@ -266,7 +271,7 @@ def explain_instance(self, instances): return explanation - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Render each instance as a token importance bar plot plus a summary. Parameters @@ -276,9 +281,9 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of typed artifacts: one plotly and one text artifact per - explained instance. + List[GroupedArtifacts] + A single grouped artifact with one group per explained instance, + each holding that instance's token plot and text summary. """ import numpy as np import pandas as pd @@ -289,7 +294,7 @@ def plot(self, explanation: dict) -> List[Artifact]: target_names = metadata["target_names"] max_tokens_plotted = 15 - artifacts = [] + groups = [] for i in exp: instance = exp[i] predicted_class = instance["predicted_class"] @@ -340,7 +345,7 @@ def plot(self, explanation: dict) -> List[Artifact]: ) title = f"Instance {int(i) + 1}" - artifacts.append(PlotlyArtifact(payload=fig, title=title)) + plot = PlotlyArtifact(payload=fig) top = data.iloc[::-1].head(3) top_tokens = ", ".join( @@ -353,6 +358,7 @@ def plot(self, explanation: dict) -> List[Artifact]: f"The model predicted {predicted_name} (p={predicted_prob}). " f"Most influential tokens: {top_tokens}." ) - artifacts.append(TextArtifact(payload=summary, title=title)) + text = TextArtifact(payload=summary) + groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return artifacts + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/global_explainer.py b/DashAI/back/explainability/global_explainer.py index 5f7b02e4f..601d3dbb3 100644 --- a/DashAI/back/explainability/global_explainer.py +++ b/DashAI/back/explainability/global_explainer.py @@ -1,8 +1,8 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Final, List, Tuple +from typing import TYPE_CHECKING, Final, List, Tuple, Union from DashAI.back.config_object import ConfigObject -from DashAI.back.core.artifacts import Artifact +from DashAI.back.core.artifacts import Artifact, GroupedArtifacts from DashAI.back.models.base_model import BaseModel if TYPE_CHECKING: @@ -65,7 +65,7 @@ def explain(self, dataset: Tuple["DatasetDict", "DatasetDict"]) -> dict: raise NotImplementedError @abstractmethod - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[Union[Artifact, GroupedArtifacts]]: """Generate renderable artifacts from a previously computed explanation. Concrete implementations must convert the explanation dictionary @@ -79,10 +79,12 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] + List[Union[Artifact, GroupedArtifacts]] A list of artifacts (:class:`PlotlyArtifact`, :class:`TableArtifact`, :class:`TextArtifact` or - :class:`ImageArtifact`) describing the explanation. + :class:`ImageArtifact`) and/or :class:`GroupedArtifacts` batches + (e.g. a summary table next to its plot for one curve/count) that + the frontend should render together, describing the explanation. Raises ------ diff --git a/DashAI/back/explainability/local_explainer.py b/DashAI/back/explainability/local_explainer.py index 7432ec76e..e8e3bb8dc 100644 --- a/DashAI/back/explainability/local_explainer.py +++ b/DashAI/back/explainability/local_explainer.py @@ -1,8 +1,8 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Final, List, Tuple +from typing import TYPE_CHECKING, Final, List, Tuple, Union from DashAI.back.config_object import ConfigObject -from DashAI.back.core.artifacts import Artifact +from DashAI.back.core.artifacts import Artifact, GroupedArtifacts from DashAI.back.models.base_model import BaseModel if TYPE_CHECKING: @@ -88,12 +88,14 @@ def explain_instance(self, instances: "DatasetDict") -> dict: raise NotImplementedError @abstractmethod - def plot(self, explanation: dict) -> List[Artifact]: + def plot(self, explanation: dict) -> List[Union[Artifact, GroupedArtifacts]]: """Generate renderable artifacts from a previously computed explanation. Concrete implementations must convert the explanation dictionary - returned by :meth:`explain_instance` into one or more typed artifacts - that can be rendered on the frontend. + returned by :meth:`explain_instance` into typed artifacts that can be + rendered on the frontend. Local explainers typically return a single + :class:`GroupedArtifacts` whose groups are one explained instance each + (the frontend renders its selector as the explained rows picker). Parameters ---------- @@ -102,11 +104,10 @@ def plot(self, explanation: dict) -> List[Artifact]: Returns ------- - List[Artifact] - A list of artifacts (:class:`PlotlyArtifact`, - :class:`TableArtifact`, :class:`TextArtifact` or - :class:`ImageArtifact`) describing the explanation, typically - one per explained instance. + List[Union[Artifact, GroupedArtifacts]] + Leaf artifacts (:class:`PlotlyArtifact`, :class:`TableArtifact`, + :class:`TextArtifact`, :class:`ImageArtifact`) and/or + :class:`GroupedArtifacts` describing the explanation. Raises ------ diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 7c5af9ddf..22555434f 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -1,5 +1,5 @@ import { React, useEffect, useState } from "react"; -import { CircularProgress, Box, Typography } from "@mui/material"; +import { CircularProgress, Box } from "@mui/material"; import PropTypes from "prop-types"; import { useSnackbar } from "notistack"; @@ -17,27 +17,159 @@ function parseExplanationArtifacts(items) { ); } +/** Build the onSaveEdit/onResetEdit/canReset props shared by every leaf. */ +function leafProps( + artifact, + { onSaveOverride, onResetOverride, overriddenIndexes }, +) { + return { + canReset: overriddenIndexes.includes(artifact.index), + onSaveEdit: onSaveOverride + ? (figure) => onSaveOverride(artifact.index, figure) + : null, + onResetEdit: onResetOverride ? () => onResetOverride(artifact.index) : null, + }; +} + /** - * Group consecutive artifacts sharing the same non null title into one - * instance group, tracking each artifact's flat index in the endpoint - * response so edits can target it. + * Lay out a batch of leaf artifacts: the first artifact fills the row beside + * whatever `leading` element is passed (a selector, or nothing); any further + * artifacts stack below at full width, most recent first. `siblings` is the + * full artifact list of the batch so the fullscreen viewer can navigate + * between them. */ -function groupArtifacts(artifacts) { - const groups = []; - artifacts.forEach((artifact, index) => { - const withIndex = { ...artifact, index }; - const lastGroup = groups[groups.length - 1]; - if ( - artifact.title != null && - lastGroup && - lastGroup.title === artifact.title - ) { - lastGroup.artifacts.push(withIndex); - } else { - groups.push({ title: artifact.title ?? null, artifacts: [withIndex] }); - } - }); - return groups; +function ArtifactBatch({ + artifacts, + siblings, + ctx, + leading = null, + leadingFlex, + leadingMinWidth = 0, +}) { + // Key by position within the batch, not by artifact.index: switching the + // selected group then reuses the same viewer/Plot instance at each slot and + // updates it in place (Plotly diffs) instead of unmounting the tall old plot + // and mounting a new one, which briefly collapses page height and makes the + // window scroll up. + const renderLeaf = (artifact, i) => ( + + ); + + const [firstArtifact, ...rest] = artifacts; + const stacked = rest.map((artifact, i) => ({ artifact, i: i + 1 })).reverse(); + + return ( + + + {leading && ( + + {leading} + + )} + {renderLeaf(firstArtifact, 0)} + + {stacked.map(({ artifact, i }) => renderLeaf(artifact, i))} + + ); +} + +ArtifactBatch.propTypes = { + artifacts: PropTypes.array.isRequired, + siblings: PropTypes.array.isRequired, + ctx: PropTypes.object.isRequired, + leading: PropTypes.node, + leadingFlex: PropTypes.string, + leadingMinWidth: PropTypes.number, +}; + +/** + * Render a GroupedArtifacts item: a selector listing every group, beside the + * selected group's first artifact (with the rest stacked below). Holds its own + * selection state, so multiple selectors on one card are independent. + * + * The selector widget depends on `datasetPath`: local explainers pass the + * explained rows dataset path so the picker shows the actual instance feature + * values (the row index selects the group); global explainers omit it and get + * a plain title list. + */ +function GroupedArtifactsView({ grouped, ctx, datasetPath = null }) { + const { t } = useTranslation(["explainers"]); + const [selected, setSelected] = useState(0); + const groups = grouped.groups ?? []; + if (groups.length === 0) return null; + + const group = groups[selected] ?? groups[0]; + const titles = groups.map( + (g, i) => + g.title ?? t("explainers:label.instanceNumber", { number: i + 1 }), + ); + const wide = Boolean(datasetPath); + + // Rendered directly (no height cap): ExplainerInstanceTable's root is + // height:100%, so it fills the stretched batch cell and matches the height + // of the first artifact beside it, scrolling internally when long. + const selector = ( + + ); + + return ( + + ); +} + +GroupedArtifactsView.propTypes = { + grouped: PropTypes.object.isRequired, + ctx: PropTypes.object.isRequired, + datasetPath: PropTypes.string, +}; + +/** + * Render one top level response item: a "grouped" selector + * (`GroupedArtifactsView`) or a plain leaf artifact (shown alone at full + * width). `datasetPath` is forwarded to grouped items so local explainers get + * the dataset row picker. + */ +function renderItem(item, ctx, datasetPath = null) { + if (item.type === "grouped") { + return ( + + ); + } + return ; } export default function ExplainersPlot({ @@ -48,8 +180,7 @@ export default function ExplainersPlot({ overriddenIndexes = [], }) { const { enqueueSnackbar } = useSnackbar(); - const [groups, setGroups] = useState([]); - const [currentGroup, setCurrentGroup] = useState(0); + const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const { t } = useTranslation(["explainers"]); const isLocal = scope === "local"; @@ -60,16 +191,13 @@ export default function ExplainersPlot({ try { const response = await getExplainerPlotRequest(explainer.id, scope); if (!response || response.length === 0) { - setGroups([]); - setCurrentGroup(0); + setItems([]); enqueueSnackbar(t("explainers:error.noData"), { variant: "warning" }); } else { - setGroups(groupArtifacts(parseExplanationArtifacts(response))); - setCurrentGroup(0); + setItems(parseExplanationArtifacts(response)); } } catch (error) { - setGroups([]); - setCurrentGroup(0); + setItems([]); enqueueSnackbar(t("explainers:error.fetchExplainers"), { variant: "error", }); @@ -94,80 +222,25 @@ export default function ExplainersPlot({ ); } - if (groups.length === 0 || !groups[currentGroup]) { + if (items.length === 0) { return {t("explainers:error.noData")}; } - const group = groups[currentGroup]; - const hasSelector = groups.length > 1; + const ctx = { onSaveOverride, onResetOverride, overriddenIndexes }; - // The explanation artifacts for the selected instance. - const detail = ( + // Every top level item renders continuously: a plain artifact at full width, + // a "grouped" item as its own self contained selector. Local explainers pass + // the explained rows dataset path so their grouped selector shows the + // instance feature values instead of plain labels. + return ( - {group.artifacts.map((artifact, i) => ( - onSaveOverride(artifact.index, figure) - : null - } - onResetEdit={ - onResetOverride ? () => onResetOverride(artifact.index) : null - } - /> + {items.map((item, i) => ( + {renderItem(item, ctx, datasetPath)} ))} ); - - // Single instance (typically a global explainer): no selector and no title, - // since the card header already names the explainer. Show it full width. - if (!hasSelector) { - return detail; - } - - // Many instances: the instance picker on the left (the explained dataset - // rows when stored, else a list of instance labels), the selected - // instance's explanation on the right. - return ( - - - - g.title ?? - t("explainers:label.instanceNumber", { number: i + 1 }), - )} - selectedIndex={currentGroup} - onSelect={setCurrentGroup} - /> - - {detail} - - ); } ExplainersPlot.propTypes = { From c5be21e126d3db4426b00d5838c8ce6b4f82b238 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 10:34:02 -0400 Subject: [PATCH 205/308] feat: update return type of plot method to GroupedArtifacts in BaseLocalExplainer and related explainer classes --- .../explainability/explainers/contrastive_shap.py | 2 +- .../explainability/explainers/dice_counterfactual.py | 2 +- DashAI/back/explainability/explainers/grad_cam.py | 2 +- DashAI/back/explainability/explainers/kernel_shap.py | 2 +- DashAI/back/explainability/explainers/lime_text.py | 2 +- .../explainers/nearest_counterfactual.py | 6 ++---- .../explainability/explainers/occlusion_saliency.py | 2 +- .../explainers/regression_kernel_shap.py | 2 +- .../back/explainability/explainers/token_ablation.py | 2 +- DashAI/back/explainability/local_explainer.py | 12 +++++------- 10 files changed, 15 insertions(+), 19 deletions(-) diff --git a/DashAI/back/explainability/explainers/contrastive_shap.py b/DashAI/back/explainability/explainers/contrastive_shap.py index 33154dd7f..5e87eeccd 100644 --- a/DashAI/back/explainability/explainers/contrastive_shap.py +++ b/DashAI/back/explainability/explainers/contrastive_shap.py @@ -461,4 +461,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/explainers/dice_counterfactual.py b/DashAI/back/explainability/explainers/dice_counterfactual.py index eea0e866a..c8fd25eb9 100644 --- a/DashAI/back/explainability/explainers/dice_counterfactual.py +++ b/DashAI/back/explainability/explainers/dice_counterfactual.py @@ -446,4 +446,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[table, text])) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/explainers/grad_cam.py b/DashAI/back/explainability/explainers/grad_cam.py index d22b95a30..dfbea897f 100644 --- a/DashAI/back/explainability/explainers/grad_cam.py +++ b/DashAI/back/explainability/explainers/grad_cam.py @@ -289,4 +289,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ) groups.append(ArtifactGroup(title=title, artifacts=[overlay, text])) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/explainers/kernel_shap.py b/DashAI/back/explainability/explainers/kernel_shap.py index 733728c3b..10fc92e4d 100644 --- a/DashAI/back/explainability/explainers/kernel_shap.py +++ b/DashAI/back/explainability/explainers/kernel_shap.py @@ -665,4 +665,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ArtifactGroup(title=f"Instance {instance_number}", artifacts=[plot]) ) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/explainers/lime_text.py b/DashAI/back/explainability/explainers/lime_text.py index 41fdd8e74..bfbc32370 100644 --- a/DashAI/back/explainability/explainers/lime_text.py +++ b/DashAI/back/explainability/explainers/lime_text.py @@ -319,4 +319,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ) groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/explainers/nearest_counterfactual.py b/DashAI/back/explainability/explainers/nearest_counterfactual.py index 0bc82c13b..1c922cf86 100644 --- a/DashAI/back/explainability/explainers/nearest_counterfactual.py +++ b/DashAI/back/explainability/explainers/nearest_counterfactual.py @@ -388,9 +388,7 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ) if counterfactuals: - lines = [ - (f"The model predicted {predicted_name} (p={predicted_prob}).") - ] + lines = [f"The model predicted {predicted_name} (p={predicted_prob})."] for cf_idx, counterfactual in enumerate(counterfactuals): cf_name = target_names[counterfactual["predicted_class"]] changed = ", ".join(counterfactual["changed_features"]) or "nothing" @@ -408,4 +406,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[table, text])) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/explainers/occlusion_saliency.py b/DashAI/back/explainability/explainers/occlusion_saliency.py index 5baa6a18d..14820c2f9 100644 --- a/DashAI/back/explainability/explainers/occlusion_saliency.py +++ b/DashAI/back/explainability/explainers/occlusion_saliency.py @@ -344,4 +344,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ) groups.append(ArtifactGroup(title=title, artifacts=[overlay, text])) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/explainers/regression_kernel_shap.py b/DashAI/back/explainability/explainers/regression_kernel_shap.py index d6c990f61..57ca83376 100644 --- a/DashAI/back/explainability/explainers/regression_kernel_shap.py +++ b/DashAI/back/explainability/explainers/regression_kernel_shap.py @@ -340,4 +340,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/explainers/token_ablation.py b/DashAI/back/explainability/explainers/token_ablation.py index 401daf1c8..889ad53d3 100644 --- a/DashAI/back/explainability/explainers/token_ablation.py +++ b/DashAI/back/explainability/explainers/token_ablation.py @@ -361,4 +361,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return [GroupedArtifacts(groups=groups)] + return GroupedArtifacts(groups=groups) diff --git a/DashAI/back/explainability/local_explainer.py b/DashAI/back/explainability/local_explainer.py index e8e3bb8dc..430739e9d 100644 --- a/DashAI/back/explainability/local_explainer.py +++ b/DashAI/back/explainability/local_explainer.py @@ -1,8 +1,8 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Final, List, Tuple, Union +from typing import TYPE_CHECKING, Final, Tuple from DashAI.back.config_object import ConfigObject -from DashAI.back.core.artifacts import Artifact, GroupedArtifacts +from DashAI.back.core.artifacts import GroupedArtifacts from DashAI.back.models.base_model import BaseModel if TYPE_CHECKING: @@ -88,7 +88,7 @@ def explain_instance(self, instances: "DatasetDict") -> dict: raise NotImplementedError @abstractmethod - def plot(self, explanation: dict) -> List[Union[Artifact, GroupedArtifacts]]: + def plot(self, explanation: dict) -> GroupedArtifacts: """Generate renderable artifacts from a previously computed explanation. Concrete implementations must convert the explanation dictionary @@ -104,10 +104,8 @@ def plot(self, explanation: dict) -> List[Union[Artifact, GroupedArtifacts]]: Returns ------- - List[Union[Artifact, GroupedArtifacts]] - Leaf artifacts (:class:`PlotlyArtifact`, :class:`TableArtifact`, - :class:`TextArtifact`, :class:`ImageArtifact`) and/or - :class:`GroupedArtifacts` describing the explanation. + GroupedArtifacts + A single grouped artifact whose groups are one explained instance each. Raises ------ From 609f15ae3801ec011a57f55b62d7713c49654b0a Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 11:19:19 -0400 Subject: [PATCH 206/308] feat: enhance artifact normalization to support grouped artifacts in local explanations --- .../back/api/api_v1/endpoints/explainers.py | 4 ++- DashAI/back/core/artifacts.py | 35 ++++++++++++++++++- DashAI/back/job/explainer_job.py | 4 ++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/DashAI/back/api/api_v1/endpoints/explainers.py b/DashAI/back/api/api_v1/endpoints/explainers.py index b9e7d7f17..b46f585e9 100755 --- a/DashAI/back/api/api_v1/endpoints/explainers.py +++ b/DashAI/back/api/api_v1/endpoints/explainers.py @@ -553,7 +553,9 @@ async def get_local_explanation_plot( detail="Internal database error", ) from e - return _apply_overrides(normalize_artifacts(plots), plot_overrides) + return _apply_overrides( + normalize_artifacts(plots, create_grouped=True), plot_overrides + ) @router.put("/{scope}/plot/{explainer_id}/override") diff --git a/DashAI/back/core/artifacts.py b/DashAI/back/core/artifacts.py index 4d8785f3e..12c7eeadb 100644 --- a/DashAI/back/core/artifacts.py +++ b/DashAI/back/core/artifacts.py @@ -384,7 +384,9 @@ def _legacy_explorer_artifact(item: Dict[str, Any]) -> Dict[str, Any]: return TextArtifact(payload=str(data)).to_dict() -def normalize_artifacts(items: Any) -> List[Dict[str, Any]]: +def normalize_artifacts( + items: Any, *, create_grouped: bool = False +) -> List[Dict[str, Any]]: """Coerce any component output into a list of artifact/group wire dicts. Handles current values (``Artifact`` or :class:`GroupedArtifacts` @@ -403,6 +405,11 @@ def normalize_artifacts(items: Any) -> List[Dict[str, Any]]: items : Any The value returned by an explainer ``plot`` method, an explorer ``get_results`` method, or loaded from a persisted result file. + create_grouped : bool, optional + When ``True``, wrap a list of leaf artifacts into a single grouped + artifact container with one selectable group per input item. This is + useful for local explainers whose output is a list of per-instance + artifacts. Returns ------- @@ -459,6 +466,32 @@ def normalize_item(item: Any) -> Dict[str, Any]: } return normalize_leaf(item) + if create_grouped and isinstance(items, list): + grouped_groups = [] + for i, item in enumerate(items): + normalized_item = normalize_item(item) + if normalized_item.get("type") == "grouped": + grouped_groups.append( + { + "title": normalized_item.get("title", f"instance {i}"), + "artifacts": normalized_item.get("groups", []), + } + ) + else: + grouped_groups.append( + { + "title": normalized_item.get("title", f"instance {i}"), + "artifacts": [normalized_item], + } + ) + return [ + { + "type": "grouped", + "title": None, + "groups": grouped_groups, + } + ] + return [normalize_item(item) for item in items] diff --git a/DashAI/back/job/explainer_job.py b/DashAI/back/job/explainer_job.py index 392b5161c..9f5e27986 100644 --- a/DashAI/back/job/explainer_job.py +++ b/DashAI/back/job/explainer_job.py @@ -286,7 +286,9 @@ def _generate_local_explanation( ) from e try: explanation = explainer.explain_instance(X) - plots = normalize_artifacts(explainer.plot(explanation)) + plots = normalize_artifacts( + explainer.plot(explanation), create_grouped=True + ) except Exception as e: log.exception(e) raise JobError( From 9882de2d474521246ce18e39843a9b2eb10512ad Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 11:21:07 -0400 Subject: [PATCH 207/308] feat: add test for normalizing local explainer items into grouped artifacts --- tests/back/core/test_artifacts.py | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/tests/back/core/test_artifacts.py b/tests/back/core/test_artifacts.py index 34a572aab..99f27e09f 100644 --- a/tests/back/core/test_artifacts.py +++ b/tests/back/core/test_artifacts.py @@ -213,6 +213,48 @@ def test_normalize_unrenderable_falls_back_to_text(): } +def test_normalize_wraps_local_explainer_items_in_grouped_artifacts(): + artifacts = normalize_artifacts( + [ + TextArtifact(payload="x", title="Instance 1"), + TextArtifact(payload="y", title="Instance 2"), + ], + create_grouped=True, + ) + assert artifacts == [ + { + "type": "grouped", + "title": None, + "groups": [ + { + "title": "Instance 1", + "artifacts": [ + { + "type": "text", + "payload": "x", + "title": "Instance 1", + "role": "explanation", + "index": 0, + } + ], + }, + { + "title": "Instance 2", + "artifacts": [ + { + "type": "text", + "payload": "y", + "title": "Instance 2", + "role": "explanation", + "index": 1, + } + ], + }, + ], + } + ] + + def test_artifact_role_defaults_to_explanation(): from DashAI.back.core.artifacts import TextArtifact From 166316b241bc78184f8df11b3c9ca65a8ff9ceb8 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 11:24:26 -0400 Subject: [PATCH 208/308] fix: update normalization logic to prevent grouping of already grouped artifacts --- DashAI/back/core/artifacts.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DashAI/back/core/artifacts.py b/DashAI/back/core/artifacts.py index 12c7eeadb..86f7de196 100644 --- a/DashAI/back/core/artifacts.py +++ b/DashAI/back/core/artifacts.py @@ -466,7 +466,11 @@ def normalize_item(item: Any) -> Dict[str, Any]: } return normalize_leaf(item) - if create_grouped and isinstance(items, list): + if ( + create_grouped + and isinstance(items, list) + and not isinstance(items[0], (GroupedArtifacts, dict)) + ): grouped_groups = [] for i, item in enumerate(items): normalized_item = normalize_item(item) From 30426150c64566ef2a9f99a956b119bd0a14d11d Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 12:31:35 -0400 Subject: [PATCH 209/308] refactor: enhance artifact normalization to handle grouped artifacts more efficiently --- DashAI/back/core/artifacts.py | 42 ++++++++++++----------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/DashAI/back/core/artifacts.py b/DashAI/back/core/artifacts.py index 86f7de196..04d423d49 100644 --- a/DashAI/back/core/artifacts.py +++ b/DashAI/back/core/artifacts.py @@ -466,35 +466,21 @@ def normalize_item(item: Any) -> Dict[str, Any]: } return normalize_leaf(item) - if ( - create_grouped - and isinstance(items, list) - and not isinstance(items[0], (GroupedArtifacts, dict)) - ): - grouped_groups = [] - for i, item in enumerate(items): - normalized_item = normalize_item(item) - if normalized_item.get("type") == "grouped": - grouped_groups.append( - { - "title": normalized_item.get("title", f"instance {i}"), - "artifacts": normalized_item.get("groups", []), - } - ) - else: - grouped_groups.append( - { - "title": normalized_item.get("title", f"instance {i}"), - "artifacts": [normalized_item], - } - ) - return [ - { - "type": "grouped", - "title": None, - "groups": grouped_groups, - } + def is_grouped(value: Any) -> bool: + return isinstance(value, GroupedArtifacts) or ( + isinstance(value, dict) and value.get("type") == "grouped" + ) + + # Migrate a flat list of per instance leaf artifacts (e.g. an old local + # explainer's output) into a single grouped artifact with one selectable + # group per instance. A payload that is already grouped is passed through + # unchanged. + if create_grouped and items and not is_grouped(items[0]): + groups = [ + {"title": leaf.get("title"), "artifacts": [leaf]} + for leaf in (normalize_leaf(item) for item in items) ] + return [{"type": "grouped", "title": None, "groups": groups}] return [normalize_item(item) for item in items] From 12e6bc2f634dbce2549cbc7c85531065541bf267 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 12:32:06 -0400 Subject: [PATCH 210/308] feat: update plot method return type to List[GroupedArtifacts] in BaseLocalExplainer and related explainer classes --- .../back/explainability/explainers/contrastive_shap.py | 2 +- .../explainability/explainers/dice_counterfactual.py | 2 +- DashAI/back/explainability/explainers/grad_cam.py | 2 +- DashAI/back/explainability/explainers/kernel_shap.py | 2 +- DashAI/back/explainability/explainers/lime_text.py | 2 +- .../explainability/explainers/nearest_counterfactual.py | 2 +- .../back/explainability/explainers/occlusion_saliency.py | 2 +- .../explainability/explainers/regression_kernel_shap.py | 2 +- DashAI/back/explainability/explainers/token_ablation.py | 2 +- DashAI/back/explainability/local_explainer.py | 9 +++++---- 10 files changed, 14 insertions(+), 13 deletions(-) diff --git a/DashAI/back/explainability/explainers/contrastive_shap.py b/DashAI/back/explainability/explainers/contrastive_shap.py index 5e87eeccd..33154dd7f 100644 --- a/DashAI/back/explainability/explainers/contrastive_shap.py +++ b/DashAI/back/explainability/explainers/contrastive_shap.py @@ -461,4 +461,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/dice_counterfactual.py b/DashAI/back/explainability/explainers/dice_counterfactual.py index c8fd25eb9..eea0e866a 100644 --- a/DashAI/back/explainability/explainers/dice_counterfactual.py +++ b/DashAI/back/explainability/explainers/dice_counterfactual.py @@ -446,4 +446,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[table, text])) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/grad_cam.py b/DashAI/back/explainability/explainers/grad_cam.py index dfbea897f..d22b95a30 100644 --- a/DashAI/back/explainability/explainers/grad_cam.py +++ b/DashAI/back/explainability/explainers/grad_cam.py @@ -289,4 +289,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ) groups.append(ArtifactGroup(title=title, artifacts=[overlay, text])) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/kernel_shap.py b/DashAI/back/explainability/explainers/kernel_shap.py index 10fc92e4d..733728c3b 100644 --- a/DashAI/back/explainability/explainers/kernel_shap.py +++ b/DashAI/back/explainability/explainers/kernel_shap.py @@ -665,4 +665,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ArtifactGroup(title=f"Instance {instance_number}", artifacts=[plot]) ) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/lime_text.py b/DashAI/back/explainability/explainers/lime_text.py index bfbc32370..41fdd8e74 100644 --- a/DashAI/back/explainability/explainers/lime_text.py +++ b/DashAI/back/explainability/explainers/lime_text.py @@ -319,4 +319,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ) groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/nearest_counterfactual.py b/DashAI/back/explainability/explainers/nearest_counterfactual.py index 1c922cf86..1c479cfa3 100644 --- a/DashAI/back/explainability/explainers/nearest_counterfactual.py +++ b/DashAI/back/explainability/explainers/nearest_counterfactual.py @@ -406,4 +406,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[table, text])) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/occlusion_saliency.py b/DashAI/back/explainability/explainers/occlusion_saliency.py index 14820c2f9..5baa6a18d 100644 --- a/DashAI/back/explainability/explainers/occlusion_saliency.py +++ b/DashAI/back/explainability/explainers/occlusion_saliency.py @@ -344,4 +344,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: ) groups.append(ArtifactGroup(title=title, artifacts=[overlay, text])) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/regression_kernel_shap.py b/DashAI/back/explainability/explainers/regression_kernel_shap.py index 57ca83376..d6c990f61 100644 --- a/DashAI/back/explainability/explainers/regression_kernel_shap.py +++ b/DashAI/back/explainability/explainers/regression_kernel_shap.py @@ -340,4 +340,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/explainers/token_ablation.py b/DashAI/back/explainability/explainers/token_ablation.py index 889ad53d3..401daf1c8 100644 --- a/DashAI/back/explainability/explainers/token_ablation.py +++ b/DashAI/back/explainability/explainers/token_ablation.py @@ -361,4 +361,4 @@ def plot(self, explanation: dict) -> List[GroupedArtifacts]: text = TextArtifact(payload=summary) groups.append(ArtifactGroup(title=title, artifacts=[plot, text])) - return GroupedArtifacts(groups=groups) + return [GroupedArtifacts(groups=groups)] diff --git a/DashAI/back/explainability/local_explainer.py b/DashAI/back/explainability/local_explainer.py index 430739e9d..5eb66d847 100644 --- a/DashAI/back/explainability/local_explainer.py +++ b/DashAI/back/explainability/local_explainer.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Final, Tuple +from typing import TYPE_CHECKING, Final, List, Tuple from DashAI.back.config_object import ConfigObject from DashAI.back.core.artifacts import GroupedArtifacts @@ -88,7 +88,7 @@ def explain_instance(self, instances: "DatasetDict") -> dict: raise NotImplementedError @abstractmethod - def plot(self, explanation: dict) -> GroupedArtifacts: + def plot(self, explanation: dict) -> List[GroupedArtifacts]: """Generate renderable artifacts from a previously computed explanation. Concrete implementations must convert the explanation dictionary @@ -104,8 +104,9 @@ def plot(self, explanation: dict) -> GroupedArtifacts: Returns ------- - GroupedArtifacts - A single grouped artifact whose groups are one explained instance each. + List[GroupedArtifacts] + A list containing a single grouped artifact whose groups + are one explained instance each. Raises ------ From 1d4cdc174fa31e9bda095ed98123ae1e3475d203 Mon Sep 17 00:00:00 2001 From: Creylay Date: Fri, 17 Jul 2026 12:40:40 -0400 Subject: [PATCH 211/308] Refactor LiveMetricsChart and ResultsGraphsLayout: enhance layout and user interaction with improved metric selection and filtering functionality. Update localization files to include new search metric labels in multiple languages. --- .../components/models/LiveMetricsChart.jsx | 25 ++- .../components/ResultsGraphsLayout.jsx | 16 +- .../components/ResultsGraphsParameters.jsx | 210 ++++++++++++------ .../results/components/ResultsGraphsPlot.jsx | 33 ++- .../src/utils/i18n/locales/de/common.json | 1 + .../src/utils/i18n/locales/de/models.json | 1 + .../src/utils/i18n/locales/en/common.json | 1 + .../src/utils/i18n/locales/en/models.json | 1 + .../src/utils/i18n/locales/es/common.json | 1 + .../src/utils/i18n/locales/es/models.json | 1 + .../src/utils/i18n/locales/pt/common.json | 1 + .../src/utils/i18n/locales/pt/models.json | 1 + .../src/utils/i18n/locales/zh/common.json | 1 + .../src/utils/i18n/locales/zh/models.json | 1 + 14 files changed, 202 insertions(+), 92 deletions(-) diff --git a/DashAI/front/src/components/models/LiveMetricsChart.jsx b/DashAI/front/src/components/models/LiveMetricsChart.jsx index 160b9a71a..c735e6fbd 100644 --- a/DashAI/front/src/components/models/LiveMetricsChart.jsx +++ b/DashAI/front/src/components/models/LiveMetricsChart.jsx @@ -377,7 +377,12 @@ export function LiveMetricsChart({ return ( - + {t("models:label.test")} + + {summaryMetrics.length > 0 && ( @@ -516,16 +529,6 @@ export function LiveMetricsChart({ )} - - - - {panels.length === 0 ? ( {/* Metric filter toolbar */} - + + + {/* Plotly chart area — bar panels + heatmap in one grid */} diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx index d63a1fac1..aad0bf720 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsParameters.jsx @@ -1,13 +1,16 @@ -import React from "react"; +import React, { useMemo, useState } from "react"; import PropTypes from "prop-types"; import { Box, Button, Checkbox, FormControlLabel, + InputAdornment, + Popover, + TextField, Typography, } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; +import { BarChart, ExpandLess, ExpandMore, Search } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; function ResultsGraphsParameters({ @@ -17,85 +20,148 @@ function ResultsGraphsParameters({ handleSelectAll, handleClearAll, }) { - const theme = useTheme(); const { t } = useTranslation(["models", "common"]); + const [anchorEl, setAnchorEl] = useState(null); + const [search, setSearch] = useState(""); + const open = Boolean(anchorEl); + + const filteredMetrics = useMemo( + () => + currentMetrics.filter((metric) => + metric.toLowerCase().includes(search.toLowerCase()), + ), + [currentMetrics, search], + ); + + const handleClose = () => { + setAnchorEl(null); + setSearch(""); + }; return ( - - - + - - - + + + setSearch(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> - {currentMetrics.length === 0 ? ( - - {t("models:label.noMetricsAvailableForThisView")} - - ) : ( - - {currentMetrics.map((metric) => ( - handleToggleMetric(metric)} + + {filteredMetrics.length === 0 ? ( + + {t("models:label.noMetricsAvailable")} + + ) : ( + filteredMetrics.map((metric) => ( + handleToggleMetric(metric)} + /> + } + label={{metric}} + sx={{ display: "flex", m: 0, width: "100%" }} /> + )) + )} + + + + + {t("common:selectAll")} + + {metric}} - sx={{ display: "flex", m: 0, mr: 3 }} - /> - ))} + sx={{ + cursor: selectedMetrics.length === 0 ? "default" : "pointer", + opacity: selectedMetrics.length === 0 ? 0.5 : 1, + }} + > + {t("common:clear")} + + - )} - + + ); } diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx index e77358e2c..e80c826f8 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx @@ -22,6 +22,11 @@ import { CSS } from "@dnd-kit/utilities"; const PANEL_ORDER_STORAGE_KEY = "dashai-results-panel-order"; const HEATMAP_ID = "__heatmap__"; +// Matches the run cards grid's minmax floor (SessionVisualization.jsx) so +// both sections switch between 1 and 2 columns at the same container width +// instead of disagreeing in a narrow "dead zone". +const PANEL_MIN_WIDTH = 340; +const GRID_GAP = 24; // gap: 3 → 3 * 8px function EmptyState({ message }) { return ( @@ -167,6 +172,29 @@ function ResultsGraphsPlot({ chartData, onToggleRun }) { }); }; + // The heatmap spans 2 grid columns, which forces the auto-fill grid to + // reserve 2 tracks even when the container is too narrow for two real + // 420px columns — CSS then resolves that shortage by splitting the + // columns unevenly instead of collapsing to one column per row. Only ask + // for the 2-column span once the container actually has room for it. + // + // Uses a state-backed (not plain useRef) callback ref: the grid only + // mounts once chartData finishes loading (before that, EmptyState renders + // instead), so a plain ref + `useEffect(..., [])` would fire before the + // node exists and never re-attach once it does. + const [gridNode, setGridNode] = useState(null); + const [canSpanTwoColumns, setCanSpanTwoColumns] = useState(true); + + useEffect(() => { + if (!gridNode) return undefined; + const TWO_COLUMN_MIN_WIDTH = PANEL_MIN_WIDTH * 2 + GRID_GAP; + const observer = new ResizeObserver(([entry]) => { + setCanSpanTwoColumns(entry.contentRect.width >= TWO_COLUMN_MIN_WIDTH); + }); + observer.observe(gridNode); + return () => observer.disconnect(); + }, [gridNode]); + if (panels.length === 0 && heatmapData.length === 0) { return ( @@ -254,10 +282,11 @@ function ResultsGraphsPlot({ chartData, onToggleRun }) { > {orderedIds.map((id) => { @@ -267,7 +296,7 @@ function ResultsGraphsPlot({ chartData, onToggleRun }) { key={id} id={id} title={t("models:label.heatmap")} - gridColumn="span 2" + gridColumn={canSpanTwoColumns ? "span 2" : undefined} > Date: Fri, 17 Jul 2026 12:41:53 -0400 Subject: [PATCH 212/308] fix: allow fullscreen navigation across groups for grouped artifacts --- .../components/explainers/ExplainersPlot.jsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersPlot.jsx b/DashAI/front/src/components/explainers/ExplainersPlot.jsx index 22555434f..18d3c010f 100644 --- a/DashAI/front/src/components/explainers/ExplainersPlot.jsx +++ b/DashAI/front/src/components/explainers/ExplainersPlot.jsx @@ -45,18 +45,23 @@ function ArtifactBatch({ leading = null, leadingFlex, leadingMinWidth = 0, + siblingOffset = 0, }) { // Key by position within the batch, not by artifact.index: switching the // selected group then reuses the same viewer/Plot instance at each slot and // updates it in place (Plotly diffs) instead of unmounting the tall old plot // and mounting a new one, which briefly collapses page height and makes the // window scroll up. + // + // siblingIndex maps this leaf into `siblings` (which may span every group, + // not just this batch) via siblingOffset, so the fullscreen viewer can page + // across groups even when each group has a single artifact. const renderLeaf = (artifact, i) => ( ); @@ -98,6 +103,7 @@ ArtifactBatch.propTypes = { leading: PropTypes.node, leadingFlex: PropTypes.string, leadingMinWidth: PropTypes.number, + siblingOffset: PropTypes.number, }; /** @@ -123,6 +129,14 @@ function GroupedArtifactsView({ grouped, ctx, datasetPath = null }) { ); const wide = Boolean(datasetPath); + // Fullscreen navigation spans every group's artifacts (flattened), so the + // viewer can page across groups even when each group has a single artifact. + // The selected group's artifacts occupy the slice starting at `offset`. + const allArtifacts = groups.flatMap((g) => g.artifacts); + const offset = groups + .slice(0, selected) + .reduce((n, g) => n + g.artifacts.length, 0); + // Rendered directly (no height cap): ExplainerInstanceTable's root is // height:100%, so it fills the stretched batch cell and matches the height // of the first artifact beside it, scrolling internally when long. @@ -138,7 +152,8 @@ function GroupedArtifactsView({ grouped, ctx, datasetPath = null }) { return ( Date: Fri, 17 Jul 2026 12:51:28 -0400 Subject: [PATCH 213/308] Refactor ResultsGraphs components: add sessionId prop to ResultsGraphs, ResultsGraphsLayout, and ResultsGraphsPlot for session-specific state management, improving user experience during metric reordering. --- .../pages/results/components/ResultsGraphs.jsx | 1 + .../results/components/ResultsGraphsLayout.jsx | 11 ++++++++++- .../results/components/ResultsGraphsPlot.jsx | 18 ++++++++++++++---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx index c949f8059..fb5984bc6 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphs.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphs.jsx @@ -197,6 +197,7 @@ function ResultsGraphs({ handleClearAll={handleClearAll} chartData={chartData} onToggleRun={handleToggleRun} + sessionId={finishedRuns[0]?.model_session_id} /> ); } diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx index d829db936..c3c383133 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsLayout.jsx @@ -13,6 +13,7 @@ function ResultsGraphsLayout({ handleClearAll, chartData, onToggleRun, + sessionId, }) { return ( - + {/* Remounts (resetting the drag-order state cleanly) whenever the + session changes instead of reusing the instance across sessions */} + ); @@ -49,6 +57,7 @@ ResultsGraphsLayout.propTypes = { handleClearAll: PropTypes.func.isRequired, chartData: PropTypes.object.isRequired, onToggleRun: PropTypes.func.isRequired, + sessionId: PropTypes.number, }; export default ResultsGraphsLayout; diff --git a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx index e80c826f8..f00200210 100644 --- a/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx +++ b/DashAI/front/src/pages/results/components/ResultsGraphsPlot.jsx @@ -106,7 +106,7 @@ function SortableCard({ id, title, gridColumn, children }) { ); } -function ResultsGraphsPlot({ chartData, onToggleRun }) { +function ResultsGraphsPlot({ chartData, onToggleRun, sessionId }) { const { t } = useTranslation(["models"]); const theme = useTheme(); const bgColor = theme.palette.background.paper; @@ -118,9 +118,18 @@ function ResultsGraphsPlot({ chartData, onToggleRun }) { const yaxis = chartData.yaxis; const heatmapData = chartData.heatmap ?? []; + // Scoped per session — otherwise dragging a card in one session's Graphs + // would silently reorder every other session's Graphs too, since it's the + // same metric/heatmap ids everywhere. The parent remounts this component + // (via `key={sessionId}`) whenever the session changes, so this only needs + // to be read once per mount. + const storageKey = sessionId + ? `${PANEL_ORDER_STORAGE_KEY}-${sessionId}` + : PANEL_ORDER_STORAGE_KEY; + const [order, setOrder] = useState(() => { try { - const saved = localStorage.getItem(PANEL_ORDER_STORAGE_KEY); + const saved = localStorage.getItem(storageKey); return saved ? JSON.parse(saved) : []; } catch { return []; @@ -153,9 +162,9 @@ function ResultsGraphsPlot({ chartData, onToggleRun }) { useEffect(() => { if (order.length > 0) { - localStorage.setItem(PANEL_ORDER_STORAGE_KEY, JSON.stringify(order)); + localStorage.setItem(storageKey, JSON.stringify(order)); } - }, [order]); + }, [order, storageKey]); const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 5 } }), @@ -354,6 +363,7 @@ SortableCard.defaultProps = { ResultsGraphsPlot.propTypes = { chartData: PropTypes.object.isRequired, onToggleRun: PropTypes.func.isRequired, + sessionId: PropTypes.number, }; export default ResultsGraphsPlot; From 8ef5a4e45120f117e0580013f9f0a1a82e7edf7a Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 13:02:02 -0400 Subject: [PATCH 214/308] feat: drag explainers from sidebar to center to create them Explainer list items are now draggable into the central model detail view, mirroring how models are dragged into the session view. Dropping an explainer opens its creation dialog preselected. The compact card header title now flexes to fill the row instead of a fixed 300px width, keeping the explainer display name and instance name on one line when space allows. --- .../components/explainers/ExplainersCard.jsx | 5 ++- .../explainers/ExplainersSidebar.jsx | 20 +++++++---- .../src/components/models/ModelsContext.jsx | 15 ++++++++ .../models/SessionVisualization.jsx | 36 +++++++++++++++---- .../components/models/model/ModelListItem.jsx | 6 ++-- 5 files changed, 65 insertions(+), 17 deletions(-) diff --git a/DashAI/front/src/components/explainers/ExplainersCard.jsx b/DashAI/front/src/components/explainers/ExplainersCard.jsx index b3c1d8e43..d58949f27 100644 --- a/DashAI/front/src/components/explainers/ExplainersCard.jsx +++ b/DashAI/front/src/components/explainers/ExplainersCard.jsx @@ -108,7 +108,10 @@ export default function ExplainersCard({ justifyContent="space-between" alignItems="center" > - + (component.display_name || component.name).toLowerCase().includes(query) || @@ -29,7 +30,8 @@ export default function ExplainersSidebar({ run, session, onCreated }) { const [localExplainers, setLocalExplainers] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [loading, setLoading] = useState(false); - const [creator, setCreator] = useState(null); + const { explainerToCreate, openExplainerCreator, closeExplainerCreator } = + useModels(); const taskName = session?.task_name; const modelName = run?.model_name; @@ -120,8 +122,12 @@ export default function ExplainersSidebar({ run, session, onCreated }) { setCreator({ scope, name: explainer.name })} + draggable + dragType="application/x-dashai-explainer" + dragPayload={{ scope, name: explainer.name }} + onClick={() => + openExplainerCreator({ scope, name: explainer.name }) + } /> ))} @@ -191,14 +197,14 @@ export default function ExplainersSidebar({ run, session, onCreated }) { )} - {creator && ( + {explainerToCreate && ( setCreator(null)} + onCancel={closeExplainerCreator} /> )} diff --git a/DashAI/front/src/components/models/ModelsContext.jsx b/DashAI/front/src/components/models/ModelsContext.jsx index bf0e9b729..d7c9ccfaf 100644 --- a/DashAI/front/src/components/models/ModelsContext.jsx +++ b/DashAI/front/src/components/models/ModelsContext.jsx @@ -91,11 +91,23 @@ export function ModelsProvider({ children }) { const [sessionRightContent, setSessionRightContent] = useState(null); const [runDetailTab, setRunDetailTab] = useState(null); const [explainerRefreshTrigger, setExplainerRefreshTrigger] = useState(0); + const [explainerToCreate, setExplainerToCreate] = useState(null); const triggerExplainerRefresh = useCallback(() => { setExplainerRefreshTrigger((prev) => prev + 1); }, []); + // Open the explainer creation dialog for a given {scope, name}. Shared so both + // the sidebar (click) and the central view (drag and drop) can trigger it, + // mirroring how selectModel opens the add model dialog. + const openExplainerCreator = useCallback((explainer) => { + setExplainerToCreate(explainer); + }, []); + + const closeExplainerCreator = useCallback(() => { + setExplainerToCreate(null); + }, []); + const selectModel = useCallback((model) => { setSelectedModel(model); setConfigOpen(true); @@ -186,6 +198,9 @@ export function ModelsProvider({ children }) { setRunDetailTab, explainerRefreshTrigger, triggerExplainerRefresh, + explainerToCreate, + openExplainerCreator, + closeExplainerCreator, }; return ( diff --git a/DashAI/front/src/components/models/SessionVisualization.jsx b/DashAI/front/src/components/models/SessionVisualization.jsx index 0a52fe476..674d9adbb 100644 --- a/DashAI/front/src/components/models/SessionVisualization.jsx +++ b/DashAI/front/src/components/models/SessionVisualization.jsx @@ -48,6 +48,7 @@ export default function SessionVisualization() { lastAddedRunId, clearLastAddedRunId, selectModel, + openExplainerCreator, explainerRefreshTrigger, triggerExplainerRefresh, } = useModels(); @@ -61,7 +62,11 @@ export default function SessionVisualization() { useEffect(() => { const onStart = (e) => { - if (e.dataTransfer.types.includes("application/x-dashai-model")) { + const types = e.dataTransfer.types; + if ( + types.includes("application/x-dashai-model") || + types.includes("application/x-dashai-explainer") + ) { setIsDragging(true); } }; @@ -214,14 +219,20 @@ export default function SessionVisualization() { data-session-viz onDragOver={(e) => { if (e.dataTransfer.types.includes("Files")) e.preventDefault(); - if (!e.dataTransfer.types.includes("application/x-dashai-model")) + if ( + !e.dataTransfer.types.includes("application/x-dashai-model") && + !e.dataTransfer.types.includes("application/x-dashai-explainer") + ) return; e.preventDefault(); e.dataTransfer.dropEffect = "copy"; }} onDragEnter={(e) => { if (e.dataTransfer.types.includes("Files")) e.preventDefault(); - if (!e.dataTransfer.types.includes("application/x-dashai-model")) + if ( + !e.dataTransfer.types.includes("application/x-dashai-model") && + !e.dataTransfer.types.includes("application/x-dashai-explainer") + ) return; e.preventDefault(); setIsDragOver(true); @@ -233,13 +244,24 @@ export default function SessionVisualization() { } }} onDrop={(e) => { + const types = e.dataTransfer.types; + const isModel = types.includes("application/x-dashai-model"); + const isExplainer = types.includes("application/x-dashai-explainer"); + if (!isModel && !isExplainer) return; e.preventDefault(); setIsDragOver(false); try { - const model = JSON.parse( - e.dataTransfer.getData("application/x-dashai-model"), - ); - if (model?.name) selectModel(model); + if (isExplainer) { + const explainer = JSON.parse( + e.dataTransfer.getData("application/x-dashai-explainer"), + ); + if (explainer?.name) openExplainerCreator(explainer); + } else { + const model = JSON.parse( + e.dataTransfer.getData("application/x-dashai-model"), + ); + if (model?.name) selectModel(model); + } } catch { // ignore invalid drops } diff --git a/DashAI/front/src/components/models/model/ModelListItem.jsx b/DashAI/front/src/components/models/model/ModelListItem.jsx index c10ac578b..405cbb9a0 100644 --- a/DashAI/front/src/components/models/model/ModelListItem.jsx +++ b/DashAI/front/src/components/models/model/ModelListItem.jsx @@ -9,6 +9,8 @@ export default function ModelListItem({ model, disabled = false, draggable = true, + dragType = "application/x-dashai-model", + dragPayload, onClick, ...props }) { @@ -65,8 +67,8 @@ export default function ModelListItem({ !disabled && draggable ? (e) => { e.dataTransfer.setData( - "application/x-dashai-model", - JSON.stringify(model), + dragType, + JSON.stringify(dragPayload ?? model), ); e.dataTransfer.effectAllowed = "copy"; setCustomDragImage(e); From 2cb01ce42987ae4d7f3fa8490a4626ef11227907 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 13:06:32 -0400 Subject: [PATCH 215/308] fix: adjust fullscreen dimensions to optimize content fit and prevent scrollbars --- .../front/src/components/shared/ArtifactViewer.jsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx index 31a8d03b9..60bd6dd1f 100644 --- a/DashAI/front/src/components/shared/ArtifactViewer.jsx +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -148,11 +148,12 @@ export default function ArtifactViewer({ height: 44, }; - // Fill most of the viewport in the fullscreen view, leaving room for the - // header bar and padding. + // Fill 75% of the viewport in the fullscreen view, minus the card's vertical + // padding (p: 4 => 32px top + 32px bottom) so the content fits within the + // 75vh card without producing a scrollbar. const fullscreenHeight = typeof window !== "undefined" - ? Math.max(360, Math.round(window.innerHeight * 0.8)) + ? Math.max(360, Math.round(window.innerHeight * 0.75) - 64) : 720; return ( @@ -340,9 +341,8 @@ export default function ArtifactViewer({ Date: Fri, 17 Jul 2026 15:42:06 -0400 Subject: [PATCH 216/308] feat: reuse the explorer plot editor for explainer plot edits Replace the plotly editable mode dialog with the explorer layout editor beside a live preview, so explainer plots are edited the same way as explorer plots. Add line color and waterfall (increasing, decreasing, totals) color controls so trace colors work for partial dependence and SHAP plots, a transparent option in the color picker, and PNG or SVG choices on the plot download button. Edited figures are flagged as overridden and rendered verbatim instead of being themed again, so saved colors and backgrounds persist. --- .../back/api/api_v1/endpoints/explainers.py | 4 + .../plotLayout/DebouncedColorPicker.jsx | 111 ++++++++--- .../explorer/plotLayout/forms/TraceForm.jsx | 74 ++++++- .../visualizations/PlotlyJsonVisualizer.jsx | 79 ++++++-- .../components/shared/ArtifactRenderer.jsx | 10 +- .../src/components/shared/ArtifactViewer.jsx | 188 +++++++++++++----- .../src/utils/i18n/locales/en/datasets.json | 5 + 7 files changed, 368 insertions(+), 103 deletions(-) diff --git a/DashAI/back/api/api_v1/endpoints/explainers.py b/DashAI/back/api/api_v1/endpoints/explainers.py index b46f585e9..16d89293b 100755 --- a/DashAI/back/api/api_v1/endpoints/explainers.py +++ b/DashAI/back/api/api_v1/endpoints/explainers.py @@ -75,6 +75,10 @@ def collect_leaves(items): leaf = leaves_by_index.get(idx) if leaf is not None and leaf.get("type") == "plotly": leaf["payload"] = figure if isinstance(figure, str) else json.dumps(figure) + # Flag so the frontend renders the user's edited figure verbatim + # instead of re-applying the app theme (which would clobber the + # edited colors/background). + leaf["overridden"] = True return artifacts diff --git a/DashAI/front/src/components/notebooks/explorer/plotLayout/DebouncedColorPicker.jsx b/DashAI/front/src/components/notebooks/explorer/plotLayout/DebouncedColorPicker.jsx index a70a839be..b70639741 100644 --- a/DashAI/front/src/components/notebooks/explorer/plotLayout/DebouncedColorPicker.jsx +++ b/DashAI/front/src/components/notebooks/explorer/plotLayout/DebouncedColorPicker.jsx @@ -1,5 +1,17 @@ import React, { useRef, useState, useEffect } from "react"; -import { Box, TextField } from "@mui/material"; +import { Box, TextField, Checkbox, FormControlLabel } from "@mui/material"; +import { useTranslation } from "react-i18next"; + +const TRANSPARENT = "rgba(0,0,0,0)"; + +// Any fully transparent CSS color (transparent keyword or an rgba/hsla with a +// zero alpha) counts as "transparent" for the toggle. +const isTransparentColor = (color) => + typeof color === "string" && + (color === "transparent" || + /^rgba?\(\s*[\d.]+\s*,\s*[\d.]+\s*,\s*[\d.]+\s*,\s*0(\.0+)?\s*\)$/.test( + color, + )); export default function DebouncedColorPicker({ label, @@ -7,8 +19,15 @@ export default function DebouncedColorPicker({ onChange, delay = 300, }) { + const { t } = useTranslation(["datasets", "common"]); const [localValue, setLocalValue] = useState(value || "#000000"); const timeoutRef = useRef(null); + const transparent = isTransparentColor(value); + // Remember the last non-transparent color so unchecking "Transparent" + // restores it instead of falling back to black. + const lastColorRef = useRef( + value && !isTransparentColor(value) ? value : "#000000", + ); // Helper to expand 3-digit hex to 6-digit const expandHex = (hex) => { @@ -80,6 +99,9 @@ export default function DebouncedColorPicker({ useEffect(() => { setLocalValue(value || "#000000"); + if (value && !isTransparentColor(value)) { + lastColorRef.current = value; + } }, [value]); const handleColorChange = (e) => { @@ -116,33 +138,72 @@ export default function DebouncedColorPicker({ } }; + const handleTransparentToggle = (e) => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + if (e.target.checked) { + onChange(TRANSPARENT); + } else { + // Leaving transparent: restore the color that was set before. + const restored = lastColorRef.current || "#000000"; + setLocalValue(restored); + onChange(restored); + } + }; + return ( - - + + + + + } + label={t("datasets:label.transparent", "Transparent")} + sx={{ + m: 0, + mt: 1.5, + "& .MuiFormControlLabel-label": { fontSize: "0.75rem" }, + }} /> ); diff --git a/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/TraceForm.jsx b/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/TraceForm.jsx index 2cee42fb5..639b66b02 100644 --- a/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/TraceForm.jsx +++ b/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/TraceForm.jsx @@ -19,8 +19,24 @@ export default function TraceForm({ }) { const { t } = useTranslation(["datasets", "common"]); + // Pick a sensible fallback color when the trace has none set. The colorway + // may be absent (e.g. backend explainer figures without a template), so guard + // every access instead of assuming layout.template.layout.colorway exists. + const colorway = layout?.template?.layout?.colorway; + const fallbackColor = (i) => + (Array.isArray(colorway) && colorway[i % colorway.length]) || "#1f77b4"; + + // Colors live in different fields per trace type: waterfall keeps them under + // increasing/decreasing/totals.marker.color, line-mode scatters under + // line.color, everything else under marker.color. + const type = trace.type || "scatter"; + const isScatter = type === "scatter" || type === "scattergl"; + const mode = trace.mode || ""; + const showLine = isScatter && (mode === "" || mode.includes("lines")); + const showMarker = !isScatter || mode === "" || mode.includes("markers"); + // px.imshow() (correlation matrix, density heatmap) sets coloraxis: "coloraxis" - // on the trace, so the colorbar lives in layout.coloraxis.colorbar — not trace.colorbar. + // on the trace, so the colorbar lives in layout.coloraxis.colorbar, not trace.colorbar. // Other heatmap types that don't reference a shared coloraxis keep their colorbar // directly on the trace. const colorbarInLayout = Boolean(trace.coloraxis); @@ -51,22 +67,58 @@ export default function TraceForm({ fullWidth /> - {/* --- Scatter Plot Options --- */} - {!usesColormap(trace) && ( + {/* --- Waterfall Options (e.g. SHAP force plots): two/three colors --- */} + {type === "waterfall" && ( <> + handleTraceChange(index, "increasing.marker.color", color) } + /> + - handleTraceChange(index, "marker.color", color) + handleTraceChange(index, "decreasing.marker.color", color) } /> + + handleTraceChange(index, "totals.marker.color", color) + } + /> + + )} + + {/* --- Line / Marker Options --- */} + {type !== "waterfall" && !usesColormap(trace) && ( + <> + {showLine && ( + + handleTraceChange(index, "line.color", color) + } + /> + )} + {showMarker && ( + + handleTraceChange(index, "marker.color", color) + } + /> + )} )} diff --git a/DashAI/front/src/components/notebooks/explorer/visualizations/PlotlyJsonVisualizer.jsx b/DashAI/front/src/components/notebooks/explorer/visualizations/PlotlyJsonVisualizer.jsx index b196fff97..1fca87070 100644 --- a/DashAI/front/src/components/notebooks/explorer/visualizations/PlotlyJsonVisualizer.jsx +++ b/DashAI/front/src/components/notebooks/explorer/visualizations/PlotlyJsonVisualizer.jsx @@ -2,7 +2,7 @@ import React, { useState, useRef } from "react"; import PropTypes from "prop-types"; import { useTheme } from "@mui/material/styles"; import Plot from "react-plotly.js"; -import { Box, IconButton, Tooltip } from "@mui/material"; +import { Box, IconButton, Tooltip, Menu, MenuItem } from "@mui/material"; import Dialog from "@mui/material/Dialog"; import ZoomInIcon from "@mui/icons-material/ZoomIn"; import ZoomOutIcon from "@mui/icons-material/ZoomOut"; @@ -15,9 +15,15 @@ const MIN_WIDTH = 300; const MIN_HEIGHT_MINIMALIST = 200; const MIN_HEIGHT_NORMAL = 500; -function PlotlyJsonVisualizer({ data, minimalist = false }) { +function PlotlyJsonVisualizer({ + data, + minimalist = false, + fillHeight = false, +}) { const theme = useTheme(); const [expanded, setExpanded] = useState(false); + // Which plot ref + anchor the download format menu (PNG/SVG) applies to. + const [downloadMenu, setDownloadMenu] = useState(null); const plotRef = useRef(null); const fullscreenPlotRef = useRef(null); @@ -40,10 +46,12 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { title: "", }, } - : { - ...parsedData, - layout: { ...parsedData.layout, height: MIN_HEIGHT_NORMAL }, - }; + : fillHeight + ? { ...parsedData, layout: { ...parsedData.layout } } + : { + ...parsedData, + layout: { ...parsedData.layout, height: MIN_HEIGHT_NORMAL }, + }; const getPlotly = (ref) => { const el = ref?.current?.el; @@ -103,12 +111,12 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { }); }; - const handleDownload = (ref) => { + const handleDownload = (ref, format = "svg") => { const el = ref?.current?.el; const Plotly = getPlotly(ref); if (el && Plotly) { Plotly.downloadImage(el, { - format: "svg", + format, filename: "dashai-plot", height: 800, width: 1200, @@ -246,10 +254,16 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { )} - + handleDownload(plotRefProp)} + onClick={(e) => + setDownloadMenu({ + mouseX: e.clientX, + mouseY: e.clientY, + ref: plotRefProp, + }) + } sx={downloadBtnSx} > @@ -265,8 +279,12 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { position: "relative", width: "100%", minWidth: MIN_WIDTH, - minHeight: minimalist ? MIN_HEIGHT_MINIMALIST : MIN_HEIGHT_NORMAL, - height: minimalist ? "100%" : "auto", + minHeight: fillHeight + ? 0 + : minimalist + ? MIN_HEIGHT_MINIMALIST + : MIN_HEIGHT_NORMAL, + height: minimalist || fillHeight ? "100%" : "auto", overflow: "hidden", display: "flex", justifyContent: "center", @@ -283,8 +301,12 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { revision={revisionRef.current} style={{ width: "100%", - minHeight: minimalist ? MIN_HEIGHT_MINIMALIST : MIN_HEIGHT_NORMAL, - height: minimalist ? "100%" : MIN_HEIGHT_NORMAL, + minHeight: fillHeight + ? 0 + : minimalist + ? MIN_HEIGHT_MINIMALIST + : MIN_HEIGHT_NORMAL, + height: minimalist || fillHeight ? "100%" : MIN_HEIGHT_NORMAL, }} config={plotConfig} useResizeHandler={true} @@ -340,6 +362,34 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) {
)} + + setDownloadMenu(null)} + anchorReference="anchorPosition" + anchorPosition={ + downloadMenu + ? { top: downloadMenu.mouseY, left: downloadMenu.mouseX } + : undefined + } + > + { + handleDownload(downloadMenu.ref, "png"); + setDownloadMenu(null); + }} + > + PNG + + { + handleDownload(downloadMenu.ref, "svg"); + setDownloadMenu(null); + }} + > + SVG + + ); } @@ -347,6 +397,7 @@ function PlotlyJsonVisualizer({ data, minimalist = false }) { PlotlyJsonVisualizer.propTypes = { data: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired, minimalist: PropTypes.bool, + fillHeight: PropTypes.bool, }; export default PlotlyJsonVisualizer; diff --git a/DashAI/front/src/components/shared/ArtifactRenderer.jsx b/DashAI/front/src/components/shared/ArtifactRenderer.jsx index 9580c7c5e..6bd20fbf6 100644 --- a/DashAI/front/src/components/shared/ArtifactRenderer.jsx +++ b/DashAI/front/src/components/shared/ArtifactRenderer.jsx @@ -35,8 +35,15 @@ export default function ArtifactRenderer({ artifact, height = 380 }) { const themedLayout = useMemo(() => { if (!parsedFigure) return {}; + // User-edited (overridden) figures are rendered verbatim so their saved + // colors/background survive; only strip fixed sizing. Non-overridden + // figures follow the app light/dark theme. + if (artifact.overridden) { + const { width: _w, height: _h, ...rest } = parsedFigure.layout ?? {}; + return rest; + } return applyThemeToLayout(parsedFigure.layout, theme); - }, [parsedFigure, theme]); + }, [parsedFigure, theme, artifact.overridden]); const highlightedCells = useMemo(() => { if (artifact.type !== "table") return new Set(); @@ -108,6 +115,7 @@ ArtifactRenderer.propTypes = { type: PropTypes.string.isRequired, payload: PropTypes.any, title: PropTypes.string, + overridden: PropTypes.bool, }).isRequired, height: PropTypes.number, }; diff --git a/DashAI/front/src/components/shared/ArtifactViewer.jsx b/DashAI/front/src/components/shared/ArtifactViewer.jsx index 60bd6dd1f..cbff74873 100644 --- a/DashAI/front/src/components/shared/ArtifactViewer.jsx +++ b/DashAI/front/src/components/shared/ArtifactViewer.jsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import PropTypes from "prop-types"; import { Box, @@ -8,20 +8,21 @@ import { Typography, Tooltip, Dialog, + Divider, } from "@mui/material"; import DownloadIcon from "@mui/icons-material/Download"; import EditIcon from "@mui/icons-material/Edit"; import FullscreenIcon from "@mui/icons-material/Fullscreen"; -import SaveIcon from "@mui/icons-material/Save"; import RestartAltIcon from "@mui/icons-material/RestartAlt"; import CloseIcon from "@mui/icons-material/Close"; import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; import { useTheme, alpha } from "@mui/material/styles"; -import Plot from "react-plotly.js"; import { useTranslation } from "react-i18next"; import ArtifactRenderer from "./ArtifactRenderer"; +import PlotLayoutForm from "../notebooks/explorer/plotLayout/PlotLayoutForm"; +import PlotlyJsonVisualizer from "../notebooks/explorer/visualizations/PlotlyJsonVisualizer"; import { applyThemeToLayout } from "../../utils/plotlyTheme"; import { downloadArtifact } from "../../utils/downloadArtifact"; @@ -48,9 +49,22 @@ export default function ArtifactViewer({ const [fullscreen, setFullscreen] = useState(false); const [fullscreenIndex, setFullscreenIndex] = useState(siblingIndex); const hasSiblings = siblingArtifacts && siblingArtifacts.length > 1; + + // The parent list does not refetch after a save, so hold the edited payload + // locally and render from it, making a save show immediately. Cleared when + // the underlying artifact prop actually changes (e.g. a real refetch/reset). + const [localPayload, setLocalPayload] = useState(null); + useEffect(() => { + setLocalPayload(null); + }, [artifact.payload]); + const shownArtifact = + localPayload != null + ? { ...artifact, payload: localPayload, overridden: true } + : artifact; + const fullscreenArtifact = hasSiblings ? siblingArtifacts[fullscreenIndex] - : artifact; + : shownArtifact; const openFullscreen = () => { setFullscreenIndex(siblingIndex); @@ -64,50 +78,53 @@ export default function ArtifactViewer({ siblingArtifacts.length, ); }; - // editInitial is the figure the editable Plot mounts with (set once per edit - // session). editFigureRef holds the latest edited figure, captured in - // onUpdate WITHOUT setState so Plotly's own edit events do not trigger a - // React re render that would re run Plotly.react and loop the page into a - // freeze. - const [editInitial, setEditInitial] = useState(null); - const editFigureRef = useRef(null); + // Working copies the form editor mutates. The preview Plot renders from these + // directly, so edits made in the form show live; the form is the single + // source of truth, so there is no Plotly edit-event feedback loop. + const [editData, setEditData] = useState(null); + const [editLayout, setEditLayout] = useState(null); const plotWrapRef = useRef(null); const isPlotly = artifact.type === "plotly"; const figure = useMemo(() => { if (!isPlotly) return null; try { - return typeof artifact.payload === "string" - ? JSON.parse(artifact.payload) - : artifact.payload; + return typeof shownArtifact.payload === "string" + ? JSON.parse(shownArtifact.payload) + : shownArtifact.payload; } catch (error) { console.error("Invalid plotly payload", error); return null; } - }, [artifact, isPlotly]); + }, [shownArtifact, isPlotly]); const startEdit = () => { if (!figure?.data) return; - const initial = { - data: JSON.parse(JSON.stringify(figure.data)), - layout: applyThemeToLayout(figure.layout, theme), - }; - setEditInitial(initial); - editFigureRef.current = initial; + setEditData(structuredClone(figure.data)); + // An already-overridden figure keeps its saved colors; a fresh one is + // themed so the editor starts from the current on-screen appearance. + setEditLayout( + shownArtifact.overridden + ? structuredClone(figure.layout ?? {}) + : applyThemeToLayout(figure.layout, theme), + ); setEditing(true); }; const closeEdit = () => { setEditing(false); - setEditInitial(null); - editFigureRef.current = null; + setEditData(null); + setEditLayout(null); }; const saveEdit = async () => { try { - if (onSaveEdit && editFigureRef.current) { - await onSaveEdit(editFigureRef.current); + const edited = { data: editData, layout: editLayout }; + if (onSaveEdit && editData) { + await onSaveEdit(edited); } + // Reflect the save immediately, since the parent list is not refetched. + setLocalPayload(JSON.stringify(edited)); closeEdit(); } catch (error) { console.error("Failed to save plot edits", error); @@ -218,7 +235,14 @@ export default function ArtifactViewer({ )} {canReset && onResetEdit && ( - + { + setLocalPayload(null); + onResetEdit(); + }} + > @@ -255,37 +279,97 @@ export default function ArtifactViewer({ {/* The instance label is shown once by the parent; suppress the per artifact title so it is not repeated on every block. */} - + - {/* Edit dialog: editable plotly figure. The Plot mounts once with - editInitial and reports edits through onUpdate into a ref; we never - feed those edits back as props, so Plotly does not re render in a - loop. */} - - - {onSaveEdit && ( - - - - - - )} - + {/* Edit dialog: a live plot preview beside the shared form layout + editor (reused from the explorer view). The form mutates editData / + editLayout and the preview renders from them, so edits show live. */} + + + + {t("explainers:button.editPlot")} + + - {editInitial && ( - { - editFigureRef.current = { data: fig.data, layout: fig.layout }; + + + {/* Live preview: reuses the explorer's plot viewer so it shows the + same overlay icon buttons (zoom, reset, fullscreen, download). */} + - )} + > + {editData && ( + + )} + + {/* Form layout/trace editor */} + + {editData && ( + + )} + + {/* Fullscreen view: dark blurred lightbox overlay, rounded/shadowed diff --git a/DashAI/front/src/utils/i18n/locales/en/datasets.json b/DashAI/front/src/utils/i18n/locales/en/datasets.json index faeae0ed6..bdcddc60e 100644 --- a/DashAI/front/src/utils/i18n/locales/en/datasets.json +++ b/DashAI/front/src/utils/i18n/locales/en/datasets.json @@ -255,6 +255,11 @@ "margins": "Margins", "marginTop": "Margin Top", "markerColor": "Marker Color", + "lineColor": "Line Color", + "increasingColor": "Increasing Color", + "decreasingColor": "Decreasing Color", + "totalsColor": "Totals Color", + "transparent": "Transparent", "max": "Max", "maxLength": "Max Length", "mean": "Mean", From 3e09d8794a119bf66b1dfc23ab067495cb716087 Mon Sep 17 00:00:00 2001 From: Irozuku Date: Fri, 17 Jul 2026 15:42:17 -0400 Subject: [PATCH 217/308] style: compact the plot edit form inputs and spacing Shrink the input boxes and recenter their labels, tidy the color picker layout with a transparent toggle on its own line, and even out the vertical and horizontal gaps between fields across the form sections. --- .../explorer/plotLayout/PlotLayoutForm.jsx | 18 +++++++++++++++++- .../plotLayout/forms/DimensionsForm.jsx | 2 +- .../explorer/plotLayout/forms/GeneralForm.jsx | 8 ++++---- .../explorer/plotLayout/forms/LegendForm.jsx | 4 ++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/DashAI/front/src/components/notebooks/explorer/plotLayout/PlotLayoutForm.jsx b/DashAI/front/src/components/notebooks/explorer/plotLayout/PlotLayoutForm.jsx index c20f4a972..cb16b741b 100644 --- a/DashAI/front/src/components/notebooks/explorer/plotLayout/PlotLayoutForm.jsx +++ b/DashAI/front/src/components/notebooks/explorer/plotLayout/PlotLayoutForm.jsx @@ -196,7 +196,23 @@ const PlotLayoutForm = memo(function PlotLayoutForm({ {/* Active section content */} - + {validSection === "general" && ( )} diff --git a/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/DimensionsForm.jsx b/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/DimensionsForm.jsx index c4f784cfa..dffbd461b 100644 --- a/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/DimensionsForm.jsx +++ b/DashAI/front/src/components/notebooks/explorer/plotLayout/forms/DimensionsForm.jsx @@ -7,7 +7,7 @@ export default function DimensionsForm({ data, handleTraceChange }) { const theme = useTheme(); return ( - + {data[0].dimensions.map((dim, idx) => ( + {/* Title */} {t("datasets:label.title", "Title")} @@ -48,7 +48,7 @@ export default function GeneralForm({ layout, handleChange }) { fullWidth /> - + {t("datasets:label.margins")} - + - + + {/* Visibility & Orientation */} {t("datasets:label.position")} - + Date: Fri, 17 Jul 2026 16:51:19 -0400 Subject: [PATCH 218/308] Refactor PredictionCard and RunResults components: enhance layout and user interaction with improved display of prediction details and filtering options. Update localization files to include new labels for manual predictions in multiple languages. --- .../src/components/models/PredictionCard.jsx | 314 +++++++------ .../src/components/models/RunResults.jsx | 422 +++++++++++------- .../src/utils/i18n/locales/de/models.json | 1 + .../src/utils/i18n/locales/en/models.json | 1 + .../src/utils/i18n/locales/es/models.json | 1 + .../src/utils/i18n/locales/pt/models.json | 1 + .../src/utils/i18n/locales/zh/models.json | 1 + 7 files changed, 435 insertions(+), 306 deletions(-) diff --git a/DashAI/front/src/components/models/PredictionCard.jsx b/DashAI/front/src/components/models/PredictionCard.jsx index ca85a7d66..6932765fd 100644 --- a/DashAI/front/src/components/models/PredictionCard.jsx +++ b/DashAI/front/src/components/models/PredictionCard.jsx @@ -1,24 +1,19 @@ import React, { useState, useCallback, useEffect } from "react"; import PropTypes from "prop-types"; import { - Card, - CardContent, Typography, IconButton, Chip, Box, Tooltip, - Button, - Collapse, CircularProgress, } from "@mui/material"; import DeleteConfirmationModal from "../threeSectionLayout/DeleteConfirmationModal"; import { - ExpandMore as ExpandMoreIcon, - ExpandLess as ExpandLessIcon, Delete as DeleteIcon, Download as DownloadIcon, Dataset as DatasetIcon, + CalendarToday as CalendarTodayIcon, } from "@mui/icons-material"; import { getPredictionStatus } from "../../utils/predictionStatus"; import { deletePrediction } from "../../api/predict"; @@ -49,25 +44,14 @@ export default function PredictionCard({ onUpdate, targetColumn = null, datasetSample = null, + displayNumber = null, }) { - const [expanded, setExpanded] = useState(() => { - const saved = localStorage.getItem(`prediction-${prediction.id}-expanded`); - return saved !== null ? JSON.parse(saved) : true; - }); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [columnTypes, setColumnTypes] = useState({}); const theme = useTheme(); const { enqueueSnackbar } = useSnackbar(); const { t } = useTranslation(["prediction", "datasets", "common"]); - // Persist expanded state - useEffect(() => { - localStorage.setItem( - `prediction-${prediction.id}-expanded`, - JSON.stringify(expanded), - ); - }, [expanded, prediction.id]); - // Fetch column types when results path changes useEffect(() => { if (!prediction?.results_path) return; @@ -177,147 +161,182 @@ export default function PredictionCard({ return ( <> - - - {/* Header with status and dataset info */} - - - - {t("prediction:label.prediction")} #{prediction.id} - - - {formatDate(prediction.created)} - + + {/* Header with status and dataset info */} + + + + {t("prediction:label.prediction")} # + {displayNumber ?? prediction.id} + + + } + variant="outlined" + size="small" + sx={{ + height: 26, + "& .MuiChip-icon": { + color: "text.secondary", + ml: 1.5, + fontSize: "1rem", + }, + "& .MuiChip-label": { px: 2 }, + }} + label={ + + + {t("common:created")} + + + {formatDate(prediction.created)} + + + } + /> {prediction.dataset_id && ( - } + variant="outlined" + size="small" sx={{ - display: "flex", - alignItems: "center", - gap: 1, - mt: 1, + height: 26, + "& .MuiChip-icon": { + color: "text.secondary", + ml: 1.5, + fontSize: "1rem", + }, + "& .MuiChip-label": { px: 2 }, }} - > - - - {prediction.dataset?.name || - t("datasets:label.unknownDataset")} - - + label={ + + + {t("common:dataset")} + + + {prediction.dataset?.name || + t("datasets:label.unknownDataset")} + + + } + /> )} - - - - - - - - - - - - setDeleteDialogOpen(true)} - disabled={isRunning} - color="error" - > - - - - - + + + + + + + + + + + + setDeleteDialogOpen(true)} + disabled={isRunning} + color="error" + > + + + + + + - {/* Expandable Results */} - {isFinished && ( - - - - - - - {t("prediction:label.resultsPreview")} - - - - - - - - )} - - {/* Show loading indicator if prediction is running */} - {isRunning && ( + {/* Results */} + {isFinished && ( + + + {t("prediction:label.resultsPreview")} + - - - {t("prediction:label.predictionInProgress")} - + - )} - - + + )} + + {/* Show loading indicator if prediction is running */} + {isRunning && ( + + + + {t("prediction:label.predictionInProgress")} + + + )} + { + const numbers = new Map(); + [ + predictions.filter((p) => p.dataset_id), + predictions.filter((p) => !p.dataset_id), + ].forEach((group) => { + [...group] + .sort((a, b) => a.id - b.id) + .forEach((p, index) => numbers.set(p.id, index + 1)); + }); + return numbers; + }, [predictions]); const [internalVisible, setInternalVisible] = useState(() => { if (run.status === 0) return false; const saved = localStorage.getItem(`run-${run.id}-results-visible`); @@ -90,6 +115,8 @@ export default function RunResults({ const [localExpanded, setLocalExpanded] = useState(true); const [datasetExpanded, setDatasetExpanded] = useState(true); const [manualExpanded, setManualExpanded] = useState(true); + // "all" | "dataset" | "manual" — which prediction section(s) are shown + const [predictionFilter, setPredictionFilter] = useState("all"); const [showDatasetPanel, setShowDatasetPanel] = useState(false); const datasetRunRef = useRef(null); const [datasetRunState, setDatasetRunState] = useState({ @@ -649,36 +676,103 @@ export default function RunResults({ {activeTab === 2 && isFinished && ( - - - - - - - - + + { + if (newValue !== null) setPredictionFilter(newValue); + }} + size="small" + > + + {t("common:all")} + + {predictions.length} + + + + {t("common:dataset")} + + {predictions.filter((p) => p.dataset_id).length} + + + + {t("models:label.manual")} + + {predictions.filter((p) => !p.dataset_id).length} + + + + + + {predictionFilter !== "manual" && ( + + )} + {predictionFilter !== "dataset" && ( + + )} + + - + {predictionFilter !== "manual" && ( - - - {t("models:label.datasetPredictions")} - - p.dataset_id).length} + + + + {t("models:label.datasetPredictions")} + + p.dataset_id).length} + size="small" + color="primary" + /> + + + onClick={() => setDatasetExpanded((prev) => !prev)} + > + {datasetExpanded ? ( + + ) : ( + + )} + - setDatasetExpanded((prev) => !prev)} - > - {datasetExpanded ? ( - + + {predictions.filter((p) => p.dataset_id).length === 0 ? ( + + {t("models:label.noDatasetPredictionsYet")} + ) : ( - + + {predictions + .filter((p) => p.dataset_id) + .map((prediction) => ( + + ))} + )} - + - - {predictions.filter((p) => p.dataset_id).length === 0 ? ( - - {t("models:label.noDatasetPredictionsYet")} - - ) : ( - - {predictions - .filter((p) => p.dataset_id) - .map((prediction) => ( - - ))} - - )} - - + )} - + {predictionFilter !== "dataset" && ( - - - {t("models:label.manualPredictions")} - - !p.dataset_id).length} + + + + {t("models:label.manualPredictions")} + + !p.dataset_id).length} + size="small" + color="primary" + /> + + + onClick={() => setManualExpanded((prev) => !prev)} + > + {manualExpanded ? ( + + ) : ( + + )} + - setManualExpanded((prev) => !prev)} - > - {manualExpanded ? ( - + + {predictions.filter((p) => !p.dataset_id).length === 0 ? ( + + {t("models:label.noManualPredictionsYet")} + ) : ( - + + {predictions + .filter((p) => !p.dataset_id) + .map((prediction) => ( + + ))} + )} - + - - {predictions.filter((p) => !p.dataset_id).length === 0 ? ( - - {t("models:label.noManualPredictionsYet")} - - ) : ( - - {predictions - .filter((p) => !p.dataset_id) - .map((prediction) => ( - - ))} - - )} - - + )} )} diff --git a/DashAI/front/src/utils/i18n/locales/de/models.json b/DashAI/front/src/utils/i18n/locales/de/models.json index d6521f2aa..13359f583 100644 --- a/DashAI/front/src/utils/i18n/locales/de/models.json +++ b/DashAI/front/src/utils/i18n/locales/de/models.json @@ -95,6 +95,7 @@ "localExplainer": "Lokales Erklärungsmodell", "localExplainers": "Lokale Erklärungsmodelle", "manualPredictions": "Manuelle Vorhersagen", + "manual": "Manuell", "metricsEmptyForDisplaySet": "Die Ergebnismetriken für {{set}} sind leer.", "metrics": "Metriken", "metricToOptimize": "Zu optimierende Metrik", diff --git a/DashAI/front/src/utils/i18n/locales/en/models.json b/DashAI/front/src/utils/i18n/locales/en/models.json index 26b80e2a1..d47a74424 100644 --- a/DashAI/front/src/utils/i18n/locales/en/models.json +++ b/DashAI/front/src/utils/i18n/locales/en/models.json @@ -95,6 +95,7 @@ "localExplainer": "Local Explainer", "localExplainers": "Local Explainers", "manualPredictions": "Manual Predictions", + "manual": "Manual", "metricsEmptyForDisplaySet": "The result metrics for {{set}} are empty.", "metrics": "Metrics", "metricToOptimize": "Metric to Optimize", diff --git a/DashAI/front/src/utils/i18n/locales/es/models.json b/DashAI/front/src/utils/i18n/locales/es/models.json index 7f1a26328..6b831bc67 100644 --- a/DashAI/front/src/utils/i18n/locales/es/models.json +++ b/DashAI/front/src/utils/i18n/locales/es/models.json @@ -96,6 +96,7 @@ "localExplainer": "Explicador Local", "localExplainers": "Explicadores Locales", "manualPredictions": "Predicciones Manuales", + "manual": "Manuales", "metricsEmptyForDisplaySet": "Las métricas de resultado para {{set}} están vacías.", "metrics": "Métricas", "metricToOptimize": "Métrica a Optimizar", diff --git a/DashAI/front/src/utils/i18n/locales/pt/models.json b/DashAI/front/src/utils/i18n/locales/pt/models.json index a4c14f486..3bada3084 100644 --- a/DashAI/front/src/utils/i18n/locales/pt/models.json +++ b/DashAI/front/src/utils/i18n/locales/pt/models.json @@ -96,6 +96,7 @@ "localExplainer": "Explicador Local", "localExplainers": "Explicadores Locais", "manualPredictions": "Previsões Manuais", + "manual": "Manuais", "metricsEmptyForDisplaySet": "As métricas de resultado para {{set}} estão vazias.", "metrics": "Métricas", "metricToOptimize": "Métrica a Otimizar", diff --git a/DashAI/front/src/utils/i18n/locales/zh/models.json b/DashAI/front/src/utils/i18n/locales/zh/models.json index af60230d5..1f920809d 100644 --- a/DashAI/front/src/utils/i18n/locales/zh/models.json +++ b/DashAI/front/src/utils/i18n/locales/zh/models.json @@ -95,6 +95,7 @@ "localExplainer": "局部解释器", "localExplainers": "局部解释器", "manualPredictions": "手动预测", + "manual": "手动", "metricsEmptyForDisplaySet": "{{set}} 的结果指标为空。", "metrics": "指标", "metricToOptimize": "待优化指标", From 8aad5d33521881901488c68d4266b7b30e7dbe17 Mon Sep 17 00:00:00 2001 From: Creylay Date: Fri, 17 Jul 2026 17:16:06 -0400 Subject: [PATCH 219/308] Refactor RunResults component: replace TrendingUpIcon with DatasetIcon and EditNoteIcon for improved clarity in button actions. Update tooltip handling for better user experience. --- DashAI/front/src/components/models/RunResults.jsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/DashAI/front/src/components/models/RunResults.jsx b/DashAI/front/src/components/models/RunResults.jsx index 3bd332399..99df0bee7 100644 --- a/DashAI/front/src/components/models/RunResults.jsx +++ b/DashAI/front/src/components/models/RunResults.jsx @@ -31,8 +31,9 @@ import { ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon, Add as AddIcon, - TrendingUp as TrendingUpIcon, Close as CloseIcon, + Dataset as DatasetIcon, + EditNote as EditNoteIcon, } from "@mui/icons-material"; import ExplainersCard from "../explainers/ExplainersCard"; import PredictionCard from "./PredictionCard"; @@ -746,7 +747,7 @@ export default function RunResults({