diff --git a/.github/workflows/build-wheels.yml b/.github/workflows/build-wheels.yml index dd694e3..73d5e8f 100644 --- a/.github/workflows/build-wheels.yml +++ b/.github/workflows/build-wheels.yml @@ -67,6 +67,7 @@ jobs: extras: "uv" env: CIBW_ARCHS: ${{ matrix.cibw_arch }} + CIBW_SKIP: "cp313t-*" - uses: actions/upload-artifact@v4 with: @@ -80,7 +81,7 @@ jobs: - uses: actions/checkout@v4 - name: Install uv and set the Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.3.2 with: python-version: 3.12 enable-cache: true @@ -113,7 +114,7 @@ jobs: run: ls -l dist - name: Install uv and set the Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.3.2 with: python-version: 3.12 # No caching needed for publish step diff --git a/.github/workflows/unittests.yml b/.github/workflows/unittests.yml index 9ee0dd8..01bf9e3 100644 --- a/.github/workflows/unittests.yml +++ b/.github/workflows/unittests.yml @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Cargo registry cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cargo/registry key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} @@ -47,7 +47,7 @@ jobs: ${{ runner.os }}-cargo-registry- - name: Set up Cargo build cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: target key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} @@ -55,8 +55,9 @@ jobs: ${{ runner.os }}-cargo-build- - name: Install uv and set the Python version - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v8.3.2 with: + version: "latest" python-version: ${{ matrix.python-version }} enable-cache: true @@ -106,4 +107,4 @@ jobs: FAST_MSSQL_TEST_DB_PASSWORD: "YourStrong@Password" FAST_MSSQL_TEST_SERVER: "localhost" FAST_MSSQL_TEST_PORT: "1433" - FAST_MSSQL_TEST_DATABASE: "master" + FAST_MSSQL_TEST_DATABASE: "master" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 6597d84..39b991f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,9 +126,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "cc" @@ -162,9 +162,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -294,7 +294,7 @@ dependencies = [ [[package]] name = "fastmssql_core" -version = "0.7.3" +version = "0.7.4" dependencies = [ "ahash", "bb8", @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.28.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" +checksum = "cd274650b21d4bfc26a0a47587962c1edb425f69287324355cd040c3ea66071c" dependencies = [ "chrono", "libc", @@ -930,9 +930,9 @@ dependencies = [ [[package]] name = "pyo3-async-runtimes" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7364a95bf00e8377bbf9b0f09d7ff9715a29d8fcf93b47d1a967363b973178" +checksum = "b3ef68daa7316a3fac65e5e18b2203f010346de1c1c53456811a2624673ab046" dependencies = [ "futures-channel", "futures-util", @@ -944,18 +944,18 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.28.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" +checksum = "c5e2a7d2f0d013342f295c048ad19237add5154a55b1c5a254c0ec93d4109078" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.28.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" +checksum = "ca85c467da1bbc8d866eea5deff9cf29ea5f7785054a17da36e65bda9c05845b" dependencies = [ "libc", "pyo3-build-config", @@ -963,9 +963,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.28.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" +checksum = "9ac53762fd065daa3194dd09337a38bd793a188100fd1a9304c4ab312d901771" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -975,13 +975,12 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.28.3" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" +checksum = "4ca3a1557399783172dc5bf39cfca835157732532cba56b71d2292161e53b362" dependencies = [ "heck", "proc-macro2", - "pyo3-build-config", "quote", "syn", ] @@ -1008,9 +1007,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "aws-lc-rs", "bytes", @@ -1098,9 +1097,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64 0.22.1", "bytes", @@ -1453,9 +1452,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" @@ -1787,9 +1786,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 3eeb2b1..1e57739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fastmssql_core" -version = "0.7.3" +version = "0.7.4" edition = "2024" [lib] @@ -27,24 +27,24 @@ debug = true # Keep debug info for development opt-level = 2 [dependencies] -pyo3 = { version = "0.28.3", features = ["extension-module", "abi3-py311", "chrono"] } -pyo3-async-runtimes = { version = "0.28.0", features = ["tokio", "tokio-runtime"] } +pyo3 = { version = "0.29.0", features = ["extension-module", "abi3-py311", "chrono"] } +pyo3-async-runtimes = { version = "0.29.0", features = ["tokio", "tokio-runtime"] } tokio = { version = "1.52.3", features = ["rt-multi-thread", "net", "sync", "time", "io-util", "process"] } tokio-util = { version = "0.7.18", features = ["compat"] } tiberius = { version = "0.12.3", features = ["chrono", "tds73", "rustls"], default-features = false } bb8 = "0.9.1" -chrono = { version = "0.4.44" } -uuid = { version = "1.23.1" } +chrono = { version = "0.4.45" } +uuid = { version = "1.23.4" } ahash = "0.8.12" # Faster hashing algorithm (used in optimized_types.rs) -smallvec = "1.15.1" # Stack-allocated vectors for small collections +smallvec = "1.15.2" # Stack-allocated vectors for small collections mimalloc = { version = "0.1.52", default-features = false } # Microsoft's fast memory allocator slab = "0.4.12" # Pinned sub-dependency due to CVE-2025-55159 -bytes = "1.11.1" # Pinned sub-dependency due to CVE-2026-25541 -quinn-proto = "0.11.14" # Pinned sub-dependency due to CVE-2026-31812 +bytes = "1.12.0" # Pinned sub-dependency due to CVE-2026-25541 +quinn-proto = "0.11.15" # Pinned sub-dependency due to CVE-2026-31812 # Azure authentication dependencies -reqwest = { version = "0.13.3", features = ["json", "form", "rustls"], default-features = false } +reqwest = { version = "0.13.4", features = ["json", "form", "rustls"], default-features = false } serde_json = "1.0.150" diff --git a/README.md b/README.md index e780ca3..fa66437 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ Great for data ingestion, bulk inserts, and large-scale query workloads. - Thread‑safe: safe to use in concurrent apps - Cross‑platform: Windows, macOS, Linux - Batch operations: high-performance bulk inserts and batch query execution -- Apache Arrow support ## Installation @@ -38,14 +37,6 @@ Great for data ingestion, bulk inserts, and large-scale query workloads. pip install fastmssql ``` -### Optional dependencies - -**Apache Arrow support** (for `to_arrow()` method): - -```bash -pip install fastmssql[arrow] -``` - ### Prerequisites - Python 3.11 to 3.14 @@ -352,42 +343,6 @@ async def main_fetching(): asyncio.run(main_fetching()) ``` -### Apache Arrow - -Convert query results to Apache Arrow tables for efficient bulk data processing and interoperability with data science tools: - -```python -import asyncio -from fastmssql import Connection - -async def main(): - conn_str = "Server=localhost;Database=master;User Id=myuser;Password=mypass" - async with Connection(conn_str) as conn: - # Execute query and convert to Arrow - result = await conn.query("SELECT id, name, salary FROM employees") - arrow_table = result.to_arrow() - - # Arrow Table enables: - # - Efficient columnar storage and compute - # - Integration with Pandas, DuckDB, Polars - # - Parquet/ORC serialization - df = arrow_table.to_pandas() # Convert to pandas DataFrame - print(df) - - # Write to Parquet for long-term storage - import pyarrow.parquet as pq - pq.write_table(arrow_table, "employees.parquet") - - # Or use with DuckDB for analytical queries - import duckdb - result = duckdb.from_arrow(arrow_table).filter("salary > 50000").execute() - print(result.fetchall()) -``` - -**Requirements**: Install PyArrow with `pip install pyarrow` - -Note: Results are converted eagerly into Arrow arrays. For very large datasets, consider chunking queries or using iteration-based processing instead. - ### Connection pooling Tune the pool to fit your workload. Constructor signature: diff --git a/pyproject.toml b/pyproject.toml index a09781d..bd964df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "fastmssql" -version = "0.7.3" +version = "0.7.4" description = "A high-performance async Python library for Microsoft SQL Server built on Rust for heavy workloads and low latency." readme = "README.md" @@ -69,14 +69,14 @@ Repository = "https://github.com/Rivendael/FastMssql" "Bug Tracker" = "https://github.com/Rivendael/FastMssql/issues" Documentation = "https://github.com/Rivendael/FastMssql#readme" -[project.optional-dependencies] -arrow = ["pyarrow>=13.0.0"] - [tool.maturin] features = ["pyo3/abi3-py311"] module-name = "fastmssql" python-source = "python" -include = ["fastmssql.pyi"] +include = [ + "python/fastmssql/*.pyi", + "python/fastmssql/py.typed", +] [tool.pytest.ini_options] minversion = "6.0" @@ -109,9 +109,6 @@ filterwarnings = [ "ignore::PendingDeprecationWarning", ] -[tool.uv] -required-version = ">=0.9.7" - [tool.cibuildwheel] build-frontend = "build[uv]" build = "cp311-* cp312-* cp313-* cp313t-* cp314-* cp314t-*" @@ -142,18 +139,16 @@ MACOSX_DEPLOYMENT_TARGET = "10.15" [dependency-groups] dev = [ - "ruff>=0.15.11", - "pytest==9.0.3", - "pytest-asyncio==1.3.0", + "ruff>=0.15.20", + "pytest==9.1.1", + "pytest-asyncio==1.4.0", "pytest-timeout==2.4.0", "psutil==7.2.2", # For memory and process monitoring in stress tests "pytest-xdist==3.8.0", - "maturin==1.13.1", + "maturin==1.14.1", "python-dotenv==1.2.2", - "cibuildwheel[uv]>=3.4.1", + "cibuildwheel[uv]>=4.1.0", "wheel==0.47.0", - "filelock==3.29.0", - "pyarrow==23.0.1", + "filelock==3.29.5", "uvloop==0.22.1" - ] diff --git a/fastmssql.pyi b/python/fastmssql/fastmssql.pyi similarity index 96% rename from fastmssql.pyi rename to python/fastmssql/fastmssql.pyi index 650920a..b56b5e4 100644 --- a/fastmssql.pyi +++ b/python/fastmssql/fastmssql.pyi @@ -9,11 +9,9 @@ High-performance Rust-backed Python driver for SQL Server with: - Memory-efficient result handling """ -from typing import Any, Coroutine, Dict, List, Optional, Tuple, TYPE_CHECKING +from typing import Any, Coroutine, Dict, List, Optional, Tuple from enum import StrEnum - -if TYPE_CHECKING: - import pyarrow +from .fastmssql import _RustConnection, _RustTransaction class PoolConfig: """ @@ -126,6 +124,7 @@ class SqlError(Exception): except SqlError as e: print(e.code, e.message, e.state) """ + code: int message: str state: int @@ -140,6 +139,7 @@ class SqlConnectionError(Exception): host: Redirect target host for routing errors, if available. port: Redirect target port for routing errors, if available. """ + message: Optional[str] host: Optional[str] port: Optional[int] @@ -152,6 +152,7 @@ class TlsError(Exception): Attributes: message: Human-readable error description. """ + message: str ... @@ -162,6 +163,7 @@ class ProtocolError(Exception): Attributes: message: Human-readable error description. """ + message: str ... @@ -172,6 +174,7 @@ class ConversionError(Exception): Attributes: message: Human-readable error description. """ + message: str ... @@ -359,26 +362,6 @@ class QueryStream: """ ... - def to_arrow(self) -> pyarrow.Table: - """ - Convert query results to Apache Arrow Table. - - Converts all rows to an Apache Arrow Table with column-oriented storage, - enabling efficient bulk data processing and integration with data analysis tools. - - Returns: - pyarrow.Table: Column-oriented result representation with schema metadata - - Raises: - ImportError: If PyArrow is not installed - RuntimeError: If conversion fails (empty results from Tiberius may lack schema info) - - Note: - All rows are converted eagerly into Arrow arrays and loaded into memory. - For very large result sets, consider processing in batches. - """ - ... - class Parameter: """ Parameter object for SQL queries with optional type hints. @@ -489,6 +472,7 @@ class Parameters: class AzureCredentialType(StrEnum): """Azure credential type constants for authentication.""" + SERVICE_PRINCIPAL: str = "ServicePrincipal" MANAGED_IDENTITY: str = "ManagedIdentity" ACCESS_TOKEN: str = "AccessToken" @@ -510,9 +494,7 @@ class AzureCredential: @staticmethod def service_principal( - client_id: str, - client_secret: str, - tenant_id: str + client_id: str, client_secret: str, tenant_id: str ) -> AzureCredential: """ Create Azure credential for Service Principal authentication. @@ -763,7 +745,7 @@ class Connection: """ ... - async def __aenter__(self) -> Connection: + async def __aenter__(self) -> _RustConnection: """Async context manager entry (initializes pool).""" ... @@ -894,7 +876,7 @@ class Transaction: """Return True if the underlying connection is currently established.""" ... - async def __aenter__(self) -> Transaction: + async def __aenter__(self) -> _RustTransaction: """Async context manager entry (begins transaction).""" ... @@ -905,7 +887,7 @@ class Transaction: class TypedNull(StrEnum): """Class to store a typed null value - This is required as some SQL Server features such as stored procedures etc. sometimes require type information for which is + This is required as some SQL Server features such as stored procedures etc. sometimes require type information for which is not possible for nulls when just using `None`. In such cases, SQL Server will complain about being unable to cast 'tinyint' to the desired data type. diff --git a/python/fastmssql/py.typed b/python/fastmssql/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/arrow_conversion.rs b/src/arrow_conversion.rs deleted file mode 100644 index 4c196bb..0000000 --- a/src/arrow_conversion.rs +++ /dev/null @@ -1,271 +0,0 @@ -use crate::type_mapping; -use crate::types::ColumnInfo; -use pyo3::prelude::*; -use pyo3::types::PyDict; -use std::sync::Arc; -use tiberius::ColumnType; - -/// Cache of frequently-used Arrow type objects to avoid repeated PyArrow imports and object creation -struct ArrowTypeCache { - int64: Py, - float64: Py, - string: Py, - bool_: Py, - binary: Py, - null: Py, - date32: Py, - /// Reference to pyarrow module for creating complex types (Decimal, Timestamp, etc.) - pyarrow: Py, -} - -impl ArrowTypeCache { - fn new(py: Python) -> PyResult { - let pyarrow = py.import("pyarrow")?; - Ok(ArrowTypeCache { - int64: pyarrow.getattr("int64")?.call0()?.unbind(), - float64: pyarrow.getattr("float64")?.call0()?.unbind(), - string: pyarrow.getattr("string")?.call0()?.unbind(), - bool_: pyarrow.getattr("bool_")?.call0()?.unbind(), - binary: pyarrow.getattr("binary")?.call0()?.unbind(), - null: pyarrow.getattr("null")?.call0()?.unbind(), - date32: pyarrow.getattr("date32")?.call0()?.unbind(), - pyarrow: pyarrow.unbind(), - }) - } -} - -/// Maps SQL column types to corresponding Arrow types, using a shared cache to minimize imports. -/// This avoids repeated PyArrow module imports and provides consistent type mapping. -fn get_arrow_type_with_cache( - col_type: ColumnType, - cache: &ArrowTypeCache, - py: Python, -) -> PyResult> { - let arrow_type = match col_type { - // Integer types: all mapped to int64 for consistency - ColumnType::Int1 - | ColumnType::Int2 - | ColumnType::Int4 - | ColumnType::Int8 - | ColumnType::Intn => cache.int64.clone_ref(py), - - // Floating point types - ColumnType::Float4 | ColumnType::Float8 | ColumnType::Floatn => cache.float64.clone_ref(py), - - // String types - ColumnType::NVarchar - | ColumnType::NChar - | ColumnType::BigVarChar - | ColumnType::BigChar - | ColumnType::Text - | ColumnType::NText - | ColumnType::Guid - | ColumnType::Xml => cache.string.clone_ref(py), - - // Boolean type - ColumnType::Bit | ColumnType::Bitn => cache.bool_.clone_ref(py), - - // Money types: decimal128 with precision 38, scale 4 - ColumnType::Money | ColumnType::Money4 => { - let pyarrow = cache.pyarrow.bind(py); - let args = pyo3::types::PyTuple::new(py, &[38i32, 4i32])?; - let decimal_method = pyarrow.getattr("decimal128")?; - decimal_method.call1(args)?.unbind() - } - - // Decimal/Numeric types: decimal128 with precision 38, scale 10 - ColumnType::Decimaln | ColumnType::Numericn => { - let pyarrow = cache.pyarrow.bind(py); - let args = pyo3::types::PyTuple::new(py, &[38i32, 10i32])?; - let decimal_method = pyarrow.getattr("decimal128")?; - decimal_method.call1(args)?.unbind() - } - - // DateTime types: timestamp with microsecond precision - ColumnType::Datetime - | ColumnType::Datetimen - | ColumnType::Datetime2 - | ColumnType::Datetime4 - | ColumnType::DatetimeOffsetn => { - let pyarrow = cache.pyarrow.bind(py); - let timestamp_method = pyarrow.getattr("timestamp")?; - timestamp_method.call1(("us",))?.unbind() - } - - // Date type: date32 (days since UNIX epoch) - ColumnType::Daten => cache.date32.clone_ref(py), - - // Time type: time64 with microsecond precision - ColumnType::Timen => { - let pyarrow = cache.pyarrow.bind(py); - let time_method = pyarrow.getattr("time64")?; - time_method.call1(("us",))?.unbind() - } - - // Binary types - ColumnType::BigVarBin | ColumnType::BigBinary | ColumnType::Image => { - cache.binary.clone_ref(py) - } - - // Special types: stored as binary - ColumnType::SSVariant | ColumnType::Udt => cache.binary.clone_ref(py), - - // Null type - ColumnType::Null => cache.null.clone_ref(py), - }; - - Ok(arrow_type) -} - -/// Converts SQL query results into Arrow arrays, one per column. -/// -/// This function handles the critical conversion of tiberius Row data to PyArrow arrays. -/// Key optimizations: -/// - ArrowTypeCache reused across all columns (single pyarrow import) -/// - Pre-allocated vectors sized to row count -/// - Type casting explicitly used only for Money, Decimal, and DateTime (types requiring schema info) -/// - Clear error messages showing problematic column for debugging -/// -/// # Arguments: -/// - `rows`: Raw SQL result rows -/// - `column_info`: Column metadata (names, types) -/// - `py`: Python interpreter context -/// -/// # Returns: Vector of PyArrow arrays (one per column), suitable for `arrow_arrays_to_pyarrow_table()` -pub fn build_arrow_columns( - rows: &[Option], - column_info: &Arc, - py: Python, -) -> PyResult>> { - let pyarrow = py.import("pyarrow")?; - let array_class = pyarrow.getattr("array")?; - let cache = ArrowTypeCache::new(py)?; - - let num_columns = column_info.names.len(); - let num_rows = rows.len(); - let mut columns = Vec::with_capacity(num_columns); - - // Handle empty result sets: create properly-typed empty arrays - if num_rows == 0 { - for (col_idx, col_type) in column_info.column_types.iter().enumerate() { - let arrow_type = get_arrow_type_with_cache(*col_type, &cache, py).map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!( - "Failed to determine Arrow type for column {}: {}", - col_idx, e - )) - })?; - let empty_list = pyo3::types::PyList::empty(py); - let array = array_class - .call((empty_list, arrow_type), None) - .map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!( - "Failed to create empty Array for column {}: {}", - col_idx, e - )) - })?; - columns.push(array.unbind()); - } - return Ok(columns); - } - - // Process each column - for col_idx in 0..num_columns { - let col_type = column_info.column_types[col_idx]; - let mut column_values = Vec::with_capacity(num_rows); - - // Convert values for this column across all rows - for (row_idx, row_opt) in rows.iter().enumerate() { - let value = if let Some(row) = row_opt { - type_mapping::sql_to_python(row, col_idx, col_type, py).map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!( - "Column '{}' (idx {}), Row {}: {}", - &column_info.names[col_idx], col_idx, row_idx, e - )) - })? - } else { - py.None() - }; - column_values.push(value); - } - - let py_list = pyo3::types::PyList::new(py, &column_values).map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!( - "Failed to create Python list for column {}: {}", - col_idx, e - )) - })?; - let arrow_type = get_arrow_type_with_cache(col_type, &cache, py).map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!( - "Failed to determine Arrow type for column {}: {}", - col_idx, e - )) - })?; - - // Use explicit type for complex types that require schema specification. - // PyArrow can infer types for simple types (int, float, string, bool) but needs - // explicit schemas for Decimal, Timestamp, and similar types to ensure correct precision/scale. - let array = match col_type { - ColumnType::Money - | ColumnType::Money4 - | ColumnType::Decimaln - | ColumnType::Numericn - | ColumnType::Datetime - | ColumnType::Datetimen - | ColumnType::Datetime2 - | ColumnType::Datetime4 - | ColumnType::DatetimeOffsetn => { - // Explicit type required for proper schema specification - array_class.call((py_list, &arrow_type), None) - } - _ => { - // PyArrow can reliably infer types for these - array_class.call1((py_list,)) - } - } - .map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!( - "Failed to create Arrow array for column {}: {}", - col_idx, e - )) - })?; - - columns.push(array.unbind()); - } - - Ok(columns) -} - -/// Constructs a PyArrow Table from pre-built columns and names. -/// -/// PyArrow Table construction maps column names to Arrow arrays. This function -/// handles the final assembly step after column conversion. -/// -/// # Arguments: -/// - `column_names`: Ordered list of column names from query results -/// - `arrays`: Arrow arrays (one per column, order must match names) -/// - `py`: Python interpreter context -/// -/// # Returns: A PyArrow Table object suitable for use in Python -pub fn arrow_arrays_to_pyarrow_table( - column_names: &[String], - arrays: Vec>, - py: Python, -) -> PyResult> { - let pyarrow = py.import("pyarrow")?; - let table_class = pyarrow.getattr("table")?; - - let dict = PyDict::new(py); - for (name, array) in column_names.iter().zip(arrays.iter()) { - dict.set_item(name, array).map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!( - "Failed to add column '{}' to PyArrow table: {}", - name, e - )) - })?; - } - - let table = table_class.call1((dict,)).map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!("Failed to create PyArrow Table: {}", e)) - })?; - Ok(table.unbind()) -} diff --git a/src/lib.rs b/src/lib.rs index 53fa76f..ab3ca91 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,6 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; use pyo3::prelude::*; -mod arrow_conversion; mod azure_auth; mod batch; mod connection; diff --git a/src/types.rs b/src/types.rs index 8e127c6..8660f9d 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,9 +1,9 @@ use crate::type_mapping; use ahash::AHashMap as HashMap; use pyo3::exceptions::{PyException, PyRuntimeError}; -use pyo3::{create_exception, exceptions::PyValueError}; use pyo3::prelude::*; use pyo3::types::PyDict; +use pyo3::{create_exception, exceptions::PyValueError}; use std::sync::Arc; use tiberius::{ColumnType, Row, error::Error as TError}; @@ -30,20 +30,16 @@ pub fn create_sql_error(err: TError, base: &'static str) -> PyErr { exc }) } - TError::Io { kind: _, message } => { - Python::attach(|py| { - let exc = SqlConnectionError::new_err(format!("{base}: {message}")); - let _ = exc.value(py).setattr("message", message.as_str()); - exc - }) - } - TError::Tls(msg) => { - Python::attach(|py| { - let exc = TlsError::new_err(format!("{base}: {msg}")); - let _ = exc.value(py).setattr("message", msg.as_str()); - exc - }) - } + TError::Io { kind: _, message } => Python::attach(|py| { + let exc = SqlConnectionError::new_err(format!("{base}: {message}")); + let _ = exc.value(py).setattr("message", message.as_str()); + exc + }), + TError::Tls(msg) => Python::attach(|py| { + let exc = TlsError::new_err(format!("{base}: {msg}")); + let _ = exc.value(py).setattr("message", msg.as_str()); + exc + }), TError::Routing { host, port } => { let message = format!("server redirected to {host}:{port}"); Python::attach(|py| { @@ -81,7 +77,7 @@ pub fn create_sql_error(err: TError, base: &'static str) -> PyErr { exc }) } - _ => PyRuntimeError::new_err(format!("{base}: {err}")) + _ => PyRuntimeError::new_err(format!("{base}: {err}")), } } @@ -466,18 +462,6 @@ impl PyQueryStream { pub fn fetchall(&mut self, py: Python<'_>) -> PyResult> { self.all(py) } - - /// Convert query results to an Apache Arrow Table - pub fn to_arrow(&self, py: Python<'_>) -> PyResult> { - let column_info = self - .column_info - .as_ref() - .ok_or_else(|| PyValueError::new_err("No column information available"))?; - - let arrays = crate::arrow_conversion::build_arrow_columns(&self.tiberius_rows, column_info, py)?; - - crate::arrow_conversion::arrow_arrays_to_pyarrow_table(&column_info.names, arrays, py) - } } impl PyQueryStream { diff --git a/tests/test_arrow_support.py b/tests/test_arrow_support.py deleted file mode 100644 index b8fd8d4..0000000 --- a/tests/test_arrow_support.py +++ /dev/null @@ -1,530 +0,0 @@ -import pytest -from conftest import Config - -try: - import pyarrow as pa -except ImportError: - pa = None - -try: - from fastmssql import Connection -except ImportError: - pytest.fail("fastmssql not available - run 'maturin develop' first") - - -# Skip all Arrow tests if PyArrow is not available -pytestmark = pytest.mark.skipif(pa is None, reason="PyArrow not installed") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_basic_to_arrow_conversion(test_config: Config): - """Test basic conversion of query results to Arrow table.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query("SELECT 1 as test_value, 'hello' as text_value") - - assert result.has_rows(), "Expected result to have rows" - - arrow_table = result.to_arrow() - - assert isinstance(arrow_table, pa.Table), "Expected PyArrow Table object" - - assert arrow_table.column_names == [ - "test_value", - "text_value", - ], "Column names mismatch" - - assert arrow_table.num_rows == 1, "Expected 1 row" - assert arrow_table["test_value"][0].as_py() == 1 - assert arrow_table["text_value"][0].as_py() == "hello" - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_with_integers(test_config: Config): - """Test Arrow conversion with various integer types.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - CAST(42 as TINYINT) as tiny_int, - CAST(1000 as SMALLINT) as small_int, - CAST(100000 as INT) as reg_int, - CAST(9223372036854775800 as BIGINT) as big_int - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - assert arrow_table["tiny_int"][0].as_py() == 42 - assert arrow_table["small_int"][0].as_py() == 1000 - assert arrow_table["reg_int"][0].as_py() == 100000 - assert arrow_table["big_int"][0].as_py() == 9223372036854775800 - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_with_floating_point(test_config: Config): - """Test Arrow conversion with floating point numbers.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - CAST(3.14 as FLOAT) as float_val, - CAST(2.71828 as REAL) as real_val - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - float_val = arrow_table["float_val"][0].as_py() - assert float_val is not None - real_val = arrow_table["real_val"][0].as_py() - assert real_val is not None - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_with_strings(test_config: Config): - """Test Arrow conversion with string data types.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - 'varchar data' as varchar_col, - N'nvarchar data' as nvarchar_col, - 'fixed char' as char_col - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - assert arrow_table["varchar_col"][0].as_py() == "varchar data" - assert arrow_table["nvarchar_col"][0].as_py() == "nvarchar data" - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_with_booleans(test_config: Config): - """Test Arrow conversion with boolean data.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - CAST(1 as BIT) as true_val, - CAST(0 as BIT) as false_val - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - assert arrow_table["true_val"][0].as_py() - assert not arrow_table["false_val"][0].as_py() - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_with_nulls(test_config: Config): - """Test Arrow conversion with NULL values.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - NULL as null_int, - NULL as null_string, - 1 as not_null, - NULL as another_null - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - assert arrow_table["null_int"][0].as_py() is None - assert arrow_table["null_string"][0].as_py() is None - assert arrow_table["not_null"][0].as_py() == 1 - assert arrow_table["another_null"][0].as_py() is None - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_with_dates(test_config: Config): - """Test Arrow conversion with date and datetime types.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - CAST('2024-02-13' as DATE) as date_val, - CAST('2024-02-13 14:30:45' as DATETIME) as datetime_val - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - assert "date_val" in arrow_table.column_names - assert "datetime_val" in arrow_table.column_names - date_val = arrow_table["date_val"][0].as_py() - assert date_val is not None - datetime_val = arrow_table["datetime_val"][0].as_py() - assert datetime_val is not None - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_with_decimals(test_config: Config): - """Test Arrow conversion with DECIMAL and MONEY types.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - CAST(123.45 as DECIMAL(10, 2)) as decimal_val, - CAST(99.99 as MONEY) as money_val - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - # Verify columns exist - assert "decimal_val" in arrow_table.column_names - assert "money_val" in arrow_table.column_names - # These should be present in the table - _ = arrow_table["decimal_val"][0].as_py() - # Money values come through as Decimal objects - money_val = arrow_table["money_val"][0].as_py() - assert money_val is not None - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_multiple_rows(test_config: Config): - """Test Arrow conversion with multiple rows.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT 1 as id, 'first' as name - UNION ALL - SELECT 2, 'second' - UNION ALL - SELECT 3, 'third' - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 3 - assert arrow_table["id"][0].as_py() == 1 - assert arrow_table["id"][1].as_py() == 2 - assert arrow_table["id"][2].as_py() == 3 - assert arrow_table["name"][0].as_py() == "first" - assert arrow_table["name"][1].as_py() == "second" - assert arrow_table["name"][2].as_py() == "third" - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_many_columns(test_config: Config): - """Test Arrow conversion with many columns.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - 1 as col1, 2 as col2, 3 as col3, 4 as col4, 5 as col5, - 6 as col6, 7 as col7, 8 as col8, 9 as col9, 10 as col10 - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - assert len(arrow_table.column_names) == 10 - for i in range(1, 11): - assert arrow_table[f"col{i}"][0].as_py() == i - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_schema_types(test_config: Config): - """Test that Arrow schema has correct types.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - 1 as int_col, - CAST(3.14 as REAL) as float_col, - 'text' as string_col, - CAST(1 as BIT) as bool_col - """ - ) - - arrow_table = result.to_arrow() - schema = arrow_table.schema - - # Verify schema field types - assert schema.field("int_col").type == pa.int64() - # Float columns might be float32 or float64 depending on REAL vs FLOAT - assert schema.field("float_col").type in [pa.float32(), pa.float64()] - assert schema.field("string_col").type == pa.string() - # Note: bool_ might be stored as int or other type depending on pyarrow version - assert schema.field("bool_col").type in [pa.bool_(), pa.int64()] - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_column_names_preserved(test_config: Config): - """Test that column names are correctly preserved in Arrow table.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - 1 as FirstColumn, - 2 as SecondColumn, - 3 as ThirdColumn - """ - ) - - arrow_table = result.to_arrow() - - assert "FirstColumn" in arrow_table.column_names - assert "SecondColumn" in arrow_table.column_names - assert "ThirdColumn" in arrow_table.column_names - assert len(arrow_table.column_names) == 3 - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_large_dataset(test_config: Config): - """Test Arrow conversion with larger dataset.""" - try: - async with Connection(test_config.connection_string) as conn: - # Generate 100+ rows - result = await conn.query( - """ - WITH Numbers AS ( - SELECT 1 as num - UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 - UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 - UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15 - UNION ALL SELECT 16 UNION ALL SELECT 17 UNION ALL SELECT 18 UNION ALL SELECT 19 UNION ALL SELECT 20 - ) - SELECT n1.num * 10 + n2.num as value FROM Numbers n1, Numbers n2 WHERE n1.num <= 5 - """ - ) - - arrow_table = result.to_arrow() - - # Should have at least 100 rows - assert arrow_table.num_rows >= 100 - assert len(arrow_table.column_names) == 1 - assert arrow_table.column_names[0] == "value" - # Verify data exists - for i in range(min(10, arrow_table.num_rows)): - assert arrow_table["value"][i].as_py() is not None - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_pyarrow_methods(test_config: Config): - """Test that returned Arrow table supports PyArrow methods.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT 1 as id, 'test' as name - UNION ALL - SELECT 2, 'data' - """ - ) - - arrow_table = result.to_arrow() - - # Test PyArrow methods - assert arrow_table.num_rows == 2 - assert arrow_table.num_columns == 2 - - # Test column access methods - col1 = arrow_table.column("id") - assert len(col1) == 2 - - # Test iteration - for i in range(arrow_table.num_rows): - row = arrow_table.take([i]) - assert row.num_rows == 1 - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_with_parameterized_query(test_config: Config): - """Test Arrow conversion with parameterized queries.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - "SELECT @P1 as param_value, @P2 as param_string", - [42, "test string"], - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - assert arrow_table["param_value"][0].as_py() == 42 - assert arrow_table["param_string"][0].as_py() == "test string" - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_null_handling_comprehensive(test_config: Config): - """Test comprehensive null value handling in Arrow conversion.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - NULL as null_int, - NULL as null_float, - NULL as null_string, - 1 as val1, - 'notNull' as val2, - NULL as val3, - 42 as val4 - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - - # Check nulls - assert arrow_table["null_int"][0].as_py() is None - assert arrow_table["null_float"][0].as_py() is None - assert arrow_table["null_string"][0].as_py() is None - - # Check non-nulls - assert arrow_table["val1"][0].as_py() == 1 - assert arrow_table["val2"][0].as_py() == "notNull" - assert arrow_table["val3"][0].as_py() is None - assert arrow_table["val4"][0].as_py() == 42 - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_mixed_types_single_row(test_config: Config): - """Test Arrow conversion with mixed data types in single row.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT - 1 as int_col, - CAST(3.14 as REAL) as float_col, - 'mixed' as string_col, - CAST(1 as BIT) as bool_col, - NULL as null_col, - CAST(99.99 as MONEY) as money_col - """ - ) - - arrow_table = result.to_arrow() - - assert arrow_table.num_rows == 1 - assert len(arrow_table.column_names) == 6 - - # Verify each column - assert arrow_table["int_col"][0].as_py() == 1 - float_val = arrow_table["float_col"][0].as_py() - assert float_val is not None - assert arrow_table["string_col"][0].as_py() == "mixed" - # Bool might be stored as int(1) or bool(True) - bool_val = arrow_table["bool_col"][0].as_py() - assert bool_val in [True, 1] - assert arrow_table["null_col"][0].as_py() is None - # Money value should be present - money_val = arrow_table["money_col"][0].as_py() - assert money_val is not None - except Exception as e: - pytest.fail(f"Test failed: {e}") - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_arrow_consistency_with_rows(test_config: Config): - """Test that Arrow conversion is consistent with rows() data.""" - try: - async with Connection(test_config.connection_string) as conn: - result = await conn.query( - """ - SELECT 1 as id, 'first' as name - UNION ALL - SELECT 2, 'second' - """ - ) - - # Get traditional rows - rows = result.rows() - - # Reset and get Arrow - result_arrow = await conn.query( - """ - SELECT 1 as id, 'first' as name - UNION ALL - SELECT 2, 'second' - """ - ) - arrow_table = result_arrow.to_arrow() - - # Verify consistency - assert len(rows) == arrow_table.num_rows - - for i, row in enumerate(rows): - assert row["id"] == arrow_table["id"][i].as_py() - assert row["name"] == arrow_table["name"][i].as_py() - except Exception as e: - pytest.fail(f"Test failed: {e}") diff --git a/uv.lock b/uv.lock index bd6c427..d7ff5b1 100644 --- a/uv.lock +++ b/uv.lock @@ -45,7 +45,7 @@ wheels = [ [[package]] name = "cibuildwheel" -version = "3.4.1" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bashlex" }, @@ -56,14 +56,11 @@ dependencies = [ { name = "filelock" }, { name = "humanize" }, { name = "packaging" }, - { name = "patchelf", marker = "(platform_machine == 'aarch64' and sys_platform == 'darwin') or (platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'arm64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, { name = "platformdirs" }, - { name = "pyelftools" }, - { name = "wheel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/b7/39dd5772a7893639e614a33c3e372eb1ca3792a536de5c5aff6d1bddc25b/cibuildwheel-3.4.1.tar.gz", hash = "sha256:c3ba783013e190849bb2d22e49359a911e368daebab8b02e3996320ab9315d34", size = 366686, upload-time = "2026-04-02T13:59:01.22Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/ae/135d31add82bb22eb49e3828726e71150be45013802becb2992956b8e655/cibuildwheel-4.1.0.tar.gz", hash = "sha256:aac6f526c0c06039feedd09f9bbceb989ab244ef0543eaea84c8abdeafb81858", size = 446157, upload-time = "2026-06-12T16:04:02.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/c6/b661315f571365d048e0f5ef3cc619f5ee215d17e2f880d35d919434e919/cibuildwheel-3.4.1-py3-none-any.whl", hash = "sha256:d495eb8780473029382976e86363562923401ac575c18bc52e6a9102512853ad", size = 130111, upload-time = "2026-04-02T13:58:59.644Z" }, + { url = "https://files.pythonhosted.org/packages/73/3f/20c00404a019b19f1d317f78a8364db3d86021c6c921d0969d1be9067051/cibuildwheel-4.1.0-py3-none-any.whl", hash = "sha256:b9dabe7713f16aec4e7064d031daa2da010e7431d58964e0b97b3eb346952454", size = 164664, upload-time = "2026-06-12T16:04:01.25Z" }, ] [package.optional-dependencies] @@ -103,21 +100,15 @@ wheels = [ [[package]] name = "fastmssql" -version = "0.7.3" +version = "0.7.4" source = { editable = "." } -[package.optional-dependencies] -arrow = [ - { name = "pyarrow" }, -] - [package.dev-dependencies] dev = [ { name = "cibuildwheel", extra = ["uv"] }, { name = "filelock" }, { name = "maturin" }, { name = "psutil" }, - { name = "pyarrow" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-timeout" }, @@ -129,33 +120,30 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "pyarrow", marker = "extra == 'arrow'", specifier = ">=13.0.0" }] -provides-extras = ["arrow"] [package.metadata.requires-dev] dev = [ - { name = "cibuildwheel", extras = ["uv"], specifier = ">=3.4.1" }, - { name = "filelock", specifier = "==3.29.0" }, - { name = "maturin", specifier = "==1.13.1" }, + { name = "cibuildwheel", extras = ["uv"], specifier = ">=4.1.0" }, + { name = "filelock", specifier = "==3.29.5" }, + { name = "maturin", specifier = "==1.14.1" }, { name = "psutil", specifier = "==7.2.2" }, - { name = "pyarrow", specifier = "==23.0.1" }, - { name = "pytest", specifier = "==9.0.3" }, - { name = "pytest-asyncio", specifier = "==1.3.0" }, + { name = "pytest", specifier = "==9.1.1" }, + { name = "pytest-asyncio", specifier = "==1.4.0" }, { name = "pytest-timeout", specifier = "==2.4.0" }, { name = "pytest-xdist", specifier = "==3.8.0" }, { name = "python-dotenv", specifier = "==1.2.2" }, - { name = "ruff", specifier = ">=0.15.11" }, + { name = "ruff", specifier = ">=0.15.20" }, { name = "uvloop", specifier = "==0.22.1" }, { name = "wheel", specifier = "==0.47.0" }, ] [[package]] name = "filelock" -version = "3.29.0" +version = "3.29.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/ee/29c668c50888588c432a702f7c2e8ee8a0c9e5286028d91f170308d6b2e9/filelock-3.29.5.tar.gz", hash = "sha256:6e6034c57a00a020e767f2614a5539863f056de7e7991d6d1473aef7ff73f156", size = 68927, upload-time = "2026-07-03T03:50:31.818Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e3/f1fae3647d170919c2cf2a898e77e7d1a4e5c7cae0aed7bb4bd3f5ebff6f/filelock-3.29.5-py3-none-any.whl", hash = "sha256:8af830889ba3a0ffcefbd6c7d2af8a54012058103771f2e10848222f476a1693", size = 45073, upload-time = "2026-07-03T03:50:30.445Z" }, ] [[package]] @@ -178,23 +166,23 @@ wheels = [ [[package]] name = "maturin" -version = "1.13.1" +version = "1.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/39/16/b284a7bc4af3dd87717c784278c1b8cb18606ad1f6f7a671c47bfd9c3df0/maturin-1.13.1.tar.gz", hash = "sha256:9a87ff3b8e4d1c6eac33ebfe8e261e8236516d98d45c0323550621819b5a1a2f", size = 340369, upload-time = "2026-04-09T15:14:07.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/b3/addd877f871fb1860d46d3a4f206ecb10b946c85846805e6367631926fd3/maturin-1.14.1.tar.gz", hash = "sha256:9d6577a62cd08e0ceba7a0db06fb098e0c9b1b3429bad747a4f3a18215a1b3df", size = 369637, upload-time = "2026-06-19T05:19:49.774Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/4d/a23fc95be881aa8c7a6ea353410417872e4d7065df03d7f3db8f0dbed4a7/maturin-1.13.1-py3-none-linux_armv6l.whl", hash = "sha256:416e4e01cb88b798e606ee43929df897e42c1647b722ef68283816cca99a8742", size = 10102444, upload-time = "2026-04-09T15:13:48.393Z" }, - { url = "https://files.pythonhosted.org/packages/a6/1e/65c385d65bae95cf04895d52f39dbed8b1453ae55da2903d252ade40a774/maturin-1.13.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:72888e87819ce546d0d2df900e4b385e4ef299077d92ee37b48923a5602dae94", size = 19576043, upload-time = "2026-04-09T15:14:08.685Z" }, - { url = "https://files.pythonhosted.org/packages/8f/13/f6bc868d0bfecd9314870b97f530a167e31f7878ac4945c78245c6eef69c/maturin-1.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:98b5fcf1a186c217830a8295ecc2989c6b1cf50945417adfc15252107b9475b7", size = 10117339, upload-time = "2026-04-09T15:13:40.559Z" }, - { url = "https://files.pythonhosted.org/packages/51/58/279e081305c11c1c1c4fccacf77df8959646c5d4de7a57ec7e787653e270/maturin-1.13.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:3da18cccf2f683c0977bff9146a0908d6ffce836d600665736ac01679f588cb9", size = 10139689, upload-time = "2026-04-09T15:13:38.291Z" }, - { url = "https://files.pythonhosted.org/packages/00/94/69391af5396c6aab723932240803f49e5f3de3dd7c57d32f02d237a0ce32/maturin-1.13.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:6b1e5916a253243e8f5f9e847b62bbc98420eec48c9ce2e2e8724c6da89d359b", size = 10551141, upload-time = "2026-04-09T15:13:42.887Z" }, - { url = "https://files.pythonhosted.org/packages/9e/bf/4edac2667b49e3733438062ae416413b8fc8d42e1bd499ba15e1fb02fc55/maturin-1.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:dc91031e0619c1e28730279ef9ee5f106c9b9ec806b013f888676b242f892eb7", size = 9983094, upload-time = "2026-04-09T15:13:56.868Z" }, - { url = "https://files.pythonhosted.org/packages/79/94/a6d651cfe8fc6bf2e892c90e3cdbb25c06d81c9115140d03ea1a68a97575/maturin-1.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:001741c6cff56aa8ea59a0d78ae990c0550d0e3e82b00b683eedb4158a8ef7e6", size = 9949980, upload-time = "2026-04-09T15:13:59.185Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d1/82c067464f848e38af9910bce55eb54302b1c1284a279d515dbfcf5994f5/maturin-1.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:01c845825c917c07c1d0b2c9032c59c16a7d383d1e649a46481d3e5693c2750f", size = 13186276, upload-time = "2026-04-09T15:13:45.725Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f4/25367baf1025580f047f9b37598bb3fadc416e24536afd4f28e190335c73/maturin-1.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f69093ed4a0e6464e52a7fc26d714f859ce15630ec8070743398c6bf41f38a9e", size = 10891837, upload-time = "2026-04-09T15:13:35.68Z" }, - { url = "https://files.pythonhosted.org/packages/af/be/caafad8ce74974b7deafdf144d12f758993dfea4c66c9905b138f51a7792/maturin-1.13.1-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:c1490584f3c70af45466ee99065b49e6657ebdccac6b10571bb44681309c9396", size = 10351032, upload-time = "2026-04-09T15:14:01.632Z" }, - { url = "https://files.pythonhosted.org/packages/66/0e/970a721d27cfa410e8bfa0a1e32e6ef52cb8169692110a5fdabe1af3f570/maturin-1.13.1-py3-none-win32.whl", hash = "sha256:c6a720b252c99de072922dbe4432ab19662b6f80045b0355fec23bdfccb450da", size = 8855465, upload-time = "2026-04-09T15:13:51.122Z" }, - { url = "https://files.pythonhosted.org/packages/88/70/7c1e0d65fa147d5479055a171541c82b8cdfc1c825d85a82240470f14176/maturin-1.13.1-py3-none-win_amd64.whl", hash = "sha256:a2017d2281203d0c6570240e7d746564d766d756105823b7de68bda6ae722711", size = 10230471, upload-time = "2026-04-09T15:13:53.89Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2a/afe0193b673a79ffd2e01ad999511b7e9e6b49af02bb3759d82a78c3043d/maturin-1.13.1-py3-none-win_arm64.whl", hash = "sha256:2839024dcd65776abb4759e5bca29941971e095574162a4d335191da4be9ff24", size = 8905575, upload-time = "2026-04-09T15:14:03.891Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f0/97c5a5bd9c71653a066c0976a484eaaae50b9369557838a4176b7b0bdaa5/maturin-1.14.1-py3-none-linux_armv6l.whl", hash = "sha256:522292398945442cdafa9daeb2271b2340fbde57027b818f923f88eab04174f8", size = 10207496, upload-time = "2026-06-19T05:19:09.321Z" }, + { url = "https://files.pythonhosted.org/packages/fe/83/294bca639b0e052f1e2f65199b3db258780c7d4e31408b934c9c974a1379/maturin-1.14.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ffe5ad71f21d1e6603c4dd75f7fee34adf5ed5ebcebb692886549888ebb329ed", size = 19680113, upload-time = "2026-06-19T05:19:13.43Z" }, + { url = "https://files.pythonhosted.org/packages/43/b6/79c881410a3b1c187f7eb3d407aecae646c6a4433d630d72200359015e83/maturin-1.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f3306078070c1508fd715b9116070cbcaff5959024272a9f1e6f5cb29768b86c", size = 10169205, upload-time = "2026-06-19T05:19:16.615Z" }, + { url = "https://files.pythonhosted.org/packages/93/9d/44b6f26dcb7f7a04c5501ac2dbb6ca1490150682baa525ca5860504f9eab/maturin-1.14.1-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:cd457cd88961156e26379e1155bd287cc0ec1c8b2f1582b0660fb31b87c8842d", size = 10188098, upload-time = "2026-06-19T05:19:19.736Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bd/9c0d5d6983905ce2c9edaa073a7e89355a9cf7f396988e05d32f1c37785d/maturin-1.14.1-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:dfc54ae32e6fcb18302193ab9a30b0b25eefffba994ae13238974805533ef75e", size = 10627576, upload-time = "2026-06-19T05:19:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/e5/33/b096412bd6a7cb399652b260666f901adf88a687181a6dbd6a3f89f0a94e/maturin-1.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a131d912b5267e640bc96d70f4914e10590aed64082ec9abacba7cea52004224", size = 10085181, upload-time = "2026-06-19T05:19:25.69Z" }, + { url = "https://files.pythonhosted.org/packages/56/8d/08c3bf469c38a23c9e6c877e338193001eb604d010fedc08341974e38528/maturin-1.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:be18fc568fb76884c0205456336892a75105ec398e6b667cd777c6268bd06d69", size = 10026363, upload-time = "2026-06-19T05:19:28.904Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/c4d1a92839f8745ab4aab988a7db884a79d6d710bd3b286fcf9316dece1a/maturin-1.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:994a0c8ba3ad8a92b3a9ee1b02645d200d610216b15cff5102b0fe65e8e08666", size = 13321347, upload-time = "2026-06-19T05:19:32.411Z" }, + { url = "https://files.pythonhosted.org/packages/b3/fa/170f04624d03fd07d2a8b1b67de83a127af93aef9eaa425839553347297b/maturin-1.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:be80866363e605d137991b491a741a84cde9ae350183c4c85f49690ca9aaaa65", size = 10877609, upload-time = "2026-06-19T05:19:35.448Z" }, + { url = "https://files.pythonhosted.org/packages/61/ad/1ae2e1d0ded282bf2c55ac13f0811d87deb425e200ae64a15785675dede9/maturin-1.14.1-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:5282dffd4b539d2be245f4e5b1a5ab6bc1033b58f4a4872f5833f9d43c954aa4", size = 10417316, upload-time = "2026-06-19T05:19:38.28Z" }, + { url = "https://files.pythonhosted.org/packages/fb/27/bf677183920718da49cd7982d6a3ffc440aad8919329f571d189f81b7bdf/maturin-1.14.1-py3-none-win32.whl", hash = "sha256:1a04de0a20188f95c721b5702eed18140bdcccb28c386797093eca3f62f4d4e0", size = 8931293, upload-time = "2026-06-19T05:19:41.183Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/585adeb9167b08d3cdff0032a938b0e72655c92003df4f52c3f696a1bcc2/maturin-1.14.1-py3-none-win_amd64.whl", hash = "sha256:3c9f94640ecc4895e94abaf834a0684430032c865b2748a36c12461fd9252fdd", size = 10314067, upload-time = "2026-06-19T05:19:44.389Z" }, + { url = "https://files.pythonhosted.org/packages/51/d4/dac8c0720ae246be1700afb6fbdbbea20fe35b13f6570b2f70faa005df77/maturin-1.14.1-py3-none-win_arm64.whl", hash = "sha256:15cea8fcb3ba47dd636f50092bb34baea8b04ac777392f23e6bf8a9a61efb894", size = 9718943, upload-time = "2026-06-19T05:19:47.49Z" }, ] [[package]] @@ -206,21 +194,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] -[[package]] -name = "patchelf" -version = "0.17.2.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2c/a3/fdd3fa938c864aa2f11dd0b7f08befeda983d2dcdee44da493c6977a653f/patchelf-0.17.2.4.tar.gz", hash = "sha256:970ee5cd8af33e5ea2099510b2f9013fa1b8d5cd763bf3fd3961281c18101a09", size = 149629, upload-time = "2025-07-23T21:16:32.071Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/a7/8c4f86c78ec03db954d05fd9c57a114cc3a172a2d3e4a8b949cd5ff89471/patchelf-0.17.2.4-py3-none-macosx_10_9_universal2.whl", hash = "sha256:343bb1b94e959f9070ca9607453b04390e36bbaa33c88640b989cefad0aa049e", size = 184436, upload-time = "2025-07-23T21:16:20.578Z" }, - { url = "https://files.pythonhosted.org/packages/7e/19/f7821ef31aab01fa7dc8ebe697ece88ec4f7a0fdd3155dab2dfee4b00e5c/patchelf-0.17.2.4-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:d9b35ebfada70c02679ad036407d9724ffe1255122ba4ac5e4be5868618a5689", size = 482846, upload-time = "2025-07-23T21:16:23.73Z" }, - { url = "https://files.pythonhosted.org/packages/d1/50/107fea848ecfd851d473b079cab79107487d72c4c3cdb25b9d2603a24ca2/patchelf-0.17.2.4-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2931a1b5b85f3549661898af7bf746afbda7903c7c9a967cfc998a3563f84fad", size = 477811, upload-time = "2025-07-23T21:16:25.145Z" }, - { url = "https://files.pythonhosted.org/packages/89/a9/a9a2103e159fd65bffbc21ecc5c8c36e44eb34fe53b4ef85fb6d08c2a635/patchelf-0.17.2.4-py3-none-manylinux2014_armv7l.manylinux_2_17_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:ae44cb3c857d50f54b99e5697aa978726ada33a8a6129d4b8b7ffd28b996652d", size = 431226, upload-time = "2025-07-23T21:16:26.765Z" }, - { url = "https://files.pythonhosted.org/packages/87/93/897d612f6df7cfd987bdf668425127efeff8d8e4ad8bfbab1c69d2a0d861/patchelf-0.17.2.4-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:680a266a70f60a7a4f4c448482c5bdba80cc8e6bb155a49dcc24238ba49927b0", size = 540276, upload-time = "2025-07-23T21:16:27.983Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b8/2b92d11533482bac9ee989081d6880845287751b5f528adbd6bb27667fbd/patchelf-0.17.2.4-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.musllinux_1_1_s390x.whl", hash = "sha256:d842b51f0401460f3b1f3a3a67d2c266a8f515a5adfbfa6e7b656cb3ac2ed8bc", size = 596632, upload-time = "2025-07-23T21:16:29.253Z" }, - { url = "https://files.pythonhosted.org/packages/14/e2/975d4bdb418f942b53e6187b95bd9e0d5e0488b7bc214685a1e43e2c2751/patchelf-0.17.2.4-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:7076d9e127230982e20a81a6e2358d3343004667ba510d9f822d4fdee29b0d71", size = 508281, upload-time = "2025-07-23T21:16:30.865Z" }, -] - [[package]] name = "platformdirs" version = "4.5.1" @@ -267,65 +240,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] -[[package]] -name = "pyarrow" -version = "23.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/22/134986a4cc224d593c1afde5494d18ff629393d74cc2eddb176669f234a4/pyarrow-23.0.1.tar.gz", hash = "sha256:b8c5873e33440b2bc2f4a79d2b47017a89c5a24116c055625e6f2ee50523f019", size = 1167336, upload-time = "2026-02-16T10:14:12.39Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/41/8e6b6ef7e225d4ceead8459427a52afdc23379768f54dd3566014d7618c1/pyarrow-23.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6f0147ee9e0386f519c952cc670eb4a8b05caa594eeffe01af0e25f699e4e9bb", size = 34302230, upload-time = "2026-02-16T10:09:03.859Z" }, - { url = "https://files.pythonhosted.org/packages/bf/4a/1472c00392f521fea03ae93408bf445cc7bfa1ab81683faf9bc188e36629/pyarrow-23.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:0ae6e17c828455b6265d590100c295193f93cc5675eb0af59e49dbd00d2de350", size = 35850050, upload-time = "2026-02-16T10:09:11.877Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b2/bd1f2f05ded56af7f54d702c8364c9c43cd6abb91b0e9933f3d77b4f4132/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:fed7020203e9ef273360b9e45be52a2a47d3103caf156a30ace5247ffb51bdbd", size = 44491918, upload-time = "2026-02-16T10:09:18.144Z" }, - { url = "https://files.pythonhosted.org/packages/0b/62/96459ef5b67957eac38a90f541d1c28833d1b367f014a482cb63f3b7cd2d/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:26d50dee49d741ac0e82185033488d28d35be4d763ae6f321f97d1140eb7a0e9", size = 47562811, upload-time = "2026-02-16T10:09:25.792Z" }, - { url = "https://files.pythonhosted.org/packages/7d/94/1170e235add1f5f45a954e26cd0e906e7e74e23392dcb560de471f7366ec/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c30143b17161310f151f4a2bcfe41b5ff744238c1039338779424e38579d701", size = 48183766, upload-time = "2026-02-16T10:09:34.645Z" }, - { url = "https://files.pythonhosted.org/packages/0e/2d/39a42af4570377b99774cdb47f63ee6c7da7616bd55b3d5001aa18edfe4f/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db2190fa79c80a23fdd29fef4b8992893f024ae7c17d2f5f4db7171fa30c2c78", size = 50607669, upload-time = "2026-02-16T10:09:44.153Z" }, - { url = "https://files.pythonhosted.org/packages/00/ca/db94101c187f3df742133ac837e93b1f269ebdac49427f8310ee40b6a58f/pyarrow-23.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:f00f993a8179e0e1c9713bcc0baf6d6c01326a406a9c23495ec1ba9c9ebf2919", size = 27527698, upload-time = "2026-02-16T10:09:50.263Z" }, - { url = "https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f", size = 34214575, upload-time = "2026-02-16T10:09:56.225Z" }, - { url = "https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7707d2b6673f7de054e2e83d59f9e805939038eebe1763fe811ee8fa5c0cd1a7", size = 35832540, upload-time = "2026-02-16T10:10:03.428Z" }, - { url = "https://files.pythonhosted.org/packages/88/7c/3d841c366620e906d54430817531b877ba646310296df42ef697308c2705/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86ff03fb9f1a320266e0de855dee4b17da6794c595d207f89bba40d16b5c78b9", size = 44470940, upload-time = "2026-02-16T10:10:10.704Z" }, - { url = "https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:813d99f31275919c383aab17f0f455a04f5a429c261cc411b1e9a8f5e4aaaa05", size = 47586063, upload-time = "2026-02-16T10:10:17.95Z" }, - { url = "https://files.pythonhosted.org/packages/5b/3c/b7d2ebcff47a514f47f9da1e74b7949138c58cfeb108cdd4ee62f43f0cf3/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bf5842f960cddd2ef757d486041d57c96483efc295a8c4a0e20e704cbbf39c67", size = 48173045, upload-time = "2026-02-16T10:10:25.363Z" }, - { url = "https://files.pythonhosted.org/packages/43/b2/b40961262213beaba6acfc88698eb773dfce32ecdf34d19291db94c2bd73/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564baf97c858ecc03ec01a41062e8f4698abc3e6e2acd79c01c2e97880a19730", size = 50621741, upload-time = "2026-02-16T10:10:33.477Z" }, - { url = "https://files.pythonhosted.org/packages/f6/70/1fdda42d65b28b078e93d75d371b2185a61da89dda4def8ba6ba41ebdeb4/pyarrow-23.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:07deae7783782ac7250989a7b2ecde9b3c343a643f82e8a4df03d93b633006f0", size = 27620678, upload-time = "2026-02-16T10:10:39.31Z" }, - { url = "https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6b8fda694640b00e8af3c824f99f789e836720aa8c9379fb435d4c4953a756b8", size = 34210066, upload-time = "2026-02-16T10:10:45.487Z" }, - { url = "https://files.pythonhosted.org/packages/cb/4f/679fa7e84dadbaca7a65f7cdba8d6c83febbd93ca12fa4adf40ba3b6362b/pyarrow-23.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:8ff51b1addc469b9444b7c6f3548e19dc931b172ab234e995a60aea9f6e6025f", size = 35825526, upload-time = "2026-02-16T10:10:52.266Z" }, - { url = "https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:71c5be5cbf1e1cb6169d2a0980850bccb558ddc9b747b6206435313c47c37677", size = 44473279, upload-time = "2026-02-16T10:11:01.557Z" }, - { url = "https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9b6f4f17b43bc39d56fec96e53fe89d94bac3eb134137964371b45352d40d0c2", size = 47585798, upload-time = "2026-02-16T10:11:09.401Z" }, - { url = "https://files.pythonhosted.org/packages/5c/20/476943001c54ef078dbf9542280e22741219a184a0632862bca4feccd666/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fc13fc6c403d1337acab46a2c4346ca6c9dec5780c3c697cf8abfd5e19b6b37", size = 48179446, upload-time = "2026-02-16T10:11:17.781Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b6/5dd0c47b335fcd8edba9bfab78ad961bd0fd55ebe53468cc393f45e0be60/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c16ed4f53247fa3ffb12a14d236de4213a4415d127fe9cebed33d51671113e2", size = 50623972, upload-time = "2026-02-16T10:11:26.185Z" }, - { url = "https://files.pythonhosted.org/packages/d5/09/a532297c9591a727d67760e2e756b83905dd89adb365a7f6e9c72578bcc1/pyarrow-23.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:cecfb12ef629cf6be0b1887f9f86463b0dd3dc3195ae6224e74006be4736035a", size = 27540749, upload-time = "2026-02-16T10:12:23.297Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8e/38749c4b1303e6ae76b3c80618f84861ae0c55dd3c2273842ea6f8258233/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:29f7f7419a0e30264ea261fdc0e5fe63ce5a6095003db2945d7cd78df391a7e1", size = 34471544, upload-time = "2026-02-16T10:11:32.535Z" }, - { url = "https://files.pythonhosted.org/packages/a3/73/f237b2bc8c669212f842bcfd842b04fc8d936bfc9d471630569132dc920d/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:33d648dc25b51fd8055c19e4261e813dfc4d2427f068bcecc8b53d01b81b0500", size = 35949911, upload-time = "2026-02-16T10:11:39.813Z" }, - { url = "https://files.pythonhosted.org/packages/0c/86/b912195eee0903b5611bf596833def7d146ab2d301afeb4b722c57ffc966/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd395abf8f91c673dd3589cadc8cc1ee4e8674fa61b2e923c8dd215d9c7d1f41", size = 44520337, upload-time = "2026-02-16T10:11:47.764Z" }, - { url = "https://files.pythonhosted.org/packages/69/c2/f2a717fb824f62d0be952ea724b4f6f9372a17eed6f704b5c9526f12f2f1/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:00be9576d970c31defb5c32eb72ef585bf600ef6d0a82d5eccaae96639cf9d07", size = 47548944, upload-time = "2026-02-16T10:11:56.607Z" }, - { url = "https://files.pythonhosted.org/packages/84/a7/90007d476b9f0dc308e3bc57b832d004f848fd6c0da601375d20d92d1519/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c2139549494445609f35a5cda4eb94e2c9e4d704ce60a095b342f82460c73a83", size = 48236269, upload-time = "2026-02-16T10:12:04.47Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3f/b16fab3e77709856eb6ac328ce35f57a6d4a18462c7ca5186ef31b45e0e0/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7044b442f184d84e2351e5084600f0d7343d6117aabcbc1ac78eb1ae11eb4125", size = 50604794, upload-time = "2026-02-16T10:12:11.797Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a1/22df0620a9fac31d68397a75465c344e83c3dfe521f7612aea33e27ab6c0/pyarrow-23.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a35581e856a2fafa12f3f54fce4331862b1cfb0bef5758347a858a4aa9d6bae8", size = 27660642, upload-time = "2026-02-16T10:12:17.746Z" }, - { url = "https://files.pythonhosted.org/packages/8d/1b/6da9a89583ce7b23ac611f183ae4843cd3a6cf54f079549b0e8c14031e73/pyarrow-23.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:5df1161da23636a70838099d4aaa65142777185cc0cdba4037a18cee7d8db9ca", size = 34238755, upload-time = "2026-02-16T10:12:32.819Z" }, - { url = "https://files.pythonhosted.org/packages/ae/b5/d58a241fbe324dbaeb8df07be6af8752c846192d78d2272e551098f74e88/pyarrow-23.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:fa8e51cb04b9f8c9c5ace6bab63af9a1f88d35c0d6cbf53e8c17c098552285e1", size = 35847826, upload-time = "2026-02-16T10:12:38.949Z" }, - { url = "https://files.pythonhosted.org/packages/54/a5/8cbc83f04aba433ca7b331b38f39e000efd9f0c7ce47128670e737542996/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b95a3994f015be13c63148fef8832e8a23938128c185ee951c98908a696e0eb", size = 44536859, upload-time = "2026-02-16T10:12:45.467Z" }, - { url = "https://files.pythonhosted.org/packages/36/2e/c0f017c405fcdc252dbccafbe05e36b0d0eb1ea9a958f081e01c6972927f/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4982d71350b1a6e5cfe1af742c53dfb759b11ce14141870d05d9e540d13bc5d1", size = 47614443, upload-time = "2026-02-16T10:12:55.525Z" }, - { url = "https://files.pythonhosted.org/packages/af/6b/2314a78057912f5627afa13ba43809d9d653e6630859618b0fd81a4e0759/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c250248f1fe266db627921c89b47b7c06fee0489ad95b04d50353537d74d6886", size = 48232991, upload-time = "2026-02-16T10:13:04.729Z" }, - { url = "https://files.pythonhosted.org/packages/40/f2/1bcb1d3be3460832ef3370d621142216e15a2c7c62602a4ea19ec240dd64/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f4763b83c11c16e5f4c15601ba6dfa849e20723b46aa2617cb4bffe8768479f", size = 50645077, upload-time = "2026-02-16T10:13:14.147Z" }, - { url = "https://files.pythonhosted.org/packages/eb/3f/b1da7b61cd66566a4d4c8383d376c606d1c34a906c3f1cb35c479f59d1aa/pyarrow-23.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:3a4c85ef66c134161987c17b147d6bffdca4566f9a4c1d81a0a01cdf08414ea5", size = 28234271, upload-time = "2026-02-16T10:14:09.397Z" }, - { url = "https://files.pythonhosted.org/packages/b5/78/07f67434e910a0f7323269be7bfbf58699bd0c1d080b18a1ab49ba943fe8/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:17cd28e906c18af486a499422740298c52d7c6795344ea5002a7720b4eadf16d", size = 34488692, upload-time = "2026-02-16T10:13:21.541Z" }, - { url = "https://files.pythonhosted.org/packages/50/76/34cf7ae93ece1f740a04910d9f7e80ba166b9b4ab9596a953e9e62b90fe1/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:76e823d0e86b4fb5e1cf4a58d293036e678b5a4b03539be933d3b31f9406859f", size = 35964383, upload-time = "2026-02-16T10:13:28.63Z" }, - { url = "https://files.pythonhosted.org/packages/46/90/459b827238936d4244214be7c684e1b366a63f8c78c380807ae25ed92199/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a62e1899e3078bf65943078b3ad2a6ddcacf2373bc06379aac61b1e548a75814", size = 44538119, upload-time = "2026-02-16T10:13:35.506Z" }, - { url = "https://files.pythonhosted.org/packages/28/a1/93a71ae5881e99d1f9de1d4554a87be37da11cd6b152239fb5bd924fdc64/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:df088e8f640c9fae3b1f495b3c64755c4e719091caf250f3a74d095ddf3c836d", size = 47571199, upload-time = "2026-02-16T10:13:42.504Z" }, - { url = "https://files.pythonhosted.org/packages/88/a3/d2c462d4ef313521eaf2eff04d204ac60775263f1fb08c374b543f79f610/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:46718a220d64677c93bc243af1d44b55998255427588e400677d7192671845c7", size = 48259435, upload-time = "2026-02-16T10:13:49.226Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f1/11a544b8c3d38a759eb3fbb022039117fd633e9a7b19e4841cc3da091915/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a09f3876e87f48bc2f13583ab551f0379e5dfb83210391e68ace404181a20690", size = 50629149, upload-time = "2026-02-16T10:13:57.238Z" }, - { url = "https://files.pythonhosted.org/packages/50/f2/c0e76a0b451ffdf0cf788932e182758eb7558953f4f27f1aff8e2518b653/pyarrow-23.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:527e8d899f14bd15b740cd5a54ad56b7f98044955373a17179d5956ddb93d9ce", size = 28365807, upload-time = "2026-02-16T10:14:03.892Z" }, -] - -[[package]] -name = "pyelftools" -version = "0.32" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/ab/33968940b2deb3d92f5b146bc6d4009a5f95d1d06c148ea2f9ee965071af/pyelftools-0.32.tar.gz", hash = "sha256:6de90ee7b8263e740c8715a925382d4099b354f29ac48ea40d840cf7aa14ace5", size = 15047199, upload-time = "2025-02-19T14:20:05.549Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/af/43/700932c4f0638c3421177144a2e86448c0d75dbaee2c7936bda3f9fd0878/pyelftools-0.32-py3-none-any.whl", hash = "sha256:013df952a006db5e138b1edf6d8a68ecc50630adbd0d83a2d41e7f846163d738", size = 188525, upload-time = "2025-02-19T14:19:59.919Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -346,7 +260,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.3" +version = "9.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -355,22 +269,22 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.3.0" +version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/7c/d36d04db312ecf4298932ef77e6e4a9e8ad017906e24e34f0b0c361a2473/pytest_asyncio-1.4.0.tar.gz", hash = "sha256:c6c0d2259945122819f171a32ecea2c349ead889ee28176caaf492143424be42", size = 58514, upload-time = "2026-05-26T09:56:04.083Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, + { url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" }, ] [[package]] @@ -409,27 +323,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.11" +version = "0.15.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, - { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, - { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, - { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, - { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, - { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, - { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, - { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, - { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, - { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, - { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, - { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, - { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, - { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, ] [[package]]