diff --git a/AGENTS.md b/AGENTS.md index 5c2026f..99acd05 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,7 +71,28 @@ assert pressure.to("kPa").m == 100.0 - `Temperature` and `TemperatureDifference` are distinct dimensionalities with deliberate arithmetic (`T - T -> ΔT`, `T ± ΔT -> T`, `ΔT - T` is an error). The comparison and pickling overrides on `Quantity` intentionally narrow pint's - signatures — that's the point of the library, not an LSP bug to fix. + signatures — that's the point of the library, not an LSP bug to fix. Two consequences + that are easy to break: + - **The unit decides the dimensionality**, and the two must never contradict each + other. `degC`/`degF` are absolute, `delta_degC`/`delta_K` are differences, and only + the multiplicative spellings (`K`, `degR`, `mK`) are genuinely ambiguous — those + default to `Temperature`. `asdim` rewrites the unit onto the matching scale in both + directions; a `Quantity[...]` hint that disagrees with the unit is re-resolved from + the unit (as for any dimensionality: `Q[Mass](1, "m")` is a `Length`). + - **A difference must never reach an absolute-temperature API.** ΔT converts to `K` by + scale alone, so `.to()` cannot catch it: CoolProp state inputs and + `ideal_gas_density` check the dimensionality explicitly. numpy dispatch does not go + through the operators, so `__array_ufunc__`/`__array_function__` route `np.add`/ + `np.subtract` to `+`/`-` and re-type the difference-producing array functions + (`_DIFFERENCE_ARRAY_FUNCTIONS`). Add to that set rather than duplicating the rules. +- **Missing values follow each container's own sentinel**: `NaN` in the numpy world, + `null` in the polars world (where `NaN` is an ordinary float *value* — polars skips + `null` in `mean()` and propagates `NaN`). encomp never *produces* a `NaN` in a polars + magnitude (an unfixable CoolProp state is `null`); a `NaN` the caller puts in a + `pl.Series` is data and is kept verbatim; IEEE arithmetic (`0.0 / 0.0`) may create one + in either world; and `astype` — the boundary between the worlds — translates the + sentinel both ways. Don't normalize magnitudes anywhere else: it would rewrite the + caller's data and cost an O(n) pass per operation. - CoolProp evaluation is **one property per DAG node** by design; don't batch multiple output properties into one plugin call. Scalars detach from the GIL in the direct PyO3 bridge and own thread-local native states; arrays run through the Rust/Polars diff --git a/README.md b/README.md index 7217e28..bc7edbf 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ pip install encomp `encomp` ships as a single per-platform wheel containing its dual-purpose native Rust artifact and one bundled CoolProp shared library, so supported platforms have nothing to build and do not install the Python `CoolProp` package. Wheels are provided for Windows (x86_64), Linux (x86_64 and arm64), and macOS (Apple Silicon only). PyPI does not publish an sdist; unsupported platforms, including Intel Macs, need a build from the git repository; see [Tests](#tests). -On macOS arm64, the native-only package reduces the resolver's combined wheel download from about 19.6 MB (`encomp` plus the separate Python CoolProp wheel) to about 9 MB for `encomp` alone—roughly a 54% reduction. The `encomp` artifact itself grows only slightly for the scalar bridge and bundled license; removing the separate CoolProp wheel is where the installation-size saving comes from. +On macOS arm64, the native-only package reduces the resolver's combined wheel download from about 19.6 MB (`encomp` plus the separate Python CoolProp wheel) to about 10 MB for `encomp` alone—roughly half. The `encomp` artifact itself grows only slightly for the scalar bridge and bundled license; removing the separate CoolProp wheel is where the installation-size saving comes from. ## The `Quantity` class @@ -67,6 +67,8 @@ Q([1, 2, 3], "bar") * 2 # [2.0 4.0 6.0] bar assert Q(0.1) == Q(10, "%") ``` +Give a quantity the whole column: a magnitude may be a `float`, a 1-D NumPy array, a Polars `Series` or a Polars `Expr`, and the dimensionality machinery is paid *per operation*, not per element. A scalar operation carries single-digit-microsecond overhead over raw pint arithmetic, which caps a Python loop over scalar quantities at roughly 10⁴–10⁵ ops/s; the same work on a vector magnitude amortizes to within ~20% of the underlying NumPy or Polars kernel at 10⁶ elements. Port scalar pint loops to array/`Series`/`Expr` magnitudes rather than iterating. + ### `Quantity` type system Each `Quantity` has a `Dimensionality` type parameter, determined at runtime from the unit. @@ -306,7 +308,7 @@ Raw arithmetic on a unit-typed column is refused because Polars cannot delegate The persisted Arrow field contract is `ARROW:extension:name = "encomp.unit"` and `ARROW:extension:metadata = `. Import `encomp.polars`—or `encomp.coolprop`, which registers the same dtype—before reading with Polars 1.x. For readers that do not import encomp, `POLARS_UNKNOWN_EXTENSION_TYPE_BEHAVIOR=load_as_extension` preserves generic extension metadata; otherwise current Polars 1.x warns and loads the numeric storage type. -The Polars extension API is marked unstable upstream, so this integration is experimental even though encomp pins its required behavior in tests. +The Polars extension API is marked unstable upstream, so this integration is experimental even though encomp pins its required behavior in tests. One upstream limitation is worth knowing: Polars' Parquet *statistics* reader has no implementation for extension dtypes, so a predicate that gets pushed into a `pl.scan_parquet(...)` on a unit-typed column aborts with a Rust panic. `QuantityFrame.scan_parquet` shields the scan (at the cost of row-group skipping on those columns), and eager `pl.read_parquet` is unaffected—filter through one of those rather than a bare lazy scan. ## The `Fluid` class @@ -416,6 +418,8 @@ df.select( # composition={species: mole fraction} dict, and a fixed phase via assume_phase='gas' ``` +The fluid name, the output property, the input pair and the input units are all validated where the expression is *built*, so a typo raises there rather than inside a plugin node at `collect()` time. What remains deferred is per-row evaluation: a state CoolProp cannot fix yields `null` for that row (`NaN` on the NumPy path), not an error. + ### Implementation Scalar calls reuse a bounded thread-local cache of native `AbstractState` handles; a handle is never shared between threads. Arrays use the batched C API (`AbstractState_update_and_1_out`), with handle-table locking confined to construction and destruction. The flash loop itself runs lock-free in C++, so independent chunks and property expressions can execute in parallel. @@ -472,14 +476,14 @@ The attributes of `encomp.settings.Settings` are overridden with a file named `. Attribute names are prefixed with `ENCOMP_`. Settings are loaded when `encomp.settings` is imported; for runtime changes to quantity and unit rendering, use `encomp.units.set_quantity_format()`. -Because the `.env` file is resolved relative to the current working directory, a stray `.env` that sets an invalid `ENCOMP_*` value (for example `ENCOMP_UNITS` pointing at a missing file) makes `import encomp` fail with a `pydantic.ValidationError`, even in an unrelated project. Remove or correct the offending value; unrelated keys in the `.env` are ignored. +Because the `.env` file is resolved relative to the current working directory, a stray `.env` that sets an invalid `ENCOMP_*` value (for example `ENCOMP_UNITS` pointing at a missing file) makes importing any `encomp` submodule (such as `encomp.units`) fail with a `pydantic.ValidationError`, even in an unrelated project. The bare `import encomp` still succeeds—it only exposes `__version__`—so the traceback points at the first real import. Remove or correct the offending value; unrelated keys in the `.env` are ignored. Importing `encomp.units` also registers a `typeguard` checker for `Quantity`, so `@typeguard.typechecked` and `encomp.misc.isinstance_types` compare dimensionality and magnitude type rather than falling back to a plain `isinstance`. -`import encomp` also installs `encomp.units.UNIT_REGISTRY` as pint's process-wide *application registry*. This is deliberate: every quantity in the process must come from that registry, or the dimensionality subclasses, the custom `[currency]` / `[normal]` dimensions and `on_redefinition="raise"` would silently not apply. The consequence is that another pint-based library in the same process gets encomp's registry (and its unit definitions, including the `Nm³` reinterpretation) after `import encomp`. Registry options that encomp pins — `force_ndarray`, `force_ndarray_like`, `autoconvert_offset_to_baseunit` — cannot be reassigned; a write that would change one is discarded and logs a warning. +Importing `encomp.units` (directly, or via any encomp submodule that uses it) also installs `encomp.units.UNIT_REGISTRY` as pint's process-wide *application registry*. This is deliberate: every quantity in the process must come from that registry, or the dimensionality subclasses, the custom `[currency]` / `[normal]` dimensions and `on_redefinition="raise"` would silently not apply. The consequence is that another pint-based library in the same process gets encomp's registry (and its unit definitions, including the `Nm³` reinterpretation) from that point on. Registry options that encomp pins — `force_ndarray`, `force_ndarray_like`, `autoconvert_offset_to_baseunit` — cannot be reassigned; a write that would change one is discarded and logs a warning. ## Documentation -The usage guide, example notebooks, and API reference are at [encomp.readthedocs.io](https://encomp.readthedocs.io). +The usage guide, example notebooks, and API reference are at [encomp.readthedocs.io](https://encomp.readthedocs.io). The [getting-started notebook](https://encomp.readthedocs.io/en/latest/notebooks/getting-started.html) is the shortest path from installation to working code. Release notes for each version are published as [GitHub Releases](https://github.com/wlaur/encomp/releases). diff --git a/docs/usage.md b/docs/usage.md index dfd54b0..0cb8f19 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -72,12 +72,12 @@ mf = Q(25, ureg.kg / ureg.h) ``` :::{warning} -`import encomp` installs {py:data}`encomp.units.UNIT_REGISTRY` as `pint`'s process-wide -*application registry*. Every quantity in the process must come from it, or the -dimensionality subclasses, the custom `[currency]` / `[normal]` dimensions and -`on_redefinition="raise"` would silently not apply. Another `pint`-based library in the -same process therefore gets `encomp`'s registry (and its unit definitions) after the -import. The registry options `force_ndarray`, `force_ndarray_like` and +Importing `encomp.units` (directly, or via any `encomp` submodule that uses it) installs +{py:data}`encomp.units.UNIT_REGISTRY` as `pint`'s process-wide *application registry*. +Every quantity in the process must come from it, or the dimensionality subclasses, the +custom `[currency]` / `[normal]` dimensions and `on_redefinition="raise"` would silently +not apply. Another `pint`-based library in the same process therefore gets `encomp`'s +registry (and its unit definitions) from that point on. The registry options `force_ndarray`, `force_ndarray_like` and `autoconvert_offset_to_baseunit` are pinned: a write that would change one is discarded and logs a warning. ::: @@ -291,6 +291,53 @@ Q(arr, "bar") # [0.0 0.2 0.4 0.6000000000000001 0.8 1.0] bar ``` +Missing values follow the convention of whichever container holds them: `NaN` in the NumPy +world, `null` in the Polars world (where `NaN` is an ordinary float value, not a missing +marker — `mean()` propagates it while it skips `null`). `encomp` never *produces* a `NaN` +in a Polars magnitude: a CoolProp state that cannot be fixed yields `null` for that row, +and the corresponding NumPy result is `NaN`. {py:meth}`encomp.units.Quantity.astype` +translates between the two spellings at the boundary: + +```python +import numpy as np +import polars as pl + +from encomp.units import Quantity as Q + +# crossing into the Polars world turns the NumPy missing marker into null +assert Q(np.array([1.0, np.nan]), "bar").astype("pl.Series").m.to_list() == [1.0, None] + +# and back again, since NumPy has no other spelling for it +assert np.isnan(Q(pl.Series([1.0, None]), "bar").astype("ndarray").m[1]) + +# a NaN you put in a Series is kept as-is: it is data, not a missing marker +assert Q(pl.Series([1.0, float("nan")]), "bar").m.null_count() == 0 +``` + +The translation is lossless from NumPy and back, but not the other way around: NumPy has a +single spelling for both meanings, so a *data* `NaN` that goes out to NumPy and comes back +returns as missing. The same one-way collapse happens at the CoolProp boundary, where a +`NaN` input is a state that cannot be fixed and the polars result is therefore `null`: + +```python +import polars as pl + +from encomp.fluids import Water +from encomp.units import Quantity as Q + +data_nan = Q(pl.Series([1.0, float("nan")]), "bar") +assert data_nan.astype("ndarray").astype("pl.Series").m.to_list() == [1.0, None] + +water = Water[pl.Series](P=Q(pl.Series([1e5, 1e5]), "Pa"), T=Q(pl.Series([300.0, float("nan")]), "K")) +density = water.D +assert density.m.null_count() == 1 +assert not density.m.is_nan().any() +``` + +Arithmetic is the exception the rule names explicitly: `0.0 / 0.0` on a polars magnitude +produces a `NaN` in the polars world, exactly as it does in plain polars, because that is +IEEE float arithmetic on data rather than a missing value encomp introduced. + ### Quantities with expression magnitudes Polars Expressions can be used as magnitude: @@ -454,6 +501,66 @@ Q(4.19, "kJ/kg/K") * Q(5, "K") # ≈ 20.95 kJ/kg The environment variable `ENCOMP_AUTOCONVERT_OFFSET_TO_BASEUNIT` can be set to `True` to disable this error (this is not recommended). ::: +Only the *multiplicative* temperature units (`K`, `degR`, `mK`, ...) are ambiguous between +the two dimensionalities; `degC`/`degF` mean absolute and `delta_degC`/`delta_degF` mean a +difference. The unit is therefore what decides the dimensionality, and +{py:meth}`encomp.units.Quantity.asdim` — the explicit reinterpretation — rewrites the unit +so it can never contradict the dimensionality it is paired with: + +```python +from encomp.units import Quantity as Q +from encomp.utypes import Temperature, TemperatureDifference + +# K expresses both readings, so the unit is unchanged by the reinterpretation +assert Q(300, "K").dt is Temperature +assert Q(300, "K").asdim(TemperatureDifference).u == Q.get_unit("K") + +# an offset or delta unit is not ambiguous, so asdim() moves it onto the matching scale +assert Q(5, "delta_degC").asdim(Temperature).u == Q.get_unit("degC") +assert Q(5, "degC").asdim(TemperatureDifference).u == Q.get_unit("delta_degC") + +# a Quantity[...] annotation that disagrees with the unit is re-resolved from the unit +assert Q[Temperature, float](5.0, "delta_degC").dt is TemperatureDifference +``` + +A temperature difference is refused wherever an absolute temperature is required, even +though it converts to `K` by scale alone: {py:class}`encomp.fluids.Fluid` state inputs, +{py:func}`encomp.gases.ideal_gas_density` and the gas-condition tuples all raise +{py:class}`encomp.units.ExpectedDimensionalityError` rather than flash a difference as an +absolute state. + +NumPy functions type their results the same way the operators do: `np.subtract(a, b)` is +exactly `a - b`, `np.add(a, b)` is exactly `a + b`, and the functions that return a +*difference* of their input — `np.diff`, `np.ediff1d`, `np.gradient`, `np.ptp`, `np.std` — +return a `TemperatureDifference` for an absolute-temperature input: + +```python +import numpy as np + +from encomp.misc import isinstance_types +from encomp.units import Quantity as Q +from encomp.utypes import Numpy1DArray, Temperature, TemperatureDifference + +temperatures = Q(np.array([300.0, 600.0, 900.0]), "K") + +# a difference of absolute temperatures ... +assert isinstance_types(np.diff(temperatures), Q[TemperatureDifference, Numpy1DArray]) + +# ... while a mean of them is still an absolute temperature +assert isinstance_types(np.mean(temperatures), Q[Temperature, float]) +``` + +NumPy's own type stubs describe these functions as returning arrays, so the dimensionality +of the result is a runtime guarantee rather than a static one — use +{py:func}`encomp.misc.isinstance_types` (as above) or the operators, which are typed. + +:::{note} +Adding two absolute temperatures is refused for the offset spellings (`Q(25, "degC") + Q(25, "degC")` +raises `pint.errors.OffsetUnitCalculusError`) but not for the multiplicative ones: `Q(300, "K") + Q(300, "K")` +returns `600 K`, because `K` is a plain scale that pint cannot object to. Summing absolute +temperatures is rarely what you want in either spelling — subtract them, or add a difference. +::: + ### Currency units The dimensionality {py:class}`encomp.utypes.Currency` represents an arbitrary currency. diff --git a/encomp/conversion.py b/encomp/conversion.py index e40390e..fee16a0 100644 --- a/encomp/conversion.py +++ b/encomp/conversion.py @@ -48,12 +48,18 @@ def convert_volume_mass( Input mass or volume (or flow) rho : Quantity[Density, Any] Density of the substance. A *missing* density is allowed and yields a missing - result at that position, exactly as a missing ``inp`` does: ``NaN`` for float and - numpy magnitudes, ``null`` for a Polars Series. A Polars result carries null - (never NaN), so with a Polars ``inp`` a NaN density is rejected -- spell the - missing density as a null in a ``pl.Series``. Every *present* value must be - finite and strictly positive. Polars Expr inputs are deferred and cannot be - data-validated here. + result at that position, spelled the way each magnitude container spells missing: + ``NaN`` for float and numpy magnitudes, ``null`` for a Polars Series. Because a + NaN would be the wrong spelling on the Polars side -- and would reach the result + as a float value rather than as missing -- a NaN density is rejected when ``inp`` + is a Polars magnitude; spell it as a null in a ``pl.Series`` instead. Every + *present* value must be finite and strictly positive. Polars Expr inputs are + deferred and cannot be data-validated here. + + This guards the density argument only, which is where NaN means *missing*. A NaN + inside a Polars ``inp`` is ordinary float data (see :class:`encomp.units.Quantity`) + and propagates through the arithmetic as IEEE requires, so the result can carry a + NaN that came from ``inp``. Returns ------- diff --git a/encomp/coolprop/README.md b/encomp/coolprop/README.md index 7c7d8b0..ab86f51 100644 --- a/encomp/coolprop/README.md +++ b/encomp/coolprop/README.md @@ -17,8 +17,8 @@ from encomp import coolprop as cp df = pl.DataFrame({"P": [50e5, 60e5], "T": [400.0, 450.0], "R": [0.4, 0.6]}) # Pa, K, - df.select( - cp.water("DMASS", "P", "T").alias("rho"), # IF97 water/steam - cp.water("HMASS", "P", "T").alias("h"), # runs in parallel + cp.water("DMASS", "P", "T").alias("rho"), # IF97 water/steam + cp.water("HMASS", "P", "T").alias("h"), # runs in parallel cp.humid_air("W", "P", "T", "R").alias("humidity_ratio"), ) ``` diff --git a/encomp/coolprop/__init__.py b/encomp/coolprop/__init__.py index 334e7ab..ceb093a 100644 --- a/encomp/coolprop/__init__.py +++ b/encomp/coolprop/__init__.py @@ -112,6 +112,8 @@ def handle_counts(self) -> tuple[int, int]: ... "lib_version", "resolve_fluid_spec", "self_check", + "validate_fluid_config", + "validate_fluid_name", "water", ] @@ -528,6 +530,51 @@ def _resolve_pair(name1: str, name2: str) -> tuple[int, bool]: return _native().resolve_input_pair(name1, name2) +@cache +def validate_fluid_config(backend: Backend | str, fluids: str, fractions: tuple[float, ...] | None) -> None: + """Raise unless CoolProp can construct an ``AbstractState`` for a resolved fluid spec. + + Takes the ``(backend, fluids, fractions)`` triple :func:`resolve_fluid_spec` produces; + :func:`validate_fluid_name` is the name-level entry point most callers want. + + ``backend`` is the ``AbstractState`` backend (``"HEOS"``, ``"IF97"``, ``"INCOMP"``, + ``"BICUBIC&HEOS"``, ...) and ``fluids`` the ``&``-joined species (``"Water"``, + ``"CO2&O2"``). The backend is annotated as the :data:`Backend` Literal widened with + ``str``, like :data:`CName`: the Literal is what editors suggest, but CoolProp -- not + encomp -- is the authority on the name, so an unlisted backend must still pass. + + Cached per triple, so the 1000th ``Fluid("Water", ...)`` pays a dict lookup rather than + another CoolProp initialization. Only *resolved* names are remembered: + ``functools.cache`` stores return values, never exceptions, so an invalid name (or a + transient CoolProp error) is re-checked on the next call instead of being cached as + "this fluid does not exist". Use ``validate_fluid_config.cache_clear()`` to drop it. + """ + # Constructing the AbstractState is the only reliable way to ask CoolProp whether a + # name resolves (it covers pure fluids, INCOMP, and mixtures alike). It costs tens of + # microseconds for the usual backends -- and seconds the very first time a tabular + # backend builds its tables, a cost that would otherwise be paid at the first property + # access anyway. + _native().validate_fluid(backend, fluids, None if fractions is None else list(fractions)) + + +def validate_fluid_name(name: CName, composition: Composition | None = None) -> None: + """Raise ``ValueError`` unless ``name`` (+ ``composition``) is a CoolProp fluid. + + The single eager fluid-name check, shared by :class:`encomp.fluids.Fluid` and the + expression builders here: a misspelled name must fail where it is written, not at + ``collect()`` time inside a plugin node. Results are cached by + :func:`validate_fluid_config`. + """ + backend, fluids, fractions = resolve_fluid_spec(name, composition) + + try: + validate_fluid_config(backend, fluids, None if fractions is None else tuple(fractions)) + except Exception as e: + raise ValueError( + f"Fluid '{name}' could not be initialized, ensure that the name is a valid CoolProp fluid name" + ) from e + + @cache def _parameter_index(name: str) -> int: return _native().parameter_index(name) @@ -768,6 +815,17 @@ def fluid( convert first (e.g. ``encomp.polars.quantities`` + ``.to(...)``), or pass raw SI values via ``.ext.storage()``. """ + # The plugin resolves the output name and the fluid inside the query engine, so a typo + # in either would only surface at collect() time, in CoolProp's words and with the + # plugin .so path in the expression dump. Both checks are cheap and cached, and + # encomp.fluids validates eagerly, so do the same here. + try: + _parameter_index(output) + except ValueError as e: + raise ValueError( + f"fluid output must be a CoolProp property name (DMASS, HMASS, T, ...); got {output!r}." + ) from e + name1, name2 = _input_name(input1), _input_name(input2) for nm in (name1, name2): if not is_fluid_input(nm): @@ -784,6 +842,7 @@ def fluid( a, b = (input2, input1) if swap else (input1, input2) # canonical order name_a, name_b = (name2, name1) if swap else (name1, name2) a_expr, b_expr = _as_expr(a), _as_expr(b) + validate_fluid_name(name, composition) backend, fluids, fractions = resolve_fluid_spec(name, composition) phase = _phase_from_assumed(assume_phase) if assume_phase is not None else None diff --git a/encomp/fluids.py b/encomp/fluids.py index 8369c38..5dc3afc 100644 --- a/encomp/fluids.py +++ b/encomp/fluids.py @@ -5,7 +5,6 @@ from abc import ABC, abstractmethod from collections import OrderedDict from collections.abc import Mapping -from functools import cache from threading import Lock from typing import Annotated, Any, ClassVar, Generic, Literal, Self, TypedDict, Unpack, cast @@ -23,11 +22,11 @@ HumidAirParam, _fluid_scalar, # pyright: ignore[reportPrivateUsage] _humid_air_scalar, # pyright: ignore[reportPrivateUsage] - _native, # pyright: ignore[reportPrivateUsage] _resolve_pair, # pyright: ignore[reportPrivateUsage] is_fluid_param, is_humid_air_param, resolve_fluid_spec, + validate_fluid_name, ) from .settings import SETTINGS from .structures import flatten @@ -59,6 +58,7 @@ SpecificInternalEnergy, SurfaceTension, Temperature, + TemperatureDifference, ThermalConductivity, Velocity, ) @@ -98,7 +98,11 @@ "Variable", "N/A", ] -BackendKind = Literal["fluid", "humid_air"] +# Which native CoolProp interface evaluates a property: AbstractState (fluid) or +# HAPropsSI (humid air). NOT a CoolProp *backend* -- that word is reserved for the +# equation of state in a fluid name ("HEOS", "IF97", "INCOMP", ...), and encomp has +# exactly one evaluation implementation (the bundled library) to choose between +NativeInterface = Literal["fluid", "humid_air"] class FluidState(TypedDict, Generic[MT], total=False): # noqa: UP046 @@ -215,32 +219,6 @@ def clear_expr_evaluation_cache() -> None: _EXPR_EVALUATION_CACHE.clear() -@cache -def _resolve_fluid_name(backend: str, fluids: str, fractions: tuple[float, ...] | None) -> None: - # Constructing the AbstractState is the only reliable way to ask CoolProp whether a - # name resolves (it covers pure fluids, INCOMP, and mixtures alike). It costs tens of - # microseconds for the usual backends -- and seconds the very first time a tabular - # backend builds its tables, a cost that would otherwise be paid at the first property - # access anyway. Cached per (backend, fluids), so the 1000th Fluid("Water", ...) pays - # a dict lookup rather than another CoolProp initialization. - # - # functools.cache stores return values, never exceptions, so only a resolved name is - # remembered: a failure (an invalid name, or a transient CoolProp error) is re-checked - # on the next call instead of being cached as "this fluid does not exist". - _native().validate_fluid(backend, fluids, None if fractions is None else list(fractions)) - - -def _validate_fluid_name(name: CName, composition: Composition | None = None) -> None: - backend, fluids, fractions = resolve_fluid_spec(name, composition) - - try: - _resolve_fluid_name(backend, fluids, None if fractions is None else tuple(fractions)) - except Exception as e: - raise ValueError( - f"Fluid '{name}' could not be initialized, ensure that the name is a valid CoolProp fluid name" - ) from e - - def _expr_cache_digest(expr: pl.Expr) -> str: serialized = expr.meta.undo_aliases().meta.serialize(format="json") return hashlib.blake2s(serialized.encode(), digest_size=16).hexdigest() @@ -264,7 +242,7 @@ def _get_expr_evaluation_cache_key( fluid_any._assumed_phase, comp_key, output, - type(fluid).BACKEND_KIND, + type(fluid).NATIVE_INTERFACE, tuple((prop, _expr_cache_digest(expr)) for prop, expr in points), ) @@ -290,7 +268,7 @@ class CoolPropFluid(ABC, Generic[MT]): # noqa: UP046 name: CName points: list[tuple[CProperty, Quantity[Any, MT] | Quantity[Any, float]]] - BACKEND_KIND: ClassVar[BackendKind] = "fluid" + NATIVE_INTERFACE: ClassVar[NativeInterface] = "fluid" # Assumed phase passed to both native interfaces. None means CoolProp determines # the phase; a value skips the expensive mixture phase-stability search. @@ -741,7 +719,7 @@ def _plugin_expr(self, output: CProperty, points: tuple[tuple[str, pl.Expr], ... ) names = [p[0] for p in points] exprs = [p[1] for p in points] - if self.BACKEND_KIND == "humid_air": + if self.NATIVE_INTERFACE == "humid_air": n1, n2, n3 = names # the plugin reads each input's property from its (aliased) name expr = _cprust.humid_air( @@ -801,12 +779,12 @@ def evaluate_single(self, output: CProperty, *points: tuple[CProperty, float]) - # Pair resolution is request validation, not a failed state evaluation. Do it # outside the try/NaN normalization so all magnitude containers reject an # unsupported pair consistently. - if self.BACKEND_KIND != "humid_air": + if self.NATIVE_INTERFACE != "humid_air": (name1, _), (name2, _) = points _resolve_pair(name1, name2) try: - if self.BACKEND_KIND == "humid_air": + if self.NATIVE_INTERFACE == "humid_air": (name1, value1), (name2, value2), (name3, value3) = points value = _humid_air_scalar(output, name1, value1, name2, value2, name3, value3) else: @@ -926,11 +904,14 @@ def construct_quantity( qty.ito(ret_unit) if convert_magnitude: - qty = qty.astype(self._mt) # ty: ignore[no-matching-overload] + qty = qty.astype(self._mt) if isinstance(qty.m, pl.Series): - # missing values surface as null, never NaN (the library's single sentinel) - qty.m = qty.m.fill_nan(None).cast(self._eager_series_output_dtype()) + # A computed polars magnitude carries null for missing, never NaN. astype() above + # already translates the sentinel when it converts from the numpy result, so the + # fill_nan is belt-and-braces for any future path that arrives here with a Series + # magnitude already in hand (convert_magnitude=False); the cast is load-bearing + qty.m = qty.m.fill_nan(None).cast(self._eager_series_output_dtype()) # ty: ignore[invalid-assignment] return cast("Quantity[Any, MT]", qty) @@ -945,6 +926,17 @@ def to_numeric_correct_unit( unit = self.get_coolprop_unit(prop) + # Temperature and TemperatureDifference share pint's [temperature] dimension, and a + # difference converts to K by scale alone, so .to() below would silently accept a + # difference wherever CoolProp wants an absolute temperature (300 ΔK flashing as + # 300 K). No CoolProp state input is a difference, so refuse the whole class here + if issubclass(qty.dt, TemperatureDifference) and unit.dimensionality == Temperature.dimensions: + raise ExpectedDimensionalityError( + f'CoolProp input for property "{prop}" must be an absolute Temperature, ' + f"passed a TemperatureDifference ({qty.u}); use .asdim(Temperature) if the " + "value really is an absolute temperature" + ) + try: m = qty.to(unit).m except DimensionalityError as e: @@ -1081,6 +1073,12 @@ def __init__( Name of the fluid. With ``composition`` this is the backend only (e.g. ``"HEOS"``); otherwise the full CoolProp name, optionally with fixed fractions (e.g. ``"HEOS::CO2[0.5]&O2[0.5]"``). + + For an incompressible solution the concentration is part of the name and is + **not** optional in practice: CoolProp reads a bare ``"INCOMP::MEG"`` as a + 0 % solution, i.e. pure water, without warning (996 kg/m³ at 25 °C, and NaN + at -20 °C where the intended glycol mixture is still liquid). Spell it out: + ``"INCOMP::MEG[0.5]"``. composition : Composition | None Fixed mixture composition as ``{species: mole fraction}`` (like CoolProp's ``"HEOS::CO2[0.5]&O2[0.5]"`` name syntax). Mole fractions must sum to 1. @@ -1104,7 +1102,7 @@ def __init__( else: self.name = name - _validate_fluid_name(self.name, self._composition) + validate_fluid_name(self.name, self._composition) points = self._build_points(kwargs) @@ -1428,7 +1426,7 @@ class HumidAir(CoolPropFluid[MT]): viscosity. This differs from ``Fluid.M``, which returns molar mass. """ - BACKEND_KIND: ClassVar[BackendKind] = "humid_air" + NATIVE_INTERFACE: ClassVar[NativeInterface] = "humid_air" STATE_INPUTS: ClassVar[frozenset[str]] = frozenset(HUMID_AIR_INPUTS) # CoolProp humid-air property names, units, and descriptions diff --git a/encomp/gases.py b/encomp/gases.py index 0db8ab8..892f8f3 100644 --- a/encomp/gases.py +++ b/encomp/gases.py @@ -28,7 +28,7 @@ from .conversion import convert_volume_mass from .fluids import Fluid from .misc import isinstance_types -from .units import Quantity +from .units import ExpectedDimensionalityError, Quantity from .utypes import ( MT, Density, @@ -39,6 +39,7 @@ NormalVolumeFlow, Pressure, Temperature, + TemperatureDifference, Volume, VolumeFlow, ) @@ -77,7 +78,7 @@ def _n_s_condition(condition: Literal["N", "S"]) -> GasCondition: def _resolve_gas_condition(condition: object, name: str) -> GasCondition: if isinstance(condition, str): if condition in ("N", "S"): - return _n_s_condition(condition) # ty: ignore[invalid-argument-type] + return _n_s_condition(condition) raise ValueError(f"{name} must be 'N', 'S', or a (pressure, temperature) tuple, got {condition!r}") @@ -136,9 +137,17 @@ def ideal_gas_density( Density of the ideal gas at the specified temperature and pressure """ + # a temperature difference converts to K by scale alone, so it would flow into the gas + # law as an absolute temperature; the sibling _resolve_gas_condition rejects it too + if issubclass(T.dt, TemperatureDifference): + raise ExpectedDimensionalityError( + f"T must be an absolute Quantity[Temperature], passed a TemperatureDifference ({T.u}); " + "use .asdim(Temperature) if the value really is an absolute temperature" + ) + # directly from ideal gas law # override the inferred type here since it's sure to be Density - rho = (P * M) / (CONSTANTS.R * T.to("K").unknown()) # ty: ignore[unsupported-operator] + rho = (P * M) / (CONSTANTS.R * T.to("K").unknown()) return rho.to("kg/m³").asdim(Density) diff --git a/encomp/polars.py b/encomp/polars.py index d0500a2..c7f3095 100644 --- a/encomp/polars.py +++ b/encomp/polars.py @@ -25,15 +25,17 @@ from __future__ import annotations +import warnings from collections.abc import Mapping from pathlib import Path from typing import Any, ClassVar, Self, cast, overload import polars as pl +from pint.errors import UndefinedUnitError from . import utypes as _ut from ._polars_dtype import EXTENSION_NAME, UnitDType -from .units import DimensionalityTypeError, Quantity, Unit +from .units import DimensionalityError, DimensionalityTypeError, Quantity, Unit from .utypes import Dimensionality, UnknownDimensionality __all__ = [ @@ -321,6 +323,22 @@ def unit( return Column(probe.u, dimensionality, name=name, _explicit_dimensionality=asdim is not None) +def _shield_extension_pushdown(lf: pl.LazyFrame) -> pl.LazyFrame: + """Re-wrap every unit-typed column so predicates cannot be pushed into the scan. + + Polars' Parquet statistics reader has no implementation for extension dtypes and + aborts the process thread with a Rust ``PanicException`` when a pushed-down predicate + makes it decode statistics for one. The re-wrap is a metadata-only identity + (``storage`` -> same dtype) that the optimizer will not push a predicate through. + """ + unit_columns = [(name, dtype) for name, dtype in lf.collect_schema().items() if isinstance(dtype, UnitDType)] + + if not unit_columns: + return lf + + return lf.with_columns(pl.col(name).ext.storage().ext.to(dtype).alias(name) for name, dtype in unit_columns) + + class QuantityFrame: """Validated, lazy Polars frame with typed quantity-column descriptors.""" @@ -371,7 +389,14 @@ def __init__(self, frame: pl.DataFrame | pl.LazyFrame) -> None: # meaning. Pint permits scale-only conversions such as delta_degC -> K, # so compare semantic dimensionality after converting as well. source = Quantity(pl.col(declaration.name).ext.storage(), dtype.unit) - converted_quantity = source.to(declaration.unit) + try: + converted_quantity = source.to(declaration.unit) + except DimensionalityError as e: + # pint's own message names the two units but never the column, which is + # the one thing the caller needs to act on in a wide frame + raise DimensionalityTypeError( + f"Cannot read column {declaration.name!r} carrying {dtype.unit} as {declaration.unit}: {e}" + ) from e if converted_quantity.dt is not declaration.dimensionality: if not declaration.dimensionality_is_explicit: raise DimensionalityTypeError( @@ -388,6 +413,17 @@ def __init__(self, frame: pl.DataFrame | pl.LazyFrame) -> None: self.lf = lf.with_columns(conversions) + def __repr__(self) -> str: + # a summary that never collects the frame: the declared columns and their units, + # which is what a notebook user evaluating the object wants to see + declarations: list[str] = [] + + for declaration in self._unit_columns.values(): + unit_repr = f"{declaration.unit:~P}" or "dimensionless" + declarations.append(f"{declaration.name} [{unit_repr}]") + + return f"<{type(self).__name__}: {', '.join(declarations) or 'no declared columns'} (lazy)>" + @classmethod def from_untyped(cls, frame: pl.DataFrame | pl.LazyFrame) -> Self: """Assign the declared units to bare numeric columns, then validate.""" @@ -405,8 +441,17 @@ def from_untyped(cls, frame: pl.DataFrame | pl.LazyFrame) -> Self: @classmethod def scan_parquet(cls, source: str | Path) -> Self: - """Lazily scan a Parquet file and validate its persisted unit schema.""" - return cls(pl.scan_parquet(source)) + """Lazily scan a Parquet file and validate its persisted unit schema. + + A predicate on a unit-typed column is deliberately *not* pushed into the Parquet + scan: polars' Parquet statistics reader panics on any extension dtype + (``polars-parquet`` ``statistics.rs``, an upstream limitation, still present in + 1.43). An identity re-wrap of every unit-typed column shields the scan, at the + cost of row-group skipping for those columns. Filtering a bare + ``pl.scan_parquet(...)`` on a unit-typed column is what triggers the panic; go + through this method, or ``pl.read_parquet``, instead. + """ + return cls(_shield_extension_pushdown(pl.scan_parquet(source))) @classmethod def derive( @@ -444,8 +489,31 @@ def units_of(frame: pl.DataFrame | pl.LazyFrame) -> dict[str, Unit[Any]]: Reads only schema metadata: no data pass, and a ``LazyFrame`` (e.g. a ``pl.scan_parquet``) is not collected. Columns without a unit dtype are omitted. + + ``EXTENSION_NAME`` is a cross-process contract, so a file written by another producer + can carry unit metadata Pint cannot parse. Such a column is omitted and reported in a + ``UserWarning`` naming it, rather than making the whole frame uninspectable. """ - return {name: dtype.unit for name, dtype in frame.collect_schema().items() if isinstance(dtype, UnitDType)} + units: dict[str, Unit[Any]] = {} + unparseable: list[str] = [] + + for name, dtype in frame.collect_schema().items(): + if not isinstance(dtype, UnitDType): + continue + + try: + units[name] = dtype.unit + except UndefinedUnitError: + unparseable.append(f"{name!r} ({dtype.ext_metadata()!r})") + + if unparseable: + warnings.warn( + f"ignoring {EXTENSION_NAME!r} columns whose unit metadata is not a known unit: {', '.join(unparseable)}", + UserWarning, + stacklevel=2, + ) + + return units @overload @@ -468,6 +536,16 @@ def with_units(frame: pl.DataFrame | pl.LazyFrame, units: Mapping[str, str | Uni missing = sorted(set(units) - set(schema)) if missing: raise ValueError(f"unit schema keys are not columns of the frame: {missing}") - return frame.with_columns( - pl.col(name).ext.to(UnitDType(unit, storage=schema[name])) for name, unit in units.items() - ) + + dtypes: dict[str, UnitDType] = {} + for name, unit in units.items(): + try: + dtypes[name] = UnitDType(unit, storage=schema[name]) + except (TypeError, UndefinedUnitError) as e: + # the dtype constructor knows the bad storage dtype or unit string, but not + # which column it came from -- which is what the caller has to fix. A note + # adds that without rewriting an exception that renders its own message + e.add_note(f"offending unit schema entry: {name!r}: {unit!r}") + raise + + return frame.with_columns(pl.col(name).ext.to(dtype) for name, dtype in dtypes.items()) diff --git a/encomp/settings.py b/encomp/settings.py index 2de7538..10a123b 100644 --- a/encomp/settings.py +++ b/encomp/settings.py @@ -39,8 +39,10 @@ class Settings(BaseSettings): Because the ``.env``-file is resolved relative to the *current working directory*, a stray ``.env`` containing an invalid ``ENCOMP_*`` value - (e.g. ``ENCOMP_UNITS`` pointing to a missing file) makes ``import encomp`` - fail with a ``ValidationError`` -- even in an unrelated project. + (e.g. ``ENCOMP_UNITS`` pointing to a missing file) makes importing any + ``encomp`` submodule (e.g. ``encomp.units``) fail with a ``ValidationError`` + -- even in an unrelated project. The bare ``import encomp`` succeeds: the + top-level package exposes only ``__version__``. * ``UNITS``: path to a file with unit definitions for ``pint`` * ``TYPESET_SYMBOL_SCRIPTS``: whether to typeset SymPy symbol sub- and superscripts diff --git a/encomp/tests/test_comparisons.py b/encomp/tests/test_comparisons.py index b3642c8..940853a 100644 --- a/encomp/tests/test_comparisons.py +++ b/encomp/tests/test_comparisons.py @@ -1,14 +1,15 @@ import math import operator from collections.abc import Callable -from typing import Any, assert_type +from typing import Any, assert_type, cast import numpy as np import polars as pl import pytest +from ..units import DimensionalityComparisonError from ..units import Quantity as Q -from ..utypes import Numpy1DBoolArray +from ..utypes import Numpy1DBoolArray, TemperatureDifference def _assert_type(val: object, typ: type) -> None: @@ -330,3 +331,44 @@ def test_numpy_and_polars_disagree_on_nan_ordering() -> None: with_null = Q(pl.Series([1.0, None]), "m") assert (with_null > one).to_list() == [False, None] assert (with_null <= one).to_list() == [True, None] + + +def test_temperature_and_difference_comparisons() -> None: + # Temperature and TemperatureDifference share [temperature] but are not comparable. + # == answers False (Python convention: 1 == "a" is False), while the ordering operators + # raise because no answer is correct -- in every magnitude container + absolute = Q(5.0, "degC") + difference = Q(5.0, "delta_degC") + + # the type checkers reject these comparisons statically; cast to reach the runtime + assert (absolute == cast(Any, difference)) is False + assert (absolute != cast(Any, difference)) is True + + for op in (operator.gt, operator.ge, operator.lt, operator.le): + with pytest.raises(DimensionalityComparisonError): + op(absolute, difference) + with pytest.raises(DimensionalityComparisonError): + op(difference, absolute) + + # the same holds for the ambiguous kelvin spelling, where only the dimensionality differs + kelvin_absolute = Q(300.0, "K") + kelvin_difference = Q(300.0, "K").asdim(TemperatureDifference) + + assert (kelvin_absolute == cast(Any, kelvin_difference)) is False + with pytest.raises(DimensionalityComparisonError): + _ = kelvin_absolute > cast(Any, kelvin_difference) + + # within one dimensionality, comparisons work across scales as usual + assert Q(5.0, "delta_degC") == Q(5.0, "K").asdim(TemperatureDifference) + assert Q(25.0, "degC") > Q(200.0, "K") + + # ... and elementwise for the vector containers + array_differences = Q(np.array([5.0, 10.0]), "delta_degC") + array_mask = array_differences > Q(6.0, "delta_degC") + # pyrefly resolves the constrained magnitude TypeVar to the scalar (bool) overload here + assert array_mask.tolist() == [False, True] # pyrefly: ignore[missing-attribute] + + series_absolute = Q(pl.Series([25.0, 30.0]), "degC") + assert (series_absolute > Q(26.0, "degC")).to_list() == [False, True] + with pytest.raises(DimensionalityComparisonError): + _ = series_absolute > cast(Any, array_differences.astype("pl.Series")) diff --git a/encomp/tests/test_conversion.py b/encomp/tests/test_conversion.py index 429cb55..90e3076 100644 --- a/encomp/tests/test_conversion.py +++ b/encomp/tests/test_conversion.py @@ -120,3 +120,25 @@ def test_nan_density_is_rejected_for_polars_inputs() -> None: # the same missing density is fine for float/numpy inputs, where NaN IS the sentinel assert np.isnan(convert_volume_mass(Q(2.0, "kg"), rho=Q(float("nan"), "kg/m³")).m) + + +def test_nan_in_a_polars_input_is_data_not_a_missing_density() -> None: + # the NaN guard covers the DENSITY, where NaN means missing. A NaN inside inp is + # ordinary float data (Quantity's rule for polars magnitudes) and propagates through + # the arithmetic as IEEE requires -- it is not rewritten to null, and not rejected + result = convert_volume_mass( + Q(pl.Series([2.0, float("nan")]), "kg"), + rho=Q(pl.Series([1000.0, 1000.0]), "kg/m³"), + ) + + assert result.m.to_list()[0] == pytest.approx(0.002) + assert result.m.is_nan().to_list() == [False, True] + assert result.m.null_count() == 0 + + # a null inp stays the missing sentinel it already is + missing = convert_volume_mass( + Q(pl.Series([2.0, None]), "kg"), + rho=Q(pl.Series([1000.0, 1000.0]), "kg/m³"), + ) + + assert missing.m.to_list() == [pytest.approx(0.002), None] diff --git a/encomp/tests/test_coolprop_plugin.py b/encomp/tests/test_coolprop_plugin.py index 6a51f55..80a8271 100644 --- a/encomp/tests/test_coolprop_plugin.py +++ b/encomp/tests/test_coolprop_plugin.py @@ -342,13 +342,21 @@ def test_invalid_input_pair_raises_before_plugin_execution() -> None: def test_invalid_runtime_names_raise_python_exceptions() -> None: - df = pl.DataFrame({"P": [101325.0], "T": [300.0]}) + # a bad output property or fluid name is rejected where the expression is BUILT, not + # at collect() time from inside a plugin node (which reports CoolProp's own words plus + # the plugin .so path in an expression dump) + with pytest.raises(ValueError, match="must be a CoolProp property name"): + cp.water(cast(Any, "NOT_A_PROPERTY"), "P", "T") - with pytest.raises(Exception, match="unknown parameter"): - df.select(cp.water(cast(Any, "NOT_A_PROPERTY"), "P", "T")) + with pytest.raises(ValueError, match="could not be initialized"): + cp.fluid("DMASS", "P", "T", name=cast(Any, "NotAFluid")) with pytest.raises(Exception, match="interior NUL"): - df.select(cp.fluid("DMASS", "P", "T", name=cast(Any, "Water\0bad"))) + cp.fluid("DMASS", "P", "T", name=cast(Any, "Water\0bad")) + + # a valid name and output still build a plan and evaluate + df = pl.DataFrame({"P": [101325.0], "T": [300.0]}) + assert df.select(cp.water("DMASS", "P", "T")).item() == pytest.approx(996.5, abs=1.0) def test_duplicate_state_inputs_raise() -> None: diff --git a/encomp/tests/test_fluids.py b/encomp/tests/test_fluids.py index 126d684..6f42184 100644 --- a/encomp/tests/test_fluids.py +++ b/encomp/tests/test_fluids.py @@ -11,6 +11,7 @@ from encomp import coolprop as encomp_coolprop from .. import utypes as ut +from ..coolprop import validate_fluid_config from ..fluids import ( CoolPropFluid, CProperty, @@ -19,10 +20,10 @@ HumidAir, HumidAirState, Water, - _resolve_fluid_name, clear_expr_evaluation_cache, ) from ..settings import SETTINGS +from ..units import ExpectedDimensionalityError from ..units import Quantity as Q from ..utypes import DT, Density, SpecificEntropy @@ -330,25 +331,25 @@ def test_invalid_fluid_name_rejected_at_construction() -> None: def test_fluid_name_validation_is_cached() -> None: # a valid name is resolved by CoolProp once; every later construction is a cache hit - _resolve_fluid_name.cache_clear() + validate_fluid_config.cache_clear() for _ in range(50): Fluid("Water", P=Q(2, "bar"), T=Q(25, "degC")) - info = _resolve_fluid_name.cache_info() + info = validate_fluid_config.cache_info() assert info.misses == 1 assert info.hits == 49 # an invalid name is NOT cached: functools.cache stores return values, not exceptions, # so a transient CoolProp failure can never be remembered as "this fluid is invalid" - _resolve_fluid_name.cache_clear() + validate_fluid_config.cache_clear() for _ in range(3): with pytest.raises(ValueError, match="could not be initialized"): Fluid("Watr", P=Q(2, "bar"), T=Q(25, "degC")) - assert _resolve_fluid_name.cache_info().currsize == 0 + assert validate_fluid_config.cache_info().currsize == 0 def test_invalid_fluid_state_repr_does_not_raise() -> None: @@ -1220,3 +1221,28 @@ def test_unknown_property_error_message_points_at_search() -> None: # dunder probing (copy, pickle, inspect) still fails plainly with pytest.raises(AttributeError): fluid.__deepcopy__ + + +def test_state_inputs_refuse_a_temperature_difference() -> None: + # Temperature and TemperatureDifference share pint's [temperature], and a difference + # converts to K by scale alone, so .to() alone would let a difference fix a state: + # 300 ΔK would silently flash as 300 K. No CoolProp state input is a difference + difference = Q(300.0, "K").asdim(ut.TemperatureDifference) + + with pytest.raises(ExpectedDimensionalityError, match="absolute Temperature"): + Water(P=Q(1.0, "bar"), T=cast(Any, difference)).D + + with pytest.raises(ExpectedDimensionalityError, match="absolute Temperature"): + Fluid("Water", P=Q(1.0, "bar"), T=cast(Any, Q(5.0, "delta_degC"))).D + + with pytest.raises(ExpectedDimensionalityError, match="absolute Temperature"): + HumidAir(T=cast(Any, difference), P=Q(1.0, "bar"), R=Q(0.5)).W + + # explicitly reinterpreted, the same value is accepted + absolute = difference.asdim(ut.Temperature) + assert Water(P=Q(1.0, "bar"), T=absolute).D.m == approx(996.5, abs=1.0) + + # a difference is still fine where CoolProp expects one dimensionally: none of the + # state inputs do, so the guard covers every temperature input including Tdb/Twb + with pytest.raises(ExpectedDimensionalityError, match="absolute Temperature"): + HumidAir(Tdb=cast(Any, difference), P=Q(1.0, "bar"), R=Q(0.5)).W diff --git a/encomp/tests/test_gases.py b/encomp/tests/test_gases.py index 3a965c7..9bea4a4 100644 --- a/encomp/tests/test_gases.py +++ b/encomp/tests/test_gases.py @@ -12,6 +12,7 @@ mass_to_normal_volume, normal_volume_to_actual_volume, ) +from ..units import ExpectedDimensionalityError from ..units import Quantity as Q from ..utypes import ( Density, @@ -20,6 +21,8 @@ NormalVolume, NormalVolumeFlow, Numpy1DArray, + Temperature, + TemperatureDifference, Volume, VolumeFlow, ) @@ -88,6 +91,21 @@ def test_ideal_gas_density() -> None: assert mixed.m[0] == approx(vector.m[0]) +def test_ideal_gas_density_refuses_a_temperature_difference() -> None: + # a ΔT converts to K by scale alone, so without this guard a difference would flow + # into the gas law as an absolute temperature and silently produce a density + difference = Q(300.0, "K").asdim(TemperatureDifference) + + with pytest.raises(ExpectedDimensionalityError, match="absolute"): + # pyrefly cannot solve the constrained magnitude TypeVar across several arguments + ideal_gas_density(Q(1.0, "bar"), cast(Any, difference), Q(29.0, "g/mol")) # pyrefly: ignore[bad-specialization] + + # the same value, explicitly reinterpreted as absolute, is accepted + absolute = difference.asdim(Temperature) + density = ideal_gas_density(Q(1.0, "bar"), absolute, Q(29.0, "g/mol")) # pyrefly: ignore[bad-specialization] + assert density.m == approx(1.1626, abs=1e-3) + + def test_gas_conversion() -> None: V = Q(1, "liter") m = Q(1, "kg") diff --git a/encomp/tests/test_polars_units.py b/encomp/tests/test_polars_units.py index dec9007..68187e2 100644 --- a/encomp/tests/test_polars_units.py +++ b/encomp/tests/test_polars_units.py @@ -159,6 +159,22 @@ class KelvinDifference(QuantityFrame): assert_type(Difference.value, Column[TemperatureDifference]) +def test_declaring_a_difference_with_an_offset_unit_rewrites_the_stored_unit() -> None: + # unit(..., asdim=...) goes through Quantity.asdim, which moves an offset unit onto the + # matching delta scale -- so the persisted metadata can never say "absolute °C" for a + # column declared to hold differences + class Differences(QuantityFrame): + value = unit("degC", asdim=TemperatureDifference) + + assert Differences.value.unit == Unit("delta_degC") + assert Differences.value.dimensionality is TemperatureDifference + + typed = Differences.from_untyped(pl.DataFrame({"value": [5.0]})) + assert units_of(typed.lf) == {"value": Unit("delta_degC")} + assert typed.value.dt is TemperatureDifference + assert typed.lf.collect()["value"].ext.storage().to_list() == [5.0] + + def test_quantity_frame_boundaries_are_explicit() -> None: with raises(TypeError, match="from_untyped"): Sensors(pl.DataFrame({"pressure": [1.0], "Volume flow": [1.0]})) @@ -278,6 +294,27 @@ def test_parquet_round_trip(tmp_path: Path) -> None: assert units_of(pl.read_parquet(sunk)) == {"P": Unit("bar"), "V": Unit("m³/h")} +def test_parquet_round_trip_preserves_null_and_nan_distinctly(tmp_path: Path) -> None: + # null (missing) and NaN (a float value) are different things in polars, and the unit + # dtype must not collapse one into the other -- not even through an on-read conversion + path = tmp_path / "temperatures.parquet" + stored = with_units(pl.DataFrame({"value": [25.0, None, float("nan")]}), {"value": "degC"}) + stored.write_parquet(path) + + back = pl.read_parquet(path)["value"].ext.storage() + assert back.to_list()[0] == 25.0 + assert back.null_count() == 1 + assert back.is_nan().to_list() == [False, None, True] + + class Kelvin(QuantityFrame): + value = unit("K") + + converted = Kelvin.scan_parquet(path).lf.collect()["value"].ext.storage() + assert converted.to_list()[0] == pytest.approx(298.15) + assert converted.null_count() == 1 + assert converted.is_nan().to_list() == [False, None, True] + + def test_coolprop_import_registers_before_parquet_read(tmp_path: Path) -> None: path = tmp_path / "bar.parquet" with_units(pl.DataFrame({"P": [5.0], "T": [400.0]}), {"P": "bar", "T": "K"}).write_parquet(path) @@ -437,6 +474,65 @@ def test_quantity_frame_derive_validates_assignments() -> None: Report.power.assign(cast(Any, sensors.pressure)) +def test_scan_parquet_survives_predicate_pushdown(tmp_path: Path) -> None: + # polars' parquet statistics reader has no implementation for extension dtypes and + # aborts a worker thread with a Rust panic when a pushed-down predicate makes it decode + # them (upstream; reproduced on 1.42 and 1.43). QuantityFrame.scan_parquet inserts an + # identity re-wrap that the optimizer will not push a predicate through + path = tmp_path / "sensors.parquet" + frame = pl.DataFrame({"pressure": [10.0, 60.0, 100.0], "Volume flow": [1.0, 2.0, 3.0]}) + Sensors.from_untyped(frame).lf.sink_parquet(path) + + scanned = Sensors.scan_parquet(path) + filtered = scanned.lf.filter(pl.col("pressure").ext.storage() > 50.0).collect() + + assert filtered["pressure"].ext.storage().to_list() == [60.0, 100.0] + assert filtered.schema["pressure"] == UnitDType("bar") + + # is_null() decodes statistics too, and a non-declared unit column is shielded as well + assert scanned.lf.filter(pl.col("Volume flow").ext.storage().is_null()).collect().height == 0 + + # pin the upstream bug itself: when it is fixed, this fails and the shield (and the + # note in QuantityFrame.scan_parquet) can be revisited + with raises(BaseException, match=r"not yet implemented|statistics"): + pl.scan_parquet(path).filter(pl.col("pressure").ext.storage() > 50.0).collect() + + +def test_units_of_degrades_per_column_for_unparseable_metadata() -> None: + # EXTENSION_NAME is a cross-process contract: another producer can write metadata pint + # cannot parse. That must not make the well-formed columns of the frame uninspectable + good = with_units(pl.DataFrame({"good": [1.0]}), {"good": "bar"}) + bad_dtype = pl.BaseExtension(EXTENSION_NAME, pl.Float64(), "totally_bogus") + frame = good.with_columns(pl.Series("bad", [2.0]).cast(pl.Float64).ext.to(bad_dtype)) + + with pytest.warns(UserWarning, match="totally_bogus"): + assert units_of(frame) == {"good": Unit("bar")} + + +def test_typed_read_errors_name_the_column() -> None: + stored = with_units(pl.DataFrame({"pressure": [300.0], "Volume flow": [1.0]}), {"pressure": "degC"}) + + with raises(DimensionalityTypeError, match="column 'pressure'"): + Sensors(stored) + + with raises(TypeError, match="float or integer") as excinfo: + with_units(pl.DataFrame({"tag": ["a"]}), {"tag": "bar"}) + + assert any("'tag'" in note for note in excinfo.value.__notes__) + + with raises(UndefinedUnitError) as unit_excinfo: + with_units(pl.DataFrame({"P": [1.0]}), {"P": "bogus_unit"}) + + assert any("'P'" in note for note in unit_excinfo.value.__notes__) + + +def test_quantity_frame_repr_summarizes_declarations() -> None: + sensors = Sensors.from_untyped(pl.DataFrame({"pressure": [1.0], "Volume flow": [10.0]})) + + # a collection-free summary: the repr of a lazy frame must not evaluate it + assert repr(sensors) == "" + + def test_quantity_frame_round_trips_fluid_expression_output(tmp_path: Path) -> None: source_path = tmp_path / "states.parquet" result_path = tmp_path / "properties.parquet" diff --git a/encomp/tests/test_resolve_fluid_spec.py b/encomp/tests/test_resolve_fluid_spec.py index 8e09b99..e2b4d47 100644 --- a/encomp/tests/test_resolve_fluid_spec.py +++ b/encomp/tests/test_resolve_fluid_spec.py @@ -5,7 +5,7 @@ and ``encomp.coolprop.fluid`` delegate to it. """ -from typing import cast +from typing import Any, cast import pytest @@ -135,3 +135,21 @@ def test_is_humid_air_input() -> None: def test_phase_ignoring_backends() -> None: assert "IF97" in cp.PHASE_IGNORING_BACKENDS assert "HEOS" not in cp.PHASE_IGNORING_BACKENDS + + +def test_fluid_expression_validates_the_name_and_output_eagerly() -> None: + # a typo in either must fail where it is written; deferring it to collect() surfaces + # CoolProp's own words plus the plugin .so path in an expression dump + with pytest.raises(ValueError, match="could not be initialized"): + cp.fluid("DMASS", "P", "T", name=cast(Any, "Watr")) + + with pytest.raises(ValueError, match="must be a CoolProp property name"): + cp.water(cast(Any, "DMAS"), "P", "T") + + with pytest.raises(ValueError, match="could not be initialized"): + cp.validate_fluid_name(cast(Any, "HEOS::NotAFluid")) + + # valid names and outputs build a plan without touching the data + assert cp.fluid("DMASS", "P", "T", name="IF97::Water").meta.output_name() == "DMASS" + cp.validate_fluid_name("Water") + cp.validate_fluid_name("INCOMP::MEG[0.5]") diff --git a/encomp/tests/test_units.py b/encomp/tests/test_units.py index 9d88764..d9c5ee0 100644 --- a/encomp/tests/test_units.py +++ b/encomp/tests/test_units.py @@ -1332,8 +1332,9 @@ def test_indexing() -> None: def test_round() -> None: - # TODO: should this even work? - # type numpy.ndarray doesn't define __round__ method + # numpy does not define __round__ on ndarray and polars spells rounding as a method, + # so every container is routed explicitly -- round() must work identically for all of + # them, like abs() and the arithmetic operators do q = Q(25.12312312312, "kg/s") @@ -1348,8 +1349,21 @@ def test_round() -> None: assert q_r2.m[0] == 25.1 assert q_r2.m[1] == 25.1 - with pytest.raises(TypeError, match="round"): - round(Q([25.12312312312], "kg/s").astype(pl.Series), 1) + q_series = round(Q([25.12312312312], "kg/s").astype(pl.Series), 1) + + assert isinstance(q_series.m, pl.Series) + assert q_series.m.to_list() == [25.1] + assert q_series.u == Unit("kg/s") + + q_expr = round(Q(25.12312312312, "kg/s").astype(pl.Expr), 1) + + assert isinstance(q_expr.m, pl.Expr) + assert pl.select(q_expr.m).item() == 25.1 + + # round() with no digits, for every container + assert round(Q(25.6, "kg/s")).m == 26.0 + assert round(Q([25.6], "kg/s")).m[0] == 26.0 + assert round(Q([25.6], "kg/s").astype(pl.Series)).m.to_list() == [26.0] def test_abs() -> None: @@ -1457,9 +1471,44 @@ class Model(BaseModel): def test_float_cast() -> None: - assert isinstance(Q([False, False]).m[0], float) + # non-float numeric sequences normalize to a float64 magnitude + assert isinstance(Q([1, 2]).m[0], float) + assert Q([1, 2]).m.dtype == np.float64 + assert Q(np.array([1, 2], dtype=np.int8)).m.dtype == np.float64 + assert Q(np.array([1.5], dtype=np.float32)).m.dtype == np.float64 + + +def test_non_numeric_array_magnitudes_are_rejected() -> None: + # a bool magnitude is always a mistake (typically a comparison mask fed back in as + # data), and every other non-numeric dtype numpy would silently coerce is wrong data: + # a complex array loses its imaginary part, a datetime64 becomes its raw tick count. + # the pl.Series magnitude path refuses exactly the same set + for values in ( + [False, True], + np.array([False, True]), + np.array([True], dtype=object), + ): + with pytest.raises(TypeError, match="not a bool"): + Q(cast(Any, values), "m") + + for array in ( + np.array([1 + 2j, 3 + 4j]), + np.array([1, 2], dtype="datetime64[s]"), + np.array([1, 2], dtype="timedelta64[s]"), + ): + with pytest.raises(TypeError, match="must contain real numbers"): + Q(cast(Any, array), "m") + + with pytest.raises(TypeError, match="must contain real numbers"): + Q(cast(Any, [1 + 2j]), "m") + + # the in-place magnitude setter validates identically + qty = Q(np.array([1.0]), "m") + + with pytest.raises(TypeError, match="not a bool"): + qty.m = cast(Any, np.array([True])) - assert (Q([False, True]) == Q(np.array([False, True]))).all() + assert qty.m.tolist() == [1.0] def test_temperature_difference() -> None: @@ -1749,6 +1798,119 @@ def test_temperature_difference_to_multiplicative_units() -> None: _ = Q(300.0, "K").to("delta_degC") +def test_unit_and_dimensionality_never_contradict_for_temperature() -> None: + # a Quantity[Temperature] must never carry a Δ unit and a Quantity[TemperatureDifference] + # must never carry an offset one -- the unit and the dimensionality would then state + # different physical meanings. The two are handled differently on purpose: + + # an offset unit under a difference subclass is a mistake with no safe reading + with pytest.raises(DimensionalityTypeError, match="delta unit"): + Q[TemperatureDifference, float](5.0, "degC") + + # a delta unit under an absolute subclass re-resolves from the unit, exactly as every + # other mismatched subclass hint does (Q[Mass](1, "m") is a Length). It has to: pint + # builds T - T as self.__class__(magnitude, delta_unit) on the absolute class + reinterpreted = Q[Temperature, float](5.0, "delta_degC") + assert reinterpreted.dt is TemperatureDifference + assert reinterpreted.u == Unit("delta_degC") + assert (Q(35.0, "degC") - Q(25.0, "degC")).dt is TemperatureDifference + + # asdim is the deliberate reinterpretation, and it moves the unit to the matching + # scale in BOTH directions so the result cannot be misread + absolute = Q(5.0, "delta_degC").asdim(Temperature) + assert absolute.dt is Temperature + assert absolute.u == Unit("degC") + assert absolute.to("K").m == approx(278.15) + + difference = Q(5.0, "degC").asdim(TemperatureDifference) + assert difference.dt is TemperatureDifference + assert difference.u == Unit("delta_degC") + + # a multiplicative unit is genuinely ambiguous and stays as it is under both readings + assert Q(300.0, "K").dt is Temperature + assert Q(300.0, "K").asdim(TemperatureDifference).u == Unit("K") + assert Q[TemperatureDifference, float](300.0, "K").dt is TemperatureDifference + assert Q(300.0, "K").asdim(TemperatureDifference).asdim(Temperature).u == Unit("K") + + +def test_numpy_dispatch_types_temperature_differences_like_the_operator() -> None: + # numpy dispatch does not go through __sub__, and pint rebuilds the result from the + # unit: np.diff of a kelvin array used to come back as an absolute Temperature (and + # could then fix a CoolProp state as one), while `-` typed the same computation as a + # difference. degC was already correct because pint emits delta_degC there + kelvin = Q(np.array([300.0, 600.0, 900.0]), "K") + celsius = Q(np.array([25.0, 20.0, 30.0]), "degC") + + for func in (np.diff, np.ediff1d, np.gradient, np.ptp, np.std): + for absolute in (kelvin, celsius): + result = cast(Any, func(absolute)) + assert result.dt is TemperatureDifference, f"{func.__name__} on {absolute.u}" + + # np.subtract(a, b) is exactly a - b, and np.add(a, b) exactly a + b + # numpy's stubs type these as arrays, so the dimensionality is a runtime guarantee + assert cast(Any, np.subtract(kelvin[:1], kelvin[1:2])).dt is TemperatureDifference + subtracted = cast(Any, np.subtract(celsius[:1], celsius[1:2])) + assert subtracted.dt is TemperatureDifference + assert subtracted.u == Unit("delta_degC") + assert subtracted.m[0] == approx((celsius[0] - celsius[1]).m) + + # ... including the ambiguity the operator refuses: pint's ufunc path answered 45 °C + with pytest.raises(OffsetUnitCalculusError): + _ = np.add(celsius[:1], celsius[1:2]) + + difference = Q(np.array([5.0, 10.0]), "delta_degC") + assert cast(Any, np.add(Q(np.array([25.0, 25.0]), "degC"), difference)).dt is Temperature + assert cast(Any, np.diff(difference)).dt is TemperatureDifference + + # a mean of absolute temperatures stays absolute, and a squared result is neither + assert cast(Any, np.mean(celsius)).dt is Temperature + assert cast(Any, np.var(celsius)).dimensionality == Temperature.dimensions**2 + + +def test_temperature_arithmetic_includes_dimensionality_subclasses() -> None: + # a subclass takes part in every other T/ΔT rule (construction, asdim, conversion + # guards), so it takes part in the arithmetic that defines the concept too + class CustomDifference(TemperatureDifference): + dimensions = TemperatureDifference.dimensions + + class CustomTemperature(Temperature): + dimensions = Temperature.dimensions + + difference = Q[CustomDifference, float](5.0, "delta_degC") + absolute = Q[CustomTemperature, float](25.0, "degC") + + assert (Q(25.0, "degC") + difference).m == approx(30.0) + assert (Q(25.0, "degC") - difference).m == approx(20.0) + assert (difference + Q(25.0, "degC")).m == approx(30.0) + assert (absolute + Q(5.0, "delta_degC")).m == approx(30.0) + assert (absolute - Q(35.0, "degC")).dt is TemperatureDifference + + # ΔT - T stays an error for a subclass too (it is not a temperature) + with pytest.raises(DimensionalityTypeError): + _ = cast(Any, difference) - Q(25.0, "degC") + + +def test_subclassing_a_distinct_dimensionality_does_not_hijack_unit_resolution() -> None: + # _distinct is a claim by the class that sets it, not by its subclasses: inheriting it + # would make every degC/K quantity in the process resolve to the subclass instead + with _reset_dimensionality_registry(): + + class CustomTemperature(Temperature): + dimensions = Temperature.dimensions + + assert not CustomTemperature.is_distinct() + assert Q(25.0, "degC").dt is Temperature + assert Q(300.0, "K").dt is Temperature + + # an explicit claim still works, and an inherited False is still honoured + class ExplicitTemperature(Temperature): + _distinct = True + dimensions = Temperature.dimensions + + assert ExplicitTemperature.is_distinct() + assert Q(25.0, "degC").dt is ExplicitTemperature + + def test_temperature_unit_inputs() -> None: for unit in [ "degC", @@ -2214,6 +2376,82 @@ def test_floordiv_is_consistent_across_magnitude_containers() -> None: assert np.signbit(negative_infinity.m) +def test_division_by_zero_follows_the_container() -> None: + # this is the one arithmetic divergence between the containers, and it is deliberate: + # a float magnitude follows Python (dividing a float by zero raises, and a Quantity + # must not swallow that), while the vector containers follow IEEE elementwise, which + # is what their kernels do and what makes a per-row zero not abort a whole column + with pytest.raises(ZeroDivisionError): + _ = Q(1.0, "m") / Q(0.0, "s") + + with pytest.raises(ZeroDivisionError): + _ = Q(1.0, "m") // Q(0.0, "m") + + with np.errstate(divide="ignore", invalid="ignore"): + assert np.isinf((Q(np.array([1.0]), "m") // Q(np.array([0.0]), "m")).m).all() + + with np.errstate(divide="ignore", invalid="ignore"): + assert np.isinf((Q(np.array([1.0]), "m") / Q(np.array([0.0]), "s")).m).all() + assert np.isnan((Q(np.array([0.0]), "m") / Q(np.array([0.0]), "s")).m).all() + + series = Q(pl.Series([1.0, 0.0]), "m") / Q(pl.Series([0.0, 0.0]), "s") + assert series.m.to_list() == [float("inf"), pytest.approx(float("nan"), nan_ok=True)] + + expression = Q(pl.lit(1.0), "m") / Q(pl.lit(0.0), "s") + assert pl.select(expression.m).item() == float("inf") + + +def test_missing_values_follow_each_containers_own_sentinel() -> None: + # polars distinguishes null (absent) from NaN (a float value); numpy has only NaN for + # both. astype is the boundary between the two worlds, so it translates the sentinel + from_numpy = Q(np.array([1.0, np.nan]), "m").astype("pl.Series") + assert from_numpy.m.to_list() == [1.0, None] + assert not from_numpy.m.is_nan().any() + + # and back again: numpy can only spell it as NaN + to_numpy = Q(pl.Series([1.0, None]), "m").astype("ndarray") + assert to_numpy.m[0] == 1.0 + assert np.isnan(to_numpy.m[1]) + + # round trip through both worlds preserves the meaning + round_tripped = Q(np.array([1.0, np.nan]), "m").astype("pl.Series").astype("ndarray") + assert round_tripped.m[0] == 1.0 + assert np.isnan(round_tripped.m[1]) + + # a NaN the caller puts in a Series is data, not a missing marker: kept as-is (and + # IEEE arithmetic can produce one in either world -- see the division-by-zero test) + kept = Q(pl.Series([1.0, float("nan")]), "m") + assert kept.m.is_nan().to_list() == [False, True] + assert kept.m.null_count() == 0 + + # nulls survive unit conversion and arithmetic as nulls + nulls = Q(pl.Series([1.0, None]), "bar") + assert nulls.to("kPa").m.to_list() == [100.0, None] + assert (nulls * 2.0).m.to_list() == [2.0, None] + + # pl.Expr is the polars world too, so astype translates there as well + assert pl.select(v=Q(float("nan"), "m").astype("pl.Expr").m).to_series().to_list() == [None] + assert pl.select(v=Q(2.0, "m").astype("pl.Expr").m).to_series().to_list() == [2.0] + + # the polars -> numpy -> polars direction cannot round trip a data-NaN: numpy has one + # spelling for both meanings, so it comes back as missing. Deliberate and documented + collapsed = Q(pl.Series([1.0, float("nan")]), "m").astype("ndarray").astype("pl.Series") + assert collapsed.m.to_list() == [1.0, None] + + +def test_ndim_reports_one_for_every_vector_container() -> None: + assert Q(1.0, "m").ndim == 0 + assert Q(np.array([1.0, 2.0]), "m").ndim == 1 + + # neither polars type has an ndim attribute; a Series/Expr magnitude is still a vector + series = Q(pl.Series([1.0, 2.0]), "m") + assert series.ndim == 1 + assert not series.is_scalar + assert len(series) == 2 + + assert Q(pl.col("x"), "m").ndim == 1 + + def test_mixed_container_arithmetic_is_rejected() -> None: array = cast(Any, Q(np.array([1.0, 2.0]), "m")) series = cast(Any, Q(pl.Series([1.0, 2.0]), "m")) diff --git a/encomp/units.py b/encomp/units.py index 0581ec9..ad0b397 100644 --- a/encomp/units.py +++ b/encomp/units.py @@ -328,8 +328,9 @@ def __init(self) -> None: # pyright: ignore[reportUnusedFunction] # invoked by # Quantity in the process must come from encomp's registry, or the dimensionality # subclasses, the custom dimensions (currency, normal) and on_redefinition="raise" would # silently not apply. The trade-off is documented (README "Settings"): another pint-based -# library in the same process sees encomp's registry after `import encomp`. This is an -# intentional, settled design decision -- not an oversight to be re-flagged. +# library in the same process sees encomp's registry once this module is imported (the +# top-level `encomp` package exposes only __version__ and does NOT trigger this). This is +# an intentional, settled design decision -- not an oversight to be re-flagged. setattr(pint, "_DEFAULT_REGISTRY", UNIT_REGISTRY) # noqa: B010 cast(Any, pint.application_registry).set(UNIT_REGISTRY) @@ -460,8 +461,20 @@ class Quantity( separate dimensionality types even though both have Pint dimension ``[temperature]``. Use :meth:`to` for unit conversion and :meth:`asdim` only for explicit semantic reinterpretation between compatible dimensionality classes. + + Missing values follow the container's own convention: ``NaN`` in the numpy world, + ``null`` in the Polars world. encomp never *produces* a ``NaN`` in a Polars + magnitude -- anything it computes without a value (a CoolProp state that cannot be + fixed, for instance) is ``null`` -- and :meth:`astype` translates between the two + spellings at the boundary. A ``NaN`` the caller puts into a ``pl.Series`` is kept + as-is: it is a float value, not a missing marker, and IEEE arithmetic + (``0.0 / 0.0``) can produce one in either world. """ + # numpy array functions whose result is a DIFFERENCE of their input, so an absolute + # temperature input yields a TemperatureDifference (see __array_function__) + _DIFFERENCE_ARRAY_FUNCTIONS: ClassVar[frozenset[str]] = frozenset({"diff", "ediff1d", "gradient", "ptp", "std"}) + # constants NORMAL_M3_VARIANTS = ("nm³", "Nm³", "nm3", "Nm3", "nm**3", "Nm**3", "nm^3", "Nm^3") TEMPERATURE_DIFFERENCE_UCS = ( @@ -589,6 +602,52 @@ def __array__(self, t: Any | None = None, copy: bool = False, dtype: str | None return self._pint_super.__array__(t) + def __array_ufunc__(self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any) -> Any: # noqa: ANN401 + # np.subtract(a, b) must mean exactly a - b, and np.add(a, b) exactly a + b. numpy + # dispatch does not go through __sub__/__add__, and pint's ufunc path rebuilds the + # result from the unit alone -- which loses every rule those operators enforce: + # T - T is a TemperatureDifference (np.subtract typed it absolute, so a difference + # could go on to fix a CoolProp state as an absolute temperature), and T + T on an + # offset scale is ambiguous (np.add quietly answered 45 °C for 25 °C + 20 °C). + # Delegating keeps one implementation of the rules instead of two + if ( + method == "__call__" + and not kwargs + and len(inputs) == 2 + and all(isinstance(operand, Quantity) for operand in inputs) + ): + first, second = cast("tuple[Quantity[Any, Any], Quantity[Any, Any]]", inputs) + + if ufunc is np.subtract: + return first - second + + if ufunc is np.add: + return first + second + + return self._pint_super.__array_ufunc__(ufunc, method, *inputs, **kwargs) + + def __array_function__(self, func: Any, types: Any, args: Any, kwargs: Any) -> Any: # noqa: ANN401 + ret: Any = self._pint_super.__array_function__(func, types, args, kwargs) + + # the same reasoning as __array_ufunc__, for the array functions that return a + # DIFFERENCE of their input: pint types the result from the unit, which is right for + # an offset spelling (degC -> delta_degC) but leaves a multiplicative one absolute + # (np.diff of a K array stayed a Temperature). Re-type so they agree with `-` + if ( + isinstance(ret, Quantity) + and getattr(func, "__name__", "") in self._DIFFERENCE_ARRAY_FUNCTIONS + and issubclass(self.dt, Temperature) + ): + difference = cast("Quantity[Any, Any]", ret) # pyrefly: ignore[redundant-cast] # cast required by pyright + + # np.var and friends leave [temperature]**2 -- only re-type an actual temperature + if difference.dimensionality == Temperature.dimensions: + return difference.asdim(TemperatureDifference) + + # cast: the isinstance narrowing above leaves a partially-unknown pint type here, + # and this hook returns whatever numpy asked pint for + return cast(Any, ret) # ty: ignore[redundant-cast] + @staticmethod def validate_magnitude_type(mt: type) -> None: """Raise ``TypeError`` unless ``mt`` is a supported magnitude container type.""" @@ -887,6 +946,10 @@ def _validate_magnitude(val: MT | Sequence[float] | str | bytes | None) -> MT: raise ValueError(f"Only 1-dimensional NumPy arrays can be used as magnitude, got shape {val.shape}") return cast("MT", Quantity._cast_array_float(val)) elif isinstance(val, pl.Series): + # nulls (the polars missing sentinel) and NaNs are both kept verbatim: the + # magnitude is the caller's data, and a NaN here is a float value rather than + # a missing marker. Only values encomp itself computes are normalized to null + # (see the class docstring, and CoolPropFluid.construct_quantity) if val.dtype == pl.Null: return cast("MT", val.cast(pl.Float64)) if val.dtype.is_integer(): @@ -973,17 +1036,36 @@ def __reduce__( # pyright: ignore[reportIncompatibleMethodOverride] # pyrefly: @staticmethod def _cast_array_float(inp: np.ndarray) -> Numpy1DArray: - # don't fail in case the array contains unsupported objects, - # cast to float64, matches the Numpy1DArray type definition + # cast to float64, matching the Numpy1DArray type definition. The accepted dtype + # kinds are allow-listed rather than reject-listed, because the cast below is + # deliberately unsafe (float16 / int64 / ... must be accepted) and every other kind + # numpy can force into a float is silently WRONG data: a bool mask becomes 1.0/0.0, + # a complex array loses its imaginary part, and a datetime64/timedelta64 becomes its + # raw tick count. The pl.Series branch of _validate_magnitude refuses exactly the + # same set, so both vector containers agree on what a magnitude is + if inp.dtype.kind == "b": + # same message as the scalar check: a bool magnitude (typically a comparison + # mask fed back in as data) is always a mistake + raise TypeError("magnitude must be a real number, not a bool") + if inp.dtype.kind in {"S", "U"}: raise ValueError("magnitude sequences must contain real numbers, not strings") if inp.dtype.kind == "O": for item in inp: + # bool is an int subclass and passes numbers.Real, so it needs its own check + if isinstance(item, bool): + raise TypeError("magnitude must be a real number, not a bool") + if item is None or isinstance(item, (str, bytes)) or not isinstance(item, numbers.Real): raise ValueError( f"magnitude sequences must contain real numbers; got {type(item).__name__}: {item!r}" ) + elif inp.dtype.kind not in {"f", "i", "u"}: + raise TypeError( + f"magnitude sequences must contain real numbers, got dtype {inp.dtype!r}. " + "Complex, datetime, timedelta and other non-numeric arrays are not valid magnitudes" + ) if inp.dtype == np.float64: return cast("Numpy1DArray", inp) @@ -1254,9 +1336,21 @@ def __new__( f"Cannot construct TemperatureDifference with offset unit {valid_unit}; use a delta unit instead" ) + # The mirror case -- a delta unit under an absolute-temperature subclass -- cannot be + # an error, because pint itself takes that path: T - T builds the difference as + # self.__class__(magnitude, delta_unit) on the absolute class. It also cannot be + # accepted as-is (that is how a Quantity[Temperature] ended up carrying Δ°C), and the + # dimensions check below cannot catch it since Temperature and TemperatureDifference + # share pint's [temperature]. So re-resolve the subclass from the unit, exactly as + # the constructor does for every other mismatched hint (Q[Mass](1, "m") is a Length): + # the unit wins, and the result is a TemperatureDifference. + delta_unit_on_absolute_subclass = issubclass( + cls._dimensionality_type, Temperature + ) and cls._is_temperature_difference_unit(valid_unit) + is_valid_subclass = True - if cls._is_incomplete_dimensionality(cls._dimensionality_type): + if cls._is_incomplete_dimensionality(cls._dimensionality_type) or delta_unit_on_absolute_subclass: is_valid_subclass = False else: # compare dimensionalities with tolerance for float precision @@ -1381,6 +1475,25 @@ def _is_offset_temperature_unit(cls, unit: Unit[Any]) -> bool: registry = cast(Any, cls._REGISTRY) # _is_multiplicative is a stable pint internal return not all(registry._is_multiplicative(u) for u in unit._units) + @classmethod + def _as_absolute_temperature_unit(cls, unit: Unit[Any]) -> Unit[Temperature]: + # the mirror of _as_temperature_difference_unit: reinterpreting a difference as an + # absolute temperature moves a delta unit back onto its own absolute scale, so the + # unit can never contradict the dimensionality (5 Δ°C -> 5 °C, not 5 K) + if unit._units == Unit("delta_degC")._units: + return Unit("degC") + + if unit._units == Unit("delta_degF")._units: + return Unit("degF") + + if unit._units == Unit("delta_degRe")._units: + return Unit("degRe") + + if unit._units == Unit("delta_K")._units: + return Unit("K") + + return cast("Unit[Temperature]", unit) + @classmethod def _as_temperature_difference_unit(cls, unit: Unit[Any]) -> Unit[TemperatureDifference]: if unit._units == Unit("delta_K")._units: @@ -1660,7 +1773,9 @@ def correct_unit(unit: str) -> str: "Δ%": "%", "‰": "permille", "r/min": "rpm", - # ΔK does not really make sense, it's not an offset scale + # "ΔK" spells a temperature difference in kelvin: K is not an offset scale, so + # pint has no delta unit for it and encomp defines delta_K itself + # (defs/units.txt) -- it is how a TemperatureDifference is written in kelvin "Δ": "delta_", } @@ -1986,7 +2101,11 @@ def check_compatibility(self, other: Quantity[Any, Any] | float) -> None: """ if not isinstance(other, Quantity): - if not self.dimensionless: + # self.u.dimensionless, not self.dimensionless: pint's quantity-level property + # converts the whole magnitude to root units (an O(n) pass and a temporary on + # every q + 1.0 / q == 5.0 with a vector magnitude), while the unit-level check + # is magnitude-independent and answers identically for every registered unit + if not self.u.dimensionless: raise DimensionalityTypeError( f"Value {other} ({type(other)}) is not compatible with dimensional quantity {self} ({type(self)})" ) @@ -2021,7 +2140,7 @@ def check_compatibility(self, other: Quantity[Any, Any] | float) -> None: ) raise DimensionalityTypeError( - f"Quantities with different dimensionalities are not compatible: {type(self)} and {type(other)}. " + f"Quantities with different dimensionalities are not compatible: {type(self)} and {type(other)}." ) def is_compatible_with( @@ -2048,16 +2167,18 @@ def _temperature_difference_add_sub( other: Quantity[TemperatureDifference, Any] | Quantity[Temperature, Any], operator: Literal["add", "sub"], ) -> Quantity[Temperature, MT]: - if self.dt == Temperature: - assert other._dimensionality_type == TemperatureDifference + # issubclass, not identity: a user-defined subclass of either dimensionality takes + # part in the same arithmetic (it does in every other T/ΔT rule) + if issubclass(self.dt, Temperature): + assert issubclass(other._dimensionality_type, TemperatureDifference) v1 = self.to("degC").m v2 = other.to("delta_degC").m val = v1 + v2 if operator == "add" else v1 - v2 temperature_unit = self.u else: - assert self.dt == TemperatureDifference - assert other._dimensionality_type == Temperature + assert issubclass(self.dt, TemperatureDifference) + assert issubclass(other._dimensionality_type, Temperature) v1 = self.to("delta_degC").m v2 = other.to("degC").m @@ -2075,12 +2196,19 @@ def __round__(self, ndigits: int | None = None) -> Quantity[DT, MT]: if ndigits is None: ndigits = 0 - if isinstance(self.m, float): + m = self.m + + if isinstance(m, float): return cast("Quantity[DT, MT]", super().__round__(ndigits)) - elif isinstance(self.m, np.ndarray): - return cast("Quantity[DT, MT]", self.__class__(np.round(self.m, ndigits), self.u)) + elif isinstance(m, np.ndarray): + return cast("Quantity[DT, MT]", self.__class__(np.round(cast("Numpy1DArray", m), ndigits), self.u)) + elif isinstance(m, (pl.Series, pl.Expr)): + # every container rounds: polars spells it as a method on the object rather + # than through __round__, so route to it explicitly (a negative ndigits is + # rejected by polars, unlike numpy -- that difference is polars', not ours) + return self._call_subclass(cast("MT", m.round(ndigits)), self.u) else: - raise TypeError(f"round() is not supported for magnitude type {type(self.m)}") + raise TypeError(f"round() is not supported for magnitude type {type(m)}") @property def is_scalar(self) -> bool: @@ -2090,11 +2218,20 @@ def is_scalar(self) -> bool: @property def ndim(self) -> int: - """Number of magnitude dimensions: 0 for a scalar, 1 for a vector magnitude.""" + """Number of magnitude dimensions: 0 for a scalar, 1 for a vector magnitude. + + A ``pl.Expr`` magnitude is a column expression, so it counts as a vector even + though its length is unknown until the plan is collected. + """ if isinstance(self.m, (float, int)): return 0 + # neither pl.Series nor pl.Expr has an ndim attribute, and only 1-dimensional + # magnitudes exist in this library, so the vector containers answer 1 directly + if isinstance(self.m, (pl.Series, pl.Expr)): + return 1 + return getattr(self.m, "ndim", 0) def asdim(self, other: type[DT_] | Quantity[DT_, MT]) -> Quantity[DT_, MT]: @@ -2131,8 +2268,13 @@ def asdim(self, other: type[DT_] | Quantity[DT_, MT]) -> Quantity[DT_, MT]: ) unit: Unit[Any] = self.u - if dim == TemperatureDifference: + if issubclass(dim, TemperatureDifference): # ty: ignore[invalid-argument-type] unit = self._as_temperature_difference_unit(unit) + elif issubclass(dim, Temperature): # ty: ignore[invalid-argument-type] + # the reverse reinterpretation: a delta unit moves back onto its absolute scale, + # so a Quantity[Temperature] never carries a Δ unit (the constructor re-resolves + # such a pair to TemperatureDifference -- asdim is the deliberate escape hatch) + unit = self._as_absolute_temperature_unit(unit) subcls = self._get_dimensional_subclass(dim, type(self.m)) # ty: ignore[invalid-argument-type] return cast("Quantity[DT_, MT]", subcls(self.m, unit)) @@ -2164,6 +2306,11 @@ def astype(self, magnitude_type: type[Any] | MagnitudeTypeName) -> Quantity[Any, Polars ``Expr``, or the corresponding string names. Units and dimensionality are unchanged. Converting to ``pl.Expr`` is only defined for scalar quantities, which become a literal expression. + + Crossing between the numpy and Polars worlds translates the missing-value + sentinel, because each world has its own: a ``NaN`` in a numpy magnitude becomes + a ``null`` in the Polars magnitude, and a ``null`` becomes ``NaN`` on the way + back. """ if isinstance(magnitude_type, str): magnitude_type = self._get_magnitude_type_from_name(magnitude_type) @@ -2177,7 +2324,10 @@ def astype(self, magnitude_type: type[Any] | MagnitudeTypeName) -> Quantity[Any, return cast("Quantity[DT, Any]", self) elif magnitude_type is pl.Expr: if isinstance(m, float): - return cast("Quantity[DT, Any]", self.get_subclass(dt, pl.Expr)(pl.lit(m), u)) + # translate the missing-value sentinel, exactly like the pl.Series branch + # below: this crosses into the polars world, where missing is null + literal = pl.lit(None, dtype=pl.Float64) if math.isnan(m) else pl.lit(m) + return cast("Quantity[DT, Any]", self.get_subclass(dt, pl.Expr)(literal, u)) raise TypeError( f"Cannot convert magnitude with type {type(m)} to Polars expression, " @@ -2202,6 +2352,13 @@ def astype(self, magnitude_type: type[Any] | MagnitudeTypeName) -> Quantity[Any, ) _m = [m] if not isinstance(m, Iterable) else m vals = pl.Series(values=_m) + + # translate the missing-value sentinel: NaN is how the numpy world spells + # missing, null is how the polars world does. The reverse direction needs no + # code -- np.array() of a Series already renders null as NaN + if vals.dtype.is_float(): + vals = vals.fill_nan(None) + return cast("Quantity[DT, Any]", self.get_subclass(dt, pl.Series)(vals, u)) else: raise TypeError(f"Cannot convert magnitude from type {type(m)} to {magnitude_type}") @@ -2270,8 +2427,8 @@ def __add__(self, other: Quantity[Any, Any] | float) -> Quantity[Any, Any]: if not isinstance(other, Quantity): raise e - self_is_temp_or_diff_temp = self.dt in (Temperature, TemperatureDifference) - other_is_temp_or_diff_temp = other._dimensionality_type in (Temperature, TemperatureDifference) + self_is_temp_or_diff_temp = issubclass(self.dt, (Temperature, TemperatureDifference)) + other_is_temp_or_diff_temp = issubclass(other._dimensionality_type, (Temperature, TemperatureDifference)) if self_is_temp_or_diff_temp and other_is_temp_or_diff_temp: return self._temperature_difference_add_sub(other, "add") # ty: ignore[invalid-argument-type] @@ -2332,14 +2489,18 @@ def __sub__(self, other: Quantity[Any, Any] | float) -> Quantity[Any, Any]: # only Temperature - TemperatureDifference is meaningful here; the # reverse (ΔT - T) is not a temperature and stays an error - if self.dt == Temperature and other._dimensionality_type == TemperatureDifference: + if issubclass(self.dt, Temperature) and issubclass(other._dimensionality_type, TemperatureDifference): return self._temperature_difference_add_sub(other, "sub") # ty: ignore[invalid-argument-type] raise e ret = cast("Quantity[DT, MT]", self._pint_super.__sub__(other)) - if isinstance(other, Quantity) and self.dt == Temperature and other._dimensionality_type == Temperature: + if ( + isinstance(other, Quantity) + and issubclass(self.dt, Temperature) + and issubclass(other._dimensionality_type, Temperature) + ): _mt = type(ret.m) subcls = self._get_dimensional_subclass(TemperatureDifference, _mt) # ty: ignore[invalid-argument-type] return subcls(ret.m, ret.u) @@ -3158,7 +3319,12 @@ def __mul__(self, other: Quantity[Any, Any] | float) -> Quantity[Any, Any]: # preserve the dimensionality for other # it might be a distinct subclass with identical units as another dimensionality - if self.dimensionless and isinstance(other, Quantity): + # NOTE: isinstance first, and self.u.dimensionless rather than self.dimensionless: + # pint's quantity-level property converts the whole magnitude to root units, which + # is an extra O(n) pass plus a temporary on the hottest operator in the library -- + # even for q * 2.0, where this branch can never be taken. The unit-level check is + # magnitude-independent and answers identically for every registered unit + if isinstance(other, Quantity) and self.u.dimensionless: subcls = self.get_subclass(other._dimensionality_type, type(ret.m)) return subcls(ret) @@ -3624,11 +3790,19 @@ def __truediv__(self, other: Quantity[DT_, MT]) -> Quantity[UnknownDimensionalit def __truediv__(self, other: Quantity[Any, Any] | float) -> Quantity[Any, Any]: if isinstance(other, Quantity): self._check_comparable_magnitudes(self.m, other.m, "combine") # ty: ignore[invalid-argument-type] + + # NOTE: division by zero deliberately follows each container's own semantics rather + # than being normalized: a float magnitude raises ZeroDivisionError (Python's rule + # for floats, which a Quantity must not swallow), while ndarray and polars + # magnitudes yield IEEE ±inf / nan elementwise (numpy warns, polars is silent). + # Normalizing either way would mean lying about one container to match the other; + # pinned by test_division_by_zero_follows_the_container ret = cast("Quantity[DT, MT]", self._pint_super.__truediv__(other)) # preserve the dimensionality for other # it might be a distinct subclass with identical units as another dimensionality - if self.dimensionless and isinstance(other, Quantity): + # (see __mul__ for why this is a unit-level check with isinstance first) + if isinstance(other, Quantity) and self.u.dimensionless: subcls = self.get_subclass(other._dimensionality_type, type(ret.m)) return subcls(ret) @@ -3682,7 +3856,9 @@ def __floordiv__(self, other: Quantity[Any, Any] | float) -> Quantity[Any, Any]: if isinstance(other, (float, int)): return self._call_subclass(cast("MT", self._floor_magnitude(self.m / other)), self.u) - if other.dimensionless: + # unit-level check: the quantity-level property would convert other's whole + # magnitude to root units just to answer a question about its unit (see __mul__) + if other.u.dimensionless: magnitude = self._floor_magnitude(self.m / other.to_base_units().m) return self._call_subclass(magnitude, self.u) diff --git a/encomp/utypes.py b/encomp/utypes.py index 3a5de95..2225a2e 100644 --- a/encomp/utypes.py +++ b/encomp/utypes.py @@ -799,12 +799,24 @@ def get_dimensionality(cls, dimensions: UnitsContainer) -> type[Dimensionality]: def is_distinct(cls) -> bool: """Whether this class is the one :meth:`get_dimensionality` returns for its dimensions. - Unless ``_distinct`` is set explicitly, only the first registered subclass with a - given ``dimensions`` is distinct. The answer is registry-relative and can change - when a later subclass registers the same dimensions. + Unless ``_distinct`` is set explicitly on the class itself, only the first + registered subclass with a given ``dimensions`` is distinct. The answer is + registry-relative and can change when a later subclass registers the same + dimensions. + + Subclassing a distinct dimensionality does not inherit its claim: a + ``class MyTemperature(Temperature)`` would otherwise take over unit resolution for + ``[temperature]`` process-wide, silently retyping every temperature quantity. An + inherited ``_distinct = False`` IS honoured -- :class:`IndistinctDimensionality` + exists to be subclassed. """ - if cls._distinct is None: + distinct = cls._distinct + + if distinct is True and "_distinct" not in cls.__dict__: + distinct = None + + if distinct is None: # special case if dimensions was overridden to None if getattr(cls, "dimensions", None) is None: return True @@ -815,7 +827,7 @@ def is_distinct(cls) -> bool: # might change when the registry is updated return ucs.count(cls.dimensions) == 1 - return cls._distinct + return distinct _DimensionlessUC = UnitsContainer({}) diff --git a/pyproject.toml b/pyproject.toml index 5e2e730..06841f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "encomp" -version = "1.9.2" +version = "1.9.3" description = "General-purpose library for engineering computations" authors = [{ name = "William Laurén", email = "lauren.william.a@gmail.com" }] requires-python = ">=3.13" diff --git a/uv.lock b/uv.lock index 23fb339..9fa8350 100644 --- a/uv.lock +++ b/uv.lock @@ -49,23 +49,23 @@ wheels = [ [[package]] name = "annotated-types" -version = "0.7.0" +version = "0.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/56/a8120250d128bed162cd73c76d45f6ef9991f3e068f62a8ee060afa3104a/annotated_types-0.8.0.tar.gz", hash = "sha256:13b2beaad985e05e2d6407ee4c4f35590b11f8d693a258a561055cac8f64cab7", size = 15893, upload-time = "2026-07-23T20:16:13.995Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/99/91/8acff4f5e50511b911bbccb72b8628a49c68ce14148cd9f6431094859a90/annotated_types-0.8.0-py3-none-any.whl", hash = "sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0", size = 13427, upload-time = "2026-07-23T20:16:12.938Z" }, ] [[package]] name = "anyio" -version = "4.14.1" +version = "4.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3b/72/5562aabb8dd7181e8e860622a38bea08d17842b99ecd4c91f84ac95251b0/anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e", size = 254831, upload-time = "2026-06-24T20:56:06.017Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/7b/90df4a0a816d98d6ea26f559d87836d494a2cf1fcf063be67df50a7bcc30/anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72", size = 124875, upload-time = "2026-06-24T20:56:04.413Z" }, + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, ] [[package]] @@ -79,11 +79,11 @@ wheels = [ [[package]] name = "asttokens" -version = "3.0.1" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, ] [[package]] @@ -156,11 +156,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.6.17" +version = "2026.7.22" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" }, + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, ] [[package]] @@ -407,56 +407,56 @@ wheels = [ [[package]] name = "coverage" -version = "7.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/04/145a3748098bcc86b631a85408d2c3dc5c104e0bd86d605468239b25b6c4/coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782", size = 220863, upload-time = "2026-07-02T13:09:25.371Z" }, - { url = "https://files.pythonhosted.org/packages/a4/5c/4ed55708fed2c64b63c9bc5715daef670872202101938869b7fe5d5fbb8f/coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430", size = 221230, upload-time = "2026-07-02T13:09:26.897Z" }, - { url = "https://files.pythonhosted.org/packages/7b/19/3a80b97d3b2a5c77a01ae359c6bed20c13738fe3d9380f08616d4fec0281/coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5", size = 252227, upload-time = "2026-07-02T13:09:28.543Z" }, - { url = "https://files.pythonhosted.org/packages/a1/fa/b70062750686bd7da454da27927622f48bbac6990ac7a4c4a4653e7b0036/coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970", size = 254823, upload-time = "2026-07-02T13:09:30.177Z" }, - { url = "https://files.pythonhosted.org/packages/a9/09/dad6a75a2e561b9dc5086a8c5257a7591d584246f67e23e70d2995b89ab6/coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c", size = 256059, upload-time = "2026-07-02T13:09:31.979Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e7/b5d2941fa9564573d44b693a871ff3156f0c42cbefe977a09fa7fdc59971/coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84", size = 258190, upload-time = "2026-07-02T13:09:34.035Z" }, - { url = "https://files.pythonhosted.org/packages/7c/1d/8e895bcde3c57ccd46d896dda5f2b3d5df761a1b0c6c9d450d175dedc632/coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389", size = 252456, upload-time = "2026-07-02T13:09:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/14/4c/f6997da343ddeb959be82c3b05322793f92c071ad45f7cb8a96336e2dd5f/coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950", size = 254192, upload-time = "2026-07-02T13:09:37.445Z" }, - { url = "https://files.pythonhosted.org/packages/17/27/a0bc09d032267b9da89d95a2d874cfbef2a5aebbf0e87cf7aba221d79a99/coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e", size = 252153, upload-time = "2026-07-02T13:09:39.422Z" }, - { url = "https://files.pythonhosted.org/packages/54/c0/77fc233d9fba07b244c40948c53fe27308b8f21732fb3417f87fbd6fd992/coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e", size = 256310, upload-time = "2026-07-02T13:09:41.006Z" }, - { url = "https://files.pythonhosted.org/packages/d5/24/601cecfb5825becacb8d45219a018a3b55b9dbaec624efdb0ea249d08be2/coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837", size = 251974, upload-time = "2026-07-02T13:09:42.733Z" }, - { url = "https://files.pythonhosted.org/packages/47/1e/6f45e5a5b3d5484318d368702af6716b5ab8913b0428bec981a562fcf296/coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37", size = 253745, upload-time = "2026-07-02T13:09:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/8e/db/4df027a77bd11d0e527f44c53557c76e54ad027413d0304252ea3a78d67e/coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b", size = 222902, upload-time = "2026-07-02T13:09:46.122Z" }, - { url = "https://files.pythonhosted.org/packages/a0/10/0355894d34e231f2c5449e71287e81a50793a325df2e2b027b7bcd9dfd19/coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629", size = 223444, upload-time = "2026-07-02T13:09:47.687Z" }, - { url = "https://files.pythonhosted.org/packages/06/ef/bb725f263befaaff851203ab338e68af15e195d7f7b5f323162532d9b6a8/coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21", size = 222839, upload-time = "2026-07-02T13:09:49.717Z" }, - { url = "https://files.pythonhosted.org/packages/4f/9c/1e3ca54f72a3185ece06c58d871099898c48f0ed6430d17b6ab75f0d180a/coverage-7.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:41cb79af843222e11da87127ad0ecbfa878abadd0f770a4a99391a27d3887324", size = 220906, upload-time = "2026-07-02T13:09:51.339Z" }, - { url = "https://files.pythonhosted.org/packages/09/37/f718613d83b274880382f6b67e78f3802549ae39b0b3e65ae5b5974df56e/coverage-7.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d2008989ef8fe54188d3f3bfa2e3099b025af11e90a6a1b9e7dc433d04263d8", size = 221239, upload-time = "2026-07-02T13:09:53.138Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ce/22bae91e0b75445f68d365c7643ed0aa4880bbf77450ee74ca65bdae53a7/coverage-7.15.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:769e8ece11a596315ebf5aa7ec383aeeed016c091d2bf6363ffb996d41529092", size = 252286, upload-time = "2026-07-02T13:09:54.996Z" }, - { url = "https://files.pythonhosted.org/packages/dd/1e/bec5e32aa508615d9d7a2790effb25fb4dc28606e995816afe400b25ece3/coverage-7.15.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:65a6b6164ee5c39e2f3803f314292d6c61a607ba7fee253d1e03c42dc3903502", size = 254789, upload-time = "2026-07-02T13:09:56.678Z" }, - { url = "https://files.pythonhosted.org/packages/17/29/0e865435b4354e4a7c03b1b7920046d31d0a273d55decefea27e011cb9bf/coverage-7.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75128817f95a5c45bb01d65fd2d8b9cb54bbe03d81608fb70e3e14b437ad56c2", size = 256135, upload-time = "2026-07-02T13:09:58.343Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/33a870b58a13325d62fc0a6c8f01fa0ff667cef60c7498e2382a147dfa18/coverage-7.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9887bb428fe2d4cd4bee89bac1a6c9932f484afd5b36fbd4ff6ea5f825bb1f5e", size = 258449, upload-time = "2026-07-02T13:10:00.057Z" }, - { url = "https://files.pythonhosted.org/packages/18/7b/6fffe596bf3ddba8462758d02c5dad730fd91055a6634aa2e4226229181a/coverage-7.15.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0bfc0be1f702042207a93a00523b1065ee1fe951e96edf311581c0bbc2e34888", size = 252313, upload-time = "2026-07-02T13:10:01.946Z" }, - { url = "https://files.pythonhosted.org/packages/58/1b/11468dd6c1676ab831a70cb9a8d4e198e8607fa0b7220ab918b73fe9bfbd/coverage-7.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f64627d55def5a43282d70e08396672692f77e4da610a5bb8bb4060b432b6859", size = 254142, upload-time = "2026-07-02T13:10:04.065Z" }, - { url = "https://files.pythonhosted.org/packages/79/41/29328e21d16b1b95092c30dd700e08cf915bd3734f836df8f3bdb0e8fa9f/coverage-7.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2c6f0fa473003905c6d5bac328ee4eba9fbea654f15bc24b8a3274b23363fa99", size = 252108, upload-time = "2026-07-02T13:10:06.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/de/05ccfb990439655b35afbfd8e0d13fe66677565a7d4eb38c3f5ef2635e1c/coverage-7.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2bcf9afaf064172c6ec3c58a325a9957ad1178c05dd934e25f253321776e0676", size = 256385, upload-time = "2026-07-02T13:10:08.141Z" }, - { url = "https://files.pythonhosted.org/packages/51/0e/486828a3d2695ea7a2609f17ff572f6b01905e608379440a11da4b8dffbe/coverage-7.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:baf06bc987115d6fb938d403f7eab684a057766c490367999a2b71a6883110c6", size = 251923, upload-time = "2026-07-02T13:10:10.179Z" }, - { url = "https://files.pythonhosted.org/packages/18/c7/03582b6715f078e5e558354c87616d945b9894cda2dace8e4009b17035e4/coverage-7.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0405f2ff97b1c4c0e782cb32e02f32369bcf2e6b618b591d67e1ea754575dfe", size = 253580, upload-time = "2026-07-02T13:10:12.052Z" }, - { url = "https://files.pythonhosted.org/packages/db/dc/9e578bbaf2ecb4959a81b7e7601ad8cca772cba2892e8d144cb749b4a71a/coverage-7.15.0-cp314-cp314-win32.whl", hash = "sha256:ab282853ed5fbd64bbb162f19cb8fcb7087187508a6374b4f9c34ec1577c4e8f", size = 223107, upload-time = "2026-07-02T13:10:13.994Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3e/c8c3b75d8dbe0e35f7b0cc3ff5e949fc59500f70b21d0398813f66740664/coverage-7.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bb3040e9f4bbe26fcb0cd7cc85ac63e630d3f3a9c74f027abf4caa27e706663", size = 223597, upload-time = "2026-07-02T13:10:15.906Z" }, - { url = "https://files.pythonhosted.org/packages/cd/bc/3cbc9fb036eb388519bccd521f783499c39b64256013fbc362782f196fe1/coverage-7.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:346771144d34f7fa84ec28386f78e0f31653f33cf35e19d253d5b35f9e8201da", size = 223020, upload-time = "2026-07-02T13:10:17.844Z" }, - { url = "https://files.pythonhosted.org/packages/28/00/199c4a8d656dff63102577a056c0fce2ff6a79e40adac092fc986c49cbf1/coverage-7.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d34a010905fb6401324ba016b5da03d574967f7b21ce48ea41e66f0f1f95f641", size = 221638, upload-time = "2026-07-02T13:10:19.703Z" }, - { url = "https://files.pythonhosted.org/packages/ba/8e/9d0092c96a3d3a26951ecc7020826aa57bcb1b119ca81acbba996884ab13/coverage-7.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bb25d825d885ca8036795dacfc3924d33091fc76d71ebc99420c6b79e77d96fa", size = 221903, upload-time = "2026-07-02T13:10:21.514Z" }, - { url = "https://files.pythonhosted.org/packages/6d/b4/c0ca3028f42c9a08e51feb4561ef1192e5de99797cd1db5b04590c215bda/coverage-7.15.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94c9686bfe8a9a6810297aecbd99beaa3445f9e8dc2f80b1382cca0d86b64461", size = 263267, upload-time = "2026-07-02T13:10:23.261Z" }, - { url = "https://files.pythonhosted.org/packages/5f/aa/a375e3846e5d3c013dc600b2a3231089055c73d77f5393dd2192a8d64da6/coverage-7.15.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bd671c25f9d85f09d7ec481d0e43d5139f486c06a37139847a7ce569788af72", size = 265390, upload-time = "2026-07-02T13:10:25.152Z" }, - { url = "https://files.pythonhosted.org/packages/92/e1/5783cdabb797305e1c9e4809fea496d31834c51fa772514f73dc148bcfc9/coverage-7.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110cbdf8d2e216577312cf06ccf85539c0e5a5420ef747e4a4719b5e483c88cd", size = 267811, upload-time = "2026-07-02T13:10:27.249Z" }, - { url = "https://files.pythonhosted.org/packages/85/31/96d8bbf58b8e9193bc8389574a91a0db48355ee98feb66aa6bf8d1b32eea/coverage-7.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c5d4619214f1d9993e7b00a8600d14614b7e9d84e89507460b126aa5e6559e5", size = 268928, upload-time = "2026-07-02T13:10:29.242Z" }, - { url = "https://files.pythonhosted.org/packages/5e/7a/5294567e811a1cb7eda93140c628fa050d66189da28da320f93d1d815c73/coverage-7.15.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:781a704516e2d8346fbbd5be6c6f3412dd824785146528b3a01816f26c081007", size = 262378, upload-time = "2026-07-02T13:10:31.107Z" }, - { url = "https://files.pythonhosted.org/packages/69/3f/3f48538421f899f28946f90a3d272136a4686e1abf461cc9249a783ee0f3/coverage-7.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd4a1b44bcb65ee29e947ac92bbee04956df3a6bfc6143641bb6cae7ede00fc9", size = 265263, upload-time = "2026-07-02T13:10:32.942Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d3/092df15efcab8a9c1467ee960eb8019bbad3f9300d115d89ea6195f369ff/coverage-7.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0e4950c9d6d3e39c64c991814ff315e2d0b9cb8152363594212c9e55208c0a8f", size = 262866, upload-time = "2026-07-02T13:10:35.104Z" }, - { url = "https://files.pythonhosted.org/packages/e5/ab/0254d2b88665efb2c57ad368cc77ab5de3435bd8d5add4729c1b0e79431e/coverage-7.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:fe9c87ff42e5472d80d21704972e1f96e104a0a599d77c5e35db5a3c562e2571", size = 266599, upload-time = "2026-07-02T13:10:37.05Z" }, - { url = "https://files.pythonhosted.org/packages/a8/79/1cfa4023e489ce6fbc7be4a5d442dbc375edb4f4fda39a352cedb53263c2/coverage-7.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f00d5ae1dd2fe13fb8186e3e7d37bcbd8b25c0d764ff7d1b32cef9be058510a8", size = 261714, upload-time = "2026-07-02T13:10:38.966Z" }, - { url = "https://files.pythonhosted.org/packages/b7/eb/fee5c8665656be63f497418d410484637c438172568688e8ac92e06574e7/coverage-7.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:363ab38cc78b615f11c9cac3cf1d7eef950c18b9fdedfb9066f59461dcf84d68", size = 264025, upload-time = "2026-07-02T13:10:40.789Z" }, - { url = "https://files.pythonhosted.org/packages/ab/99/63005db722f91edc81abc16302f9cc2f6228c1679e46e15be9ae144b14d0/coverage-7.15.0-cp314-cp314t-win32.whl", hash = "sha256:54fd9c53a5fafff509195f1b6a3f9be615d8e8362a3629ff1de23d270c03c86b", size = 223413, upload-time = "2026-07-02T13:10:42.597Z" }, - { url = "https://files.pythonhosted.org/packages/c1/e8/2bc6181c4fb06f1a6b981eb85330cc57bfad7e3f710fc9c9d350013ba228/coverage-7.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:87b47553097ba185ed964866078e7e63adea9f5f51b5f39691c34f30afd21080", size = 224245, upload-time = "2026-07-02T13:10:44.47Z" }, - { url = "https://files.pythonhosted.org/packages/79/b8/4d959bf9cc45d0cfed2f4d35cafcab978cdb6ea02eb5100009cd740632a3/coverage-7.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeefb2dd178fe7eee79f0ad25d75855cb35ee9ed472db2c5ea06f5b4fd00cec5", size = 223558, upload-time = "2026-07-02T13:10:46.368Z" }, - { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, +version = "7.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/d5/f8c838e6b7282976f7c918884b792df7a0c42c5bba5d99c60ad2d221d56d/coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", size = 221606, upload-time = "2026-07-15T18:54:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/97c926376364f66298cc44893b89cdf17b8bc406376497c4061ae4b8a8ff/coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", size = 221982, upload-time = "2026-07-15T18:54:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/a36050a6e83c2135ee0776f452ca3948224befc6d7f26acecc082d0c106a/coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", size = 252972, upload-time = "2026-07-15T18:54:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/06b5f1daf95f0f15ab05bd75f26ba5f3c8b33d0bb72f3aaa3cf41d1bad3a/coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", size = 255569, upload-time = "2026-07-15T18:54:51.098Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/9afb3f8de2b8d36960391c48559a2e3ff96594b58099f115921549ea8d0d/coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", size = 256806, upload-time = "2026-07-15T18:54:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/64/d8/b989f96061a5e32d82fddd1b1b9ff48a7c8f8ae7606f0e80fd9de54b1e33/coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", size = 258936, upload-time = "2026-07-15T18:54:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/f99771f5110457c7b511c1935ca49ddf288218eaa84322e028b9334146ae/coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", size = 253178, upload-time = "2026-07-15T18:54:57.527Z" }, + { url = "https://files.pythonhosted.org/packages/f6/96/c098a6044d119c751ceede7be91035fa8310170ec24a6523aff72f0a5793/coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", size = 254934, upload-time = "2026-07-15T18:54:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a2/1457b3a7a50c8d77500103b97a046db863e2f59a1cf6d2f814595f349885/coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", size = 252898, upload-time = "2026-07-15T18:55:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/76958874c471ecfcdde0d2b2747bb2c61bdbf34a40636f4ce9db9923e643/coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", size = 257056, upload-time = "2026-07-15T18:55:03.243Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7c/3d7c4e3bf58baa40327dc7edc2272b17cf02299366d52763db1b0ca1556a/coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", size = 252718, upload-time = "2026-07-15T18:55:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/1cecffed9ce14fb25be9ba42d37b6bb61485c9a3ddd43cd3dde36b6087d8/coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", size = 254490, upload-time = "2026-07-15T18:55:06.889Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2c/42984561bc7f4c045dca67516a0c50ee5ef8d84352dbeb5559dc86c4823e/coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", size = 223647, upload-time = "2026-07-15T18:55:08.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/9f/39c7c9245efc583beddf89a87683574e663ed93637f3afb6cd7b88405676/coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", size = 224190, upload-time = "2026-07-15T18:55:10.789Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/3a2883cf8a213659280ef4b403059e17a9acaeb7fc7fd4105e1226ff2e6d/coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", size = 223583, upload-time = "2026-07-15T18:55:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/aed265fd7a3551a394f36dfe41868aee709b7f95db4052205b4ad1563ac3/coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", size = 221650, upload-time = "2026-07-15T18:55:14.527Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2c/222ba12a545189017120f8eddfc1a0bd4616b47d5d4a8d99421edb2fe4c6/coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", size = 221988, upload-time = "2026-07-15T18:55:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/aa/38/304b5877ab46e6c290b4292cfcf3fe28245f0e5597cad7f6acc91fc7e0a4/coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", size = 253029, upload-time = "2026-07-15T18:55:18.856Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/821b533b8db9e44cf1d8a97bd525149ced40dde1d0093da02cb78e715244/coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", size = 255536, upload-time = "2026-07-15T18:55:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f2/7aa06604c389d32ea7f0a6a988359a7eafc3cd3f8e7bc2e88cd2fdf0b877/coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", size = 256881, upload-time = "2026-07-15T18:55:23.125Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4f/1ef342339c7916d0096bc5888cc0f653882cc7bc8f897d5cb89143287c9b/coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", size = 259196, upload-time = "2026-07-15T18:55:25.099Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f4/7ed055d7a9c5ec13b161773a115a5ccc6b0081d568c31fad830806306cc7/coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", size = 253036, upload-time = "2026-07-15T18:55:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/14/79/ea82cca18c242a3a38b6c017da39726aa62dcb64aa635abf79b92009975c/coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", size = 254887, upload-time = "2026-07-15T18:55:29.084Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ba/a136db3c0d9562b00e10b72540dbf3a33cd3bc5b95060c9308e247494623/coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", size = 252852, upload-time = "2026-07-15T18:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/ea334246b16b7d059953fad6fdefa11e33c68efbd3fe37b1098120a1fac2/coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", size = 257128, upload-time = "2026-07-15T18:55:33.163Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c3/074fb66d46d607855f710876b117cbda562c5ab08363528e78820449f937/coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", size = 252668, upload-time = "2026-07-15T18:55:35.063Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c1/f620850ada9b36435921c9a3a8057013422b1d964eb4bf37fe138724d192/coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", size = 254325, upload-time = "2026-07-15T18:55:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/cc/31/a729ca3689404493af82ef8e6ff70bd88bdda8da89aeef6ca9b387aeb2b4/coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", size = 223844, upload-time = "2026-07-15T18:55:39.078Z" }, + { url = "https://files.pythonhosted.org/packages/c6/83/5d809dc808fb1698c671f3e372259bb9158e64b7ea526fc6ab7de64de9fe/coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", size = 224331, upload-time = "2026-07-15T18:55:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/4e/35e488548e952795829e129995c4174df33bf432b591d1aa42c8d9e4e7ad/coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", size = 223760, upload-time = "2026-07-15T18:55:43.518Z" }, + { url = "https://files.pythonhosted.org/packages/ed/49/dd2c86cd6374038f6e415fb5bfb86db5218553209c081384a020369dee79/coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", size = 222384, upload-time = "2026-07-15T18:55:45.569Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/173ff17a1c0808e5a438f549f6f145d5ac7528f2791310b63523e3200ac7/coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", size = 222647, upload-time = "2026-07-15T18:55:47.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/f8/b8cba872162356fb44ac79c10309d987206a4461e32072fc29228dad7331/coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", size = 264013, upload-time = "2026-07-15T18:55:49.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/67/a807a7586d0b8cae485308ddd55756f0806c92f8e0b411bacbf23c48edf3/coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a", size = 266135, upload-time = "2026-07-15T18:55:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/cd78771dc985f7e4ebdcc82b1a96d9a932af9e806f01f2f91a89f4c72e80/coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", size = 268555, upload-time = "2026-07-15T18:55:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/18/3e/10134cf81275188c58568f324fc74aedff32c63ca4d5bbc513a91944a6f0/coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", size = 269674, upload-time = "2026-07-15T18:55:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/75/4a/771b77de446cba985dc414bbc5844bd21604da05dbc044286df8318a48a7/coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", size = 263101, upload-time = "2026-07-15T18:55:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b5/70a7011da15f4071943361183aefa27847f3e3aec4fd335f1cb3d3a622b1/coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", size = 266007, upload-time = "2026-07-15T18:56:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/f9547e804ce7ad49646ffeffac26699510efbe6c0f751b66fdc960c4e825/coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", size = 263611, upload-time = "2026-07-15T18:56:02.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/59/f576a396659c0efd351f5c1544f67c3560e89c7761cabf7f65e412beeda5/coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", size = 267344, upload-time = "2026-07-15T18:56:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5d/c2e4fce3579c0cb635024293f1a32bbe26df101b3e3a69f22243d1352b6c/coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", size = 262456, upload-time = "2026-07-15T18:56:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/956287d69436b66094bc4b57ac2da71e43bfd2a5524e958900b9f582fcf8/coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", size = 264771, upload-time = "2026-07-15T18:56:08.795Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5a/6f979530c2734c575de77cf58f5f28d51f7123a94b5030fd9156fe5f363c/coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", size = 224151, upload-time = "2026-07-15T18:56:10.856Z" }, + { url = "https://files.pythonhosted.org/packages/54/7e/27f6b2a74d484742f4017553e710b01e396b23d809df3e95ca0bb9a2824b/coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", size = 224981, upload-time = "2026-07-15T18:56:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/b1/48/284863423aa474240f6842bd00d680da22f4e6ea2e466618ef7c9c9e69a9/coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", size = 224294, upload-time = "2026-07-15T18:56:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, ] [[package]] @@ -514,7 +514,7 @@ wheels = [ [[package]] name = "encomp" -version = "1.9.2" +version = "1.9.3" source = { editable = "." } dependencies = [ { name = "numpy" }, @@ -625,11 +625,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.29.7" +version = "3.32.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/35/94/00f2059e4835eace3ae8fde680b932c496f8ec7bdc99168dfa53fb2e6b79/filelock-3.29.7.tar.gz", hash = "sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d", size = 71521, upload-time = "2026-07-08T05:46:58.716Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/80/8232b582c4b318b817cf1274ba74976b07b34d35ef439b3eb948f98645a1/filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", size = 213757, upload-time = "2026-07-21T13:17:42.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/02/be4a57b60c7149b55b9e3b3c13f609cd8eb5307c751f22bd8fb8d262e75b/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51", size = 46036, upload-time = "2026-07-08T05:46:57.53Z" }, + { url = "https://files.pythonhosted.org/packages/06/79/b4c714bef36bc4ec2beeae1e0c124f0223888cd8c6feb1cdc56038116920/filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3", size = 97732, upload-time = "2026-07-21T13:17:41.55Z" }, ] [[package]] @@ -722,34 +722,46 @@ wheels = [ [[package]] name = "hypothesis" -version = "6.156.2" +version = "6.161.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sortedcontainers" }, ] -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/f1/40b782791455411d09eb181fd688d615b0f46945f50a2b8ad1525a9aa0e2/hypothesis-6.156.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5ebeddf95d2d60283d079e9e3ee434ec2654868d35deb8d7de01221d86b9bd67", size = 749445, upload-time = "2026-07-07T20:24:33.387Z" }, - { url = "https://files.pythonhosted.org/packages/33/92/e92493ff3903a57d84618d379a180566e6e77b3a8e14fab0e6e15d6958b8/hypothesis-6.156.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:082f81fb0697d42809b9ca9b7d2b01e319363f9b6b368dded0f7cf29574228f8", size = 742078, upload-time = "2026-07-07T20:24:35.178Z" }, - { url = "https://files.pythonhosted.org/packages/0c/fb/3c9ae5cdc9e2c296bbd08a00f6a369df509b4ec19443d099d59258f02738/hypothesis-6.156.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82a23eede8f93ae5768b6161e51511e40fd0b3e6f08f25d94f17e476827a3e3f", size = 1069980, upload-time = "2026-07-07T20:23:49.575Z" }, - { url = "https://files.pythonhosted.org/packages/f8/0d/b9cd9bb75beeda4dc520b81d818db300c67659bb15d4c0d1eaac7b5818dd/hypothesis-6.156.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cc703d7f8d16006f2afb8b5c9de79d145888e1bc917431edef7ab058447910a", size = 1119942, upload-time = "2026-07-07T20:23:42.174Z" }, - { url = "https://files.pythonhosted.org/packages/63/b7/590bda127ac08d5ca03787f530093d9a97746550bded6cbe11c38cc33526/hypothesis-6.156.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bc6a9801926b34d124628e54166c4f5e466831456349c1b32b3abb4320c5588", size = 1244026, upload-time = "2026-07-07T20:24:47.441Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f2/45416c2fa565a6d9df9f1ee3844123c6332cc2f5444e65d0089cd4250d8b/hypothesis-6.156.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9494e7dfdcbafa1fb0b109929500e1f2ee71e58cc531919cc7721acb3ea0e0a9", size = 1286715, upload-time = "2026-07-07T20:24:38.136Z" }, - { url = "https://files.pythonhosted.org/packages/cf/15/c973adc18512e74da8e9deba457019b19b142f6e6b0fae432637ed7c3ce2/hypothesis-6.156.2-cp313-cp313-win_amd64.whl", hash = "sha256:e1778a7446a7aff9420367241740d021bc9093f6f38f39ebdd45c3737fa7fcb4", size = 637936, upload-time = "2026-07-07T20:23:44.498Z" }, - { url = "https://files.pythonhosted.org/packages/24/ec/6446a111a1bc74a3dc41e4d0efc196fc79d93c9976f5fda06e81577a36b4/hypothesis-6.156.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d9ad1b6bcfc0d7e89be08ac8607fb75ebac7a95dc549ad0364fad141e9b06154", size = 749477, upload-time = "2026-07-07T20:24:05.453Z" }, - { url = "https://files.pythonhosted.org/packages/3b/f6/68422861dd20b71596cca2e26567dba64c59bec243db34909f571137571a/hypothesis-6.156.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7320f3725f694151bfb5e5c61ba62585fbe326b1ef7e9c7d7ba2ec8671ff9b70", size = 742301, upload-time = "2026-07-07T20:23:57.514Z" }, - { url = "https://files.pythonhosted.org/packages/b1/78/151738ee8606b9d3a838a0acb704a8f92e2b21ac5177f92be27fe959df73/hypothesis-6.156.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1f355b2923c0f93b77837a50d6d34da01e0ffc7d368b0cf1784756b7148894d", size = 1070153, upload-time = "2026-07-07T20:23:56.195Z" }, - { url = "https://files.pythonhosted.org/packages/7c/5b/86e2799b2cd736f72a4d866544511ec6fa040c624022e57e36881991dabc/hypothesis-6.156.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd042841fc0dfa62ce34db75cb49435ff77e70bf600aeee46c06152ba48e7c37", size = 1120175, upload-time = "2026-07-07T20:24:15.573Z" }, - { url = "https://files.pythonhosted.org/packages/8f/b8/078a73d85368902f00d1b3428afd8a98805e27485c1aaf0dad793507acb8/hypothesis-6.156.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5773eb13c8a1fc387119d7fcb5020881df7b3d87cdcb2a46b9d7ed64cf8b7bb5", size = 1244298, upload-time = "2026-07-07T20:24:21.232Z" }, - { url = "https://files.pythonhosted.org/packages/92/5a/fbc263393e2ed6ae16fa9a6dda4c28abbb5306ebcb93546d8cf5e496d39f/hypothesis-6.156.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0422a383b8e6b249f0daa44c43126afb6e268064902917707db92e51a15d11f", size = 1287226, upload-time = "2026-07-07T20:23:48.296Z" }, - { url = "https://files.pythonhosted.org/packages/92/66/50297b7dd2275d6bd3fe55c07e195cc2cc7f266f2055932c85a37ec9e9f6/hypothesis-6.156.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:c3b020a7baada24c5f6e11516a9391a3dc87ee414acf9a2a07d98f7d5bcb119b", size = 586422, upload-time = "2026-07-07T20:24:22.751Z" }, - { url = "https://files.pythonhosted.org/packages/c0/4c/4deded2dbc5b6e372fbbabc97b95f4c74c3370cdd3efae08ebb26718b03b/hypothesis-6.156.2-cp314-cp314-win_amd64.whl", hash = "sha256:ca3ee647b8795e13ebb47ecacf6318a6ac31c2d431d9d08f92b9d6af2676fd0d", size = 637747, upload-time = "2026-07-07T20:24:01.473Z" }, - { url = "https://files.pythonhosted.org/packages/f0/19/3681202960f66d22ce154d45ba0a0865160f85cc64de958a1a8a06356735/hypothesis-6.156.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:b39c53e2eac55181dd9c2d701d1369575b4cb2a672e098515016ab3cdbab1cf1", size = 747519, upload-time = "2026-07-07T20:24:04.124Z" }, - { url = "https://files.pythonhosted.org/packages/83/b8/d08016b2f5db927b16b8437b79a3fb4b2b2d14c3e04b492ef634a32b42a5/hypothesis-6.156.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41dd8d0305ee482ee5244c23190e7b6bb886836098dc6d2b7f5c9dde4047062c", size = 740868, upload-time = "2026-07-07T20:24:27.389Z" }, - { url = "https://files.pythonhosted.org/packages/69/2c/c130cf4e7e8390b05fd27a395db73e31fadb6ffd3139cc82590dc149c7cb/hypothesis-6.156.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52d0b106f4f0925955cfb0ceb485ac4208b6494320d3be3171e5dbe4f0a0fcb3", size = 1069173, upload-time = "2026-07-07T20:23:45.744Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8f/2f2998931c9c3a4440e100856607019ecae5c04ab93d0e2fb1f9406326c4/hypothesis-6.156.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf23a2746085ecc874f6fc8d709498f1b20c9d8077532802b3e73e1ee65454a7", size = 1119250, upload-time = "2026-07-07T20:24:17.178Z" }, - { url = "https://files.pythonhosted.org/packages/d3/54/daf1e6dd20c0378fdf9d96bcf88ed6b5de34f97724e81ccd480265c43238/hypothesis-6.156.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:73644c1a403ea5864f87db073412985029dad019cec3c9350fc387623f4c999d", size = 1243065, upload-time = "2026-07-07T20:24:00.111Z" }, - { url = "https://files.pythonhosted.org/packages/41/36/8ba904da1928287757c009eb51bdd021d03b4e5def66ee6701c0ac94ac98/hypothesis-6.156.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:de71493b174e6857b1939240a8c9efa1486edd8e30aa524ed67ea6b3e79c05a7", size = 1285470, upload-time = "2026-07-07T20:24:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/3b/ff/968df32637fdb4a8867b3102a4a1c43c2f07106a47caa0439e59f985804d/hypothesis-6.156.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0d7ab28a7de1f316598037862f4accc12597cc86b48d1c9c63a4d95484be4e92", size = 637839, upload-time = "2026-07-07T20:24:52.058Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f7/5b/98162d7cf48866e18b59d12e975229af411575a20d9a75c303345cc3380b/hypothesis-6.161.2.tar.gz", hash = "sha256:e25f1e6f8a2ea4cadfeaeba18cb8676e5510f52fefd5c9bc165e3eb81e1ef497", size = 486148, upload-time = "2026-07-24T06:43:23.774Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/8f/a565e6ab67de327eee310ef306d715d8f5143e729835450715dde121e912/hypothesis-6.161.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c61b868484dbcd9d2d6d25662f60fa2cee464d18061315efd997efe721250f14", size = 766523, upload-time = "2026-07-24T06:42:57.381Z" }, + { url = "https://files.pythonhosted.org/packages/92/d7/5deb1e38c8c253c879bec75a95fe0ff01d60366d7bd33c59012a8d23a8a1/hypothesis-6.161.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b22012a92b8d2f70f7e7a6a4761166b920edf92900de458d6c9d272057dd48d8", size = 762160, upload-time = "2026-07-24T06:42:21.945Z" }, + { url = "https://files.pythonhosted.org/packages/dd/21/1f03b88df69b9b22429ad608c7a4778e01e9bb10656142e0426dbf0865eb/hypothesis-6.161.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d59687ed88b290e450505d71a5950422d39791dbaf48a4159b876634976e7898", size = 1091358, upload-time = "2026-07-24T06:42:03.947Z" }, + { url = "https://files.pythonhosted.org/packages/70/9f/5c761fdb60ed5c547b6803620822bca14486fda32d785a31416f1bbab970/hypothesis-6.161.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7af888ad7fe38c33112fde21756621b0d8f69167bbb01a5d66569aa90d6c692c", size = 1140836, upload-time = "2026-07-24T06:43:22.104Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d5/1eb85756989ea885c325442ef2ebe5bd7347cbd4f99a907c384d10b343e5/hypothesis-6.161.2-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:9bd07a407addd5a9318305bc3cf78215662c6970b699a2df610955bf41750abc", size = 1096169, upload-time = "2026-07-24T06:42:09.992Z" }, + { url = "https://files.pythonhosted.org/packages/08/0e/c65f8c76f4d9d54203cbe5df6cf33e9b918629150b526d5df0ca5350b9fe/hypothesis-6.161.2-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:140e154d7317e8fcb538a8ff1c7b8ee52b67c9ab53647078e11cb950d2264c11", size = 1132927, upload-time = "2026-07-24T06:42:49.536Z" }, + { url = "https://files.pythonhosted.org/packages/37/da/be099e47ec288a8ce7a9030c47882166694b62c0fcd5d468f46559333848/hypothesis-6.161.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:febed60ec1ce744040f8f1e94a994a567a61b3f4bd82be4c3883b93ac76ec58a", size = 1265174, upload-time = "2026-07-24T06:42:34.36Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7c/e591135729eee9dd84cf68a539612ee9571b449f7a32ed9070802ccae4c4/hypothesis-6.161.2-cp310-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:d01840d49772e3a29117b936dd5b71c13a9a82b39a739ac49f228af9601c40e8", size = 1265785, upload-time = "2026-07-24T06:42:18.135Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e3/67a5f389fa1be4f4a68b3c65c15f3273d4fb5910cbc4c99754260ae52112/hypothesis-6.161.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4179f4db70daec64dc43d3fa9b23851d78791ca5d84605b3794df2dfc3150c61", size = 1307846, upload-time = "2026-07-24T06:43:05.511Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d8/45ffe9a9f069853fabb7b91fe72d46f9b2f08508550f87f3567fd1d46775/hypothesis-6.161.2-cp310-abi3-win32.whl", hash = "sha256:fabc7595dd1e0c66936e9777843dbef3c2e3f31983a669bda6b635909878176e", size = 652400, upload-time = "2026-07-24T06:42:54.175Z" }, + { url = "https://files.pythonhosted.org/packages/6a/14/81af65ce429671ee4e4b9fb6924a02564dd9e430c543da9ac9449dd07042/hypothesis-6.161.2-cp310-abi3-win_amd64.whl", hash = "sha256:425a35e9240761a2e71743424b193fd799dfa3117bad21982bbe8110637256b2", size = 658534, upload-time = "2026-07-24T06:43:10.538Z" }, + { url = "https://files.pythonhosted.org/packages/8e/98/3d4849d78b7eeafac335d55a8cbc647602eca41cce9fe5b99f28025d6f3b/hypothesis-6.161.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:c77580949be61ce4b946f8dfba38ed652ce28deb6302167550f85720f08c1864", size = 767990, upload-time = "2026-07-24T06:42:43.432Z" }, + { url = "https://files.pythonhosted.org/packages/50/eb/096d58e24d727a69e3ad3fb3887f4b3750d0e41287bb3a6ebfb9f20b6b8d/hypothesis-6.161.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed59ea708743da8e91f975d3848d2ed0a46548d833c8703b53fa854bbb8f3ebd", size = 759677, upload-time = "2026-07-24T06:42:07.612Z" }, + { url = "https://files.pythonhosted.org/packages/e3/11/511ff00654fdc9a52316e1a772cecc6ef0a0c24f1138a20aeab2f632ae5b/hypothesis-6.161.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73fa1136acb5f554c6b958f8a9d33b2d7f6816909ccf07b22e17351df9bec146", size = 1090072, upload-time = "2026-07-24T06:43:03.917Z" }, + { url = "https://files.pythonhosted.org/packages/99/b8/2c833554fb3ba22d2841888b3409b634d6059420d1f5269f7cf3294358ed/hypothesis-6.161.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:584b1d91b5f3fa200b35bc0533e30e9fea81c852bb150eedbf0a7fc46d7639be", size = 1140009, upload-time = "2026-07-24T06:42:58.975Z" }, + { url = "https://files.pythonhosted.org/packages/35/36/c351c14155225b6290ce94fbd5610a690231312d5aa2121ad436888e3dfe/hypothesis-6.161.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3d2f1b8324259cc1a149286b8856ed07447f9f28f1338452eec73708cd7e5", size = 1263030, upload-time = "2026-07-24T06:42:23.475Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/1dcbafa23a2fc07aede597e0c9ea2093378a1c05a858dfbbb3627a1a7e11/hypothesis-6.161.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c1c6c7d30c9aaa11b3e79c40a9a7ac1f559fce44853292d5383c0b1d11623658", size = 1306906, upload-time = "2026-07-24T06:42:06.2Z" }, + { url = "https://files.pythonhosted.org/packages/26/93/371966030a92748fcdef978f4ecfd69aa7b0d92e4bcdc398f5938941ef2c/hypothesis-6.161.2-cp313-cp313-win_amd64.whl", hash = "sha256:176d4f1a58f808891eec2a448889a24522002cb952df99c6fe4ab150f81a0eae", size = 655650, upload-time = "2026-07-24T06:42:51.08Z" }, + { url = "https://files.pythonhosted.org/packages/96/bf/fc502b4e92361e0ded96f77f3f40bcc6b16d0ab0511a54afb1110133d18a/hypothesis-6.161.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0101112993070b1f3ba00b44ea3913e06d7b14c0c658f61e19f196aa7a36c2ce", size = 768219, upload-time = "2026-07-24T06:43:20.437Z" }, + { url = "https://files.pythonhosted.org/packages/33/f0/aa051525002a853914fd5e14ea0921ccdd02081d715cb52473f35fcb2325/hypothesis-6.161.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a8323ffde6b3c01103c69af857e085dc4d74cbde07e8f8dac297d81870f0b8dc", size = 759825, upload-time = "2026-07-24T06:42:44.901Z" }, + { url = "https://files.pythonhosted.org/packages/32/24/88b22af1f34b773542eb8c465d3609bc6e35ddf22e4598fbed9b3aa12956/hypothesis-6.161.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d05e9343fbf172dc409fbf2b5c8894d2fde6d0748a852b9b8ef2366292240ff", size = 1090594, upload-time = "2026-07-24T06:42:47.918Z" }, + { url = "https://files.pythonhosted.org/packages/bc/4d/b800afacd75cb89e9663178e22393e9b69289ad43ef1cc868a8d0a243482/hypothesis-6.161.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84f5f15959b16356534007ba4a0cc95184576639b839a311eb953b07947c9e8c", size = 1140179, upload-time = "2026-07-24T06:42:41.644Z" }, + { url = "https://files.pythonhosted.org/packages/56/89/c33695781141b78b84ce888beb6a0e6865f0fc45b1395fa4d7039d31e29a/hypothesis-6.161.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c5e898aaf65476e4c3e2953dfb71b16112537704c9cb083e7920c2525a96d97", size = 1263351, upload-time = "2026-07-24T06:42:26.369Z" }, + { url = "https://files.pythonhosted.org/packages/98/9e/8e508005a821a46e230704c69790ab6631d6e94404c7275d5bf7f34aa8d7/hypothesis-6.161.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f733deeeae110c490610eba1c72adbf2b56f358bed3750e1a07d4c42a5ddaffa", size = 1307209, upload-time = "2026-07-24T06:42:19.493Z" }, + { url = "https://files.pythonhosted.org/packages/23/03/62301eab71ce45cf7e9c84e52a4cfcd2d51abcc81a9f733e89a171474395/hypothesis-6.161.2-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:7870df5cc41377aa76a42650be68b1cab87fd7b7a1ae1a7a83b06fc49809b6f1", size = 599729, upload-time = "2026-07-24T06:41:59.905Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a8/4e1479e2136b051adc2a141b2c26b8c8a18deda9d7c1713e816eac6b374b/hypothesis-6.161.2-cp314-cp314-win_amd64.whl", hash = "sha256:cc6bc6e1c6228ac32e7c22110eb663dc5abb844ed83eb2199f00fac004327d6d", size = 655563, upload-time = "2026-07-24T06:42:05.089Z" }, + { url = "https://files.pythonhosted.org/packages/22/8c/9c0f53d4d244cb8ad243a5dbfd1e733eaf5617f1e867c45bac15dfaa88c7/hypothesis-6.161.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f91ffa32ef1597158da5696e68f1d99ef0c23dbe286a6d4ea5d94e4f856614d5", size = 766796, upload-time = "2026-07-24T06:42:16.803Z" }, + { url = "https://files.pythonhosted.org/packages/ce/0c/3aa7877ebb5f333ff864e1465d579cbad85eb87ab45e9d532093ebd88dd0/hypothesis-6.161.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:825feec7f819c44e02a047b7c1c6672793e584b7a659cf2fad107fa031722515", size = 758288, upload-time = "2026-07-24T06:43:16.998Z" }, + { url = "https://files.pythonhosted.org/packages/05/f0/452289f386654e22f11d9cef03a8d2931fede39f028e679ebd40b2dd9513/hypothesis-6.161.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8aeecadd8d0df819ccde1d00d8f2f8e5cd5d0ac196967d959b789b111be54cb1", size = 1089175, upload-time = "2026-07-24T06:42:32.859Z" }, + { url = "https://files.pythonhosted.org/packages/85/d9/9e70c36b84392b0e13ead6601032e06fdbac6898d67515140a123242a081/hypothesis-6.161.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:526663659adf7408a2e550440d16020a2f49e4e3e69ccea0a6aa23ed5ff6f6b8", size = 1139093, upload-time = "2026-07-24T06:42:08.771Z" }, + { url = "https://files.pythonhosted.org/packages/98/e5/464b33a0eab6cd8df301ec45bea759c6f66a331f59f4a2d9c600c8e0a07f/hypothesis-6.161.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:96d3609972a89cb28794014a67aab4354dfb30e65c3ea92ce5318044d69f0f47", size = 1261590, upload-time = "2026-07-24T06:42:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/58/b3/e37438facec5f3c3021e2d49610d803ccbf88b594090194fc4fa7f89c49b/hypothesis-6.161.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:adc49e00f4e3245ae5372c602bab929fec10be922fdb5766c73f936a616c0948", size = 1305967, upload-time = "2026-07-24T06:42:15.575Z" }, + { url = "https://files.pythonhosted.org/packages/5b/43/6ac7a850101db2ddaf351ee9e2d649a1a2a10bdc9ce1d83a60cbb0ddc565/hypothesis-6.161.2-cp314-cp314t-win_amd64.whl", hash = "sha256:e368ebca6616dd17cd4d00ea1e03705aac850bcc4a8de322c4942a1d63a80c88", size = 655715, upload-time = "2026-07-24T06:42:40.252Z" }, ] [[package]] @@ -1082,11 +1094,11 @@ source = { git = "https://github.com/microsoft/python-type-stubs.git#d23acef5204 [[package]] name = "mistune" -version = "3.3.2" +version = "3.3.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/5f/007786743f962224423753b78f7d7acb0f2ade46d1604f2e0fa2bedf9020/mistune-3.3.2.tar.gz", hash = "sha256:e12ee4f1e74336e91aa1141e35f913b337c40bdf7c0cc49f21fb853a27e8b62f", size = 111284, upload-time = "2026-06-23T00:29:28.568Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/92/328a294a6de83bacb95bed01f04e0eaff4e3616ee359fc821a5dfc539b02/mistune-3.3.4.tar.gz", hash = "sha256:58b5c96d6fcb61190dfe5fae498d2b2065f99cf61e9649418fd54cf1ada86dfe", size = 121426, upload-time = "2026-07-22T05:22:30.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/43/894c2cbbcbdf53b57d1257a249811abe2ee9ab7ef76af301b40f1c054533/mistune-3.3.2-py3-none-any.whl", hash = "sha256:a678a56387d487db7368ede4647cb2ba1deff22ce61f92343e4ebe0ddfce4f2d", size = 61554, upload-time = "2026-06-23T00:29:27.088Z" }, + { url = "https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl", hash = "sha256:ee015381e955e370962968befe1d729ab60fafb6a715ac6751763fbce38c8d4a", size = 66862, upload-time = "2026-07-22T05:22:29.419Z" }, ] [[package]] @@ -1117,11 +1129,11 @@ wheels = [ [[package]] name = "narwhals" -version = "2.23.0" +version = "2.24.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/ac/66ed1fc6e38a0c0f330627ec5c5d597990d6159b6712b82af0ad2c65f06c/narwhals-2.23.0.tar.gz", hash = "sha256:13e7ff5b4bb4a2f77b907c2e4d8a76e273dfc1323a3c997440a2f9fd26aed408", size = 656209, upload-time = "2026-07-01T11:21:53.278Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/1d/58946e5aab18393e793bd4add6985b95d0e01c3a2d832f38f54468b10dcd/narwhals-2.24.0.tar.gz", hash = "sha256:b5c0f684ccd9d7475b564111e319a4964abcf2baf79d3cf6b1003d06ac9b828d", size = 661143, upload-time = "2026-07-13T10:49:19.086Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/4e/afc8c31605cb8be1d3bb4438c4d979daa104dab6306cd2b87abe9c3a7299/narwhals-2.23.0-py3-none-any.whl", hash = "sha256:769e7b9ab102c93d8fa019f6b4cd1a657909b04a20bf6210e5a35aae06814ae9", size = 458938, upload-time = "2026-07-01T11:21:51.677Z" }, + { url = "https://files.pythonhosted.org/packages/7e/85/a5bfaebfd305ac18b57b0854d74e37e586809061a91fda62f0bd50c8518e/narwhals-2.24.0-py3-none-any.whl", hash = "sha256:42fdedf44e5b2ca7505630d45b4ac3058f38d8485cba9fe1652ca23152df7489", size = 461030, upload-time = "2026-07-13T10:49:17.571Z" }, ] [[package]] @@ -1311,46 +1323,39 @@ wheels = [ [[package]] name = "pandas" -version = "3.0.3" +version = "3.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, - { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, - { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, - { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, - { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, - { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, - { url = "https://files.pythonhosted.org/packages/86/54/effdcc3c0ff7a08037889200e148ebe94c16c4f653be078c7b3675955df1/pandas-3.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3650109c0f22879df8bd6179ab9ee3d7f1d1d4e7e0094a3f0032d9f51e2e64ac", size = 10336065, upload-time = "2026-05-11T18:53:41.099Z" }, - { url = "https://files.pythonhosted.org/packages/68/10/bf2d6738d72748b961a3751ab89522d58c54efc36a8e1a12161216cd45cf/pandas-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:bab900348131a7db1f69a7309ef141fd5680f1487094193bcbbb61791573bf8f", size = 9926101, upload-time = "2026-05-11T18:53:43.515Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/e35cf11c8a136e757b956f5f0efdcaa50aecde85ea055f1898dfc68262f3/pandas-3.0.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba7e08b9ac1d54569cd1e256e3668975ed624d6826f7b68df0342b012007bddb", size = 10457553, upload-time = "2026-05-11T18:53:46.394Z" }, - { url = "https://files.pythonhosted.org/packages/58/3b/1cdec6772bdbaf7b25dab360c59f03cadf05492dd724c6540af905389b07/pandas-3.0.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d71c63ae4ebdbf70209742096f1fc46a83a0613c99d4b23766cced9ff8cd62a", size = 10914065, upload-time = "2026-05-11T18:53:49.134Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/1ef644445fcd72e3627bceec77e3560636f87ddce4ed841afe76b83b5bf9/pandas-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e3a2ec42c98ffa2565a67e08e218d06d72576d758d90facb7c00805194d8f360", size = 11459188, upload-time = "2026-05-11T18:53:52.527Z" }, - { url = "https://files.pythonhosted.org/packages/7e/49/4d8d4f42cbc9c4adc7a1870f269c02cbd6cd40d059622c06fb298addcbad/pandas-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:335f62418ed562cfc3c49e9e196375c28b729dcef8543abf4f9438e381bf3c76", size = 11982966, upload-time = "2026-05-11T18:53:55.043Z" }, - { url = "https://files.pythonhosted.org/packages/38/55/792619469bab9882d8bbd5865d45a72f6478762d04a9af4bf0d08c503e95/pandas-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:3c20a521bbb85902f79f7270c80a59e1b5452d96d170c034f207181870f97ac5", size = 9876755, upload-time = "2026-05-11T18:53:58.067Z" }, - { url = "https://files.pythonhosted.org/packages/2a/af/33c469653b0ba03b50c3a98192d4c07f0c75c66b263ceb097fce0ee97d31/pandas-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:a2d2dff8a04f3917b55ab3910c32990f8ddf7eceba114947838cefa976a68977", size = 9198658, upload-time = "2026-05-11T18:54:00.733Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fa/b8c257bd76b8bd060c3a9151c1fca05e9b9c5e3af5d0f549c0356f6d143d/pandas-3.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:0d589105b3c14645af1738ff279b2995102d8f7a03b0a66dc8d95550eb513e04", size = 10787242, upload-time = "2026-05-11T18:54:03.564Z" }, - { url = "https://files.pythonhosted.org/packages/54/eb/f19206ffb0bf1919002969aa448b4702c6594845156a6f8050674855aac3/pandas-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:13fc1e853d9e04743d11ba75a985ccbc2a317fe07d8af61e445a6fd24dacd6a6", size = 10436369, upload-time = "2026-05-11T18:54:06.311Z" }, - { url = "https://files.pythonhosted.org/packages/fd/24/c7c39fb4fe22b71a0c2d78bf0c585c600092d85f94f086d2b3b2f6ca27e2/pandas-3.0.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:819959dab7bbd0049c15623fbac4e29a191b9528160a61fb1032242d8ced2d9c", size = 10358306, upload-time = "2026-05-11T18:54:09.085Z" }, - { url = "https://files.pythonhosted.org/packages/16/ec/dd2a9eb7fa1204df88c0864164e35b228ac581062ac612ba0a67fd812e4c/pandas-3.0.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60ae316d3fd75d1858d450d0db0103ea2be3e7d4a95ec2f064f7e2ae63f7b028", size = 10758394, upload-time = "2026-05-11T18:54:11.956Z" }, - { url = "https://files.pythonhosted.org/packages/95/6e/00c61ea8e85b4f6d8d35e11852a1a4998fc7fafc91c6a602d1cc9c972d64/pandas-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd3a518890b400d32f9023722dc9a9a5c969f00b415419a3c06c043f09bb5d7d", size = 11375717, upload-time = "2026-05-11T18:54:14.539Z" }, - { url = "https://files.pythonhosted.org/packages/31/89/8fc1c268969fac43688d65fd92e67df24bd128d53cb4d2eee534cd307399/pandas-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c39be2d709d01fa972a0cabc522389fceca4f3969332ba25a7d6c5802cf976a", size = 11828897, upload-time = "2026-05-11T18:54:17.146Z" }, - { url = "https://files.pythonhosted.org/packages/56/3b/e7d20dea247a3e6dc0bd8a6953854afbedc03951def4e7371e05e7263e25/pandas-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4db8c527972a821cf5286b40ccc57642a39bc62e62022b42f99f8a67fca8c3a1", size = 10900855, upload-time = "2026-05-11T18:54:19.72Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/68a0978d1ef8502b8492099beaa6e7a0c1b32e3b5d4f677f5810cb08711c/pandas-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b2c95f8bfc1ee412bf482605d7bfd30c12d1d26bd59fdd91efeef1d4718decb1", size = 9466464, upload-time = "2026-05-11T18:54:22.754Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/be/4f/5f3422a2afec5ffc46308b79e53291365a93748b498ac2e58bead0197916/pandas-3.0.5.tar.gz", hash = "sha256:dca3734d6ab7c906e6730f0788b0a1dbb9f2467731f9711f77995c8e9d62d712", size = 4658219, upload-time = "2026-07-22T22:19:28.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/09/7b95c4a0025227d6f118c4039b423412ac6a982db02864166185d812fbc7/pandas-3.0.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c1c05a767fe8e5b4fe9e1c29806829c582052eaedb9120a3da83ba3f69e24a5b", size = 10385742, upload-time = "2026-07-22T22:18:29.346Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0c/dc78fd8c4da477b4b5e8ad37295af352190d21ef63a9ee1bc071753074cc/pandas-3.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b86765f268b56f7e665b93bce9d5df69dee7f99e595cf8fb839483ab315942a3", size = 9932067, upload-time = "2026-07-22T22:18:31.833Z" }, + { url = "https://files.pythonhosted.org/packages/3e/71/3592c055cf44df9808550f9368ceda80ff2b224d355ef73fe251dcda1802/pandas-3.0.5-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c597ecf5616b5c420372c1d4d4c00dbbfba7398bea857dcc984347e1ea48417b", size = 10466756, upload-time = "2026-07-22T22:18:34.195Z" }, + { url = "https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b11c36e218331d0387cbe3a0a5f75162357a1d92d57b2b08a336ff94b19b2be", size = 10938525, upload-time = "2026-07-22T22:18:36.81Z" }, + { url = "https://files.pythonhosted.org/packages/f7/d0/317e7a0c67c0e69fa905a0161409397a7dc2d46ff611f6ca4803352c042b/pandas-3.0.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cf52e1f61d229496da17dc7ab54acdee627357e7008fd4fecba3d0ba2937fa58", size = 11489303, upload-time = "2026-07-22T22:18:39.287Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8d/36dade89b49e4f9d5cbdbe863772581f98c0c6d78fc39ad4c557f6f2e17e/pandas-3.0.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:db172144bb56422bd157812f3b021eacc255451470b31e2c633c349490a1cfee", size = 11989004, upload-time = "2026-07-22T22:18:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/9c/ba/18c4ec8a746e177da05a9e7a7963781d8ea195780724f854601b6ebd6b78/pandas-3.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:0d298e951f23016ce4699951d044ae6418dbc91bf68cefca0f77666fcbb4e5c6", size = 9826896, upload-time = "2026-07-22T22:18:44.539Z" }, + { url = "https://files.pythonhosted.org/packages/de/ec/28a57266b753799a87b8bc79e7887ac6fd981b8c6d2978a0b7e7b6bd708c/pandas-3.0.5-cp313-cp313-win_arm64.whl", hash = "sha256:66266d3442a5e8b3c90274c2b8b230bee42dd1c286bc822cc2f9f2c7e12b883e", size = 9094790, upload-time = "2026-07-22T22:18:47.468Z" }, + { url = "https://files.pythonhosted.org/packages/51/2f/cf6aae281264f4463f0875bcbb15fd2bb6d291cc535187dad1732475e4a9/pandas-3.0.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f264fc46911cc8131a7322a16199bbf8e353d27c10bb211f5bd0c814324dc36", size = 10390034, upload-time = "2026-07-22T22:18:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/06/ec/5189518c7a7659c4bdcc6b1eb32c46c6f3c86b0661ffd84143d1112c7732/pandas-3.0.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:53730687fcd161883b24e10411c06d6a4c0f2275d2faf3bb2bc25deb4ba8007c", size = 9980065, upload-time = "2026-07-22T22:18:52.249Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f1/598503ce8d7e3c35601e0747ba288c7864baae66380725bc12f13f884dfe/pandas-3.0.5-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:960d3ebcf249f75206899fcd2c6de53f736b7265759ced0d3e559df0b8b709b0", size = 10545532, upload-time = "2026-07-22T22:18:54.813Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/ceae2adf7034e07e9910299fe412e1819c4f0dd520700a888bcb03625448/pandas-3.0.5-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e94c2c5ca43bd3ca32bf64d32308887b65e5f9bfd8023ea52755107a999f93b", size = 10963120, upload-time = "2026-07-22T22:18:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/66/25/86e0f4451874eb79e688deeebe3c451fec4557f8952005818d800ee8ac7e/pandas-3.0.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e819dd5f62966b481a8cb649d3299ebd886a1ea91ed5a99bf7ce77c98d18ab94", size = 11563178, upload-time = "2026-07-22T22:18:59.729Z" }, + { url = "https://files.pythonhosted.org/packages/f3/45/8643daa3b4147e433adfcccefdd0380d3aad79d86b15d8999730fe1944d5/pandas-3.0.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3c5ed2e7c06e91d340dfd091d7934f9bc82e4a36b95f647f090b9d1c9ac649da", size = 12028708, upload-time = "2026-07-22T22:19:02.164Z" }, + { url = "https://files.pythonhosted.org/packages/96/58/ad979ae617615576e8aafd569c9d4b62f1191d896e38f51d66ba06f3b89a/pandas-3.0.5-cp314-cp314-win_amd64.whl", hash = "sha256:cd8f7c6dc98527058ee6264219343f5392240a6f1bfa654fc5d79023020d0c92", size = 9951806, upload-time = "2026-07-22T22:19:04.596Z" }, + { url = "https://files.pythonhosted.org/packages/69/32/7ac03886b304049a9d2625ee88f59af760d8a93bd30ed9239bce7b9869a8/pandas-3.0.5-cp314-cp314-win_arm64.whl", hash = "sha256:5183427f5a8156d480f30333777bc978be93650a49a7c01db26adffe95b31e85", size = 9238297, upload-time = "2026-07-22T22:19:06.836Z" }, + { url = "https://files.pythonhosted.org/packages/be/ed/1d1f2ee5547d5167face2376d11c8b2a4c7bfff5a416ee7a9046891fab1e/pandas-3.0.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:303da736987d481074ca720ada325f8bd80c64ebc2d45ed79b29df3aaa4a26ca", size = 10849690, upload-time = "2026-07-22T22:19:09.391Z" }, + { url = "https://files.pythonhosted.org/packages/57/55/17e17152e98fbb0c4b1e562bc65387a2f20a80db0f4a86bf8d3a0e4248d4/pandas-3.0.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3b2801bbb049d0136f6c213eae02b5fca969384fc2064dd728d8620552aa49da", size = 10509945, upload-time = "2026-07-22T22:19:11.773Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/817d44dbf83facf9556f33576d9af0a241981e7bb5c00606c0bcb5df8dda/pandas-3.0.5-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cce3a9d11d2b1f82c69a27ec1f4948a170e2c403c4bbfa8cca62e3fdebe2ef3a", size = 10392197, upload-time = "2026-07-22T22:19:14.024Z" }, + { url = "https://files.pythonhosted.org/packages/f1/da/889f00c0a6f5aa1545add70abbf01502dff87ab577adb855bd631c54d2f2/pandas-3.0.5-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef01af4d8dc6cd2c8d6c7736f149574ef93fe043811eeb5e445f2647154b5040", size = 10862726, upload-time = "2026-07-22T22:19:16.351Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/f1e934fb3c98fce859c6147c6785816c7b5b9ab7821115c5d8c4de9842b9/pandas-3.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e2759e890db96dfcffdbd9b86c3c2cb6afaf58def482820317e06163ec1066cd", size = 11414864, upload-time = "2026-07-22T22:19:18.981Z" }, + { url = "https://files.pythonhosted.org/packages/fe/be/d448af7d657d82e1888dd8551f79c6d6fb161080b5b9752d84d910ec2319/pandas-3.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b58b1b39d46a5862e3fb18f50d1a201398619d16a0f9f73f57eea5583cf0e63c", size = 11925105, upload-time = "2026-07-22T22:19:21.515Z" }, + { url = "https://files.pythonhosted.org/packages/29/c1/ccb4238212c8c4f496c584f3044d94e0c030ed8e1d68999db46c91c2242f/pandas-3.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:1c10461f6eeb35d8f05b6184c65c8b9991663b66c46b1d559b682cb34ae7c6ea", size = 10387612, upload-time = "2026-07-22T22:19:24.257Z" }, + { url = "https://files.pythonhosted.org/packages/d2/cf/6a51b2c38980e04c279fd2fa908a1b0982064e860444acfca4ec2e2c8359/pandas-3.0.5-cp314-cp314t-win_arm64.whl", hash = "sha256:3c5015fd1730fbf883647e88068176c839c102cea883ba1769a6f4593bfc1f8c", size = 9509776, upload-time = "2026-07-22T22:19:26.694Z" }, ] [[package]] @@ -1390,16 +1395,16 @@ wheels = [ [[package]] name = "panel-material-ui" -version = "0.13.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "bokeh" }, { name = "packaging" }, { name = "panel" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/c0/9be7012b9d0c9961c1af3a916d7a2c82231ef685f483ce1296c89d1d2d92/panel_material_ui-0.13.0.tar.gz", hash = "sha256:acbe510cbe1e5b11b313d51337dc8e3ce44d6161c0272e4587dfb2a4e3b98cb6", size = 3991602, upload-time = "2026-07-01T14:27:41.36Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/18/2056a59f07ab2de3775adfba4fda807ac451482cd6765c566d7aa1c22151/panel_material_ui-0.14.0.tar.gz", hash = "sha256:486f1e163aa62c2221a5a0003208feb143b135ff563f418d1df30ce2647db665", size = 3986513, upload-time = "2026-07-10T10:43:17.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/e3/bc943f1139a2397d1561afea7e7fa9385907699ec34bbfd6e46032eb462c/panel_material_ui-0.13.0-py3-none-any.whl", hash = "sha256:af7e5e70b769bffc4ed02e4e40f2dc1c4156cccba1a6a0eb31b987d47c8b82c1", size = 1741599, upload-time = "2026-07-01T14:27:39.238Z" }, + { url = "https://files.pythonhosted.org/packages/36/f2/4dac7c73673dd2b824aa9c539cbcbec3fadb5b602009ac3fa634f8d00438/panel_material_ui-0.14.0-py3-none-any.whl", hash = "sha256:152e09fc0b903e37d3b45bf8d3018cc3360b9112588a80c1254c3da3f3c2f22d", size = 1732403, upload-time = "2026-07-10T10:43:15.609Z" }, ] [[package]] @@ -1511,11 +1516,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.10.0" +version = "4.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/47/e4501f49c178ae1d9f4a75073fda4204f52647993f075a9db4d14930e0c5/platformdirs-4.10.0.tar.gz", hash = "sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7", size = 31224, upload-time = "2026-05-28T03:32:53.587Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/e6/cd9575ac904136b3cbf7aa7ee819ef86eedb7274e46f230e94ea4342e729/platformdirs-4.10.0-py3-none-any.whl", hash = "sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a", size = 22743, upload-time = "2026-05-28T03:32:52.175Z" }, + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, ] [[package]] @@ -1529,35 +1534,35 @@ wheels = [ [[package]] name = "polars" -version = "1.42.1" +version = "1.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "polars-runtime-32" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/27/99/fe77f10a13a778705ef05b499fc708c9a0b0a3680d9eb6bc6e1b6a6b9914/polars-1.42.1.tar.gz", hash = "sha256:2fe94f3059334650bd850ae19a9c165dcd5d9cb12cd95ea04de2201662e70e8a", size = 741532, upload-time = "2026-06-30T04:57:51.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/5b/5d0f0aa53c6e9a8ecbc99ff502edcf9584e5d08ab34ea407c086999103d5/polars-1.43.0.tar.gz", hash = "sha256:bb2c67553e4968c18dfe268a88ff9a5790d5c2e0b7ea7efe97640b9a90438c88", size = 749537, upload-time = "2026-07-21T04:30:25.966Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/be/6a/edd939cc6fa04b6415aaa9bf19720fc74ead81234b3d38542e0005816d4d/polars-1.42.1-py3-none-any.whl", hash = "sha256:3c0c65cdfa21a621650c4bdcbbccf93964d052fd766c3e70e84a55d961c259fd", size = 837622, upload-time = "2026-06-30T04:56:34.686Z" }, + { url = "https://files.pythonhosted.org/packages/76/28/a8eac2c1d1b2d2a4ba2eb745921616d863185d94b1afd91cbb07af9ef21a/polars-1.43.0-py3-none-any.whl", hash = "sha256:c49078b14e2d6b8ff5cc5b78b6d9638603ea5dffafb889d9204818822f55b813", size = 846493, upload-time = "2026-07-21T04:29:06.68Z" }, ] [[package]] name = "polars-runtime-32" -version = "1.42.1" +version = "1.43.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/59/15bcc4dac380c6d63efa5446d8317f22671cbd6c9dadd576bd17a334c45a/polars_runtime_32-1.42.1.tar.gz", hash = "sha256:4d4809e1c1b9a6611f6944f27b24abea902b5159e6b6fa262fd716e947af5afd", size = 3045460, upload-time = "2026-06-30T04:57:52.866Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/96/7e714cad082e9e6aaebb8886fcb1b0220d5c35149d4c8d3466bd7e7d581e/polars_runtime_32-1.43.0.tar.gz", hash = "sha256:5fb47a3a883402e62eab2fde5922f78c531d037aeece3640c15225f39228621e", size = 3090044, upload-time = "2026-07-21T04:30:27.185Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/29/16ff6e4e91d71e530d3581f45e342a9cc35072ac6b31dcbc2fa33de2569e/polars_runtime_32-1.42.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bbdc26d68ee5b23b0ce227fa0599220aa35b77c826b6b0a6b2d8e7f6c1c36974", size = 53117325, upload-time = "2026-06-30T04:56:37.972Z" }, - { url = "https://files.pythonhosted.org/packages/04/8e/4f8296fcfd1347f1351342fecf13bf2430d7efbae2f1f45964ec7930a99e/polars_runtime_32-1.42.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f6c0288be940b607dc4a7476c01e67fb6bbee93f5f1dd42c64970274c71008ba", size = 47446251, upload-time = "2026-06-30T04:56:41.459Z" }, - { url = "https://files.pythonhosted.org/packages/88/2e/0d66a7deadc453b890c3391034ca8ab4b05d0beaebbb92a7d65199fba61b/polars_runtime_32-1.42.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635d9dbcae2302ae223afb395d5cd220bffa61a53d0ab6871d17c8bc830101cf", size = 51359402, upload-time = "2026-06-30T04:56:44.595Z" }, - { url = "https://files.pythonhosted.org/packages/5d/10/ffb85fa380bc9c9000dc35f40f44954dde49023018501c54faab94b3a39e/polars_runtime_32-1.42.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d059e8e53cc114ff82f9bd791fd341dc53534a2c745e6f6aa37594c3a93f01fe", size = 57302723, upload-time = "2026-06-30T04:56:47.609Z" }, - { url = "https://files.pythonhosted.org/packages/c2/63/ca50adc62e44224ca5c622a842ba6f35ee87d1d40ef0df7ea2ed6c6edb08/polars_runtime_32-1.42.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f91f0b13588324905682809d270e1de5f1990c908721c8527657d77a044c9919", size = 51515673, upload-time = "2026-06-30T04:56:50.88Z" }, - { url = "https://files.pythonhosted.org/packages/63/c3/08fbbf38deaa17bf34a601d327cb7451074098673c78b7c1a8538dde9794/polars_runtime_32-1.42.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8b8bf972d99d48aaaa2582e2bce966a6f43bc815bd8725d15f5cab9e2fb15d17", size = 55217259, upload-time = "2026-06-30T04:56:53.834Z" }, - { url = "https://files.pythonhosted.org/packages/d1/0e/51db89361668fe077a835fc579277f824ba526e7daf7b94d23d25439e0d0/polars_runtime_32-1.42.1-cp310-abi3-win_amd64.whl", hash = "sha256:e9364c26da389a8b7339e4d29e20a3d12af730247e6ed3b7804bddce2477f428", size = 52715432, upload-time = "2026-06-30T04:56:57.109Z" }, - { url = "https://files.pythonhosted.org/packages/76/c5/2fb8592d691bd114de25d9c84300b23541dca7060eac11d7b4bed0327786/polars_runtime_32-1.42.1-cp310-abi3-win_arm64.whl", hash = "sha256:7051226e6b42ffc395a7a9190377cd28649fbfb991b8f85c6271f4e1cfb736fb", size = 46718300, upload-time = "2026-06-30T04:56:59.855Z" }, + { url = "https://files.pythonhosted.org/packages/0f/14/9b1f5eb1c5104ba1ceb380a7308ffcd979f3ce1df86e76293062698f2ff1/polars_runtime_32-1.43.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6707193d30a7135bce0424304f76d8145270527444097548e794ad8d26823b70", size = 53059463, upload-time = "2026-07-21T04:29:09.194Z" }, + { url = "https://files.pythonhosted.org/packages/ca/14/73d77d1c0c928eb599d9516d874af0cd2b6225201e1327a6c4857e6776d0/polars_runtime_32-1.43.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:78ca2f97740b2a6beb36eb112749280b5e08750c60f53c08d2feffebdba9d35a", size = 47499586, upload-time = "2026-07-21T04:29:13.229Z" }, + { url = "https://files.pythonhosted.org/packages/80/b1/98278fa796f93d0975fd3fe1d4ab4031707d4a9f1da44c21c29996b62c73/polars_runtime_32-1.43.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa99bb2c7ee0a9392ae50b350af0ed17acf0519d15c75fe223021798566174", size = 51326702, upload-time = "2026-07-21T04:29:16.084Z" }, + { url = "https://files.pythonhosted.org/packages/ab/7d/24ae73389aac03296925973c4e2cbe2e4982e859b9fad69eb1a72b9026fa/polars_runtime_32-1.43.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ecc8feaf04de5989a29245885921db612ef1ce9065e5cb6ec37495acfa55bba", size = 57266705, upload-time = "2026-07-21T04:29:19.288Z" }, + { url = "https://files.pythonhosted.org/packages/5c/49/2026be1f7b51242ad62e08b20728462558e161fb84b564e5b986f2b38664/polars_runtime_32-1.43.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:01e1471a5ee161a969c7a96991d8e5d20b97a0b7fe075df9c7a01f52a49c5ac2", size = 51484129, upload-time = "2026-07-21T04:29:22.639Z" }, + { url = "https://files.pythonhosted.org/packages/db/e2/047c7695f08a9b18614c0e1ec5e70a754455542a21a6d52955f7f05c6268/polars_runtime_32-1.43.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9cd8b813afe67d59e87027dedcaf5c6a06fe602472fd74e1a49f4bafea47259c", size = 55169401, upload-time = "2026-07-21T04:29:25.902Z" }, + { url = "https://files.pythonhosted.org/packages/92/dc/bfd2533c487563c7a21ab7d7af3d78f820b0236e14dc5ee63d46188cd275/polars_runtime_32-1.43.0-cp310-abi3-win_amd64.whl", hash = "sha256:41a75fb3cb4cc574eb21801383578f75cfc374597c22322ef457ab7bac8a3301", size = 52541527, upload-time = "2026-07-21T04:29:29.162Z" }, + { url = "https://files.pythonhosted.org/packages/46/d7/5c47f1bf57479d1671669af9c421f9abf4986b2ddaa64c177244ff811de0/polars_runtime_32-1.43.0-cp310-abi3-win_arm64.whl", hash = "sha256:c285e598dd91e08560e519275b8b8108adbafb438d218a175ebe073dbc2027fb", size = 46552281, upload-time = "2026-07-21T04:29:32.224Z" }, ] [[package]] name = "pre-commit" -version = "4.6.0" +version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -1566,9 +1571,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/22/2de9408ac81acbb8a7d05d4cc064a152ccf33b3d480ebe0cd292153db239/pre_commit-4.6.0.tar.gz", hash = "sha256:718d2208cef53fdc38206e40524a6d4d9576d103eb16f0fec11c875e7716e9d9", size = 198525, upload-time = "2026-04-21T20:31:41.613Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/6e/4b28b62ecb6aae56769c34a8ff1d661473ec1e9519e2d5f8b2c150086b26/pre_commit-4.6.0-py2.py3-none-any.whl", hash = "sha256:e2cf246f7299edcabcf15f9b0571fdce06058527f0a06535068a86d38089f29b", size = 226472, upload-time = "2026-04-21T20:31:40.092Z" }, + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, ] [[package]] @@ -1864,15 +1869,15 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.4.3" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/26/8b004cc36f430345136f6f00fa1aa9ed596c8ed1e8504625fa79522ff39c/python_discovery-1.4.3.tar.gz", hash = "sha256:ad57d7045a862460d4a235986c33f13ed707d3aeb9153fa47eb7dfd0d4673289", size = 70438, upload-time = "2026-07-03T13:21:51.621Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/78/9b77ecb4644d1bbea94d29abf78f21c47eca6eb79e9745b702ec0bed2e19/python_discovery-1.4.3-py3-none-any.whl", hash = "sha256:b6e1e4a7d9e3f6948c39746ffe8218225162d738ba39d05ab1d2f6c1cac4878c", size = 33885, upload-time = "2026-07-03T13:21:50.174Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, ] [[package]] @@ -2095,27 +2100,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.20" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489, upload-time = "2026-06-25T17:20:37.578Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665, upload-time = "2026-06-25T17:19:44.702Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649, upload-time = "2026-06-25T17:19:48.787Z" }, - { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638, upload-time = "2026-06-25T17:19:51.354Z" }, - { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227, upload-time = "2026-06-25T17:19:54.044Z" }, - { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882, upload-time = "2026-06-25T17:19:57.037Z" }, - { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808, upload-time = "2026-06-25T17:20:00.357Z" }, - { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094, upload-time = "2026-06-25T17:20:03.446Z" }, - { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176, upload-time = "2026-06-25T17:20:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767, upload-time = "2026-06-25T17:20:09.191Z" }, - { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132, upload-time = "2026-06-25T17:20:13.602Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828, upload-time = "2026-06-25T17:20:16.635Z" }, - { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418, upload-time = "2026-06-25T17:20:19.4Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770, upload-time = "2026-06-25T17:20:22.033Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698, upload-time = "2026-06-25T17:20:25.259Z" }, - { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322, upload-time = "2026-06-25T17:20:28.612Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274, upload-time = "2026-06-25T17:20:31.871Z" }, - { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/94/1e5e4967626faf12fa56999cd6222dff6992ceb086ad7945756baf70c7a7/ruff-0.16.0.tar.gz", hash = "sha256:e460aafd5495ec89efaa6ced2e4a9a581116451e1c88b9d37ef497e0f8e93982", size = 4790557, upload-time = "2026-07-23T19:11:30.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/81/1c8818fee7ce1a04cd7d1b3172e0a8f8e4f1dc4feb7fc390e16daa8af323/ruff-0.16.0-py3-none-linux_armv6l.whl", hash = "sha256:e5115729eb08c585e5121978ba5d5b60caeae394ce21b9fb5e6cd33a1c6c9b1e", size = 10754633, upload-time = "2026-07-23T19:10:46.415Z" }, + { url = "https://files.pythonhosted.org/packages/23/df/beaf59c09d68db84304d555f188b276a77132a5d5b0b67a5c762aa143628/ruff-0.16.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3c954b1d580bfa035b41654f7858cc7e71d5fc3ac5b723dd62bd9133830ed522", size = 10969164, upload-time = "2026-07-23T19:10:50.271Z" }, + { url = "https://files.pythonhosted.org/packages/42/ce/741cd197496a1abbf51352710fd15ed995d2a2be87189c1da26a450d6e83/ruff-0.16.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e01c21d10eb1b29f47b7454e1f4056db9a3f0260c646aa88457c610291db9f81", size = 10488846, upload-time = "2026-07-23T19:10:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/52/2a/a2db8e88cade358f5cdcb05674a917751074109315d014eb6352d9a893f7/ruff-0.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e364e5ed22ed8dc05082fd78e35308618260907ac2d3c1d637b2e682415b6c9", size = 10889729, upload-time = "2026-07-23T19:10:54.89Z" }, + { url = "https://files.pythonhosted.org/packages/42/65/62a771694ebd63029dc953e27dbad40e1588bd4860ff9fe881018fddaa49/ruff-0.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d327b8fc113a1d4421a04f3839d3752057c8dd1ee320223a6f3f52d04ada462a", size = 10568275, upload-time = "2026-07-23T19:10:56.993Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e2/ced249fe8af5f086c5c58cc21cc3356d50f32f7401c5df87050c999620a7/ruff-0.16.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9b50c55e263103586b3dcf5f73d479eb8cb5fdb6098fec59a62891dab653717", size = 11385112, upload-time = "2026-07-23T19:10:59.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/05154977a8fd69eeb6c103271f55403bfd8711f5c0f8ed07489d95a504e7/ruff-0.16.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff4a79ce3ec0172f3241943835de1c4cb4e2dcd07f0f8c2d02603dbbbee4b17", size = 12207008, upload-time = "2026-07-23T19:11:02.154Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/98225831a3a1eab0e02f4acc6ca6559a98611dcc68b6965ff4b7234627c1/ruff-0.16.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e95c448fca1fb2a18372a9440926c5a6ee789639bb975c72e7ae6d0b04218ab4", size = 11650842, upload-time = "2026-07-23T19:11:04.557Z" }, + { url = "https://files.pythonhosted.org/packages/91/66/6bd3cf90500653d55dc0ffc8507aa8300bd49d0214b2e8cb4d3fef2943ba/ruff-0.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f11a8d11010301d0a398a2fdef67691feca7294da6aef55e2150e8fa2cd520b", size = 11400718, upload-time = "2026-07-23T19:11:09.233Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a2/a54eb4eae05d66364050a5d3b8a9c5ef88196531b3cbe7109d873f87f819/ruff-0.16.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:48044c678e9cb8698246c99b14aaccfa6601dea7379eb48a6f8f73f7a6d86cd0", size = 11426177, upload-time = "2026-07-23T19:11:11.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/be/16e3eea4b2a478a496919f5e36f17c4559e54620bd3bbac5d6affa068006/ruff-0.16.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7aa0959bad8eb8bef50340154fc9b58678dae31fa4293afa38b44b6e552c0213", size = 10856126, upload-time = "2026-07-23T19:11:14.221Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/252eb8b868a16eec7257c14f504f77537e734b2d69c762e639e588e304a3/ruff-0.16.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:28ea2b7df8ebf7f9da6b7d47b230ab48f387c0a29be3b474c4d0740e197bb9af", size = 10571208, upload-time = "2026-07-23T19:11:16.378Z" }, + { url = "https://files.pythonhosted.org/packages/21/09/817a482f542f7570cbb4554b26e896610c7114f539b1d9e2d2145bf6bef6/ruff-0.16.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:33a3dfac8c35f81498dea9181bccc2f4c4bc8f1521a1dd9406e77643e0f0fb09", size = 11063329, upload-time = "2026-07-23T19:11:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/2e/23/9403c180ca1cb9b1f7335f5c3e5305c09d49ea5b345196682a36028bde4a/ruff-0.16.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a5237a0bda500d30d81b8e07a6973a5cbc772864cbf746ae2f4e8a2e01c9f4ed", size = 11489751, upload-time = "2026-07-23T19:11:21.74Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/1b2ef7bcde851c78d7f17f1cca13fd6dc695fc4b3d6197941e72cae5b132/ruff-0.16.0-py3-none-win32.whl", hash = "sha256:7fab76fa065c873f41ff744347c6e77bcc3dfec4bcc754dc26b63d23c0f7f5fb", size = 10785885, upload-time = "2026-07-23T19:11:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a3/d5e4ef7a56be3f928ffb90b94c25ba7d3cb9c7fe0736aeaaedf361770712/ruff-0.16.0-py3-none-win_amd64.whl", hash = "sha256:429c117f022bf481fabd9d551e7a3952b24c65e6ef44337ea09d90bebef14472", size = 11923141, upload-time = "2026-07-23T19:11:26.409Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9a/8415f2657cbe200f41a4531ccededf135505a92d4a012229121f885b26f9/ruff-0.16.0-py3-none-win_arm64.whl", hash = "sha256:14296fedcd2705c77ab8235439278bbb38f285cf7da5528b00b3e330c3d4872d", size = 11273407, upload-time = "2026-07-23T19:11:28.705Z" }, ] [[package]] @@ -2147,11 +2152,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.4" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/38/e12680bbe6b4f8f3d17adcaf38d26850aa756c85cf4a80e79fc12a018fe8/soupsieve-2.9.1.tar.gz", hash = "sha256:c33e6605bbc71dd628b00c632d58ae607c22bade247e52553928f83bbb75b4ba", size = 122261, upload-time = "2026-07-21T16:57:17.452Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/437fe806897c2d6cfdc3ee43a18da8bf8e568530a4ae9bac781541ca9896/soupsieve-2.9.1-py3-none-any.whl", hash = "sha256:4f4477399246b7a0c720a88ca2454b11cd6bb9ae4c9d170140786e916776c14c", size = 37404, upload-time = "2026-07-21T16:57:16.421Z" }, ] [[package]] @@ -2201,14 +2206,14 @@ wheels = [ [[package]] name = "sphinx-autodoc-typehints" -version = "3.12.1" +version = "3.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "sphinx" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/ae/8ad5e79a0f7beac5e5810b2a8d0a1c6c63c795ba265a81970070abf15f32/sphinx_autodoc_typehints-3.12.1.tar.gz", hash = "sha256:4bbf6f6b907586d3b2a3ae2b23e0f86be939f19a7449e917d304df57ff9674d1", size = 84421, upload-time = "2026-07-03T13:52:09.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/99/e5a23a7bc3f9b6b799dbf72ab3955cdc0b2382803b5d11eaafcc76621662/sphinx_autodoc_typehints-3.13.0.tar.gz", hash = "sha256:59eb4fe26f71ccd7a8f10caadddcb3c3b31df7016f91bda546da2a333bae4e12", size = 85285, upload-time = "2026-07-21T13:10:44.981Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/a2/d5964523f0c98e39ef608a09f3de23032aa32fbe3e98a262e2112c39f985/sphinx_autodoc_typehints-3.12.1-py3-none-any.whl", hash = "sha256:932472c9cc160e6a0dc34eca13d3d6a9823ed6e6dc505d7c12f6963c983cf8f6", size = 42950, upload-time = "2026-07-03T13:52:07.696Z" }, + { url = "https://files.pythonhosted.org/packages/39/1c/4b3c9b1a3130b9d717f517351ce8325a62fad7c4f846cc324025c22a43c6/sphinx_autodoc_typehints-3.13.0-py3-none-any.whl", hash = "sha256:d71c388c865f3bedc1f32416febb0f8886b3051a4cd8bd8677e64b8b65c9b475", size = 43440, upload-time = "2026-07-21T13:10:43.793Z" }, ] [[package]] @@ -2383,14 +2388,14 @@ wheels = [ [[package]] name = "tqdm" -version = "4.68.4" +version = "4.69.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/5f/57ff8b434839e70dab45601284ea413e947a63799891b7553e5960a793a8/tqdm-4.68.4.tar.gz", hash = "sha256:19829c9673638f2a0b8617da4cdcb927e831cd88bcfcb6e78d42a4d1af131520", size = 792418, upload-time = "2026-07-07T09:58:18.369Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/84/da0e5038228fa34dfd77c5026b173ed035d2a3ba31f1077590c013de2bff/tqdm-4.69.1.tar.gz", hash = "sha256:2be21080a0ce17e902c2f1baeb6a74bf551b67bbdfa4bc0109fad471d0b4cb0d", size = 793046, upload-time = "2026-07-24T14:22:02.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/2a/5e5e750890ada51017d18d0d4c30da696e5b5bd3180947729927628fc3cb/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2", size = 676612, upload-time = "2026-07-07T09:58:16.256Z" }, + { url = "https://files.pythonhosted.org/packages/01/50/5817619a0fca56aff06383dbfde7ae017b3ca383915b3f1e4713164273cf/tqdm-4.69.1-py3-none-any.whl", hash = "sha256:0a654b96f7a2660cceb615b56f307ec2bef96c515409014a429a561981ab52b4", size = 675452, upload-time = "2026-07-24T14:22:00.048Z" }, ] [[package]] @@ -2404,27 +2409,27 @@ wheels = [ [[package]] name = "ty" -version = "0.0.56" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/55/07/fb29aea5235b0aa8ecfc4d1cc6ddf9fba8b863d67d96c6d345694d644c43/ty-0.0.56.tar.gz", hash = "sha256:84d114dc3796361c0fc72945016eabd74d46b9ee64f198cb0e485719704681e5", size = 6050123, upload-time = "2026-07-01T16:44:56.036Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/48/bce79e7ca5c1cc529d3e0d37ddd1121aea4b68a4f749974ad1cc77161871/ty-0.0.56-py3-none-linux_armv6l.whl", hash = "sha256:186d4a53e15747c947e1ec3d7eec8e345d8e40a1ca10e634c585db52497e87dd", size = 11643066, upload-time = "2026-07-01T16:44:18.374Z" }, - { url = "https://files.pythonhosted.org/packages/80/d1/22555d8a1d719661f10050f3865d877bbf497da908961c75fe22217dd18a/ty-0.0.56-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aae1a980fd9535da0469b7ba2b2e1b54a907743a5e0f442dd57eee9f5bfd034c", size = 11407487, upload-time = "2026-07-01T16:44:20.956Z" }, - { url = "https://files.pythonhosted.org/packages/cf/2d/b3b7a74ce8bc59ef48843ad80179bb0d9598bbd6cfc0d11d519bdf6b1352/ty-0.0.56-py3-none-macosx_11_0_arm64.whl", hash = "sha256:afd3058c0a6c5f241e814734f133008c93ee805f61c9cf4ce7412b8822b5d9ad", size = 10962270, upload-time = "2026-07-01T16:44:22.959Z" }, - { url = "https://files.pythonhosted.org/packages/64/ac/6c2fd7de0304a8a7218a756af74f7e62a5e8540fdb175e0a869e51042345/ty-0.0.56-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:058b52f7a823ac13aae3cae30809dd6b5145794b64d8478f9ef38c75d79b4483", size = 11471406, upload-time = "2026-07-01T16:44:25.327Z" }, - { url = "https://files.pythonhosted.org/packages/50/b6/11d861156861c03c7726b74558f9a0e0092661aff83a4fda1279df28c425/ty-0.0.56-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c66e00c1522add1f2bbdd2e45828c953b35c306b7bef03ec9169c75a63699a0", size = 11445612, upload-time = "2026-07-01T16:44:27.531Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ba/09df108582090f3c0770ec4bc8675affed60248f6793a78d909be16211d9/ty-0.0.56-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40903d71c669a30691b5a5d5728056c7877a1bd6be4f233a38883a8b28cf34d7", size = 12093889, upload-time = "2026-07-01T16:44:29.548Z" }, - { url = "https://files.pythonhosted.org/packages/d7/f7/dbb4b4ccb69cd64c209ae55b1ab788ace8222c2bc1f6845be9e7cbedbf25/ty-0.0.56-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63fe3947fe0c46c69a7d950e6832ee70a9ec17321fefbff3d2e3c20baf9e5bd0", size = 12666337, upload-time = "2026-07-01T16:44:31.586Z" }, - { url = "https://files.pythonhosted.org/packages/86/e9/73f903fe4a3d9ea02f26f57c1eb07e3b1029ec92b0e8c2364718893440e3/ty-0.0.56-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71a0c1a72f9854532e710e119b6871ffe4542c8a65146f1f65dcd78fecd885b4", size = 12280247, upload-time = "2026-07-01T16:44:33.637Z" }, - { url = "https://files.pythonhosted.org/packages/d6/90/cebd222495832f1a00dcd321ba25f3cab804221a4991b992c2178bec68ee/ty-0.0.56-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70d1665596494e24d8ebd198438872b5a56ec3cae5f2bcf6c673be797acc4e3c", size = 11991107, upload-time = "2026-07-01T16:44:36.122Z" }, - { url = "https://files.pythonhosted.org/packages/b7/07/8f7337a07250f42d975cdb6decf47fc5b421e6c7da5e3e7be1e85f63a7e5/ty-0.0.56-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:778f99e51558afc1dbbe48ee38ab6aae7b31390ed8c1a1ef1499b295e9f1e82f", size = 12298970, upload-time = "2026-07-01T16:44:38.243Z" }, - { url = "https://files.pythonhosted.org/packages/3c/b9/a52cd59034a48f5f18c6b155cc2cc36861d874b6d0af204b12c898024c3d/ty-0.0.56-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:867bc5708e0066bb4ff6c7db524bd5deea2676c62bfe71d3303138b3be850af0", size = 11425683, upload-time = "2026-07-01T16:44:40.473Z" }, - { url = "https://files.pythonhosted.org/packages/1d/2e/48e42d33357d52eefb695c0c3fcfc96879b73668a7447d1d1e0ad774fedc/ty-0.0.56-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a6012f4189c928edb330a37deb9930f982380bd4aa7c4b8e0428eec9651c7551", size = 11469258, upload-time = "2026-07-01T16:44:42.513Z" }, - { url = "https://files.pythonhosted.org/packages/d5/01/ad1b4138be1e3fa97863af3925aa2134f17a593240c35dc38c3429fb5ad1/ty-0.0.56-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8ee83de1a7ff4cc32837ec06134ce391d441bc5b35ecd8d3cfe053f120f3e4c1", size = 11758736, upload-time = "2026-07-01T16:44:44.567Z" }, - { url = "https://files.pythonhosted.org/packages/09/34/9d81967ff240eaa57e9249728ef7b7790747cf6d3c9a98ec86b2cfdcc8ee/ty-0.0.56-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:62619b3b0e2c6248ef30d3f0e2f2217ae9893040585be07f32324242f197cd6f", size = 12100242, upload-time = "2026-07-01T16:44:46.584Z" }, - { url = "https://files.pythonhosted.org/packages/c3/36/f51d4666d2de6cf33c1f3a1fcc4bb6b70b197dd6ceaa491eef71d78fe8e8/ty-0.0.56-py3-none-win32.whl", hash = "sha256:b30687bb5cd9729d34c889a289edf32770388d9bb05243e534e723fb45e0381b", size = 11093759, upload-time = "2026-07-01T16:44:49.171Z" }, - { url = "https://files.pythonhosted.org/packages/5e/b4/8fb5d4acfa4afb152245b20fa263069a7547bd1f8e4bfca4eda280c897d7/ty-0.0.56-py3-none-win_amd64.whl", hash = "sha256:ad4c8c47b6f4e3f9ed3fc0b1a5d650088d229e17dd8f63c1826d6bbe94cc3235", size = 12100327, upload-time = "2026-07-01T16:44:51.26Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fc/6a183e71edde90d0c35c2303f23f7a45b6891d1a2c45daf7b8f869831e19/ty-0.0.56-py3-none-win_arm64.whl", hash = "sha256:57538f273d444a5f1293fa7860e967178afe3917611fc5eff16b64e1204fe0d6", size = 11538780, upload-time = "2026-07-01T16:44:53.8Z" }, +version = "0.0.63" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/ce/cbeaa5c7576fec643609dfbf200d59493523b1cc0481d4e7a5effcbf0630/ty-0.0.63.tar.gz", hash = "sha256:c2f66439393b3acac69306c117d4ae44638ce5fffa4a20c21046e85bd473359f", size = 6280695, upload-time = "2026-07-23T11:41:39.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/e4/d17a8e113ab15c692fe6fb9c422112d4bee7e829d80ced35826b45d98d98/ty-0.0.63-py3-none-linux_armv6l.whl", hash = "sha256:9a4ef7782e3af314fb63d006bec5ac3025bd70fee774b4dcfb5e44e8564f1994", size = 12056302, upload-time = "2026-07-23T11:41:03.5Z" }, + { url = "https://files.pythonhosted.org/packages/be/0b/357234c815dc4bfcc88c3f860aa0983fe4228c8aa2a5bb16c35ee08a94af/ty-0.0.63-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:01eab0ab70d51ad10298aa2d4b058b387a1fe93e5ee52d2a1ee23e9c69ba8354", size = 11737674, upload-time = "2026-07-23T11:41:05.862Z" }, + { url = "https://files.pythonhosted.org/packages/1c/13/193d9aeeb6774690351cff9fafabd3ae9b54cc225d125e07fa004ce23bdc/ty-0.0.63-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a671b61eaad16178389e05b9c108c9cb75ae8d84968fe55906484f55d6268338", size = 11264191, upload-time = "2026-07-23T11:41:07.963Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/c5843e16759a2b25fc8a7197473474038e585ac9fdaa6fa16aeb6384bf6a/ty-0.0.63-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b255cc83d95c51bb9ee4931303fdbece8cb1d6d7c655eb77a3f2c8f349fb64d6", size = 11818890, upload-time = "2026-07-23T11:41:09.885Z" }, + { url = "https://files.pythonhosted.org/packages/06/20/adf83ae1fc570d9bb449605e1eeeaa523ad2329ca838a636698b5c135d11/ty-0.0.63-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0da1e8183aa4f893421a173904478021498f542ee41273660538da6649a9631c", size = 11853141, upload-time = "2026-07-23T11:41:12.151Z" }, + { url = "https://files.pythonhosted.org/packages/ea/90/f8effd846e3ee13486ea08257c13094d58b9f188c5641bf609d6a7d5c09f/ty-0.0.63-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128f38eb5a67199e3811426386f7ec96f41251ddf45e04ddc0bcbb29d4853245", size = 12545184, upload-time = "2026-07-23T11:41:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/b8/76/aac3a30d40431eb1d329acea016b248f5b6255e30ef3d28e19447e344be2/ty-0.0.63-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bedf34aff8b0557f2a7119b314a68a9c9e58c1d21554d0e2748f2c5fe6a1f638", size = 13062375, upload-time = "2026-07-23T11:41:16.441Z" }, + { url = "https://files.pythonhosted.org/packages/62/3d/0158733932893e17f6008dadd74c8d7aed422fefc66e47fe77888014fe8c/ty-0.0.63-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7328d63c34587606dce02935a25404f54cd0161bfe413b8f7fc21d174aead612", size = 12619461, upload-time = "2026-07-23T11:41:18.538Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/e280ad095b050778f16f493d49a073fa5f6d8f301d3e2e59be6a672ba05c/ty-0.0.63-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504c4457f3a62afe836c1f26a2c9a12549299095f9cc4146778558df51a7515c", size = 12376402, upload-time = "2026-07-23T11:41:20.482Z" }, + { url = "https://files.pythonhosted.org/packages/57/57/619788bf335cb86b090470b557ae1eed33613dc24e12142505d32b462e58/ty-0.0.63-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7394ed39424b027c89d5f0c818871c791e33958b88f5bb13e14bd4a12a3ca631", size = 12671377, upload-time = "2026-07-23T11:41:22.489Z" }, + { url = "https://files.pythonhosted.org/packages/07/a2/0aff2cdd3c98e2c4729337542b8da0ce1980790e1600e0c4a717a1f46293/ty-0.0.63-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:94c3f155230490f8fb505911655e940c630c25cc0c35a76690cb413254fb4437", size = 11768090, upload-time = "2026-07-23T11:41:24.558Z" }, + { url = "https://files.pythonhosted.org/packages/43/4a/cdb5f1d26154144dfee08e1bd671daf827238170f7cee9dad43990bb2016/ty-0.0.63-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c16e1d8b4f0ae99106d5f13a894034a202baf6cdab61e2bb1a239de82904f839", size = 11867747, upload-time = "2026-07-23T11:41:26.794Z" }, + { url = "https://files.pythonhosted.org/packages/7e/26/ecc09ecb70bc9fbfdf42fa57ff29568f173e8200df575a0d72dc2b8486f9/ty-0.0.63-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b66293dad89eaed4b9fbf3b661614dbeb85b59ba037178a3f9a0adedf264da5c", size = 12120000, upload-time = "2026-07-23T11:41:28.731Z" }, + { url = "https://files.pythonhosted.org/packages/14/07/862822f9c2c397785b69b25cf79b4dfc3c0d55684b9adf11ac194450f8e1/ty-0.0.63-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8b29cc832e2ec73502c97dd7325d6ae0e085b34a33529a0f785192317d9862fc", size = 12477862, upload-time = "2026-07-23T11:41:30.847Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c2/50b08b641578d6f48e8aa28a91d0de32c91aa24dd926ed199e8afd2d37ea/ty-0.0.63-py3-none-win32.whl", hash = "sha256:f0a8fbfd1f990c0c5d85cce018d438969ac79e49247e2192c9745394bb7d17ab", size = 11433155, upload-time = "2026-07-23T11:41:33.28Z" }, + { url = "https://files.pythonhosted.org/packages/92/2d/d422a5568f0d1f317186be22241288dc6e08b71dc24aca223f22038ac2d2/ty-0.0.63-py3-none-win_amd64.whl", hash = "sha256:2aa2370bdd6f42e9f37518c812379bef70b9162f9e5aad511495229b0b71cb93", size = 12450036, upload-time = "2026-07-23T11:41:35.559Z" }, + { url = "https://files.pythonhosted.org/packages/35/97/2c9748e28ead0650c7ad3e5f74f178832ceabd7cb5c272a882f29eb32ee4/ty-0.0.63-py3-none-win_arm64.whl", hash = "sha256:95ac1a62162c3c7ac204731e95ebf766d62a46a7bfa238a6acbe923fcb772cb1", size = 11826243, upload-time = "2026-07-23T11:41:37.749Z" }, ] [[package]] @@ -2462,11 +2467,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2026.2" +version = "2026.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/ff/5a28bdfd8c3ebec42564ac7d0e54ca3db65044a9314a97f9564fa7a1e926/tzdata-2026.3.tar.gz", hash = "sha256:4a1518b8993086a7982523e071643f3c0e5f213e75b21318e78bcabfff9d1415", size = 198674, upload-time = "2026-07-10T08:50:37.887Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, + { url = "https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl", hash = "sha256:dc096730c87af6cab1b171c9d532be840741ff5d459015e7f6947bd7d7e54931", size = 348168, upload-time = "2026-07-10T08:50:36.46Z" }, ] [[package]] @@ -2498,20 +2503,20 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.50.2" +version = "0.51.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/f6/cc9aadc0e481344a42095d222bfa764122fb8cfba708d1922917bd8bfb01/uvicorn-0.50.2.tar.gz", hash = "sha256:b92bf03509b82bcb9d49e7335b4fd364518ad021c2dc18b4e6a2fec8c955a0bb", size = 93716, upload-time = "2026-07-06T10:38:31.984Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/65/b7c6c443ccc58678c91e1e973bbe2a878591538655d6e1d47f24ba1c51f3/uvicorn-0.51.0.tar.gz", hash = "sha256:f6f4b69b657c312f516dd2d268ab9ae6f254b11e4bac504f37b2ab58b24dd0b0", size = 94412, upload-time = "2026-07-08T10:59:05.962Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/f0/7c228ee10c7ab8fd3a21d06579a6f7c6075c6ce72594a20fb5d2f206ff24/uvicorn-0.50.2-py3-none-any.whl", hash = "sha256:4ae72a385630bcc17a0adb8290f26c993865e0b43a2114c2aab96420172c056a", size = 72846, upload-time = "2026-07-06T10:38:30.543Z" }, + { url = "https://files.pythonhosted.org/packages/45/ec/dbb7e5a6b91f86bfb9eb7d2988a2730907b6a729875b949c7f022e8b88fa/uvicorn-0.51.0-py3-none-any.whl", hash = "sha256:5d38af6cd620f2ae3849fb44fd4879e0890aa1febe8d47eb355fb45d93fe6a5b", size = 73219, upload-time = "2026-07-08T10:59:04.44Z" }, ] [[package]] name = "virtualenv" -version = "21.6.0" +version = "21.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -2519,9 +2524,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/65/ec1d92091671e6407d3e7c1f5801413bb7b2b57630a50cae7750456ba0ed/virtualenv-21.6.0.tar.gz", hash = "sha256:e18a4d750f2b64dea73e72ffde3922f3c52365fabdc8157ebd3da20d031c4734", size = 5526111, upload-time = "2026-07-06T22:49:56.972Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/e7/2fbd0cc1653c53eed8f10670538bb547de2b3e37aacad283faa82a71094b/virtualenv-21.6.0-py3-none-any.whl", hash = "sha256:bce9d097950fef9d81129b333babfb7767072850c2f1acce0ec536708401bfd1", size = 5506216, upload-time = "2026-07-06T22:49:54.941Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, ] [[package]] @@ -2616,38 +2621,62 @@ wheels = [ [[package]] name = "websockets" -version = "16.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, - { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, - { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, - { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, - { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, - { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, - { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, - { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, - { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, - { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, - { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, - { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, - { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, - { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, - { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, - { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, - { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, - { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, - { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, - { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +version = "16.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/f7/bc3a25c5ec26ce62ce487690becc2f3710bbc7b33338f005ad390db0b986/websockets-16.1.1.tar.gz", hash = "sha256:db234eda965dcce15df96bb9709f587cd87d4d52aaf0e80e2f34ec04c7670c57", size = 182204, upload-time = "2026-07-17T22:51:05.858Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/fd/6ec6c6d2850aea25b1b2aa9901a016980bb87d01e89b3eb00470b1b5d471/websockets-16.1.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ab59169ace05dcb49a1d4118f0bde139557adf45091bd85747e36bf5de984dd1", size = 179587, upload-time = "2026-07-17T22:49:38.959Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d8/1d299d2dd34087db39831a34cc645ef8a6f89d78efada6983093513cd81c/websockets-16.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5e3b7d601f6f84156b08cc4a5e541c2b50ad7b36cfc302b657a12477c904a5df", size = 177272, upload-time = "2026-07-17T22:49:40.293Z" }, + { url = "https://files.pythonhosted.org/packages/3d/86/0a70d3ae2f0f2256bb41302d9804dbca65d4360281e7feb3e1f94102ac46/websockets-16.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cd2ca96a082a36964aca83e992f72abeb61b7306c1a6cba4c7d06a7b93750cac", size = 177530, upload-time = "2026-07-17T22:49:41.786Z" }, + { url = "https://files.pythonhosted.org/packages/b5/c2/c676c69444d9db448b3f0a55a98dcc534affce0bce961d9d2f0b8499b10a/websockets-16.1.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f5d497865f05bb222cab7016c6034542e84e5f29f49c6fd3f4939cda7197b5b8", size = 187197, upload-time = "2026-07-17T22:49:43.658Z" }, + { url = "https://files.pythonhosted.org/packages/0b/13/88137fbaf726ebe29d62c1117fa11fa2bbb6209dc79d4ad738efbe36a2aa/websockets-16.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bae954c382e013d5ea5b190d2830526bfa45ad121c326da0049b8c769f185db6", size = 188433, upload-time = "2026-07-17T22:49:45.147Z" }, + { url = "https://files.pythonhosted.org/packages/01/6d/46c2f2ce6751cb26f39293e1ecbf8544cb01321397cd476c2756b98c216d/websockets-16.1.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e09f753a169951eb4f28c2c774f71069304f66e7277e0f5a2892423599cfa854", size = 189868, upload-time = "2026-07-17T22:49:46.581Z" }, + { url = "https://files.pythonhosted.org/packages/29/2b/170a9e8097636cfde4dc3c592b6e00b18a44a2f5407606d96ca542dd5838/websockets-16.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:024193f8551a2b0eafbdd160911012c4e6c228c28430c84433253299a9e42d6a", size = 189059, upload-time = "2026-07-17T22:49:47.972Z" }, + { url = "https://files.pythonhosted.org/packages/a7/48/f0d4ebc9ab4b473b8861b9e20fdb663d515d42f7befdf62cdb60fee7a1ec/websockets-16.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:aabe464bfd13bd25f4821faf111da6fefdc389f870265a53105580e45b0a2e49", size = 187814, upload-time = "2026-07-17T22:49:49.344Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ba/39a41d3ae8e72696a9492581900611c5a91e2b07563b0bcd2523adea9854/websockets-16.1.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a28fcbc9b6baf54a2e23f8655f308e4ccc6afdd7266f8fe7954f320dcda0f785", size = 185229, upload-time = "2026-07-17T22:49:50.787Z" }, + { url = "https://files.pythonhosted.org/packages/3c/36/ac15b604f850d1907f0a85ed721cefe47cd45034b3620069b829746cccbe/websockets-16.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79eace538c6a97e96d0d03d4f9d314f9677f5ed85a8a984992ffd90b13cb8a56", size = 187874, upload-time = "2026-07-17T22:49:52.228Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/3fbd5d71d59299c3770faa5884d4f45070236ca5a35ab3a61830812c409a/websockets-16.1.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:496af849a472b531f758dbd4d61338f5000538cb1a7b3d20d9d32a264517f509", size = 186469, upload-time = "2026-07-17T22:49:53.776Z" }, + { url = "https://files.pythonhosted.org/packages/b4/fc/dd90349bba58af2a53ef2ddd9c32716c81eb6d59a0687939fff561860878/websockets-16.1.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5283810d2646741a0d8da2aa733d6aefa0545809afccb2a5d105a26bc45125f1", size = 188347, upload-time = "2026-07-17T22:49:55.202Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/f73ba86427682da59b78c11d77ba56d5b801c32e84afe79b274bbd6a9bb2/websockets-16.1.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:4e3b680b1e0a27457e727a0d572fd81dffa87b6dbf8b228ab57da64f7d85aead", size = 185903, upload-time = "2026-07-17T22:49:56.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/7c/f95eb20e80104173b3a0a092291f89ea4047ef6e608e0a57ca06eb14eecb/websockets-16.1.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69159730a823dde3ea8d08783e8d47ef135a6d7e8d44eb127e32b321c9db8e3e", size = 186855, upload-time = "2026-07-17T22:49:58.467Z" }, + { url = "https://files.pythonhosted.org/packages/b0/35/dd875b3e050ff232d60fa377707f890e369f74d134f1be32e8f68879747c/websockets-16.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ed5bb271084b46530ee2ddc0410537a9961152c5ccba2fc98c5276d992ccba87", size = 187140, upload-time = "2026-07-17T22:50:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/e8/dc/5cbfcb41824502f6af93b8f3943a4d06c67c23c7d2e31eb18748c4a5b2a7/websockets-16.1.1-cp313-cp313-win32.whl", hash = "sha256:cfb70b4eb56cac4da0a83588f3ad50d46beb0690391082f3d4e2d488c70b68ea", size = 179928, upload-time = "2026-07-17T22:50:01.685Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c1/71e5deb5b7f8f226997ab64908c184ac3105c0155ce2d486f318e5dd08a8/websockets-16.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:d9531d9cbeac99af6f038fb1bc351403531f7d634a2c2e10e2f7c854c6ed5b68", size = 180242, upload-time = "2026-07-17T22:50:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/73/a2/ba78a164eeea4620df4a4df4bd2ed6017438c4655cc0f36f2c0bc0432355/websockets-16.1.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:443aefe96b7fdb132e2a70806cca1f2af49bb3f28e47abcd7c2e9dcf4d8fa1b8", size = 179635, upload-time = "2026-07-17T22:50:05.001Z" }, + { url = "https://files.pythonhosted.org/packages/b9/08/d26d7a7628cd4ac34cbbdb63ac80914ca842ed8e42938c40a53567806df3/websockets-16.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6456ff333092d509127d75a638cb411afae8ff17f092635015d1902efec8a293", size = 177320, upload-time = "2026-07-17T22:50:06.427Z" }, + { url = "https://files.pythonhosted.org/packages/0f/45/ebec83e6269536aa5932533c67b0af5c781f3e73fdbcd68672dcf43f4f44/websockets-16.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fce6c48559c86d1ac3632ccb1bebc7d5442fbe79bd9bb0e40379ee54be2a4051", size = 177544, upload-time = "2026-07-17T22:50:07.834Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d5/abc614d2297f6c1c3e01e61260364457a47c25cc1cf6a879038902bc6aa8/websockets-16.1.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:92b820d345f7a3fc7b8163949ee92df910f290c3fc517b3d5301c78065adafe1", size = 187270, upload-time = "2026-07-17T22:50:09.275Z" }, + { url = "https://files.pythonhosted.org/packages/52/71/4c99af3b87dff1b2927981f6876607d4acb45338c665242168d3982f7758/websockets-16.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a606d9c24035242a3e256e9d5b77ed9cd6bccfcb7cf993e5ca3c0f6f68fb6a7", size = 188509, upload-time = "2026-07-17T22:50:10.722Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b4/5c8ca14b0df7eb84ed0524165c5359150210140817a3312aee57bf62a1cf/websockets-16.1.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:414e596c75f74e0994084694189d7dc9229fb278e33064d6784b73ffbba3ca31", size = 189882, upload-time = "2026-07-17T22:50:12.293Z" }, + { url = "https://files.pythonhosted.org/packages/25/c1/bedfba9e70557129cb8083748d167bdcc01483dedf0f0df143676df05cbe/websockets-16.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:536676848fc5961aca9d20389951f59169508f765637a172403dc5434d722fa0", size = 189114, upload-time = "2026-07-17T22:50:13.789Z" }, + { url = "https://files.pythonhosted.org/packages/df/09/aa835b2787835aebd839114be5de51b797cb480b63ba42b26d34dfe147cb/websockets-16.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:97fd3a0e8b53efa41970ac1dff3d8cf0d2884cadeb4caaf95db7ad1526926ee3", size = 187861, upload-time = "2026-07-17T22:50:15.179Z" }, + { url = "https://files.pythonhosted.org/packages/20/26/f6408330694dbc9830857d9d23bc14ac4f6875127a480cfdda8d5ca21198/websockets-16.1.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7b1b19636af86a3c7995d4d028dbe376f39b4bf31541146f9c123582a6c94562", size = 185286, upload-time = "2026-07-17T22:50:16.741Z" }, + { url = "https://files.pythonhosted.org/packages/17/9a/e0675e70dd8a80762cf35bb18799d3f290a4890ffe6439bc51d222796083/websockets-16.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:41c8e77f17294c0ac18008a7309b99b34ee72247ef10b6dff4c3f8b5ac29896b", size = 187935, upload-time = "2026-07-17T22:50:18.213Z" }, + { url = "https://files.pythonhosted.org/packages/33/c1/3234cfb86afde01b81e9bddcc6e534c440975d60a13991259e833069ab3e/websockets-16.1.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:9f63bcef7f4b02b06b35fc01c93b96c43b5e88e1e8868676caacf493d5a31f3a", size = 186444, upload-time = "2026-07-17T22:50:19.67Z" }, + { url = "https://files.pythonhosted.org/packages/89/87/9c15206e1d778923d8daa9657de07aa62ea815e13448319c98458c37b281/websockets-16.1.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dab9eb87869da2d6ed3af3f3adf28414baae6ec9d4df355ffc18889132f3436c", size = 188409, upload-time = "2026-07-17T22:50:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/cf5de5c67676de2d3eef8b2a518f168f6796595447a5b7161ba0d012915c/websockets-16.1.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:43e3a9fdd7cbf7ba6040c31fae0faf84ca1474fef777c4e37912f1540f854499", size = 185958, upload-time = "2026-07-17T22:50:22.719Z" }, + { url = "https://files.pythonhosted.org/packages/62/c0/731b6ddede2e4136912ec4cff2cffbda35af73546be4762c3d7bd3bd79af/websockets-16.1.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:056ae37939ed7e9974f364f5864e76e49182622d8f9751ac1903c0d09b013985", size = 186911, upload-time = "2026-07-17T22:50:24.108Z" }, + { url = "https://files.pythonhosted.org/packages/8c/7f/39c634472c4469a24a7c09cecddffb08fac6d0e74f73881a94ee8a40a196/websockets-16.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0eadbbf2c30f01efa58e1f110eb6fa293261f6b0b1aa38f7f48707107690af9", size = 187204, upload-time = "2026-07-17T22:50:25.548Z" }, + { url = "https://files.pythonhosted.org/packages/26/89/9667c256c256dafcc62d21328ce7a40067da857969b68ee9af375b0aaf72/websockets-16.1.1-cp314-cp314-win32.whl", hash = "sha256:195c978b065fa40910582464f99d6b15c8b314c68e0546549a55ed83f4735328", size = 179603, upload-time = "2026-07-17T22:50:27.086Z" }, + { url = "https://files.pythonhosted.org/packages/bd/dd/1c099d6c0fc5deb6b46ccdbb6981fdb4b12c917869cb3952408409dc18db/websockets-16.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:4e8d01cc3bcae7bbf8167f944aeafefed590fae5693552bba9794a9df68371cc", size = 179948, upload-time = "2026-07-17T22:50:28.521Z" }, + { url = "https://files.pythonhosted.org/packages/35/25/9956b2d5e0529d5d23924f21bba1440d4c5c88a562e4f08550871ffa97a7/websockets-16.1.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0ffd3031ea8bda8d61762e84220186105ba3b748b3c8da2ae4f7816fac03e573", size = 179963, upload-time = "2026-07-17T22:50:29.982Z" }, + { url = "https://files.pythonhosted.org/packages/17/06/55ffc976c488b6aee9ea05761ff7c4e88e7c1fd82818c8ca7b556ad2f90c/websockets-16.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:84a2cef8deffbd9ab8ee0ea546a2a6a7030c28f44e6cdd4547dbfeb489eb8999", size = 177497, upload-time = "2026-07-17T22:50:31.396Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e8/f7dac2e980bacc92bdc26cebae4ae4d50cae5380732c50980598fc0bbae4/websockets-16.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3df13f73af9b3b38ab1195eb299ecb67a4330c911c97ae04043ff74085728abe", size = 177698, upload-time = "2026-07-17T22:50:32.829Z" }, + { url = "https://files.pythonhosted.org/packages/b2/39/26762f734113e22da2b942c3aca85798e0c0405d64c256549540ff31e5a1/websockets-16.1.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:23253dd5bcae3f9aaee0a1d30967a8dbd52e5d3cff93a2e5b84df57b77d4750d", size = 187561, upload-time = "2026-07-17T22:50:34.24Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/c3f330851806b9b02138b774d593478323e73c99238681b4b93efe64e02d/websockets-16.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c1c5705e314449e3308872fe084b8571ce078ee4fc55a98a769bdefe5917392", size = 188732, upload-time = "2026-07-17T22:50:36.088Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f2/eb2c450f052de334ae33cf200ece6e87b0e14d186807074e4eb1cd2cdea2/websockets-16.1.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69e52d175a0a7d1e13b4b67ad41c560b7d98e8c6f6126eb0bda496c784faf8c7", size = 190872, upload-time = "2026-07-17T22:50:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/70/31/2ac8cecf3a74f7fed9132129fc3d90b3998a1554570c11a69b2a8c20332d/websockets-16.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f79c89b5eb034d1722938a891916582f8f7f503f58ca22518a63c3f2cd18499", size = 189305, upload-time = "2026-07-17T22:50:39.53Z" }, + { url = "https://files.pythonhosted.org/packages/6a/cf/8ab19650d3c0d4562c92e70ab47c257c4aa5c6a713ed87fe63766b31fefc/websockets-16.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39f2a024af5c345ffe8fcf1ee18c049c024c94df393bb09b044a6917c77bde43", size = 188033, upload-time = "2026-07-17T22:50:40.912Z" }, + { url = "https://files.pythonhosted.org/packages/66/d7/a49a38a6127a4acb134fb1912b215d900cc657605cff32445bf519f3acc4/websockets-16.1.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:952303a7318d4cbe1011400839bb2051c9f84fa0a35923267f5daba34b15d458", size = 185748, upload-time = "2026-07-17T22:50:42.559Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/ad1fa40388c7f2e0bb2c7930d0090b6c5498594bd1cdaec18864df3d9e97/websockets-16.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:249116b4a76063d930a46391ad56e135c286e4562a18309029fc2c73f4ed4c62", size = 188285, upload-time = "2026-07-17T22:50:43.974Z" }, + { url = "https://files.pythonhosted.org/packages/35/b8/d5db28ca264b9104f82196f92dc8843e35fd391f763d42e4ad358f5bc97e/websockets-16.1.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:61922544a0587a13fd3f53e4c0e5e606510c7b0d9d22c8444e5fae22a06b38cb", size = 186777, upload-time = "2026-07-17T22:50:45.474Z" }, + { url = "https://files.pythonhosted.org/packages/42/9c/726cb39d0cc43ae848dce4aa2acb04eecc6738b1264ec6d700bf6bcfb9f8/websockets-16.1.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:46dcaa042cd1de6c59e7d9269fa63ff7572b6df40510600b678f0826b3c7af51", size = 188682, upload-time = "2026-07-17T22:50:46.973Z" }, + { url = "https://files.pythonhosted.org/packages/be/c7/1168704de8c2dd483edabe4a22cbe4465dd8be8dd95561d214f9fe092871/websockets-16.1.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:38565aca3e01ea8734e578fb2118dade0ecb0250533f29e22b8d1a7a196cf4d0", size = 186377, upload-time = "2026-07-17T22:50:48.413Z" }, + { url = "https://files.pythonhosted.org/packages/ca/40/f9ff2d630ffce4e7dfea0b2288e1caf9ebbf9ff8a9ec9396136ce8b94935/websockets-16.1.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:42f599f4d48c7e1a3338fdaac3acd075be3b3cf02d4b274f3bf2767aedd3d217", size = 187148, upload-time = "2026-07-17T22:50:49.845Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/e177c8299f78d7cbe2d14df228643c10c70c0e86e108e092056bbcc16e46/websockets-16.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dcc04fedf83effaeb9cce98abc9469bb1b42ef85f03e01c8c1f4438ef7555737", size = 187578, upload-time = "2026-07-17T22:50:51.619Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/b6987faf330f5af5c787a2610124c2e8403d51724f9001ec4fff6311fe7a/websockets-16.1.1-cp314-cp314t-win32.whl", hash = "sha256:8483c2096363120eea8b07c06ae7304d520f686665fffd4811fad423930a65d7", size = 179729, upload-time = "2026-07-17T22:50:53.269Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6e/fbac6ed878dd362fbad7d415fa4f84d38e3e33fed8cde45c64e783acf826/websockets-16.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bcce07e23e5769375158f5efdcdafa8d5cd014b93c6683865b840ed65b96f231", size = 180072, upload-time = "2026-07-17T22:50:54.969Z" }, + { url = "https://files.pythonhosted.org/packages/be/4d/2d0d67834092e354d2b0498f014a41249a89556bc406cf86f3e1557bb463/websockets-16.1.1-py3-none-any.whl", hash = "sha256:6abbd3e82c731c8e531714466acd5d87b5e88ac3243465337ba71d68e23ae7e3", size = 173814, upload-time = "2026-07-17T22:51:04.184Z" }, ] [[package]]