diff --git a/mostlyai/sdk/_data/file/base.py b/mostlyai/sdk/_data/file/base.py index 07c311ae..3bcb43d3 100644 --- a/mostlyai/sdk/_data/file/base.py +++ b/mostlyai/sdk/_data/file/base.py @@ -18,7 +18,7 @@ import re import time from abc import abstractmethod -from collections.abc import Generator, Iterable +from collections.abc import Generator, Iterable, Iterator from enum import Enum from pathlib import Path from typing import Any @@ -375,6 +375,17 @@ def handle_if_exists(self, if_exists: str = "fail") -> str: return "a" return "w" + def iter_partitions(self) -> Iterator[tuple[int, Path, pd.DataFrame]]: + """Iterate over dataset partitions yielding (index, file_path, dataframe).""" + for idx, file_path in enumerate(self.dataset.files): + data = pd.read_parquet(file_path) + yield idx, Path(file_path), data + + @property + def files(self) -> list[Path]: + """Get the list of partition files.""" + return [Path(f) for f in self.dataset.files] + class FileContainer(DataContainer): SCHEMES = ["http", "https"] diff --git a/mostlyai/sdk/_data/non_context.py b/mostlyai/sdk/_data/non_context.py index 170a1622..d2f5537c 100644 --- a/mostlyai/sdk/_data/non_context.py +++ b/mostlyai/sdk/_data/non_context.py @@ -12,19 +12,224 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Non-context foreign key handling module. + +This module provides functionality for handling non-context foreign keys in two modes: +1. Pull-phase: Mark which FKs should be null during data extraction +2. Assignment phase: + - ML-based: Use trained neural network models for intelligent FK matching + - Random: Fallback random sampling when ML models are not available + +Also includes PartitionedDataset for efficient handling of large partitioned datasets. +""" + +import hashlib +import json import logging -from copy import copy +from collections import defaultdict +from collections.abc import Iterator +from copy import copy as shallow_copy +from copy import deepcopy +from functools import lru_cache +from pathlib import Path +import numpy as np import pandas as pd +import pyarrow.parquet as pq +import torch +import torch.nn.functional as F +from pathvalidate import sanitize_filename +from torch import nn +from torch.utils.data import DataLoader, TensorDataset, random_split +from mostlyai.engine._encoding_types.tabular.categorical import ( + analyze_categorical, + analyze_reduce_categorical, + encode_categorical, +) +from mostlyai.engine._encoding_types.tabular.datetime import analyze_datetime, analyze_reduce_datetime, encode_datetime +from mostlyai.engine._encoding_types.tabular.numeric import analyze_numeric, analyze_reduce_numeric, encode_numeric from mostlyai.sdk._data.base import DataIdentifier, DataTable, NonContextRelation, Schema +from mostlyai.sdk._data.file.base import FileDataTable +from mostlyai.sdk._data.util.common import IS_NULL, NON_CONTEXT_COLUMN_INFIX _LOG = logging.getLogger(__name__) -# PULL +# ============================================================================= +# GLOBAL HYPERPARAMETER DEFAULTS FOR ML-BASED FK MODELS +# ============================================================================= + +# Model Architecture Parameters +SUB_COLUMN_EMBEDDING_DIM = 32 +ENTITY_HIDDEN_DIM = 256 +ENTITY_EMBEDDING_DIM = 16 +SIMILARITY_HIDDEN_DIM = 256 +PEAKEDNESS_SCALER = 7.0 + +# Training Parameters +BATCH_SIZE = 128 +LEARNING_RATE = 0.0003 +MAX_EPOCHS = 1000 +PATIENCE = 20 +N_NEGATIVE_SAMPLES = 10 +VAL_SPLIT = 0.2 +DROPOUT_RATE = 0.2 +EARLY_STOPPING_DELTA = 1e-5 +NUMERICAL_STABILITY_EPSILON = 1e-10 + +# Data Sampling Parameters +MAX_PARENT_SAMPLE_SIZE = 10000 +MAX_TGT_PER_PARENT = 2 + +# Inference Parameters +TEMPERATURE = 1.0 +TOP_K = 20 +TOP_P = 0.95 + + +# ============================================================================= +# PARTITIONED DATASET FOR EFFICIENT DATA HANDLING +# ============================================================================= + + +class PartitionedDataset: + """Cached wrapper for FileDataTable with slicing and random sampling capabilities.""" + + def __init__(self, table: FileDataTable, max_cached_partitions: int = 1): + self.table = table + self.max_cached_partitions = max_cached_partitions + self.partition_info = [] + + # unlimited cache if max_cached_partitions is -1 + cache_maxsize = None if max_cached_partitions == -1 else max_cached_partitions + self._load_partition_cached = lru_cache(maxsize=cache_maxsize)(self._load_partition_uncached) + + self._build_partition_index() + + def _build_partition_index(self): + """Build partition index using table's files.""" + current_total = 0 + for file in self.table.files: + partition_size = self._get_row_count_fast(file) + self.partition_info.append( + { + "file": file, + "start_idx": current_total, + "end_idx": current_total + partition_size, + "size": partition_size, + } + ) + current_total += partition_size + + def __getitem__(self, key) -> pd.DataFrame: + """Support slicing: dataset[start:end]""" + if isinstance(key, slice): + return self._slice_data(key.start or 0, key.stop or len(self)) + else: + raise TypeError("Key must be slice") + + def random_sample(self, n_items: int) -> pd.DataFrame: + """Randomly sample n_items from the dataset.""" + if n_items <= 0: + return pd.DataFrame() + + selected_partitions = set() + total_available = 0 + available_partitions = list(range(len(self.partition_info))) + np.random.shuffle(available_partitions) + + for partition_idx in available_partitions: + if total_available >= n_items: + break + selected_partitions.add(partition_idx) + total_available += self.partition_info[partition_idx]["size"] + + all_data = [] + for partition_idx in selected_partitions: + partition = self.partition_info[partition_idx] + df = self._load_partition(partition["file"]) + all_data.append(df) + + combined_df = pd.concat(all_data, ignore_index=True) + + if len(combined_df) >= n_items: + sampled_df = combined_df.sample(n=n_items, replace=False).reset_index(drop=True) + else: + sampled_df = combined_df.sample(n=n_items, replace=True).reset_index(drop=True) + + return sampled_df + + def __len__(self) -> int: + return self.table.row_count + + def _get_row_count_fast(self, file_path: Path) -> int: + """Get row count from parquet metadata without reading data.""" + if len(self.table.files) == 1: + return self.table.row_count + + parquet_file = pq.ParquetFile(file_path) + return parquet_file.metadata.num_rows + + def _load_partition_uncached(self, file_path: Path) -> pd.DataFrame: + """Load partition data from disk (no caching).""" + return pd.read_parquet(file_path) -def handle_non_context_relations( + def _load_partition(self, file_path: Path) -> pd.DataFrame: + """Load partition with caching.""" + return self._load_partition_cached(file_path).copy() + + def _find_partition_for_index(self, global_idx: int) -> dict: + """Find which partition contains the given global index.""" + for partition in self.partition_info: + if partition["start_idx"] <= global_idx < partition["end_idx"]: + return partition + raise IndexError(f"Index {global_idx} out of range [0, {len(self)}") + + def _slice_data(self, start: int, end: int) -> pd.DataFrame: + """Load data for slice range [start:end]""" + if start >= len(self) or end <= 0: + return pd.DataFrame() + + start = max(0, start) + end = min(len(self), end) + + needed_partitions = [] + for partition in self.partition_info: + if partition["end_idx"] > start and partition["start_idx"] < end: + local_start = max(0, start - partition["start_idx"]) + local_end = min(partition["size"], end - partition["start_idx"]) + needed_partitions.append((partition, local_start, local_end)) + + result_dfs = [] + for partition, local_start, local_end in needed_partitions: + df = self._load_partition(partition["file"]) + slice_df = df.iloc[local_start:local_end] + result_dfs.append(slice_df) + + return pd.concat(result_dfs, ignore_index=True) if result_dfs else pd.DataFrame() + + def iter_partitions(self) -> Iterator[tuple[int, Path, pd.DataFrame]]: + """Iterate over partitions using table's method.""" + yield from self.table.iter_partitions() + + @property + def files(self) -> list[Path]: + """Access partition files using table's files.""" + return self.table.files + + def clear_cache(self) -> None: + """Clear the partition cache.""" + self._load_partition_cached.cache_clear() + + +# ============================================================================= +# PULL PHASE: MARK NON-CONTEXT FKS FOR NULL HANDLING +# ============================================================================= + + +def add_is_null_for_non_context_relations( schema: Schema, table_name: str, data: pd.DataFrame, @@ -36,7 +241,7 @@ def handle_non_context_relations( relations_to=[table_name], ).relations for relation in non_context_relations: - data = handle_non_context_relation( + data = add_is_null_for_non_context_relation( data=data, table=schema.tables[relation.parent.table], relation=relation, @@ -45,7 +250,7 @@ def handle_non_context_relations( return data -def handle_non_context_relation( +def add_is_null_for_non_context_relation( data: pd.DataFrame, table: DataTable, relation: NonContextRelation, @@ -85,7 +290,9 @@ def handle_non_context_relation( return data -# POSTPROC +# ============================================================================= +# RANDOM FK ASSIGNMENT (FALLBACK) +# ============================================================================= def sample_non_context_keys( @@ -109,15 +316,16 @@ def sample_non_context_keys( return sampled_keys -def postproc_non_context( +def assign_non_context_fks_randomly( tgt_data: pd.DataFrame, generated_data_schema: Schema, tgt: str, ) -> pd.DataFrame: """ Apply non-context keys allocation for each non-context relation for a generated table. + Uses random sampling as a fallback when ML models are not available. """ - tgt_data = copy(tgt_data) + tgt_data = shallow_copy(tgt_data) for rel in generated_data_schema.relations: if not isinstance(rel, NonContextRelation) or rel.child.table != tgt: continue @@ -136,3 +344,767 @@ def postproc_non_context( tgt_data.insert(tgt_data.columns.get_loc(tgt_is_null_column_name), tgt_fk_name, sampled_keys) tgt_data = tgt_data.drop(columns=[tgt_is_null_column_name]) return tgt_data + + +# ============================================================================= +# ML-BASED FK MODELS: NEURAL NETWORK ARCHITECTURE +# ============================================================================= + + +class EntityEncoder(nn.Module): + """Neural network encoder for entity embeddings.""" + + def __init__( + self, + cardinalities: dict[str, int], + sub_column_embedding_dim: int = SUB_COLUMN_EMBEDDING_DIM, + entity_hidden_dim: int = ENTITY_HIDDEN_DIM, + entity_embedding_dim: int = ENTITY_EMBEDDING_DIM, + ): + super().__init__() + self.cardinalities = cardinalities + self.sub_column_embedding_dim = sub_column_embedding_dim + self.entity_hidden_dim = entity_hidden_dim + self.entity_embedding_dim = entity_embedding_dim + self.embeddings = nn.ModuleDict( + { + col: nn.Embedding(num_embeddings=cardinality, embedding_dim=self.sub_column_embedding_dim) + for col, cardinality in self.cardinalities.items() + } + ) + entity_dim = len(self.cardinalities) * self.sub_column_embedding_dim + self.entity_encoder = nn.Sequential( + nn.Linear(entity_dim, self.entity_hidden_dim), + nn.ReLU(), + nn.Dropout(DROPOUT_RATE), + nn.Linear(self.entity_hidden_dim, self.entity_embedding_dim), + nn.ReLU(), + nn.Dropout(DROPOUT_RATE), + nn.Linear(self.entity_embedding_dim, self.entity_embedding_dim), + ) + + def forward(self, inputs: dict[str, torch.Tensor]) -> torch.Tensor: + embeddings = torch.cat([self.embeddings[col](inputs[col]) for col in inputs.keys()], dim=1) + encoded = self.entity_encoder(embeddings) + return encoded + + +class ParentChildMatcher(nn.Module): + """Neural network model for parent-child relationship matching.""" + + def __init__( + self, + parent_cardinalities: dict[str, int], + child_cardinalities: dict[str, int], + sub_column_embedding_dim: int = SUB_COLUMN_EMBEDDING_DIM, + entity_hidden_dim: int = ENTITY_HIDDEN_DIM, + entity_embedding_dim: int = ENTITY_EMBEDDING_DIM, + similarity_hidden_dim: int = SIMILARITY_HIDDEN_DIM, + ): + super().__init__() + self.entity_embedding_dim = entity_embedding_dim + self.similarity_hidden_dim = similarity_hidden_dim + + self.parent_encoder = EntityEncoder( + cardinalities=parent_cardinalities, + sub_column_embedding_dim=sub_column_embedding_dim, + entity_hidden_dim=entity_hidden_dim, + entity_embedding_dim=self.entity_embedding_dim, + ) + self.child_encoder = EntityEncoder( + cardinalities=child_cardinalities, + sub_column_embedding_dim=sub_column_embedding_dim, + entity_hidden_dim=entity_hidden_dim, + entity_embedding_dim=self.entity_embedding_dim, + ) + + def forward(self, parent_inputs: dict[str, torch.Tensor], child_inputs: dict[str, torch.Tensor]) -> torch.Tensor: + parent_encoded = self.parent_encoder(parent_inputs) + child_encoded = self.child_encoder(child_inputs) + + similarity = F.cosine_similarity(parent_encoded, child_encoded, dim=1) + probability = torch.sigmoid(similarity * PEAKEDNESS_SCALER).unsqueeze(1) + + return probability + + +# ============================================================================= +# ML-BASED FK MODELS: DATA ENCODING & STATISTICS +# ============================================================================= + + +def safe_name(text: str) -> str: + """Generate a safe filename with hash suffix.""" + safe = sanitize_filename(text) + digest = hashlib.md5(safe.encode("utf-8")).hexdigest()[:8] + return f"{safe}-{digest}" + + +def get_cardinalities(*, stats_dir: Path) -> dict[str, int]: + """Extract cardinalities from stats file.""" + stats_path = stats_dir / "stats.json" + stats = json.loads(stats_path.read_text()) + cardinalities = { + f"{column}_{sub_column}": cardinality + for column, column_stats in stats["columns"].items() + for sub_column, cardinality in column_stats["cardinalities"].items() + } + return cardinalities + + +def analyze_df( + *, + df: pd.DataFrame, + primary_key: str | None = None, + parent_key: str | None = None, + data_columns: list[str] | None = None, + stats_dir: Path, +) -> None: + """Analyze dataframe and compute statistics for encoding.""" + stats_dir.mkdir(parents=True, exist_ok=True) + + key_columns = [] + if primary_key is not None: + key_columns.append(primary_key) + if parent_key is not None: + key_columns.append(parent_key) + + data_columns = data_columns or list(df.columns) + + # preserve column order to ensure deterministic encoding + data_columns = [col for col in data_columns if col not in key_columns and col in df.columns] + num_columns = [col for col in data_columns if col in df.select_dtypes(include="number").columns] + dt_columns = [col for col in data_columns if col in df.select_dtypes(include="datetime").columns] + cat_columns = [col for col in data_columns if col not in num_columns + dt_columns] + + stats = { + "primary_key": primary_key, + "parent_key": parent_key, + "data_columns": data_columns, + "cat_columns": cat_columns, + "num_columns": num_columns, + "dt_columns": dt_columns, + "columns": {}, + } + for col in data_columns: + values = df[col] + root_keys = pd.Series(np.arange(len(values)), name="root_keys") + if col in cat_columns: + analyze, reduce = analyze_categorical, analyze_reduce_categorical + elif col in num_columns: + analyze, reduce = analyze_numeric, analyze_reduce_numeric + elif col in dt_columns: + analyze, reduce = analyze_datetime, analyze_reduce_datetime + else: + raise ValueError(f"unknown column type: {col}") + col_stats = analyze(values, root_keys) + col_stats = reduce([col_stats], value_protection=True) + stats["columns"][col] = col_stats + + stats_path = stats_dir / "stats.json" + stats_path.write_text(json.dumps(stats, indent=4)) + + +def encode_df( + *, df: pd.DataFrame, stats_dir: Path, include_primary_key: bool = True, include_parent_key: bool = True +) -> pd.DataFrame: + """Encode dataframe using pre-computed statistics.""" + stats_path = stats_dir / "stats.json" + stats = json.loads(stats_path.read_text()) + primary_key = stats["primary_key"] + parent_key = stats["parent_key"] + cat_columns = stats["cat_columns"] + num_columns = stats["num_columns"] + dt_columns = stats["dt_columns"] + + data = [] + for col, col_stats in stats["columns"].items(): + if col in cat_columns: + encode = encode_categorical + elif col in num_columns: + encode = encode_numeric + elif col in dt_columns: + encode = encode_datetime + else: + raise ValueError(f"unknown column type: {col}") + + values = df[col].copy() + df_encoded = encode(values, col_stats) + df_encoded = df_encoded.add_prefix(col + "_") + data.append(df_encoded) + + # optionally include keys + for key, include_key in [(primary_key, include_primary_key), (parent_key, include_parent_key)]: + if key is not None and include_key: + data.insert(0, df[key]) + + data = pd.concat(data, axis=1) + + return data + + +# ============================================================================= +# ML-BASED FK MODELS: TRAINING DATA PREPARATION +# ============================================================================= + + +def fetch_parent_data(parent_table: DataTable, max_sample_size: int = MAX_PARENT_SAMPLE_SIZE) -> pd.DataFrame: + """ + Fetch unique parent data with optional sampling limit. + + Reads the parent table in chunks to efficiently collect unique parent records + until the maximum sample size is reached. Stops early once the limit is met + to avoid unnecessary data processing. + + Args: + parent_table: Parent table to extract data from. Must have a primary key defined. + max_sample_size: Maximum number of unique records to collect. Defaults to 10,000. + + Returns: + DataFrame containing complete parent records with all columns. + Records are unique by primary key. + """ + primary_key = parent_table.primary_key + seen_keys = set() + collected_rows = [] + + for chunk_df in parent_table.read_chunks(columns=parent_table.columns, do_coerce_dtypes=True): + chunk_df = chunk_df.drop_duplicates(subset=[primary_key]) + + for _, row in chunk_df.iterrows(): + key = row[primary_key] + if key not in seen_keys: + seen_keys.add(key) + collected_rows.append(row) + if len(collected_rows) >= max_sample_size: + break + + if len(collected_rows) >= max_sample_size: + break + + parent_data = pd.DataFrame(collected_rows).reset_index(drop=True) + return parent_data + + +def fetch_tgt_data( + *, + tgt_table: DataTable, + tgt_parent_key: str, + parent_keys: list, + max_tgt_per_parent: int = MAX_TGT_PER_PARENT, +) -> pd.DataFrame: + """ + Fetch target data with per-parent limits. + + Reads target table in chunks and tracks how many target records each parent has. + Stops adding target records for a parent once the limit is reached. + + Args: + tgt_table: Target table to fetch from. + tgt_parent_key: Foreign key column in target table. + parent_keys: List of parent key values to filter by. + max_tgt_per_parent: Maximum target records per parent. Defaults to 1. + + Returns: + DataFrame containing target records, limited by max_tgt_per_parent constraint. + """ + parent_counts = defaultdict(int) + collected_rows = [] + where = {tgt_parent_key: parent_keys} + + for chunk_df in tgt_table.read_chunks(where=where, columns=tgt_table.columns, do_coerce_dtypes=True): + if len(chunk_df) == 0: + continue + + for _, row in chunk_df.iterrows(): + parent_id = row[tgt_parent_key] + + if parent_counts[parent_id] < max_tgt_per_parent: + collected_rows.append(row) + parent_counts[parent_id] += 1 + + child_data = pd.DataFrame(collected_rows).reset_index(drop=True) + _LOG.info(f"fetch_child_data | fetched: {len(child_data)}") + return child_data + + +def pull_fk_model_training_data( + *, + tgt_table: DataTable, + tgt_parent_key: str, + parent_table: DataTable, + parent_primary_key: str, + max_parent_sample_size: int = MAX_PARENT_SAMPLE_SIZE, + max_tgt_per_parent: int = MAX_TGT_PER_PARENT, +) -> tuple[pd.DataFrame, pd.DataFrame]: + """ + Pull training data for a specific non-context FK relation. + + Args: + tgt_table: Target/child table + tgt_parent_key: Foreign key column in target table + parent_table: Parent table + parent_primary_key: Primary key column in parent table + max_parent_sample_size: Maximum parent keys to sample + max_tgt_per_parent: Maximum target records per parent + + Returns: + Tuple of (parent_data, tgt_data) + """ + parent_data = fetch_parent_data(parent_table=parent_table, max_sample_size=max_parent_sample_size) + parent_keys_list = parent_data[parent_primary_key].tolist() if not parent_data.empty else [] + tgt_data = fetch_tgt_data( + tgt_table=tgt_table, + tgt_parent_key=tgt_parent_key, + parent_keys=parent_keys_list, + max_tgt_per_parent=max_tgt_per_parent, + ) + return parent_data, tgt_data + + +def prepare_training_data( + parent_encoded_data: pd.DataFrame, + tgt_encoded_data: pd.DataFrame, + parent_primary_key: str, + tgt_parent_key: str, + sample_size: int | None = None, + n_negative: int = N_NEGATIVE_SAMPLES, +) -> tuple[pd.DataFrame, pd.DataFrame, pd.Series]: + """ + Prepare training data for a parent-child matching model. + For each non-null child, samples will include: + - One positive pair (correct parent) with label=1 + - Multiple negative pairs (wrong parents) with label=0 + + Null children are excluded from training - nulls will be handled via _is_null column during inference. + + Args: + parent_encoded_data: Encoded parent data + tgt_encoded_data: Encoded child data + parent_primary_key: Primary key of parents + tgt_parent_key: Foreign key of children + sample_size: Number of children to sample (None = use all) + n_negative: Number of negative samples per child + """ + if sample_size is None: + sample_size = len(tgt_encoded_data) + + parent_keys = parent_encoded_data[parent_primary_key].to_numpy() + parents_X = parent_encoded_data.drop(columns=[parent_primary_key]).to_numpy(dtype=np.float32) + n_parents = parents_X.shape[0] + parent_index_by_key = pd.Series(np.arange(n_parents), index=parent_keys) + + child_keys = tgt_encoded_data[tgt_parent_key].to_numpy() + children_X = tgt_encoded_data.drop(columns=[tgt_parent_key]).to_numpy(dtype=np.float32) + n_children = children_X.shape[0] + + sample_size = min(int(sample_size), n_children) + rng = np.random.default_rng() + sampled_child_indices = rng.choice(n_children, size=sample_size, replace=False) + children_X = children_X[sampled_child_indices] + child_keys = child_keys[sampled_child_indices] + + # null children excluded from training - handled via _is_null column during inference + non_null_mask = ~pd.isna(child_keys) + children_X = children_X[non_null_mask] + child_keys = child_keys[non_null_mask] + n_non_null = len(children_X) + + if n_non_null == 0: + raise ValueError("No non-null children found in training data") + + true_parent_pos = parent_index_by_key.loc[child_keys].to_numpy() + if np.any(pd.isna(true_parent_pos)): + raise ValueError("Some child foreign keys do not match any parent primary key") + + # positive pairs (label=1) - one per non-null child + pos_parents = parents_X[true_parent_pos] + pos_labels = np.ones(n_non_null, dtype=np.float32) + + # negative pairs (label=0) - n_negative per non-null child (vectorized) + neg_indices = rng.integers(0, n_parents, size=(n_non_null, n_negative)) + + # ensure negatives are not the true parent if there is more than one parent + true_parent_pos_expanded = true_parent_pos[:, np.newaxis] + mask = neg_indices == true_parent_pos_expanded + + while mask.any() and n_parents > 1: + neg_indices[mask] = rng.integers(0, n_parents, size=mask.sum()) + mask = neg_indices == true_parent_pos_expanded + + neg_parents = parents_X[neg_indices.ravel()] + neg_children = np.repeat(children_X, n_negative, axis=0) + neg_labels = np.zeros(n_non_null * n_negative, dtype=np.float32) + + parent_vecs = np.vstack([pos_parents, neg_parents]).astype(np.float32, copy=False) + child_vecs = np.vstack([children_X, neg_children]).astype(np.float32, copy=False) + labels_vec = np.concatenate([pos_labels, neg_labels]).astype(np.float32, copy=False) + + parent_pd = pd.DataFrame(parent_vecs, columns=parent_encoded_data.drop(columns=[parent_primary_key]).columns) + child_pd = pd.DataFrame(child_vecs, columns=tgt_encoded_data.drop(columns=[tgt_parent_key]).columns) + labels_pd = pd.Series(labels_vec, name="labels") + + return parent_pd, child_pd, labels_pd + + +# ============================================================================= +# ML-BASED FK MODELS: TRAINING +# ============================================================================= + + +def train( + *, + model: ParentChildMatcher, + parent_pd: pd.DataFrame, + child_pd: pd.DataFrame, + labels: pd.Series, +) -> None: + """Train the parent-child matching model.""" + patience = PATIENCE + best_val_loss = float("inf") + epochs_no_improve = 0 + max_epochs = MAX_EPOCHS + + X_parent = torch.tensor(parent_pd.values, dtype=torch.int64) + X_child = torch.tensor(child_pd.values, dtype=torch.int64) + y = torch.tensor(labels.values, dtype=torch.float32).unsqueeze(1) + dataset = TensorDataset(X_parent, X_child, y) + + val_size = int(VAL_SPLIT * len(dataset)) + train_size = len(dataset) - val_size + train_ds, val_ds = random_split(dataset, [train_size, val_size]) + + train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True) + val_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False) + + optimizer = torch.optim.Adam(model.parameters(), lr=LEARNING_RATE) + loss_fn = nn.BCELoss() + + train_losses, val_losses = [], [] + best_model_state = None + + for epoch in range(max_epochs): + model.train() + train_loss = 0 + for batch_parent, batch_child, batch_y in train_loader: + batch_parent = {col: batch_parent[:, i] for i, col in enumerate(parent_pd.columns)} + batch_child = {col: batch_child[:, i] for i, col in enumerate(child_pd.columns)} + optimizer.zero_grad() + pred = model(batch_parent, batch_child) + loss = loss_fn(pred, batch_y) + loss.backward() + optimizer.step() + train_loss += loss.item() * batch_y.size(0) + train_loss /= train_size + train_losses.append(train_loss) + + model.eval() + val_loss = 0 + with torch.no_grad(): + for batch_parent, batch_child, batch_y in val_loader: + batch_parent = {col: batch_parent[:, i] for i, col in enumerate(parent_pd.columns)} + batch_child = {col: batch_child[:, i] for i, col in enumerate(child_pd.columns)} + pred = model(batch_parent, batch_child) + loss = loss_fn(pred, batch_y) + val_loss += loss.item() * batch_y.size(0) + val_loss /= val_size + val_losses.append(val_loss) + + _LOG.info(f"Epoch {epoch + 1}/{max_epochs}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}") + + if val_loss < best_val_loss - EARLY_STOPPING_DELTA: + best_val_loss = val_loss + epochs_no_improve = 0 + best_model_state = deepcopy(model.state_dict()) + else: + epochs_no_improve += 1 + if epochs_no_improve >= patience: + _LOG.info(f"Early stopping at epoch {epoch + 1}") + break + + assert best_model_state is not None + model.load_state_dict(best_model_state) + _LOG.info("Best model restored (lowest validation loss).") + + +# ============================================================================= +# ML-BASED FK MODELS: PERSISTENCE +# ============================================================================= + + +def store_fk_model(*, model: ParentChildMatcher, fk_model_workspace_dir: Path) -> None: + """Save FK model to disk.""" + fk_model_workspace_dir.mkdir(parents=True, exist_ok=True) + model_config = { + "parent_encoder": { + "cardinalities": model.parent_encoder.cardinalities, + "sub_column_embedding_dim": model.parent_encoder.sub_column_embedding_dim, + "entity_hidden_dim": model.parent_encoder.entity_hidden_dim, + "entity_embedding_dim": model.parent_encoder.entity_embedding_dim, + }, + "child_encoder": { + "cardinalities": model.child_encoder.cardinalities, + "sub_column_embedding_dim": model.child_encoder.sub_column_embedding_dim, + "entity_hidden_dim": model.child_encoder.entity_hidden_dim, + "entity_embedding_dim": model.child_encoder.entity_embedding_dim, + }, + "similarity_hidden_dim": model.similarity_hidden_dim, + } + model_config_path = fk_model_workspace_dir / "model_config.json" + model_config_path.write_text(json.dumps(model_config, indent=4)) + model_state_path = fk_model_workspace_dir / "model_weights.pt" + torch.save(model.state_dict(), model_state_path) + + +def load_fk_model(*, fk_model_workspace_dir: Path) -> ParentChildMatcher: + """Load FK model from disk.""" + model_config_path = fk_model_workspace_dir / "model_config.json" + model_config = json.loads(model_config_path.read_text()) + model = ParentChildMatcher( + parent_cardinalities=model_config["parent_encoder"]["cardinalities"], + child_cardinalities=model_config["child_encoder"]["cardinalities"], + sub_column_embedding_dim=model_config["parent_encoder"]["sub_column_embedding_dim"], + entity_hidden_dim=model_config["parent_encoder"]["entity_hidden_dim"], + entity_embedding_dim=model_config["parent_encoder"]["entity_embedding_dim"], + similarity_hidden_dim=model_config["similarity_hidden_dim"], + ) + model_state_path = fk_model_workspace_dir / "model_weights.pt" + model.load_state_dict(torch.load(model_state_path)) + return model + + +# ============================================================================= +# ML-BASED FK MODELS: INFERENCE +# ============================================================================= + + +def build_parent_child_probabilities( + *, + model: ParentChildMatcher, + tgt_encoded: pd.DataFrame, + parent_encoded: pd.DataFrame, +) -> torch.Tensor: + """ + Build probability matrix for parent-child matching. + + Args: + model: Trained parent-child matching model + tgt_encoded: Encoded target/child data (C rows) + parent_encoded: Encoded parent data (Cp rows - assigned parent batch) + + Returns: + prob_matrix: (C, Cp) - probability each parent candidate is a match for each child + """ + n_tgt = tgt_encoded.shape[0] + n_parent_batch = parent_encoded.shape[0] + + tgt_inputs = {col: torch.tensor(tgt_encoded[col].values.astype(np.int64)) for col in tgt_encoded.columns} + parent_inputs = {col: torch.tensor(parent_encoded[col].values.astype(np.int64)) for col in parent_encoded.columns} + + model.eval() + with torch.no_grad(): + child_embeddings = model.child_encoder(tgt_inputs) + parent_embeddings = model.parent_encoder(parent_inputs) + + # create cartesian product: each child with all parent candidates + child_embeddings_interleaved = child_embeddings.repeat_interleave(n_parent_batch, dim=0) + parent_embeddings_interleaved = parent_embeddings.repeat(n_tgt, 1) + + similarity = F.cosine_similarity(parent_embeddings_interleaved, child_embeddings_interleaved, dim=1) + similarity = similarity.view(n_tgt, n_parent_batch) + prob_matrix = F.softmax(similarity * PEAKEDNESS_SCALER, dim=1) + + return prob_matrix + + +def sample_best_parents( + *, + prob_matrix: torch.Tensor, + temperature: float, + top_k: int | None, + top_p: float | None, +) -> np.ndarray: + """ + Sample best parent for each child based on match probabilities. + + Args: + prob_matrix: (n_tgt, n_parent) probability each parent is a match + temperature: Controls variance in parent selection (default=1.0) + - temperature=0.0: Always pick argmax (most confident match) + - temperature=1.0: Sample from original probabilities + - temperature>1.0: Increase variance (flatten distribution) + Higher values create more diverse matches but may reduce quality. + top_k: If specified, only sample from top-K most probable parents per child. + This prevents unrealistic outlier matches while maintaining variance. + Recommended: 10-50 depending on parent pool size. + top_p: If specified, use nucleus sampling - only sample from the smallest set + of parents whose cumulative probability exceeds p (0.0 < p <= 1.0). + This dynamically adjusts the candidate pool size based on probability mass. + If both top_k and top_p are specified, top_k is applied first, then top_p. + Recommended: 0.9-0.95 for high quality matches with adaptive diversity. + + Returns: + best_parent_indices: Array of parent indices for each child + """ + n_tgt = prob_matrix.shape[0] + best_parent_indices = np.full(n_tgt, -1, dtype=np.int64) + + rng = np.random.default_rng() + + for i in range(n_tgt): + if temperature == 0.0: + best_parent_indices[i] = torch.argmax(prob_matrix[i]).cpu().numpy() + else: + probs = prob_matrix[i] + candidate_indices = torch.arange(len(probs)) + + # apply top_k filtering first if specified + if top_k is not None and top_k < len(probs): + top_k_values, top_k_indices = torch.topk(probs, k=top_k) + probs = top_k_values + candidate_indices = top_k_indices + + # apply top_p (nucleus) filtering if specified + if top_p is not None and 0.0 < top_p < 1.0: + # sort probabilities in descending order + sorted_probs, sorted_indices = torch.sort(probs, descending=True) + + # compute cumulative probabilities + cumsum_probs = torch.cumsum(sorted_probs, dim=0) + + # find the cutoff index where cumulative probability exceeds top_p + # keep at least one candidate + cutoff_idx = torch.searchsorted(cumsum_probs, top_p, right=False).item() + 1 + cutoff_idx = max(1, min(cutoff_idx, len(sorted_probs))) + + # filter to nucleus candidates + probs = sorted_probs[:cutoff_idx] + candidate_indices = candidate_indices[sorted_indices[:cutoff_idx]] + + # apply temperature scaling (higher = more uniform sampling) + logits = torch.log(probs + NUMERICAL_STABILITY_EPSILON) / temperature + probs = torch.softmax(logits, dim=0).cpu().numpy() + + sampled_candidate = rng.choice(len(probs), p=probs) + best_parent_indices[i] = candidate_indices[sampled_candidate].cpu().numpy() + + return best_parent_indices + + +def match_non_context( + *, + fk_models_workspace_dir: Path, + tgt_data: pd.DataFrame, + parent_data: pd.DataFrame, + tgt_parent_key: str, + parent_primary_key: str, + parent_table_name: str, + temperature: float = TEMPERATURE, + top_k: int | None = TOP_K, + top_p: float | None = TOP_P, +) -> pd.DataFrame: + """ + Match non-context foreign keys using trained ML models. + + This function uses a trained neural network to intelligently assign foreign keys + based on the similarity between parent and child records. + + Args: + fk_models_workspace_dir: Directory containing trained FK models + tgt_data: Target/child data to assign FKs to + parent_data: Parent data to sample from + tgt_parent_key: Foreign key column name in target table + parent_primary_key: Primary key column name in parent table + parent_table_name: Name of parent table + temperature: Sampling temperature (0=greedy, 1=normal, >1=diverse) + top_k: Number of top candidates to consider per match + top_p: Nucleus sampling threshold (0.0 < p <= 1.0) for dynamic candidate filtering + + Returns: + Target data with FK column populated + """ + # check for _is_null column (format: {fk_name}.{parent_table_name}._is_null) + is_null_col = NON_CONTEXT_COLUMN_INFIX.join([tgt_parent_key, parent_table_name, IS_NULL]) + has_is_null = is_null_col in tgt_data.columns + + tgt_data[tgt_parent_key] = pd.NA + + if has_is_null: + # _is_null column contains string values "True" or "False" + is_null_values = tgt_data[is_null_col].astype(str) + null_mask = is_null_values == "True" + non_null_mask = ~null_mask + + _LOG.info( + f"FK matching data | total_rows: {len(tgt_data)} | null_rows: {null_mask.sum()} | non_null_rows: {non_null_mask.sum()}" + ) + + if non_null_mask.sum() == 0: + _LOG.warning(f"All rows have null FK values (via {is_null_col})") + if is_null_col in tgt_data.columns: + tgt_data = tgt_data.drop(columns=[is_null_col]) + return tgt_data + + non_null_indices = tgt_data.index[non_null_mask].tolist() + + tgt_data_non_null = tgt_data.loc[non_null_mask].copy().reset_index(drop=True) + + # remove _is_null column before encoding (not used by FK model) + if is_null_col in tgt_data_non_null.columns: + tgt_data_non_null = tgt_data_non_null.drop(columns=[is_null_col]) + else: + _LOG.info(f"FK matching data | total_rows: {len(tgt_data)} | null_rows: 0 | non_null_rows: {len(tgt_data)}") + tgt_data_non_null = tgt_data.copy() + non_null_indices = tgt_data.index.tolist() + non_null_mask = pd.Series(True, index=tgt_data.index) + + fk_model_workspace_dir = fk_models_workspace_dir / safe_name(tgt_parent_key) + tgt_stats_dir = fk_model_workspace_dir / "tgt-stats" + parent_stats_dir = fk_model_workspace_dir / "parent-stats" + + tgt_encoded = encode_df( + df=tgt_data_non_null, + stats_dir=tgt_stats_dir, + include_primary_key=False, + include_parent_key=False, + ) + parent_encoded = encode_df( + df=parent_data, + stats_dir=parent_stats_dir, + include_primary_key=False, + ) + + model = load_fk_model(fk_model_workspace_dir=fk_model_workspace_dir) + + fk_parent_sample_size = len(parent_encoded) + _LOG.info( + f"FK model matching | temperature: {temperature} | top_k: {top_k} | top_p: {top_p} | parent_sample_size: {fk_parent_sample_size}" + ) + + prob_matrix = build_parent_child_probabilities( + model=model, + tgt_encoded=tgt_encoded, + parent_encoded=parent_encoded, + ) + + best_parent_indices = sample_best_parents( + prob_matrix=prob_matrix, + temperature=temperature, + top_k=top_k, + top_p=top_p, + ) + + best_parent_ids = parent_data.iloc[best_parent_indices][parent_primary_key].values + + parent_ids_series = pd.Series(best_parent_ids, index=non_null_indices) + + tgt_data.loc[non_null_indices, tgt_parent_key] = parent_ids_series + + if has_is_null and is_null_col in tgt_data.columns: + tgt_data = tgt_data.drop(columns=[is_null_col]) + + n_matched = non_null_mask.sum() + n_null = (~non_null_mask).sum() + _LOG.info(f"FK matching completed | matched: {n_matched} | null: {n_null}") + + return tgt_data diff --git a/mostlyai/sdk/_data/pull_utils.py b/mostlyai/sdk/_data/pull_utils.py index 265b4efc..4d53fab0 100644 --- a/mostlyai/sdk/_data/pull_utils.py +++ b/mostlyai/sdk/_data/pull_utils.py @@ -46,7 +46,7 @@ drop_language_columns_in_target, split_language_model, ) -from mostlyai.sdk._data.non_context import handle_non_context_relations +from mostlyai.sdk._data.non_context import add_is_null_for_non_context_relations from mostlyai.sdk._data.progress_callback import ProgressCallbackWrapper from mostlyai.sdk._data.util.common import TEMPORARY_PRIMARY_KEY from mostlyai.sdk.domain import ModelEncodingType, ModelType @@ -774,7 +774,7 @@ def split_context( ] chunk = chunk.drop(columns=non_ctx_cols) else: - chunk = handle_non_context_relations( + chunk = add_is_null_for_non_context_relations( schema=schema, table_name=table.name, data=chunk, @@ -869,7 +869,7 @@ def _hash_column(chunk): return chunk.index for idx, chunk in enumerate(iterator): - chunk = handle_non_context_relations( + chunk = add_is_null_for_non_context_relations( schema=schema, table_name=table.name, data=chunk, diff --git a/mostlyai/sdk/_local/execution/jobs.py b/mostlyai/sdk/_local/execution/jobs.py index 1aca9bc4..96f8e3f9 100644 --- a/mostlyai/sdk/_local/execution/jobs.py +++ b/mostlyai/sdk/_local/execution/jobs.py @@ -43,6 +43,7 @@ execute_step_finalize_generation, update_total_rows, ) +from mostlyai.sdk._local.execution.step_finalize_training import execute_step_finalize_training from mostlyai.sdk._local.execution.step_generate_data import execute_step_generate_data from mostlyai.sdk._local.execution.step_generate_model_report_data import ( execute_step_generate_model_report_data, @@ -85,13 +86,15 @@ def _set_random_state(random_state: int | None = None): def _move_training_artefacts(generator_dir: Path, job_workspace_dir: Path): - for dir in ["Logs", "ModelStore", "ModelQAReports", "ModelQAStatistics"]: + for dir in ["Logs", "ModelStore", "FKModelsStore", "ModelQAReports", "ModelQAStatistics"]: shutil.rmtree(generator_dir / dir, ignore_errors=True) (generator_dir / dir).mkdir() for path in job_workspace_dir.absolute().rglob("*"): if path.is_dir() and path.name == "ModelStore": model_label = path.parent.name path.rename(generator_dir / "ModelStore" / model_label) + if path.is_dir() and path.name == "FKModelsStore": + path.rename(generator_dir / "FKModelsStore") if path.is_file() and path.parent.name == "ModelQAReports": path.rename(generator_dir / "ModelQAReports" / path.name) if path.is_dir() and path.name == "ModelQAStatistics": @@ -180,6 +183,14 @@ def _copy_model(generator_dir: Path, model_label: str, workspace_dir: Path) -> b return False +def _copy_fk_models(generator_dir: Path, job_workspace_dir: Path) -> bool: + models_path = generator_dir / "FKModelsStore" + if models_path.exists(): + shutil.copytree(models_path, job_workspace_dir / "FKModelsStore") + return True + return False + + def _copy_statistics(generator_dir: Path, model_label: str, workspace_dir: Path) -> bool: statistics_path = generator_dir / "ModelQAStatistics" / model_label if statistics_path.exists(): @@ -431,7 +442,13 @@ def execute_task_train(self, task: Task): tgt_table.language_model_metrics = model_metrics def execute_task_finalize_training(self, task: Task): - pass + generator = self._generator + connectors = [ + read_connector_from_json(self._home_dir / "connectors" / t.source_connector_id) for t in generator.tables + ] + execute_step_finalize_training( + generator=generator, connectors=connectors, job_workspace_dir=self._job_workspace_dir + ) def execute_task_generate(self, task: Task): generator = self._generator @@ -531,16 +548,22 @@ def execute_task_generate(self, task: Task): self.execute_deliver_data() def execute_finalize_generation(self): + generator = self._generator + generator_dir = self._home_dir / "generators" / generator.id + job_workspace_dir = self._job_workspace_dir + + _copy_fk_models(generator_dir=generator_dir, job_workspace_dir=self._job_workspace_dir) + schema = create_generation_schema( generator=self._generator, - job_workspace_dir=self._job_workspace_dir, + job_workspace_dir=job_workspace_dir, step="finalize_generation", ) usages = execute_step_finalize_generation( schema=schema, is_probe=False, - job_workspace_dir=self._job_workspace_dir, + job_workspace_dir=job_workspace_dir, update_progress=LocalProgressCallback( resource_path=self._home_dir / "synthetic-datasets" / self._synthetic_dataset.id, model_label=None, diff --git a/mostlyai/sdk/_local/execution/plan.py b/mostlyai/sdk/_local/execution/plan.py index 5925e280..81dbb6aa 100644 --- a/mostlyai/sdk/_local/execution/plan.py +++ b/mostlyai/sdk/_local/execution/plan.py @@ -155,10 +155,9 @@ def make_generator_execution_plan(generator: Generator) -> ExecutionPlan: target_table_name=table.name, include_report=table.language_model_configuration.enable_model_report, ) - execution_plan.add_task(TaskType.sync) - # post_training_sync = execution_plan.add_task(TaskType.sync) - # finalize_task = execution_plan.add_task(TaskType.finalize_training, parent=post_training_sync) - # execution_plan.add_task(TaskType.sync, parent=finalize_task) + post_training_sync = execution_plan.add_task(TaskType.sync) + finalize_task = execution_plan.add_task(TaskType.finalize_training, parent=post_training_sync) + execution_plan.add_task(TaskType.sync, parent=finalize_task) return execution_plan diff --git a/mostlyai/sdk/_local/execution/step_finalize_generation.py b/mostlyai/sdk/_local/execution/step_finalize_generation.py index 5fc4e548..66e72cda 100644 --- a/mostlyai/sdk/_local/execution/step_finalize_generation.py +++ b/mostlyai/sdk/_local/execution/step_finalize_generation.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import logging +import math import uuid import zipfile from pathlib import Path @@ -24,7 +25,7 @@ from mostlyai.sdk._data.file.base import LocalFileContainer from mostlyai.sdk._data.file.table.csv import CsvDataTable from mostlyai.sdk._data.file.table.parquet import ParquetDataTable -from mostlyai.sdk._data.non_context import postproc_non_context +from mostlyai.sdk._data.non_context import PartitionedDataset, assign_non_context_fks_randomly, match_non_context from mostlyai.sdk._data.progress_callback import ProgressCallback, ProgressCallbackWrapper from mostlyai.sdk._data.util.common import ( IS_NULL, @@ -35,6 +36,10 @@ _LOG = logging.getLogger(__name__) +# FK processing constants +FK_MIN_CHILDREN_BATCH_SIZE = 10 +FK_PARENT_BATCH_SIZE = 1000 + def execute_step_finalize_generation( *, @@ -56,6 +61,7 @@ def execute_step_finalize_generation( target_table_name=table_name, delivery_dir=delivery_dir, export_csv=False, + job_workspace_dir=job_workspace_dir, ) return usages @@ -76,6 +82,7 @@ def execute_step_finalize_generation( target_table_name=tgt, delivery_dir=delivery_dir, export_csv=export_csv, + job_workspace_dir=job_workspace_dir, ) progress.update(advance=1) @@ -193,14 +200,232 @@ def export_random_samples( ) -def finalize_probing(schema: Schema, delivery_dir: Path): - for tgt in schema.tables: - finalize_table_generation( +def filter_and_order_columns(data: pd.DataFrame, table_name: str, schema: Schema) -> pd.DataFrame: + """Keep only original columns in the right order.""" + tgt_cols = schema.tables[table_name].columns or data.columns + drop_cols = [c for c in tgt_cols if c not in data] + if drop_cols: + _LOG.info(f"remove columns from final output: {', '.join(drop_cols)}") + keep_cols = [c for c in tgt_cols if c in data] + return data[keep_cols] + + +def write_batch_outputs( + data: pd.DataFrame, table_name: str, batch_counter: int, pqt_path: Path, csv_path: Path | None +) -> None: + """Write batch to parquet and optionally CSV.""" + batch_filename = f"part.{batch_counter:06d}.parquet" + _LOG.info(f"store post-processed batch {batch_counter} ({len(data)} rows) as PQT") + pqt_post = ParquetDataTable(path=pqt_path / batch_filename, name=table_name) + pqt_post.write_data(data) + + if csv_path: + _LOG.info(f"store post-processed batch {batch_counter} as CSV") + csv_post = CsvDataTable(path=csv_path / f"{table_name}.csv", name=table_name) + csv_post.write_data(data, if_exists="append") + + +def setup_output_paths(delivery_dir: Path, target_table_name: str, export_csv: bool) -> tuple[Path, Path | None]: + """Create output directories for parquet and optionally CSV.""" + pqt_path = delivery_dir / target_table_name / "parquet" + pqt_path.mkdir(exist_ok=True, parents=True) + _LOG.info(f"prepared {pqt_path=} for storing post-processed data as PQT files") + + csv_path = None + if export_csv: + csv_path = delivery_dir / target_table_name / "csv" + csv_path.mkdir(exist_ok=True, parents=True) + _LOG.info(f"prepared {csv_path=} for storing post-processed data as CSV file") + + return pqt_path, csv_path + + +def are_fk_models_available(job_workspace_dir: Path, target_table_name: str) -> bool: + """Check if FK models are available for the given table.""" + fk_models_dir = job_workspace_dir / "FKModelsStore" / target_table_name + has_fk_models = fk_models_dir.exists() and any(fk_models_dir.glob("*/model_weights.pt")) + return has_fk_models + + +def process_table_with_random_fk_assignment( + table_name: str, + schema: Schema, + pqt_path: Path, + csv_path: Path | None, +) -> None: + """Process table with random FK assignment, partition by partition.""" + table = schema.tables[table_name] + + for partition_idx, _, partition_data in table.iter_partitions(): + _LOG.info(f"Processing partition {partition_idx + 1} ({len(partition_data)} rows)") + processed_data = assign_non_context_fks_randomly( + tgt_data=partition_data, generated_data_schema=schema, - target_table_name=tgt, - delivery_dir=delivery_dir, - export_csv=False, + tgt=table_name, + ) + processed_data = filter_and_order_columns(processed_data, table_name, schema) + write_batch_outputs(processed_data, table_name, partition_idx, pqt_path, csv_path) + + +def calculate_optimal_child_batch_size_for_relation( + parent_dataset: PartitionedDataset, + children_dataset: PartitionedDataset, + parent_batch_size: int, + relation_name: str, +) -> int: + """Calculate optimal child batch size for a specific FK relationship.""" + total_children = len(children_dataset) + parent_size = len(parent_dataset) + num_parent_batches = max(1, math.ceil(parent_size / parent_batch_size)) + + # ideal batch size for full parent utilization + ideal_batch_size = total_children // num_parent_batches + + # apply minimum batch size constraint + optimal_batch_size = max(ideal_batch_size, FK_MIN_CHILDREN_BATCH_SIZE) + + # log utilization metrics + num_child_batches = total_children // optimal_batch_size + parent_utilization = min(num_child_batches / num_parent_batches * 100, 100) + + _LOG.info( + f"[{relation_name}] Batch size optimization | " + f"total_children: {total_children} | " + f"parent_size: {parent_size} | " + f"parent_batch_size: {parent_batch_size} | " + f"parent_batches: {num_parent_batches} | " + f"ideal_child_batch: {ideal_batch_size} | " + f"optimal_child_batch: {optimal_batch_size} | " + f"parent_utilization: {parent_utilization:.1f}%" + ) + + return optimal_batch_size + + +def assign_parent_partition_round_robin( + parent_dataset: PartitionedDataset, + child_batch_idx: int, + parent_batch_size: int, +) -> pd.DataFrame: + """Assign parent data partition using round robin strategy.""" + parent_dataset_size = len(parent_dataset) + start_idx = (child_batch_idx * parent_batch_size) % parent_dataset_size + end_idx = min(start_idx + parent_batch_size, parent_dataset_size) + + # handle wrap-around to beginning if batch spans end boundary + if end_idx - start_idx < parent_batch_size and parent_dataset_size > parent_batch_size: + first_part = parent_dataset[start_idx:end_idx] + remaining_needed = parent_batch_size - (end_idx - start_idx) + second_part = parent_dataset[0:remaining_needed] + assigned_parent_data = pd.concat([first_part, second_part], ignore_index=True) + else: + assigned_parent_data = parent_dataset[start_idx:end_idx] + + return assigned_parent_data + + +def process_table_with_fk_models( + *, + table_name: str, + schema: Schema, + pqt_path: Path, + csv_path: Path | None, + parent_batch_size: int = FK_PARENT_BATCH_SIZE, + job_workspace_dir: Path, +) -> None: + """Process table with ML model-based FK assignment using natural dataset partitions and per-relationship batch sizes.""" + + fk_models_workspace_dir = job_workspace_dir / "FKModelsStore" / table_name + non_ctx_relations = [rel for rel in schema.non_context_relations if rel.child.table == table_name] + children_table = schema.tables[table_name] + children_dataset = PartitionedDataset(children_table) + parent_datasets = {} + for relation in non_ctx_relations: + parent_table_name = relation.parent.table + if parent_table_name not in parent_datasets: + parent_table = schema.tables[parent_table_name] + parent_datasets[parent_table_name] = PartitionedDataset(parent_table) + + # calculate optimal batch size for each relationship + relationship_batch_sizes = {} + for relation in non_ctx_relations: + parent_table_name = relation.parent.table + parent_dataset = parent_datasets[parent_table_name] + relation_name = f"{relation.child.table}.{relation.child.column}->{parent_table_name}" + + optimal_batch_size = calculate_optimal_child_batch_size_for_relation( + parent_dataset=parent_dataset, + children_dataset=children_dataset, + parent_batch_size=parent_batch_size, + relation_name=relation_name, ) + relationship_batch_sizes[relation] = optimal_batch_size + + # process data using natural dataset partitions with buffering + relationship_batch_indices = {relation: 0 for relation in non_ctx_relations} + leftover_buffers = {} # incomplete batches buffered for next partition + + total_partitions = len(children_dataset.files) + + for partition_idx, _, partition_data in children_dataset.iter_partitions(): + is_final_partition = partition_idx == total_partitions - 1 + + _LOG.info(f"Processing partition {partition_idx + 1} ({len(partition_data)} rows)") + + for relation in non_ctx_relations: + parent_table_name = relation.parent.table + parent_dataset = parent_datasets[parent_table_name] + relation_name = f"{relation.child.table}.{relation.child.column}->{parent_table_name}" + optimal_batch_size = relationship_batch_sizes[relation] + + current_data = partition_data.copy() + if relation in leftover_buffers: + current_data = pd.concat([leftover_buffers[relation], current_data], ignore_index=True) + del leftover_buffers[relation] + + _LOG.info(f" Processing relationship {relation_name} with batch size {optimal_batch_size}") + + processed_chunks = [] + + for chunk_start in range(0, len(current_data), optimal_batch_size): + chunk_end = min(chunk_start + optimal_batch_size, len(current_data)) + chunk_data = current_data.iloc[chunk_start:chunk_end].copy() + + # process complete batches and final batches; buffer incomplete batches + is_complete_batch = chunk_end - chunk_start == optimal_batch_size + is_final_batch = is_final_partition and (chunk_end == len(current_data)) + + if is_complete_batch or is_final_batch: + current_batch_idx = relationship_batch_indices[relation] + assigned_parent_data = assign_parent_partition_round_robin( + parent_dataset, current_batch_idx, parent_batch_size + ) + + processed_chunk = match_non_context( + fk_models_workspace_dir=fk_models_workspace_dir, + tgt_data=chunk_data, + parent_data=assigned_parent_data, + tgt_parent_key=relation.child.column, + parent_primary_key=relation.parent.column, + parent_table_name=parent_table_name, + ) + + processed_chunks.append(processed_chunk) + relationship_batch_indices[relation] += 1 + else: + leftover_buffers[relation] = chunk_data + break + + if processed_chunks: + partition_data = pd.concat(processed_chunks, ignore_index=True) + + parent_datasets[parent_table_name].clear_cache() + + partition_data = filter_and_order_columns(partition_data, table_name, schema) + + write_batch_outputs(partition_data, table_name, partition_idx, pqt_path, csv_path) + + del partition_data def finalize_table_generation( @@ -208,73 +433,42 @@ def finalize_table_generation( target_table_name: str, delivery_dir: Path, export_csv: bool, + job_workspace_dir: Path, ) -> None: """ Post-process the generated data for a given table. - * handle non-context keys + * handle non-context keys (using FK models if available) * handle reference keys * keep only needed columns, and in the right order * export to PARQUET, and optionally also to CSV (without col prefixes) """ - # read generated "raw" data into memory - # Note: We should avoid reading all data into memory. E.g. by looping over source.dataset.files or over - # source.dataset.to_batches(), then process in batches, and then append to CSV, respectively write out - # as separate parquet files. - table = generated_data_schema.tables[target_table_name] - tgt_table_files = table.dataset.files - n_partitions = len(tgt_table_files) - _LOG.info(f"POSTPROC will handle {n_partitions} partitions") - - pqt_path = delivery_dir / target_table_name / "parquet" - pqt_path.mkdir(exist_ok=True, parents=True) - _LOG.info(f"prepared {pqt_path=} for storing post-processed data as PQT files") - - if export_csv: - csv_path = delivery_dir / target_table_name / "csv" - csv_path.mkdir(exist_ok=True, parents=True) - _LOG.info(f"prepared {csv_path=} for storing post-processed data as CSV file") - else: - csv_path = None - - # instantiate container outside of loop to avoid memory to pile up - container = type(table.container)() - for part_i, part_file in enumerate(tgt_table_files, start=1): - container.set_location(part_file) - part_table = ParquetDataTable(container=container) - tgt_data = part_table.read_data(do_coerce_dtypes=True) - - # post-process non-context keys - tgt_data = postproc_non_context( - tgt_data=tgt_data, - generated_data_schema=generated_data_schema, - tgt=target_table_name, - ) - - # keep only original columns, and in the right order - tgt_cols = ( - cols if (cols := generated_data_schema.tables[target_table_name].columns) is not None else tgt_data.columns + pqt_path, csv_path = setup_output_paths(delivery_dir, target_table_name, export_csv) + fk_models_available = are_fk_models_available(job_workspace_dir, target_table_name) + fk_models_failed = False + + if fk_models_available: + try: + _LOG.info(f"Assigning non context FKs (if exists) through FK models for table {target_table_name}") + process_table_with_fk_models( + table_name=target_table_name, + schema=generated_data_schema, + pqt_path=pqt_path, + csv_path=csv_path, + job_workspace_dir=job_workspace_dir, + ) + except Exception as e: + _LOG.error(f"Non context FKs assignment through FK models failed for table {target_table_name}: {e}") + fk_models_failed = True + + if not fk_models_available or fk_models_failed: + _LOG.info(f"Assigning non context FKs (if exists) through random assignment for table {target_table_name}") + process_table_with_random_fk_assignment( + table_name=target_table_name, + schema=generated_data_schema, + pqt_path=pqt_path, + csv_path=csv_path, ) - drop_cols = [c for c in tgt_cols if c not in tgt_data] - if drop_cols: - _LOG.info(f"remove columns from final output: {', '.join(drop_cols)}") - keep_cols = [c for c in tgt_cols if c in tgt_data] - tgt_data = tgt_data[keep_cols] - - partition_text = f"{part_i} out of {n_partitions}" - - # store post-processed data as PQT files - _LOG.info(f"store post-processed {partition_text} as PQT") - pqt_post = ParquetDataTable(path=pqt_path / f"{Path(part_file).stem}.parquet", name=target_table_name) - pqt_post.write_data(tgt_data) - - # store post-processed data as single CSV file - if csv_path: - _LOG.info(f"store post-processed {partition_text} as CSV") - csv_post = CsvDataTable(path=csv_path / f"{target_table_name}.csv", name=target_table_name) - csv_post.write_data(tgt_data) - - del tgt_data def export_data_to_excel(delivery_dir: Path, output_dir: Path): diff --git a/mostlyai/sdk/_local/execution/step_finalize_training.py b/mostlyai/sdk/_local/execution/step_finalize_training.py index 4d2bbd0e..3dc6da2b 100644 --- a/mostlyai/sdk/_local/execution/step_finalize_training.py +++ b/mostlyai/sdk/_local/execution/step_finalize_training.py @@ -13,5 +13,156 @@ # limitations under the License. -def execute_step_finalize_training(): - pass +import logging +from pathlib import Path + +from mostlyai.sdk._data.base import NonContextRelation, Schema +from mostlyai.sdk._data.non_context import ( + ParentChildMatcher, + analyze_df, + encode_df, + get_cardinalities, + prepare_training_data, + pull_fk_model_training_data, + safe_name, + store_fk_model, + train, +) +from mostlyai.sdk._local.execution.step_pull_training_data import create_training_schema +from mostlyai.sdk.domain import Connector, Generator + +_LOG = logging.getLogger(__name__) + + +def execute_train_fk_models_for_single_table( + *, + tgt_table_name: str, + schema: Schema, + fk_models_workspace_dir: Path, +): + non_ctx_relations = [rel for rel in schema.non_context_relations if rel.child.table == tgt_table_name] + if not non_ctx_relations: + # no non-context relations, so no parent-child matchers to train + return + + fk_models_workspace_dir.mkdir(parents=True, exist_ok=True) + + for non_ctx_relation in non_ctx_relations: + tgt_parent_key = non_ctx_relation.child.column + fk_model_workspace_dir = fk_models_workspace_dir / safe_name(tgt_parent_key) + + execute_train_fk_model_for_single_relation( + tgt_table_name=tgt_table_name, + non_ctx_relation=non_ctx_relation, + schema=schema, + fk_model_workspace_dir=fk_model_workspace_dir, + ) + + +def execute_train_fk_model_for_single_relation( + *, + tgt_table_name: str, + non_ctx_relation: NonContextRelation, + schema: Schema, + fk_model_workspace_dir: Path, +): + tgt_table = schema.tables[tgt_table_name] + tgt_primary_key = tgt_table.primary_key + tgt_parent_key = non_ctx_relation.child.column + tgt_foreign_keys = [fk.column for fk in tgt_table.foreign_keys] + tgt_data_columns = [c for c in tgt_table.columns if c != tgt_table.primary_key and c not in tgt_foreign_keys] + + parent_table = schema.tables[non_ctx_relation.parent.table] + parent_primary_key = non_ctx_relation.parent.column + parent_foreign_keys = [fk.column for fk in parent_table.foreign_keys] + parent_data_columns = [ + c for c in parent_table.columns if c != parent_table.primary_key and c not in parent_foreign_keys + ] + parent_table_name = non_ctx_relation.parent.table + + parent_data, tgt_data = pull_fk_model_training_data( + tgt_table=tgt_table, + tgt_parent_key=tgt_parent_key, + parent_table=parent_table, + parent_primary_key=parent_primary_key, + ) + + if parent_data.empty or tgt_data.empty: + # no data to train matcher model, so skip + return + + fk_model_workspace_dir.mkdir(parents=True, exist_ok=True) + + tgt_stats_dir = fk_model_workspace_dir / "tgt-stats" + analyze_df( + df=tgt_data, + primary_key=tgt_primary_key, + parent_key=tgt_parent_key, + data_columns=tgt_data_columns, + stats_dir=tgt_stats_dir, + ) + + parent_stats_dir = fk_model_workspace_dir / "parent-stats" + analyze_df( + df=parent_data, + primary_key=parent_primary_key, + data_columns=parent_data_columns, + stats_dir=parent_stats_dir, + ) + + tgt_encoded_data = encode_df( + df=tgt_data, + stats_dir=tgt_stats_dir, + include_primary_key=False, + ) + + parent_encoded_data = encode_df( + df=parent_data, + stats_dir=parent_stats_dir, + ) + + parent_cardinalities = get_cardinalities(stats_dir=parent_stats_dir) + tgt_cardinalities = get_cardinalities(stats_dir=tgt_stats_dir) + model = ParentChildMatcher( + parent_cardinalities=parent_cardinalities, + child_cardinalities=tgt_cardinalities, + ) + + parent_pd, child_pd, labels_pd = prepare_training_data( + parent_encoded_data=parent_encoded_data, + tgt_encoded_data=tgt_encoded_data, + parent_primary_key=parent_primary_key, + tgt_parent_key=tgt_parent_key, + sample_size=None, # no additional sampling - already done in data pull phase + ) + + train( + model=model, + parent_pd=parent_pd, + child_pd=child_pd, + labels=labels_pd, + ) + + store_fk_model(model=model, fk_model_workspace_dir=fk_model_workspace_dir) + + _LOG.info(f"Child-parent matcher model trained and stored for parent table: {parent_table_name}") + + +def execute_step_finalize_training( + *, + generator: Generator, + connectors: list[Connector], + job_workspace_dir: Path, +): + schema = create_training_schema(generator=generator, connectors=connectors) + for tgt_table_name in schema.tables: + fk_models_workspace_dir = job_workspace_dir / "FKModelsStore" / tgt_table_name + try: + execute_train_fk_models_for_single_table( + tgt_table_name=tgt_table_name, + schema=schema, + fk_models_workspace_dir=fk_models_workspace_dir, + ) + except Exception as e: + _LOG.error(f"FK model training failed for table {tgt_table_name}: {e}") + continue diff --git a/mostlyai/sdk/_local/execution/step_pull_training_data.py b/mostlyai/sdk/_local/execution/step_pull_training_data.py index 511b666f..6cdee313 100644 --- a/mostlyai/sdk/_local/execution/step_pull_training_data.py +++ b/mostlyai/sdk/_local/execution/step_pull_training_data.py @@ -31,7 +31,7 @@ def execute_step_pull_training_data( workspace_dir: Path, update_progress: Callable, ) -> tuple[list[str], int]: - schema = _create_training_schema(generator=generator, connectors=connectors) + schema = create_training_schema(generator=generator, connectors=connectors) # fetch total rows tgt_table_total_rows = schema.tables[target_table_name].row_count @@ -54,11 +54,10 @@ def execute_step_pull_training_data( workspace_dir=workspace_dir, update_progress=update_progress, ) - return tgt_table_columns, tgt_table_total_rows -def _create_training_schema(generator: Generator, connectors: list[Connector]) -> Schema: +def create_training_schema(generator: Generator, connectors: list[Connector]) -> Schema: tables = {} for table in generator.tables: # create DataContainer diff --git a/mostlyai/sdk/_local/generators.py b/mostlyai/sdk/_local/generators.py index aead8ae8..8e49cba0 100644 --- a/mostlyai/sdk/_local/generators.py +++ b/mostlyai/sdk/_local/generators.py @@ -15,6 +15,7 @@ from pathlib import Path from mostlyai.sdk._local.execution.plan import ( + FINALIZE_TRAINING_TASK_STEPS, TRAINING_TASK_REPORT_STEPS, TRAINING_TASK_STEPS, has_language_model, @@ -122,6 +123,16 @@ def create_generator(home_dir: Path, config: GeneratorConfig) -> Generator: status=ProgressStatus.new, ) ) + for step in FINALIZE_TRAINING_TASK_STEPS: + progress_steps.append( + ProgressStep( + task_type=TaskType.finalize_training, + model_label=None, + step_code=step, + progress=ProgressValue(value=0, max=1), + status=ProgressStatus.new, + ) + ) job_progress = JobProgress( id=generator.id, progress=ProgressValue(value=0, max=len(progress_steps)), diff --git a/pyproject.toml b/pyproject.toml index aa3b8e97..915fed35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,7 @@ dependencies = [ "ipywidgets>=8.1.0", "duckdb>=1.2.1", "sqlparse>=0.5.3", + "pathvalidate>=3.3.1", ] [project.optional-dependencies] diff --git a/tests/_data/unit/test_finalize_generation.py b/tests/_data/unit/test_finalize_generation.py index 0e3506f8..ec0777f7 100644 --- a/tests/_data/unit/test_finalize_generation.py +++ b/tests/_data/unit/test_finalize_generation.py @@ -65,6 +65,7 @@ def test_finalize_table_generation(tmp_path, tgt_data): target_table_name="tgt", delivery_dir=tmp_path, export_csv=True, + job_workspace_dir=tmp_path, ) # check post-processed data df_expected = tgt_data diff --git a/tests/_data/unit/test_non_context.py b/tests/_data/unit/test_non_context.py index 1115fa6a..5fce94df 100644 --- a/tests/_data/unit/test_non_context.py +++ b/tests/_data/unit/test_non_context.py @@ -12,14 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +import tempfile +from pathlib import Path + import pandas as pd +import pytest from mostlyai.sdk._data.base import DataIdentifier, ForeignKey, NonContextRelation, Schema from mostlyai.sdk._data.file.table.parquet import ParquetDataTable from mostlyai.sdk._data.non_context import ( - handle_non_context_relation, - handle_non_context_relations, - postproc_non_context, + PartitionedDataset, + add_is_null_for_non_context_relation, + add_is_null_for_non_context_relations, + assign_non_context_fks_randomly, sample_non_context_keys, ) @@ -46,7 +51,7 @@ def test_handle_non_context_relation(tmp_path): parent=DataIdentifier(table="non_ctx", column="int"), child=DataIdentifier(table="tgt", column="non_ctx_id"), ) - enriched_data = handle_non_context_relation( + enriched_data = add_is_null_for_non_context_relation( data=data, table=non_context_table, relation=relation, @@ -101,7 +106,7 @@ def test_handle_non_context_relations(tmp_path): schema = Schema(tables=tables) data = schema.tables["tgt"].read_data_prefixed(include_table_prefix=False) - data = handle_non_context_relations( + data = add_is_null_for_non_context_relations( schema=schema, table_name="tgt", data=data, @@ -164,7 +169,7 @@ def test_postproc_non_context(tmp_path): ) # sample non-context keys - tgt_postprocessed_data = postproc_non_context( + tgt_postprocessed_data = assign_non_context_fks_randomly( tgt_data=tgt_data, generated_data_schema=schema, tgt="tgt", @@ -173,3 +178,603 @@ def test_postproc_non_context(tmp_path): assert tgt_postprocessed_data["uncle"].isna()[0] assert not tgt_postprocessed_data["uncle"].isna()[1] assert "uncle._is_null" not in tgt_postprocessed_data.columns + + +class TestPartitionedDatasetBasic: + """Test basic functionality of PartitionedDataset.""" + + def test_partitioned_dataset_initialization(self): + """Test dataset initialization with different partition configurations.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create partitions with different sizes + df1 = pd.DataFrame({"id": range(0, 100), "value": range(0, 100)}) + df2 = pd.DataFrame({"id": range(100, 250), "value": range(100, 250)}) + df3 = pd.DataFrame({"id": range(250, 300), "value": range(250, 300)}) + + for i, df in enumerate([df1, df2, df3]): + file_path = temp_path / f"part{i}.parquet" + df.to_parquet(file_path) + + # Create ParquetDataTable from the directory containing all parquet files + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + + assert len(dataset) == 300 + assert len(dataset.partition_info) == 3 + assert dataset.partition_info[0]["size"] == 100 + assert dataset.partition_info[1]["size"] == 150 + assert dataset.partition_info[2]["size"] == 50 + + def test_single_partition(self): + """Test dataset with single partition.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 100), "value": ["A"] * 100}) + file_path = temp_path / "single.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + assert len(dataset) == 100 + assert len(dataset.partition_info) == 1 + + def test_empty_dataset(self): + """Test dataset with empty partition files.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create empty dataframe + df_empty = pd.DataFrame({"id": [], "value": []}) + df_normal = pd.DataFrame({"id": range(0, 50), "value": ["A"] * 50}) + + file_empty = temp_path / "empty.parquet" + file_normal = temp_path / "normal.parquet" + df_empty.to_parquet(file_empty) + df_normal.to_parquet(file_normal) + + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + + assert len(dataset) == 50 + assert dataset.partition_info[0]["size"] == 0 + assert dataset.partition_info[1]["size"] == 50 + + def test_partitioned_dataset_length(self): + """Test total row count matches sum of partitions.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + sizes = [50, 75, 25, 100] + total_expected = sum(sizes) + + start_id = 0 + for i, size in enumerate(sizes): + df = pd.DataFrame({"id": range(start_id, start_id + size)}) + file_path = temp_path / f"part{i}.parquet" + df.to_parquet(file_path) + start_id += size + + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + assert len(dataset) == total_expected + + +class TestPartitionedDatasetSlicing: + """Test slicing functionality across partitions.""" + + def test_slice_within_single_partition(self): + """Test slicing that stays within a single partition.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df1 = pd.DataFrame({"id": range(0, 100), "category": ["A"] * 100}) + df2 = pd.DataFrame({"id": range(100, 200), "category": ["B"] * 100}) + + file1 = temp_path / "part1.parquet" + file2 = temp_path / "part2.parquet" + df1.to_parquet(file1) + df2.to_parquet(file2) + + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + + # Slice within first partition + slice_result = dataset[10:50] + assert len(slice_result) == 40 + assert all(slice_result.category == "A") + assert slice_result.id.min() == 10 + assert slice_result.id.max() == 49 + + def test_slice_across_multiple_partitions(self): + """Test slicing that spans multiple partitions.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df1 = pd.DataFrame({"id": range(0, 100), "category": ["A"] * 100}) + df2 = pd.DataFrame({"id": range(100, 200), "category": ["B"] * 100}) + df3 = pd.DataFrame({"id": range(200, 300), "category": ["C"] * 100}) + + for i, df in enumerate([df1, df2, df3]): + file_path = temp_path / f"part{i}.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + + # Slice across partitions 1 and 2 + slice_result = dataset[50:150] + assert len(slice_result) == 100 + categories = slice_result.category.unique() + assert "A" in categories and "B" in categories + assert slice_result.id.min() == 50 + assert slice_result.id.max() == 149 + + def test_slice_at_partition_boundaries(self): + """Test slicing exactly at partition boundaries.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df1 = pd.DataFrame({"id": range(0, 100), "category": ["A"] * 100}) + df2 = pd.DataFrame({"id": range(100, 200), "category": ["B"] * 100}) + + file1 = temp_path / "part1.parquet" + file2 = temp_path / "part2.parquet" + df1.to_parquet(file1) + df2.to_parquet(file2) + + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + + # Slice exactly at boundary + slice_result = dataset[100:200] + assert len(slice_result) == 100 + assert all(slice_result.category == "B") + + def test_out_of_bounds_slicing(self): + """Test slicing with out-of-bounds indices.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 100)}) + file_path = temp_path / "part.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + # Test various out-of-bounds scenarios + assert len(dataset[50:150]) == 50 # End beyond dataset + assert len(dataset[-10:50]) == 50 # Negative start (becomes 0) + assert len(dataset[150:200]) == 0 # Start beyond dataset + assert len(dataset[50:50]) == 0 # Empty slice + + def test_empty_slices(self): + """Test slicing that results in empty results.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 100)}) + file_path = temp_path / "part.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + empty_slice = dataset[200:300] # Completely out of bounds + assert len(empty_slice) == 0 + assert isinstance(empty_slice, pd.DataFrame) + + +class TestPartitionedDatasetRandomSampling: + """Test random sampling functionality.""" + + def test_random_sampling_basic(self): + """Test basic random sampling scenarios.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 1000), "value": range(0, 1000)}) + file_path = temp_path / "data.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + # Test sampling less than total + sample = dataset.random_sample(100) + assert len(sample) == 100 + assert set(sample.columns) == {"id", "value"} + + # Test sampling exactly total + sample_all = dataset.random_sample(1000) + assert len(sample_all) == 1000 + + def test_random_sampling_distribution(self): + """Test partition-weighted distribution over multiple samples.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create partitions with known size ratio (1:2:1) + df1 = pd.DataFrame({"id": range(0, 100), "partition": ["A"] * 100}) + df2 = pd.DataFrame({"id": range(100, 300), "partition": ["B"] * 200}) + df3 = pd.DataFrame({"id": range(300, 400), "partition": ["C"] * 100}) + + for i, df in enumerate([df1, df2, df3]): + file_path = temp_path / f"part{i}.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + + # Sample multiple times and check distribution + samples = [] + for _ in range(10): # Multiple samples for statistical significance + sample = dataset.random_sample(80) # 20% of total + distribution = sample.partition.value_counts() + samples.append(distribution) + + # The algorithm selects partitions until it has enough data, + # so we expect some samples to be entirely from one partition + # and others to be mixed. This is the expected behavior. + total_samples = sum(s.sum() for s in samples) + assert total_samples == 800 # 10 samples * 80 each + + def test_random_sampling_edge_cases(self): + """Test edge cases in random sampling.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 100)}) + file_path = temp_path / "data.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + # Test sampling 0 items + empty_sample = dataset.random_sample(0) + assert len(empty_sample) == 0 + assert isinstance(empty_sample, pd.DataFrame) + + # Test sampling 1 item + single_sample = dataset.random_sample(1) + assert len(single_sample) == 1 + + def test_random_sampling_with_empty_partitions(self): + """Test sampling when some partitions are empty.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df_empty = pd.DataFrame({"id": [], "value": []}) + df_normal = pd.DataFrame({"id": range(0, 100), "value": range(0, 100)}) + + file_empty = temp_path / "empty.parquet" + file_normal = temp_path / "normal.parquet" + df_empty.to_parquet(file_empty) + df_normal.to_parquet(file_normal) + + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + + sample = dataset.random_sample(50) + assert len(sample) == 50 + # Should only contain data from non-empty partition + + +class TestPartitionedDatasetErrorHandling: + """Test error handling and edge cases.""" + + def test_invalid_slice_types(self): + """Test invalid slice types raise appropriate errors.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 100)}) + file_path = temp_path / "data.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + with pytest.raises(TypeError): + _ = dataset["invalid"] + + with pytest.raises(TypeError): + _ = dataset[{"invalid": "key"}] + + def test_out_of_range_index_access(self): + """Test accessing indices that are out of range.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 100)}) + file_path = temp_path / "data.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + # Test accessing out-of-range indices + with pytest.raises(IndexError): + dataset._find_partition_for_index(150) + + with pytest.raises(IndexError): + dataset._find_partition_for_index(-1) + + +class TestPartitionedDatasetPerformance: + """Test performance characteristics and scalability.""" + + def test_metadata_reading_efficiency(self): + """Test that metadata reading is fast and doesn't load full data.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create a moderately large dataset + df = pd.DataFrame({"id": range(0, 10000), "data": ["x"] * 10000}) + file_path = temp_path / "large.parquet" + df.to_parquet(file_path) + + # Creating dataset should be fast (only reads metadata) + import time + + start_time = time.time() + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + init_time = time.time() - start_time + + assert len(dataset) == 10000 + assert init_time < 1.0 # Should be very fast + + +class TestPartitionedDatasetIntegration: + """Integration tests with realistic scenarios.""" + + def test_realistic_fk_scenario(self): + """Test scenario similar to FK model usage.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create realistic parent data (multiple partitions) + parent_dir = temp_path / "parent_data" + parent_dir.mkdir() + for i in range(3): + parent_df = pd.DataFrame( + { + "parent_id": range(i * 1000, (i + 1) * 1000), + "parent_value": [f"parent_{j}" for j in range(i * 1000, (i + 1) * 1000)], + } + ) + file_path = parent_dir / f"parents_{i}.parquet" + parent_df.to_parquet(file_path) + + # Create child data + child_dir = temp_path / "child_data" + child_dir.mkdir() + for i in range(2): + child_df = pd.DataFrame( + { + "child_id": range(i * 500, (i + 1) * 500), + "child_value": [f"child_{j}" for j in range(i * 500, (i + 1) * 500)], + } + ) + file_path = child_dir / f"children_{i}.parquet" + child_df.to_parquet(file_path) + + parent_table = ParquetDataTable(path=parent_dir) + child_table = ParquetDataTable(path=child_dir) + parent_dataset = PartitionedDataset(parent_table) + child_dataset = PartitionedDataset(child_table) + + # Simulate FK processing: batch of children, sample parents + children_batch_size = 200 + parents_per_child = 100 + + for start_idx in range(0, len(child_dataset), children_batch_size): + end_idx = min(start_idx + children_batch_size, len(child_dataset)) + + # Get children batch + children_batch = child_dataset[start_idx:end_idx] + + # Sample parents for this batch + n_parents_needed = len(children_batch) * parents_per_child + parent_sample = parent_dataset.random_sample(n_parents_needed) + + # Verify sizes + assert len(children_batch) <= children_batch_size + assert len(parent_sample) == n_parents_needed # With replacement if needed + + def test_memory_bounded_processing(self): + """Test that memory usage is bounded regardless of dataset size.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create dataset larger than cache + for i in range(10): # 10 partitions + df = pd.DataFrame({"id": range(i * 1000, (i + 1) * 1000)}) + file_path = temp_path / f"part{i}.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table) + + # Process data in batches + batch_size = 500 + for start_idx in range(0, len(dataset), batch_size): + end_idx = min(start_idx + batch_size, len(dataset)) + batch = dataset[start_idx:end_idx] + + assert len(batch) <= batch_size + + +class TestPartitionedDatasetCaching: + """Test caching functionality.""" + + def test_basic_caching(self): + """Test that caching works and returns copies.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create test data + df = pd.DataFrame({"id": range(0, 100), "value": range(0, 100)}) + file_path = temp_path / "data.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table, max_cached_partitions=2) + + # Load data twice - should be cached on second access + data1 = dataset[0:50] + data2 = dataset[0:50] + + # Verify data is the same but different objects (copies) + pd.testing.assert_frame_equal(data1, data2) + assert data1 is not data2 # Different objects + + # Verify cache has 1 partition (using lru_cache stats) + cache_info = dataset._load_partition_cached.cache_info() + assert cache_info.currsize == 1 + + def test_cache_eviction(self): + """Test that LRU eviction works correctly.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create 3 partitions + for i in range(3): + df = pd.DataFrame({"id": range(i * 100, (i + 1) * 100)}) + file_path = temp_path / f"part{i}.parquet" + df.to_parquet(file_path) + + # Cache limit of 2 + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table, max_cached_partitions=2) + + # Access first partition + _ = dataset[0:10] + assert dataset._load_partition_cached.cache_info().currsize == 1 + + # Access second partition + _ = dataset[100:110] + assert dataset._load_partition_cached.cache_info().currsize == 2 + + # Access third partition - should evict first (LRU) + _ = dataset[200:210] + assert dataset._load_partition_cached.cache_info().currsize == 2 + + def test_clear_cache(self): + """Test cache clearing functionality.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 100)}) + file_path = temp_path / "data.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + # Load data to populate cache + _ = dataset[0:10] + assert dataset._load_partition_cached.cache_info().currsize == 1 + + # Clear cache + dataset.clear_cache() + assert dataset._load_partition_cached.cache_info().currsize == 0 + + def test_cache_hit_ratio(self): + """Test that cache hits improve with repeated access.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + df = pd.DataFrame({"id": range(0, 1000)}) + file_path = temp_path / "data.parquet" + df.to_parquet(file_path) + + table = ParquetDataTable(path=file_path) + dataset = PartitionedDataset(table) + + # Initial access - should be cache miss + _ = dataset[0:100] + cache_info = dataset._load_partition_cached.cache_info() + assert cache_info.misses == 1 + assert cache_info.hits == 0 + + # Repeated access - should be cache hit + _ = dataset[50:150] # Overlapping slice, same partition + cache_info = dataset._load_partition_cached.cache_info() + assert cache_info.misses == 1 # Still only 1 miss + assert cache_info.hits == 1 # Now 1 hit + + def test_unlimited_cache_with_minus_one(self): + """Test that max_cached_partitions=-1 keeps all partitions in memory.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create 5 partitions (more than typical cache limit) + for i in range(5): + df = pd.DataFrame({"id": range(i * 100, (i + 1) * 100)}) + file_path = temp_path / f"part{i}.parquet" + df.to_parquet(file_path) + + # Use -1 for unlimited caching + table = ParquetDataTable(path=temp_path) + dataset = PartitionedDataset(table, max_cached_partitions=-1) + + # Access all partitions + for i in range(5): + _ = dataset[i * 100 : (i * 100) + 10] + + # All partitions should be cached (no eviction with unlimited cache) + cache_info = dataset._load_partition_cached.cache_info() + assert cache_info.currsize == 5 # All 5 partitions cached + assert cache_info.misses == 5 # 5 initial misses + assert cache_info.hits == 0 # No hits yet + + # Access partitions again - should all be cache hits + for i in range(5): + _ = dataset[i * 100 : (i * 100) + 10] + + cache_info = dataset._load_partition_cached.cache_info() + assert cache_info.currsize == 5 # Still all 5 partitions cached + assert cache_info.misses == 5 # Still only 5 misses + assert cache_info.hits == 5 # Now 5 hits + + def test_unlimited_cache_vs_limited_cache(self): + """Test comparison between unlimited and limited cache behavior.""" + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Create 4 partitions + for i in range(4): + df = pd.DataFrame({"id": range(i * 100, (i + 1) * 100)}) + file_path = temp_path / f"part{i}.parquet" + df.to_parquet(file_path) + + # Test with limited cache (maxsize=2) + limited_table = ParquetDataTable(path=temp_path) + limited_dataset = PartitionedDataset(limited_table, max_cached_partitions=2) + + # Access all 4 partitions + for i in range(4): + _ = limited_dataset[i * 100 : (i * 100) + 10] + + # Should only cache 2 partitions (LRU eviction) + limited_cache_info = limited_dataset._load_partition_cached.cache_info() + assert limited_cache_info.currsize == 2 + + # Test with unlimited cache (-1) + unlimited_table = ParquetDataTable(path=temp_path) + unlimited_dataset = PartitionedDataset(unlimited_table, max_cached_partitions=-1) + + # Access all 4 partitions + for i in range(4): + _ = unlimited_dataset[i * 100 : (i * 100) + 10] + + # Should cache all 4 partitions + unlimited_cache_info = unlimited_dataset._load_partition_cached.cache_info() + assert unlimited_cache_info.currsize == 4 diff --git a/tests/_local/unit/test_execution_plan.py b/tests/_local/unit/test_execution_plan.py index 68f7beee..b4fbd1d8 100644 --- a/tests/_local/unit/test_execution_plan.py +++ b/tests/_local/unit/test_execution_plan.py @@ -195,7 +195,9 @@ def test_make_generator_execution_plan(): expected_execution_plan.add_task( TaskType.train_language, parent=sync_task, target_table_name="comments", include_report=False ) - expected_execution_plan.add_task(TaskType.sync) + post_training_sync = expected_execution_plan.add_task(TaskType.sync) + finalize_task = expected_execution_plan.add_task(TaskType.finalize_training, parent=post_training_sync) + expected_execution_plan.add_task(TaskType.sync, parent=finalize_task) assert len(execution_plan.tasks) == len(expected_execution_plan.tasks) for actual, expected in zip(execution_plan.tasks, expected_execution_plan.tasks): @@ -214,10 +216,11 @@ def test_make_generator_execution_plan(): actual_step_codes = [s.step_code for s in actual.steps] expected_step_codes = [s.step_code for s in expected.steps] assert actual_step_codes == expected_step_codes - if actual.target_table_name == "comments" and actual.type == TaskType.train_language: - assert actual_step_codes == TRAINING_TASK_STEPS - else: - assert actual_step_codes == TRAINING_TASK_STEPS + TRAINING_TASK_REPORT_STEPS + if actual.type in (TaskType.train_tabular, TaskType.train_language): + if actual.target_table_name == "comments" and actual.type == TaskType.train_language: + assert actual_step_codes == TRAINING_TASK_STEPS + else: + assert actual_step_codes == TRAINING_TASK_STEPS + TRAINING_TASK_REPORT_STEPS def test_make_synthetic_dataset_execution_plan_with_probe(): diff --git a/uv.lock b/uv.lock index 16ba8b8f..0b25ace0 100644 --- a/uv.lock +++ b/uv.lock @@ -18,7 +18,7 @@ resolution-markers = [ [[package]] name = "accelerate" -version = "1.10.1" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, @@ -29,9 +29,9 @@ dependencies = [ { name = "safetensors" }, { name = "torch" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/72/ff3961c19ee395c3d30ac630ee77bfb0e1b46b87edc504d4f83bb4a89705/accelerate-1.10.1.tar.gz", hash = "sha256:3dea89e433420e4bfac0369cae7e36dcd6a56adfcfd38cdda145c6225eab5df8", size = 392446, upload-time = "2025-08-25T13:57:06.21Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/60/2757c4f03a8705dbf80b1268b03881927878dca5ed07d74f733fb6c219e0/accelerate-1.11.0.tar.gz", hash = "sha256:bb1caf2597b4cd632b917b5000c591d10730bb024a79746f1ee205bba80bd229", size = 393715, upload-time = "2025-10-20T14:42:25.025Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/a0/d9ef19f780f319c21ee90ecfef4431cbeeca95bec7f14071785c17b6029b/accelerate-1.10.1-py3-none-any.whl", hash = "sha256:3621cff60b9a27ce798857ece05e2b9f56fcc71631cfb31ccf71f0359c311f11", size = 374909, upload-time = "2025-08-25T13:57:04.55Z" }, + { url = "https://files.pythonhosted.org/packages/77/85/85951bc0f9843e2c10baaa1b6657227056095de08f4d1eea7d8b423a6832/accelerate-1.11.0-py3-none-any.whl", hash = "sha256:a628fa6beb069b8e549460fc449135d5bd8d73e7a11fd09f0bc9fc4ace7f06f1", size = 375777, upload-time = "2025-10-20T14:42:23.256Z" }, ] [[package]] @@ -80,7 +80,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.0" +version = "3.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -92,76 +92,76 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/f1/8515650ac3121a9e55c7b217c60e7fae3e0134b5acfe65691781b5356929/aiohttp-3.13.0.tar.gz", hash = "sha256:378dbc57dd8cf341ce243f13fa1fa5394d68e2e02c15cd5f28eae35a70ec7f67", size = 7832348, upload-time = "2025-10-06T19:58:48.089Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/18/a3a9c9b7c8d400f71d1ff93c3e1520a5d53dba170f829ca9c6b2b070677b/aiohttp-3.13.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ca69ec38adf5cadcc21d0b25e2144f6a25b7db7bea7e730bac25075bc305eff0", size = 734428, upload-time = "2025-10-06T19:54:40.285Z" }, - { url = "https://files.pythonhosted.org/packages/aa/02/f1eac06d78997e015030130ccf1c7cf864a919f97d77ff27e89c82fc3186/aiohttp-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:240f99f88a9a6beb53ebadac79a2e3417247aa756202ed234b1dbae13d248092", size = 491939, upload-time = "2025-10-06T19:54:42.113Z" }, - { url = "https://files.pythonhosted.org/packages/e1/db/5d65af7cbe5f302e23b1ea5cfc156cd0c7738a0d2db531a3837d2754de94/aiohttp-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a4676b978a9711531e7cea499d4cdc0794c617a1c0579310ab46c9fdf5877702", size = 487229, upload-time = "2025-10-06T19:54:43.978Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d5/56c622ad3bd57ff4adc2b701f298dcc0408735a8af998cec1c66a9ce224e/aiohttp-3.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48fcdd5bc771cbbab8ccc9588b8b6447f6a30f9fe00898b1a5107098e00d6793", size = 1666118, upload-time = "2025-10-06T19:54:46.569Z" }, - { url = "https://files.pythonhosted.org/packages/44/16/db236671ec3758e3a6be6977009e74016470368012a58fea4b3799546549/aiohttp-3.13.0-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:eeea0cdd2f687e210c8f605f322d7b0300ba55145014a5dbe98bd4be6fff1f6c", size = 1633983, upload-time = "2025-10-06T19:54:48.244Z" }, - { url = "https://files.pythonhosted.org/packages/19/ad/d96d7d7023e7f5215b8737cad21a7637f6d9d10fbfbfef0435d0277f71a2/aiohttp-3.13.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b3f01d5aeb632adaaf39c5e93f040a550464a768d54c514050c635adcbb9d0", size = 1725922, upload-time = "2025-10-06T19:54:49.885Z" }, - { url = "https://files.pythonhosted.org/packages/88/d7/e8a5ba2bbd929ed587b2a8ea9390765daede2d8cd28dfae3a0773c6d3fbc/aiohttp-3.13.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a4dc0b83e25267f42ef065ea57653de4365b56d7bc4e4cfc94fabe56998f8ee6", size = 1813770, upload-time = "2025-10-06T19:54:51.648Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/135c21e85ffeff66b80ecd8a647ca104f2e5a91c37dc86649244ddbf87ab/aiohttp-3.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:72714919ed9b90f030f761c20670e529c4af96c31bd000917dd0c9afd1afb731", size = 1667322, upload-time = "2025-10-06T19:54:53.668Z" }, - { url = "https://files.pythonhosted.org/packages/f6/38/348c4343052a400968dbf2051ee3dc222bdefd95af5874cf0f04cc7a8c92/aiohttp-3.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:564be41e85318403fdb176e9e5b3e852d528392f42f2c1d1efcbeeed481126d7", size = 1553270, upload-time = "2025-10-06T19:54:56.054Z" }, - { url = "https://files.pythonhosted.org/packages/47/89/71cbda30f0900ab16084769960c467a355d6b1db51668fbb821c4a4ad5ed/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:84912962071087286333f70569362e10793f73f45c48854e6859df11001eb2d3", size = 1637087, upload-time = "2025-10-06T19:54:58.548Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b1/5ff5fcaecccdcd5be7ff717cbde6e630760a8130e89167c3aa05b6b57707/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90b570f1a146181c3d6ae8f755de66227ded49d30d050479b5ae07710f7894c5", size = 1643443, upload-time = "2025-10-06T19:55:00.856Z" }, - { url = "https://files.pythonhosted.org/packages/87/e2/1d1f202f43c8be1956f05196159064cc05dc6842a33c1397cbb1b99610af/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2d71ca30257ce756e37a6078b1dff2d9475fee13609ad831eac9a6531bea903b", size = 1695571, upload-time = "2025-10-06T19:55:03.006Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b9/53c1df2991686f947a9651265757ea12c4afc29b351a249b73a0fc81dd3c/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:cd45eb70eca63f41bb156b7dffbe1a7760153b69892d923bdb79a74099e2ed90", size = 1539975, upload-time = "2025-10-06T19:55:04.839Z" }, - { url = "https://files.pythonhosted.org/packages/93/24/345166f9c4cd2f5cc1d2173131998ee4adab0db8729126db32a7f91ed400/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:5ae3a19949a27982c7425a7a5a963c1268fdbabf0be15ab59448cbcf0f992519", size = 1712866, upload-time = "2025-10-06T19:55:06.905Z" }, - { url = "https://files.pythonhosted.org/packages/09/f1/e8f70462848b74d49b3115050623ecbd697889713c2c93c96616da56b2de/aiohttp-3.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ea6df292013c9f050cbf3f93eee9953d6e5acd9e64a0bf4ca16404bfd7aa9bcc", size = 1654058, upload-time = "2025-10-06T19:55:08.51Z" }, - { url = "https://files.pythonhosted.org/packages/23/ba/47fd065510a8bfab5d5f6e1d97c0de672447c0a941c5021298bd7210afc3/aiohttp-3.13.0-cp310-cp310-win32.whl", hash = "sha256:3b64f22fbb6dcd5663de5ef2d847a5638646ef99112503e6f7704bdecb0d1c4d", size = 430230, upload-time = "2025-10-06T19:55:10.178Z" }, - { url = "https://files.pythonhosted.org/packages/c4/38/f5385cb79afa1f31bcaa3625a9e8d849b782edaeac09f894f46439e006a1/aiohttp-3.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:f8d877aa60d80715b2afc565f0f1aea66565824c229a2d065b31670e09fed6d7", size = 453013, upload-time = "2025-10-06T19:55:11.623Z" }, - { url = "https://files.pythonhosted.org/packages/b1/db/df80cacac46cd548a736c5535b13cc18925cf6f9f83cd128cf3839842219/aiohttp-3.13.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:99eb94e97a42367fef5fc11e28cb2362809d3e70837f6e60557816c7106e2e20", size = 741374, upload-time = "2025-10-06T19:55:13.095Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f9/2d6d93fd57ab4726e18a7cdab083772eda8302d682620fbf2aef48322351/aiohttp-3.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4696665b2713021c6eba3e2b882a86013763b442577fe5d2056a42111e732eca", size = 494956, upload-time = "2025-10-06T19:55:14.687Z" }, - { url = "https://files.pythonhosted.org/packages/89/a6/e1c061b079fed04ffd6777950c82f2e8246fd08b7b3c4f56fdd47f697e5a/aiohttp-3.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3e6a38366f7f0d0f6ed7a1198055150c52fda552b107dad4785c0852ad7685d1", size = 491154, upload-time = "2025-10-06T19:55:16.661Z" }, - { url = "https://files.pythonhosted.org/packages/fe/4d/ee8913c0d2c7da37fdc98673a342b51611eaa0871682b37b8430084e35b5/aiohttp-3.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aab715b1a0c37f7f11f9f1f579c6fbaa51ef569e47e3c0a4644fba46077a9409", size = 1745707, upload-time = "2025-10-06T19:55:18.376Z" }, - { url = "https://files.pythonhosted.org/packages/f9/70/26b2c97e8fa68644aec43d788940984c5f3b53a8d1468d5baaa328f809c9/aiohttp-3.13.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7972c82bed87d7bd8e374b60a6b6e816d75ba4f7c2627c2d14eed216e62738e1", size = 1702404, upload-time = "2025-10-06T19:55:20.098Z" }, - { url = "https://files.pythonhosted.org/packages/65/1e/c8aa3c293a0e8b18968b1b88e9bd8fb269eb67eb7449f504a4c3e175b159/aiohttp-3.13.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca8313cb852af788c78d5afdea24c40172cbfff8b35e58b407467732fde20390", size = 1805519, upload-time = "2025-10-06T19:55:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/51/b6/a3753fe86249eb441768658cfc00f8c4e0913b255c13be00ddb8192775e1/aiohttp-3.13.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c333a2385d2a6298265f4b3e960590f787311b87f6b5e6e21bb8375914ef504", size = 1893904, upload-time = "2025-10-06T19:55:23.462Z" }, - { url = "https://files.pythonhosted.org/packages/51/6d/7b1e020fe1d2a2be7cf0ce5e35922f345e3507cf337faa1a6563c42065c1/aiohttp-3.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc6d5fc5edbfb8041d9607f6a417997fa4d02de78284d386bea7ab767b5ea4f3", size = 1745043, upload-time = "2025-10-06T19:55:25.208Z" }, - { url = "https://files.pythonhosted.org/packages/e6/df/aad5dce268f9d4f29759c3eeb5fb5995c569d76abb267468dc1075218d5b/aiohttp-3.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ddedba3d0043349edc79df3dc2da49c72b06d59a45a42c1c8d987e6b8d175b8", size = 1604765, upload-time = "2025-10-06T19:55:27.157Z" }, - { url = "https://files.pythonhosted.org/packages/1c/19/a84a0e97b2da2224c8b85e1aef5cac834d07b2903c17bff1a6bdbc7041d2/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23ca762140159417a6bbc959ca1927f6949711851e56f2181ddfe8d63512b5ad", size = 1721737, upload-time = "2025-10-06T19:55:28.854Z" }, - { url = "https://files.pythonhosted.org/packages/6c/61/ca6ad390128d964a08554fd63d6df5810fb5fbc7e599cb9e617f1729ae19/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfe824d6707a5dc3c5676685f624bc0c63c40d79dc0239a7fd6c034b98c25ebe", size = 1716052, upload-time = "2025-10-06T19:55:30.563Z" }, - { url = "https://files.pythonhosted.org/packages/2a/71/769e249e6625372c7d14be79b8b8c3b0592963a09793fb3d36758e60952c/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3c11fa5dd2ef773a8a5a6daa40243d83b450915992eab021789498dc87acc114", size = 1783532, upload-time = "2025-10-06T19:55:32.798Z" }, - { url = "https://files.pythonhosted.org/packages/66/64/b9cd03cdbb629bc492e4a744fbe96550a8340b0cd7a0cc4a9c90cfecd8d3/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:00fdfe370cffede3163ba9d3f190b32c0cfc8c774f6f67395683d7b0e48cdb8a", size = 1593072, upload-time = "2025-10-06T19:55:34.686Z" }, - { url = "https://files.pythonhosted.org/packages/24/0e/87922c8cfdbd09f5e2197e9d87714a98c99c423560d44739e3af55400fe3/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6475e42ef92717a678bfbf50885a682bb360a6f9c8819fb1a388d98198fdcb80", size = 1798613, upload-time = "2025-10-06T19:55:36.393Z" }, - { url = "https://files.pythonhosted.org/packages/c5/bb/a3adfe2af76e1ee9e3b5464522004b148b266bc99d7ec424ca7843d64a3c/aiohttp-3.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:77da5305a410910218b99f2a963092f4277d8a9c1f429c1ff1b026d1826bd0b6", size = 1737480, upload-time = "2025-10-06T19:55:38.043Z" }, - { url = "https://files.pythonhosted.org/packages/ad/53/e124dcbd64e6365602f3493fe37a11ca5b7ac0a40822a6e2bc8260cd08e0/aiohttp-3.13.0-cp311-cp311-win32.whl", hash = "sha256:2f9d9ea547618d907f2ee6670c9a951f059c5994e4b6de8dcf7d9747b420c820", size = 429824, upload-time = "2025-10-06T19:55:39.595Z" }, - { url = "https://files.pythonhosted.org/packages/3e/bd/485d98b372a2cd6998484a93ddd401ec6b6031657661c36846a10e2a1f6e/aiohttp-3.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f19f7798996d4458c669bd770504f710014926e9970f4729cf55853ae200469", size = 454137, upload-time = "2025-10-06T19:55:41.617Z" }, - { url = "https://files.pythonhosted.org/packages/3a/95/7e8bdfa6e79099a086d59d42589492f1fe9d29aae3cefb58b676015ce278/aiohttp-3.13.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c272a9a18a5ecc48a7101882230046b83023bb2a662050ecb9bfcb28d9ab53a", size = 735585, upload-time = "2025-10-06T19:55:43.401Z" }, - { url = "https://files.pythonhosted.org/packages/9f/20/2f1d3ee06ee94eafe516810705219bff234d09f135d6951661661d5595ae/aiohttp-3.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:97891a23d7fd4e1afe9c2f4473e04595e4acb18e4733b910b6577b74e7e21985", size = 490613, upload-time = "2025-10-06T19:55:45.237Z" }, - { url = "https://files.pythonhosted.org/packages/74/15/ab8600ef6dc1dcd599009a81acfed2ea407037e654d32e47e344e0b08c34/aiohttp-3.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:475bd56492ce5f4cffe32b5533c6533ee0c406d1d0e6924879f83adcf51da0ae", size = 489750, upload-time = "2025-10-06T19:55:46.937Z" }, - { url = "https://files.pythonhosted.org/packages/33/59/752640c2b86ca987fe5703a01733b00d375e6cd2392bc7574489934e64e5/aiohttp-3.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c32ada0abb4bc94c30be2b681c42f058ab104d048da6f0148280a51ce98add8c", size = 1736812, upload-time = "2025-10-06T19:55:48.917Z" }, - { url = "https://files.pythonhosted.org/packages/3d/c6/dd6b86ddb852a7fdbcdc7a45b6bdc80178aef713c08279afcaee7a5a9f07/aiohttp-3.13.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4af1f8877ca46ecdd0bc0d4a6b66d4b2bddc84a79e2e8366bc0d5308e76bceb8", size = 1698535, upload-time = "2025-10-06T19:55:50.75Z" }, - { url = "https://files.pythonhosted.org/packages/33/e2/27c92d205b9e8cee7661670e8e9f187931b71e26d42796b153d2a0ba6949/aiohttp-3.13.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e04ab827ec4f775817736b20cdc8350f40327f9b598dec4e18c9ffdcbea88a93", size = 1766573, upload-time = "2025-10-06T19:55:53.106Z" }, - { url = "https://files.pythonhosted.org/packages/df/6a/1fc1ad71d130a30f7a207d8d958a41224c29b834463b5185efb2dbff6ad4/aiohttp-3.13.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a6d9487b9471ec36b0faedf52228cd732e89be0a2bbd649af890b5e2ce422353", size = 1865229, upload-time = "2025-10-06T19:55:55.01Z" }, - { url = "https://files.pythonhosted.org/packages/14/51/d0c1701a79fcb0109cff5304da16226581569b89a282d8e7f1549a7e3ec0/aiohttp-3.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e66c57416352f36bf98f6641ddadd47c93740a22af7150d3e9a1ef6e983f9a8", size = 1750379, upload-time = "2025-10-06T19:55:57.219Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3d/2ec4b934f85856de1c0c18e90adc8902adadbfac2b3c0b831bfeb7214fc8/aiohttp-3.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:469167d5372f5bb3aedff4fc53035d593884fff2617a75317740e885acd48b04", size = 1560798, upload-time = "2025-10-06T19:55:58.888Z" }, - { url = "https://files.pythonhosted.org/packages/38/56/e23d9c3e13006e599fdce3851517c70279e177871e3e567d22cf3baf5d6c/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a9f3546b503975a69b547c9fd1582cad10ede1ce6f3e313a2f547c73a3d7814f", size = 1697552, upload-time = "2025-10-06T19:56:01.172Z" }, - { url = "https://files.pythonhosted.org/packages/56/cb/caa32c2ccaeca0a3dc39129079fd2ad02f9406c3a5f7924340435b87d4cd/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6b4174fcec98601f0cfdf308ee29a6ae53c55f14359e848dab4e94009112ee7d", size = 1718609, upload-time = "2025-10-06T19:56:03.102Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c0/5911856fef9e40fd1ccbb8c54a90116875d5753a92c1cac66ce2059b390d/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a533873a7a4ec2270fb362ee5a0d3b98752e4e1dc9042b257cd54545a96bd8ed", size = 1735887, upload-time = "2025-10-06T19:56:04.841Z" }, - { url = "https://files.pythonhosted.org/packages/0e/48/8d6f4757a24c02f0a454c043556593a00645d10583859f7156db44d8b7d3/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ce887c5e54411d607ee0959cac15bb31d506d86a9bcaddf0b7e9d63325a7a802", size = 1553079, upload-time = "2025-10-06T19:56:07.197Z" }, - { url = "https://files.pythonhosted.org/packages/39/fa/e82c9445e40b50e46770702b5b6ca2f767966d53e1a5eef03583ceac6df6/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d871f6a30d43e32fc9252dc7b9febe1a042b3ff3908aa83868d7cf7c9579a59b", size = 1762750, upload-time = "2025-10-06T19:56:09.376Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e6/9d30554e7f1e700bfeae4ab6b153d5dc7441606a9ec5e929288fa93a1477/aiohttp-3.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:222c828243b4789d79a706a876910f656fad4381661691220ba57b2ab4547865", size = 1717461, upload-time = "2025-10-06T19:56:11.551Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e5/29cca547990a59ea54f0674fc01de98519fc628cfceeab6175711750eca7/aiohttp-3.13.0-cp312-cp312-win32.whl", hash = "sha256:682d2e434ff2f1108314ff7f056ce44e457f12dbed0249b24e106e385cf154b9", size = 424633, upload-time = "2025-10-06T19:56:13.316Z" }, - { url = "https://files.pythonhosted.org/packages/8b/68/46dd042d7bc62eab30bafdb8569f55ef125c3a88bb174270324224f8df56/aiohttp-3.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a2be20eb23888df130214b91c262a90e2de1553d6fb7de9e9010cec994c0ff2", size = 451401, upload-time = "2025-10-06T19:56:15.188Z" }, - { url = "https://files.pythonhosted.org/packages/86/2c/ac53efdc9c10e41399acc2395af98f835b86d0141d5c3820857eb9f6a14a/aiohttp-3.13.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:00243e51f16f6ec0fb021659d4af92f675f3cf9f9b39efd142aa3ad641d8d1e6", size = 730090, upload-time = "2025-10-06T19:56:16.858Z" }, - { url = "https://files.pythonhosted.org/packages/13/18/1ac95683e1c1d48ef4503965c96f5401618a04c139edae12e200392daae8/aiohttp-3.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059978d2fddc462e9211362cbc8446747ecd930537fa559d3d25c256f032ff54", size = 488041, upload-time = "2025-10-06T19:56:18.659Z" }, - { url = "https://files.pythonhosted.org/packages/fd/79/ef0d477c771a642d1a881b92d226314c43d3c74bc674c93e12e679397a97/aiohttp-3.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:564b36512a7da3b386143c611867e3f7cfb249300a1bf60889bd9985da67ab77", size = 486989, upload-time = "2025-10-06T19:56:20.371Z" }, - { url = "https://files.pythonhosted.org/packages/37/b4/0e440481a0e77a551d6c5dcab5d11f1ff6b2b2ddb8dedc24f54f5caad732/aiohttp-3.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4aa995b9156ae499393d949a456a7ab0b994a8241a96db73a3b73c7a090eff6a", size = 1718331, upload-time = "2025-10-06T19:56:22.188Z" }, - { url = "https://files.pythonhosted.org/packages/e6/59/76c421cc4a75bb1aceadb92f20ee6f05a990aa6960c64b59e8e0d340e3f5/aiohttp-3.13.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55ca0e95a3905f62f00900255ed807c580775174252999286f283e646d675a49", size = 1686263, upload-time = "2025-10-06T19:56:24.393Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ac/5095f12a79c7775f402cfc3e83651b6e0a92ade10ddf7f2c78c4fed79f71/aiohttp-3.13.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:49ce7525853a981fc35d380aa2353536a01a9ec1b30979ea4e35966316cace7e", size = 1754265, upload-time = "2025-10-06T19:56:26.365Z" }, - { url = "https://files.pythonhosted.org/packages/05/d7/a48e4989bd76cc70600c505bbdd0d90ca1ad7f9053eceeb9dbcf9345a9ec/aiohttp-3.13.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2117be9883501eaf95503bd313eb4c7a23d567edd44014ba15835a1e9ec6d852", size = 1856486, upload-time = "2025-10-06T19:56:28.438Z" }, - { url = "https://files.pythonhosted.org/packages/1e/02/45b388b49e37933f316e1fb39c0de6fb1d77384b0c8f4cf6af5f2cbe3ea6/aiohttp-3.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d169c47e40c911f728439da853b6fd06da83761012e6e76f11cb62cddae7282b", size = 1737545, upload-time = "2025-10-06T19:56:30.688Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a7/4fde058f1605c34a219348a83a99f14724cc64e68a42480fc03cf40f9ea3/aiohttp-3.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:703ad3f742fc81e543638a7bebddd35acadaa0004a5e00535e795f4b6f2c25ca", size = 1552958, upload-time = "2025-10-06T19:56:32.528Z" }, - { url = "https://files.pythonhosted.org/packages/d1/12/0bac4d29231981e3aa234e88d1931f6ba38135ff4c2cf3afbb7895527630/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bf635c3476f4119b940cc8d94ad454cbe0c377e61b4527f0192aabeac1e9370", size = 1681166, upload-time = "2025-10-06T19:56:34.81Z" }, - { url = "https://files.pythonhosted.org/packages/71/95/b829eb5f8ac1ca1d8085bb8df614c8acf3ff32e23ad5ad1173c7c9761daa/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:cfe6285ef99e7ee51cef20609be2bc1dd0e8446462b71c9db8bb296ba632810a", size = 1710516, upload-time = "2025-10-06T19:56:36.787Z" }, - { url = "https://files.pythonhosted.org/packages/47/6d/15ccf4ef3c254d899f62580e0c7fc717014f4d14a3ac31771e505d2c736c/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8af6391c5f2e69749d7f037b614b8c5c42093c251f336bdbfa4b03c57d6c4", size = 1731354, upload-time = "2025-10-06T19:56:38.659Z" }, - { url = "https://files.pythonhosted.org/packages/46/6a/8acf6c57e03b6fdcc8b4c06392e66abaff3213ea275e41db3edb20738d91/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:12f5d820fadc5848d4559ea838aef733cf37ed2a1103bba148ac2f5547c14c29", size = 1548040, upload-time = "2025-10-06T19:56:40.578Z" }, - { url = "https://files.pythonhosted.org/packages/75/7d/fbfd59ab2a83fe2578ce79ac3db49727b81e9f4c3376217ad09c03c6d279/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f1338b61ea66f4757a0544ed8a02ccbf60e38d9cfb3225888888dd4475ebb96", size = 1756031, upload-time = "2025-10-06T19:56:42.492Z" }, - { url = "https://files.pythonhosted.org/packages/99/e7/cc9f0fdf06cab3ca61e6b62bff9a4b978b8ca736e9d76ddf54365673ab19/aiohttp-3.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:582770f82513419512da096e8df21ca44f86a2e56e25dc93c5ab4df0fe065bf0", size = 1714933, upload-time = "2025-10-06T19:56:45.542Z" }, - { url = "https://files.pythonhosted.org/packages/db/43/7abbe1de94748a58a71881163ee280fd3217db36e8344d109f63638fe16a/aiohttp-3.13.0-cp313-cp313-win32.whl", hash = "sha256:3194b8cab8dbc882f37c13ef1262e0a3d62064fa97533d3aa124771f7bf1ecee", size = 423799, upload-time = "2025-10-06T19:56:47.779Z" }, - { url = "https://files.pythonhosted.org/packages/c9/58/afab7f2b9e7df88c995995172eb78cae8a3d5a62d5681abaade86b3f0089/aiohttp-3.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:7897298b3eedc790257fef8a6ec582ca04e9dbe568ba4a9a890913b925b8ea21", size = 450138, upload-time = "2025-10-06T19:56:49.49Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/ba/fa/3ae643cd525cf6844d3dc810481e5748107368eb49563c15a5fb9f680750/aiohttp-3.13.1.tar.gz", hash = "sha256:4b7ee9c355015813a6aa085170b96ec22315dabc3d866fd77d147927000e9464", size = 7835344, upload-time = "2025-10-17T14:03:29.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/5097441cc3047eccc2e0bfed3760ed068489b8392545d3aec0d8fbfab2b5/aiohttp-3.13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2349a6b642020bf20116a8a5c83bae8ba071acf1461c7cbe45fc7fafd552e7e2", size = 735069, upload-time = "2025-10-17T13:58:56.602Z" }, + { url = "https://files.pythonhosted.org/packages/8c/2b/726466b4b4b16271a3db2a8a914d754d6cb9cee7bebde1f3ac6043e4e030/aiohttp-3.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2a8434ca31c093a90edb94d7d70e98706ce4d912d7f7a39f56e1af26287f4bb7", size = 492575, upload-time = "2025-10-17T13:58:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/82/1f/364e64292c95bb6c9e2823b0afa1ad3f06524c573d45df82294be572489d/aiohttp-3.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0bd610a7e87431741021a9a6ab775e769ea8c01bf01766d481282bfb17df597f", size = 487862, upload-time = "2025-10-17T13:59:00.315Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/c5a774b3125ac854987b8ca45a6d995829987d01ece4525d3fc369a9ca88/aiohttp-3.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:777ec887264b629395b528af59b8523bf3164d4c6738cd8989485ff3eda002e2", size = 1666761, upload-time = "2025-10-17T13:59:02.224Z" }, + { url = "https://files.pythonhosted.org/packages/29/be/32c6c1d3a6c69e594b855bbf4014bea4c42008b0daac8c6e5c9f03207b89/aiohttp-3.13.1-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ac1892f56e2c445aca5ba28f3bf8e16b26dfc05f3c969867b7ef553b74cb4ebe", size = 1634627, upload-time = "2025-10-17T13:59:03.829Z" }, + { url = "https://files.pythonhosted.org/packages/73/8d/fde3a8f4801b14e0b9490f5bc86c5106cb7d96bd60ff2aaee53749c72fe1/aiohttp-3.13.1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:499a047d1c5e490c31d16c033e2e47d1358f0e15175c7a1329afc6dfeb04bc09", size = 1726564, upload-time = "2025-10-17T13:59:05.997Z" }, + { url = "https://files.pythonhosted.org/packages/52/b2/8290556f1f6b17b1af976a9abb17f9b54dc7218e11bbf6abbebaa7cc70fb/aiohttp-3.13.1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:610be925f89501938c770f1e28ca9dd62e9b308592c81bd5d223ce92434c0089", size = 1814413, upload-time = "2025-10-17T13:59:08.975Z" }, + { url = "https://files.pythonhosted.org/packages/ef/6b/4b657e9fa72479df38117609d4ec8e4b07e8110b872df3872f9c6a96e26b/aiohttp-3.13.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90eb902c06c6ac85d6b80fa9f2bd681f25b1ebf73433d428b3d182a507242711", size = 1667964, upload-time = "2025-10-17T13:59:10.606Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ed/563de175d01fa26459a60a7c82dbf69d20e356d459476a7526329091b4c3/aiohttp-3.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ab8ac3224b2beb46266c094b3869d68d5f96f35dba98e03dea0acbd055eefa03", size = 1553917, upload-time = "2025-10-17T13:59:12.312Z" }, + { url = "https://files.pythonhosted.org/packages/39/26/48a4b5681eada16eb5b39cae277765aed1644b03610c43eadb8b331ccfea/aiohttp-3.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:79ac65b6e2731558aad1e4c1a655d2aa2a77845b62acecf5898b0d4fe8c76618", size = 1637730, upload-time = "2025-10-17T13:59:14.395Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/57b137af37344e03c7f6b28ddf38a4af820b53c1fa9ce13f668fe468d2e2/aiohttp-3.13.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4dadbd858ed8c04d1aa7a2a91ad65f8e1fbd253ae762ef5be8111e763d576c3c", size = 1644088, upload-time = "2025-10-17T13:59:16.749Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c4/e49bafa4babef09929b10968a6b6efe3707fbaa5c5bb7c8db7f810232269/aiohttp-3.13.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e0b2ccd331bc77149e88e919aa95c228a011e03e1168fd938e6aeb1a317d7a8a", size = 1696215, upload-time = "2025-10-17T13:59:18.711Z" }, + { url = "https://files.pythonhosted.org/packages/15/e4/8414be434b3e50f9089ffa7c4d5130ba6ff0d1c6fa9f55cd760b088abbe0/aiohttp-3.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:fba3c85fb24fe204e73f3c92f09f4f5cfa55fa7e54b34d59d91b7c5a258d0f6a", size = 1540617, upload-time = "2025-10-17T13:59:20.46Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8b/31cb6725f819b74a9c0b0055c500187294e73aea40708b6a5aa7b328ea4c/aiohttp-3.13.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d5011e4e741d2635cda18f2997a56e8e1d1b94591dc8732f2ef1d3e1bfc5f45", size = 1713509, upload-time = "2025-10-17T13:59:22.61Z" }, + { url = "https://files.pythonhosted.org/packages/24/ac/49a79c2711423cfa091e265c46e58617de31258c64502b890f25421cb742/aiohttp-3.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c5fe2728a89c82574bd3132d59237c3b5fb83e2e00a320e928d05d74d1ae895f", size = 1654702, upload-time = "2025-10-17T13:59:24.396Z" }, + { url = "https://files.pythonhosted.org/packages/30/52/1cf23cffeda1f079f20cd9c72174a76e8b0c6595def6803892e37ee35c8a/aiohttp-3.13.1-cp310-cp310-win32.whl", hash = "sha256:add14a5e68cbcfc526c89c1ed8ea963f5ff8b9b4b854985b07820c6fbfdb3c3c", size = 430898, upload-time = "2025-10-17T13:59:26.227Z" }, + { url = "https://files.pythonhosted.org/packages/0e/13/214a01f2936f4645b1fbd5cba9001331ca5af5c04bbdbe747eed330a8516/aiohttp-3.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:a4cc9d9cfdf75a69ae921c407e02d0c1799ab333b0bc6f7928c175f47c080d6a", size = 453684, upload-time = "2025-10-17T13:59:28.129Z" }, + { url = "https://files.pythonhosted.org/packages/be/2c/739d03730ffce57d2093e2e611e1541ac9a4b3bb88288c33275058b9ffc2/aiohttp-3.13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eefa0a891e85dca56e2d00760945a6325bd76341ec386d3ad4ff72eb97b7e64", size = 742004, upload-time = "2025-10-17T13:59:29.73Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f8/7f5b7f7184d7c80e421dbaecbd13e0b2a0bb8663fd0406864f9a167a438c/aiohttp-3.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c20eb646371a5a57a97de67e52aac6c47badb1564e719b3601bbb557a2e8fd0", size = 495601, upload-time = "2025-10-17T13:59:31.312Z" }, + { url = "https://files.pythonhosted.org/packages/3e/af/fb78d028b9642dd33ff127d9a6a151586f33daff631b05250fecd0ab23f8/aiohttp-3.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfc28038cd86fb1deed5cc75c8fda45c6b0f5c51dfd76f8c63d3d22dc1ab3d1b", size = 491790, upload-time = "2025-10-17T13:59:33.304Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ae/e40e422ee995e4f91f7f087b86304e3dd622d3a5b9ca902a1e94ebf9a117/aiohttp-3.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b22eeffca2e522451990c31a36fe0e71079e6112159f39a4391f1c1e259a795", size = 1746350, upload-time = "2025-10-17T13:59:35.158Z" }, + { url = "https://files.pythonhosted.org/packages/28/a5/fe6022bb869bf2d2633b155ed8348d76358c22d5ff9692a15016b2d1019f/aiohttp-3.13.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:65782b2977c05ebd78787e3c834abe499313bf69d6b8be4ff9c340901ee7541f", size = 1703046, upload-time = "2025-10-17T13:59:37.077Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a5/c4ef3617d7cdc49f2d5af077f19794946f0f2d94b93c631ace79047361a2/aiohttp-3.13.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dacba54f9be3702eb866b0b9966754b475e1e39996e29e442c3cd7f1117b43a9", size = 1806161, upload-time = "2025-10-17T13:59:38.837Z" }, + { url = "https://files.pythonhosted.org/packages/ad/45/b87d2430aee7e7d00b24e3dff2c5bd69f21017f6edb19cfd91e514664fc8/aiohttp-3.13.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aa878da718e8235302c365e376b768035add36b55177706d784a122cb822a6a4", size = 1894546, upload-time = "2025-10-17T13:59:40.741Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a2/79eb466786a7f11a0292c353a8a9b95e88268c48c389239d7531d66dbb48/aiohttp-3.13.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e4b4e607fbd4964d65945a7b9d1e7f98b0d5545736ea613f77d5a2a37ff1e46", size = 1745683, upload-time = "2025-10-17T13:59:42.59Z" }, + { url = "https://files.pythonhosted.org/packages/93/1a/153b0ad694f377e94eacc85338efe03ed4776a396c8bb47bd9227135792a/aiohttp-3.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c3db2d0e5477ad561bf7ba978c3ae5f8f78afda70daa05020179f759578754f", size = 1605418, upload-time = "2025-10-17T13:59:45.229Z" }, + { url = "https://files.pythonhosted.org/packages/3f/4e/18605b1bfeb4b00d3396d833647cdb213118e2a96862e5aebee62ad065b4/aiohttp-3.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9739d34506fdf59bf2c092560d502aa728b8cdb33f34ba15fb5e2852c35dd829", size = 1722379, upload-time = "2025-10-17T13:59:46.969Z" }, + { url = "https://files.pythonhosted.org/packages/72/13/0a38ad385d547fb283e0e1fe1ff1dff8899bd4ed0aaceeb13ec14abbf136/aiohttp-3.13.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:b902e30a268a85d50197b4997edc6e78842c14c0703450f632c2d82f17577845", size = 1716693, upload-time = "2025-10-17T13:59:49.217Z" }, + { url = "https://files.pythonhosted.org/packages/55/65/7029d7573ab9009adde380052c6130d02c8db52195fda112db35e914fe7b/aiohttp-3.13.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbfc04c8de7def6504cce0a97f9885a5c805fd2395a0634bc10f9d6ecb42524", size = 1784174, upload-time = "2025-10-17T13:59:51.439Z" }, + { url = "https://files.pythonhosted.org/packages/2d/36/fd46e39cb85418e45b0e4a8bfc39651ee0b8f08ea006adf217a221cdb269/aiohttp-3.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:6941853405a38a5eeb7d9776db77698df373ff7fa8c765cb81ea14a344fccbeb", size = 1593716, upload-time = "2025-10-17T13:59:53.367Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/188e0cb1be37b4408373171070fda17c3bf9c67c0d3d4fd5ee5b1fa108e1/aiohttp-3.13.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7764adcd2dc8bd21c8228a53dda2005428498dc4d165f41b6086f0ac1c65b1c9", size = 1799254, upload-time = "2025-10-17T13:59:55.352Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/fdf768764eb427b0cc9ebb2cebddf990f94d98b430679f8383c35aa114be/aiohttp-3.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c09e08d38586fa59e5a2f9626505a0326fadb8e9c45550f029feeb92097a0afc", size = 1738122, upload-time = "2025-10-17T13:59:57.263Z" }, + { url = "https://files.pythonhosted.org/packages/94/84/fce7a4d575943394d7c0e632273838eb6f39de8edf25386017bf5f0de23b/aiohttp-3.13.1-cp311-cp311-win32.whl", hash = "sha256:ce1371675e74f6cf271d0b5530defb44cce713fd0ab733713562b3a2b870815c", size = 430491, upload-time = "2025-10-17T13:59:59.466Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d2/d21b8ab6315a5d588c550ab285b4f02ae363edf012920e597904c5a56608/aiohttp-3.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:77a2f5cc28cf4704cc157be135c6a6cfb38c9dea478004f1c0fd7449cf445c28", size = 454808, upload-time = "2025-10-17T14:00:01.247Z" }, + { url = "https://files.pythonhosted.org/packages/1a/72/d463a10bf29871f6e3f63bcf3c91362dc4d72ed5917a8271f96672c415ad/aiohttp-3.13.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0760bd9a28efe188d77b7c3fe666e6ef74320d0f5b105f2e931c7a7e884c8230", size = 736218, upload-time = "2025-10-17T14:00:03.51Z" }, + { url = "https://files.pythonhosted.org/packages/26/13/f7bccedbe52ea5a6eef1e4ebb686a8d7765319dfd0a5939f4238cb6e79e6/aiohttp-3.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7129a424b441c3fe018a414401bf1b9e1d49492445f5676a3aecf4f74f67fcdb", size = 491251, upload-time = "2025-10-17T14:00:05.756Z" }, + { url = "https://files.pythonhosted.org/packages/0c/7c/7ea51b5aed6cc69c873f62548da8345032aa3416336f2d26869d4d37b4a2/aiohttp-3.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e1cb04ae64a594f6ddf5cbb024aba6b4773895ab6ecbc579d60414f8115e9e26", size = 490394, upload-time = "2025-10-17T14:00:07.504Z" }, + { url = "https://files.pythonhosted.org/packages/31/05/1172cc4af4557f6522efdee6eb2b9f900e1e320a97e25dffd3c5a6af651b/aiohttp-3.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:782d656a641e755decd6bd98d61d2a8ea062fd45fd3ff8d4173605dd0d2b56a1", size = 1737455, upload-time = "2025-10-17T14:00:09.403Z" }, + { url = "https://files.pythonhosted.org/packages/24/3d/ce6e4eca42f797d6b1cd3053cf3b0a22032eef3e4d1e71b9e93c92a3f201/aiohttp-3.13.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f92ad8169767429a6d2237331726c03ccc5f245222f9373aa045510976af2b35", size = 1699176, upload-time = "2025-10-17T14:00:11.314Z" }, + { url = "https://files.pythonhosted.org/packages/25/04/7127ba55653e04da51477372566b16ae786ef854e06222a1c96b4ba6c8ef/aiohttp-3.13.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0e778f634ca50ec005eefa2253856921c429581422d887be050f2c1c92e5ce12", size = 1767216, upload-time = "2025-10-17T14:00:13.668Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/43bca1e75847e600f40df829a6b2f0f4e1d4c70fb6c4818fdc09a462afd5/aiohttp-3.13.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9bc36b41cf4aab5d3b34d22934a696ab83516603d1bc1f3e4ff9930fe7d245e5", size = 1865870, upload-time = "2025-10-17T14:00:15.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/69/b204e5d43384197a614c88c1717c324319f5b4e7d0a1b5118da583028d40/aiohttp-3.13.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3fd4570ea696aee27204dd524f287127ed0966d14d309dc8cc440f474e3e7dbd", size = 1751021, upload-time = "2025-10-17T14:00:18.297Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/845dc6b6fdf378791d720364bf5150f80d22c990f7e3a42331d93b337cc7/aiohttp-3.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7bda795f08b8a620836ebfb0926f7973972a4bf8c74fdf9145e489f88c416811", size = 1561448, upload-time = "2025-10-17T14:00:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/91/d2ab08cd77ed76a49e4106b1cfb60bce2768242dd0c4f9ec0cb01e2cbf94/aiohttp-3.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:055a51d90e351aae53dcf324d0eafb2abe5b576d3ea1ec03827d920cf81a1c15", size = 1698196, upload-time = "2025-10-17T14:00:22.131Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d1/082f0620dc428ecb8f21c08a191a4694915cd50f14791c74a24d9161cc50/aiohttp-3.13.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d4131df864cbcc09bb16d3612a682af0db52f10736e71312574d90f16406a867", size = 1719252, upload-time = "2025-10-17T14:00:24.453Z" }, + { url = "https://files.pythonhosted.org/packages/fc/78/2af2f44491be7b08e43945b72d2b4fd76f0a14ba850ba9e41d28a7ce716a/aiohttp-3.13.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:163d3226e043f79bf47c87f8dfc89c496cc7bc9128cb7055ce026e435d551720", size = 1736529, upload-time = "2025-10-17T14:00:26.567Z" }, + { url = "https://files.pythonhosted.org/packages/b0/34/3e919ecdc93edaea8d140138049a0d9126141072e519535e2efa38eb7a02/aiohttp-3.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a2370986a3b75c1a5f3d6f6d763fc6be4b430226577b0ed16a7c13a75bf43d8f", size = 1553723, upload-time = "2025-10-17T14:00:28.592Z" }, + { url = "https://files.pythonhosted.org/packages/21/4b/d8003aeda2f67f359b37e70a5a4b53fee336d8e89511ac307ff62aeefcdb/aiohttp-3.13.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d7c14de0c7c9f1e6e785ce6cbe0ed817282c2af0012e674f45b4e58c6d4ea030", size = 1763394, upload-time = "2025-10-17T14:00:31.051Z" }, + { url = "https://files.pythonhosted.org/packages/4c/7b/1dbe6a39e33af9baaafc3fc016a280663684af47ba9f0e5d44249c1f72ec/aiohttp-3.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb611489cf0db10b99beeb7280bd39e0ef72bc3eb6d8c0f0a16d8a56075d1eb7", size = 1718104, upload-time = "2025-10-17T14:00:33.407Z" }, + { url = "https://files.pythonhosted.org/packages/5c/88/bd1b38687257cce67681b9b0fa0b16437be03383fa1be4d1a45b168bef25/aiohttp-3.13.1-cp312-cp312-win32.whl", hash = "sha256:f90fe0ee75590f7428f7c8b5479389d985d83c949ea10f662ab928a5ed5cf5e6", size = 425303, upload-time = "2025-10-17T14:00:35.829Z" }, + { url = "https://files.pythonhosted.org/packages/0e/e3/4481f50dd6f27e9e58c19a60cff44029641640237e35d32b04aaee8cf95f/aiohttp-3.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:3461919a9dca272c183055f2aab8e6af0adc810a1b386cce28da11eb00c859d9", size = 452071, upload-time = "2025-10-17T14:00:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/16/6d/d267b132342e1080f4c1bb7e1b4e96b168b3cbce931ec45780bff693ff95/aiohttp-3.13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:55785a7f8f13df0c9ca30b5243d9909bd59f48b274262a8fe78cee0828306e5d", size = 730727, upload-time = "2025-10-17T14:00:39.681Z" }, + { url = "https://files.pythonhosted.org/packages/92/c8/1cf495bac85cf71b80fad5f6d7693e84894f11b9fe876b64b0a1e7cbf32f/aiohttp-3.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bef5b83296cebb8167707b4f8d06c1805db0af632f7a72d7c5288a84667e7c3", size = 488678, upload-time = "2025-10-17T14:00:41.541Z" }, + { url = "https://files.pythonhosted.org/packages/a8/19/23c6b81cca587ec96943d977a58d11d05a82837022e65cd5502d665a7d11/aiohttp-3.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27af0619c33f9ca52f06069ec05de1a357033449ab101836f431768ecfa63ff5", size = 487637, upload-time = "2025-10-17T14:00:43.527Z" }, + { url = "https://files.pythonhosted.org/packages/48/58/8f9464afb88b3eed145ad7c665293739b3a6f91589694a2bb7e5778cbc72/aiohttp-3.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a47fe43229a8efd3764ef7728a5c1158f31cdf2a12151fe99fde81c9ac87019c", size = 1718975, upload-time = "2025-10-17T14:00:45.496Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8b/c3da064ca392b2702f53949fd7c403afa38d9ee10bf52c6ad59a42537103/aiohttp-3.13.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6e68e126de5b46e8b2bee73cab086b5d791e7dc192056916077aa1e2e2b04437", size = 1686905, upload-time = "2025-10-17T14:00:47.707Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a4/9c8a3843ecf526daee6010af1a66eb62579be1531d2d5af48ea6f405ad3c/aiohttp-3.13.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e65ef49dd22514329c55970d39079618a8abf856bae7147913bb774a3ab3c02f", size = 1754907, upload-time = "2025-10-17T14:00:49.702Z" }, + { url = "https://files.pythonhosted.org/packages/a4/80/1f470ed93e06436e3fc2659a9fc329c192fa893fb7ed4e884d399dbfb2a8/aiohttp-3.13.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e425a7e0511648b3376839dcc9190098671a47f21a36e815b97762eb7d556b0", size = 1857129, upload-time = "2025-10-17T14:00:51.822Z" }, + { url = "https://files.pythonhosted.org/packages/cc/e6/33d305e6cce0a8daeb79c7d8d6547d6e5f27f4e35fa4883fc9c9eb638596/aiohttp-3.13.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:010dc9b7110f055006acd3648d5d5955bb6473b37c3663ec42a1b4cba7413e6b", size = 1738189, upload-time = "2025-10-17T14:00:53.976Z" }, + { url = "https://files.pythonhosted.org/packages/ac/42/8df03367e5a64327fe0c39291080697795430c438fc1139c7cc1831aa1df/aiohttp-3.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b5c722d0ca5f57d61066b5dfa96cdb87111e2519156b35c1f8dd17c703bee7a", size = 1553608, upload-time = "2025-10-17T14:00:56.144Z" }, + { url = "https://files.pythonhosted.org/packages/96/17/6d5c73cd862f1cf29fddcbb54aac147037ff70a043a2829d03a379e95742/aiohttp-3.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:93029f0e9b77b714904a281b5aa578cdc8aa8ba018d78c04e51e1c3d8471b8ec", size = 1681809, upload-time = "2025-10-17T14:00:58.603Z" }, + { url = "https://files.pythonhosted.org/packages/be/31/8926c8ab18533f6076ce28d2c329a203b58c6861681906e2d73b9c397588/aiohttp-3.13.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d1824c7d08d8ddfc8cb10c847f696942e5aadbd16fd974dfde8bd2c3c08a9fa1", size = 1711161, upload-time = "2025-10-17T14:01:01.744Z" }, + { url = "https://files.pythonhosted.org/packages/f2/36/2f83e1ca730b1e0a8cf1c8ab9559834c5eec9f5da86e77ac71f0d16b521d/aiohttp-3.13.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8f47d0ff5b3eb9c1278a2f56ea48fda667da8ebf28bd2cb378b7c453936ce003", size = 1731999, upload-time = "2025-10-17T14:01:04.626Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ec/1f818cc368dfd4d5ab4e9efc8f2f6f283bfc31e1c06d3e848bcc862d4591/aiohttp-3.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8a396b1da9b51ded79806ac3b57a598f84e0769eaa1ba300655d8b5e17b70c7b", size = 1548684, upload-time = "2025-10-17T14:01:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ad/33d36efd16e4fefee91b09a22a3a0e1b830f65471c3567ac5a8041fac812/aiohttp-3.13.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d9c52a65f54796e066b5d674e33b53178014752d28bca555c479c2c25ffcec5b", size = 1756676, upload-time = "2025-10-17T14:01:09.517Z" }, + { url = "https://files.pythonhosted.org/packages/3c/c4/4a526d84e77d464437713ca909364988ed2e0cd0cdad2c06cb065ece9e08/aiohttp-3.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a89da72d18d6c95a653470b78d8ee5aa3c4b37212004c103403d0776cbea6ff0", size = 1715577, upload-time = "2025-10-17T14:01:11.958Z" }, + { url = "https://files.pythonhosted.org/packages/a2/21/e39638b7d9c7f1362c4113a91870f89287e60a7ea2d037e258b81e8b37d5/aiohttp-3.13.1-cp313-cp313-win32.whl", hash = "sha256:02e0258b7585ddf5d01c79c716ddd674386bfbf3041fbbfe7bdf9c7c32eb4a9b", size = 424468, upload-time = "2025-10-17T14:01:14.344Z" }, + { url = "https://files.pythonhosted.org/packages/cc/00/f3a92c592a845ebb2f47d102a67f35f0925cb854c5e7386f1a3a1fdff2ab/aiohttp-3.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:ef56ffe60e8d97baac123272bde1ab889ee07d3419606fae823c80c2b86c403e", size = 450806, upload-time = "2025-10-17T14:01:16.437Z" }, ] [[package]] @@ -221,11 +221,11 @@ wheels = [ [[package]] name = "argcomplete" -version = "3.6.2" +version = "3.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/16/0f/861e168fc813c56a78b35f3c30d91c6757d1fd185af1110f1aec784b35d0/argcomplete-3.6.2.tar.gz", hash = "sha256:d0519b1bc867f5f4f4713c41ad0aba73a4a5f007449716b16f385f2166dc6adf", size = 73403, upload-time = "2025-04-03T04:57:03.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/da/e42d7a9d8dd33fa775f467e4028a47936da2f01e4b0e561f9ba0d74cb0ca/argcomplete-3.6.2-py3-none-any.whl", hash = "sha256:65b3133a29ad53fb42c48cf5114752c7ab66c1c38544fdf6460f450c09b42591", size = 43708, upload-time = "2025-04-03T04:57:01.591Z" }, + { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, ] [[package]] @@ -1464,37 +1464,37 @@ wheels = [ [[package]] name = "dulwich" -version = "0.24.5" +version = "0.24.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.12'" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/bd/660f8e45a4e9dab4fa6a92fcfb392dd84d99909548c17e85e05cb172f14d/dulwich-0.24.5.tar.gz", hash = "sha256:502079fe552a85b187542ae63587745279ecb01bbff0afa98f12e53b695e1f38", size = 913018, upload-time = "2025-10-16T09:49:31.412Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e9/51579ecdea44c621227a3579034b183c9dcdab1cddc9f66e7d7847d81f2c/dulwich-0.24.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7c57066f877807384ea6dd1ae8b9345e07cc38aa72e82d4d08738516f7a6d942", size = 1162242, upload-time = "2025-10-16T09:48:36.542Z" }, - { url = "https://files.pythonhosted.org/packages/76/5a/01f1afd7d09d4e68f12479d3526e577f3cddfbbd3b94220becc2525e08f7/dulwich-0.24.5-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6aa3245188c07256265bf9790690afc5042a2b4d0251e05b2a1a915bca4962de", size = 1241694, upload-time = "2025-10-16T09:48:38.929Z" }, - { url = "https://files.pythonhosted.org/packages/fd/de/302ddfa8b8223e3a74aebcc783714e123de6f2df818ef57183d813b04c60/dulwich-0.24.5-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:dd95f8032475fa70281ff3bb9f3742e09c42c00b56dd0d23939e59ce986d7449", size = 1267234, upload-time = "2025-10-16T09:48:40.833Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5e/71cc12f397bdb4ec6a17acbee93b291209f1e114a48a0d3a811ff30098e7/dulwich-0.24.5-cp310-cp310-win32.whl", hash = "sha256:b5c7022f7efd3acd546e3d371cc3550825dbf8f8d9d24bb65eaf9b02e24bc95a", size = 838782, upload-time = "2025-10-16T09:48:42.237Z" }, - { url = "https://files.pythonhosted.org/packages/3e/36/e2e907dc840979ce182bc05c7368f1d169d61089da54c46fe90977b5d056/dulwich-0.24.5-cp310-cp310-win_amd64.whl", hash = "sha256:ca76cc9b632628ba62f332e477a0e314e34d669330ef447cfb8af211367095d4", size = 855283, upload-time = "2025-10-16T09:48:44.08Z" }, - { url = "https://files.pythonhosted.org/packages/3a/74/248b5ae72f4433663af31abd6a0b4abb21da37dd553bb23fbe79c4fb5859/dulwich-0.24.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3272df496affe0df398568abb25a582ab826cb15b2cebf7c09c2fd85feadb239", size = 1161244, upload-time = "2025-10-16T09:48:45.962Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8d/9e7dc9509dc626365cc7f3d3c1de46af8a9eb6e53656d1142d7887cc6dba/dulwich-0.24.5-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2f3c57c98bc199812948797df26d9e697e2233e8f9cd44323416dea2bb0f9884", size = 1240824, upload-time = "2025-10-16T09:48:48.001Z" }, - { url = "https://files.pythonhosted.org/packages/e0/e9/78cdea4b35311343dbea4cb12c9659d7b31cff3370dffacba8b60cf48b74/dulwich-0.24.5-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a3d473b0049486d3e50cd4985150f525292fbacb96c979edb1c0835eaf098b5f", size = 1266414, upload-time = "2025-10-16T09:48:49.593Z" }, - { url = "https://files.pythonhosted.org/packages/27/ca/f444dc40dff2dd53458e236b860c9b89e686359388b19d2ac4d2a396412b/dulwich-0.24.5-cp311-cp311-win32.whl", hash = "sha256:11d10965d7ced5d9a22027bce2d8fa59d3bacb17f97b2dec5c24ddf35d98b1de", size = 837618, upload-time = "2025-10-16T09:48:51.264Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b8/4fdd93bbc9f57b481a432630edddf7a486192f0d449c68fdb2b435d481cc/dulwich-0.24.5-cp311-cp311-win_amd64.whl", hash = "sha256:332894c4bd5f83cc26e8a67d5a82534db843e21f2e6da301814c61fe270529bd", size = 855029, upload-time = "2025-10-16T09:48:52.696Z" }, - { url = "https://files.pythonhosted.org/packages/3c/58/661bf5095c68bf9d9fe670b5daec85296622f6ec1aa87883cc248161e191/dulwich-0.24.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bbd69b0965a85b53764675c585e595946ae53a5e20e954616f0b482d2fe3eca6", size = 1154108, upload-time = "2025-10-16T09:48:54.11Z" }, - { url = "https://files.pythonhosted.org/packages/7c/37/2faeaae8271d4458d74548478c7144cdf0e98f525cc6782cbb6e0c2e9b70/dulwich-0.24.5-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86326efba6ba753db334aa1f2cc9dec3a397663d002b55122510f87b3855ae82", size = 1241200, upload-time = "2025-10-16T09:48:55.732Z" }, - { url = "https://files.pythonhosted.org/packages/c6/17/3c822956c7fe8e7df1c192150b950336eeaff39d569235b2bc039bb6e4a6/dulwich-0.24.5-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:1acd3d086b129c5fba431bc93799cb51a5409734275d699558d5776b4a94e848", size = 1266366, upload-time = "2025-10-16T09:48:57.841Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3c/3f74f293ae3417a30c7093720c06b1cea62fc1c434a892df15cd0d7542a0/dulwich-0.24.5-cp312-cp312-win32.whl", hash = "sha256:16852f91f35624d91c57f676cf62fffbf5a5769f3e3acad58132283252d6c588", size = 837305, upload-time = "2025-10-16T09:48:59.674Z" }, - { url = "https://files.pythonhosted.org/packages/a8/78/3c7ef6bf55e1b8126936d21aacaa98cd82aae08c0f990d6a4448d027f2ca/dulwich-0.24.5-cp312-cp312-win_amd64.whl", hash = "sha256:c653f829389b80a924f84670b064ede7ea2fbf2770d9ff7ba3b7c5d62b8b564d", size = 855416, upload-time = "2025-10-16T09:49:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/90/90/3d1032e8ad3dbdaad0711dccb14a03cd9066cd291e4a19fce6b0b007e828/dulwich-0.24.5-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:043b76daa2eb8ce5dea5a07bb2ed66da5b09c1c0ba5b1731197ce11a48b26165", size = 1263636, upload-time = "2025-10-16T09:49:03.113Z" }, - { url = "https://files.pythonhosted.org/packages/0e/3a/0ddd5ec6ac5494960043f2d72e8555639b8abbe499400abe1b2d3248360e/dulwich-0.24.5-cp313-cp313-android_21_x86_64.whl", hash = "sha256:0e4531b04aa84d510977979dfd67390d6f8ff86ff5e269b6afa6833e49ca8fbf", size = 1263629, upload-time = "2025-10-16T09:49:04.68Z" }, - { url = "https://files.pythonhosted.org/packages/be/c0/bb7fcb6227ce940849b07325c0ada39da378d35d9b37cf45f6d6e8d5e6c5/dulwich-0.24.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:242805e81a79e97a90c602f1d19f106e99631af205b0f55bd786c768c5e65505", size = 1154077, upload-time = "2025-10-16T09:49:06.079Z" }, - { url = "https://files.pythonhosted.org/packages/32/3c/a6c5c2db71256b123e17d26e277e59b0ed5078d4d19d9ffcea982a374a0d/dulwich-0.24.5-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:d5210f560c90f3239e0cc99c85a801b0da2e5934cc169af2815d52e314a00afb", size = 1241510, upload-time = "2025-10-16T09:49:07.719Z" }, - { url = "https://files.pythonhosted.org/packages/17/6a/eac505bb799c2e4936ceba6c97537a775ca28bd6e6cdb149e84e343796db/dulwich-0.24.5-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:eca95ff02d835c6536018c902818422bf2c6ef48dba71b3605d8f484720f6be8", size = 1266008, upload-time = "2025-10-16T09:49:09.73Z" }, - { url = "https://files.pythonhosted.org/packages/d1/da/e6f2b4ff31edf5e7af4abc4fb9e40886c1be1933a3c016c892872593b44b/dulwich-0.24.5-cp313-cp313-win32.whl", hash = "sha256:215be6377a0516654ea4ddaacd60787d291c7f1e519086b668509afc0677613d", size = 837432, upload-time = "2025-10-16T09:49:11.331Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c7/bf5e28897dbadc61c17a287b036182c236cde6b0d3d8796dad9eec63c86c/dulwich-0.24.5-cp313-cp313-win_amd64.whl", hash = "sha256:9e83e0ed0df2b8b5af77c30a401e3475031e06f0ca27e3be4930cfedab99f59a", size = 855442, upload-time = "2025-10-16T09:49:13.21Z" }, - { url = "https://files.pythonhosted.org/packages/10/9d/62bde011277a738fbb8b6a0833d44097af65281242684c85c5f1d1e1928e/dulwich-0.24.5-py3-none-any.whl", hash = "sha256:788ec804cb2981c72f9f8372b4015f44bc04fd5c1bcafd50c86c8cfa90cb6f1c", size = 515491, upload-time = "2025-10-16T09:49:29.684Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/18/e7/3d4861edda4d68d9bd0380ce8190601db6ac6d34ca423f2d568e75ad002a/dulwich-0.24.6.tar.gz", hash = "sha256:e8aebdb52cee481ddc038a2b88376bc28767127fdf3e5ea08b52ae1f60e1e15b", size = 946625, upload-time = "2025-10-19T11:48:22.079Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fd/93d608c48708e1cd50334e556f6d8bba88be49974ccb5152257c33437f1e/dulwich-0.24.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ff0c88dd7d9d87c208d22a0952d3b2f6234851cd89d58aa0a8395888b69846b7", size = 1181802, upload-time = "2025-10-19T11:47:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/60/0c/6cda4a117fa698c63f6e36a79dc83b1987c1b6708e86769e55b92667c40e/dulwich-0.24.6-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:95889897b60965d40800c6089b3a889d51b3702b421ecfdd51ad62030a2cec10", size = 1261563, upload-time = "2025-10-19T11:47:31.2Z" }, + { url = "https://files.pythonhosted.org/packages/e4/11/5a9c73904edcf9fa5ac2034e226faa7287cf503926f1074a4fbb624f4fcd/dulwich-0.24.6-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7b2e07adbeb1fcc148ae2ad42b8adcdeda38b27410c0b130e69284ff2238450d", size = 1286854, upload-time = "2025-10-19T11:47:33.225Z" }, + { url = "https://files.pythonhosted.org/packages/2d/3e/090e403d4ec040e39e63f26b0ffa53075482bbd9de8257a1b80e1b93db8c/dulwich-0.24.6-cp310-cp310-win32.whl", hash = "sha256:7e3eaff784434ec02fddceb13536a71183c2a057bc23c903e1fabe1da990952c", size = 858728, upload-time = "2025-10-19T11:47:35.192Z" }, + { url = "https://files.pythonhosted.org/packages/a8/81/bb9de7eaff0c4a902693c6fca6923e9d8ca92c24246ef3ad063d825032e6/dulwich-0.24.6-cp310-cp310-win_amd64.whl", hash = "sha256:08ae1b04d7061392c531998d0a8e7204a44d653df30e9a1e688e72ef3f665918", size = 875186, upload-time = "2025-10-19T11:47:37.015Z" }, + { url = "https://files.pythonhosted.org/packages/5d/41/7305e9db3f87078f4a623e5bf102cbcdd35018b6822db29409e5cf2eae18/dulwich-0.24.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aac4a0911c6329662c52fb57ac4b130d03e814f09d5857e7650ff1c1402c0d39", size = 1180549, upload-time = "2025-10-19T11:47:38.961Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a0/6ed16d3e5515694100d15e0d576ff4b7687b5198425bc34bc56a80fc4b20/dulwich-0.24.6-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:0bf0a4a6efb59742e20988021f508892672c18dc15753458cb5a82bf1cf6a0f9", size = 1260748, upload-time = "2025-10-19T11:47:40.761Z" }, + { url = "https://files.pythonhosted.org/packages/61/67/1d24e202def75ebf36b90845a0de43e04cbfab293074cfa8430a226e2543/dulwich-0.24.6-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:c1bf5f80924688e68f5d4739f0786870e08926bfb3fd3406fcc06ce5755c2cf2", size = 1286083, upload-time = "2025-10-19T11:47:42.391Z" }, + { url = "https://files.pythonhosted.org/packages/20/b0/965a8e0d0efd09cf34c9dc0daced95b86973b4eb0a12403dfab7598244b1/dulwich-0.24.6-cp311-cp311-win32.whl", hash = "sha256:e162ecfd84370f0eaff1220ac8c728ccdb88df72f4c366db9ddb775e5a8de079", size = 857555, upload-time = "2025-10-19T11:47:44.183Z" }, + { url = "https://files.pythonhosted.org/packages/b3/5a/930c5b02f666c255da59df9ddc3f1b28c9e33bafd328be96398d343a022c/dulwich-0.24.6-cp311-cp311-win_amd64.whl", hash = "sha256:159313deeaa61ff7da7938a83245207668d6c74fe486be7c8e01c661a6ca5468", size = 874978, upload-time = "2025-10-19T11:47:46.108Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/dc28908e2643fc3f6facbd13afa17a0608927b0ff6212a7210444784c041/dulwich-0.24.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f821b78595893442707cd4e7b3dafac616a92d8b9135d138021798084e6ccfc1", size = 1173552, upload-time = "2025-10-19T11:47:47.919Z" }, + { url = "https://files.pythonhosted.org/packages/0a/84/390c64c35978da2d2b08fc486051859da0bde807b95ec80e5cab2063d33c/dulwich-0.24.6-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:15bfb32b972d9a3068ff6973bdd01eb1f470379f62a49d53c41f50ce8cb78508", size = 1261066, upload-time = "2025-10-19T11:47:49.416Z" }, + { url = "https://files.pythonhosted.org/packages/28/22/ca23d786761fd502a52cf783c698eb7a6d65f7d9d27148e7a20458047c48/dulwich-0.24.6-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a3381a5caf11849230a70879628e00bfcfdb58bda585566aad585544f22e9d08", size = 1286212, upload-time = "2025-10-19T11:47:51.254Z" }, + { url = "https://files.pythonhosted.org/packages/c6/09/c8318628cabd4ddc6cea36e9488352e0070735d4590e0040e98f7b2c2811/dulwich-0.24.6-cp312-cp312-win32.whl", hash = "sha256:cf838356a1aff0efb281066e4d471b2a9e809eb1e1126b195a921287801c8d09", size = 857352, upload-time = "2025-10-19T11:47:53.005Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/6157a369294e753a34437eadd0dfd85270d5ae230b8eab821f21cc7e9073/dulwich-0.24.6-cp312-cp312-win_amd64.whl", hash = "sha256:d7461fc5646df3239f38d608e70ab13b6b051b5287ade6d0a694c93f852b7ece", size = 875132, upload-time = "2025-10-19T11:47:55.053Z" }, + { url = "https://files.pythonhosted.org/packages/6a/86/b1d3d1d323067bc5e3b246a3daa83332820f6a273f32cd97fe9b6489d06b/dulwich-0.24.6-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:80f1805ce44f8952cea6375809abf3093c59a704194a03b74d9815fd1567671a", size = 1283637, upload-time = "2025-10-19T11:47:56.508Z" }, + { url = "https://files.pythonhosted.org/packages/65/54/0696883432af4f6aae7b4e797913f319932be0454bbb167443c0b5bfa9af/dulwich-0.24.6-cp313-cp313-android_21_x86_64.whl", hash = "sha256:a0946e29d1c76f7fc5ce6de5a04247464204ef0dac371d899ef6613e73f459a2", size = 1283630, upload-time = "2025-10-19T11:47:58.14Z" }, + { url = "https://files.pythonhosted.org/packages/02/3b/77952178d700b43b1c457d6d3cd9ecceb392b1de78a3d7cb5cc005e0dedc/dulwich-0.24.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd8ee92dced3b5251f520880d42b0af2b99b5741b7855e59aabac09045655d72", size = 1173521, upload-time = "2025-10-19T11:47:59.539Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e4/d1d4e86ec52143c3afae5e3e2a2f6b623331726321045552e8540daff47a/dulwich-0.24.6-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:4e058228b125567be0cc2b3c081cc9c56122494443a90f4b40c893ddaf9bc458", size = 1261497, upload-time = "2025-10-19T11:48:01.014Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/b096e408a0326a22af450535c422c2674caf9810ba25454b6e9cb33a1250/dulwich-0.24.6-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:91f9ac0192bb9e08c282be12572cfee94bbb612a4e91a2dcdf85ed9d906064e1", size = 1286008, upload-time = "2025-10-19T11:48:02.493Z" }, + { url = "https://files.pythonhosted.org/packages/28/9b/ebc88fd66ce7585cc88007df260a9c908c708eda0a17efad5b55a378101d/dulwich-0.24.6-cp313-cp313-win32.whl", hash = "sha256:98020e3bea3ec21af2539e5c0471c479bbe7985a788ad99891986c71eb583b16", size = 857434, upload-time = "2025-10-19T11:48:04.713Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/8ff7c5949455bf92043b6ab4f1ee02ff01b4ab95ea557b2958e2a0169cc5/dulwich-0.24.6-cp313-cp313-win_amd64.whl", hash = "sha256:96b5bd0c23a2f7d80cdd5bf277bc8cc696cdce9b1a467457c85463bc44c07e0a", size = 875244, upload-time = "2025-10-19T11:48:06.103Z" }, + { url = "https://files.pythonhosted.org/packages/26/bf/860f7bcaef02db9e2d194402de345a71e1911f103d5b6d8ce4a0e681fd37/dulwich-0.24.6-py3-none-any.whl", hash = "sha256:d5bf23d61a9f366ebb00a764d8157fbfe2bf693317e60f32b696991adaefe3c6", size = 535369, upload-time = "2025-10-19T11:48:20.598Z" }, ] [[package]] @@ -1589,16 +1589,16 @@ standard = [ [[package]] name = "fastapi-cli" -version = "0.0.13" +version = "0.0.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rich-toolkit", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "typer", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "uvicorn", extra = ["standard"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/4e/3f61850012473b097fc5297d681bd85788e186fadb8555b67baf4c7707f4/fastapi_cli-0.0.13.tar.gz", hash = "sha256:312addf3f57ba7139457cf0d345c03e2170cc5a034057488259c33cd7e494529", size = 17780, upload-time = "2025-09-20T16:37:31.089Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/13/11e43d630be84e51ba5510a6da6a11eb93b44b72caa796137c5dddda937b/fastapi_cli-0.0.14.tar.gz", hash = "sha256:ddfb5de0a67f77a8b3271af1460489bd4d7f4add73d11fbfac613827b0275274", size = 17994, upload-time = "2025-10-20T16:33:21.054Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/36/7432750f3638324b055496d2c952000bea824259fca70df5577a6a3c172f/fastapi_cli-0.0.13-py3-none-any.whl", hash = "sha256:219b73ccfde7622559cef1d43197da928516acb4f21f2ec69128c4b90057baba", size = 11142, upload-time = "2025-09-20T16:37:29.695Z" }, + { url = "https://files.pythonhosted.org/packages/40/e8/bc8bbfd93dcc8e347ce98a3e654fb0d2e5f2739afb46b98f41a30c339269/fastapi_cli-0.0.14-py3-none-any.whl", hash = "sha256:e66b9ad499ee77a4e6007545cde6de1459b7f21df199d7f29aad2adaab168eca", size = 11151, upload-time = "2025-10-20T16:33:19.318Z" }, ] [package.optional-dependencies] @@ -2065,14 +2065,14 @@ wheels = [ [[package]] name = "googleapis-common-protos" -version = "1.70.0" +version = "1.71.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903, upload-time = "2025-04-14T10:17:02.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/43/b25abe02db2911397819003029bef768f68a974f2ece483e6084d1a5f754/googleapis_common_protos-1.71.0.tar.gz", hash = "sha256:1aec01e574e29da63c80ba9f7bbf1ccfaacf1da877f23609fe236ca7c72a2e2e", size = 146454, upload-time = "2025-10-20T14:58:08.732Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530, upload-time = "2025-04-14T10:17:01.271Z" }, + { url = "https://files.pythonhosted.org/packages/25/e8/eba9fece11d57a71e3e22ea672742c8f3cf23b35730c9e96db768b295216/googleapis_common_protos-1.71.0-py3-none-any.whl", hash = "sha256:59034a1d849dc4d18971997a72ac56246570afdd17f9369a0ff68218d50ab78c", size = 294576, upload-time = "2025-10-20T14:56:21.295Z" }, ] [[package]] @@ -2401,11 +2401,11 @@ wheels = [ [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +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/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { 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]] @@ -2437,7 +2437,7 @@ wheels = [ [[package]] name = "ipykernel" -version = "6.30.1" +version = "6.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appnope", marker = "sys_platform == 'darwin'" }, @@ -2455,9 +2455,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bb/76/11082e338e0daadc89c8ff866185de11daf67d181901038f9e139d109761/ipykernel-6.30.1.tar.gz", hash = "sha256:6abb270161896402e76b91394fcdce5d1be5d45f456671e5080572f8505be39b", size = 166260, upload-time = "2025-08-04T15:47:35.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/1d/d5ba6edbfe6fae4c3105bca3a9c889563cc752c7f2de45e333164c7f4846/ipykernel-6.31.0.tar.gz", hash = "sha256:2372ce8bc1ff4f34e58cafed3a0feb2194b91fc7cad0fc72e79e47b45ee9e8f6", size = 167493, upload-time = "2025-10-20T11:42:39.948Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/c7/b445faca8deb954fe536abebff4ece5b097b923de482b26e78448c89d1dd/ipykernel-6.30.1-py3-none-any.whl", hash = "sha256:aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4", size = 117484, upload-time = "2025-08-04T15:47:32.622Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d8/502954a4ec0efcf264f99b65b41c3c54e65a647d9f0d6f62cd02227d242c/ipykernel-6.31.0-py3-none-any.whl", hash = "sha256:abe5386f6ced727a70e0eb0cf1da801fa7c5fa6ff82147747d5a0406cd8c94af", size = 117003, upload-time = "2025-10-20T11:42:37.502Z" }, ] [[package]] @@ -2715,11 +2715,11 @@ wheels = [ [[package]] name = "json-repair" -version = "0.52.0" +version = "0.52.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/63/2c3c3c8cc1c28a0a20a9ab0eff5439c989ce3cc5956d8a4c7cf1eae0a06e/json_repair-0.52.0.tar.gz", hash = "sha256:0eee59cb3145b462b0734d4cf3246b797686caa669d52eee8dd30e09ea6d7876", size = 35384, upload-time = "2025-10-05T17:18:12.387Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/93/5220c447b9ce20ed14ab33bae9a29772be895a8949bb723eaa30cc42a4e1/json_repair-0.52.2.tar.gz", hash = "sha256:1c83e1811d7e57092ad531b333f083166bdf398b042c95f3cd62b30d74dc7ecd", size = 35584, upload-time = "2025-10-20T07:24:20.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/7f/3a4e456da9a0f9ac54d9842ed51e96960826a98456f0826a9b3e808713c4/json_repair-0.52.0-py3-none-any.whl", hash = "sha256:c783069906a456f62e2a553fbef32a420a4745ff943e2014411728edcc7bf60a", size = 26350, upload-time = "2025-10-05T17:18:10.859Z" }, + { url = "https://files.pythonhosted.org/packages/87/20/1935a6082988efea16432cecfdb757111122c32a07acaa595ccd78a55c47/json_repair-0.52.2-py3-none-any.whl", hash = "sha256:c7bb514d3f59d49364653717233eb4466bda0f4fdd511b4dc268aa877d406c81", size = 26512, upload-time = "2025-10-20T07:24:18.893Z" }, ] [[package]] @@ -3497,29 +3497,29 @@ wheels = [ [[package]] name = "mlx" -version = "0.29.2" +version = "0.29.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mlx-metal", marker = "sys_platform == 'darwin'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/f0/2c2f99a91ed9dfcc78d31d9e5d3bb2f5305a8d65953cbc41f34f8056c49a/mlx-0.29.2-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:b46c1a24b9b8f7145e4d84410552ddfa03f40f9afdbe8f819f6b4b52b4db5d30", size = 547369, upload-time = "2025-09-26T22:21:33.668Z" }, - { url = "https://files.pythonhosted.org/packages/3b/06/0edddf0a5facb58c17616cd33da6de2722636da8d8d3927272a5a88658e4/mlx-0.29.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:18c5b63c6e4b35f75f477f74d3942870e0bc9c9f1c6a071d8a058bbd46681bf4", size = 547367, upload-time = "2025-09-26T22:21:36.63Z" }, - { url = "https://files.pythonhosted.org/packages/e1/cd/8c089cf1678a752ed4175a7ad5f08823ceef75d797217e12a44a71d6c062/mlx-0.29.2-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:ed53b8383ad4ee4311400558e0a5ec61105fc4553950b5732c66e7081cd1a9e8", size = 547365, upload-time = "2025-09-26T22:21:32.585Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f0/f57349f37cf5dd53f95127e141fc59fc435e4b6bfabba5a84c65de4d3597/mlx-0.29.2-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:e74965369227230374b3e8e8c8d46e209e5221a9b76bbb0fa788617e2c68f73c", size = 547581, upload-time = "2025-09-26T22:21:39.24Z" }, - { url = "https://files.pythonhosted.org/packages/66/04/e016ca28dc9e0738a2541581420125cfe6bba24466a64420600bdd6fd52c/mlx-0.29.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0f79194eeac78e85b96439d3bbc17aae5aba045a2af083c000b4fbbc501f253e", size = 547581, upload-time = "2025-09-26T22:21:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b3/e2595e70ef8d4438dff694857745b0e108911e5b5fb83259dde6e5dc5bd1/mlx-0.29.2-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:33bbbb0fd24895d5ff080bb4d10e3e77017bba675d9a12466c8866eaf9b47854", size = 547578, upload-time = "2025-09-26T22:21:22.041Z" }, - { url = "https://files.pythonhosted.org/packages/f3/84/7250237039e91d8e44ca0cf3522f189164844c196f262509afd29ef54710/mlx-0.29.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:eec950bf7118ad0865d0fc4686bd85d99bf8463fc717d836a5132e1a08b4f129", size = 548336, upload-time = "2025-09-26T22:21:44.914Z" }, - { url = "https://files.pythonhosted.org/packages/13/47/428ac8d9b0cb5c136e5ce6c726cfdd55caa5b9497dafb6221acfee18f145/mlx-0.29.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bef7333268d6d02e50a9ac6b10f661b711cd02da4a5e2d7619cf198a7e530308", size = 548334, upload-time = "2025-09-26T22:21:21.41Z" }, - { url = "https://files.pythonhosted.org/packages/14/f0/7d5d3527ca3fdc664c900b4b822028691739e58c8e8f7975b33df4d3536e/mlx-0.29.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:f622fc6a84542a08ad2136e9251822d2c08106e5a1a0bd5d249a2d72bccd6577", size = 548330, upload-time = "2025-09-26T22:21:41.182Z" }, - { url = "https://files.pythonhosted.org/packages/a0/9a/91f6f5d031f109fa8c00ba9dd4f7a3fc42e1097a57c26783ce000069c264/mlx-0.29.2-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:05ea54173f4bde11b2c93e673d65d72523f5d850f5112d3874156a6fc74ca591", size = 548297, upload-time = "2025-09-26T22:21:41.991Z" }, - { url = "https://files.pythonhosted.org/packages/2b/2d/dae7ca0b7fa68c6c1f2b896dfe1b8060647f144d5c5da2d53388e38809b1/mlx-0.29.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:199dd029b5e55b6d94f1ce366d0137824e46e4333891424dd00413c739f50ae9", size = 548305, upload-time = "2025-09-26T22:21:41.083Z" }, - { url = "https://files.pythonhosted.org/packages/b1/56/f02f5c9e1fc11c020982501a763fa92b497ea50671a587760543987ba8c8/mlx-0.29.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:b6dd4e5f227414882b1676d99250d99389228d1bdc14e4e4e88c95d4903810b7", size = 548302, upload-time = "2025-09-26T22:21:30.546Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8a/743ff24a07f8cfd6fb14b3fe05f122f1d8e04e8a912b2f6d0e14369c8caf/mlx-0.29.3-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:340d46443fe0b1e5d84c1e36aa633310de70365ce79aefcaa6f618e62bd4b045", size = 548930, upload-time = "2025-10-17T19:16:49.872Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2a/af1b8391b6f543e59ca595f63aaddc33e320d3cc57a4c86ded6932d9dc3c/mlx-0.29.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8449c0e7c221e38368a734a471e0c4b1a7fea072947c75e893a1ee214f208d34", size = 548928, upload-time = "2025-10-17T19:16:58.275Z" }, + { url = "https://files.pythonhosted.org/packages/49/85/0c58bdc5733ba92f78f067fc25e131e34db46562719d7909cebfad9313c5/mlx-0.29.3-cp310-cp310-macosx_15_0_arm64.whl", hash = "sha256:1bba5203ed3f785167f5b8891c2e91ede23401586b0a723bfaf815a3ed450e3d", size = 548931, upload-time = "2025-10-17T19:16:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/3e91a37fa55dc0e9114620729ab61b27f45ed59053fc77846cad2df54f21/mlx-0.29.3-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:0ffdf1f171c903adeaa688210ba39063059b102f3dcc52a64c2200d95d237f15", size = 549089, upload-time = "2025-10-17T19:17:00.446Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/ce008d14fbd2e22b694f568ab4014e14c979a2262c5f8c10e06d4806709f/mlx-0.29.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e7d1d815be0d4a41e598bdb2992822dafd9ab0d59d4b88af760ee0b6584506b7", size = 549091, upload-time = "2025-10-17T19:16:54.428Z" }, + { url = "https://files.pythonhosted.org/packages/72/1c/45642746d36e91e26f3401e9b7931f92d8cc1eb6015cc40218628f320747/mlx-0.29.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:d33bff69887fadfd85ce67b8e11318c2319984f3ad4157f871aa9d3beb9de972", size = 549092, upload-time = "2025-10-17T19:17:10.963Z" }, + { url = "https://files.pythonhosted.org/packages/07/f5/14e12e219a2715296150d35f930dc3a6ff319cd60126408e563f03100113/mlx-0.29.3-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:86c62791ce930028d75c41b88b4e3ceb58f5f2e263ff9bfacda998b0c03d9544", size = 549516, upload-time = "2025-10-17T19:18:13.831Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e2/5177c80e8c33a8be89fa45fa0a839d5b6a5578687d0ec973bf03638a4e73/mlx-0.29.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cddf6bcdc561094af6b3f0706f8768ecc5216a97eb6973e838c3ac2e2fca2cc8", size = 549509, upload-time = "2025-10-17T19:17:21.517Z" }, + { url = "https://files.pythonhosted.org/packages/11/89/aa424217a7a0291b84f8969d504ac63f5af0ef60f248fe5562c3d6e44048/mlx-0.29.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:b2e1a249437d017a7425358420d28e641b7bc9c2650f3e013c1b1f4f239d8533", size = 549511, upload-time = "2025-10-17T19:16:54.227Z" }, + { url = "https://files.pythonhosted.org/packages/fe/a2/078152b45aa8a23949a1b09601d0044f8bb4ab85e909e4475a440c21aaea/mlx-0.29.3-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:d59eccf6a1e1e131becc5a3910504507862da3a4e9b7bd9e73a625515d767844", size = 549585, upload-time = "2025-10-17T19:17:01.872Z" }, + { url = "https://files.pythonhosted.org/packages/ae/bb/869eaac4efaae033c13db5fddd6a8907b5d667d135a35a2e482b1af402ee/mlx-0.29.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6642aa0a6dc2242c024fb8274d00631a7e7ffbdcef26148afd299b877c1e6a4a", size = 549586, upload-time = "2025-10-17T19:16:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/ad/76/196c248c2b2a471f795356564ad1d7dc40284160c8b66370ffadfd991fa1/mlx-0.29.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:ec0aef311fab10cb5f2c274afa6edf6c482636096a5f7886aba43676454aa462", size = 549586, upload-time = "2025-10-17T19:16:39.912Z" }, ] [[package]] name = "mlx-lm" -version = "0.28.2" +version = "0.28.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2", marker = "sys_platform == 'darwin'" }, @@ -3529,19 +3529,19 @@ dependencies = [ { name = "pyyaml", marker = "sys_platform == 'darwin'" }, { name = "transformers", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/d7/fdde445c7bd443a2ed23badda6064f1477c4051543922106f365e94082cd/mlx_lm-0.28.2.tar.gz", hash = "sha256:d28752635ed5c89ff2b41361916c928e6b16f765c07b2908044e1dcaf921ed9b", size = 209374, upload-time = "2025-10-02T14:23:57.497Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/f6/15e002d52c28d8c544ec3aaf9053677468333e6ef0e76ea68579fd77b76d/mlx_lm-0.28.3.tar.gz", hash = "sha256:75df2b925d343ebaf50b63008dede4fe98cd3b02b1b24b7da71ebeb198d674f0", size = 214455, upload-time = "2025-10-17T21:44:33.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/1c/89e0f60d45e364de8507065f73aeb8d2fd810d6cb95a9a512880b09399d5/mlx_lm-0.28.2-py3-none-any.whl", hash = "sha256:1501529e625d0d648216f7bb543b8b449d5fd17bd598f635536dbc1fbde6d1d6", size = 284600, upload-time = "2025-10-02T14:23:56.395Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a6/db3b44a5ac1a1174605628b0a477fbe4632d4fad1f94cf08647e27cc79ad/mlx_lm-0.28.3-py3-none-any.whl", hash = "sha256:ec103e2c9a06bd2cbafd41aafc975e40262176f7360d4f53ec342cebb9e0e6ea", size = 294506, upload-time = "2025-10-17T21:44:32.447Z" }, ] [[package]] name = "mlx-metal" -version = "0.29.2" +version = "0.29.3" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/a5/a045006546fed791f6e9a74ed4451dac871d3c35f9e54a3a25d820668a85/mlx_metal-0.29.2-py3-none-macosx_13_0_arm64.whl", hash = "sha256:cf8f83a521e620357185c57945142718d526b9312ee112e5a89eb5600480f4d6", size = 35056194, upload-time = "2025-09-26T22:23:47.201Z" }, - { url = "https://files.pythonhosted.org/packages/4c/8c/4bdd3a7d04ed477b32aec30d30236dfca9f9ac27706cb309511278ddd281/mlx_metal-0.29.2-py3-none-macosx_14_0_arm64.whl", hash = "sha256:fa944001970813b296e8aff5616f2fa9daeda6bc1d190c17fbe8a7ca838ecef0", size = 34791708, upload-time = "2025-09-26T22:23:30.599Z" }, - { url = "https://files.pythonhosted.org/packages/b1/11/12e158848fe4d3316c999ffb6c2d88f554bde98d69022b3385e25ece997e/mlx_metal-0.29.2-py3-none-macosx_15_0_arm64.whl", hash = "sha256:08d8b7fe305425a14b74ebf36cee176575bfd4cd8d34a2aaae8f05b9983d2d71", size = 34784506, upload-time = "2025-09-26T22:23:29.207Z" }, + { url = "https://files.pythonhosted.org/packages/41/95/a00054a006df82bb1b5b8f666ae44a676b259146fadbff90fe654309fefc/mlx_metal-0.29.3-py3-none-macosx_13_0_arm64.whl", hash = "sha256:27b5a4d905202a71e84d9fd559ea0236813f6f960ef494e5cafe9c45df4c9d7c", size = 36817352, upload-time = "2025-10-17T19:19:25.801Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d8/5ee91eac16dfcf0334103120b47d4abd8c890ccc0d73d3eee4770ce8810f/mlx_metal-0.29.3-py3-none-macosx_14_0_arm64.whl", hash = "sha256:f426d4b67f96b4d6f0ed50d5992933595aadb370dc3e9ed2410bafbc16229882", size = 36555573, upload-time = "2025-10-17T19:18:42.098Z" }, + { url = "https://files.pythonhosted.org/packages/cd/9a/39b7ecdf21cf2a39ced8d7933eed65c6cb38295cadfd0907dd1abd4d1ded/mlx_metal-0.29.3-py3-none-macosx_15_0_arm64.whl", hash = "sha256:106616f7f825851043c53d3dc186965c003985da9cbb6e5c034f35108fc1fc27", size = 36549163, upload-time = "2025-10-17T19:18:37.701Z" }, ] [[package]] @@ -3574,7 +3574,7 @@ wheels = [ [[package]] name = "mostlyai" -version = "5.3.3" +version = "5.3.4" source = { editable = "." } dependencies = [ { name = "duckdb" }, @@ -3584,6 +3584,7 @@ dependencies = [ { name = "httpx" }, { name = "ipywidgets" }, { name = "pandas" }, + { name = "pathvalidate" }, { name = "psutil" }, { name = "pyarrow" }, { name = "pycryptodomex" }, @@ -3737,6 +3738,7 @@ requires-dist = [ { name = "openpyxl", marker = "extra == 'local-gpu'", specifier = ">=3.1.5" }, { name = "oracledb", marker = "extra == 'oracle'", specifier = ">=2.2.1,<3" }, { name = "pandas", specifier = ">=2.0.0" }, + { name = "pathvalidate", specifier = ">=3.3.1" }, { name = "psutil", specifier = ">=5.9.5" }, { name = "psycopg2", marker = "extra == 'postgres'", specifier = ">=2.9.4,<3" }, { name = "pyarrow", specifier = ">=16.0.0" }, @@ -4134,11 +4136,11 @@ wheels = [ [[package]] name = "narwhals" -version = "2.8.0" +version = "2.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/05/79a5b5a795f36c1aaa002d194c1ef71e5d95f7e1900155bbfde734815ab9/narwhals-2.8.0.tar.gz", hash = "sha256:52e0b22d54718264ae703bd9293af53b04abc995a1414908c3b807ba8c913858", size = 574277, upload-time = "2025-10-13T08:44:28.81Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/95/aa46616f5e567ff5d262f4c207d5ca79cb2766010c786c351b8e7f930ef4/narwhals-2.9.0.tar.gz", hash = "sha256:d8cde40a6a8a7049d8e66608b7115ab19464acc6f305d136a8dc8ba396c4acfe", size = 584098, upload-time = "2025-10-20T12:19:16.893Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/86/ac808ecb94322a3f1ea31627d13ab3e50dd4333564d711e0e481ad0f4586/narwhals-2.8.0-py3-none-any.whl", hash = "sha256:6304856676ba4a79fd34148bda63aed8060dd6edb1227edf3659ce5e091de73c", size = 415852, upload-time = "2025-10-13T08:44:25.421Z" }, + { url = "https://files.pythonhosted.org/packages/13/34/00c7ae8194074ed82b64e0bb7c24220eac5f77ac90c16e23cf0d2cfd2a03/narwhals-2.9.0-py3-none-any.whl", hash = "sha256:c59f7de4763004ae81691ce16df71b4e55aead0ead7ccde8c8f2ef8c9559c765", size = 422255, upload-time = "2025-10-20T12:19:15.228Z" }, ] [[package]] @@ -4494,7 +4496,7 @@ wheels = [ [[package]] name = "openai" -version = "2.4.0" +version = "2.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -4506,9 +4508,9 @@ dependencies = [ { name = "tqdm", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/24/d0b0f088c39fa75a73292aef24d7436fbc537f12bf6026c6a69a1bfdae6e/openai-2.4.0.tar.gz", hash = "sha256:97860859172b637ffb308433c207a371d4683586ed2b24b360cb4c08cf377d01", size = 591541, upload-time = "2025-10-16T15:14:05.163Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/c7/e42bcd89dfd47fec8a30b9e20f93e512efdbfbb3391b05bbb79a2fb295fa/openai-2.6.0.tar.gz", hash = "sha256:f119faf7fc07d7e558c1e7c32c873e241439b01bd7480418234291ee8c8f4b9d", size = 592904, upload-time = "2025-10-20T17:17:24.588Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/f6/68a8bbb62c001e5580de6b89372ddab03c3484ac3e251c298a30da094f5e/openai-2.4.0-py3-none-any.whl", hash = "sha256:5099f4fbfa80e7e5785ba52402c580eadba21e6172c85df05455676605ad150f", size = 1003092, upload-time = "2025-10-16T15:14:02.826Z" }, + { url = "https://files.pythonhosted.org/packages/c0/0a/58e9dcd34abe273eaeac3807a8483073767b5609d01bb78ea2f048e515a0/openai-2.6.0-py3-none-any.whl", hash = "sha256:f33fa12070fe347b5787a7861c8dd397786a4a17e1c3186e239338dac7e2e743", size = 1005403, upload-time = "2025-10-20T17:17:22.091Z" }, ] [[package]] @@ -4740,6 +4742,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] +[[package]] +name = "pathvalidate" +version = "3.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, +] + [[package]] name = "pbs-installer" version = "2025.10.14" @@ -5328,7 +5339,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/8a/b35a615ae6f04550d696bb179c414538b3b477999435fdd4ad75b76139e4/pybase64-1.4.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a370dea7b1cee2a36a4d5445d4e09cc243816c5bc8def61f602db5a6f5438e52", size = 54320, upload-time = "2025-07-27T13:03:27.495Z" }, { url = "https://files.pythonhosted.org/packages/d3/a9/8bd4f9bcc53689f1b457ecefed1eaa080e4949d65a62c31a38b7253d5226/pybase64-1.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9aa4de83f02e462a6f4e066811c71d6af31b52d7484de635582d0e3ec3d6cc3e", size = 56482, upload-time = "2025-07-27T13:03:28.942Z" }, { url = "https://files.pythonhosted.org/packages/75/e5/4a7735b54a1191f61c3f5c2952212c85c2d6b06eb5fb3671c7603395f70c/pybase64-1.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83a1c2f9ed00fee8f064d548c8654a480741131f280e5750bb32475b7ec8ee38", size = 70959, upload-time = "2025-07-27T13:03:30.171Z" }, - { url = "https://files.pythonhosted.org/packages/f4/56/5337f27a8b8d2d6693f46f7b36bae47895e5820bfa259b0072574a4e1057/pybase64-1.4.2-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:0f331aa59549de21f690b6ccc79360ffed1155c3cfbc852eb5c097c0b8565a2b", size = 33888, upload-time = "2025-07-27T13:03:35.698Z" }, { url = "https://files.pythonhosted.org/packages/e3/ff/470768f0fe6de0aa302a8cb1bdf2f9f5cffc3f69e60466153be68bc953aa/pybase64-1.4.2-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:69d3f0445b0faeef7bb7f93bf8c18d850785e2a77f12835f49e524cc54af04e7", size = 30914, upload-time = "2025-07-27T13:03:38.475Z" }, { url = "https://files.pythonhosted.org/packages/75/6b/d328736662665e0892409dc410353ebef175b1be5eb6bab1dad579efa6df/pybase64-1.4.2-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2372b257b1f4dd512f317fb27e77d313afd137334de64c87de8374027aacd88a", size = 31380, upload-time = "2025-07-27T13:03:39.7Z" }, { url = "https://files.pythonhosted.org/packages/ca/96/7ff718f87c67f4147c181b73d0928897cefa17dc75d7abc6e37730d5908f/pybase64-1.4.2-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:fb794502b4b1ec91c4ca5d283ae71aef65e3de7721057bd9e2b3ec79f7a62d7d", size = 38230, upload-time = "2025-07-27T13:03:41.637Z" }, @@ -5435,7 +5445,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.2" +version = "2.12.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -5443,9 +5453,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/35/d319ed522433215526689bad428a94058b6dd12190ce7ddd78618ac14b28/pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd", size = 816358, upload-time = "2025-10-14T15:02:21.842Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/1e/4f0a3233767010308f2fd6bd0814597e3f63f1dc98304a9112b8759df4ff/pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74", size = 819383, upload-time = "2025-10-17T15:04:21.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/98/468cb649f208a6f1279448e6e5247b37ae79cf5e4041186f1e2ef3d16345/pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae", size = 460628, upload-time = "2025-10-14T15:02:19.623Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6b/83661fa77dcefa195ad5f8cd9af3d1a7450fd57cc883ad04d65446ac2029/pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf", size = 462431, upload-time = "2025-10-17T15:04:19.346Z" }, ] [package.optional-dependencies] @@ -5646,34 +5656,46 @@ wheels = [ [[package]] name = "pyodbc" -version = "5.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a0/36/a1ac7d23a1611e7ccd4d27df096f3794e8d1e7faa040260d9d41b6fc3185/pyodbc-5.2.0.tar.gz", hash = "sha256:de8be39809c8ddeeee26a4b876a6463529cd487a60d1393eb2a93e9bcd44a8f5", size = 116908, upload-time = "2024-10-16T01:40:13.425Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/01/05c4a4ec122c4a8a37fa1be5bdbf6fb23724a2ee3b1b771bb46f710158a9/pyodbc-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb0850e3e3782f57457feed297e220bb20c3e8fd7550d7a6b6bb96112bd9b6fe", size = 72483, upload-time = "2024-10-16T01:39:23.697Z" }, - { url = "https://files.pythonhosted.org/packages/73/22/ba718cc5508bdfbb53e1906018d7f597be37241c769dda8a48f52af96fe3/pyodbc-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0dae0fb86078c87acf135dbe5afd3c7d15d52ab0db5965c44159e84058c3e2fb", size = 71794, upload-time = "2024-10-16T01:39:25.372Z" }, - { url = "https://files.pythonhosted.org/packages/24/e4/9d859ea3642059c10a6644a00ccb1f8b8e02c1e4f49ab34250db1273c2c5/pyodbc-5.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6493b9c7506ca964b80ad638d0dc82869df7058255d71f04fdd1405e88bcb36b", size = 332850, upload-time = "2024-10-16T01:39:27.789Z" }, - { url = "https://files.pythonhosted.org/packages/b9/a7/98c3555c10cfeb343ec7eea69ecb17476aa3ace72131ea8a4a1f8250318c/pyodbc-5.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04de873607fb960e71953c164c83e8e5d9291ce0d69e688e54947b254b04902", size = 336009, upload-time = "2024-10-16T01:39:29.694Z" }, - { url = "https://files.pythonhosted.org/packages/24/c1/d5b16dd62eb70f281bc90cdc1e3c46af7acda3f0f6afb34553206506ccb2/pyodbc-5.2.0-cp310-cp310-win32.whl", hash = "sha256:74135cb10c1dcdbd99fe429c61539c232140e62939fa7c69b0a373cc552e4a08", size = 62407, upload-time = "2024-10-16T01:39:31.894Z" }, - { url = "https://files.pythonhosted.org/packages/f5/12/22c83669abee4ca5915aa89172cf1673b58ca05f44dabeb8b0bac9b7fecc/pyodbc-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:d287121eeaa562b9ab3d4c52fa77c793dfedd127049273eb882a05d3d67a8ce8", size = 68874, upload-time = "2024-10-16T01:39:33.325Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a2/5907ce319a571eb1e271d6a475920edfeacd92da1021bb2a15ed1b7f6ac1/pyodbc-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4627779f0a608b51ce2d2fe6d1d395384e65ca36248bf9dbb6d7cf2c8fda1cab", size = 72536, upload-time = "2024-10-16T01:39:34.715Z" }, - { url = "https://files.pythonhosted.org/packages/e1/b8/bd438ab2bb9481615142784b0c9778079a87ae1bca7a0fe8aabfc088aa9f/pyodbc-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d997d3b6551273647825c734158ca8a6f682df269f6b3975f2499c01577ddec", size = 71825, upload-time = "2024-10-16T01:39:36.343Z" }, - { url = "https://files.pythonhosted.org/packages/8b/82/cf71ae99b511a7f20c380ce470de233a0291fa3798afa74e0adc8fad1675/pyodbc-5.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5102007a8c78dd2fc1c1b6f6147de8cfc020f81013e4b46c33e66aaa7d1bf7b1", size = 342304, upload-time = "2024-10-16T01:39:37.82Z" }, - { url = "https://files.pythonhosted.org/packages/43/ea/03fe042f4a390df05e753ddd21c6cab006baae1eee71ce230f6e2a883944/pyodbc-5.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e3cbc7075a46c411b531ada557c4aef13d034060a70077717124cabc1717e2d", size = 346186, upload-time = "2024-10-16T01:39:39.3Z" }, - { url = "https://files.pythonhosted.org/packages/f9/80/48178bb50990147adb72ec9e377e94517a0dfaf2f2a6e3fe477d9a33671f/pyodbc-5.2.0-cp311-cp311-win32.whl", hash = "sha256:de1ee7ec2eb326b7be5e2c4ce20d472c5ef1a6eb838d126d1d26779ff5486e49", size = 62418, upload-time = "2024-10-16T01:39:40.797Z" }, - { url = "https://files.pythonhosted.org/packages/7c/6b/f0ad7d8a535d58f35f375ffbf367c68d0ec54452a431d23b0ebee4cd44c6/pyodbc-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:113f904b9852c12f10c7a3288f5a3563ecdbbefe3ccc829074a9eb8255edcd29", size = 68871, upload-time = "2024-10-16T01:39:41.997Z" }, - { url = "https://files.pythonhosted.org/packages/26/26/104525b728fedfababd3143426b9d0008c70f0d604a3bf5d4773977d83f4/pyodbc-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be43d1ece4f2cf4d430996689d89a1a15aeb3a8da8262527e5ced5aee27e89c3", size = 73014, upload-time = "2024-10-16T01:39:43.332Z" }, - { url = "https://files.pythonhosted.org/packages/4f/7d/bb632488b603bcd2a6753b858e8bc7dd56146dd19bd72003cc09ae6e3fc0/pyodbc-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9f7badd0055221a744d76c11440c0856fd2846ed53b6555cf8f0a8893a3e4b03", size = 72515, upload-time = "2024-10-16T01:39:44.506Z" }, - { url = "https://files.pythonhosted.org/packages/ab/38/a1b9bfe5a7062672268553c2d6ff93676173b0fb4bd583e8c4f74a0e296f/pyodbc-5.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad633c52f4f4e7691daaa2278d6e6ebb2fe4ae7709e610e22c7dd1a1d620cf8b", size = 348561, upload-time = "2024-10-16T01:39:45.986Z" }, - { url = "https://files.pythonhosted.org/packages/71/82/ddb1c41c682550116f391aa6cab2052910046a30d63014bbe6d09c4958f4/pyodbc-5.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d086a8f7a302b74c9c2e77bedf954a603b19168af900d4d3a97322e773df63", size = 353962, upload-time = "2024-10-16T01:39:47.254Z" }, - { url = "https://files.pythonhosted.org/packages/e5/29/fec0e739d0c1cab155843ed71d0717f5e1694effe3f28d397168f48bcd92/pyodbc-5.2.0-cp312-cp312-win32.whl", hash = "sha256:0e4412f8e608db2a4be5bcc75f9581f386ed6a427dbcb5eac795049ba6fc205e", size = 63050, upload-time = "2024-10-16T01:39:48.8Z" }, - { url = "https://files.pythonhosted.org/packages/21/7f/3a47e022a97b017ffb73351a1061e4401bcb5aa4fc0162d04f4e5452e4fc/pyodbc-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1f5686b142759c5b2bdbeaa0692622c2ebb1f10780eb3c174b85f5607fbcf55", size = 69485, upload-time = "2024-10-16T01:39:49.732Z" }, - { url = "https://files.pythonhosted.org/packages/90/be/e5f8022ec57a7ea6aa3717a3f307a44c3b012fce7ad6ec91aad3e2a56978/pyodbc-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:26844d780045bbc3514d5c2f0d89e7fda7df7db0bd24292eb6902046f5730885", size = 72982, upload-time = "2024-10-16T01:39:50.738Z" }, - { url = "https://files.pythonhosted.org/packages/5c/0e/71111e4f53936b0b99731d9b6acfc8fc95660533a1421447a63d6e519112/pyodbc-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:26d2d8fd53b71204c755abc53b0379df4e23fd9a40faf211e1cb87e8a32470f0", size = 72515, upload-time = "2024-10-16T01:39:51.86Z" }, - { url = "https://files.pythonhosted.org/packages/a5/09/3c06bbc1ebb9ae15f53cefe10774809b67da643883287ba1c44ba053816a/pyodbc-5.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a27996b6d27e275dfb5fe8a34087ba1cacadfd1439e636874ef675faea5149d9", size = 347470, upload-time = "2024-10-16T01:39:53.594Z" }, - { url = "https://files.pythonhosted.org/packages/a4/35/1c7efd4665e7983169d20175014f68578e0edfcbc4602b0bafcefa522c4a/pyodbc-5.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaf42c4bd323b8fd01f1cd900cca2d09232155f9b8f0b9bcd0be66763588ce64", size = 353025, upload-time = "2024-10-16T01:39:55.124Z" }, - { url = "https://files.pythonhosted.org/packages/6d/c9/736d07fa33572abdc50d858fd9e527d2c8281f3acbb90dff4999a3662edd/pyodbc-5.2.0-cp313-cp313-win32.whl", hash = "sha256:207f16b7e9bf09c591616429ebf2b47127e879aad21167ac15158910dc9bbcda", size = 63052, upload-time = "2024-10-16T01:39:56.565Z" }, - { url = "https://files.pythonhosted.org/packages/73/2a/3219c8b7fa3788fc9f27b5fc2244017223cf070e5ab370f71c519adf9120/pyodbc-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:96d3127f28c0dacf18da7ae009cd48eac532d3dcc718a334b86a3c65f6a5ef5c", size = 69486, upload-time = "2024-10-16T01:39:57.57Z" }, +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/85/44b10070a769a56bd910009bb185c0c0a82daff8d567cd1a116d7d730c7d/pyodbc-5.3.0.tar.gz", hash = "sha256:2fe0e063d8fb66efd0ac6dc39236c4de1a45f17c33eaded0d553d21c199f4d05", size = 121770, upload-time = "2025-10-17T18:04:09.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/cd/d0ac9e8963cf43f3c0e8ebd284cd9c5d0e17457be76c35abe4998b7b6df2/pyodbc-5.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6682cdec78f1302d0c559422c8e00991668e039ed63dece8bf99ef62173376a5", size = 71888, upload-time = "2025-10-17T18:02:58.285Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/95ea2795ea8a0db60414e14f117869a5ba44bd52387886c1a210da637315/pyodbc-5.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9cd3f0a9796b3e1170a9fa168c7e7ca81879142f30e20f46663b882db139b7d2", size = 71813, upload-time = "2025-10-17T18:02:59.722Z" }, + { url = "https://files.pythonhosted.org/packages/95/c9/6f4644b60af513ea1c9cab1ff4af633e8f300e8468f4ae3507f04524e641/pyodbc-5.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46185a1a7f409761716c71de7b95e7bbb004390c650d00b0b170193e3d6224bb", size = 318556, upload-time = "2025-10-17T18:03:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/19/3f/24876d9cb9c6ce1bd2b6f43f69ebc00b8eb47bf1ed99ee95e340bf90ed79/pyodbc-5.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:349a9abae62a968b98f6bbd23d2825151f8d9de50b3a8f5f3271b48958fdb672", size = 322048, upload-time = "2025-10-17T18:03:02.522Z" }, + { url = "https://files.pythonhosted.org/packages/1f/27/faf17353605ac60f80136bc3172ed2d69d7defcb9733166293fc14ac2c52/pyodbc-5.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ac23feb7ddaa729f6b840639e92f83ff0ccaa7072801d944f1332cd5f5b05f47", size = 1286123, upload-time = "2025-10-17T18:03:04.157Z" }, + { url = "https://files.pythonhosted.org/packages/d4/61/c9d407d2aa3e89f9bb68acf6917b0045a788ae8c3f4045c34759cb77af63/pyodbc-5.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8aa396c6d6af52ccd51b8c8a5bffbb46fd44e52ce07ea4272c1d28e5e5b12722", size = 1343502, upload-time = "2025-10-17T18:03:05.485Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9f/f1b0f3238d873d4930aa2a2b8d5ba97132f6416764bf0c87368f8d6f2139/pyodbc-5.3.0-cp310-cp310-win32.whl", hash = "sha256:46869b9a6555ff003ed1d8ebad6708423adf2a5c88e1a578b9f029fb1435186e", size = 62968, upload-time = "2025-10-17T18:03:06.933Z" }, + { url = "https://files.pythonhosted.org/packages/d8/26/5f8ebdca4735aad0119aaaa6d5d73b379901b7a1dbb643aaa636040b27cf/pyodbc-5.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:705903acf6f43c44fc64e764578d9a88649eb21bf7418d78677a9d2e337f56f2", size = 69397, upload-time = "2025-10-17T18:03:08.49Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c8/480a942fd2e87dd7df6d3c1f429df075695ed8ae34d187fe95c64219fd49/pyodbc-5.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:c68d9c225a97aedafb7fff1c0e1bfe293093f77da19eaf200d0e988fa2718d16", size = 64446, upload-time = "2025-10-17T18:03:09.333Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c7/534986d97a26cb8f40ef456dfcf00d8483161eade6d53fa45fcf2d5c2b87/pyodbc-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ebc3be93f61ea0553db88589e683ace12bf975baa954af4834ab89f5ee7bf8ae", size = 71958, upload-time = "2025-10-17T18:03:10.163Z" }, + { url = "https://files.pythonhosted.org/packages/69/3c/6fe3e9eae6db1c34d6616a452f9b954b0d5516c430f3dd959c9d8d725f2a/pyodbc-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b987a25a384f31e373903005554230f5a6d59af78bce62954386736a902a4b3", size = 71843, upload-time = "2025-10-17T18:03:11.058Z" }, + { url = "https://files.pythonhosted.org/packages/44/0e/81a0315d0bf7e57be24338dbed616f806131ab706d87c70f363506dc13d5/pyodbc-5.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:676031723aac7dcbbd2813bddda0e8abf171b20ec218ab8dfb21d64a193430ea", size = 327191, upload-time = "2025-10-17T18:03:11.93Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/b95bb2068f911950322a97172c68675c85a3e87dc04a98448c339fcbef21/pyodbc-5.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5c30c5cd40b751f77bbc73edd32c4498630939bcd4e72ee7e6c9a4b982cc5ca", size = 332228, upload-time = "2025-10-17T18:03:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/dc/21/2433625f7d5922ee9a34e3805805fa0f1355d01d55206c337bb23ec869bf/pyodbc-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2035c7dfb71677cd5be64d3a3eb0779560279f0a8dc6e33673499498caa88937", size = 1296469, upload-time = "2025-10-17T18:03:14.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f4/c760caf7bb9b3ab988975d84bd3e7ebda739fe0075c82f476d04ee97324c/pyodbc-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5cbe4d753723c8a8f65020b7a259183ef5f14307587165ce37e8c7e251951852", size = 1353163, upload-time = "2025-10-17T18:03:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/14/ad/f9ca1e9e44fd91058f6e35b233b1bb6213d590185bfcc2a2c4f1033266e7/pyodbc-5.3.0-cp311-cp311-win32.whl", hash = "sha256:d255f6b117d05cfc046a5201fdf39535264045352ea536c35777cf66d321fbb8", size = 62925, upload-time = "2025-10-17T18:03:17.649Z" }, + { url = "https://files.pythonhosted.org/packages/e6/cf/52b9b94efd8cfd11890ae04f31f50561710128d735e4e38a8fbb964cd2c2/pyodbc-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:f1ad0e93612a6201621853fc661209d82ff2a35892b7d590106fe8f97d9f1f2a", size = 69329, upload-time = "2025-10-17T18:03:18.474Z" }, + { url = "https://files.pythonhosted.org/packages/8b/6f/bf5433bb345007f93003fa062e045890afb42e4e9fc6bd66acc2c3bd12ca/pyodbc-5.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:0df7ff47fab91ea05548095b00e5eb87ed88ddf4648c58c67b4db95ea4913e23", size = 64447, upload-time = "2025-10-17T18:03:19.691Z" }, + { url = "https://files.pythonhosted.org/packages/f5/0c/7ecf8077f4b932a5d25896699ff5c394ffc2a880a9c2c284d6a3e6ea5949/pyodbc-5.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5ebf6b5d989395efe722b02b010cb9815698a4d681921bf5db1c0e1195ac1bde", size = 72994, upload-time = "2025-10-17T18:03:20.551Z" }, + { url = "https://files.pythonhosted.org/packages/03/78/9fbde156055d88c1ef3487534281a5b1479ee7a2f958a7e90714968749ac/pyodbc-5.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:197bb6ddafe356a916b8ee1b8752009057fce58e216e887e2174b24c7ab99269", size = 72535, upload-time = "2025-10-17T18:03:21.423Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f9/8c106dcd6946e95fee0da0f1ba58cd90eb872eebe8968996a2ea1f7ac3c1/pyodbc-5.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6ccb5315ec9e081f5cbd66f36acbc820ad172b8fa3736cf7f993cdf69bd8a96", size = 333565, upload-time = "2025-10-17T18:03:22.695Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/2c70f47a76a4fafa308d148f786aeb35a4d67a01d41002f1065b465d9994/pyodbc-5.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5dd3d5e469f89a3112cf8b0658c43108a4712fad65e576071e4dd44d2bd763c7", size = 340283, upload-time = "2025-10-17T18:03:23.691Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b2/0631d84731606bfe40d3b03a436b80cbd16b63b022c7b13444fb30761ca8/pyodbc-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b180bc5e49b74fd40a24ef5b0fe143d0c234ac1506febe810d7434bf47cb925b", size = 1302767, upload-time = "2025-10-17T18:03:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/74/b9/707c5314cca9401081b3757301241c167a94ba91b4bd55c8fa591bf35a4a/pyodbc-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e3c39de3005fff3ae79246f952720d44affc6756b4b85398da4c5ea76bf8f506", size = 1361251, upload-time = "2025-10-17T18:03:26.538Z" }, + { url = "https://files.pythonhosted.org/packages/97/7c/893036c8b0c8d359082a56efdaa64358a38dda993124162c3faa35d1924d/pyodbc-5.3.0-cp312-cp312-win32.whl", hash = "sha256:d32c3259762bef440707098010035bbc83d1c73d81a434018ab8c688158bd3bb", size = 63413, upload-time = "2025-10-17T18:03:27.903Z" }, + { url = "https://files.pythonhosted.org/packages/c0/70/5e61b216cc13c7f833ef87f4cdeab253a7873f8709253f5076e9bb16c1b3/pyodbc-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe77eb9dcca5fc1300c9121f81040cc9011d28cff383e2c35416e9ec06d4bc95", size = 70133, upload-time = "2025-10-17T18:03:28.746Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/e7d0629c9714a85eb4f85d21602ce6d8a1ec0f313fde8017990cf913e3b4/pyodbc-5.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:afe7c4ac555a8d10a36234788fc6cfc22a86ce37fc5ba88a1f75b3e6696665dc", size = 64700, upload-time = "2025-10-17T18:03:29.638Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/9e74cbcc1d4878553eadfd59138364b38656369eb58f7e5b42fb344c0ce7/pyodbc-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e9ab0b91de28a5ab838ac4db0253d7cc8ce2452efe4ad92ee6a57b922bf0c24", size = 72975, upload-time = "2025-10-17T18:03:30.466Z" }, + { url = "https://files.pythonhosted.org/packages/37/c7/27d83f91b3144d3e275b5b387f0564b161ddbc4ce1b72bb3b3653e7f4f7a/pyodbc-5.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6132554ffbd7910524d643f13ce17f4a72f3a6824b0adef4e9a7f66efac96350", size = 72541, upload-time = "2025-10-17T18:03:31.348Z" }, + { url = "https://files.pythonhosted.org/packages/1b/33/2bb24e7fc95e98a7b11ea5ad1f256412de35d2e9cc339be198258c1d9a76/pyodbc-5.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1629af4706e9228d79dabb4863c11cceb22a6dab90700db0ef449074f0150c0d", size = 343287, upload-time = "2025-10-17T18:03:32.287Z" }, + { url = "https://files.pythonhosted.org/packages/fa/24/88cde8b6dc07a93a92b6c15520a947db24f55db7bd8b09e85956642b7cf3/pyodbc-5.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ceaed87ba2ea848c11223f66f629ef121f6ebe621f605cde9cfdee4fd9f4b68", size = 350094, upload-time = "2025-10-17T18:03:33.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/99/53c08562bc171a618fa1699297164f8885e66cde38c3b30f454730d0c488/pyodbc-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3cc472c8ae2feea5b4512e23b56e2b093d64f7cbc4b970af51da488429ff7818", size = 1301029, upload-time = "2025-10-17T18:03:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/d8/10/68a0b5549876d4b53ba4c46eed2a7aca32d589624ed60beef5bd7382619e/pyodbc-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c79df54bbc25bce9f2d87094e7b39089c28428df5443d1902b0cc5f43fd2da6f", size = 1361420, upload-time = "2025-10-17T18:03:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/41/0f/9dfe4987283ffcb981c49a002f0339d669215eb4a3fe4ee4e14537c52852/pyodbc-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c2eb0b08e24fe5c40c7ebe9240c5d3bd2f18cd5617229acee4b0a0484dc226f2", size = 63399, upload-time = "2025-10-17T18:03:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/56/03/15dcefe549d3888b649652af7cca36eda97c12b6196d92937ca6d11306e9/pyodbc-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:01166162149adf2b8a6dc21a212718f205cabbbdff4047dc0c415af3fd85867e", size = 70133, upload-time = "2025-10-17T18:03:38.47Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c1/c8b128ae59a14ecc8510e9b499208e342795aecc3af4c3874805c720b8db/pyodbc-5.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:363311bd40320b4a61454bebf7c38b243cd67c762ed0f8a5219de3ec90c96353", size = 64683, upload-time = "2025-10-17T18:03:39.68Z" }, ] [[package]] @@ -5993,7 +6015,7 @@ wheels = [ [[package]] name = "ray" -version = "2.50.0" +version = "2.50.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -6006,18 +6028,18 @@ dependencies = [ { name = "requests", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/35/ff/18252433cc917c2a59baef51535285cc3ea67fef6682b312b84557dac57c/ray-2.50.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:b164d6af420c7a38a8323f8ff3788b8c57cef4679b3488354a12371aac01874e", size = 67622394, upload-time = "2025-10-10T21:55:33.19Z" }, - { url = "https://files.pythonhosted.org/packages/71/da/c3109967da9ba183a0eb2451fe690744ed392a3f7de30f5fe7a1f0e81e61/ray-2.50.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:0062a7fa563f424665b1cdec0fee9c70708da807ecc5916c49925a21438897d0", size = 70121055, upload-time = "2025-10-10T21:55:39.96Z" }, - { url = "https://files.pythonhosted.org/packages/3b/6d/6782de95992fd34c84330e60caa91244655809c8ad75926b1763a3db6db4/ray-2.50.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:c831bffba0f6f0dfa25b65c5b800022c7234cbec1b98a8ede17f97cc825f700d", size = 70937020, upload-time = "2025-10-10T21:55:45.263Z" }, - { url = "https://files.pythonhosted.org/packages/55/07/87a35efc7363cd29a7b9070385f632f98930267651c70f103e10adda2afa/ray-2.50.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:619b2e6b880d5af126df1dfcdaa27314edd43d2c6d6315e01d1e311839abffe4", size = 67625645, upload-time = "2025-10-10T21:55:54.829Z" }, - { url = "https://files.pythonhosted.org/packages/01/64/2fff8ca682d6d07dc830900830cdf586e6061c6bdd7b601e75a43169d6bc/ray-2.50.0-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b139239f30140e982f7bc5fb1cc26948a0a76c980eba371efdde3fb045d0a7e4", size = 70245044, upload-time = "2025-10-10T21:56:00.5Z" }, - { url = "https://files.pythonhosted.org/packages/a8/39/59b37d06cf2fb8d3f88bcaae1e7098e12564751e876856e9906f21f9e85c/ray-2.50.0-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:03b65fce8ede26e2a52365f20967c09b3a35e9d7d2b856645dcc680435a47e43", size = 71054909, upload-time = "2025-10-10T21:56:06.099Z" }, - { url = "https://files.pythonhosted.org/packages/95/eb/99ac4a39c497c017fa03582e23c8e451e7726420fe1fbc8b176e039f2cea/ray-2.50.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:e683981b51ff534c09a48b3a5b65fc61886de3da8aae8738239591dab2b67ce5", size = 67610267, upload-time = "2025-10-10T21:56:15.595Z" }, - { url = "https://files.pythonhosted.org/packages/24/8e/34cfbc7bf3335968569037e9a4281987227be8f61acd77c74133afb18b73/ray-2.50.0-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:cfde02a1cb114481758abfe40b4b8c31afe199150887372251466256756911cb", size = 70288832, upload-time = "2025-10-10T21:56:21.316Z" }, - { url = "https://files.pythonhosted.org/packages/be/60/cd05d1f0f91249b140cf44d4dcf8bb4c39c0d3e0d1f8c2664eaff9d846f6/ray-2.50.0-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:214a006489419c786715cb2a81b3c52c88e08ea3295f48ba97ab69b888d8f818", size = 71125972, upload-time = "2025-10-10T21:56:26.742Z" }, - { url = "https://files.pythonhosted.org/packages/91/3b/3f8aae31166256fb01a88db461558289714255b641a4e38b5b87f4e11494/ray-2.50.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c3da96695e3f63bd0eecad6d30ec7cf6d0309a8738e31ac270957812bcc1cd8b", size = 67555250, upload-time = "2025-10-10T21:56:36.605Z" }, - { url = "https://files.pythonhosted.org/packages/e2/16/3845f71116108cd1ee9aa2cf3e62502bdff544409de06b397b6d9f61d58f/ray-2.50.0-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:135c70c92bd3c3050441c905576e871b879dca486649218c45241a3ec4572399", size = 70195757, upload-time = "2025-10-10T21:56:41.935Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ee/3a6658967bb7d6878f27458247f1f94b67a573afe74b95f3b97c8a71cf29/ray-2.50.0-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:838d2ca6065c1255b6c70f1d2041ca0b6ab3abab9083303b65d7b3138c022de2", size = 71038372, upload-time = "2025-10-10T21:56:47.044Z" }, + { url = "https://files.pythonhosted.org/packages/e4/32/2370ca78453a0efb2c005a44e9277b57d8a89c93fd377c5a0dce4e5ce3a9/ray-2.50.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0ee61b69b06acb7754f6cd08716084ce495fbbd963aeb72cfb4d14525d0e0969", size = 67625348, upload-time = "2025-10-18T01:40:06.107Z" }, + { url = "https://files.pythonhosted.org/packages/98/f2/589660c250b155333c2648d534dbec9777dd6125bba66c06f2e35751b102/ray-2.50.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:b061816f8aed4bca0b81174dbffe717c69fb7bca20efd8f75560d3a6f8ccb280", size = 70121032, upload-time = "2025-10-18T01:40:13.076Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/fd3558e2cfb1525cc922c49ce3a69770a2ebfc4792205e3e5a7ee7e5bdcc/ray-2.50.1-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:dea9cc60e92dd089689156756e6d99c99fe3e49134180f715436b2e352b5df86", size = 70937579, upload-time = "2025-10-18T01:40:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/27/15/b489f5b022e203a21db86c07626da4e3a0779343fb22aab4ef33c639ff9f/ray-2.50.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb33fd81684fead4aab706bd88b0aa27b94684906bfb511e59cc5756885a3f6f", size = 67628477, upload-time = "2025-10-18T01:40:28.414Z" }, + { url = "https://files.pythonhosted.org/packages/69/97/b8959faa369533a504b2486699175f9a13c2b7e3269b0609a1dfd4a2b0f0/ray-2.50.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:76e1eaa627e19b706fa21e489cba692c7143d7d35843373ba71941def07ab58b", size = 70245929, upload-time = "2025-10-18T01:40:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/92/50/b426daa685c545fb577260da157a2e5afb6f693c669508951fa3be881f4b/ray-2.50.1-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:85f476bb4e667daad65318f29a35b13d6faa8e0530079c667d548c00c2d925e8", size = 71055788, upload-time = "2025-10-18T01:40:39.591Z" }, + { url = "https://files.pythonhosted.org/packages/1e/81/571fd2872eab5a3433d64498a7a4dd7f793ab06dd346905da3c8b3b5fc82/ray-2.50.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:723e56c8193f8adde3ec18817ab437ad1cc9d4e72df1263e85697be282cfc526", size = 67612834, upload-time = "2025-10-18T01:40:48.952Z" }, + { url = "https://files.pythonhosted.org/packages/89/3d/8272a45dc8ef0d2fd69442bdb30f3f017a30df24f9befd1ab66afc124009/ray-2.50.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a8424fd3a4a1ef314a85f80c361f22e9bd949c7a63e238cf4172dc1955b12c7c", size = 70289553, upload-time = "2025-10-18T01:40:54.412Z" }, + { url = "https://files.pythonhosted.org/packages/5e/db/f6b2a5b86c827269877d234120fb5d6979f8c15020645dc33e651a853ae7/ray-2.50.1-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:75c884e31d4dc0c384d4a4b68e9611175b6acba8622352bcabb73190cb9f8c3f", size = 71126830, upload-time = "2025-10-18T01:41:00.095Z" }, + { url = "https://files.pythonhosted.org/packages/fa/51/6b4b8481bd626db3eb3a51dcea8dd2189eb2cac5d8aa7d7d9fe43200dcd5/ray-2.50.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:254a257dc2ba4349a4784af1f204c4d8169908ea779a2e5d4de87311ab5f525f", size = 67557809, upload-time = "2025-10-18T01:41:09.369Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b3/059854143d1b487e172269aa36c06a5e2a33825a4a277c069f9fa44e6f55/ray-2.50.1-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:40cb56cb82a2779d5b2676b7bcd911d0f0a78d2234a15abb4f982415b651cfca", size = 70196002, upload-time = "2025-10-18T01:41:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/76/3a/976308e8042301eae36df1a820719299625b03b07b739f764a5a5c0df952/ray-2.50.1-cp313-cp313-manylinux2014_x86_64.whl", hash = "sha256:7a52554bd55f2a6188af56ffe5c7bd977e40eb97b7b6282d827a8d3a73f0789a", size = 71039153, upload-time = "2025-10-18T01:41:20.491Z" }, ] [package.optional-dependencies] @@ -6074,81 +6096,81 @@ wheels = [ [[package]] name = "regex" -version = "2025.9.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/d3/eaa0d28aba6ad1827ad1e716d9a93e1ba963ada61887498297d3da715133/regex-2025.9.18.tar.gz", hash = "sha256:c5ba23274c61c6fef447ba6a39333297d0c247f53059dba0bca415cac511edc4", size = 400917, upload-time = "2025-09-19T00:38:35.79Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d8/7e06171db8e55f917c5b8e89319cea2d86982e3fc46b677f40358223dece/regex-2025.9.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:12296202480c201c98a84aecc4d210592b2f55e200a1d193235c4db92b9f6788", size = 484829, upload-time = "2025-09-19T00:35:05.215Z" }, - { url = "https://files.pythonhosted.org/packages/8d/70/bf91bb39e5bedf75ce730ffbaa82ca585584d13335306d637458946b8b9f/regex-2025.9.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:220381f1464a581f2ea988f2220cf2a67927adcef107d47d6897ba5a2f6d51a4", size = 288993, upload-time = "2025-09-19T00:35:08.154Z" }, - { url = "https://files.pythonhosted.org/packages/fe/89/69f79b28365eda2c46e64c39d617d5f65a2aa451a4c94de7d9b34c2dc80f/regex-2025.9.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:87f681bfca84ebd265278b5daa1dcb57f4db315da3b5d044add7c30c10442e61", size = 286624, upload-time = "2025-09-19T00:35:09.717Z" }, - { url = "https://files.pythonhosted.org/packages/44/31/81e62955726c3a14fcc1049a80bc716765af6c055706869de5e880ddc783/regex-2025.9.18-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34d674cbba70c9398074c8a1fcc1a79739d65d1105de2a3c695e2b05ea728251", size = 780473, upload-time = "2025-09-19T00:35:11.013Z" }, - { url = "https://files.pythonhosted.org/packages/fb/23/07072b7e191fbb6e213dc03b2f5b96f06d3c12d7deaded84679482926fc7/regex-2025.9.18-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:385c9b769655cb65ea40b6eea6ff763cbb6d69b3ffef0b0db8208e1833d4e746", size = 849290, upload-time = "2025-09-19T00:35:12.348Z" }, - { url = "https://files.pythonhosted.org/packages/b3/f0/aec7f6a01f2a112210424d77c6401b9015675fb887ced7e18926df4ae51e/regex-2025.9.18-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8900b3208e022570ae34328712bef6696de0804c122933414014bae791437ab2", size = 897335, upload-time = "2025-09-19T00:35:14.058Z" }, - { url = "https://files.pythonhosted.org/packages/cc/90/2e5f9da89d260de7d0417ead91a1bc897f19f0af05f4f9323313b76c47f2/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c204e93bf32cd7a77151d44b05eb36f469d0898e3fba141c026a26b79d9914a0", size = 789946, upload-time = "2025-09-19T00:35:15.403Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d5/1c712c7362f2563d389be66bae131c8bab121a3fabfa04b0b5bfc9e73c51/regex-2025.9.18-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3acc471d1dd7e5ff82e6cacb3b286750decd949ecd4ae258696d04f019817ef8", size = 780787, upload-time = "2025-09-19T00:35:17.061Z" }, - { url = "https://files.pythonhosted.org/packages/4f/92/c54cdb4aa41009632e69817a5aa452673507f07e341076735a2f6c46a37c/regex-2025.9.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6479d5555122433728760e5f29edb4c2b79655a8deb681a141beb5c8a025baea", size = 773632, upload-time = "2025-09-19T00:35:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/db/99/75c996dc6a2231a8652d7ad0bfbeaf8a8c77612d335580f520f3ec40e30b/regex-2025.9.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:431bd2a8726b000eb6f12429c9b438a24062a535d06783a93d2bcbad3698f8a8", size = 844104, upload-time = "2025-09-19T00:35:20.259Z" }, - { url = "https://files.pythonhosted.org/packages/1c/f7/25aba34cc130cb6844047dbfe9716c9b8f9629fee8b8bec331aa9241b97b/regex-2025.9.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:0cc3521060162d02bd36927e20690129200e5ac9d2c6d32b70368870b122db25", size = 834794, upload-time = "2025-09-19T00:35:22.002Z" }, - { url = "https://files.pythonhosted.org/packages/51/eb/64e671beafa0ae29712268421597596d781704973551312b2425831d4037/regex-2025.9.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a021217b01be2d51632ce056d7a837d3fa37c543ede36e39d14063176a26ae29", size = 778535, upload-time = "2025-09-19T00:35:23.298Z" }, - { url = "https://files.pythonhosted.org/packages/26/33/c0ebc0b07bd0bf88f716cca240546b26235a07710ea58e271cfe390ae273/regex-2025.9.18-cp310-cp310-win32.whl", hash = "sha256:4a12a06c268a629cb67cc1d009b7bb0be43e289d00d5111f86a2efd3b1949444", size = 264115, upload-time = "2025-09-19T00:35:25.206Z" }, - { url = "https://files.pythonhosted.org/packages/59/39/aeb11a4ae68faaec2498512cadae09f2d8a91f1f65730fe62b9bffeea150/regex-2025.9.18-cp310-cp310-win_amd64.whl", hash = "sha256:47acd811589301298c49db2c56bde4f9308d6396da92daf99cba781fa74aa450", size = 276143, upload-time = "2025-09-19T00:35:26.785Z" }, - { url = "https://files.pythonhosted.org/packages/29/04/37f2d3fc334a1031fc2767c9d89cec13c2e72207c7e7f6feae8a47f4e149/regex-2025.9.18-cp310-cp310-win_arm64.whl", hash = "sha256:16bd2944e77522275e5ee36f867e19995bcaa533dcb516753a26726ac7285442", size = 268473, upload-time = "2025-09-19T00:35:28.39Z" }, - { url = "https://files.pythonhosted.org/packages/58/61/80eda662fc4eb32bfedc331f42390974c9e89c7eac1b79cd9eea4d7c458c/regex-2025.9.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:51076980cd08cd13c88eb7365427ae27f0d94e7cebe9ceb2bb9ffdae8fc4d82a", size = 484832, upload-time = "2025-09-19T00:35:30.011Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d9/33833d9abddf3f07ad48504ddb53fe3b22f353214bbb878a72eee1e3ddbf/regex-2025.9.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:828446870bd7dee4e0cbeed767f07961aa07f0ea3129f38b3ccecebc9742e0b8", size = 288994, upload-time = "2025-09-19T00:35:31.733Z" }, - { url = "https://files.pythonhosted.org/packages/2a/b3/526ee96b0d70ea81980cbc20c3496fa582f775a52e001e2743cc33b2fa75/regex-2025.9.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c28821d5637866479ec4cc23b8c990f5bc6dd24e5e4384ba4a11d38a526e1414", size = 286619, upload-time = "2025-09-19T00:35:33.221Z" }, - { url = "https://files.pythonhosted.org/packages/65/4f/c2c096b02a351b33442aed5895cdd8bf87d372498d2100927c5a053d7ba3/regex-2025.9.18-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:726177ade8e481db669e76bf99de0b278783be8acd11cef71165327abd1f170a", size = 792454, upload-time = "2025-09-19T00:35:35.361Z" }, - { url = "https://files.pythonhosted.org/packages/24/15/b562c9d6e47c403c4b5deb744f8b4bf6e40684cf866c7b077960a925bdff/regex-2025.9.18-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f5cca697da89b9f8ea44115ce3130f6c54c22f541943ac8e9900461edc2b8bd4", size = 858723, upload-time = "2025-09-19T00:35:36.949Z" }, - { url = "https://files.pythonhosted.org/packages/f2/01/dba305409849e85b8a1a681eac4c03ed327d8de37895ddf9dc137f59c140/regex-2025.9.18-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dfbde38f38004703c35666a1e1c088b778e35d55348da2b7b278914491698d6a", size = 905899, upload-time = "2025-09-19T00:35:38.723Z" }, - { url = "https://files.pythonhosted.org/packages/fe/d0/c51d1e6a80eab11ef96a4cbad17fc0310cf68994fb01a7283276b7e5bbd6/regex-2025.9.18-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f2f422214a03fab16bfa495cfec72bee4aaa5731843b771860a471282f1bf74f", size = 798981, upload-time = "2025-09-19T00:35:40.416Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5e/72db90970887bbe02296612bd61b0fa31e6d88aa24f6a4853db3e96c575e/regex-2025.9.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a295916890f4df0902e4286bc7223ee7f9e925daa6dcdec4192364255b70561a", size = 781900, upload-time = "2025-09-19T00:35:42.077Z" }, - { url = "https://files.pythonhosted.org/packages/50/ff/596be45eea8e9bc31677fde243fa2904d00aad1b32c31bce26c3dbba0b9e/regex-2025.9.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:5db95ff632dbabc8c38c4e82bf545ab78d902e81160e6e455598014f0abe66b9", size = 852952, upload-time = "2025-09-19T00:35:43.751Z" }, - { url = "https://files.pythonhosted.org/packages/e5/1b/2dfa348fa551e900ed3f5f63f74185b6a08e8a76bc62bc9c106f4f92668b/regex-2025.9.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb967eb441b0f15ae610b7069bdb760b929f267efbf522e814bbbfffdf125ce2", size = 844355, upload-time = "2025-09-19T00:35:45.309Z" }, - { url = "https://files.pythonhosted.org/packages/f4/bf/aefb1def27fe33b8cbbb19c75c13aefccfbef1c6686f8e7f7095705969c7/regex-2025.9.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f04d2f20da4053d96c08f7fde6e1419b7ec9dbcee89c96e3d731fca77f411b95", size = 787254, upload-time = "2025-09-19T00:35:46.904Z" }, - { url = "https://files.pythonhosted.org/packages/e3/4e/8ef042e7cf0dbbb401e784e896acfc1b367b95dfbfc9ada94c2ed55a081f/regex-2025.9.18-cp311-cp311-win32.whl", hash = "sha256:895197241fccf18c0cea7550c80e75f185b8bd55b6924fcae269a1a92c614a07", size = 264129, upload-time = "2025-09-19T00:35:48.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7d/c4fcabf80dcdd6821c0578ad9b451f8640b9110fb3dcb74793dd077069ff/regex-2025.9.18-cp311-cp311-win_amd64.whl", hash = "sha256:7e2b414deae99166e22c005e154a5513ac31493db178d8aec92b3269c9cce8c9", size = 276160, upload-time = "2025-09-19T00:36:00.45Z" }, - { url = "https://files.pythonhosted.org/packages/64/f8/0e13c8ae4d6df9d128afaba138342d532283d53a4c1e7a8c93d6756c8f4a/regex-2025.9.18-cp311-cp311-win_arm64.whl", hash = "sha256:fb137ec7c5c54f34a25ff9b31f6b7b0c2757be80176435bf367111e3f71d72df", size = 268471, upload-time = "2025-09-19T00:36:02.149Z" }, - { url = "https://files.pythonhosted.org/packages/b0/99/05859d87a66ae7098222d65748f11ef7f2dff51bfd7482a4e2256c90d72b/regex-2025.9.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:436e1b31d7efd4dcd52091d076482031c611dde58bf9c46ca6d0a26e33053a7e", size = 486335, upload-time = "2025-09-19T00:36:03.661Z" }, - { url = "https://files.pythonhosted.org/packages/97/7e/d43d4e8b978890932cf7b0957fce58c5b08c66f32698f695b0c2c24a48bf/regex-2025.9.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c190af81e5576b9c5fdc708f781a52ff20f8b96386c6e2e0557a78402b029f4a", size = 289720, upload-time = "2025-09-19T00:36:05.471Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3b/ff80886089eb5dcf7e0d2040d9aaed539e25a94300403814bb24cc775058/regex-2025.9.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e4121f1ce2b2b5eec4b397cc1b277686e577e658d8f5870b7eb2d726bd2300ab", size = 287257, upload-time = "2025-09-19T00:36:07.072Z" }, - { url = "https://files.pythonhosted.org/packages/ee/66/243edf49dd8720cba8d5245dd4d6adcb03a1defab7238598c0c97cf549b8/regex-2025.9.18-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:300e25dbbf8299d87205e821a201057f2ef9aa3deb29caa01cd2cac669e508d5", size = 797463, upload-time = "2025-09-19T00:36:08.399Z" }, - { url = "https://files.pythonhosted.org/packages/df/71/c9d25a1142c70432e68bb03211d4a82299cd1c1fbc41db9409a394374ef5/regex-2025.9.18-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b47fcf9f5316c0bdaf449e879407e1b9937a23c3b369135ca94ebc8d74b1742", size = 862670, upload-time = "2025-09-19T00:36:10.101Z" }, - { url = "https://files.pythonhosted.org/packages/f8/8f/329b1efc3a64375a294e3a92d43372bf1a351aa418e83c21f2f01cf6ec41/regex-2025.9.18-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:57a161bd3acaa4b513220b49949b07e252165e6b6dc910ee7617a37ff4f5b425", size = 910881, upload-time = "2025-09-19T00:36:12.223Z" }, - { url = "https://files.pythonhosted.org/packages/35/9e/a91b50332a9750519320ed30ec378b74c996f6befe282cfa6bb6cea7e9fd/regex-2025.9.18-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f130c3a7845ba42de42f380fff3c8aebe89a810747d91bcf56d40a069f15352", size = 802011, upload-time = "2025-09-19T00:36:13.901Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1d/6be3b8d7856b6e0d7ee7f942f437d0a76e0d5622983abbb6d21e21ab9a17/regex-2025.9.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f96fa342b6f54dcba928dd452e8d8cb9f0d63e711d1721cd765bb9f73bb048d", size = 786668, upload-time = "2025-09-19T00:36:15.391Z" }, - { url = "https://files.pythonhosted.org/packages/cb/ce/4a60e53df58bd157c5156a1736d3636f9910bdcc271d067b32b7fcd0c3a8/regex-2025.9.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f0d676522d68c207828dcd01fb6f214f63f238c283d9f01d85fc664c7c85b56", size = 856578, upload-time = "2025-09-19T00:36:16.845Z" }, - { url = "https://files.pythonhosted.org/packages/86/e8/162c91bfe7217253afccde112868afb239f94703de6580fb235058d506a6/regex-2025.9.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:40532bff8a1a0621e7903ae57fce88feb2e8a9a9116d341701302c9302aef06e", size = 849017, upload-time = "2025-09-19T00:36:18.597Z" }, - { url = "https://files.pythonhosted.org/packages/35/34/42b165bc45289646ea0959a1bc7531733e90b47c56a72067adfe6b3251f6/regex-2025.9.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:039f11b618ce8d71a1c364fdee37da1012f5a3e79b1b2819a9f389cd82fd6282", size = 788150, upload-time = "2025-09-19T00:36:20.464Z" }, - { url = "https://files.pythonhosted.org/packages/79/5d/cdd13b1f3c53afa7191593a7ad2ee24092a5a46417725ffff7f64be8342d/regex-2025.9.18-cp312-cp312-win32.whl", hash = "sha256:e1dd06f981eb226edf87c55d523131ade7285137fbde837c34dc9d1bf309f459", size = 264536, upload-time = "2025-09-19T00:36:21.922Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f5/4a7770c9a522e7d2dc1fa3ffc83ab2ab33b0b22b447e62cffef186805302/regex-2025.9.18-cp312-cp312-win_amd64.whl", hash = "sha256:3d86b5247bf25fa3715e385aa9ff272c307e0636ce0c9595f64568b41f0a9c77", size = 275501, upload-time = "2025-09-19T00:36:23.4Z" }, - { url = "https://files.pythonhosted.org/packages/df/05/9ce3e110e70d225ecbed455b966003a3afda5e58e8aec2964042363a18f4/regex-2025.9.18-cp312-cp312-win_arm64.whl", hash = "sha256:032720248cbeeae6444c269b78cb15664458b7bb9ed02401d3da59fe4d68c3a5", size = 268601, upload-time = "2025-09-19T00:36:25.092Z" }, - { url = "https://files.pythonhosted.org/packages/d2/c7/5c48206a60ce33711cf7dcaeaed10dd737733a3569dc7e1dce324dd48f30/regex-2025.9.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a40f929cd907c7e8ac7566ac76225a77701a6221bca937bdb70d56cb61f57b2", size = 485955, upload-time = "2025-09-19T00:36:26.822Z" }, - { url = "https://files.pythonhosted.org/packages/e9/be/74fc6bb19a3c491ec1ace943e622b5a8539068771e8705e469b2da2306a7/regex-2025.9.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c90471671c2cdf914e58b6af62420ea9ecd06d1554d7474d50133ff26ae88feb", size = 289583, upload-time = "2025-09-19T00:36:28.577Z" }, - { url = "https://files.pythonhosted.org/packages/25/c4/9ceaa433cb5dc515765560f22a19578b95b92ff12526e5a259321c4fc1a0/regex-2025.9.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a351aff9e07a2dabb5022ead6380cff17a4f10e4feb15f9100ee56c4d6d06af", size = 287000, upload-time = "2025-09-19T00:36:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e6/68bc9393cb4dc68018456568c048ac035854b042bc7c33cb9b99b0680afa/regex-2025.9.18-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc4b8e9d16e20ddfe16430c23468a8707ccad3365b06d4536142e71823f3ca29", size = 797535, upload-time = "2025-09-19T00:36:31.876Z" }, - { url = "https://files.pythonhosted.org/packages/6a/1c/ebae9032d34b78ecfe9bd4b5e6575b55351dc8513485bb92326613732b8c/regex-2025.9.18-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4b8cdbddf2db1c5e80338ba2daa3cfa3dec73a46fff2a7dda087c8efbf12d62f", size = 862603, upload-time = "2025-09-19T00:36:33.344Z" }, - { url = "https://files.pythonhosted.org/packages/3b/74/12332c54b3882557a4bcd2b99f8be581f5c6a43cf1660a85b460dd8ff468/regex-2025.9.18-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a276937d9d75085b2c91fb48244349c6954f05ee97bba0963ce24a9d915b8b68", size = 910829, upload-time = "2025-09-19T00:36:34.826Z" }, - { url = "https://files.pythonhosted.org/packages/86/70/ba42d5ed606ee275f2465bfc0e2208755b06cdabd0f4c7c4b614d51b57ab/regex-2025.9.18-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92a8e375ccdc1256401c90e9dc02b8642894443d549ff5e25e36d7cf8a80c783", size = 802059, upload-time = "2025-09-19T00:36:36.664Z" }, - { url = "https://files.pythonhosted.org/packages/da/c5/fcb017e56396a7f2f8357412638d7e2963440b131a3ca549be25774b3641/regex-2025.9.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dc6893b1f502d73037cf807a321cdc9be29ef3d6219f7970f842475873712ac", size = 786781, upload-time = "2025-09-19T00:36:38.168Z" }, - { url = "https://files.pythonhosted.org/packages/c6/ee/21c4278b973f630adfb3bcb23d09d83625f3ab1ca6e40ebdffe69901c7a1/regex-2025.9.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a61e85bfc63d232ac14b015af1261f826260c8deb19401c0597dbb87a864361e", size = 856578, upload-time = "2025-09-19T00:36:40.129Z" }, - { url = "https://files.pythonhosted.org/packages/87/0b/de51550dc7274324435c8f1539373ac63019b0525ad720132866fff4a16a/regex-2025.9.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1ef86a9ebc53f379d921fb9a7e42b92059ad3ee800fcd9e0fe6181090e9f6c23", size = 849119, upload-time = "2025-09-19T00:36:41.651Z" }, - { url = "https://files.pythonhosted.org/packages/60/52/383d3044fc5154d9ffe4321696ee5b2ee4833a28c29b137c22c33f41885b/regex-2025.9.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d3bc882119764ba3a119fbf2bd4f1b47bc56c1da5d42df4ed54ae1e8e66fdf8f", size = 788219, upload-time = "2025-09-19T00:36:43.575Z" }, - { url = "https://files.pythonhosted.org/packages/20/bd/2614fc302671b7359972ea212f0e3a92df4414aaeacab054a8ce80a86073/regex-2025.9.18-cp313-cp313-win32.whl", hash = "sha256:3810a65675845c3bdfa58c3c7d88624356dd6ee2fc186628295e0969005f928d", size = 264517, upload-time = "2025-09-19T00:36:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/07/0f/ab5c1581e6563a7bffdc1974fb2d25f05689b88e2d416525271f232b1946/regex-2025.9.18-cp313-cp313-win_amd64.whl", hash = "sha256:16eaf74b3c4180ede88f620f299e474913ab6924d5c4b89b3833bc2345d83b3d", size = 275481, upload-time = "2025-09-19T00:36:46.965Z" }, - { url = "https://files.pythonhosted.org/packages/49/22/ee47672bc7958f8c5667a587c2600a4fba8b6bab6e86bd6d3e2b5f7cac42/regex-2025.9.18-cp313-cp313-win_arm64.whl", hash = "sha256:4dc98ba7dd66bd1261927a9f49bd5ee2bcb3660f7962f1ec02617280fc00f5eb", size = 268598, upload-time = "2025-09-19T00:36:48.314Z" }, - { url = "https://files.pythonhosted.org/packages/e8/83/6887e16a187c6226cb85d8301e47d3b73ecc4505a3a13d8da2096b44fd76/regex-2025.9.18-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fe5d50572bc885a0a799410a717c42b1a6b50e2f45872e2b40f4f288f9bce8a2", size = 489765, upload-time = "2025-09-19T00:36:49.996Z" }, - { url = "https://files.pythonhosted.org/packages/51/c5/e2f7325301ea2916ff301c8d963ba66b1b2c1b06694191df80a9c4fea5d0/regex-2025.9.18-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b9d9a2d6cda6621551ca8cf7a06f103adf72831153f3c0d982386110870c4d3", size = 291228, upload-time = "2025-09-19T00:36:51.654Z" }, - { url = "https://files.pythonhosted.org/packages/91/60/7d229d2bc6961289e864a3a3cfebf7d0d250e2e65323a8952cbb7e22d824/regex-2025.9.18-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:13202e4c4ac0ef9a317fff817674b293c8f7e8c68d3190377d8d8b749f566e12", size = 289270, upload-time = "2025-09-19T00:36:53.118Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d7/b4f06868ee2958ff6430df89857fbf3d43014bbf35538b6ec96c2704e15d/regex-2025.9.18-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:874ff523b0fecffb090f80ae53dc93538f8db954c8bb5505f05b7787ab3402a0", size = 806326, upload-time = "2025-09-19T00:36:54.631Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e4/bca99034a8f1b9b62ccf337402a8e5b959dd5ba0e5e5b2ead70273df3277/regex-2025.9.18-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d13ab0490128f2bb45d596f754148cd750411afc97e813e4b3a61cf278a23bb6", size = 871556, upload-time = "2025-09-19T00:36:56.208Z" }, - { url = "https://files.pythonhosted.org/packages/6d/df/e06ffaf078a162f6dd6b101a5ea9b44696dca860a48136b3ae4a9caf25e2/regex-2025.9.18-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:05440bc172bc4b4b37fb9667e796597419404dbba62e171e1f826d7d2a9ebcef", size = 913817, upload-time = "2025-09-19T00:36:57.807Z" }, - { url = "https://files.pythonhosted.org/packages/9e/05/25b05480b63292fd8e84800b1648e160ca778127b8d2367a0a258fa2e225/regex-2025.9.18-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5514b8e4031fdfaa3d27e92c75719cbe7f379e28cacd939807289bce76d0e35a", size = 811055, upload-time = "2025-09-19T00:36:59.762Z" }, - { url = "https://files.pythonhosted.org/packages/70/97/7bc7574655eb651ba3a916ed4b1be6798ae97af30104f655d8efd0cab24b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:65d3c38c39efce73e0d9dc019697b39903ba25b1ad45ebbd730d2cf32741f40d", size = 794534, upload-time = "2025-09-19T00:37:01.405Z" }, - { url = "https://files.pythonhosted.org/packages/b4/c2/d5da49166a52dda879855ecdba0117f073583db2b39bb47ce9a3378a8e9e/regex-2025.9.18-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ae77e447ebc144d5a26d50055c6ddba1d6ad4a865a560ec7200b8b06bc529368", size = 866684, upload-time = "2025-09-19T00:37:03.441Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2d/0a5c4e6ec417de56b89ff4418ecc72f7e3feca806824c75ad0bbdae0516b/regex-2025.9.18-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e3ef8cf53dc8df49d7e28a356cf824e3623764e9833348b655cfed4524ab8a90", size = 853282, upload-time = "2025-09-19T00:37:04.985Z" }, - { url = "https://files.pythonhosted.org/packages/f4/8e/d656af63e31a86572ec829665d6fa06eae7e144771e0330650a8bb865635/regex-2025.9.18-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9feb29817df349c976da9a0debf775c5c33fc1c8ad7b9f025825da99374770b7", size = 797830, upload-time = "2025-09-19T00:37:06.697Z" }, - { url = "https://files.pythonhosted.org/packages/db/ce/06edc89df8f7b83ffd321b6071be4c54dc7332c0f77860edc40ce57d757b/regex-2025.9.18-cp313-cp313t-win32.whl", hash = "sha256:168be0d2f9b9d13076940b1ed774f98595b4e3c7fc54584bba81b3cc4181742e", size = 267281, upload-time = "2025-09-19T00:37:08.568Z" }, - { url = "https://files.pythonhosted.org/packages/83/9a/2b5d9c8b307a451fd17068719d971d3634ca29864b89ed5c18e499446d4a/regex-2025.9.18-cp313-cp313t-win_amd64.whl", hash = "sha256:d59ecf3bb549e491c8104fea7313f3563c7b048e01287db0a90485734a70a730", size = 278724, upload-time = "2025-09-19T00:37:10.023Z" }, - { url = "https://files.pythonhosted.org/packages/3d/70/177d31e8089a278a764f8ec9a3faac8d14a312d622a47385d4b43905806f/regex-2025.9.18-cp313-cp313t-win_arm64.whl", hash = "sha256:dbef80defe9fb21310948a2595420b36c6d641d9bea4c991175829b2cc4bc06a", size = 269771, upload-time = "2025-09-19T00:37:13.041Z" }, +version = "2025.10.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/f2/97d95db85e11cc85f97581cfc8b4a0405c7fb6099003c23ffaaa0cb4f31d/regex-2025.10.22.tar.gz", hash = "sha256:cc50db098b9d678ace33176a3ab4099616726ae4680fee6ac292302e8950fc4c", size = 400985, upload-time = "2025-10-21T00:48:37.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/42/2904bb22aaaebaa8348673cfbacd704dba2160d847bf17cc6209349a8b7d/regex-2025.10.22-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:afa5307263ef2883cff3c1055a58239d97c28a888b813489b04ff063f64610d6", size = 487959, upload-time = "2025-10-21T00:45:00.385Z" }, + { url = "https://files.pythonhosted.org/packages/28/87/ecc953aec36f3c79585d40d2ce3a90ae28aed434c681cfcbed19ce9b4bba/regex-2025.10.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cfd87258e5879cec2f02907a043d69d72c864723209565ae8cd905a823b94976", size = 290421, upload-time = "2025-10-21T00:45:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/e5/81/aca223093854fb1e385580f6e7ef48fc895ecfe2a8d66133850b8cc12d49/regex-2025.10.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:53a184fa09354b02f18fe3c50de3b809386dbc1bbfa8e51598e300342cde5a11", size = 288284, upload-time = "2025-10-21T00:45:03.587Z" }, + { url = "https://files.pythonhosted.org/packages/42/36/08e03e31cc9dbf5951012a2188d5fd8c79ddc10c2e12849bf434158a1ae3/regex-2025.10.22-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:924a79f8e248271713bc0e1fdd7e48b4632a61152f448e446b8fd724f0715ae8", size = 781457, upload-time = "2025-10-21T00:45:05.105Z" }, + { url = "https://files.pythonhosted.org/packages/af/28/a1e08f43b850948044b3ab3169472c62e0d59be3e47049a27817a8b3c694/regex-2025.10.22-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:84cd327fd1f245e74a6fe0827e2775cd1de83c4a8cbce1da1627d07c233c5f58", size = 850605, upload-time = "2025-10-21T00:45:06.647Z" }, + { url = "https://files.pythonhosted.org/packages/5f/65/d864a9a4a3e0ba4ff3f8798481cc9bdc7304a337c999b69e148d0ad320ff/regex-2025.10.22-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:28c4fcf105ae1a09769110669280a3dfe84b291d856368c8b4d77ccf4345434e", size = 898563, upload-time = "2025-10-21T00:45:08.618Z" }, + { url = "https://files.pythonhosted.org/packages/cc/95/6ae15342e49b9fc1cd8aef350675b3b53446599114c190b3b9df5f4e0bce/regex-2025.10.22-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32f91f414442d0d6fc6e0b7b58e05afd4deed92c852796f3122822f646fc42e", size = 791535, upload-time = "2025-10-21T00:45:09.888Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f9/b557590b7ed1f5b8d2452ba8eda8959c4acacbad4ddd764df32438e74f2d/regex-2025.10.22-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11d2a65fd118c1e409e27dab9aa0a65ebbcab1b836ed441e6e4f78dccc4bd6ef", size = 782461, upload-time = "2025-10-21T00:45:11.636Z" }, + { url = "https://files.pythonhosted.org/packages/94/dd/1cf6bb815f96137f500282ff209c4cfddfaebfe52cf7eb52ce183d389b41/regex-2025.10.22-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7ebde462d55fbbc96d888dad35bd413c8a3d53e3423aa23cc8f01c3398f39148", size = 774582, upload-time = "2025-10-21T00:45:14.192Z" }, + { url = "https://files.pythonhosted.org/packages/03/17/5d6777c93df720c755e4a3b85badaaece51dfe8161cbd1cf70b5a6522a5c/regex-2025.10.22-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1093a856ed0afdcfc89f65c97a143b1593538827701cc6519c6bc0f1c150e5f6", size = 845647, upload-time = "2025-10-21T00:45:15.486Z" }, + { url = "https://files.pythonhosted.org/packages/dd/65/431ae5c24c4db5a26b9d5a4c927381b351c6eaa031b61c91e2ed17857135/regex-2025.10.22-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:716a35741a61333c16d29e544685f3dbfa1df48593ad07e92f77b4a831b4c271", size = 836036, upload-time = "2025-10-21T00:45:16.869Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0e/12c4dce8880364dfb0f31a46ee8dc896805fc6cef473b7491879f30ebd33/regex-2025.10.22-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4782376eb8dbeacaa69b34498e280e8e95947532f8938081e916bbce871bfbab", size = 779705, upload-time = "2025-10-21T00:45:18.472Z" }, + { url = "https://files.pythonhosted.org/packages/1d/6b/cd053d41840fd1e4a2cce4abab07248d4ca70c52ed6555490b56e077920c/regex-2025.10.22-cp310-cp310-win32.whl", hash = "sha256:086cc892b1f8e1d8fe7a060012268a21b96ec25b87b4618c12a853564261f63e", size = 265664, upload-time = "2025-10-21T00:45:20.163Z" }, + { url = "https://files.pythonhosted.org/packages/22/66/557b06253b10ea57198362fb4f6df8860f9d84ee25fcf9a7ca065c9c9984/regex-2025.10.22-cp310-cp310-win_amd64.whl", hash = "sha256:e25f9fb71b775a6d97096cb6c2ac26c675e8c99219afac7f9321f2f4daa46227", size = 277587, upload-time = "2025-10-21T00:45:21.579Z" }, + { url = "https://files.pythonhosted.org/packages/32/44/37a7cbcac47804b4ed34ffb03da494db7eef3992d42d4eb4fa4e0e840a11/regex-2025.10.22-cp310-cp310-win_arm64.whl", hash = "sha256:d0ecea4950b363a9bb1d01c35cff73c0bc762ebdf91109c806ca33a0cbc9ff03", size = 269980, upload-time = "2025-10-21T00:45:22.889Z" }, + { url = "https://files.pythonhosted.org/packages/4e/88/739a7c7dc641976fa3d66c0770f6bb2c6ef5cc3f6b44e039f58bffcfbff3/regex-2025.10.22-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e6b0c007a8b6a9500354eeab8478b18b1cca6ac3fd500f6c3ae017ed617de497", size = 487951, upload-time = "2025-10-21T00:45:24.675Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6f/7157a845b79bfc68560f17268e8b6c2cd5757b5ca396608118a8209c3489/regex-2025.10.22-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:51170deaffec87e48004f9dab53ff0c4db8d10e2ff7630a78467ccd50f656328", size = 290421, upload-time = "2025-10-21T00:45:26.281Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e4/a73127c12d6ed1ee97b81aed80b3a63499e409fe947cfcc491197312ebf0/regex-2025.10.22-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:333afc5e00f43598080ff1d00d5948462905ea514343fbdc5a889e7c3d7c23b6", size = 288282, upload-time = "2025-10-21T00:45:27.988Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/10f1d84cd43ce52257cbc8b4af0e1a7b1b61988ee22e494eda7419702884/regex-2025.10.22-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31221a2a095173e3121842c9f864a5902703dc5ff0d3298c0fe08f9a8a1d80b1", size = 793289, upload-time = "2025-10-21T00:45:30.192Z" }, + { url = "https://files.pythonhosted.org/packages/dd/30/cb4dd079787a76c96acddb15465bc1895ef67a02c4de60890b7b073328ad/regex-2025.10.22-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5de5505e5aac808e2a97515e1d74db99da23259da9dfaf833c1a10f8972d2096", size = 860320, upload-time = "2025-10-21T00:45:31.587Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6f/25fd36431739dce27bdecb7c6a7e215a545a40577e683fc2708fa6235639/regex-2025.10.22-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:809c6f74840f18574da0ce8365d8635f0f1568552363b9a54adf0b41039a4406", size = 907011, upload-time = "2025-10-21T00:45:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/0d/96/67fc321360de627c5406aed97be803240227770a29d09117157d56899c4d/regex-2025.10.22-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bd26a33cad0f24c045fe2d84e70a75f8bd82cb79121382c0ed6c035d247854c", size = 800313, upload-time = "2025-10-21T00:45:34.943Z" }, + { url = "https://files.pythonhosted.org/packages/17/e9/eff1e7cebb027130242b70b2c81a07d9a2d98414c67ea81fac5e32cda8d2/regex-2025.10.22-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:330b0cd6922f93cc0322002467f347b605555a4d64997f3598c06cf8c1303a7f", size = 782837, upload-time = "2025-10-21T00:45:36.335Z" }, + { url = "https://files.pythonhosted.org/packages/a5/64/d9eab04a6f3c043ef5d9cabc94d2d6b522c2bc57e68de8e6f88b080ff66a/regex-2025.10.22-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6763d77bcca503aa1c24b675d05d44c764149f222b7eb6bb3423cebea5eec6e9", size = 854270, upload-time = "2025-10-21T00:45:43.158Z" }, + { url = "https://files.pythonhosted.org/packages/84/8f/a354bf4b41bfa157d731d3628ba677aff7f0c33603939459bba5ba2e4204/regex-2025.10.22-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1eba7681913574c0a8025d435bbc6d10855b273d8f8c0e2d2fc9a981cd05704f", size = 845770, upload-time = "2025-10-21T00:45:44.776Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9e/40a95cc48771d29a55e36d98e34be4f6a8d965fef99dff9056003e32273d/regex-2025.10.22-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:25b80a2ea85f6e06cecf5a3d3a51adb62d19072055bf39d9cabcb29462fffd1d", size = 788777, upload-time = "2025-10-21T00:45:46.551Z" }, + { url = "https://files.pythonhosted.org/packages/68/87/c9d542090675d014d36bece68d48c314a733ad59d3f4999103813a7bb020/regex-2025.10.22-cp311-cp311-win32.whl", hash = "sha256:c4d655be922039bb4ff8fd8363c71bc8da439f7c7260045e4ff10c774e80606b", size = 265667, upload-time = "2025-10-21T00:45:48.211Z" }, + { url = "https://files.pythonhosted.org/packages/47/89/98075b8c5a30b70f156af5caa833f57d0967cb0385fbcc1df37a9a0ca702/regex-2025.10.22-cp311-cp311-win_amd64.whl", hash = "sha256:b7ec554c0ed3aa93e0fb91c436b69654c11ab84a701ae3918dbe8fcd1b73984a", size = 277601, upload-time = "2025-10-21T00:45:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b7/6664611fc6bdd38e8bf773e135954d10c0ee4326099114b0d00a52c85c96/regex-2025.10.22-cp311-cp311-win_arm64.whl", hash = "sha256:c4347ab5146bdd8b27fdb831f8cf882ec0238c7fdb6baddda1344d07ea8245b2", size = 269973, upload-time = "2025-10-21T00:45:51.535Z" }, + { url = "https://files.pythonhosted.org/packages/95/a8/3380a8cb20c255878a9f1165b33c4d6a31d8f5417650c22b73bdcaadd281/regex-2025.10.22-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8b66971471306def7e6baf18ead3f416347d56eb5e295f8a75014d13be92e9fd", size = 489185, upload-time = "2025-10-21T00:45:52.929Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1c/e1eb33fc1f3a7851cc0f53b588790e14edeeb618e80fd5fd7ea987f9957d/regex-2025.10.22-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8c93b179960f4f2f517fe47da9984848d8342a6903b4d24649f4ee9bd22ccd3c", size = 291124, upload-time = "2025-10-21T00:45:54.934Z" }, + { url = "https://files.pythonhosted.org/packages/1b/21/6cc0fe9d4ebd7d6e19c08e77f41082103d52c671eb7eb01cc032e9bccbd4/regex-2025.10.22-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9b4fa8d221b5db3226029978c8c3f66f2e4c6d871e94b726bcd357e746b7a63", size = 288796, upload-time = "2025-10-21T00:45:56.248Z" }, + { url = "https://files.pythonhosted.org/packages/23/b0/d74069acbcc60b54977e693dd673099352b024f7f037cec201b0d96b7d99/regex-2025.10.22-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2a0d4e5f63c8de13fbab94d4a25cc6b02f1007b84e2d4c74f48c242eacb06f1", size = 798441, upload-time = "2025-10-21T00:45:57.896Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f3/69cd09c226ce0fc6a5cf48b5dea716c0139abed41d02fa81fa774e56e713/regex-2025.10.22-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d8df6c82c544eed8314667a1fb8f705a9a802a9d6368045354319588ff56708d", size = 864038, upload-time = "2025-10-21T00:46:00.298Z" }, + { url = "https://files.pythonhosted.org/packages/8e/b0/77bd0e6838f579cc5a02b9e18bc0a759d0ed85b9a8d4d44ad6d3478a40ec/regex-2025.10.22-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a114c2735369334a755a844abd15d5a12716635cc4677fb4e6d793ce369310f6", size = 912054, upload-time = "2025-10-21T00:46:02.358Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/c320c3408050eefa516d352d9e05fd4d6af5da7ec0daea56d1e68bb9096c/regex-2025.10.22-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d53115edada199723b831a49c7e1585ddda7940fb2ba7a78d12bf22e92f23e2", size = 803374, upload-time = "2025-10-21T00:46:03.837Z" }, + { url = "https://files.pythonhosted.org/packages/88/ed/0942c27223ce6bff95087f4859991634d995d6e186807e038fd1c2c3759c/regex-2025.10.22-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b4a7d813fdffe99ae0ecc17c80f652c8946c05a6a090eb2560719d02dfdb4b0", size = 787714, upload-time = "2025-10-21T00:46:05.934Z" }, + { url = "https://files.pythonhosted.org/packages/1c/40/10e2657ed24966742efd68eeb566e26af1eea3925dfe761ce14260a69161/regex-2025.10.22-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:81fb24976e3f71d765edec8a3175abb10359918d8997ca6a756fd68dd3c051f6", size = 858392, upload-time = "2025-10-21T00:46:07.801Z" }, + { url = "https://files.pythonhosted.org/packages/f3/48/bd382281e2f3bcfc2f355b5283ef16d8175b6df4cb6ed532529b715baf07/regex-2025.10.22-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d881e96a443528a83f46ab69714befeb35f4d0caf359c43a606b82cb717a5df9", size = 850482, upload-time = "2025-10-21T00:46:09.893Z" }, + { url = "https://files.pythonhosted.org/packages/2e/5c/fdc0ac5eb3f21a6f19158cce3150e57a65d9770709b8521e09fe9febe813/regex-2025.10.22-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:42abc81ee54e06bef4dbc8e7b8394a57882c718ed3c6aabfea47e429feb94ee9", size = 789633, upload-time = "2025-10-21T00:46:11.687Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ef/c2e63968c9130a17d79431ba8aa98ada02962435436ef506fb4cef139760/regex-2025.10.22-cp312-cp312-win32.whl", hash = "sha256:db30ab87b3d745b7e95e69099e1c4bf544c3f3800b9376b935943e86f650705a", size = 266060, upload-time = "2025-10-21T00:46:13.577Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9d/57bc04978add42a62391f8082e94ec3a8c3448d49e349ede8c2c66ca0a55/regex-2025.10.22-cp312-cp312-win_amd64.whl", hash = "sha256:64190fa0432ed254416898ff3b687648e025445bfa357988f20f1332f651f650", size = 276928, upload-time = "2025-10-21T00:46:15.18Z" }, + { url = "https://files.pythonhosted.org/packages/89/50/760700909a618de1c2405f3a0557a3ec9b4eba516a261aa85fe973d3a354/regex-2025.10.22-cp312-cp312-win_arm64.whl", hash = "sha256:cdfc74d0af9b0cb9bd442619489582b32efc348db651a44967ba5fb71b8d3dee", size = 270103, upload-time = "2025-10-21T00:46:16.903Z" }, + { url = "https://files.pythonhosted.org/packages/c9/25/4c056f41ae981b41e316e44e0ba76efe0b3655c8a070580c3c069765d4e8/regex-2025.10.22-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d49aebe7cb99d80680ff55ff9475bf122c6e3e8a34aec7496aefc90196ac350", size = 488944, upload-time = "2025-10-21T00:46:18.67Z" }, + { url = "https://files.pythonhosted.org/packages/b5/4e/79e7882d35a613517a63d574d80e68c2e8e2d4c67aeaa0c564025cb9e3d6/regex-2025.10.22-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:45367f329e32988d33e5ebdb69b7fb9eb3fc1d9b789b00724e5ddabb75647064", size = 290995, upload-time = "2025-10-21T00:46:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/228d94f8af1da578100822d7a3e8a82dc4f0ffbf07c626293deb0b0aff86/regex-2025.10.22-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fb449bc9d0f379c1064986621e6088a8d28cf628074700c18bd151855f4c9e2f", size = 288686, upload-time = "2025-10-21T00:46:21.769Z" }, + { url = "https://files.pythonhosted.org/packages/be/e9/203bff375a555b79d36fc707ad99584dc8847b4ef5182656a6e156946395/regex-2025.10.22-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154919a381798a7ff07371bff86c6ca4cd9cee6110d163867ff12311ad18d7ac", size = 798465, upload-time = "2025-10-21T00:46:23.55Z" }, + { url = "https://files.pythonhosted.org/packages/fd/31/0660d5bbefcc0ecb0e4f654f69a28a47253da7997ae64fc24e86aff27971/regex-2025.10.22-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:29b4f447d8a514021011d24a50979d5aa1e7d2a99b150eea979221849bd9c77a", size = 863995, upload-time = "2025-10-21T00:46:25.129Z" }, + { url = "https://files.pythonhosted.org/packages/c8/45/a9e1b6fc5b91976ef5b7f456213da52fb4ce24a7846de7d8777a1c305ac5/regex-2025.10.22-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c0bd5398ca8b3f9c1f0d09719c195124e955c4677b55b9d5a728eca5f407eb03", size = 912144, upload-time = "2025-10-21T00:46:26.747Z" }, + { url = "https://files.pythonhosted.org/packages/6b/86/98813e259d8b791891b27c2a6e7ce4fc23bc4222fb46e55f473683ae586e/regex-2025.10.22-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb0fbbd37ae701d12b90bacb03ad36c89b0d2d67eab02b5862ab3e1a50ea49e", size = 803370, upload-time = "2025-10-21T00:46:28.314Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8e/53f27f735368896d777603cf76124b74949ce89123c2c99006834ee29924/regex-2025.10.22-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:419c5fff30240ed10ee55f2d7dd3b54dcc02502568e94be4522b54be63d59aff", size = 787763, upload-time = "2025-10-21T00:46:30.378Z" }, + { url = "https://files.pythonhosted.org/packages/c5/83/2759cdcdff775205871e10db4d1bf09afa7fbb55af850c5cfb0e9e699090/regex-2025.10.22-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b71b5c4a00467304ebfae0235b763129af2de074b02e78e959d8990c553c0a6e", size = 858336, upload-time = "2025-10-21T00:46:32.287Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b5/6fe37d832e1e2cb4e82c444844e1eca88de9171d766f2f9cbe308409a2d8/regex-2025.10.22-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa800228137127de4cce1875f0ddeb4ce19d33fd0ac6450c3b00b942866748e7", size = 850401, upload-time = "2025-10-21T00:46:34.275Z" }, + { url = "https://files.pythonhosted.org/packages/30/57/b9c2b316a87dad82a8845b1854be743441ef375774497f11f13658d016b7/regex-2025.10.22-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:44c8c46b7160260e0cd8b0f7c20ff6269976278d8187646d3e741d8dfe5fcdbc", size = 789738, upload-time = "2025-10-21T00:46:36.421Z" }, + { url = "https://files.pythonhosted.org/packages/d1/5f/e8bb23662647d519d1ea24f9b30d19c291237aea721662b3d563af6326df/regex-2025.10.22-cp313-cp313-win32.whl", hash = "sha256:701c53e8cb0c73c39d72dc4be71ee88478904b4066bd31f95e2b6fdfac49102e", size = 266055, upload-time = "2025-10-21T00:46:38.062Z" }, + { url = "https://files.pythonhosted.org/packages/d9/12/035e5c09d1c5e64a640b3c0b2e4b01580e8a36cf0abb99d978422601158d/regex-2025.10.22-cp313-cp313-win_amd64.whl", hash = "sha256:4a3a6320015223d0a14fdc2706e65ca64e7e3d97016acef1349a39c3a0bbbd81", size = 276919, upload-time = "2025-10-21T00:46:39.636Z" }, + { url = "https://files.pythonhosted.org/packages/be/d3/44dfed03966d26942c53597951035cece3ecf4cb56945ee0bf15014ff092/regex-2025.10.22-cp313-cp313-win_arm64.whl", hash = "sha256:dbb3eb2433ad2158e9719369ea2184329145f50ffae2e6328985fc0de6a71984", size = 270104, upload-time = "2025-10-21T00:46:41.349Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b9/ccd603c3ad0eead387eaa79203eca0c6846e065e10cb30a717ce2813a878/regex-2025.10.22-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3fcce0c2b0b7a8f4a029154d7ae9040d2ff5bed77085cd3bf9a56b61a8cda009", size = 491846, upload-time = "2025-10-21T00:46:43.097Z" }, + { url = "https://files.pythonhosted.org/packages/06/f4/e96216c9faf36fbf42474702afe6efdaecf5b9e5fbce0a77ead5f00191d8/regex-2025.10.22-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46338f1390c9ddf6c163949cd53558a89ab7c7edbb4713b9d2b7cdf71c87a75a", size = 292541, upload-time = "2025-10-21T00:46:44.996Z" }, + { url = "https://files.pythonhosted.org/packages/08/19/26b9fbd2daac8e783d3f008e5e18e99c9f31c880c9ba644511e3107e2f86/regex-2025.10.22-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ca58844dc33b4297ae24505db9528be6862a8b2b961f60f6acc0869ea1291d1a", size = 290899, upload-time = "2025-10-21T00:46:46.564Z" }, + { url = "https://files.pythonhosted.org/packages/9b/43/cd1512382caedfdb2f663948485ab001cb073631a0d94706db524385eaf5/regex-2025.10.22-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c4d54ae939c325b8027277f998cc7dd175447745bd12d6a93c09ebebda1226a", size = 807309, upload-time = "2025-10-21T00:46:48.408Z" }, + { url = "https://files.pythonhosted.org/packages/13/69/6aaa805ed5b53a1a3d6115691745cfd20370f3dddc027f4fcdb8cb050251/regex-2025.10.22-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8c311ee233a59483d6e3b78d669981f387ca2ce162b029895bddb74cbc37e53", size = 873241, upload-time = "2025-10-21T00:46:50.056Z" }, + { url = "https://files.pythonhosted.org/packages/75/21/224fe5b25fff1c6ac921246e51603785e688fc8e0d23dabc77d7e62b1b6b/regex-2025.10.22-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64fc5557f8798a6ac439cabb80ea28c97e509e03ed1a1b23e16f6f7f95ee53fc", size = 914793, upload-time = "2025-10-21T00:46:51.648Z" }, + { url = "https://files.pythonhosted.org/packages/15/56/9349b5a283b3b05387ecd147962880ef1532827c073d5caf0d291048aaea/regex-2025.10.22-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7957cab18a1148752372bd6acf23ecc54785d13439ef14024134d37e51e9b77", size = 812580, upload-time = "2025-10-21T00:46:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/39/71/450cb85d91bc3c6e01589caa6de4b28445ae77fb8915895d9427996926d7/regex-2025.10.22-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9adaf0a0cefd826192045946bb8922e19d321934fa661efa3744d0aea130b667", size = 795344, upload-time = "2025-10-21T00:46:55.312Z" }, + { url = "https://files.pythonhosted.org/packages/75/b3/f8e6f2651a22662b00005f0b26f53438b89b33159469e8a279a07b9d951a/regex-2025.10.22-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:61e564ff5eb999e2ccf8311d7cb61ecb24c502ee5116b181b0348b4d882de480", size = 868213, upload-time = "2025-10-21T00:46:57.255Z" }, + { url = "https://files.pythonhosted.org/packages/37/aa/9dfa760dd368f2a9bc01d1a50edbc838b5ce330ca4142149420acde6d13d/regex-2025.10.22-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:1aa9a1ec0ab3f10210626795bcfe84b0ac20490d085ea4d7628fe381a98592be", size = 854538, upload-time = "2025-10-21T00:46:58.992Z" }, + { url = "https://files.pythonhosted.org/packages/55/62/e3ef2330f1b2e63fb1e096a53d3335a2dea5e77364cf8a17341e8acb24f1/regex-2025.10.22-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ffe59e0b0d93cf4999565236b5a36a7d22b10f5f7fed59f423bd5f7542453832", size = 799346, upload-time = "2025-10-21T00:47:00.738Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/ae3de5c8a26394be05ad1e2b252dd82425ab72ff7f4e79b03f8a431ecbfa/regex-2025.10.22-cp313-cp313t-win32.whl", hash = "sha256:36ba31e30b9c74a536a08635ca12cb0588ce39298b2cd7904194c2227c284d88", size = 268657, upload-time = "2025-10-21T00:47:02.958Z" }, + { url = "https://files.pythonhosted.org/packages/4e/1a/d6673cb4f28a368d51316b67c1067a246651731c8fbff50e99060b8ed483/regex-2025.10.22-cp313-cp313t-win_amd64.whl", hash = "sha256:d7d9992c44a5186c6539f9717b6a6e639d4f57f919d238e660f4ce42a22f0ced", size = 280076, upload-time = "2025-10-21T00:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/26/40/30702d35b888a6cc1a290ec6b244109f827eddedb61af77b42c6c5f63928/regex-2025.10.22-cp313-cp313t-win_arm64.whl", hash = "sha256:28ce6c33b836c63ef0a4ec137fd0f136627b71075a5cfffb8c5aaef8ce4535b6", size = 271219, upload-time = "2025-10-21T00:47:06.678Z" }, ] [[package]] @@ -6672,15 +6694,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.42.0" +version = "2.42.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "urllib3", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/b2/7481156cf42b7f66cffb371e504b7ace12b4f016b8872ffcf0873ae9534b/sentry_sdk-2.42.0.tar.gz", hash = "sha256:91c69c9372fb5fb4df0ac39456ccf7286f0428b3ee1cdd389f9dd36c04e0f5c9", size = 351242, upload-time = "2025-10-15T07:41:15.577Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/04/ec8c1dd9250847303d98516e917978cb1c7083024770d86d657d2ccb5a70/sentry_sdk-2.42.1.tar.gz", hash = "sha256:8598cc6edcfe74cb8074ba6a7c15338cdee93d63d3eb9b9943b4b568354ad5b6", size = 354839, upload-time = "2025-10-20T12:38:40.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/58/4a/9810a246ec5d1df2ae066efefeecfa91d3c548fa2bd5390184e016112887/sentry_sdk-2.42.0-py2.py3-none-any.whl", hash = "sha256:1a7986e638306ff158f52dd47d9480a4055e6c289388caa90628acb2563fe7bd", size = 379496, upload-time = "2025-10-15T07:41:13.802Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cb/c21b96ff379923310b4fb2c06e8d560d801e24aeb300faa72a04776868fc/sentry_sdk-2.42.1-py2.py3-none-any.whl", hash = "sha256:f8716b50c927d3beb41bc88439dc6bcd872237b596df5b14613e2ade104aee02", size = 380952, upload-time = "2025-10-20T12:38:38.88Z" }, ] [[package]] @@ -6764,14 +6786,14 @@ wheels = [ [[package]] name = "smart-open" -version = "7.3.1" +version = "7.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "wrapt" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/be/bf2d60280a9d7fac98ece2150a22538fa4332cda67d04d9618c8406f791e/smart_open-7.3.1.tar.gz", hash = "sha256:b33fee8dffd206f189d5e704106a8723afb4210d2ff47e0e1f7fbe436187a990", size = 51405, upload-time = "2025-09-08T10:03:53.726Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/d9/03809d3d403a8f804d5d990e55175fa12e4a001b51e60291e761ff6da34b/smart_open-7.4.0.tar.gz", hash = "sha256:cb989a38db774c5aadc416020dff21da6cea11059309f50e237d6520d6636247", size = 53185, upload-time = "2025-10-20T11:56:42.87Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/d9/460cf1d58945dd771c228c29d5664f431dfc4060d3d092fed40546b11472/smart_open-7.3.1-py3-none-any.whl", hash = "sha256:e243b2e7f69d6c0c96dd763d6fbbedbb4e0e4fc6d74aa007acc5b018d523858c", size = 61722, upload-time = "2025-09-08T10:03:52.02Z" }, + { url = "https://files.pythonhosted.org/packages/ea/c3/9eda530579efb091f546f1f02c7010e47b675769c381c4cdab68bbdb848d/smart_open-7.4.0-py3-none-any.whl", hash = "sha256:ff75c536bc3c534fca8e8fbebe1f0e22cafecc1a398d096924720c995d71f34a", size = 63211, upload-time = "2025-10-20T11:56:41.326Z" }, ] [package.optional-dependencies] @@ -7409,7 +7431,7 @@ wheels = [ [[package]] name = "typer" -version = "0.19.2" +version = "0.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -7417,9 +7439,9 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/ca/950278884e2ca20547ff3eb109478c6baf6b8cf219318e6bc4f666fad8e8/typer-0.19.2.tar.gz", hash = "sha256:9ad824308ded0ad06cc716434705f691d4ee0bfd0fb081839d2e426860e7fdca", size = 104755, upload-time = "2025-09-23T09:47:48.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/28/7c85c8032b91dbe79725b6f17d2fffc595dff06a35c7a30a37bef73a1ab4/typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37", size = 106492, upload-time = "2025-10-20T17:03:49.445Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/22/35617eee79080a5d071d0f14ad698d325ee6b3bf824fc0467c03b30e7fa8/typer-0.19.2-py3-none-any.whl", hash = "sha256:755e7e19670ffad8283db353267cb81ef252f595aa6834a0d1ca9312d9326cb9", size = 46748, upload-time = "2025-09-23T09:47:46.777Z" }, + { url = "https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a", size = 47028, upload-time = "2025-10-20T17:03:47.617Z" }, ] [[package]]